tide-commander 1.111.2 → 1.113.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/assets/{BossLogsModal-DLTh_jKZ.js → BossLogsModal-Bc0DqA9X.js} +1 -1
  2. package/dist/assets/{BossSpawnModal-DzivoRl1.js → BossSpawnModal-D-HI5-Rm.js} +1 -1
  3. package/dist/assets/{ControlsModal-B8j6My0t.js → ControlsModal-CTIbjWJQ.js} +1 -1
  4. package/dist/assets/{DockerLogsModal-BNwx7lVp.js → DockerLogsModal-CZrAeIrY.js} +1 -1
  5. package/dist/assets/{EmbeddedEditor-CBoOoGL8.js → EmbeddedEditor-DeNPLdlN.js} +1 -1
  6. package/dist/assets/{GmailOAuthSetup-BACIjX54.js → GmailOAuthSetup-CVE2MUNk.js} +1 -1
  7. package/dist/assets/{GoogleOAuthSetup-BuASGyWh.js → GoogleOAuthSetup-DPVaJm-J.js} +1 -1
  8. package/dist/assets/{IframeModal-ckpeQpQB.js → IframeModal-D0Njx7nV.js} +1 -1
  9. package/dist/assets/{IntegrationsPanel-Dh7UOnTC.js → IntegrationsPanel-N8GC9dFw.js} +2 -2
  10. package/dist/assets/{LogViewerModal-Cj7Hv-mD.js → LogViewerModal-DXfAOI0h.js} +1 -1
  11. package/dist/assets/{MonitoringModal-Bx4vcjBr.js → MonitoringModal-uCFkCztW.js} +1 -1
  12. package/dist/assets/{PM2LogsModal-RBIjfi4J.js → PM2LogsModal-DGW_92OG.js} +1 -1
  13. package/dist/assets/{RestoreArchivedAreaModal-BBYlYIso.js → RestoreArchivedAreaModal-DWyz3iM2.js} +1 -1
  14. package/dist/assets/{Scene2DCanvas-CYB1Y5Wa.js → Scene2DCanvas-CNpWfkXW.js} +1 -1
  15. package/dist/assets/{SceneManager-6vk047Kd.js → SceneManager-EwY3yHEm.js} +1 -1
  16. package/dist/assets/{SkillsPanel-CRRypqdy.js → SkillsPanel-YTLlXzWr.js} +1 -1
  17. package/dist/assets/{SlackMultiInstanceSetup-DisMBGxX.js → SlackMultiInstanceSetup-bAWpygC_.js} +1 -1
  18. package/dist/assets/{SpawnModal-qiu5nT-F.js → SpawnModal-BWuxUbwF.js} +1 -1
  19. package/dist/assets/StatisticsModal-fsomOWqv.js +1 -0
  20. package/dist/assets/{SubordinateAssignmentModal-AUCAiGkE.js → SubordinateAssignmentModal-Cyhy71yX.js} +1 -1
  21. package/dist/assets/{TriggerManagerPanel-56SuYzNh.js → TriggerManagerPanel-BCDVqT8Q.js} +1 -1
  22. package/dist/assets/{WorkflowEditorPanel-H0IGkyMd.js → WorkflowEditorPanel-Btq2XPt5.js} +1 -1
  23. package/dist/assets/{index-94AqwQn0.js → index-BEm8Kg82.js} +1 -1
  24. package/dist/assets/{index-9NugNkE4.js → index-BPAwPCsE.js} +1 -1
  25. package/dist/assets/{index-DY395tVJ.js → index-C1Y50w20.js} +2 -2
  26. package/dist/assets/{index--3sQ1gHE.js → index-CMa6Bc5_.js} +3 -3
  27. package/dist/assets/{index-C-kx99MP.js → index-Cn-EYfAP.js} +1 -1
  28. package/dist/assets/{index-DFWA70Lq.js → index-DvzFVxNn.js} +1 -1
  29. package/dist/assets/{index-DZP3fZ3b.js → index-Glbq4ytE.js} +1 -1
  30. package/dist/assets/{index-pRI5GcXd.js → index-aMq5H0fp.js} +1 -1
  31. package/dist/assets/{index-Dv0tQnLX.js → index-qAdi3D_H.js} +1 -1
  32. package/dist/assets/main-BEfBoz8e.css +1 -0
  33. package/dist/assets/main-vfMoYhg1.js +214 -0
  34. package/dist/assets/{web-CccJFC0m.js → web-CMiQoHZs.js} +1 -1
  35. package/dist/assets/{web-CdhZGenV.js → web-DN5Nx28H.js} +1 -1
  36. package/dist/assets/{web-RZLwjKCT.js → web-ZMOcyRHy.js} +1 -1
  37. package/dist/index.html +2 -2
  38. package/dist/locales/de/terminal.json +1 -0
  39. package/dist/locales/en/terminal.json +1 -0
  40. package/dist/locales/es/terminal.json +1 -0
  41. package/dist/locales/fr/terminal.json +1 -0
  42. package/dist/locales/hi/terminal.json +1 -0
  43. package/dist/locales/it/terminal.json +1 -0
  44. package/dist/locales/ja/terminal.json +1 -0
  45. package/dist/locales/pt/terminal.json +1 -0
  46. package/dist/locales/ru/terminal.json +1 -0
  47. package/dist/locales/zh-CN/terminal.json +1 -0
  48. package/dist/src/packages/server/claude/backend.js +2 -0
  49. package/dist/src/packages/server/data/builtin-skills/boss-instructions.js +1 -1
  50. package/dist/src/packages/server/routes/agents.js +17 -1
  51. package/dist/src/packages/server/services/claude-usage-service.js +164 -9
  52. package/dist/src/packages/server/services/llm-matcher-service.js +5 -0
  53. package/dist/src/packages/shared/agent-types.js +2 -0
  54. package/package.json +1 -1
  55. package/dist/assets/StatisticsModal-Bzk7qqOd.js +0 -1
  56. package/dist/assets/main-BUSv52j9.css +0 -1
  57. package/dist/assets/main-Bon1sZAi.js +0 -214
