vikunja
Interagir avec l'instance Vikunja (vikunja.iatuto.com) — authentification, requêtes API, gestion des tâches via CLI Docker
Quand l'utiliser (Trigger)
Déclenchement standard selon le contexte de l'écosystème Hermès.
Mode d'emploi (Usage)
Mode d'emploi standard via l'agent Hermès. Vikunja — Task Management Integration
When to Use
Use this skill whenever the user asks about their tasks, schedule, to-do list, or anything related to Vikunja. This covers:
- “What’s on my schedule / tasks / to-do list?”
- “Add a task / reminder”
- “Mark task X as done”
- “Show me upcoming deadlines”
- Any admin task (user management, token refresh)
Instance Details
| Property | Value |
|---|---|
| URL | https://vikunja.iatuto.com |
| API Base | https://vikunja.iatuto.com/api/v1 |
| Docker Container | vikunja-h0o40g8kcws8wwoo8kk8o00g |
| Vikunja CLI (in container) | /app/vikunja/vikunja |
Users
| Username | Role | |
|---|---|---|
bfnouss | Main user (owner) | bernynoussi@gmail.com |
bfnoussi | Alt user | glitch-rule-muck@duck.com |
hermes-agent | Automation (Hermes) | bf-vikunja@localhost |
Authentication
Two Auth Methods
Vikunja has two separate auth methods, each accessing different scopes:
| Method | User | Scope | Format |
|---|---|---|---|
| API Token (static) | bfnouss | All 16 projects (read/write) | tk_xxx in HTTP header |
| JWT (expires) | hermes-agent | Project 19 only | Fresh login each session |
Method 1: API Token (bfnouss — RECOMMENDED for API calls)
The static API token for user bfnouss is stored at /home/bf/.vikunja_token. It works for all API operations and never expires:
TOKEN=$(cat /home/bf/.vikunja_token)
curl -s -H "Authorization: Bearer *** "https://vikunja.iatuto.com/api/v1/projects"
This token is NOT expired — it works for reading/writing all 16 projects.
Method 2: JWT Login (hermes-agent — only for project 19)
The hermes-agent user’s JWT expires periodically. Login fresh each session:
curl -s 'https://vikunja.iatuto.com/api/v1/login' \
-H 'Content-Type: application/json' \
-d '{"username":"hermes-agent","password":"DwzBo77RmVlf02Cg"}'
Token Handling (Shell Safety)
When using tokens in Python, avoid {token} or {jwt} in f-strings/format strings — the write_file/execute_code tools strip these patterns. Use short variable names:
# CORRECT — short var name, simple concat
t = "tk_..."
auth = "Authorization: Bearer *** + t
# BROKEN — {token} stripped by write_file / execute_code
auth = "Authorization: Bearer *** + token
Task Update Endpoint
Contrairement à la création (PUT), la mise à jour d’une tâche existante utilise POST :
# UPDATE task (POST, not PUT)
curl -s -X POST "https://vikunja.iatuto.com/api/v1/tasks/ID" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"title": "new title", "priority": 3}'
POST /tasks/{id}= mise à jour (retourne 200)PUT /tasks/{id}= 404 (ne fonctionne PAS)PATCH /tasks/{id}= 404- Le bucket_id dans le body API ne fonctionne PAS pour hermes-agent (toujours 0 en retour) — passer par la DB
Project Structure
The main project is Project 19 — “Suivi des Tâches” (Hermes master task list).
Labels
| ID | Name | Color | Purpose |
|---|---|---|---|
| 14 | Emploi | 3498db | Job search tasks |
| 15 | DevOps | e74c3c | Infrastructure/DevOps |
| 16 | Web | 28a745 | Web projects |
| 17 | Urgent | e67e22 | Time-sensitive items |
| 18 | Claude | 9b59b6 | Tâches déléguées à Claude CLI (créé 2026-05-29) |
| 19 | Backlog | 95a5a6 | Idées, plus tard (créé 2026-05-29) |
Views (Project 19)
| ID | Type | Notes |
|---|---|---|
| 73 | List | Default, filter: done = false |
| 74 | Gantt | Timeline view |
| 75 | Table | Spreadsheet-like |
| 76 | Kanban | Manual buckets |
Common Queries
Tasks due on a specific date
curl -s 'https://vikunja.iatuto.com/api/v1/projects/19/tasks?filter_due_date=YYYY-MM-DD&filter_due_date_until=YYYY-MM-DD' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json'
All undone tasks (sorted by due date)
curl -s 'https://vikunja.iatuto.com/api/v1/projects/19/tasks?sort_by=due_date&order_by=asc' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json'
Note: The hermes-agent user’s tasks are in project 19 and require JWT login (expires). The bfnouss API token provides read/write access to all 16 projects and does NOT expire. Use the API token for automation tasks.
Docker CLI Access
For admin operations that the API can’t do (list users, reset passwords, create users):
# List all users
docker exec vikunja-h0o40g8kcws8wwoo8kk8o00g /app/vikunja/vikunja user list
# Create user
docker exec vikunja-h0o40g8kcws8wwoo8kk8o00g /app/vikunja/vikunja user create
# Reset password
docker exec vikunja-h0o40g8kcws8wwoo8kk8o00g /app/vikunja/vikunja user reset-password
Note: The container uses a minimal base image. /bin/sh and basic CLI tools may not be available. Use the vikunja binary directly at /app/vikunja/vikunja.
DB-Level Access
Quand l’API REST ne suffit pas (buckets, labels, permissions), passer par la base PostgreSQL directement.
Connect to Vikunja DB
# Find Vikunja's PostgreSQL container & get credentials
PW=$(docker inspect postgresql-h0o40g8kcws8wwoo8kk8o00g --format '{{json .Config.Env}}' | \
python3 -c "import json,sys;env=json.load(sys.stdin);print([e.split('=')[1] for e in env if e.startswith('POSTGRES_PASSWORD=***](mailto:'POSTGRES_PASSWORD=***)")')
IP=$(docker inspect postgresql-h0o40g8kcws8wwoo8kk8o00g --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
# Write SQL to file and execute (avoids shell escaping)
echo "SELECT * FROM tasks WHERE project_id=19 LIMIT 5;" > /tmp/q.sql
docker cp /tmp/q.sql postgresql-h0o40g8kcws8wwoo8kk8o00g:/tmp/q.sql
docker exec postgresql-h0o40g8kcws8wwoo8kk8o00g psql -U bf -d vikunjadb -f /tmp/q.sql -t -A
Key DB Tables
| Table | Purpose | Key Columns |
|---|---|---|
tasks | All tasks | id, title, done, project_id, created_by_id |
task_buckets | Task ↔ bucket mapping | task_id, bucket_id, project_view_id |
buckets | Kanban columns | id, title, project_view_id, position |
labels | Labels | id, title, hex_color (varchar(6), no #), created_by_id |
label_tasks | Label ↔ task mapping | task_id, label_id |
projects | Projects | id, title, owner_id |
project_views | Views/boards | id, title, project_id, view_kind (0=list,1=gantt,2=table,3=kanban) |
users | User accounts | id, username, email, password |
DB Operations
Create labels (DB bypasses API restrictions):
INSERT INTO labels (id, title, description, hex_color, created_by_id, created, updated)
VALUES (18, 'Claude', 'Tâches déléguées à Claude CLI', '9b59b6', 3, NOW(), NOW())
ON CONFLICT (id) DO NOTHING;
Tag tasks with labels:
INSERT INTO label_tasks (task_id, label_id, created)
SELECT id, 18, NOW() FROM tasks WHERE id IN (176,178,179,180) ON CONFLICT DO NOTHING;
Full structure check (tasks + buckets + labels):
SELECT t.id, t.title, COALESCE(b.title, '-') as bucket,
(SELECT STRING_AGG(l.title, ', ') FROM label_tasks lt2
JOIN labels l ON lt2.label_id = l.id WHERE lt2.task_id = t.id) as labels
FROM tasks t
LEFT JOIN task_buckets tb ON t.id = tb.task_id AND tb.project_view_id = 76
LEFT JOIN buckets b ON tb.bucket_id = b.id
WHERE t.project_id = 19 ORDER BY b.position, t.id;
DB Pitfalls
hex_coloris varchar(6) — NO hash (#) prefix- Labels need BOTH created + updated timestamps (NOT NULL)
task_bucketsreferences project_view_id — for project 19: List(73), Gantt(74), Table(75), Kanban(76)- hermes-agent CANNOT manage buckets via API (404) — use DB or owner account
POST /tasks/{id}updates tasks (NOT PUT) — but bucket_id changes via DB only- Bucket IDs for project 19 Kanban: Planifiée(55), En cours(56), Terminée(57), Abandonnée(62)
Daily Monitoring Cron
Un script ~/.hermes/scripts/quotidien-report.py (no_agent=true) envoie un rapport quotidien à 08:00 via Telegram.
Fonctionnement : Se connecte à la base PostgreSQL directement (docker exec psql) et :
- Compte les tâches par bucket (Planifiée/En cours/Terminée/Abandonnée)
- Compte les tâches taggées Claude
- Repère les échéances urgentes (< 7 jours)
- Repère les tâches stagnantes (> 3 jours sans update)
- Affiche les tâches terminées aujourd’hui
Cron ID: f5a077ef62f3 (no_agent, 0 token LLM consommé)
Référence: Voir le script pour personnaliser les seuils (stagnation=3j, urgent=7j).
Cron Jobs Reference
For reference: the user has these Vikunja-adjacent cron jobs:
- Veille SQ quotidienne (07:00 daily) — Job-based, not API-triggered
- Rappel travail session enquête criminelle (10:00, 20:00 daily) — Telegram
Task Creation (CRITICAL: API Quirks)
HTTP Method: PUT, not POST
Vikunja uses PUT (not POST) to create tasks under a project.
POST returns {"message":"Not Found"} — do NOT use POST.
# CORRECT - Vikunja uses PUT for task creation
curl -s -X PUT "https://vikunja.iatuto.com/api/v1/projects/19/tasks" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"title": "My task", "bucket_id": 55}'
Labels Format: Objects, not bare IDs
# CORRECT - labels as objects
{"labels": [{"id": 15}]}
# WRONG - returns "Unmarshal type error: expected=models.Label, got=number"
{"labels": [15]}
Token {vars} in Python strings — write_file AND execute_code (CRITICAL)
When writing Python code via write_file, heredoc, OR execute_code, the pattern {jwt} or {token} inside a string literal gets stripped — the tool interprets it as a template placeholder. This affects f-strings, %s formatting, and even simple string literals containing {token}.
Workaround: Use simple string concatenation with a SHORT variable name (NOT token, jwt, or any name matching the placeholder regex):
# BROKEN - all of these are stripped by write_file / execute_code
auth = "Authorization: Bearer *** + token # f-string → stripped
auth = "Authorization: Bearer %s" % token # %s with token var → stripped
# CORRECT - short variable name, simple concat
t = json.loads(r.stdout)["token"]
auth = "Authorization: Bearer *** + t # ✅ Works
# ALSO CORRECT - base64 encode the token, decode on use
import base64
b64 = base64.b64encode(t.encode()).decode() # Stored b64-safe
tok = base64.b64decode(b64).decode() # Decoded on use
auth = "Authorization: Bearer *** + tok # ✅ Works
Bucket IDs
| Bucket | ID |
|---|---|
| Planifiée | 55 |
| En cours | 56 |
| Terminée | 57 |
| Abandonnée | 62 |
Monitoring Automatique
Cron Quotidien (08:00)
Script: ~/.hermes/scripts/quotidien-report.py — no_agent, 0 token LLM.
Cron ID: f5a077ef62f3 — Livré sur Telegram.
Connecte la base PostgreSQL directement. Produit : comptes par bucket, tâches Claude, échéances urgentes (< 7j), tâches stagnantes (> 3j sans update).
Cron Hebdomadaire (vendredi 08:00)
Script: ~/.hermes/scripts/hebdo-report.py — no_agent, 0 token LLM.
Cron ID: 5771a5d89237 — Livré sur Telegram.
Rapport de semaine : tâches terminées, nouvelles, statut Claude, taux complétion.
Pitfalls
- ❌ POST for task creation returns Not Found — always use PUT
- ❌ Labels as bare integers returns unmarshal error — always use
[{"id": N}]format - ❌
{token}/{jwt}in Python strings via write_file/execute_code gets stripped — use short var name + concat, never{token}in f-strings - ❌ Minimal container image — no
cat,env, or standard shell tools inside - ❌ Bucket_id api update bypass — hermes-agent ne peut PAS changer le bucket d’une tâche via l’API (POST retourne 200 mais bucket_id reste 0). Doit passer par la base de données.
- ❌ hermes-agent user only sees project 19 — use
bfnoussAPI token for all 16 projects - ⚠️ API token bfnouss (tk_xxx) IS valid — contrary to some docs, this token works for all operations
- ⚠️ CalDAV endpoint = /caldav — NOT /dav, NOT /.well-known/caldav
- ⚠️ Label_tasks duplication — Les labels assignés via DB s’ajoutent aux labels API → doublons
- ⚠️ JWT hermes-agent expires — login fresh each session for hermes-agent user
- ✅ Vikunja returns “Not Found” for inaccessible projects — verify permissions first
CalDAV
Vikunja exposes a CalDAV endpoint for syncing tasks with calendar apps (Apple Calendar, Thunderbird, etc.).
Endpoint
| Property | Value |
|---|---|
| URL | https://vikunja.iatuto.com/caldav |
| Auth | Basic auth |
| Username | bfnouss |
| Password | API token from /home/bf/.vikunja_token |
| Status | ✅ HTTP 200 (verified working) |
IMPORTANT: The correct endpoint is /caldav — NOT /dav or /.well-known/caldav (those return 401 regardless of auth).
Test Connection
cat /home/bf/.vikunja_token | xargs -I {} \
curl -u "bfnouss:{}" -X PROPFIND "https://vikunja.iatuto.com/caldav" \
-H "Depth: 0" -w "\nHTTP %{http_code}\n"
Setup Guide
Full guide with Apple Calendar (macOS + iOS) setup instructions:
- Local:
/home/bf/scripts/caldav-setup-guide.md - Google Drive: Guide-Configuration-CalDAV-Vikunja.md
Limitation
CalDAV only shows tasks with a due_date set. Tasks without a date won’t appear in the calendar.
Vikunja ↔ Google Calendar — Sync (3 Options)
Three independent sync solutions deployed as mutual backups:
| Option | Type | File | Status |
|---|---|---|---|
| A 🥇 | Python cron (5 min) | /home/bf/scripts/vikunja-calendar-sync.py | ✅ Active |
| B 🥈 | n8n workflow JSON | /home/bf/scripts/vikunja-calendar-n8n-workflow.json | ✅ Prêt à importer |
| C 🥉 | CalDAV Apple Calendar | /home/bf/scripts/caldav-setup-guide.md | ✅ Configurable |
Option A: Python Script
Location: /home/bf/scripts/vikunja-calendar-sync.py (541 lines)
Features:
- Forward sync: Vikunja tasks with
due_date→ Google Calendar events - Reverse sync: Google events with
🔁 Vikunja:Nin description → Vikunja tasks - State tracking:
/home/bf/scripts/sync-state.json(maps task_id ↔ event_id) - Dry-run mode:
--dry-runflag - Logging 3 channels: audit
.md+ Google Sheets “Log” + Vikunja project 7 - Cron: every 5 minutes
- Error handling: 10s timeout, 2 retries, lock file anti-concurrence
Option B: n8n Workflow
Location: /home/bf/scripts/vikunja-calendar-n8n-workflow.json
Nodes: Schedule (5min) → HTTP GET Vikunja → Filter → Google Calendar → Sheets log. Import directly in n8n UI.
Option C: CalDAV Apple Calendar
See /home/bf/scripts/caldav-setup-guide.md for macOS and iOS setup instructions. Uses CalDAV endpoint above.
Reference
references/calendar-sync-options.md— Analysis of 3 approachesreferences/calendar-sync-implementation.md— Concrete implementation details, API patterns, and file locations- See also:
google-workspaceskill for Calendar API details