tide-commander 1.111.2 → 1.112.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 (41) hide show
  1. package/dist/assets/{BossLogsModal-DLTh_jKZ.js → BossLogsModal-CwHTS7Io.js} +1 -1
  2. package/dist/assets/{BossSpawnModal-DzivoRl1.js → BossSpawnModal-BXfNSkDz.js} +1 -1
  3. package/dist/assets/{ControlsModal-B8j6My0t.js → ControlsModal-DR0t4ILF.js} +1 -1
  4. package/dist/assets/{DockerLogsModal-BNwx7lVp.js → DockerLogsModal-DwnzwL14.js} +1 -1
  5. package/dist/assets/{EmbeddedEditor-CBoOoGL8.js → EmbeddedEditor-Cgbf4PPi.js} +1 -1
  6. package/dist/assets/{GmailOAuthSetup-BACIjX54.js → GmailOAuthSetup-COUn-rL4.js} +1 -1
  7. package/dist/assets/{GoogleOAuthSetup-BuASGyWh.js → GoogleOAuthSetup-DY-oV0lC.js} +1 -1
  8. package/dist/assets/{IframeModal-ckpeQpQB.js → IframeModal-Cw0HXgic.js} +1 -1
  9. package/dist/assets/{IntegrationsPanel-Dh7UOnTC.js → IntegrationsPanel-Barw4hcr.js} +2 -2
  10. package/dist/assets/{LogViewerModal-Cj7Hv-mD.js → LogViewerModal-Wbt81evL.js} +1 -1
  11. package/dist/assets/{MonitoringModal-Bx4vcjBr.js → MonitoringModal-CNicCwC1.js} +1 -1
  12. package/dist/assets/{PM2LogsModal-RBIjfi4J.js → PM2LogsModal-CjxdOw_Q.js} +1 -1
  13. package/dist/assets/{RestoreArchivedAreaModal-BBYlYIso.js → RestoreArchivedAreaModal-DMQSgBDn.js} +1 -1
  14. package/dist/assets/{Scene2DCanvas-CYB1Y5Wa.js → Scene2DCanvas-Bv96khxo.js} +1 -1
  15. package/dist/assets/{SceneManager-6vk047Kd.js → SceneManager-BegYb5dZ.js} +1 -1
  16. package/dist/assets/{SkillsPanel-CRRypqdy.js → SkillsPanel-BMXqdVzv.js} +1 -1
  17. package/dist/assets/{SlackMultiInstanceSetup-DisMBGxX.js → SlackMultiInstanceSetup-C00oSgQy.js} +1 -1
  18. package/dist/assets/{SpawnModal-qiu5nT-F.js → SpawnModal-pU2K5vrz.js} +1 -1
  19. package/dist/assets/StatisticsModal-CC9rhzmt.js +1 -0
  20. package/dist/assets/{SubordinateAssignmentModal-AUCAiGkE.js → SubordinateAssignmentModal-BPSip6u2.js} +1 -1
  21. package/dist/assets/{TriggerManagerPanel-56SuYzNh.js → TriggerManagerPanel-DPSqS7Vo.js} +1 -1
  22. package/dist/assets/{WorkflowEditorPanel-H0IGkyMd.js → WorkflowEditorPanel-Cq7ZF7dg.js} +1 -1
  23. package/dist/assets/{index-pRI5GcXd.js → index-44R4VNzL.js} +1 -1
  24. package/dist/assets/{index--3sQ1gHE.js → index-B-I8YHsB.js} +3 -3
  25. package/dist/assets/{index-DFWA70Lq.js → index-BL_PuMCK.js} +1 -1
  26. package/dist/assets/{index-C-kx99MP.js → index-ByFl2HJL.js} +1 -1
  27. package/dist/assets/{index-DY395tVJ.js → index-CiYtVsF2.js} +2 -2
  28. package/dist/assets/{index-94AqwQn0.js → index-DFJv26as.js} +1 -1
  29. package/dist/assets/{index-Dv0tQnLX.js → index-DKYfRvHE.js} +1 -1
  30. package/dist/assets/{index-DZP3fZ3b.js → index-DhZrpzSa.js} +1 -1
  31. package/dist/assets/{index-9NugNkE4.js → index-WmtsKBi6.js} +1 -1
  32. package/dist/assets/{main-Bon1sZAi.js → main-D9ETZv9V.js} +20 -20
  33. package/dist/assets/{main-BUSv52j9.css → main-OfLIQ_-I.css} +1 -1
  34. package/dist/assets/{web-RZLwjKCT.js → web-B5kMJoeB.js} +1 -1
  35. package/dist/assets/{web-CccJFC0m.js → web-DthvRrDx.js} +1 -1
  36. package/dist/assets/{web-CdhZGenV.js → web-gLG4upzZ.js} +1 -1
  37. package/dist/index.html +2 -2
  38. package/dist/src/packages/server/routes/agents.js +17 -1
  39. package/dist/src/packages/server/services/claude-usage-service.js +164 -9
  40. package/package.json +1 -1
  41. package/dist/assets/StatisticsModal-Bzk7qqOd.js +0 -1
@@ -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-D9ETZv9V.js";import{ImpactStyle as i,NotificationType as r}from"./index-CiYtVsF2.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 +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-D9ETZv9V.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-D9ETZv9V.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};
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-D9ETZv9V.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-OfLIQ_-I.css">
29
29
  </head>
30
30
  <body>
31
31
  <div id="app"></div>
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tide-commander",
3
- "version": "1.111.2",
3
+ "version": "1.112.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};