@swarmify/agents-cli 1.10.4 → 1.11.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 (86) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +45 -2
  3. package/dist/commands/__tests__/sessions.test.js +266 -0
  4. package/dist/commands/__tests__/sessions.test.js.map +1 -1
  5. package/dist/commands/commands.d.ts.map +1 -1
  6. package/dist/commands/commands.js +5 -11
  7. package/dist/commands/commands.js.map +1 -1
  8. package/dist/commands/exec.d.ts.map +1 -1
  9. package/dist/commands/exec.js +11 -1
  10. package/dist/commands/exec.js.map +1 -1
  11. package/dist/commands/hooks.d.ts.map +1 -1
  12. package/dist/commands/hooks.js +5 -11
  13. package/dist/commands/hooks.js.map +1 -1
  14. package/dist/commands/mcp.d.ts.map +1 -1
  15. package/dist/commands/mcp.js +109 -43
  16. package/dist/commands/mcp.js.map +1 -1
  17. package/dist/commands/packages.d.ts.map +1 -1
  18. package/dist/commands/packages.js +92 -42
  19. package/dist/commands/packages.js.map +1 -1
  20. package/dist/commands/permissions.d.ts.map +1 -1
  21. package/dist/commands/permissions.js +38 -34
  22. package/dist/commands/permissions.js.map +1 -1
  23. package/dist/commands/pull.d.ts.map +1 -1
  24. package/dist/commands/pull.js +14 -15
  25. package/dist/commands/pull.js.map +1 -1
  26. package/dist/commands/push.d.ts.map +1 -1
  27. package/dist/commands/push.js +2 -0
  28. package/dist/commands/push.js.map +1 -1
  29. package/dist/commands/rules.d.ts.map +1 -1
  30. package/dist/commands/rules.js +5 -11
  31. package/dist/commands/rules.js.map +1 -1
  32. package/dist/commands/sessions.d.ts.map +1 -1
  33. package/dist/commands/sessions.js +157 -30
  34. package/dist/commands/sessions.js.map +1 -1
  35. package/dist/commands/skills.d.ts.map +1 -1
  36. package/dist/commands/skills.js +5 -11
  37. package/dist/commands/skills.js.map +1 -1
  38. package/dist/commands/versions.d.ts.map +1 -1
  39. package/dist/commands/versions.js +5 -1
  40. package/dist/commands/versions.js.map +1 -1
  41. package/dist/commands/view.d.ts.map +1 -1
  42. package/dist/commands/view.js +36 -29
  43. package/dist/commands/view.js.map +1 -1
  44. package/dist/index.js +1 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/lib/__tests__/exec.test.js +34 -1
  47. package/dist/lib/__tests__/exec.test.js.map +1 -1
  48. package/dist/lib/agents.d.ts +19 -0
  49. package/dist/lib/agents.d.ts.map +1 -1
  50. package/dist/lib/agents.js +177 -16
  51. package/dist/lib/agents.js.map +1 -1
  52. package/dist/lib/exec.d.ts +3 -0
  53. package/dist/lib/exec.d.ts.map +1 -1
  54. package/dist/lib/exec.js +26 -0
  55. package/dist/lib/exec.js.map +1 -1
  56. package/dist/lib/registry.d.ts.map +1 -1
  57. package/dist/lib/registry.js +8 -0
  58. package/dist/lib/registry.js.map +1 -1
  59. package/dist/lib/resources.d.ts +2 -0
  60. package/dist/lib/resources.d.ts.map +1 -1
  61. package/dist/lib/resources.js +7 -7
  62. package/dist/lib/resources.js.map +1 -1
  63. package/dist/lib/session/discover.d.ts.map +1 -1
  64. package/dist/lib/session/discover.js +341 -114
  65. package/dist/lib/session/discover.js.map +1 -1
  66. package/dist/lib/session/prompt.d.ts +3 -0
  67. package/dist/lib/session/prompt.d.ts.map +1 -0
  68. package/dist/lib/session/prompt.js +40 -0
  69. package/dist/lib/session/prompt.js.map +1 -0
  70. package/dist/lib/session/render.d.ts.map +1 -1
  71. package/dist/lib/session/render.js +6 -37
  72. package/dist/lib/session/render.js.map +1 -1
  73. package/dist/lib/session/types.d.ts +1 -0
  74. package/dist/lib/session/types.d.ts.map +1 -1
  75. package/dist/lib/types.d.ts +1 -0
  76. package/dist/lib/types.d.ts.map +1 -1
  77. package/dist/lib/types.js.map +1 -1
  78. package/dist/lib/usage.d.ts +1 -0
  79. package/dist/lib/usage.d.ts.map +1 -1
  80. package/dist/lib/usage.js +45 -33
  81. package/dist/lib/usage.js.map +1 -1
  82. package/dist/lib/versions.d.ts +28 -0
  83. package/dist/lib/versions.d.ts.map +1 -1
  84. package/dist/lib/versions.js +181 -1
  85. package/dist/lib/versions.js.map +1 -1
  86. package/package.json +1 -1