@@ -1 +1 @@
1
- import{cn as t}from"./main-Bon1sZAi.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class o extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{o as AppWeb};
1
+ import{co as t}from"./main-vfMoYhg1.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class a extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{a as AppWeb};
@@ -1 +1 @@
1
- import{cn as s}from"./main-Bon1sZAi.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
1
+ import{co as s}from"./main-vfMoYhg1.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
@@ -1 +1 @@
1
- import{cn as a}from"./main-Bon1sZAi.js";import{ImpactStyle as i,NotificationType as r}from"./index-DY395tVJ.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
1
+ import{co as a}from"./main-vfMoYhg1.js";import{ImpactStyle as i,NotificationType as r}from"./index-C1Y50w20.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
package/dist/index.html CHANGED
@@ -22,10 +22,10 @@
22
22
  <link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
23
23
  <link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
24
24
  <title>Tide Commander</title>
25
- <script type="module" crossorigin src="/assets/main-Bon1sZAi.js"></script>
25
+ <script type="module" crossorigin src="/assets/main-vfMoYhg1.js"></script>
26
26
  <link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
27
27
  <link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
28
- <link rel="stylesheet" crossorigin href="/assets/main-BUSv52j9.css">
28
+ <link rel="stylesheet" crossorigin href="/assets/main-BEfBoz8e.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="app"></div>
@@ -229,6 +229,7 @@
229
229
  "byStatus": "Nach Status",
230
230
  "byName": "Nach Name",
231
231
  "searchAgents": "Agenten suchen...",
232
+ "searchArea": "Bereich suchen...",
232
233
  "areas": "Bereiche",
233
234
  "noAgentsDeployed": "Keine Agenten bereitgestellt",
234
235
  "noAgentsMatch": "Keine Agenten passen zu den Filtern",
@@ -249,6 +249,7 @@
249
249
  "byStatus": "By Status",
250
250
  "byName": "By Name",
251
251
  "searchAgents": "Search agents...",
252
+ "searchArea": "Search area...",
252
253
  "areas": "Areas",
253
254
  "noAgentsDeployed": "No agents deployed",
254
255
  "noAgentsMatch": "No agents match filters",
