agent-orchestrator-kit 0.9.0 β†’ 0.10.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.10.0] - 2026-08-31
8
+
9
+ ### Added
10
+ - **`costUsdEstimated` first-class** in `metrics.json` (`spend`, platforms, models, sessions, sources). Cursor writes a labeled estimate whenever tokens exist: grok-4.5/4.6 keep xAI API rates (`costSource: "api-estimate"`); other models use a versioned fallback of $3/1M input + $15/1M output (or $3.50/1M when only `totalTokens` is present, `costSource: "api-estimate-fallback"`). This is **not** a Cursor invoice and is never mixed into billed `costUsd`. Self-report `cost_usd` stays billed and is not copied into `costUsdEstimated`. Amp without a `Cost:` line leaves `costUsd: null` (tokens are not converted to USD; credits stay separate). Archived `metrics.json` files are not backfilled.
11
+
7
12
  ## [0.9.0] - 2026-08-31
8
13
 
9
14
  ### Added
@@ -252,6 +257,7 @@ All notable changes to this project will be documented in this file.
252
257
  ### Added
253
258
  - Initial release: 5-role orchestration pipeline, `/opsx:*` commands, IDE sync
254
259
 
260
+ [0.10.0]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.9.0...v0.10.0
255
261
  [0.9.0]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.8.0...v0.9.0
256
262
  [0.8.0]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.7.0...v0.8.0
257
263
  [0.7.0]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.6.0...v0.7.0
package/README.md CHANGED
@@ -74,7 +74,7 @@ npx agent-orchestrator-kit@latest init --profile generic --ci gitlab --spec-veri
74
74
 