@@ -5,6 +5,7 @@ import * as crypto from 'crypto';
5
5
  import * as readline from 'readline';
6
6
  import { execSync } from 'child_process';
7
7
  import { SESSION_AGENTS } from './types.js';
8
+ import { extractSessionTopic } from './prompt.js';
8
9
  const HOME = os.homedir();
9
10
  const AGENTS_DIR = path.join(HOME, '.agents');
10
11
  const SESSIONS_DIR = path.join(AGENTS_DIR, 'sessions');
@@ -43,12 +44,15 @@ export async function discoverSessions(options) {
43
44
  toSave.set(s.id, s);
44
45
  }
45
46
  saveIndex([...toSave.values()]);
47
+ const projectQuery = options?.project?.trim();
46
48
  // Filter by project (case-insensitive substring match)
47
- if (options?.project) {
48
- const query = options.project.toLowerCase();
49
+ if (projectQuery) {
50
+ const query = projectQuery.toLowerCase();
49
51
  sessions = sessions.filter(s => s.project?.toLowerCase().includes(query));
50
52
  }
51
- if (!options?.all) {
53
+ // An explicit project search should scan across directories instead of
54
+ // intersecting with the default cwd-only scope.
55
+ if (!options?.all && !projectQuery) {
52
56
  const currentDir = normalizeCwd(options?.cwd || process.cwd());
53
57
  sessions = sessions.filter(s => normalizeCwd(s.cwd) === currentDir);
54
58
  }
@@ -113,18 +117,7 @@ function saveIndex(sessions) {
113
117
  if (seen.has(s.id))
114
118
  continue;
115
119
  seen.add(s.id);
116
- lines.push(JSON.stringify({
117
- id: s.id,
118
- shortId: s.shortId,
119
- agent: s.agent,
120
- timestamp: s.timestamp,
121
- project: s.project,
122
- cwd: s.cwd,
123
- filePath: s.filePath,
124
- gitBranch: s.gitBranch,
125
- version: s.version,
126
- account: s.account,
127
- }));
120
+ lines.push(JSON.stringify(s));
128
121
  }
129
122
  fs.writeFileSync(INDEX_PATH, lines.join('\n') + '\n', 'utf-8');
130
123
  }
@@ -269,41 +262,24 @@ async function discoverClaudeSessions() {
269
262
  return sessions;
270
263
  }
271
264
  async function readClaudeMeta(filePath, sessionId, account) {
272
- const lines = await readFirstLines(filePath, 10);
273
- let topic;
274
- for (const line of lines) {
275
- let parsed;
276
- try {
277
- parsed = JSON.parse(line);
278
- }
279
- catch {
280
- continue;
281
- }
282
- // Extract topic from first user message
283
- if (!topic && parsed.type === 'user' && parsed.message?.content) {
284
- const text = Array.isArray(parsed.message.content)
285
- ? parsed.message.content.find((b) => b.type === 'text')?.text
286
- : typeof parsed.message.content === 'string' ? parsed.message.content : undefined;
287
- if (text)
288
- topic = extractTopic(text);
289
- }
290
- // Look for first user or assistant line with timestamp/cwd
291
- if ((parsed.type === 'user' || parsed.type === 'assistant') && parsed.timestamp) {
292
- const cwd = parsed.cwd || '';
293
- return {
294
- id: sessionId,
295
- shortId: sessionId.slice(0, 8),
296
- agent: 'claude',
297
- timestamp: parsed.timestamp,
298
- project: cwd ? path.basename(cwd) : undefined,
299
- cwd,
300
- filePath,
301
- gitBranch: parsed.gitBranch || undefined,
302
- version: parsed.version || undefined,
303
- account,
304
- topic,
305
- };
306
- }
265
+ const scan = await scanClaudeSession(filePath);
266
+ if (scan.timestamp) {
267
+ const cwd = scan.cwd || '';
268
+ return {
269
+ id: sessionId,
270
+ shortId: sessionId.slice(0, 8),
271
+ agent: 'claude',
272
+ timestamp: scan.timestamp,
273
+ project: cwd ? path.basename(cwd) : undefined,
274
+ cwd,
275
+ filePath,
276
+ gitBranch: scan.gitBranch,
277
+ version: scan.version,
278
+ account,
279
+ topic: scan.topic,
280
+ messageCount: scan.messageCount,
281
+ tokenCount: scan.tokenCount,
282
+ };
307
283
  }
308
284
  // Fallback: use file mtime
309
285
  const stat = safeStatSync(filePath);
@@ -314,6 +290,9 @@ async function readClaudeMeta(filePath, sessionId, account) {
314
290
  timestamp: stat ? stat.mtime.toISOString() : new Date().toISOString(),
315
291
  filePath,
316
292
  account,
293
+ messageCount: scan.messageCount,
294
+ tokenCount: scan.tokenCount,
295
+ topic: scan.topic,
317
296
  };
318
297
  }
319
298
  // ---------------------------------------------------------------------------
@@ -388,50 +367,24 @@ async function discoverCodexSessions() {
388
367
  return sessions;
389
368
  }
390
369
  async function readCodexMeta(filePath, account) {
391
- const lines = await readFirstLines(filePath, 5);
392
- if (lines.length === 0)
393
- return null;
394
- let parsed;
395
- try {
396
- parsed = JSON.parse(lines[0]);
397
- }
398
- catch {
399
- return null;
400
- }
401
- if (parsed.type !== 'session_meta')
402
- return null;
403
- const payload = parsed.payload || {};
404
- const sessionId = payload.id || '';
370
+ const scan = await scanCodexSession(filePath);
371
+ const sessionId = scan.sessionId || '';
405
372
  if (!sessionId)
406
373
  return null;
407
- // Extract topic from first user message in subsequent lines
408
- let topic;
409
- for (let i = 1; i < lines.length; i++) {
410
- try {
411
- const ev = JSON.parse(lines[i]);
412
- if (ev.type === 'message' && ev.role === 'user' && ev.content) {
413
- const text = typeof ev.content === 'string' ? ev.content
414
- : Array.isArray(ev.content) ? ev.content.find((b) => b.type === 'input_text')?.text : undefined;
415
- if (text) {
416
- topic = extractTopic(text);
417
- break;
418
- }
419
- }
420
- }
421
- catch { /* malformed event line */ }
422
- }
423
- const cwd = payload.cwd || '';
374
+ const cwd = scan.cwd || '';
424
375
  return {
425
376
  id: sessionId,
426
377
  shortId: sessionId.slice(0, 8),
427
378
  agent: 'codex',
428
- timestamp: payload.timestamp || parsed.timestamp || new Date().toISOString(),
379
+ timestamp: scan.timestamp || new Date().toISOString(),
429
380
  project: cwd ? path.basename(cwd) : undefined,
430
381
  cwd,
431
382
  filePath,
432
- gitBranch: payload.git?.branch || undefined,
433
- version: payload.version || undefined,
434
- topic,
383
+ gitBranch: scan.gitBranch,
384
+ version: scan.version,
385
+ topic: scan.topic,
386
+ messageCount: scan.messageCount,
387
+ tokenCount: scan.tokenCount,
435
388
  account,
436
389
  };
437
390
  }
@@ -483,16 +436,16 @@ async function discoverGeminiSessions() {
483
436
  return sessions;
484
437
  }
485
438
  function readGeminiMeta(filePath, hashDir, projectMap) {
486
- // Read the first ~2KB to get top-level fields without parsing entire messages array
487
- const fd = fs.openSync(filePath, 'r');
488
- const buf = Buffer.alloc(2048);
489
- const bytesRead = fs.readSync(fd, buf, 0, 2048, 0);
490
- fs.closeSync(fd);
491
- const header = buf.toString('utf-8', 0, bytesRead);
492
- // Extract fields via regex (avoids parsing potentially huge messages array)
493
- const sessionId = extractJsonField(header, 'sessionId');
494
- const startTime = extractJsonField(header, 'startTime');
495
- const projectHash = extractJsonField(header, 'projectHash');
439
+ let session;
440
+ try {
441
+ session = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
442
+ }
443
+ catch {
444
+ return null;
445
+ }
446
+ const sessionId = typeof session.sessionId === 'string' ? session.sessionId : '';
447
+ const startTime = typeof session.startTime === 'string' ? session.startTime : '';
448
+ const projectHash = typeof session.projectHash === 'string' ? session.projectHash : '';
496
449
  if (!sessionId)
497
450
  return null;
498
451
  // Resolve project name from hash
@@ -500,11 +453,31 @@ function readGeminiMeta(filePath, hashDir, projectMap) {
500
453
  const project = projectInfo?.name || hashDir.slice(0, 12);
501
454
  const cwd = projectInfo?.path;
502
455
  const stat = safeStatSync(filePath);
503
- // Try to extract first user message from the header bytes
456
+ const messages = Array.isArray(session.messages) ? session.messages : [];
504
457
  let topic;
505
- const userMsgMatch = header.match(/"role"\s*:\s*"user"[\s\S]*?"text"\s*:\s*"([^"]{1,200})"/);
506
- if (userMsgMatch)
507
- topic = extractTopic(userMsgMatch[1]);
458
+ let messageCount = 0;
459
+ let tokenCount = 0;
460
+ let sawTokenCount = false;
461
+ for (const message of messages) {
462
+ if (message.type === 'user') {
463
+ const text = extractGeminiMessageText(message.content);
464
+ if (text) {
465
+ messageCount++;
466
+ if (!topic)
467
+ topic = extractSessionTopic(text);
468
+ }
469
+ }
470
+ else if (message.type === 'gemini') {
471
+ if (extractGeminiMessageText(message.content)) {
472
+ messageCount++;
473
+ }
474
+ }
475
+ const total = getGeminiTokenCount(message.tokens);
476
+ if (total !== null) {
477
+ tokenCount += total;
478
+ sawTokenCount = true;
479
+ }
480
+ }
508
481
  return {
509
482
  id: sessionId,
510
483
  shortId: sessionId.slice(0, 8),
@@ -514,6 +487,8 @@ function readGeminiMeta(filePath, hashDir, projectMap) {
514
487
  cwd,
515
488
  filePath,
516
489
  topic,
490
+ messageCount,
491
+ tokenCount: sawTokenCount ? tokenCount : undefined,
517
492
  };
518
493
  }
519
494
  function buildGeminiProjectMap() {
@@ -598,9 +573,32 @@ async function discoverOpenCodeSessions() {
598
573
  // Query sessions. time_created is millisecond epoch. Limit to 200 most recent.
599
574
  // Use session.title as topic (OpenCode auto-generates good titles).
600
575
  const query = `
601
- SELECT id, title, directory, version, time_created
602
- FROM session
603
- WHERE parent_id IS NULL
576
+ SELECT
577
+ s.id,
578
+ s.title,
579
+ s.directory,
580
+ s.version,
581
+ s.time_created,
582
+ COALESCE(stats.message_count, 0),
583
+ stats.token_count,
584
+ COALESCE(stats.has_token_data, 0)
585
+ FROM session s
586
+ LEFT JOIN (
587
+ SELECT
588
+ session_id,
589
+ COUNT(*) AS message_count,
590
+ SUM(
591
+ COALESCE(json_extract(data, '$.tokens.input'), 0) +
592
+ COALESCE(json_extract(data, '$.tokens.output'), 0) +
593
+ COALESCE(json_extract(data, '$.tokens.reasoning'), 0) +
594
+ COALESCE(json_extract(data, '$.tokens.cache.read'), 0) +
595
+ COALESCE(json_extract(data, '$.tokens.cache.write'), 0)
596
+ ) AS token_count,
597
+ MAX(CASE WHEN json_type(data, '$.tokens') IS NOT NULL THEN 1 ELSE 0 END) AS has_token_data
598
+ FROM message
599
+ GROUP BY session_id
600
+ ) stats ON stats.session_id = s.id
601
+ WHERE s.parent_id IS NULL
604
602
  ORDER BY time_created DESC
605
603
  LIMIT 200;
606
604
  `.replace(/\n/g, ' ');
@@ -609,10 +607,13 @@ async function discoverOpenCodeSessions() {
609
607
  for (const line of out.split('\n')) {
610
608
  if (!line.trim())
611
609
  continue;
612
- const [id, title, directory, version, timeCreatedStr] = line.split('|||');
610
+ const [id, title, directory, version, timeCreatedStr, messageCountStr, tokenCountStr, hasTokenDataStr] = line.split('|||');
613
611
  if (!id)
614
612
  continue;
615
613
  const timeCreated = parseInt(timeCreatedStr, 10);
614
+ const messageCount = parseInt(messageCountStr, 10);
615
+ const tokenCount = parseInt(tokenCountStr, 10);
616
+ const hasTokenData = hasTokenDataStr === '1';
616
617
  const timestamp = isNaN(timeCreated) ? new Date().toISOString() : new Date(timeCreated).toISOString();
617
618
  const topic = title || undefined;
618
619
  sessions.push({
@@ -626,6 +627,8 @@ async function discoverOpenCodeSessions() {
626
627
  version: version || undefined,
627
628
  account,
628
629
  topic,
630
+ messageCount: Number.isNaN(messageCount) ? undefined : messageCount,
631
+ tokenCount: hasTokenData && !Number.isNaN(tokenCount) ? tokenCount : undefined,
629
632
  });
630
633
  }
631
634
  return sessions;
@@ -722,6 +725,142 @@ async function discoverOpenClawSessions() {
722
725
  }
723
726
  return sessions;
724
727
  }
728
+ async function scanClaudeSession(filePath) {
729
+ const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
730
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
731
+ let timestamp;
732
+ let cwd;
733
+ let gitBranch;
734
+ let version;
735
+ let topic;
736
+ let messageCount = 0;
737
+ let tokenCount = 0;
738
+ let sawTokenCount = false;
739
+ const seenAssistantIds = new Set();
740
+ try {
741
+ for await (const line of rl) {
742
+ if (!line.trim())
743
+ continue;
744
+ let parsed;
745
+ try {
746
+ parsed = JSON.parse(line);
747
+ }
748
+ catch {
749
+ continue;
750
+ }
751
+ if (!timestamp && (parsed.type === 'user' || parsed.type === 'assistant') && parsed.timestamp) {
752
+ timestamp = parsed.timestamp;
753
+ cwd = parsed.cwd || '';
754
+ gitBranch = parsed.gitBranch || undefined;
755
+ version = parsed.version || undefined;
756
+ }
757
+ if (parsed.type === 'user') {
758
+ const text = extractClaudeUserText(parsed);
759
+ if (text) {
760
+ messageCount++;
761
+ if (!topic)
762
+ topic = extractSessionTopic(text);
763
+ }
764
+ continue;
765
+ }
766
+ if (parsed.type !== 'assistant')
767
+ continue;
768
+ const assistantId = typeof parsed.message?.id === 'string'
769
+ ? parsed.message.id
770
+ : typeof parsed.uuid === 'string'
771
+ ? parsed.uuid
772
+ : undefined;
773
+ const logicalId = assistantId || `${parsed.timestamp || ''}:${seenAssistantIds.size}`;
774
+ if (seenAssistantIds.has(logicalId))
775
+ continue;
776
+ seenAssistantIds.add(logicalId);
777
+ messageCount++;
778
+ const usage = getClaudeUsageTotal(parsed.message?.usage || parsed.usage);
779
+ if (usage !== null) {
780
+ tokenCount += usage;
781
+ sawTokenCount = true;
782
+ }
783
+ }
784
+ }
785
+ finally {
786
+ rl.close();
787
+ stream.destroy();
788
+ }
789
+ return {
790
+ timestamp,
791
+ cwd,
792
+ gitBranch,
793
+ version,
794
+ topic,
795
+ messageCount,
796
+ tokenCount: sawTokenCount ? tokenCount : undefined,
797
+ };
798
+ }
799
+ async function scanCodexSession(filePath) {
800
+ const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
801
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
802
+ let sessionId;
803
+ let timestamp;
804
+ let cwd;
805
+ let gitBranch;
806
+ let version;
807
+ let topic;
808
+ let messageCount = 0;
809
+ let tokenCount;
810
+ try {
811
+ for await (const line of rl) {
812
+ if (!line.trim())
813
+ continue;
814
+ let parsed;
815
+ try {
816
+ parsed = JSON.parse(line);
817
+ }
818
+ catch {
819
+ continue;
820
+ }
821
+ if (parsed.type === 'session_meta') {
822
+ const payload = parsed.payload || {};
823
+ sessionId = payload.id || sessionId;
824
+ timestamp = payload.timestamp || parsed.timestamp || timestamp;
825
+ cwd = payload.cwd || cwd;
826
+ gitBranch = payload.git?.branch || gitBranch;
827
+ version = payload.version || version;
828
+ continue;
829
+ }
830
+ if (parsed.type === 'response_item' && parsed.payload?.type === 'message') {
831
+ const role = parsed.payload.role === 'user' || parsed.payload.role === 'developer'
832
+ ? 'user'
833
+ : 'assistant';
834
+ const text = extractCodexMessageText(parsed.payload.content, role);
835
+ if (!text)
836
+ continue;
837
+ messageCount++;
838
+ if (role === 'user' && !topic)
839
+ topic = extractSessionTopic(text);
840
+ continue;
841
+ }
842
+ if (parsed.type === 'event_msg' && parsed.payload?.type === 'token_count') {
843
+ const total = getCodexTokenCount(parsed.payload.info?.total_token_usage);
844
+ if (total !== null)
845
+ tokenCount = total;
846
+ }
847
+ }
848
+ }
849
+ finally {
850
+ rl.close();
851
+ stream.destroy();
852
+ }
853
+ return {
854
+ sessionId,
855
+ timestamp,
856
+ cwd,
857
+ gitBranch,
858
+ version,
859
+ topic,
860
+ messageCount,
861
+ tokenCount,
862
+ };
863
+ }
725
864
  // ---------------------------------------------------------------------------
726
865
  // Utilities
727
866
  // ---------------------------------------------------------------------------
@@ -777,11 +916,6 @@ export function walkForFiles(dir, ext, limit) {
777
916
  results.sort((a, b) => b.mtime - a.mtime);
778
917
  return results.slice(0, limit).map(r => r.path);
779
918
  }
780
- function extractJsonField(text, field) {
781
- const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`, 'i');
782
- const match = text.match(re);
783
- return match ? match[1] : '';
784
- }
785
919
  function sha256(input) {
786
920
  return crypto.createHash('sha256').update(input).digest('hex');
787
921
  }
@@ -801,11 +935,104 @@ function safeRealpathSync(p) {
801
935
  return null;
802
936
  }
803
937
  }
804
- function extractTopic(text) {
805
- // Strip leading whitespace, slash commands, and system tags
806
- let clean = text.replace(/^[\s\n]+/, '').replace(/<[^>]+>/g, '').trim();
807
- // Take first line only
808
- const firstLine = clean.split('\n')[0].trim();
809
- return firstLine;
938
+ function extractClaudeUserText(parsed) {
939
+ if (parsed.isMeta === true)
940
+ return undefined;
941
+ const content = parsed.message?.content;
942
+ if (typeof content === 'string') {
943
+ const text = content.trim();
944
+ return isLocalCommandMessage(text) ? undefined : text || undefined;
945
+ }
946
+ if (!Array.isArray(content))
947
+ return undefined;
948
+ const text = content
949
+ .filter((block) => block.type === 'text')
950
+ .map((block) => String(block.text || '').trim())
951
+ .find((value) => value && !value.startsWith('[Request interrupted'));
952
+ if (!text || isLocalCommandMessage(text))
953
+ return undefined;
954
+ return text;
955
+ }
956
+ function isLocalCommandMessage(text) {
957
+ return /<local-command-caveat>|<bash-(input|stdout|stderr)>/i.test(text);
958
+ }
959
+ function getClaudeUsageTotal(usage) {
960
+ if (!usage || typeof usage !== 'object')
961
+ return null;
962
+ return sumKnownNumbers([
963
+ usage.input_tokens,
964
+ usage.output_tokens,
965
+ usage.cache_creation_input_tokens,
966
+ usage.cache_read_input_tokens,
967
+ ]);
968
+ }
969
+ function extractCodexMessageText(contentBlocks, role) {
970
+ if (!Array.isArray(contentBlocks))
971
+ return undefined;
972
+ const matches = role === 'user'
973
+ ? contentBlocks.filter((block) => block.type === 'input_text')
974
+ : contentBlocks.filter((block) => block.type === 'output_text');
975
+ const text = matches
976
+ .map((block) => String(block.text || '').trim())
977
+ .find((value) => {
978
+ if (!value)
979
+ return false;
980
+ if (role === 'user' && (value.length >= 2000 || value.includes('<permissions instructions>') || value.startsWith('# AGENTS.md instructions'))) {
981
+ return false;
982
+ }
983
+ return true;
984
+ });
985
+ return text || undefined;
986
+ }
987
+ function getCodexTokenCount(totalTokenUsage) {
988
+ if (!totalTokenUsage || typeof totalTokenUsage !== 'object')
989
+ return null;
990
+ return sumKnownNumbers([
991
+ totalTokenUsage.input_tokens,
992
+ totalTokenUsage.cached_input_tokens,
993
+ totalTokenUsage.output_tokens,
994
+ totalTokenUsage.reasoning_output_tokens,
995
+ ]);
996
+ }
997
+ function extractGeminiMessageText(content) {
998
+ if (typeof content === 'string')
999
+ return content.trim();
1000
+ if (Array.isArray(content)) {
1001
+ return content
1002
+ .map((part) => {
1003
+ if (typeof part === 'string')
1004
+ return part;
1005
+ if (typeof part?.text === 'string')
1006
+ return part.text;
1007
+ return '';
1008
+ })
1009
+ .join('\n')
1010
+ .trim();
1011
+ }
1012
+ return '';
1013
+ }
1014
+ function getGeminiTokenCount(tokens) {
1015
+ if (!tokens || typeof tokens !== 'object')
1016
+ return null;
1017
+ if (typeof tokens.total === 'number')
1018
+ return tokens.total;
1019
+ return sumKnownNumbers([
1020
+ tokens.input,
1021
+ tokens.output,
1022
+ tokens.cached,
1023
+ tokens.thoughts,
1024
+ tokens.tool,
1025
+ ]);
1026
+ }
1027
+ function sumKnownNumbers(values) {
1028
+ let total = 0;
1029
+ let found = false;
1030
+ for (const value of values) {
1031
+ if (typeof value !== 'number' || Number.isNaN(value))
1032
+ continue;
1033
+ total += value;
1034
+ found = true;
1035
+ }
1036
+ return found ? total : null;
810
1037
  }
811
1038
  //# sourceMappingURL=discover.js.map