Aller au contenu
Hermès Skills
← Retour au catalogue

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

PropertyValue
URLhttps://vikunja.iatuto.com
API Basehttps://vikunja.iatuto.com/api/v1
Docker Containervikunja-h0o40g8kcws8wwoo8kk8o00g
Vikunja CLI (in container)/app/vikunja/vikunja

Users

UsernameRoleEmail
bfnoussMain user (owner)bernynoussi@gmail.com
bfnoussiAlt userglitch-rule-muck@duck.com
hermes-agentAutomation (Hermes)bf-vikunja@localhost

Authentication

Two Auth Methods

Vikunja has two separate auth methods, each accessing different scopes:

MethodUserScopeFormat
API Token (static)bfnoussAll 16 projects (read/write)tk_xxx in HTTP header
JWT (expires)hermes-agentProject 19 onlyFresh login each session

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

IDNameColorPurpose
14Emploi3498dbJob search tasks
15DevOpse74c3cInfrastructure/DevOps
16Web28a745Web projects
17Urgente67e22Time-sensitive items
18Claude9b59b6Tâches déléguées à Claude CLI (créé 2026-05-29)
19Backlog95a5a6Idées, plus tard (créé 2026-05-29)

Views (Project 19)

IDTypeNotes
73ListDefault, filter: done = false
74GanttTimeline view
75TableSpreadsheet-like
76KanbanManual 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

TablePurposeKey Columns
tasksAll tasksid, title, done, project_id, created_by_id
task_bucketsTask ↔ bucket mappingtask_id, bucket_id, project_view_id
bucketsKanban columnsid, title, project_view_id, position
labelsLabelsid, title, hex_color (varchar(6), no #), created_by_id
label_tasksLabel ↔ task mappingtask_id, label_id
projectsProjectsid, title, owner_id
project_viewsViews/boardsid, title, project_id, view_kind (0=list,1=gantt,2=table,3=kanban)
usersUser accountsid, 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_color is varchar(6) — NO hash (#) prefix
  • Labels need BOTH created + updated timestamps (NOT NULL)
  • task_buckets references 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

BucketID
Planifiée55
En cours56
Terminée57
Abandonnée62

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 bfnouss API 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

PropertyValue
URLhttps://vikunja.iatuto.com/caldav
AuthBasic auth
Usernamebfnouss
PasswordAPI 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:

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:

OptionTypeFileStatus
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:N in description → Vikunja tasks
  • State tracking: /home/bf/scripts/sync-state.json (maps task_id ↔ event_id)
  • Dry-run mode: --dry-run flag
  • 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 approaches
  • references/calendar-sync-implementation.md — Concrete implementation details, API patterns, and file locations
  • See also: google-workspace skill for Calendar API details