@@ -229,6 +229,7 @@
229
229
  "byStatus": "Por estado",
230
230
  "byName": "Por nombre",
231
231
  "searchAgents": "Buscar agentes...",
232
+ "searchArea": "Buscar área...",
232
233
  "areas": "Areas",
233
234
  "noAgentsDeployed": "Sin agentes desplegados",
234
235
  "noAgentsMatch": "Ningun agente coincide con los filtros",
@@ -229,6 +229,7 @@
229
229
  "byStatus": "Par statut",
230
230
  "byName": "Par nom",
231
231
  "searchAgents": "Rechercher des agents...",
232
+ "searchArea": "Rechercher une zone...",
232
233
  "areas": "Zones",
233
234
  "noAgentsDeployed": "Aucun agent déployé",
234
235
  "noAgentsMatch": "Aucun agent ne correspond aux filtres",
@@ -229,6 +229,7 @@
229
229
  "byStatus": "स्थिति के अनुसार",
230
230
  "byName": "नाम के अनुसार",
231
231
  "searchAgents": "एजेंट खोजें...",
232
+ "searchArea": "क्षेत्र खोजें...",
232
233
  "areas": "क्षेत्र",
233
234
  "noAgentsDeployed": "कोई एजेंट तैनात नहीं",
234
235
  "noAgentsMatch": "कोई एजेंट फ़िल्टर से मेल नहीं खाता",
@@ -229,6 +229,7 @@
229
229
  "byStatus": "Per stato",
230
230
  "byName": "Per nome",
231
231
  "searchAgents": "Cerca agenti...",
232
+ "searchArea": "Cerca area...",
232
233
  "areas": "Aree",
233
234
  "noAgentsDeployed": "Nessun agente distribuito",
234
235
  "noAgentsMatch": "Nessun agente corrisponde ai filtri",
@@ -229,6 +229,7 @@
229
229
  "byStatus": "ステータス別",
230
230
  "byName": "名前別",
231
231
  "searchAgents": "エージェントを検索...",
232
+ "searchArea": "エリアを検索...",
232
233
  "areas": "エリア",
233
234
  "noAgentsDeployed": "配置済みエージェントなし",
234
235
  "noAgentsMatch": "フィルターに一致するエージェントなし",
@@ -229,6 +229,7 @@
229
229
  "byStatus": "Por Status",
230
230
  "byName": "Por Nome",
231
231
  "searchAgents": "Pesquisar agentes...",
232
+ "searchArea": "Pesquisar área...",
232
233
  "areas": "Areas",
233
234
  "noAgentsDeployed": "Nenhum agente implantado",
234
235
  "noAgentsMatch": "Nenhum agente corresponde aos filtros",
@@ -229,6 +229,7 @@
229
229
  "byStatus": "По статусу",
230
230
  "byName": "По имени",
231
231
  "searchAgents": "Поиск агентов...",
232
+ "searchArea": "Поиск области...",
232
233
  "areas": "Зоны",
233
234
  "noAgentsDeployed": "Нет развёрнутых агентов",
234
235
  "noAgentsMatch": "Нет агентов, соответствующих фильтрам",
@@ -229,6 +229,7 @@
229
229
  "byStatus": "按状态",
230
230
  "byName": "按名称",
231
231
  "searchAgents": "搜索代理...",
232
+ "searchArea": "搜索区域...",
232
233
  "areas": "区域",
233
234
  "noAgentsDeployed": "未部署代理",
234
235
  "noAgentsMatch": "没有匹配的代理",
@@ -168,6 +168,8 @@ export class ClaudeBackend {
168
168
  cliModel = 'claude-opus-4-7';
169
169
  else if (config.model === 'claude-opus-4-8[1m]')
170
170
  cliModel = 'claude-opus-4-8';
171
+ else if (config.model === 'claude-fable-5[1m]')
172
+ cliModel = 'claude-fable-5';
171
173
  args.push('--model', cliModel);
172
174
  }
173
175
  // Reasoning effort level
@@ -427,7 +427,7 @@ You can ONLY spawn new agents when the user EXPLICITLY requests it.
427
427
  "name": "<Agent Name>",
428
428
  "class": "<agent class slug>",