75
75
  See [Installation](#installation) for profile/CI options.
76
76
 
77
- **πŸ”„ Already have the kit installed? Upgrade to latest (UTC timestamps + Amp stamp parse, Amp billed `$`, Cursor API estimate, archive auto-collect in v0.9.0; locked Amp/Cursor client in v0.8.0; `## Metrics` self-report + opt-in `--collect` in v0.7.0 β€” **BREAKING:** `--no-collect` is gone; change metrics in v0.5.0+, factory phases 1–3 in v0.4.0+, lean pipeline / archive CLI in v0.3.0+, handoff CLI in v0.1.14+, Figma PAT in v0.1.11+):**
77
+ **πŸ”„ Already have the kit installed? Upgrade to latest (Cursor fallback estimate + first-class `costUsdEstimated` in v0.10.0; UTC timestamps + Amp stamp parse, Amp billed `$`, Cursor API estimate, archive auto-collect in v0.9.0; locked Amp/Cursor client in v0.8.0; `## Metrics` self-report + opt-in `--collect` in v0.7.0 β€” **BREAKING:** `--no-collect` is gone; change metrics in v0.5.0+, factory phases 1–3 in v0.4.0+, lean pipeline / archive CLI in v0.3.0+, handoff CLI in v0.1.14+, Figma PAT in v0.1.11+):**
78
78
 
79
79
  ```bash
80
80
  npx agent-orchestrator-kit@latest update
@@ -804,7 +804,7 @@ Every change accumulates git-tracked `openspec/changes/<name>/metrics.json` β€”
804
804
  - **`## Metrics` self-report** β€” Session Exit fills `handoff.md` with `platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source` (`unknown` when missing). Persist reads that section; `metrics.json` is the source of truth for what landed. CLI flags do not rewrite the section.
805
805
  - **`session.model`** β€” `--model` β†’ `## Metrics: model` β†’ `AOK_MODEL` β†’ collected sources (only with `--collect`) β†’ `null` (stderr warning). Never a Closed role.
806
806
  - **Session start** β€” `handoff --restore` writes a `pending` marker (`startedAt`, expected role).
807
- - **Session end** β€” `handoff <name>` closes the pending session: duration, closed role, mapped phase (`explore` / `design` / `spec` / `review` / `apply` / `archive`), runtime (local/cloud), tasks snapshot (`n/m`), and spend from flags β†’ self-report β†’ adapters. `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session totals only and do not wipe `spendByPlatform` / `spendByModel`. Amp billed USD comes from `amp threads usage --details` (fail-open), not from converting credits. Cursor tokens get a labeled `costUsdEstimated` (xAI API rates for grok-4.6/4.5, including `-fast` and the 200k cliff) β€” that estimate is **not** the Cursor invoice and is never added into billed `costUsd`. Timestamps are ISO-8601 UTC (`…Z`). No restore marker? Pass `--started-at <iso>` or the duration stays honestly `null`.
807
+ - **Session end** β€” `handoff <name>` closes the pending session: duration, closed role, mapped phase (`explore` / `design` / `spec` / `review` / `apply` / `archive`), runtime (local/cloud), tasks snapshot (`n/m`), and spend from flags β†’ self-report β†’ adapters. `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session totals only and do not wipe `spendByPlatform` / `spendByModel`. Amp billed USD comes from `amp threads usage --details` (`Cost: $N` only; missing line β†’ `costUsd: null`), not from converting credits. Cursor always writes a labeled `costUsdEstimated` when tokens exist: grok-4.6/4.5 use xAI API rates (`costSource: api-estimate`, including `-fast` and the 200k cliff); other models use a versioned fallback of $3/1M input + $15/1M output (or $3.50/1M when only `totalTokens` is present, `costSource: api-estimate-fallback`). That estimate is **not** the Cursor invoice and is never added into billed `costUsd`. Timestamps are ISO-8601 UTC (`…Z`). No restore marker? Pass `--started-at <iso>` or the duration stays honestly `null`.
808
808
  - **Archive** β€” successful `archive <name>` always creates or finalizes `metrics.json`, sets `archivedAt`, appends an Archiver session, collects the locked client (Cursor hook / Amp export+usage / Claude JSONL) in `[last session.endedAt, now]`, and prints the same human summary as `metrics <name>`. `--collect` still runs all three adapters. Leftover apply `## Metrics` that repeats the previous session is ignored so those tokens are not counted twice.
809
809
  - **Platform** β€” `--platform` β†’ `## Metrics: platform` β†’ `AOK_PLATFORM` β†’ **pending client from `--restore`** β†’ host env (Amp / Cursor / Claude Code) β†’ collected sources (`cursor|claude|amp` only). Invalid `--platform` fails before persist/move.
810
810
  - **Locked client** β€” `--restore` records `pending.platform` and Amp `pending.threadId` before phase work. Persist follows that client’s flow even if persist runs in another shell (no `AMP_*` / `CURSOR_*`). Amp: `amp threads export` plus `amp threads usage --details` (`AOK_AMP_BIN`) and local `threads/*.json`. Export supplies `model` / tokens / `agentMode`; usage supplies billed `$`. If Amp runs tools over a pipe (`/dev/null`), thread id comes from `amp threads list`, not stale `session.json` `lastThreadId`. Cursor: spend hook file. Claude: `~/.claude/projects`. `--collect` still runs all three adapters.
@@ -1003,6 +1003,9 @@ Phase bounds and non-goals: [`openspec/specs/agentic-factory-roadmap/spec.md`](o
1003
1003
 
1004
1004
  ## Changelog
1005
1005
 
1006
+ ### 0.10.0
1007
+ - `costUsdEstimated` is first-class on `metrics.json`; Cursor writes a labeled estimate whenever tokens exist β€” grok via xAI API (`costSource: api-estimate`), other models via versioned fallback $3/1M in + $15/1M out (or $3.50/1M when only `totalTokens`, `costSource: api-estimate-fallback`). The estimate is **not** an invoice and is never mixed into billed `costUsd`. Amp without a `Cost:` line leaves `costUsd: null`; self-report `cost_usd` stays billed.
1008
+
1006
1009
  ### 0.9.0
1007
1010
  - **UTC timestamps** in `metrics.json` (`…Z`; broken Amp stamps parsed and normalized)
1008
1011
  - Amp `threads usage --details` billed `$` + `agentMode` (never stored as `session.model`)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from 'commander';
3
3
  import pc from 'picocolors';
4
- import { readFileSync, existsSync, mkdirSync, copyFileSync, readdirSync, statSync, writeFileSync, rmSync, renameSync, chmodSync } from 'fs';
4
+ import { readFileSync, existsSync, mkdirSync, copyFileSync, readdirSync, statSync, writeFileSync, rmSync, renameSync, chmodSync, realpathSync } from 'fs';
5
5
  import { join, dirname, basename, resolve } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { execSync } from 'child_process';
@@ -4248,4 +4248,18 @@ program
4248
4248
  for (const line of renderMetricsSummary(metrics)) console.log(line);
4249
4249
  });
4250
4250
 
4251
- program.parse();
4251
+ function isDirectCliRun() {
4252
+ const entry = process.argv[1];
4253
+ if (!entry) return false;
4254
+ try {
4255
+ return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
4256
+ } catch {
4257
+ return resolve(entry) === fileURLToPath(import.meta.url);
4258
+ }
4259
+ }
4260
+
4261
+ if (isDirectCliRun()) {
4262
+ program.parse();
4263
+ }
4264
+
4265
+ export { formatMetricsCostLine, resolveSessionSpend };
@@ -14,6 +14,10 @@ const GROK_46 = {
14
14
  longAt: 200000,
15
15
  };
16
16
 
