v2.1 — Tout autoriser (finit la tâche sans redemander), reprise durcie, Écosystème+ (nœuds, incidents, actions PM2/logs/git), chat (file d attente, chrono, copie code)
- permission Tout autoriser: autoAllowAll par session, serveur auto-approuve jusqu à la fin - reprise: réouverture auto de la dernière conversation au lancement + reconnexion immédiate au retour au premier plan (visibilitychange/online) - Écosystème: santé des 5 nœuds (charge/RAM/disque/apps), incidents globaux, actions admin par site (restart PM2, logs, git & commits), statut PM2 sur les cartes - chat: file d attente de message pendant exécution, chrono de tâche, bouton copier sur les blocs de code, events system (compact_boundary) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 changed files +460 −56
modified
data/audit.jsonl
+44 −0
@@ -51,3 +51,47 @@ | ||
| 51 | 51 | {"ts":"2026-08-20T02:00:31.512Z","kind":"result","chatId":"352c98e9e9d31975","subtype":"success","cost":0.10990075,"duration_ms":4957,"turns":1} |
| 52 | 52 | {"ts":"2026-08-20T04:16:58.224Z","kind":"prompt","chatId":"21d6c1690bd84a05","project":"crea-ka@M3U96b","node":"M3U96b","dir":"~/apps/crea-ka","model":"fable","permMode":"default","prompt":"Allo"} |
| 53 | 53 | {"ts":"2026-08-20T04:17:05.971Z","kind":"result","chatId":"21d6c1690bd84a05","subtype":"success","cost":0.15040725000000002,"duration_ms":6960,"turns":1} |
| 54 | +{"ts":"2026-08-20T05:07:34.857Z","kind":"prompt","chatId":"790a640fda34ccfe","project":"crea-ka@M3U96b","node":"M3U96b","dir":"~/apps/crea-ka","model":"fable","permMode":"bypass","prompt":"Vérifie avec Bash: (1) apify info fonctionne, (2) $APIFY_TOKEN est défini, (3) liste 3 de nos acteurs Apify via l API. Réponds en 3 lignes courtes."} | |
| 55 | +{"ts":"2026-08-20T05:07:42.291Z","kind":"tool_use","chatId":"790a640fda34ccfe","tool":"Bash","input":"{\"command\":\"apify info 2>&1 | head -5\",\"description\":\"Vérifier l'authentification apify-cli\"}"} | |
| 56 | +{"ts":"2026-08-20T05:07:43.648Z","kind":"tool_use","chatId":"790a640fda34ccfe","tool":"Bash","input":"{\"command\":\"[ -n \\\"$APIFY_TOKEN\\\" ] && echo \\\"APIFY_TOKEN défini (${#APIFY_TOKEN} chars)\\\" || echo \\\"APIFY_TOKEN NON défini\\\"\",\"description\":\"Vérifier que APIFY_TOKEN est défini\"}"} | |
| 57 | +{"ts":"2026-08-20T05:07:45.597Z","kind":"tool_use","chatId":"790a640fda34ccfe","tool":"Bash","input":"{\"command\":\"curl -s \\\"https://api.apify.com/v2/acts?my=1&limit=3\\\" -H \\\"Authorization: Bearer $APIFY_TOKEN\\\" | python3 -c \\\"import json,sys; d=json.load(sys.stdin); print('\\\\n'.join(a['username']+'/'+a['name'] for a in d['data']['items']))\\\"\",\"description\":\"Lister 3 acteurs Apify via l'API\"}"} | |
| 58 | +{"ts":"2026-08-20T05:07:51.494Z","kind":"result","chatId":"790a640fda34ccfe","subtype":"success","cost":0.17530639999999997,"duration_ms":16339,"turns":4} | |
| 59 | +{"ts":"2026-08-20T05:14:27.998Z","kind":"prompt","chatId":"cc2cede3557827a3","project":"resto-ka@M3U96b","node":"M3U96b","dir":"~/apps/resto-ka","model":"fable","permMode":"bypass","prompt":"Vérifie avec Bash que $SERPER_API_KEY est défini, puis fais un appel Serper /places pour \"boulangerie Limoilou Québec\" (gl:ca, hl:fr) et donne le nom du premier résultat. 2 lignes max."} | |
| 60 | +{"ts":"2026-08-20T05:14:35.776Z","kind":"tool_use","chatId":"cc2cede3557827a3","tool":"Bash","input":"{\"command\":\"[ -n \\\"$SERPER_API_KEY\\\" ] && echo \\\"défini\\\" && curl -s -X POST https://google.serper.dev/places -H \\\"X-API-KEY: $SERPER_API_KEY\\\" -H \\\"Content-Type: application/json\\\" -d '{\\\"q\\\":\\\"boulangerie Limoilou Québec\\\",\\\"gl\\\":\\\"ca\\\",\\\"hl\\\":\\\"fr\\\"}' | python3 -c \\\"import json,sys; print(json.load(sys.stdin)['places'][0]['title'])\\\"\",\"description\":\"Vérifier SERPER_API_KEY et interroger Serper /places\"}"} | |
| 61 | +{"ts":"2026-08-20T05:14:42.950Z","kind":"result","chatId":"cc2cede3557827a3","subtype":"success","cost":0.1742392,"duration_ms":14722,"turns":2} | |
| 62 | +{"ts":"2026-08-20T05:15:59.123Z","kind":"login_ok","ip":"2605:b100:162:1acc:6416:9fc4:e074:e856"} | |
| 63 | +{"ts":"2026-08-20T15:33:54.476Z","kind":"prompt","chatId":"aefe0a00a95947de","project":"sorti-ka@M3U96a","node":"M3U96a","dir":"/Users/simon-pierreboucher/apps/sorti-ka","model":"fable","permMode":"bypass","prompt":"Vérifie avec Bash que $TAVILY_API_KEY est défini, puis fais un appel Tavily /search pour \"festivals Québec septembre 2026\" (max_results 3) et donne le titre du premier résultat. 2 lignes max."} | |
| 64 | +{"ts":"2026-08-20T15:34:01.747Z","kind":"tool_use","chatId":"aefe0a00a95947de","tool":"Bash","input":"{\"command\":\"[ -n \\\"$TAVILY_API_KEY\\\" ] && echo \\\"défini\\\" && curl -s -X POST https://api.tavily.com/search -H \\\"Authorization: Bearer $TAVILY_API_KEY\\\" -H \\\"Content-Type: application/json\\\" -d '{\\\"query\\\":\\\"festivals Québec septembre 2026\\\",\\\"max_results\\\":3}' | python3 -c \\\"import json,sys; print(json.load(sys.stdin)['results'][0]['title'])\\\"\",\"description\":\"Vérifie TAVILY_API_KEY et interroge Tavily /search\"}"} | |
| 65 | +{"ts":"2026-08-20T15:34:08.560Z","kind":"result","chatId":"aefe0a00a95947de","subtype":"success","cost":0.43942950000000003,"duration_ms":13814,"turns":2} | |
| 66 | +{"ts":"2026-08-20T15:38:56.522Z","kind":"prompt","chatId":"cb1d74028a79f2ca","project":"fabri-ka@M4M64a","node":"M4M64a","dir":"~/fabri-ka","model":"fable","permMode":"bypass","prompt":"Vérifie avec Bash que $SCRAPFLY_KEY et $BRIGHTDATA_API_KEY sont définis, puis: (1) appelle le endpoint account de Scrapfly et donne le nom du plan, (2) fais une requête Bright Data zone web_unlocker1 sur https://httpbin.org/ip et donne le champ origin. 3 lignes max."} | |
| 67 | +{"ts":"2026-08-20T15:39:02.482Z","kind":"tool_use","chatId":"cb1d74028a79f2ca","tool":"Bash","input":"{\"command\":\"[ -n \\\"$SCRAPFLY_KEY\\\" ] && [ -n \\\"$BRIGHTDATA_API_KEY\\\" ] && echo \\\"both set\\\" || echo \\\"missing\\\"\",\"description\":\"Check both API keys are set\"}"} | |
| 68 | +{"ts":"2026-08-20T15:39:06.974Z","kind":"tool_use","chatId":"cb1d74028a79f2ca","tool":"Bash","input":"{\"command\":\"curl -s \\\"https://api.scrapfly.io/account?key=$SCRAPFLY_KEY\\\" | python3 -c \\\"import json,sys; d=json.load(sys.stdin); print(d.get('subscription',{}).get('plan_name') or d.get('plan') or json.dumps(d)[:300])\\\"\",\"description\":\"Fetch Scrapfly account plan name\"}"} | |
| 69 | +{"ts":"2026-08-20T15:39:08.150Z","kind":"tool_use","chatId":"cb1d74028a79f2ca","tool":"Bash","input":"{\"command\":\"curl -s -X POST https://api.brightdata.com/request -H \\\"Authorization: Bearer $BRIGHTDATA_API_KEY\\\" -H \\\"Content-Type: application/json\\\" -d '{\\\"zone\\\":\\\"web_unlocker1\\\",\\\"url\\\":\\\"https://httpbin.org/ip\\\",\\\"format\\\":\\\"raw\\\"}'\",\"description\":\"Bright Data web_unlocker1 request to httpbin.org/ip\"}"} | |
| 70 | +{"ts":"2026-08-20T15:39:16.616Z","kind":"result","chatId":"cb1d74028a79f2ca","subtype":"success","cost":0.15460305000000002,"duration_ms":19830,"turns":4} | |
| 71 | +{"ts":"2026-08-20T15:45:00.668Z","kind":"prompt","chatId":"dba95f85f96f8d8c","project":"trouve-ka@M2M32","node":"M2M32","dir":"~/trouve-ka","model":"fable","permMode":"bypass","prompt":"Vérifie avec Bash que $OXYLABS_USER et $OXYLABS_PASS sont définis, puis appelle Oxylabs realtime (source google_search, query \"emplois Québec\", geo_location \"Quebec,Canada\", parse true) et donne le titre du premier résultat organique. 2 lignes max."} | |
| 72 | +{"ts":"2026-08-20T15:45:10.544Z","kind":"tool_use","chatId":"dba95f85f96f8d8c","tool":"Bash","input":"{\"command\":\"[ -n \\\"$OXYLABS_USER\\\" ] && [ -n \\\"$OXYLABS_PASS\\\" ] && echo \\\"vars OK\\\" && curl -s https://realtime.oxylabs.io/v1/queries --user \\\"$OXYLABS_USER:$OXYLABS_PASS\\\" -H \\\"Content-Type: application/json\\\" -d '{\\\"source\\\":\\\"google_search\\\",\\\"query\\\":\\\"emplois Québec\\\",\\\"geo_location\\\":\\\"Quebec,Canada\\\",\\\"parse\\\":true}' | python3 -c \\\"import json,sys; d=json.load(sys.stdin); print(d['results'][0]['content']['results']['organic'][0]['title'])\\\"\",\"description\":\"Vérifie les vars Oxylabs et interroge google_search parsé\"}"} | |
| 73 | +{"ts":"2026-08-20T15:45:17.924Z","kind":"result","chatId":"dba95f85f96f8d8c","subtype":"success","cost":0.1776467,"duration_ms":16577,"turns":2} | |
| 74 | +{"ts":"2026-08-20T15:49:36.800Z","kind":"prompt","chatId":"c419eb2ef73e99aa","project":"crea-ka@M3U96b","node":"M3U96b","dir":"~/apps/crea-ka","model":"fable","permMode":"bypass","prompt":"Vérifie avec Bash que les variables OXYLABS_PROXY, OXYLABS_PROXY_USER et OXYLABS_PROXY_PASS sont définies, puis fais une requête via le proxy résidentiel avec ciblage -cc-CA vers https://ip.oxylabs.io/location et donne l IP et le pays obtenus. 2 lignes max."} | |
| 75 | +{"ts":"2026-08-20T15:49:44.688Z","kind":"tool_use","chatId":"c419eb2ef73e99aa","tool":"Bash","input":"{\"command\":\"source ~/.claude/.env 2>/dev/null; echo \\\"PROXY=${OXYLABS_PROXY:+set} USER=${OXYLABS_PROXY_USER:+set} PASS=${OXYLABS_PROXY_PASS:+set}\\\" && curl -s -x \\\"$OXYLABS_PROXY\\\" -U \\\"$OXYLABS_PROXY_USER-cc-CA:$OXYLABS_PROXY_PASS\\\" https://ip.oxylabs.io/location\",\"description\":\"Check Oxylabs proxy vars and fetch IP location via CA residential proxy\"}"} | |
| 76 | +{"ts":"2026-08-20T15:49:50.021Z","kind":"result","chatId":"c419eb2ef73e99aa","subtype":"success","cost":0.1325511,"duration_ms":12415,"turns":2} | |
| 77 | +{"ts":"2026-08-20T15:59:36.034Z","kind":"prompt","chatId":"b159f38d3ca2de8c","project":"sorti-ka@M3U96a","node":"M3U96a","dir":"/Users/simon-pierreboucher/apps/sorti-ka","model":"fable","permMode":"default","prompt":"Fais ces 3 étapes avec l outil Bash, une commande à la fois: (1) date, puis (2) uname -a, puis (3) sw_vers. Ne combine pas, exécute-les séparément. Aucun fichier modifié."} | |
| 78 | +{"ts":"2026-08-20T15:59:42.128Z","kind":"tool_use","chatId":"b159f38d3ca2de8c","tool":"Bash","input":"{\"command\":\"date\",\"description\":\"Afficher la date courante\"}"} | |
| 79 | +{"ts":"2026-08-20T15:59:45.550Z","kind":"tool_use","chatId":"b159f38d3ca2de8c","tool":"Bash","input":"{\"command\":\"uname -a\",\"description\":\"Afficher les informations du noyau\"}"} | |
| 80 | +{"ts":"2026-08-20T15:59:48.860Z","kind":"tool_use","chatId":"b159f38d3ca2de8c","tool":"Bash","input":"{\"command\":\"sw_vers\",\"description\":\"Afficher la version de macOS\"}"} | |
| 81 | +{"ts":"2026-08-20T15:59:48.931Z","kind":"permission_request","chatId":"b159f38d3ca2de8c","tool":"Bash","input":"{\"command\":\"sw_vers\",\"description\":\"Afficher la version de macOS\"}"} | |
| 82 | +{"ts":"2026-08-20T15:59:48.994Z","kind":"permission_response","chatId":"b159f38d3ca2de8c","requestId":"240a8214f4cebf26","allow":true,"always":false} | |
| 83 | +{"ts":"2026-08-20T15:59:53.753Z","kind":"result","chatId":"b159f38d3ca2de8c","subtype":"success","cost":0.7970205,"duration_ms":17454,"turns":4} | |
| 84 | +{"ts":"2026-08-20T16:00:32.721Z","kind":"prompt","chatId":"eccdbc00ed937c41","project":"sorti-ka@M3U96a","node":"M3U96a","dir":"/Users/simon-pierreboucher/apps/sorti-ka","model":"fable","permMode":"default","prompt":"Avec Bash, exécute: whoami. Rien d autre."} | |
| 85 | +{"ts":"2026-08-20T16:00:38.776Z","kind":"tool_use","chatId":"eccdbc00ed937c41","tool":"Bash","input":"{\"command\":\"whoami\",\"description\":\"Show current user\"}"} | |
| 86 | +{"ts":"2026-08-20T16:00:41.774Z","kind":"result","chatId":"eccdbc00ed937c41","subtype":"success","cost":0.424488,"duration_ms":8786,"turns":2} | |
| 87 | +{"ts":"2026-08-20T16:00:42.389Z","kind":"prompt","chatId":"eccdbc00ed937c41","project":"sorti-ka@M3U96a","node":"M3U96a","dir":"/Users/simon-pierreboucher/apps/sorti-ka","model":"fable","permMode":"default","prompt":"Maintenant avec Bash, exécute 2 commandes séparées: hostname puis pwd. Rien d autre."} | |
| 88 | +{"ts":"2026-08-20T16:00:46.253Z","kind":"tool_use","chatId":"eccdbc00ed937c41","tool":"Bash","input":"{\"command\":\"hostname\",\"description\":\"Show hostname\"}"} | |
| 89 | +{"ts":"2026-08-20T16:00:47.247Z","kind":"tool_use","chatId":"eccdbc00ed937c41","tool":"Bash","input":"{\"command\":\"pwd\",\"description\":\"Show current directory\"}"} | |
| 90 | +{"ts":"2026-08-20T16:00:51.569Z","kind":"result","chatId":"eccdbc00ed937c41","subtype":"success","cost":0.108309,"duration_ms":8890,"turns":3} | |
| 91 | +{"ts":"2026-08-20T16:02:05.824Z","kind":"prompt","chatId":"b159f38d3ca2de8c","project":"sorti-ka@M3U96a","node":"M3U96a","dir":"/Users/simon-pierreboucher/apps/sorti-ka","model":"fable","permMode":"default","prompt":"Avec Bash, exécute exactement: touch /tmp/adminka-allowall-proof && echo OK. C est un fichier temporaire, pas de commit."} | |
| 92 | +{"ts":"2026-08-20T16:02:11.064Z","kind":"tool_use","chatId":"b159f38d3ca2de8c","tool":"Bash","input":"{\"command\":\"touch /tmp/adminka-allowall-proof && echo OK\",\"description\":\"Créer un fichier témoin dans /tmp\"}"} | |
| 93 | +{"ts":"2026-08-20T16:02:11.139Z","kind":"permission_auto_all","chatId":"b159f38d3ca2de8c","tool":"Bash"} | |
| 94 | +{"ts":"2026-08-20T16:02:15.143Z","kind":"result","chatId":"b159f38d3ca2de8c","subtype":"success","cost":0.1130845,"duration_ms":9029,"turns":2} | |
| 95 | +{"ts":"2026-08-20T16:02:38.972Z","kind":"prompt","chatId":"07d6fd82a8859fac","project":"sorti-ka@M3U96a","node":"M3U96a","dir":"/Users/simon-pierreboucher/apps/sorti-ka","model":"fable","permMode":"bypass","prompt":"Avec Bash lance en arrière-plan une commande qui écrit 5 lignes espacées de 6s dans /tmp/adminka-reco.log (une boucle), puis réponds FINI. Utilise run_in_background."} | |
| 96 | +{"ts":"2026-08-20T16:02:44.643Z","kind":"tool_use","chatId":"07d6fd82a8859fac","tool":"Bash","input":"{\"command\":\"for i in 1 2 3 4 5; do echo \\\"ligne $i $(date '+%H:%M:%S')\\\" >> /tmp/adminka-reco.log; [ $i -lt 5 ] && sleep 6; done\",\"description\":\"Write 5 lines to /tmp/adminka-reco.log every 6s\",\"run_in_background\":true}"} | |
| 97 | +{"ts":"2026-08-20T16:02:48.377Z","kind":"result","chatId":"07d6fd82a8859fac","subtype":"success","cost":0.46677100000000005,"duration_ms":9133,"turns":2} | |
modified
data/backend.log
+6 −0
@@ -8,3 +8,9 @@ admin-ka v2 sur port 3300 (dual-stack) | ||
| 8 | 8 | admin-ka v2 sur port 3300 (dual-stack) |
| 9 | 9 | admin-ka v2 sur port 3300 (dual-stack) |
| 10 | 10 | admin-ka v2 sur port 3300 (dual-stack) |
| 11 | +admin-ka v2 sur port 3300 (dual-stack) | |
| 12 | +admin-ka v2 sur port 3300 (dual-stack) | |
| 13 | +admin-ka v2 sur port 3300 (dual-stack) | |
| 14 | +admin-ka v2 sur port 3300 (dual-stack) | |
| 15 | +admin-ka v2 sur port 3300 (dual-stack) | |
| 16 | +admin-ka v2 sur port 3300 (dual-stack) | |
modified
data/chats.json
+1 −21
@@ -1,21 +1 @@ | ||
| 1 | −[ | |
| 2 | − { | |
| 3 | − "id": "21d6c1690bd84a05", | |
| 4 | − "title": "Allo", | |
| 5 | − "projectId": "crea-ka@M3U96b", | |
| 6 | − "projectName": "Créa·Ka", | |
| 7 | − "node": "M3U96b", | |
| 8 | − "dir": "~/apps/crea-ka", | |
| 9 | − "model": "fable", | |
| 10 | − "permMode": "default", | |
| 11 | − "claudeSessionId": "1a808aed-e9c3-4913-bef8-698d375e8789", | |
| 12 | − "state": "idle", | |
| 13 | − "totalCost": 0.15040725000000002, | |
| 14 | − "turns": 1, | |
| 15 | − "contextTokens": 39717, | |
| 16 | − "alwaysAllow": [], | |
| 17 | − "created": 1787199418217, | |
| 18 | − "updated": 1787199426469, | |
| 19 | − "lastModel": "claude-fable-5" | |
| 20 | − } | |
| 21 | −] | |
| \ No newline at end of file | ||
| 1 | +[] | |
| \ No newline at end of file | ||
modified
data/eco.sqlite
+0 −0
Binary file not shown.
added
data/mcp-b159f38d3ca2de8c.json
+1 −0
@@ -0,0 +1 @@ | ||
| 1 | +{"mcpServers":{"adminka":{"command":"/opt/homebrew/bin/node","args":["/Users/simon-pierreboucher/apps/admin-ka/server/perm-mcp.js"],"env":{"ADMIN_KA_PERM_URL":"http://127.0.0.1:3300/internal/perm","ADMIN_KA_PERM_TOKEN":"07f87c6a31356a9ca3aa6dd69c3afe6a"}}}} | |
| \ No newline at end of file | ||
added
data/mcp-eccdbc00ed937c41.json
+1 −0
@@ -0,0 +1 @@ | ||
| 1 | +{"mcpServers":{"adminka":{"command":"/opt/homebrew/bin/node","args":["/Users/simon-pierreboucher/apps/admin-ka/server/perm-mcp.js"],"env":{"ADMIN_KA_PERM_URL":"http://127.0.0.1:3300/internal/perm","ADMIN_KA_PERM_TOKEN":"376c06a13ce42e5535d6cb34bac4eb74"}}}} | |
| \ No newline at end of file | ||
modified
data/ngrok.log
+105 −0
@@ -329,3 +329,108 @@ t=2026-08-20T01:05:35-0400 lvl=info msg="join connections" obj=join id=67f44373f | ||
| 329 | 329 | t=2026-08-20T01:05:35-0400 lvl=info msg="join connections" obj=join id=4b8ab47d4ea5 l=[::1]:3300 r=70.25.239.68:61603 |
| 330 | 330 | t=2026-08-20T01:05:36-0400 lvl=info msg="join connections" obj=join id=c5fc9e96ae20 l=[::1]:3300 r=70.25.239.68:61607 |
| 331 | 331 | t=2026-08-20T01:05:42-0400 lvl=info msg="join connections" obj=join id=4c93058d9170 l=[::1]:3300 r=70.25.239.68:61603 |
| 332 | +t=2026-08-20T01:07:34-0400 lvl=info msg="join connections" obj=join id=963fcf1e3aac l=[::1]:3300 r=76.70.60.50:60489 | |
| 333 | +t=2026-08-20T01:14:27-0400 lvl=info msg="join connections" obj=join id=e1c4e795d08a l=[::1]:3300 r=76.70.60.50:62628 | |
| 334 | +t=2026-08-20T01:15:45-0400 lvl=info msg="join connections" obj=join id=7f0223ecb825 l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54003 | |
| 335 | +t=2026-08-20T01:15:45-0400 lvl=info msg="join connections" obj=join id=fc30692d7329 l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54003 | |
| 336 | +t=2026-08-20T01:15:45-0400 lvl=info msg="join connections" obj=join id=92f8e9598469 l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54004 | |
| 337 | +t=2026-08-20T01:15:59-0400 lvl=info msg="join connections" obj=join id=4cc87622fda6 l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54003 | |
| 338 | +t=2026-08-20T01:15:59-0400 lvl=info msg="join connections" obj=join id=9b0be2f353df l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54007 | |
| 339 | +t=2026-08-20T01:32:16-0400 lvl=info msg="join connections" obj=join id=ed07fd070246 l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54427 | |
| 340 | +t=2026-08-20T01:36:34-0400 lvl=info msg="join connections" obj=join id=643abb28df0d l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54636 | |
| 341 | +t=2026-08-20T01:36:34-0400 lvl=info msg="join connections" obj=join id=786b7c56f979 l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54636 | |
| 342 | +t=2026-08-20T01:36:34-0400 lvl=info msg="join connections" obj=join id=5e0d51cd69ea l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54639 | |
| 343 | +t=2026-08-20T01:36:34-0400 lvl=info msg="join connections" obj=join id=4c019432f6a4 l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54641 | |
| 344 | +t=2026-08-20T01:37:41-0400 lvl=info msg="join connections" obj=join id=edaae8660071 l=[::1]:3300 r=[2605:b100:162:1acc:6416:9fc4:e074:e856]:54888 | |
| 345 | +t=2026-08-20T02:09:22-0400 lvl=info msg="update available" obj=updater | |
| 346 | +t=2026-08-20T02:24:55-0400 lvl=info msg="join connections" obj=join id=6cb4f8c38359 l=[::1]:3300 r=141.51.110.129:23486 | |
| 347 | +t=2026-08-20T03:33:55-0400 lvl=info msg="join connections" obj=join id=176368f31dd7 l=[::1]:3300 r=141.51.110.129:22910 | |
| 348 | +t=2026-08-20T04:12:39-0400 lvl=info msg="join connections" obj=join id=3709228f83c0 l=[::1]:3300 r=43.155.27.244:35938 | |
| 349 | +t=2026-08-20T04:35:34-0400 lvl=info msg="join connections" obj=join id=d6168ca290f6 l=[::1]:3300 r=43.131.253.14:53098 | |
| 350 | +t=2026-08-20T05:18:18-0400 lvl=info msg="join connections" obj=join id=7201675da6d5 l=[::1]:3300 r=34.222.50.169:60700 | |
| 351 | +t=2026-08-20T05:18:18-0400 lvl=info msg="join connections" obj=join id=a66721d8ab41 l=[::1]:3300 r=34.222.50.169:60714 | |
| 352 | +t=2026-08-20T05:41:47-0400 lvl=info msg="join connections" obj=join id=e44afb16a98e l=[::1]:3300 r=52.38.4.149:37046 | |
| 353 | +t=2026-08-20T05:41:47-0400 lvl=info msg="join connections" obj=join id=525df583147c l=[::1]:3300 r=52.38.4.149:37046 | |
| 354 | +t=2026-08-20T05:41:47-0400 lvl=info msg="join connections" obj=join id=acff3fe57cbf l=[::1]:3300 r=52.38.4.149:37046 | |
| 355 | +t=2026-08-20T05:41:47-0400 lvl=info msg="join connections" obj=join id=aadfc0dbd3c5 l=[::1]:3300 r=52.38.4.149:37046 | |
| 356 | +t=2026-08-20T05:41:47-0400 lvl=info msg="join connections" obj=join id=efe2ebc1edc7 l=[::1]:3300 r=52.38.4.149:37046 | |
| 357 | +t=2026-08-20T05:41:55-0400 lvl=info msg="join connections" obj=join id=da21eecc42fa l=[::1]:3300 r=52.38.4.149:53036 | |
| 358 | +t=2026-08-20T05:41:55-0400 lvl=info msg="join connections" obj=join id=609bd3e52dc2 l=[::1]:3300 r=52.38.4.149:53036 | |
| 359 | +t=2026-08-20T05:41:55-0400 lvl=info msg="join connections" obj=join id=0bda978ec83b l=[::1]:3300 r=52.38.4.149:53036 | |
| 360 | +t=2026-08-20T05:41:55-0400 lvl=info msg="join connections" obj=join id=41ad65727158 l=[::1]:3300 r=52.38.4.149:53036 | |
| 361 | +t=2026-08-20T05:41:55-0400 lvl=info msg="join connections" obj=join id=4f1134e3094f l=[::1]:3300 r=52.38.4.149:53036 | |
| 362 | +t=2026-08-20T05:49:48-0400 lvl=info msg="join connections" obj=join id=9f3b8f7a2872 l=[::1]:3300 r=202.78.167.217:46554 | |
| 363 | +t=2026-08-20T05:52:37-0400 lvl=info msg="join connections" obj=join id=c6f7dbcef5b3 l=[::1]:3300 r=141.51.110.129:12547 | |
| 364 | +t=2026-08-20T06:06:45-0400 lvl=info msg="join connections" obj=join id=33f2190cdfcb l=[::1]:3300 r=49.51.204.74:51658 | |
| 365 | +t=2026-08-20T06:25:57-0400 lvl=info msg="join connections" obj=join id=5902b96e7b79 l=[::1]:3300 r=170.106.15.3:42822 | |
| 366 | +t=2026-08-20T07:32:10-0400 lvl=info msg="join connections" obj=join id=a4fcd45ae7ba l=[::1]:3300 r=[2a02:c206:3015:63::1]:51030 | |
| 367 | +t=2026-08-20T08:02:34-0400 lvl=info msg="join connections" obj=join id=3cb291192f35 l=[::1]:3300 r=43.165.7.135:59840 | |
| 368 | +t=2026-08-20T08:09:22-0400 lvl=info msg="update available" obj=updater | |
| 369 | +t=2026-08-20T09:06:25-0400 lvl=info msg="join connections" obj=join id=6cc6cac91d64 l=[::1]:3300 r=43.135.134.127:45356 | |
| 370 | +t=2026-08-20T10:18:55-0400 lvl=info msg="join connections" obj=join id=f53b29d57fe3 l=[::1]:3300 r=43.161.234.148:38794 | |
| 371 | +t=2026-08-20T11:02:56-0400 lvl=info msg="join connections" obj=join id=741c4420d406 l=[::1]:3300 r=141.51.110.129:11172 | |
| 372 | +t=2026-08-20T11:07:07-0400 lvl=info msg="join connections" obj=join id=23758722dfdf l=[::1]:3300 r=124.156.226.179:51488 | |
| 373 | +t=2026-08-20T11:09:23-0400 lvl=info msg="join connections" obj=join id=0a69abc46fc6 l=[::1]:3300 r=[2605:b100:160:d77:8c8f:747d:426e:54a5]:62728 | |
| 374 | +t=2026-08-20T11:09:23-0400 lvl=info msg="join connections" obj=join id=fed84df813fb l=[::1]:3300 r=[2605:b100:160:d77:8c8f:747d:426e:54a5]:62729 | |
| 375 | +t=2026-08-20T11:19:52-0400 lvl=info msg="join connections" obj=join id=e33a403762e0 l=[::1]:3300 r=136.117.63.81:53415 | |
| 376 | +t=2026-08-20T11:30:05-0400 lvl=info msg="join connections" obj=join id=beb7e179d056 l=[::1]:3300 r=70.25.239.68:62671 | |
| 377 | +t=2026-08-20T11:30:05-0400 lvl=info msg="join connections" obj=join id=0c54d5122e32 l=[::1]:3300 r=70.25.239.68:62671 | |
| 378 | +t=2026-08-20T11:30:05-0400 lvl=info msg="join connections" obj=join id=4975b64a3402 l=[::1]:3300 r=70.25.239.68:62671 | |
| 379 | +t=2026-08-20T11:30:05-0400 lvl=info msg="join connections" obj=join id=034799ef7bb9 l=[::1]:3300 r=70.25.239.68:62671 | |
| 380 | +t=2026-08-20T11:30:05-0400 lvl=info msg="join connections" obj=join id=973d918ff7f8 l=[::1]:3300 r=70.25.239.68:62678 | |
| 381 | +t=2026-08-20T11:33:54-0400 lvl=info msg="join connections" obj=join id=193e0e84b1db l=[::1]:3300 r=76.70.60.50:60299 | |
| 382 | +t=2026-08-20T11:38:56-0400 lvl=info msg="join connections" obj=join id=3d8feffe3e5f l=[::1]:3300 r=76.70.60.50:60793 | |
| 383 | +t=2026-08-20T11:45:00-0400 lvl=info msg="join connections" obj=join id=5c08ba3b912b l=[::1]:3300 r=76.70.60.50:61672 | |
| 384 | +t=2026-08-20T11:49:36-0400 lvl=info msg="join connections" obj=join id=a20b8dc12aff l=[::1]:3300 r=76.70.60.50:62510 | |
| 385 | +t=2026-08-20T11:50:43-0400 lvl=info msg="join connections" obj=join id=7d2ba1d363a0 l=[::1]:3300 r=70.25.239.68:63178 | |
| 386 | +t=2026-08-20T11:50:43-0400 lvl=info msg="join connections" obj=join id=b098394e1c2a l=[::1]:3300 r=70.25.239.68:63178 | |
| 387 | +t=2026-08-20T11:50:43-0400 lvl=info msg="join connections" obj=join id=940c40218a43 l=[::1]:3300 r=70.25.239.68:63180 | |
| 388 | +t=2026-08-20T11:53:24-0400 lvl=info msg="join connections" obj=join id=1131cea62849 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58053 | |
| 389 | +t=2026-08-20T11:53:24-0400 lvl=info msg="join connections" obj=join id=6be825f93991 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58053 | |
| 390 | +t=2026-08-20T11:53:24-0400 lvl=info msg="join connections" obj=join id=f67dac1eacde l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58055 | |
| 391 | +t=2026-08-20T11:53:46-0400 lvl=info msg="join connections" obj=join id=1bca1ffa0b80 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58053 | |
| 392 | +t=2026-08-20T11:55:06-0400 lvl=info msg="join connections" obj=join id=e1c9bc9feaac l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58057 | |
| 393 | +t=2026-08-20T11:56:52-0400 lvl=info msg="join connections" obj=join id=87b9f14da523 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58078 | |
| 394 | +t=2026-08-20T11:56:55-0400 lvl=info msg="join connections" obj=join id=1c73492d5fae l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58079 | |
| 395 | +t=2026-08-20T11:57:34-0400 lvl=info msg="join connections" obj=join id=04e452f70339 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58173 | |
| 396 | +t=2026-08-20T11:57:35-0400 lvl=info msg="join connections" obj=join id=b1fab5a1e1c6 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58175 | |
| 397 | +t=2026-08-20T11:59:13-0400 lvl=info msg="join connections" obj=join id=c3b8846a94dd l=[::1]:3300 r=70.25.239.68:50440 | |
| 398 | +t=2026-08-20T11:59:14-0400 lvl=info msg="join connections" obj=join id=1b2e5b73f86a l=[::1]:3300 r=70.25.239.68:50441 | |
| 399 | +t=2026-08-20T11:59:14-0400 lvl=info msg="join connections" obj=join id=decfdd901f51 l=[::1]:3300 r=70.25.239.68:50442 | |
| 400 | +t=2026-08-20T11:59:17-0400 lvl=info msg="join connections" obj=join id=5cfe556b26f2 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58182 | |
| 401 | +t=2026-08-20T11:59:20-0400 lvl=info msg="join connections" obj=join id=c6bfda921bd0 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58183 | |
| 402 | +t=2026-08-20T11:59:20-0400 lvl=info msg="join connections" obj=join id=4e66267eab22 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58183 | |
| 403 | +t=2026-08-20T11:59:21-0400 lvl=info msg="join connections" obj=join id=c631373f363e l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58185 | |
| 404 | +t=2026-08-20T11:59:35-0400 lvl=info msg="join connections" obj=join id=817475b80f16 l=[::1]:3300 r=76.70.60.50:63549 | |
| 405 | +t=2026-08-20T12:00:32-0400 lvl=info msg="join connections" obj=join id=74187b8310d3 l=[::1]:3300 r=76.70.60.50:63700 | |
| 406 | +t=2026-08-20T12:00:39-0400 lvl=info msg="join connections" obj=join id=e9ce07abf09c l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58183 | |
| 407 | +t=2026-08-20T12:00:50-0400 lvl=info msg="join connections" obj=join id=91297ffb0f09 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58183 | |
| 408 | +t=2026-08-20T12:00:50-0400 lvl=info msg="join connections" obj=join id=320d829c02f5 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58183 | |
| 409 | +t=2026-08-20T12:00:50-0400 lvl=info msg="join connections" obj=join id=fa25ed350244 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58192 | |
| 410 | +t=2026-08-20T12:01:23-0400 lvl=info msg="join connections" obj=join id=3ce9d66858b2 l=[::1]:3300 r=70.25.239.68:50545 | |
| 411 | +t=2026-08-20T12:02:05-0400 lvl=info msg="join connections" obj=join id=3e56ec195b5a l=[::1]:3300 r=70.25.239.68:50548 | |
| 412 | +t=2026-08-20T12:02:05-0400 lvl=info msg="join connections" obj=join id=a8100d9daade l=[::1]:3300 r=76.70.60.50:63943 | |
| 413 | +t=2026-08-20T12:02:12-0400 lvl=info msg="join connections" obj=join id=eee240bdcaed l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58223 | |
| 414 | +t=2026-08-20T12:02:19-0400 lvl=info msg="join connections" obj=join id=75c000c933a7 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58224 | |
| 415 | +t=2026-08-20T12:02:20-0400 lvl=info msg="join connections" obj=join id=d27403284af1 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58224 | |
| 416 | +t=2026-08-20T12:02:20-0400 lvl=info msg="join connections" obj=join id=4bdd39afe33f l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58224 | |
| 417 | +t=2026-08-20T12:02:20-0400 lvl=info msg="join connections" obj=join id=8e9554bc41a4 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58224 | |
| 418 | +t=2026-08-20T12:02:20-0400 lvl=info msg="join connections" obj=join id=48e1f998c221 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58224 | |
| 419 | +t=2026-08-20T12:02:35-0400 lvl=info msg="join connections" obj=join id=be391ba9ce46 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58240 | |
| 420 | +t=2026-08-20T12:02:36-0400 lvl=info msg="join connections" obj=join id=e79689c744fb l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58240 | |
| 421 | +t=2026-08-20T12:02:38-0400 lvl=info msg="join connections" obj=join id=74bb0cc99c4e l=[::1]:3300 r=76.70.60.50:64022 | |
| 422 | +t=2026-08-20T12:03:00-0400 lvl=info msg="join connections" obj=join id=f46bf2e5ea60 l=[::1]:3300 r=76.70.60.50:64110 | |
| 423 | +t=2026-08-20T12:03:24-0400 lvl=info msg="join connections" obj=join id=b58b2d4839ef l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58244 | |
| 424 | +t=2026-08-20T12:03:25-0400 lvl=info msg="join connections" obj=join id=71f5393d27e0 l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58246 | |
| 425 | +t=2026-08-20T12:03:26-0400 lvl=info msg="join connections" obj=join id=66b1f7d13cab l=[::1]:3300 r=[2605:b100:163:7e01:f4e9:12d7:4243:3876]:58248 | |
| 426 | +t=2026-08-20T12:08:21-0400 lvl=info msg="join connections" obj=join id=f282fb8bb05d l=[::1]:3300 r=70.25.239.68:50666 | |
| 427 | +t=2026-08-20T12:08:21-0400 lvl=info msg="join connections" obj=join id=40f086da7408 l=[::1]:3300 r=70.25.239.68:50667 | |
| 428 | +t=2026-08-20T12:08:21-0400 lvl=info msg="join connections" obj=join id=ec0fae51f04f l=[::1]:3300 r=70.25.239.68:50668 | |
| 429 | +t=2026-08-20T12:08:22-0400 lvl=info msg="join connections" obj=join id=e369df499e44 l=[::1]:3300 r=70.25.239.68:50669 | |
| 430 | +t=2026-08-20T12:08:22-0400 lvl=info msg="join connections" obj=join id=fc496a7edacb l=[::1]:3300 r=70.25.239.68:50670 | |
| 431 | +t=2026-08-20T12:08:22-0400 lvl=info msg="join connections" obj=join id=e7242ee00ba0 l=[::1]:3300 r=70.25.239.68:50671 | |
| 432 | +t=2026-08-20T12:08:22-0400 lvl=info msg="join connections" obj=join id=a2a4d597e1c7 l=[::1]:3300 r=70.25.239.68:50672 | |
| 433 | +t=2026-08-20T12:08:22-0400 lvl=info msg="join connections" obj=join id=9b56f3c84285 l=[::1]:3300 r=70.25.239.68:50673 | |
| 434 | +t=2026-08-20T12:08:22-0400 lvl=info msg="join connections" obj=join id=1dc4b1daa5a3 l=[::1]:3300 r=70.25.239.68:50674 | |
| 435 | +t=2026-08-20T12:08:22-0400 lvl=info msg="join connections" obj=join id=47ecb4682671 l=[::1]:3300 r=70.25.239.68:50675 | |
| 436 | +t=2026-08-20T12:08:23-0400 lvl=info msg="join connections" obj=join id=d98a9d87f7c1 l=[::1]:3300 r=70.25.239.68:50676 | |
modified
data/sessions.json
+1 −1
@@ -1 +1 @@ | ||
| 1 | −{"d08eba390f7e302448b3ad0904841b180b7a97c4be547185d6b383f341f5b651":{"created":1787184586590,"last":1787184586861},"bf96e101ba362b5964767484aaf148081bf85dbaefafb6396a346223f3f4bbfc":{"created":1787184858159,"last":1787191223674},"1729725f9065ea67c047e062f2dce440e44660763199c5cd81f614c1bb669069":{"created":1787185142512,"last":1787185142512},"58389ecc05a5ef3d63f28c27d46026a1184e275de3ddc9ae375dfc7f6564e191":{"created":1787185142681,"last":1787191476301},"3dffbd143a2cc043fde94e4a5b9c93a395f09d814797c04cabd1be8832d2e062":{"created":1787185194996,"last":1787199741938}} | |
| \ No newline at end of file | ||
| 1 | +{"d08eba390f7e302448b3ad0904841b180b7a97c4be547185d6b383f341f5b651":{"created":1787184586590,"last":1787184586861},"bf96e101ba362b5964767484aaf148081bf85dbaefafb6396a346223f3f4bbfc":{"created":1787184858159,"last":1787191223674},"1729725f9065ea67c047e062f2dce440e44660763199c5cd81f614c1bb669069":{"created":1787185142512,"last":1787185142512},"58389ecc05a5ef3d63f28c27d46026a1184e275de3ddc9ae375dfc7f6564e191":{"created":1787185142681,"last":1787241725744},"a16eb7773a82288c6c8bd32753d0bfe305ad653ad98f940b2c0b73471a32c21f":{"created":1787202959122,"last":1787202959122}} | |
| \ No newline at end of file | ||
deleted
data/transcripts/21d6c1690bd84a05.jsonl
+0 −8
@@ -1,8 +0,0 @@ | ||
| 1 | −{"type":"user_prompt","text":"Allo","ts":1787199418219} | |
| 2 | −{"type":"status","state":"running"} | |
| 3 | −{"type":"claude","event":{"type":"system","subtype":"init","cwd":"/Users/simon-pierreboucher/apps/crea-ka","session_id":"1a808aed-e9c3-4913-bef8-698d375e8789","tools":["Task","AskUserQuestion","Bash","CronCreate","CronDelete","CronList","Edit","EnterPlanMode","EnterWorktree","ExitPlanMode","ExitWorktree","Glob","Grep","Monitor","NotebookEdit","PushNotification","Read","RemoteTrigger","ScheduleWakeup","Skill","TaskOutput","TaskStop","TodoWrite","ToolSearch","WebFetch","WebSearch","Write"],"mcp_servers":[{"name":"adminka","status":"connected"}],"model":"claude-fable-5","permissionMode":"default","slash_commands":["update-config","debug","simplify","batch","fewer-permission-prompts","loop","schedule","claude-api","clear","compact","context","cost","heapdump","init","review","security-review","insights","team-onboarding"],"apiKeySource":"ANTHROPIC_API_KEY","claude_code_version":"2.1.117","output_style":"default","agents":["Explore","general-purpose","Plan","statusline-setup"],"skills":["update-config","debug","simplify","batch","fewer-permission-prompts","loop","schedule","claude-api"],"plugins":[],"uuid":"b6371561-ada7-4736-b07c-7b339cef27d3","memory_paths":{"auto":"/Users/simon-pierreboucher/.claude/projects/-Users-simon-pierreboucher-apps-crea-ka/memory/"},"fast_mode_state":"off"}} | |
| 4 | −{"type":"claude","event":{"type":"system","subtype":"status","status":"requesting","uuid":"5033ddbf-fdac-42fd-a18c-31ad87c78c4c","session_id":"1a808aed-e9c3-4913-bef8-698d375e8789"}} | |
| 5 | −{"type":"claude","event":{"type":"assistant","message":{"model":"claude-fable-5","id":"msg_011CeDMWHgBmF1HqDGXubBtY","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"CAIS3gIKpAEIEBgCKkC316Sy+OTuBVYoIgZuUsMrkEVoa7m9pMW1lNwVODFFdod5/ftcpvPEqsCL8FVYp9gBbIpKVoQv+R1IPV/XOzx5Mg5jbGF1ZGUtZmFibGUtNTgBQgh0aGlua2luZ1okNmZjYzhmODMtMWY5Yy00ZDMzLWJhMDctNGQ0ZGYzN2Q2N2Y1chD0eFf7D17nbHkCPBxNVtIaiAEBqAHA/5nUBhIMqQ1Wc1+Fi07SJqJPGgweovy1bvKmD82eK4kiMCls4n81u7qJv7VenCk7jjVpqwOuXKGKPgGJIkHOZBKD5TowDl6I/OOrgUcz9YmepCpngnb/GcFbt+wvRpxTEzn+1XQhTPEY0A/6FUJ3JZT3+G+byJpqUktUocjsPEsHzUKLIxEaGLUzUNPOdzloQDeVK41Gsny4hXrdGlCKuTYWss/rmacTS5LqawikGziOBbkasMoGb/auaxgB"}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":39715,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":39715,"ephemeral_1h_input_tokens":0},"output_tokens":2,"service_tier":"standard","inference_geo":"global"},"context_management":null},"parent_tool_use_id":null,"session_id":"1a808aed-e9c3-4913-bef8-698d375e8789","uuid":"16e03e27-d007-4bab-987e-b27f34c9adb9"}} | |
| 6 | −{"type":"claude","event":{"type":"assistant","message":{"model":"claude-fable-5","id":"msg_011CeDMWHgBmF1HqDGXubBtY","type":"message","role":"assistant","content":[{"type":"text","text":"Allo ! Je suis sur M3U96b, dans le repo **crea-ka** (`~/apps/crea-ka`). Qu'est-ce que je peux faire pour toi ?"}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":39715,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":39715,"ephemeral_1h_input_tokens":0},"output_tokens":2,"service_tier":"standard","inference_geo":"global"},"context_management":null},"parent_tool_use_id":null,"session_id":"1a808aed-e9c3-4913-bef8-698d375e8789","uuid":"9e733460-f754-49d5-b4bf-04ab1d28c2fe"}} | |
| 7 | −{"type":"claude","event":{"type":"result","subtype":"success","is_error":false,"api_error_status":null,"duration_ms":6960,"duration_api_ms":6935,"num_turns":1,"result":"Allo ! Je suis sur M3U96b, dans le repo **crea-ka** (`~/apps/crea-ka`). Qu'est-ce que je peux faire pour toi ?","stop_reason":"end_turn","session_id":"1a808aed-e9c3-4913-bef8-698d375e8789","total_cost_usd":0.15040725000000002,"usage":{"input_tokens":2,"cache_creation_input_tokens":39715,"cache_read_input_tokens":0,"output_tokens":98,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":39715},"inference_geo":"","iterations":[{"input_tokens":2,"output_tokens":98,"cache_read_input_tokens":0,"cache_creation_input_tokens":39715,"cache_creation":{"ephemeral_5m_input_tokens":39715,"ephemeral_1h_input_tokens":0},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-fable-5":{"inputTokens":2,"outputTokens":98,"cacheReadInputTokens":0,"cacheCreationInputTokens":39715,"webSearchRequests":0,"costUSD":0.15040725000000002,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"029e0311-11f4-4729-a3a1-6022a5eff3c2"}} | |
| 8 | −{"type":"status","state":"idle"} | |
modified
public/app.js
+175 −22
@@ -92,6 +92,18 @@ | ||
| 92 | 92 | container.innerHTML = marked.parse(text || ''); |
| 93 | 93 | container.querySelectorAll('pre code').forEach((b) => { try { hljs.highlightElement(b); } catch {} }); |
| 94 | 94 | } |
| 95 | + // bouton copier sur tous les blocs de code (délégué, marche pour tout <pre>) | |
| 96 | + messagesEl.addEventListener('click', (e) => { | |
| 97 | + const pre = e.target.closest('pre'); | |
| 98 | + if (!pre || e.target.closest('button') || pre.dataset.copying) return; | |
| 99 | + if (window.getSelection().toString()) return; | |
| 100 | + navigator.clipboard.writeText(pre.textContent).then(() => { | |
| 101 | + pre.dataset.copying = '1'; | |
| 102 | + const tag = el('span', 'copied-tag', 'copié ✓'); | |
| 103 | + pre.appendChild(tag); | |
| 104 | + setTimeout(() => { tag.remove(); delete pre.dataset.copying; }, 1200); | |
| 105 | + }).catch(() => {}); | |
| 106 | + }); | |
| 95 | 107 | function truncPre(text, limit = 3000) { |
| 96 | 108 | const pre = el('pre'); |
| 97 | 109 | const full = String(text ?? ''); |
@@ -105,6 +117,7 @@ | ||
| 105 | 117 | } |
| 106 | 118 | |
| 107 | 119 | function workingEl() { return messagesEl.querySelector('.working'); } |
| 120 | + let workTimer = null, workStart = 0; | |
| 108 | 121 | function setRunning(r) { |
| 109 | 122 | isRunning = r; |
| 110 | 123 | sendBtn.classList.toggle('hidden', r); |
@@ -112,11 +125,30 @@ | ||
| 112 | 125 | let w = workingEl(); |
| 113 | 126 | if (r && !w) { |
| 114 | 127 | $('welcome').classList.add('hidden'); |
| 115 | − w = el('div', 'working', '<div class="dots"><i></i><i></i><i></i></div><span>Claude Code travaille…</span>'); | |
| 128 | + w = el('div', 'working', '<div class="dots"><i></i><i></i><i></i></div><span>Claude Code travaille…</span><b class="work-time"></b>'); | |
| 116 | 129 | messagesEl.appendChild(w); |
| 117 | 130 | scrollBottom(true); |
| 118 | − } else if (!r && w) w.remove(); | |
| 119 | − if (!r) { finalizeLive(); finalizeThink(); } | |
| 131 | + workStart = Date.now(); | |
| 132 | + clearInterval(workTimer); | |
| 133 | + workTimer = setInterval(() => { | |
| 134 | + const t = w.querySelector('.work-time'); | |
| 135 | + if (!t) { clearInterval(workTimer); return; } | |
| 136 | + const s = Math.floor((Date.now() - workStart) / 1000); | |
| 137 | + t.textContent = s >= 60 ? Math.floor(s / 60) + ' min ' + (s % 60) + ' s' : s + ' s'; | |
| 138 | + }, 1000); | |
| 139 | + } else if (!r && w) { w.remove(); clearInterval(workTimer); } | |
| 140 | + if (!r) { | |
| 141 | + finalizeLive(); finalizeThink(); | |
| 142 | + flushQueued(); | |
| 143 | + } | |
| 144 | + } | |
| 145 | + // file d'attente : un message tapé pendant l'exécution part dès que Claude a fini | |
| 146 | + let queuedPrompt = null; | |
| 147 | + function flushQueued() { | |
| 148 | + if (!queuedPrompt) return; | |
| 149 | + const q = queuedPrompt; queuedPrompt = null; | |
| 150 | + const n = messagesEl.querySelector('.queued-notice'); if (n) n.remove(); | |
| 151 | + doSend(q); | |
| 120 | 152 | } |
| 121 | 153 | |
| 122 | 154 | // ---------- streaming live ---------- |
@@ -241,11 +273,18 @@ | ||
| 241 | 273 | const allow = el('button', 'perm-allow', isPlan ? 'Approuver' : 'Autoriser'); |
| 242 | 274 | actions.append(deny, allow); |
| 243 | 275 | if (!isPlan) { |
| 244 | − const always = el('button', 'perm-always', 'Toujours'); | |
| 276 | + const always = el('button', 'perm-always', 'Toujours<br><small>cet outil</small>'); | |
| 245 | 277 | always.onclick = () => send({ type: 'perm', requestId: ev.requestId, allow: true, always: true }); |
| 246 | 278 | actions.insertBefore(always, allow); |
| 279 | + // « Tout autoriser » : le serveur approuve TOUTES les permissions de la | |
| 280 | + // session — la tâche va au bout même téléphone rangé | |
| 281 | + const all = el('button', 'perm-allall', '🔓 Tout autoriser — finir la tâche sans redemander'); | |
| 282 | + all.onclick = () => send({ type: 'perm', requestId: ev.requestId, allow: true, allowAll: true }); | |
| 283 | + card.appendChild(actions); | |
| 284 | + card.appendChild(all); | |
| 285 | + } else { | |
| 286 | + card.appendChild(actions); | |
| 247 | 287 | } |
| 248 | − card.appendChild(actions); | |
| 249 | 288 | deny.onclick = () => send({ type: 'perm', requestId: ev.requestId, allow: false }); |
| 250 | 289 | allow.onclick = () => send({ type: 'perm', requestId: ev.requestId, allow: true }); |
| 251 | 290 | addMsg(card); |
@@ -258,6 +297,8 @@ | ||
| 258 | 297 | if (!card) return; |
| 259 | 298 | const actions = card.querySelector('.perm-actions'); |
| 260 | 299 | if (actions) actions.remove(); |
| 300 | + const allBtn = card.querySelector('.perm-allall'); | |
| 301 | + if (allBtn) allBtn.remove(); | |
| 261 | 302 | card.appendChild(el('div', 'perm-resolved', |
| 262 | 303 | ev.allow ? ('✓ Autorisé' + (ev.always ? ' (toujours)' : '')) : ('✗ Refusé' + (ev.reason === 'timeout' ? ' (délai dépassé)' : '')))); |
| 263 | 304 | } |
@@ -279,8 +320,14 @@ | ||
| 279 | 320 | } |
| 280 | 321 | |
| 281 | 322 | function renderClaude(ev) { |
| 282 | − if (ev.type === 'system' && ev.subtype === 'init') { | |
| 283 | − addMsg(el('div', 'meta-line', esc(`⌁ ${ev.model || ''} · session ${String(ev.session_id || '').slice(0, 8)} · ${(ev.cwd || '').replace(/^\/Users\/[^/]+\//, '~/')}`))); | |
| 323 | + if (ev.type === 'system') { | |
| 324 | + if (ev.subtype === 'init') { | |
| 325 | + addMsg(el('div', 'meta-line', esc(`⌁ ${ev.model || ''} · session ${String(ev.session_id || '').slice(0, 8)} · ${(ev.cwd || '').replace(/^\/Users\/[^/]+\//, '~/')}`))); | |
| 326 | + } else if (ev.subtype === 'compact_boundary') { | |
| 327 | + addMsg(el('div', 'meta-line', '⇣ contexte compacté')); | |
| 328 | + } else if (ev.subtype && ev.subtype !== 'hook') { | |
| 329 | + addMsg(el('div', 'meta-line', esc('⌁ ' + ev.subtype))); | |
| 330 | + } | |
| 284 | 331 | return; |
| 285 | 332 | } |
| 286 | 333 | if (ev.type === 'stream_event' && ev.event) { |
@@ -347,10 +394,10 @@ | ||
| 347 | 394 | const meta = $('sessMeta'); |
| 348 | 395 | if (currentChat && (currentChat.claudeSessionId || currentChat.totalCost)) { |
| 349 | 396 | meta.classList.remove('hidden'); |
| 350 | − $('metaModel').textContent = currentChat.lastModel || MODEL_LABEL[model]; | |
| 397 | + $('metaModel').textContent = (currentChat.autoAllowAll ? '🔓 ' : '') + (currentChat.lastModel || MODEL_LABEL[model]); | |
| 351 | 398 | $('metaSid').textContent = currentChat.claudeSessionId ? 'sess ' + currentChat.claudeSessionId.slice(0, 8) : ''; |
| 352 | 399 | $('metaCost').textContent = fmt$(currentChat.totalCost); |
| 353 | − $('metaCtx').textContent = currentChat.contextTokens ? 'ctx ' + fmtTok(currentChat.contextTokens) + ' tok' : ''; | |
| 400 | + $('metaCtx').textContent = currentChat.contextTokens ? 'ctx ' + fmtTok(currentChat.contextTokens) + ' tok (' + Math.min(99, Math.round(currentChat.contextTokens / 2000)) + '%)' : ''; | |
| 354 | 401 | } else meta.classList.add('hidden'); |
| 355 | 402 | } |
| 356 | 403 | |
@@ -383,14 +430,16 @@ | ||
| 383 | 430 | handleServer(msg); |
| 384 | 431 | }; |
| 385 | 432 | } |
| 433 | + function rememberChat(id) { settings.lastChatId = id; saveStore(); } | |
| 386 | 434 | function handleServer(msg) { |
| 387 | − if (msg.type === 'chat_created') { currentChat = msg.chat; refreshChips(); return; } | |
| 435 | + if (msg.type === 'chat_created') { currentChat = msg.chat; rememberChat(msg.chat.id); refreshChips(); return; } | |
| 388 | 436 | if (msg.type === 'chat_meta') { |
| 389 | 437 | if (currentChat && msg.chat.id === currentChat.id) { currentChat = msg.chat; refreshChips(); } |
| 390 | 438 | return; |
| 391 | 439 | } |
| 392 | 440 | if (msg.type === 'history') { |
| 393 | 441 | currentChat = msg.chat; |
| 442 | + rememberChat(msg.chat.id); | |
| 394 | 443 | clearConversation(true); |
| 395 | 444 | for (const ev of msg.events) { try { renderEvent(ev); } catch {} } |
| 396 | 445 | setRunning(msg.running); |
@@ -441,9 +490,19 @@ | ||
| 441 | 490 | } |
| 442 | 491 | |
| 443 | 492 | // ---------- composer ---------- |
| 493 | + function doSend(text) { | |
| 494 | + send({ | |
| 495 | + type: 'start', | |
| 496 | + chatId: currentChat ? currentChat.id : null, | |
| 497 | + project: projectSel.value, | |
| 498 | + model: currentChat ? currentChat.model : settings.model, | |
| 499 | + permMode: currentChat ? currentChat.permMode : settings.permMode, | |
| 500 | + prompt: text, | |
| 501 | + }); | |
| 502 | + } | |
| 444 | 503 | function sendPrompt() { |
| 445 | 504 | const text = inputEl.value.trim(); |
| 446 | − if (!text || isRunning) return; | |
| 505 | + if (!text) return; | |
| 447 | 506 | // commandes slash locales |
| 448 | 507 | if (text === '/clear') { if (currentChat) send({ type: 'clear', chatId: currentChat.id }); inputEl.value = ''; autoGrow(); return; } |
| 449 | 508 | if (text === '/cost') { |
@@ -454,14 +513,16 @@ | ||
| 454 | 513 | inputEl.value = ''; |
| 455 | 514 | autoGrow(); |
| 456 | 515 | hideSlash(); |
| 457 | − send({ | |
| 458 | − type: 'start', | |
| 459 | − chatId: currentChat ? currentChat.id : null, | |
| 460 | − project: projectSel.value, | |
| 461 | − model: currentChat ? currentChat.model : settings.model, | |
| 462 | − permMode: currentChat ? currentChat.permMode : settings.permMode, | |
| 463 | − prompt: text, | |
| 464 | − }); | |
| 516 | + if (isRunning) { | |
| 517 | + // Claude travaille : le message part automatiquement dès la fin du tour | |
| 518 | + queuedPrompt = queuedPrompt ? queuedPrompt + '\n' + text : text; | |
| 519 | + let n = messagesEl.querySelector('.queued-notice'); | |
| 520 | + if (!n) { n = el('div', 'notice-line queued-notice'); messagesEl.appendChild(n); } | |
| 521 | + n.textContent = '⏳ en attente de la fin du tour : « ' + queuedPrompt.slice(0, 80) + ' »'; | |
| 522 | + scrollBottom(true); | |
| 523 | + return; | |
| 524 | + } | |
| 525 | + doSend(text); | |
| 465 | 526 | } |
| 466 | 527 | sendBtn.onclick = sendPrompt; |
| 467 | 528 | stopBtn.onclick = () => { if (currentChat) send({ type: 'interrupt', chatId: currentChat.id }); }; |
@@ -544,6 +605,8 @@ | ||
| 544 | 605 | }; |
| 545 | 606 | function newChat() { |
| 546 | 607 | currentChat = null; |
| 608 | + queuedPrompt = null; | |
| 609 | + settings.lastChatId = null; saveStore(); | |
| 547 | 610 | clearConversation(); |
| 548 | 611 | setRunning(false); |
| 549 | 612 | refreshChips(); |
@@ -594,6 +657,24 @@ | ||
| 594 | 657 | } |
| 595 | 658 | $('newSessBtn').onclick = newChat; |
| 596 | 659 | |
| 660 | + // incidents globaux (bouton en tête de l'onglet Écosystème) | |
| 661 | + $('incBtn').onclick = async () => { | |
| 662 | + const r = await fetch('/api/eco/incidents'); | |
| 663 | + if (!r.ok) return; | |
| 664 | + const { incidents } = await r.json(); | |
| 665 | + const label = (app) => (ecoData?.sites.find(s => s.app === app)?.label) || app; | |
| 666 | + openSheet(` | |
| 667 | + <div class="sheet-h"><h3>Incidents</h3></div> | |
| 668 | + <div class="sheet-sec"> | |
| 669 | + <h4>Historique — 50 derniers</h4> | |
| 670 | + ${incidents.length ? incidents.map(i => ` | |
| 671 | + <div class="inc-item"><span class="${i.ended ? 'inc-ok' : 'inc-dot'}">${i.ended ? '✓' : '■'}</span> | |
| 672 | + <span><b>${esc(label(i.site))}</b> · ${new Date(i.started).toLocaleString('fr-CA', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })} | |
| 673 | + ${i.ended ? ' → rétabli en ' + Math.round((i.ended - i.started) / 60000) + ' min' : ' — <b style="color:var(--bad)">EN COURS</b>'} · ${esc(i.reason || '')}</span></div>`).join('') | |
| 674 | + : '<div style="color:var(--muted)">Aucun incident enregistré 🎉</div>'} | |
| 675 | + </div>`); | |
| 676 | + }; | |
| 677 | + | |
| 597 | 678 | // ---------- écosystème ---------- |
| 598 | 679 | function siteStatus(s) { |
| 599 | 680 | if (s.incident || (s.last && !s.last.ok)) return 'down'; |
@@ -622,10 +703,35 @@ | ||
| 622 | 703 | return `<svg class="spark" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none"><path d="${d}" fill="none" stroke="var(--orange-deep)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>${fails}</svg>`; |
| 623 | 704 | } |
| 624 | 705 | async function loadEco() { |
| 625 | − const r = await fetch('/api/eco/summary'); | |
| 706 | + const [r, rn] = await Promise.all([fetch('/api/eco/summary'), fetch('/api/eco/nodes')]); | |
| 626 | 707 | if (!r.ok) return; |
| 627 | 708 | ecoData = await r.json(); |
| 628 | 709 | renderEco(); |
| 710 | + if (rn.ok) renderNodes(await rn.json()); | |
| 711 | + } | |
| 712 | + function nbar(label, val, pct) { | |
| 713 | + const c = pct > 88 ? 'var(--bad)' : pct > 70 ? 'var(--warn)' : 'var(--good)'; | |
| 714 | + return `<div class="nbar"><div class="nbar-l"><span>${label}</span><b>${val}</b></div> | |
| 715 | + <div class="nbar-track"><div class="nbar-fill" style="width:${Math.max(2, Math.min(100, pct))}%;background:${c}"></div></div></div>`; | |
| 716 | + } | |
| 717 | + function renderNodes(data) { | |
| 718 | + const grid = $('ecoNodes'); | |
| 719 | + if (!grid) return; | |
| 720 | + grid.innerHTML = ''; | |
| 721 | + for (const n of data.nodes) { | |
| 722 | + const L = n.last; | |
| 723 | + const memPct = L && L.mem_total ? Math.round(L.mem_used / L.mem_total * 100) : null; | |
| 724 | + const upApps = n.apps.filter(a => a.status === 'online').length; | |
| 725 | + const badApp = n.apps.some(a => a.status && a.status !== 'online'); | |
| 726 | + const card = el('div', 'node-card' + (badApp ? ' warn' : '')); | |
| 727 | + card.innerHTML = ` | |
| 728 | + <div class="node-top"><span class="node-name">${esc(n.node)}</span> | |
| 729 | + <span class="node-apps${badApp ? ' bad' : ''}">${upApps}/${n.apps.length} apps</span></div> | |
| 730 | + ${nbar('charge', L ? L.load1.toFixed(1) : '—', L ? Math.min(100, L.load1 / 32 * 100) : 0)} | |
| 731 | + ${nbar('RAM', memPct !== null ? memPct + '%' : '—', memPct || 0)} | |
| 732 | + ${nbar('disque', L ? L.disk_pct + '%' : '—', L ? L.disk_pct : 0)}`; | |
| 733 | + grid.appendChild(card); | |
| 734 | + } | |
| 629 | 735 | } |
| 630 | 736 | function applyChecks(items) { |
| 631 | 737 | if (!ecoData) return; |
@@ -668,6 +774,7 @@ | ||
| 668 | 774 | <span>up 24h <b>${s.uptime24 ?? '—'}%</b></span> |
| 669 | 775 | <span>p95 <b>${s.p95_24 ?? '—'} ms</b></span> |
| 670 | 776 | <span>${esc(s.node)}</span> |
| 777 | + ${s.proc?.status ? `<span class="${s.proc.status === 'online' ? '' : 'foot-bad'}">pm2 <b>${esc(s.proc.status)}</b></span>` : ''} | |
| 671 | 778 | ${s.certExpires ? `<span>SSL <b>${Math.max(0, Math.round((s.certExpires - Date.now()) / 86400000))} j</b></span>` : ''} |
| 672 | 779 | </div>`; |
| 673 | 780 | card.onclick = () => openSiteSheet(s.app); |
@@ -721,13 +828,45 @@ | ||
| 721 | 828 | ${i.ended ? ' → ' + Math.round((i.ended - i.started) / 60000) + ' min' : ' — EN COURS'} · ${esc(i.reason || '')}</span></div>`).join('')}</div>` : ''} |
| 722 | 829 | ${d.errors.length ? `<div class="sheet-sec"><h4>Erreurs récentes</h4>${d.errors.slice(0, 8).map(e => ` |
| 723 | 830 | <div class="inc-item"><span class="inc-dot">✗</span><span>${new Date(e.ts).toLocaleString('fr-CA', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })} · ${e.code ? 'HTTP ' + e.code : esc(e.err || '')}</span></div>`).join('')}</div>` : ''} |
| 724 | − <button class="btn-cta" style="width:100%" id="shChat">Ouvrir dans le chat →</button> | |
| 831 | + <div class="sheet-sec"> | |
| 832 | + <h4>Actions d'administration</h4> | |
| 833 | + <div class="admin-actions"> | |
| 834 | + <button class="btn-ghost" id="aRestart">↻ Redémarrer PM2</button> | |
| 835 | + <button class="btn-ghost" id="aLogs">📜 Logs récents</button> | |
| 836 | + <button class="btn-ghost" id="aCommits">⌥ Git & commits</button> | |
| 837 | + <a class="btn-ghost" href="https://${esc(d.domain)}" target="_blank" rel="noopener">↗ Ouvrir le site</a> | |
| 838 | + </div> | |
| 839 | + <div id="aOut" class="admin-out hidden"></div> | |
| 840 | + </div> | |
| 841 | + <button class="btn-cta" style="width:100%" id="shChat">💬 Demander à Claude Code →</button> | |
| 725 | 842 | `); |
| 726 | 843 | $('shChat').onclick = () => { |
| 727 | 844 | closeSheet(); |
| 728 | 845 | newChat(); |
| 729 | 846 | for (const o of projectSel.options) if (o.value.startsWith(d.app + '@')) projectSel.value = o.value; |
| 730 | 847 | }; |
| 848 | + const out = () => $('aOut'); | |
| 849 | + const showOut = (html) => { out().classList.remove('hidden'); out().innerHTML = html; }; | |
| 850 | + $('aRestart').onclick = async () => { | |
| 851 | + if (!confirm(`Redémarrer ${d.label} (pm2 restart) sur ${d.node} ?`)) return; | |
| 852 | + showOut('<div class="admin-load">redémarrage…</div>'); | |
| 853 | + const r = await fetch('/api/admin/action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ app: d.app, action: 'restart' }) }); | |
| 854 | + const j = await r.json(); | |
| 855 | + showOut(`<div class="${j.ok ? 'admin-ok' : 'admin-err'}">${j.ok ? '✓ redémarré' : '✗ échec'} — statut : ${esc(j.output || '')}</div>`); | |
| 856 | + if (navigator.vibrate) navigator.vibrate(60); | |
| 857 | + }; | |
| 858 | + $('aLogs').onclick = async () => { | |
| 859 | + showOut('<div class="admin-load">chargement des logs…</div>'); | |
| 860 | + const r = await fetch('/api/admin/logs/' + d.app); | |
| 861 | + const j = await r.json(); | |
| 862 | + showOut(`<pre>${esc((j.logs || '').slice(-4000))}</pre>`); | |
| 863 | + }; | |
| 864 | + $('aCommits').onclick = async () => { | |
| 865 | + showOut('<div class="admin-load">…</div>'); | |
| 866 | + const r = await fetch('/api/admin/commits/' + d.app); | |
| 867 | + const j = await r.json(); | |
| 868 | + showOut(`<pre>${esc(j.text || '')}</pre>`); | |
| 869 | + }; | |
| 731 | 870 | attachChartTips(); |
| 732 | 871 | } |
| 733 | 872 | |
@@ -802,9 +941,20 @@ | ||
| 802 | 941 | |
| 803 | 942 | // rafraîchissement périodique de l'onglet éco (en plus du push) |
| 804 | 943 | setInterval(() => { if (curView === 'eco' && !document.hidden) loadEco(); }, 60000); |
| 944 | + // retour au premier plan (iOS coupe le WS quand l'écran se verrouille) : | |
| 945 | + // reconnexion IMMÉDIATE + resynchronisation de la conversation en cours | |
| 805 | 946 | document.addEventListener('visibilitychange', () => { |
| 806 | − if (!document.hidden && curView === 'eco') loadEco(); | |
| 947 | + if (document.hidden) return; | |
| 948 | + if (!ws || ws.readyState !== 1) { | |
| 949 | + wsRetry = 500; | |
| 950 | + try { ws && ws.close(); } catch {} | |
| 951 | + connect(); | |
| 952 | + } else if (currentChat) { | |
| 953 | + send({ type: 'open', chatId: currentChat.id }); | |
| 954 | + } | |
| 955 | + if (curView === 'eco') loadEco(); | |
| 807 | 956 | }); |
| 957 | + window.addEventListener('online', () => { wsRetry = 500; if (!ws || ws.readyState !== 1) connect(); }); | |
| 808 | 958 | |
| 809 | 959 | // ---------- feuille ---------- |
| 810 | 960 | function openSheet(html) { |
@@ -861,6 +1011,9 @@ | ||
| 861 | 1011 | projectSel.onchange = () => { settings.projectId = projectSel.value; saveStore(); }; |
| 862 | 1012 | refreshChips(); |
| 863 | 1013 | refreshSettingsView(); |
| 1014 | + // reprise automatique : on rouvre la dernière conversation (historique + | |
| 1015 | + // live si une tâche tourne encore) même après fermeture complète de l'app | |
| 1016 | + if (settings.lastChatId) currentChat = { id: settings.lastChatId }; | |
| 864 | 1017 | connect(); |
| 865 | 1018 | } |
| 866 | 1019 | $('loginBtn').onclick = doLogin; |
modified
public/index.html
+6 −1
@@ -90,10 +90,15 @@ | ||
| 90 | 90 | <section id="view-eco" class="view hidden"> |
| 91 | 91 | <div class="view-head"> |
| 92 | 92 | <h2>Écosystème</h2> |
| 93 | − <span id="ecoStamp" class="eco-stamp"></span> | |
| 93 | + <div class="head-actions"> | |
| 94 | + <button id="incBtn" class="chip">Incidents</button> | |
| 95 | + <span id="ecoStamp" class="eco-stamp"></span> | |
| 96 | + </div> | |
| 94 | 97 | </div> |
| 95 | 98 | <div id="ecoOverview" class="eco-overview"></div> |
| 96 | 99 | <div id="ecoGrid" class="eco-grid"><div class="eco-loading">Chargement…</div></div> |
| 100 | + <h3 class="eco-subhead">Nœuds du cluster</h3> | |
| 101 | + <div id="ecoNodes" class="node-grid"></div> | |
| 97 | 102 | </section> |
| 98 | 103 | |
| 99 | 104 | <!-- ======== Vue RÉGLAGES ======== --> |
modified
public/style.css
+49 −1
@@ -328,10 +328,31 @@ details.thinking-box summary { color: var(--muted); font-size: 12.5px; cursor: p | ||
| 328 | 328 | border: 2px solid var(--ink); |
| 329 | 329 | } |
| 330 | 330 | .perm-allow { background: var(--good); color: #fff; box-shadow: var(--bshadow-sm); } |
| 331 | −.perm-always { background: var(--good-soft); color: var(--good); } | |
| 331 | +.perm-always { background: var(--good-soft); color: var(--good); line-height: 1.05; } | |
| 332 | +.perm-always small { font-weight: 600; font-size: 9.5px; opacity: 0.8; } | |
| 332 | 333 | .perm-deny { background: var(--card); color: var(--bad); } |
| 334 | +.perm-allall { | |
| 335 | + width: 100%; margin-top: 8px; padding: 12px; border-radius: 999px; | |
| 336 | + border: 2px solid var(--ink); background: var(--ink); color: var(--orange); | |
| 337 | + font-weight: 800; font-size: 13.5px; cursor: pointer; box-shadow: var(--bshadow-sm); | |
| 338 | +} | |
| 339 | +.perm-allall:active { transform: translate(1px,1px); box-shadow: none; } | |
| 333 | 340 | .perm-resolved { color: var(--muted); font-size: 12.5px; margin-top: 10px; font-weight: 700; } |
| 334 | 341 | |
| 342 | +/* bouton copier sur les blocs de code */ | |
| 343 | +.md pre, .tool-body pre { position: relative; } | |
| 344 | +.copied-tag { | |
| 345 | + position: absolute; top: 6px; right: 8px; | |
| 346 | + background: var(--good); color: #fff; font-size: 10px; font-weight: 800; | |
| 347 | + border-radius: 6px; padding: 2px 7px; font-family: var(--round); | |
| 348 | +} | |
| 349 | + | |
| 350 | +/* file d'attente */ | |
| 351 | +.queued-notice { align-self: center; background: var(--warn-soft); border: 1.5px solid var(--warn); border-radius: 999px; padding: 5px 14px; } | |
| 352 | +.working .work-time { margin-left: 2px; color: var(--orange-deep); font-family: var(--mono); font-size: 12px; } | |
| 353 | + | |
| 354 | +.head-actions { display: flex; align-items: center; gap: 10px; } | |
| 355 | + | |
| 335 | 356 | .working { |
| 336 | 357 | align-self: flex-start; |
| 337 | 358 | display: flex; align-items: center; gap: 10px; |
@@ -466,6 +487,33 @@ details.thinking-box summary { color: var(--muted); font-size: 12.5px; cursor: p | ||
| 466 | 487 | .site-foot { display: flex; gap: 9px; flex-wrap: wrap; font-size: 10.5px; color: var(--muted); font-family: var(--mono); } |
| 467 | 488 | .site-foot b { color: var(--ink); } |
| 468 | 489 | |
| 490 | +.site-foot .foot-bad b { color: var(--bad); } | |
| 491 | + | |
| 492 | +/* nœuds du cluster */ | |
| 493 | +.eco-subhead { max-width: 780px; margin: 20px auto 10px; width: 100%; font-size: 15px; font-weight: 800; letter-spacing: -0.3px; } | |
| 494 | +.node-grid { display: grid; grid-template-columns: 1fr; gap: 10px; max-width: 780px; margin: 0 auto; width: 100%; } | |
| 495 | +@media (min-width: 560px) { .node-grid { grid-template-columns: 1fr 1fr; } } | |
| 496 | +.node-card { background: var(--card); border: 2px solid var(--ink); border-radius: var(--r-lg); box-shadow: var(--bshadow-sm); padding: 12px 14px; } | |
| 497 | +.node-card.warn { border-color: var(--warn); } | |
| 498 | +.node-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 9px; } | |
| 499 | +.node-name { font-weight: 800; font-size: 15px; font-family: var(--mono); } | |
| 500 | +.node-apps { font-size: 11px; font-weight: 800; border: 1.5px solid var(--ink); border-radius: 999px; padding: 2px 9px; background: var(--good-soft); color: var(--good); } | |
| 501 | +.node-apps.bad { background: var(--warn-soft); color: var(--warn); } | |
| 502 | +.nbar { margin: 5px 0; } | |
| 503 | +.nbar-l { display: flex; justify-content: space-between; font-size: 11px; font-weight: 700; color: var(--muted); margin-bottom: 2px; } | |
| 504 | +.nbar-l b { color: var(--ink); font-family: var(--mono); } | |
| 505 | +.nbar-track { height: 7px; background: var(--card2); border: 1.5px solid var(--line); border-radius: 999px; overflow: hidden; } | |
| 506 | +.nbar-fill { height: 100%; border-radius: 999px; transition: width 0.4s; } | |
| 507 | + | |
| 508 | +/* actions d'admin dans la feuille */ | |
| 509 | +.admin-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } | |
| 510 | +.admin-actions .btn-ghost { text-align: center; text-decoration: none; } | |
| 511 | +.admin-out { margin-top: 10px; } | |
| 512 | +.admin-out pre { background: var(--ink); color: #e8e6df; border-radius: var(--r-sm); padding: 10px; font-size: 11px; font-family: var(--mono); overflow-x: auto; max-height: 320px; overflow-y: auto; white-space: pre-wrap; word-break: break-word; margin: 0; } | |
| 513 | +.admin-load { color: var(--muted); font-weight: 700; font-size: 13px; } | |
| 514 | +.admin-ok { color: var(--good); font-weight: 800; } | |
| 515 | +.admin-err { color: var(--bad); font-weight: 800; } | |
| 516 | + | |
| 469 | 517 | /* feuille détail */ |
| 470 | 518 | .sheet { position: fixed; inset: 0; z-index: var(--z-sheet); } |
| 471 | 519 | .sheet-back { position: absolute; inset: 0; background: rgba(26, 22, 17, 0.45); } |
modified
server/monitor.js
+14 −0
@@ -238,3 +238,17 @@ export function ecoSite(app) { | ||
| 238 | 238 | export function ecoIncidents() { |
| 239 | 239 | return db.prepare(`SELECT site,started,ended,reason FROM incidents ORDER BY started DESC LIMIT 50`).all(); |
| 240 | 240 | } |
| 241 | + | |
| 242 | +export function ecoNodes() { | |
| 243 | + const out = []; | |
| 244 | + for (const node of NODES) { | |
| 245 | + const last = db.prepare('SELECT ts,load1,mem_used,mem_total,disk_pct FROM nodestats WHERE node=? ORDER BY ts DESC LIMIT 1').get(node); | |
| 246 | + const hist = db.prepare('SELECT ts,load1,mem_used,mem_total FROM nodestats WHERE node=? AND ts>? ORDER BY ts').all(node, Date.now() - 24 * 3600_000); | |
| 247 | + const apps = SITES.filter(s => s.node === node).map(s => { | |
| 248 | + const p = db.prepare('SELECT cpu,mem,status,restarts FROM appstats WHERE app=? ORDER BY ts DESC LIMIT 1').get(s.app); | |
| 249 | + return { app: s.app, label: s.label, ...p }; | |
| 250 | + }); | |
| 251 | + out.push({ node, last: last || null, hist, apps }); | |
| 252 | + } | |
| 253 | + return { nodes: out, ts: Date.now() }; | |
| 254 | +} | |
modified
server/server.js
+57 −2
@@ -9,7 +9,8 @@ import os from 'node:os'; | ||
| 9 | 9 | import { spawn } from 'node:child_process'; |
| 10 | 10 | import { fileURLToPath } from 'node:url'; |
| 11 | 11 | import { WebSocketServer } from 'ws'; |
| 12 | −import { startMonitor, ecoSummary, ecoSite, ecoIncidents } from './monitor.js'; | |
| 12 | +import { startMonitor, ecoSummary, ecoSite, ecoIncidents, ecoNodes, SITES } from './monitor.js'; | |
| 13 | +import { execFile } from 'node:child_process'; | |
| 13 | 14 | |
| 14 | 15 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 15 | 16 | const APP_DIR = path.join(__dirname, '..'); |
@@ -112,13 +113,14 @@ for (const c of chats) { | ||
| 112 | 113 | if (!c.state) c.state = 'idle'; |
| 113 | 114 | if (c.totalCost === undefined) c.totalCost = 0; |
| 114 | 115 | if (!c.alwaysAllow) c.alwaysAllow = []; |
| 116 | + if (c.autoAllowAll === undefined) c.autoAllowAll = false; | |
| 115 | 117 | } |
| 116 | 118 | function saveChats() { fs.writeFileSync(CHATS_PATH, JSON.stringify(chats, null, 1)); } |
| 117 | 119 | function getChat(id) { return chats.find(c => c.id === id); } |
| 118 | 120 | function publicChat(c) { |
| 119 | 121 | return { |
| 120 | 122 | id: c.id, title: c.title, projectId: c.projectId, projectName: c.projectName, |
| 121 | − model: c.model, permMode: c.permMode, state: c.state, | |
| 123 | + model: c.model, permMode: c.permMode, state: c.state, autoAllowAll: !!c.autoAllowAll, | |
| 122 | 124 | claudeSessionId: c.claudeSessionId, lastModel: c.lastModel || null, |
| 123 | 125 | totalCost: c.totalCost || 0, turns: c.turns || 0, contextTokens: c.contextTokens || 0, |
| 124 | 126 | created: c.created, updated: c.updated, |
@@ -376,6 +378,17 @@ function interrupt(chatId) { | ||
| 376 | 378 | setTimeout(() => { try { st.proc.kill('SIGKILL'); } catch {} }, 5000); |
| 377 | 379 | } |
| 378 | 380 | |
| 381 | +// exécuter un script sur un nœud (local ou ssh) — pour les actions d'admin | |
| 382 | +function runOnNode(node, script) { | |
| 383 | + return new Promise((resolve) => { | |
| 384 | + const full = `export PATH=/opt/homebrew/bin:/usr/local/bin:$PATH; ${script}`; | |
| 385 | + const bin = node === SELF_NODE ? '/bin/bash' : 'ssh'; | |
| 386 | + const args = node === SELF_NODE ? ['-c', full] : ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=12', node, full]; | |
| 387 | + execFile(bin, args, { timeout: 60_000, maxBuffer: 4 * 1024 * 1024, env: { ...process.env, PATH: '/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin', HOME } }, | |
| 388 | + (err, stdout, stderr) => resolve({ stdout: String(stdout || ''), stderr: String(stderr || ''), error: err ? (stderr || err.message) : null })); | |
| 389 | + }); | |
| 390 | +} | |
| 391 | + | |
| 379 | 392 | // ---------- HTTP ---------- |
| 380 | 393 | const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon', '.json': 'application/json', '.woff2': 'font/woff2' }; |
| 381 | 394 | |
@@ -408,6 +421,14 @@ const server = http.createServer(async (req, res) => { | ||
| 408 | 421 | const chat = getChat(chatId); |
| 409 | 422 | const toolKey = permKey(body.tool_name, body.input); |
| 410 | 423 | |
| 424 | + // « Tout autoriser » activé → toute la session est auto-approuvée : la tâche | |
| 425 | + // va au bout sans jamais attendre l'utilisateur | |
| 426 | + if (chat && chat.autoAllowAll) { | |
| 427 | + audit({ kind: 'permission_auto_all', chatId, tool: body.tool_name }); | |
| 428 | + emitAndLog(chatId, { type: 'permission_auto', tool: body.tool_name, key: 'tout autoriser', input: body.input }); | |
| 429 | + return json(res, 200, { behavior: 'allow', updatedInput: body.input || {} }); | |
| 430 | + } | |
| 431 | + | |
| 411 | 432 | // « Toujours autoriser » déjà accordé pour cet outil dans cette conversation |
| 412 | 433 | if (chat && (chat.alwaysAllow || []).includes(toolKey)) { |
| 413 | 434 | audit({ kind: 'permission_auto', chatId, tool: body.tool_name, key: toolKey }); |
@@ -480,10 +501,35 @@ const server = http.createServer(async (req, res) => { | ||
| 480 | 501 | } |
| 481 | 502 | if (p === '/api/eco/summary') return json(res, 200, ecoSummary()); |
| 482 | 503 | if (p === '/api/eco/incidents') return json(res, 200, { incidents: ecoIncidents() }); |
| 504 | + if (p === '/api/eco/nodes') return json(res, 200, ecoNodes()); | |
| 483 | 505 | if (p.startsWith('/api/eco/site/')) { |
| 484 | 506 | const d = ecoSite(p.split('/')[4]); |
| 485 | 507 | return d ? json(res, 200, d) : json(res, 404, { error: 'site inconnu' }); |
| 486 | 508 | } |
| 509 | + | |
| 510 | + // --- actions d'administration par site (pm2 / logs / commits) --- | |
| 511 | + if (p === '/api/admin/action' && req.method === 'POST') { | |
| 512 | + let body; | |
| 513 | + try { body = JSON.parse(await readBody(req)); } catch { return json(res, 400, { error: 'bad json' }); } | |
| 514 | + const site = SITES.find(s => s.app === body.app); | |
| 515 | + const action = ['restart', 'stop', 'start'].includes(body.action) ? body.action : null; | |
| 516 | + if (!site || !action) return json(res, 400, { error: 'app ou action invalide' }); | |
| 517 | + audit({ kind: 'admin_action', app: site.app, node: site.node, action, ip: clientIp(req) }); | |
| 518 | + const out = await runOnNode(site.node, `pm2 ${action} ${site.pm2} && pm2 jlist | python3 -c "import json,sys; l=json.load(sys.stdin); p=[x for x in l if x['name']=='${site.pm2}']; print(p[0]['pm2_env']['status'] if p else 'introuvable')"`); | |
| 519 | + return json(res, 200, { ok: !out.error, output: (out.stdout || out.error || '').trim().split('\n').pop(), full: (out.stdout || '').slice(-1500) }); | |
| 520 | + } | |
| 521 | + if (p.startsWith('/api/admin/logs/')) { | |
| 522 | + const site = SITES.find(s => s.app === p.split('/')[4]); | |
| 523 | + if (!site) return json(res, 404, { error: 'site inconnu' }); | |
| 524 | + const out = await runOnNode(site.node, `pm2 logs ${site.pm2} --nostream --lines 60 2>&1 | tail -70`); | |
| 525 | + return json(res, 200, { logs: out.stdout || out.error || '(vide)' }); | |
| 526 | + } | |
| 527 | + if (p.startsWith('/api/admin/commits/')) { | |
| 528 | + const site = SITES.find(s => s.app === p.split('/')[4]); | |
| 529 | + if (!site) return json(res, 404, { error: 'site inconnu' }); | |
| 530 | + const out = await runOnNode(site.node, `cd ${site.dir} && echo "=== derniers commits ===" && git log --oneline -8 && echo && echo "=== statut ===" && (git status --short | head -10; [ -z "$(git status --short)" ] && echo "propre ✓")`); | |
| 531 | + return json(res, 200, { text: out.stdout || out.error || '(vide)' }); | |
| 532 | + } | |
| 487 | 533 | return json(res, 404, { error: 'not found' }); |
| 488 | 534 | } |
| 489 | 535 | |
@@ -581,6 +627,7 @@ function handleWsMessage(ws, msg) { | ||
| 581 | 627 | if (!chat) return; |
| 582 | 628 | if (MODELS.includes(msg.model)) chat.model = msg.model; |
| 583 | 629 | if (PERM_MODES.includes(msg.permMode)) chat.permMode = msg.permMode; |
| 630 | + if (typeof msg.autoAllowAll === 'boolean') chat.autoAllowAll = msg.autoAllowAll; | |
| 584 | 631 | saveChats(); |
| 585 | 632 | broadcastAll({ type: 'chat_meta', chat: publicChat(chat) }); |
| 586 | 633 | return; |
@@ -601,6 +648,14 @@ function handleWsMessage(ws, msg) { | ||
| 601 | 648 | clearTimeout(pending.timer); |
| 602 | 649 | pendingPerms.delete(msg.requestId); |
| 603 | 650 | const chat = getChat(pending.chatId); |
| 651 | + if (msg.allow && msg.allowAll && chat) { | |
| 652 | + // « Tout autoriser » : plus AUCUNE permission ne sera demandée pour cette | |
| 653 | + // conversation — le serveur approuve tout jusqu'à la fin de la tâche | |
| 654 | + chat.autoAllowAll = true; | |
| 655 | + saveChats(); | |
| 656 | + emitAndLog(chat.id, { type: 'notice', text: '🔓 Tout autoriser activé — la tâche ira au bout sans autre demande de permission.' }); | |
| 657 | + broadcastAll({ type: 'chat_meta', chat: publicChat(chat) }); | |
| 658 | + } | |
| 604 | 659 | if (msg.allow && msg.always && chat) { |
| 605 | 660 | if (!chat.alwaysAllow.includes(pending.toolKey)) chat.alwaysAllow.push(pending.toolKey); |
| 606 | 661 | saveChats(); |
| 607 | 662 | |