429
429
  "cwd": "<optional working directory>",
430
- "model": "<optional: claude-opus-4-8[1m] | opus[1m] | claude-opus-4-8 | claude-opus-4-7 | claude-sonnet-4-6 | claude-haiku-4-5 | etc.>",
430
+ "model": "<optional: claude-fable-5[1m] | claude-fable-5 | claude-opus-4-8[1m] | opus[1m] | claude-opus-4-8 | claude-opus-4-7 | claude-sonnet-4-6 | claude-haiku-4-5 | etc.>",
431
431
  "effort": "<optional: low | medium | high | xHigh | max>",
432
432
  "initialSkillIds": ["<optional skill-id-1>", "<skill-id-2>"],
433
433
  "provider": "<optional: claude | codex | opencode>",
@@ -19,7 +19,7 @@ import { buildCustomAgentConfig } from '../websocket/handlers/command-handler.js
19
19
  import { clearDelegation, getBossForSubordinate } from '../websocket/handlers/boss-response-handler.js';
20
20
  import { OpencodeBackend } from '../opencode/backend.js';
21
21
  import { getSystemPrompt, setSystemPrompt, clearSystemPrompt, isEchoPromptEnabled, setEchoPromptEnabled, getCodexBinaryPath, setCodexBinaryPath, isTmuxModeEnabled, setTmuxModeEnabled } from '../services/system-prompt-service.js';
22
- import { buildClaudeUsageByAgentSummary, buildClaudeUsageSnapshot } from '../services/claude-usage-service.js';
22
+ import { buildClaudeUsageByAgentSummary, buildClaudeUsageByDaySummary, buildClaudeUsageSnapshot } from '../services/claude-usage-service.js';
23
23
  import { getBackupStatus, setBackupEnabled } from '../services/backup-service.js';
24
24
  const log = createLogger('Routes');
25
25
  const router = Router();
@@ -292,6 +292,22 @@ router.get('/usage-by-agent', async (req, res) => {
292
292
  res.status(500).json({ error: err?.message ?? 'Failed to build usage summary' });
293
293
  }
294
294
  });
295
+ // GET /api/agents/usage-by-day - Claude JSONL usage totals grouped by local day
296
+ router.get('/usage-by-day', async (req, res) => {
297
+ try {
298
+ const agents = agentService.getAllAgents();
299
+ const summary = await buildClaudeUsageByDaySummary(agents, {
300
+ since: req.query.since,
301
+ until: req.query.until,
302
+ days: req.query.days,
303
+ });
304
+ res.json(summary);
305
+ }
306
+ catch (err) {
307
+ log.error(' Failed to build usage-by-day summary:', err);
308
+ res.status(500).json({ error: err?.message ?? 'Failed to build daily usage summary' });
309
+ }
310
+ });
295
311
  // GET /api/agents/:id/process-output - Get `witr --pid` output for this agent process
