@stackmemoryai/stackmemory 1.5.3 → 1.5.5

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.
@@ -60,6 +60,55 @@ function formatElapsed(ms) {
60
60
  const days = Math.floor(hours / 24);
61
61
  return `${days}d ago`;
62
62
  }
63
+ function fmtTokens(n) {
64
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
65
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
66
+ return String(n);
67
+ }
68
+ function budgetBar(pct, width = 30) {
69
+ const filled = Math.min(Math.round(pct / 100 * width), width);
70
+ const empty = width - filled;
71
+ const color = pct >= 75 ? "\x1B[31m" : pct >= 50 ? "\x1B[33m" : "\x1B[32m";
72
+ const dim = "\x1B[2m";
73
+ const rst = "\x1B[0m";
74
+ return `${color}${"\u2588".repeat(filled)}${dim}${"\u2591".repeat(empty)}${rst} ${String(pct).padStart(3)}%`;
75
+ }
76
+ function fmtMinutes(m) {
77
+ if (m < 0) return "N/A";
78
+ if (m >= 60) return `${Math.floor(m / 60)}h ${m % 60}m`;
79
+ return `${m}m`;
80
+ }
81
+ function printUsageSummary(u) {
82
+ const totalTokens = u.totalTokens || 0;
83
+ const inputTokens = u.inputTokens || 0;
84
+ const outputTokens = u.outputTokens || 0;
85
+ const estMessages = u.estimatedMessages || 0;
86
+ const tokensPerMin = u.tokensPerMin || 0;
87
+ const budgetPct5x = u.budgetPct5x || 0;
88
+ const budgetPct20x = u.budgetPct20x || 0;
89
+ const mins5x = u.minutesRemaining5x ?? -1;
90
+ const mins20x = u.minutesRemaining20x ?? -1;
91
+ const cacheHitRate = u.cacheHitRate || 0;
92
+ const b = "\x1B[1m";
93
+ const d = "\x1B[2m";
94
+ const w = "\x1B[37m";
95
+ const r = "\x1B[0m";
96
+ console.log(`${b}Token Usage${r}`);
97
+ console.log(
98
+ ` Input ${w}${fmtTokens(inputTokens)}${r} ${d}|${r} Output ${w}${fmtTokens(outputTokens)}${r} ${d}|${r} Total ${w}${fmtTokens(totalTokens)}${r}`
99
+ );
100
+ console.log(
101
+ ` Rate ${w}${fmtTokens(tokensPerMin)}/min${r} ${d}|${r} Messages ${w}${estMessages}${r} ${d}|${r} Cache hit ${w}${cacheHitRate}%${r}`
102
+ );
103
+ console.log("");
104
+ console.log(`${b}Budget (Max plan, 5h window)${r}`);
105
+ console.log(
106
+ ` 5x (225 msgs) ${budgetBar(budgetPct5x)} ${d}~${fmtMinutes(mins5x)} left${r}`
107
+ );
108
+ console.log(
109
+ ` 20x (900 msgs) ${budgetBar(budgetPct20x)} ${d}~${fmtMinutes(mins20x)} left${r}`
110
+ );
111
+ }
63
112
  function createConductorCommands() {
64
113
  const cmd = new Command("conductor");
65
114
  cmd.description("Conductor \u2014 autonomous agent orchestration via Linear");
@@ -334,6 +383,54 @@ function createConductorCommands() {
334
383
  process.on("SIGTERM", forward);
335
384
  });
336
385
  });
386
+ cmd.command("usage").description("Show token usage, budget, and time-to-exhaustion").option("--json", "Output as JSON", false).option("--scan", "Scan Claude Code JSONL logs for historical data", false).action(async (options) => {
387
+ const statusPath = join(
388
+ process.cwd(),
389
+ ".stackmemory",
390
+ "conductor-status.json"
391
+ );
392
+ if (options.scan) {
393
+ const conductor = new Conductor({ repoRoot: process.cwd() });
394
+ await conductor.scanUsageLogs();
395
+ const summary = conductor.getUsageSummary();
396
+ if (options.json) {
397
+ console.log(JSON.stringify(summary, null, 2));
398
+ return;
399
+ }
400
+ printUsageSummary(summary);
401
+ return;
402
+ }
403
+ if (!existsSync(statusPath)) {
404
+ console.log(
405
+ "No conductor-status.json found. Run with --scan to check JSONL logs, or start the conductor first."
406
+ );
407
+ return;
408
+ }
409
+ try {
410
+ const data = JSON.parse(readFileSync(statusPath, "utf-8"));
411
+ const usage = data.usage || {};
412
+ if (options.json) {
413
+ console.log(JSON.stringify(usage, null, 2));
414
+ return;
415
+ }
416
+ printUsageSummary(usage);
417
+ const rl = data.rateLimit;
418
+ if (rl) {
419
+ console.log("");
420
+ if (rl.inBackoff) {
421
+ console.log(
422
+ `Rate limit: \x1B[31mBACKOFF\x1B[0m (${rl.backoffRemainingSec}s remaining, ${rl.totalHits} total hits)`
423
+ );
424
+ } else {
425
+ console.log(
426
+ `Rate limit: \x1B[32mOK\x1B[0m (${rl.totalHits} total hits)`
427
+ );
428
+ }
429
+ }
430
+ } catch (err) {
431
+ console.error("Failed to read status file:", err.message);
432
+ }
433
+ });
337
434
  cmd.command("start").description("Start the orchestrator daemon").option("--team <id>", "Linear team ID").option(
338
435
  "--states <states>",
339
436
  "Comma-separated issue states to pick up",
@@ -346,7 +443,11 @@ function createConductorCommands() {
346
443
  "--in-review <state>",
347
444
  "State name for completed review",
348
445
  "In Review"
349
- ).option("--poll <ms>", "Polling interval in milliseconds", "30000").option("--concurrency <n>", "Max concurrent agents", "3").option("--workspace-root <path>", "Workspace root directory").option("--repo <path>", "Git repo root for worktrees", process.cwd()).option("--branch <name>", "Base branch for worktrees", "main").option("--retries <n>", "Max retries per issue", "1").option("--turn-timeout <ms>", "Agent turn timeout in ms", "3600000").action(async (options) => {
446
+ ).option("--poll <ms>", "Polling interval in milliseconds", "30000").option("--concurrency <n>", "Max concurrent agents", "3").option("--workspace-root <path>", "Workspace root directory").option("--repo <path>", "Git repo root for worktrees", process.cwd()).option("--branch <name>", "Base branch for worktrees", "main").option("--retries <n>", "Max retries per issue", "1").option("--turn-timeout <ms>", "Agent turn timeout in ms", "3600000").option(
447
+ "--mode <mode>",
448
+ 'Agent mode: "cli" (claude -p, session auth) or "adapter" (JSON-RPC, API key)',
449
+ "cli"
450
+ ).action(async (options) => {
350
451
  const conductor = new Conductor({
351
452
  teamId: options.team,
352
453
  activeStates: options.states.split(",").map((s) => s.trim()),
@@ -358,7 +459,8 @@ function createConductorCommands() {
358
459
  repoRoot: options.repo,
359
460
  baseBranch: options.branch,
360
461
  maxRetries: parseInt(options.retries, 10),
361
- turnTimeoutMs: parseInt(options.turnTimeout, 10)
462
+ turnTimeoutMs: parseInt(options.turnTimeout, 10),
463
+ agentMode: options.mode === "adapter" ? "adapter" : "cli"
362
464
  });
363
465
  await conductor.start();
364
466
  });