17
+ const FALLBACK_INPUT_PER_M = 3;
18
+ const FALLBACK_OUTPUT_PER_M = 15;
19
+ const FALLBACK_TOTAL_PER_M = 3.5;
20
+
17
21
  function ratesForModel(model) {
18
22
  const id = String(model || '').toLowerCase();
19
23
  if (!id) return null;
@@ -32,19 +36,41 @@ function ratesForModel(model) {
32
36
  return rates;
33
37
  }
34
38
 
35
- export function estimateCursorCostUsd({ model, inputTokens, outputTokens, cacheReadTokens } = {}) {
39
+ export function estimateCursorCostUsd({ model, inputTokens, outputTokens, cacheReadTokens, totalTokens } = {}) {
36
40
  const rates = ratesForModel(model);
37
- if (!rates) return null;
41
+ if (rates) {
42
+ const input = numOrNull(inputTokens);
43
+ const output = numOrNull(outputTokens) ?? 0;
44
+ if (input == null && output == 0) return null;
45
+ const totalInput = input ?? 0;
46
+ const cached = Math.min(numOrNull(cacheReadTokens) ?? 0, totalInput);
47
+ const fresh = Math.max(0, totalInput - cached);
48
+ const long = totalInput >= rates.longAt;
49
+ const inputRate = long ? rates.longInputPerM : rates.inputPerM;
50
+ const cachedRate = long ? rates.longCachedPerM : rates.cachedPerM;
51
+ const outputRate = long ? rates.longOutputPerM : rates.outputPerM;
52
+ const usd = (fresh * inputRate + cached * cachedRate + output * outputRate) / 1e6;
53
+ return Math.round(usd * 10000) / 10000;
54
+ }
38
55
  const input = numOrNull(inputTokens);
39
- const output = numOrNull(outputTokens) ?? 0;
40
- if (input == null && output == 0) return null;
41
- const totalInput = input ?? 0;
42
- const cached = Math.min(numOrNull(cacheReadTokens) ?? 0, totalInput);
43
- const fresh = Math.max(0, totalInput - cached);
44
- const long = totalInput >= rates.longAt;
45
- const inputRate = long ? rates.longInputPerM : rates.inputPerM;
46
- const cachedRate = long ? rates.longCachedPerM : rates.cachedPerM;
47
- const outputRate = long ? rates.longOutputPerM : rates.outputPerM;
48
- const usd = (fresh * inputRate + cached * cachedRate + output * outputRate) / 1e6;
49
- return Math.round(usd * 10000) / 10000;
56
+ const output = numOrNull(outputTokens);
57
+ if (input != null || output != null) {
58
+ const usd = ((input ?? 0) * FALLBACK_INPUT_PER_M + (output ?? 0) * FALLBACK_OUTPUT_PER_M) / 1e6;
59
+ return Math.round(usd * 10000) / 10000;
60
+ }
61
+ const total = numOrNull(totalTokens);
62
+ if (total != null) {
63
+ const usd = total * FALLBACK_TOTAL_PER_M / 1e6;
64
+ return Math.round(usd * 10000) / 10000;
65
+ }
66
+ return null;
67
+ }
68
+
69
+ export function describeCursorCostEstimate(args) {
70
+ const usd = estimateCursorCostUsd(args);
71
+ if (usd == null) return null;
72
+ return {
73
+ usd,
74
+ costSource: ratesForModel(args && args.model) != null ? 'api-estimate' : 'api-estimate-fallback',
75
+ };
50
76
  }
@@ -4,7 +4,7 @@ import { homedir as osHomedir } from 'os';
4
4
  import { execFileSync } from 'child_process';
5
5
  import { listRecentAmpThreadIds } from './session-client.js';
6
6
  import { formatUtcIso, parseFlexibleIso } from './metrics-time.js';
7
- import { estimateCursorCostUsd } from './cursor-cost-estimate.js';
7
+ import { describeCursorCostEstimate } from './cursor-cost-estimate.js';
8
8
  import { ampAgentMode, matchAmpUsageModel, parseAmpUsageDetails } from './amp-usage.js';
9
9
 
10
10
  const PLATFORMS = ['cursor', 'claude', 'amp'];
@@ -535,7 +535,7 @@ function collectCursor({ cwd, windowStart, windowEnd, existing, notes }) {
535
535
  if (inputTokens == null && outputTokens == null) continue;
536
536
  const model = row.model || row.modelId;
537
537
  const cacheReadTokens = numOrNull(row.cacheReadTokens);
538
- const estimated = estimateCursorCostUsd({
538
+ const described = describeCursorCostEstimate({
539
539
  model,
540
540
  inputTokens,
541
541
  outputTokens,
@@ -551,8 +551,8 @@ function collectCursor({ cwd, windowStart, windowEnd, existing, notes }) {
551
551
  ampCredits: null,
552
552
  at: row.at,
553
553
  cacheReadTokens,
554
- costUsdEstimated: estimated,
555
- costSource: estimated != null ? 'api-estimate' : null,
554
+ costUsdEstimated: described?.usd ?? null,
555
+ costSource: described?.costSource ?? null,
556
556
  });
557
557
  const previous = bestById.get(id);
558
558
  if (!previous || (record.totalTokens ?? 0) >= (previous.totalTokens ?? 0)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-orchestrator-kit",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Universal AI agent orchestration kit for Cursor, Claude Code, and Amp Code β€” spec-driven OpenSpec pipeline, conductor subagents, durable session handoff, factory gates and MCP setup, cloud-agent handoff, and optional local Figma PAT setup",
5
5
  "keywords": [
6
6
  "ai-agent",