296
312
  router.get('/:id/process-output', async (req, res) => {
297
313
  try {
@@ -42,6 +42,8 @@ function toPositiveInteger(value) {
42
42
  : 0;
43
43
  }
44
44
  function parseBoundary(value) {
45
+ if (typeof value === 'number' && Number.isFinite(value))
46
+ return value;
45
47
  if (typeof value !== 'string' || value.trim() === '')
46
48
  return null;
47
49
  const asNumber = Number(value);
@@ -154,21 +156,76 @@ function addUsage(acc, requestId, timestamp, usage) {
154
156
  acc.lastTimestamp = timestamp;
155
157
  }
156
158
  }
159
+ function createTokenTotals() {
160
+ return {
161
+ input: 0,
162
+ cacheCreation: 0,
163
+ cacheRead: 0,
164
+ output: 0,
165
+ total: 0,
166
+ };
167
+ }
157
168
  function createAccumulator() {
158
169
  return {
159
170
  requestIds: new Set(),
160
171
  firstTimestamp: null,
161
172
  lastTimestamp: null,
162
- tokens: {
163
- input: 0,
164
- cacheCreation: 0,
165
- cacheRead: 0,
166
- output: 0,
167
- total: 0,
168
- },
173
+ tokens: createTokenTotals(),
169
174
  };
170
175
  }
171
- async function scanFileIntoAccumulator(filePath, acc, sinceMs, untilMs) {
176
+ function createDailyAccumulator() {
177
+ return {
178
+ requestIds: new Set(),
179
+ buckets: new Map(),
180
+ };
181
+ }
182
+ function addTokenTotals(target, source) {
183
+ target.input += source.input;
184
+ target.cacheCreation += source.cacheCreation;
185
+ target.cacheRead += source.cacheRead;
186
+ target.output += source.output;
187
+ target.total += source.total;
188
+ }
189
+ function localDateKey(timestampMs) {
190
+ const d = new Date(timestampMs);
191
+ const yyyy = d.getFullYear();
192
+ const mm = String(d.getMonth() + 1).padStart(2, '0');
193
+ const dd = String(d.getDate()).padStart(2, '0');
194
+ return `${yyyy}-${mm}-${dd}`;
195
+ }
196
+ function startOfLocalDay(timestampMs) {
197
+ const d = new Date(timestampMs);
198
+ d.setHours(0, 0, 0, 0);
199
+ return d.getTime();
200
+ }
201
+ function addUsageToDailyAccumulator(acc, requestId, timestamp, usage) {
202
+ if (acc.requestIds.has(requestId))
203
+ return;
204
+ const timestampMs = Date.parse(timestamp);
205
+ if (!Number.isFinite(timestampMs))
206
+ return;
207
+ const input = toPositiveInteger(usage.input_tokens);
208
+ const cacheCreation = toPositiveInteger(usage.cache_creation_input_tokens);
209
+ const cacheRead = toPositiveInteger(usage.cache_read_input_tokens);
210
+ const output = toPositiveInteger(usage.output_tokens);
211
+ const total = input + cacheCreation + cacheRead + output;
212
+ if (total <= 0)
213
+ return;
214
+ acc.requestIds.add(requestId);
215
+ const date = localDateKey(timestampMs);
216
+ const bucket = acc.buckets.get(date) ?? {
217
+ requestCount: 0,
218
+ tokens: createTokenTotals(),
219
+ };
220
+ bucket.requestCount += 1;
221
+ bucket.tokens.input += input;
222
+ bucket.tokens.cacheCreation += cacheCreation;
223
+ bucket.tokens.cacheRead += cacheRead;
224
+ bucket.tokens.output += output;
225
+ bucket.tokens.total += total;
226
+ acc.buckets.set(date, bucket);
227
+ }
228
+ async function scanFileUsageRecords(filePath, sinceMs, untilMs, onRecord) {
172
229
  // Fast-path: if `since` is set and the file hasn't been touched since then,
173
230
  // no line in it can be within the window — skip the read entirely. Safe
174
231
  // because Claude appends to these files; mtime tracks the latest line.
@@ -195,9 +252,19 @@ async function scanFileIntoAccumulator(filePath, acc, sinceMs, untilMs) {
195
252
  continue;
196
253
  if (untilMs !== null && timestampMs > untilMs)
197
254
  continue;
198
- addUsage(acc, parsed.requestId, parsed.timestamp, parsed.usage);
255
+ onRecord(parsed.requestId, parsed.timestamp, parsed.usage);
199
256
  }
200
257
  }
258
+ async function scanFileIntoAccumulator(filePath, acc, sinceMs, untilMs) {
259
+ await scanFileUsageRecords(filePath, sinceMs, untilMs, (requestId, timestamp, usage) => {
260
+ addUsage(acc, requestId, timestamp, usage);
261
+ });
262
+ }
263
+ async function scanFileIntoDailyAccumulator(filePath, acc, sinceMs, untilMs) {
264
+ await scanFileUsageRecords(filePath, sinceMs, untilMs, (requestId, timestamp, usage) => {
265
+ addUsageToDailyAccumulator(acc, requestId, timestamp, usage);
266
+ });
267
+ }
201
268
  function todayString() {
202
269
  const d = new Date();
203
270
  const yyyy = d.getFullYear();
@@ -293,3 +360,91 @@ export async function buildClaudeUsageByAgentSummary(agents, opts = {}) {
293
360
  entries,
294
361
  };
295
362
  }
363
+ function parseDays(value) {
364
+ const parsed = typeof value === 'string' ? Number(value) : value;
365
+ if (typeof parsed !== 'number' || !Number.isFinite(parsed))
366
+ return 14;
367
+ return Math.max(2, Math.min(60, Math.round(parsed)));
368
+ }
369
+ function buildDayKeys(sinceMs, untilMs) {
370
+ const keys = [];
371
+ const untilDay = startOfLocalDay(untilMs);
372
+ for (let cursor = startOfLocalDay(sinceMs); cursor <= untilDay; cursor += 24 * 60 * 60 * 1000) {
373
+ keys.push(localDateKey(cursor));
374
+ }
375
+ return keys;
376
+ }
377
+ export async function buildClaudeUsageByDaySummary(agents, opts = {}) {
378
+ const untilMs = parseBoundary(opts.until) ?? Date.now();
379
+ const sinceMs = parseBoundary(opts.since) ?? (startOfLocalDay(untilMs) - ((parseDays(opts.days) - 1) * 24 * 60 * 60 * 1000));
380
+ const agentBuckets = new Map();
381
+ for (const agent of agents) {
382
+ if (agent.provider !== 'claude' || !agent.sessionId)
383
+ continue;
384
+ const paths = findClaudeSessionFile(agent);
385
+ if (!paths)
386
+ continue;
387
+ try {
388
+ const mainAcc = createDailyAccumulator();
389
+ await scanFileIntoDailyAccumulator(paths.main, mainAcc, sinceMs, untilMs);
390
+ const subAcc = createDailyAccumulator();
391
+ for (const subPath of paths.subagents) {
392
+ await scanFileIntoDailyAccumulator(subPath, subAcc, sinceMs, untilMs);
393
+ }
394
+ const buckets = new Map();
395
+ const mergeBucket = (date, source) => {
396
+ const target = buckets.get(date) ?? {
397
+ requestCount: 0,
398
+ tokens: createTokenTotals(),
399
+ };
400
+ target.requestCount += source.requestCount;
401
+ addTokenTotals(target.tokens, source.tokens);
402
+ buckets.set(date, target);
403
+ };
404
+ for (const [date, bucket] of mainAcc.buckets)
405
+ mergeBucket(date, bucket);
406
+ for (const [date, bucket] of subAcc.buckets)
407
+ mergeBucket(date, bucket);
408
+ const agentTotal = [...buckets.values()].reduce((sum, bucket) => sum + bucket.tokens.total, 0);
409
+ if (agentTotal <= 0)
410
+ continue;
411
+ agentBuckets.set(agent.id, {
412
+ agentId: agent.id,
413
+ agentName: agent.name,
414
+ buckets,
415
+ });
416
+ }
417
+ catch (err) {
418
+ log.warn(`Failed to scan Claude daily usage for agent ${agent.name} (${agent.id}): ${err}`);
419
+ }
420
+ }
421
+ const days = buildDayKeys(sinceMs, untilMs).map((date) => {
422
+ const dayAgents = [];
423
+ for (const agentUsage of agentBuckets.values()) {
424
+ const bucket = agentUsage.buckets.get(date);
425
+ if (!bucket || bucket.tokens.total <= 0)
426
+ continue;
427
+ dayAgents.push({
428
+ agentId: agentUsage.agentId,
429
+ agentName: agentUsage.agentName,
430
+ tokens: { ...bucket.tokens },
431
+ requestCount: bucket.requestCount,
432
+ });
433
+ }
434
+ dayAgents.sort((a, b) => b.tokens.total - a.tokens.total);
435
+ return {
436
+ date,
437
+ totalTokens: dayAgents.reduce((sum, entry) => sum + entry.tokens.total, 0),
438
+ requestCount: dayAgents.reduce((sum, entry) => sum + entry.requestCount, 0),
439
+ agents: dayAgents,
440
+ };
441
+ });
442
+ return {
443
+ provider: 'claude',
444
+ fetchedAt: Date.now(),
445
+ since: normalizeBoundary(sinceMs),
446
+ until: normalizeBoundary(untilMs),
447
+ source: 'claude-jsonl',
448
+ days,
449
+ };
450
+ }
@@ -18,6 +18,11 @@ const MODEL_MAP = {
18
18
  haiku: 'claude-haiku-4-5-20251001',
19
19
  sonnet: 'claude-sonnet-4-6-20250514',
20
20
  opus: 'claude-opus-4-8',
21
+ fable: 'claude-fable-5',
22
+ 'fable-5': 'claude-fable-5',
23
+ fable5: 'claude-fable-5',
24
+ 'claude-fable-5': 'claude-fable-5',
25
+ 'claude-fable-5[1m]': 'claude-fable-5',
21
26
  'claude-opus-4-8': 'claude-opus-4-8',
22
27
  'claude-opus-4-8[1m]': 'claude-opus-4-8',
23
28
  'claude-opus-4-7': 'claude-opus-4-7',
@@ -63,12 +63,14 @@ export const CODEX_MODELS = {
63
63
  };
64
64
  export const CLAUDE_MODELS = {
65
65
  sonnet: { label: 'Sonnet', description: 'Balanced performance and cost (recommended)', icon: '⚡', contextWindow: 200000 },
66
+ 'claude-fable-5[1m]': { label: 'Fable 5 [1M]', description: 'Most powerful, most intelligent Claude model — new tier above Opus, 1M token context window', icon: '🪄', contextWindow: 1000000 },
66
67
  'claude-opus-4-8[1m]': { label: 'Opus 4.8 [1M]', description: 'Latest Opus with 1M token context window — most capable, best for very long tasks', icon: '🧠', contextWindow: 1000000 },
67
68
  'opus[1m]': { label: 'Opus 4.7 [1M]', description: 'Previous Opus generation with 1M token context window', icon: '🧠', contextWindow: 1000000 },
68
69
  haiku: { label: 'Haiku', description: 'Fast and economical', icon: '🚀', contextWindow: 200000 },
69
70
  // Plain (200K) Opus IDs are kept as valid model values for existing agents
70
71
  // and CLI passthrough, but hidden from the "new agent" picker in favor of
71
72
  // the 1M variants above.
73
+ 'claude-fable-5': { label: 'Fable 5 (200K)', description: 'Fable 5, 200K context window (1M variant preferred)', icon: '🪄', contextWindow: 200000, deprecated: true },
72
74
  'claude-opus-4-8': { label: 'Opus 4.8 (200K)', description: 'Latest Opus, 200K context window (1M variant preferred)', icon: '🧠', contextWindow: 200000, deprecated: true },
73
75
  'claude-opus-4-7': { label: 'Opus 4.7 (200K)', description: 'Previous Opus generation, 200K context window (1M variant preferred)', icon: '🧠', contextWindow: 200000, deprecated: true },
74
76
  opus: { label: 'Opus (legacy)', description: 'Legacy alias — prefer Opus 4.8 [1M]', icon: '🧠', contextWindow: 200000, deprecated: true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "1.111.2",
3
+ "version": "1.113.0",
4
4
  "description": "Visual multi-agent orchestrator and manager for Claude Code with 3D/2D interface",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- import{u as y,h as w,ad as l,cb as C,j as s,b6 as S,I as j,bx as N}from"./main-Bon1sZAi.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";const d=["#6ab8c8","#c89a5a","#9a80c0","#5cb88a","#c85a5a","#c87a9a","#c8c87a","#5a8fd4","#d45a5a","#8a6fbf"];function V(){const i=new Date;return i.setHours(0,0,0,0),i.getTime()}function b(i){if(!i)return"N/A";try{return new Date(i).toLocaleString()}catch{return"N/A"}}function D(i,n){if(i.length===0||n<=0)return"conic-gradient(rgba(50, 50, 62, 0.95) 0deg 360deg)";let t=0;return`conic-gradient(${i.map((u,e)=>{const m=t,c=u.tokens.total/n*360;return t+=c,`${d[e%d.length]} ${m.toFixed(2)}deg ${t.toFixed(2)}deg`}).join(", ")})`}function T({isOpen:i,onClose:n}){const{t}=y(["terminal","common"]),{handleMouseDown:_,handleClick:u}=w(n),[e,m]=l.useState(null),[c,h]=l.useState(!1),[o,p]=l.useState(null),x=l.useMemo(()=>V(),[]),g=l.useCallback(async()=>{h(!0),p(null);try{const a=await C({since:x,until:Date.now()});m(a)}catch(a){p((a==null?void 0:a.message)||"Error")}finally{h(!1)}},[x]);if(l.useEffect(()=>{i&&g()},[i,g]),!i)return null;const r=(e==null?void 0:e.entries)??[],f=(e==null?void 0:e.totalTokens)??0,v=D(r,f);return s.jsx(S,{children:s.jsx("div",{className:"modal-overlay visible",onMouseDown:_,onClick:u,children:s.jsxs("div",{className:"modal statistics-modal",children:[s.jsxs("div",{className:"modal-header statistics-modal__header",children:[s.jsxs("div",{className:"statistics-modal__title",children:[s.jsx("span",{className:"statistics-modal__icon",children:s.jsx(j,{name:"dashboard",size:15})}),s.jsx("span",{children:t("terminal:statistics.title",{defaultValue:"Statistics"})})]}),s.jsx("button",{className:"modal-close statistics-modal__close",onClick:n,title:t("common:buttons.close"),children:"×"})]}),s.jsx("div",{className:"modal-body statistics-modal__body",children:s.jsxs("section",{className:"statistics-panel",children:[s.jsxs("div",{className:"statistics-panel__header",children:[s.jsxs("div",{children:[s.jsx("h3",{children:t("terminal:statistics.usageByAgent",{defaultValue:"Claude usage today"})}),s.jsx("span",{className:"statistics-panel__subtitle",children:t("terminal:statistics.usageByAgentSubtitle",{defaultValue:"Input + cache creation + cache read + output tokens, deduped by Claude request id"})})]}),s.jsx("button",{type:"button",className:"statistics-panel__refresh",onClick:g,disabled:c,title:t("terminal:statistics.refresh",{defaultValue:"Refresh usage"}),children:s.jsx(j,{name:"refresh",size:14})})]}),c&&!e&&s.jsx("div",{className:"statistics-panel__empty",children:t("terminal:statistics.loading",{defaultValue:"Loading usage..."})}),!c&&o&&s.jsx("div",{className:"statistics-panel__empty statistics-panel__empty--error",children:t("terminal:statistics.error",{defaultValue:"Failed to load usage: {{error}}",error:o})}),!o&&e&&r.length===0&&s.jsx("div",{className:"statistics-panel__empty",children:t("terminal:statistics.empty",{defaultValue:"No Claude token usage found for today."})}),!o&&e&&r.length>0&&s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"statistics-panel__layout",children:[s.jsx("div",{className:"statistics-panel__chart-wrap",children:s.jsx("div",{className:"statistics-panel__pie",style:{background:v},role:"img","aria-label":t("terminal:statistics.pieLabel",{defaultValue:"Claude token usage by agent"}),children:s.jsxs("div",{className:"statistics-panel__pie-hole",children:[s.jsx("strong",{children:N(f)}),s.jsx("span",{children:t("terminal:statistics.total",{defaultValue:"total"})})]})})}),s.jsx("div",{className:"statistics-panel__list",children:r.map((a,k)=>s.jsxs("div",{className:"statistics-panel__row",title:`${a.agentName}: ${a.tokens.total.toLocaleString()} tokens`,children:[s.jsx("span",{className:"statistics-panel__swatch",style:{backgroundColor:d[k%d.length]}}),s.jsxs("div",{className:"statistics-panel__row-main",children:[s.jsx("div",{className:"statistics-panel__row-name",children:a.agentName}),s.jsxs("div",{className:"statistics-panel__row-meta",children:[a.requestCount.toLocaleString()," ",t("terminal:statistics.requests",{defaultValue:"requests"})," / ",a.percent,"%"]})]}),s.jsx("strong",{children:N(a.tokens.total)})]},a.agentId))})]}),s.jsx("div",{className:"statistics-panel__footnote",children:t("terminal:statistics.window",{defaultValue:"Window: {{since}} to {{until}}",since:b(e.since),until:b(e.until)})})]})]})})]})})})}export{T as StatisticsModal};