swarmdo 1.19.0 → 1.29.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/.claude/helpers/auto-memory-hook.mjs +5 -1
- package/.claude-plugin/README.md +3 -3
- package/.claude-plugin/docs/INSTALLATION.md +1 -1
- package/.claude-plugin/docs/PLUGIN_SUMMARY.md +1 -1
- package/.claude-plugin/docs/QUICKSTART.md +3 -3
- package/.claude-plugin/hooks/hooks.json +19 -1
- package/.claude-plugin/marketplace.json +17 -17
- package/.claude-plugin/plugin.json +7 -39
- package/.claude-plugin/scripts/install.sh +3 -3
- package/.claude-plugin/scripts/swarmdo-hook.sh +1 -1
- package/.claude-plugin/scripts/verify.sh +2 -2
- package/README.md +10 -8
- package/package.json +1 -1
- package/v3/@swarmdo/cli/.claude/helpers/auto-memory-hook.mjs +5 -1
- package/v3/@swarmdo/cli/bin/cli.js +38 -0
- package/v3/@swarmdo/cli/dist/src/apply/apply.d.ts +8 -0
- package/v3/@swarmdo/cli/dist/src/apply/apply.js +70 -10
- package/v3/@swarmdo/cli/dist/src/changelog/changelog.js +26 -3
- package/v3/@swarmdo/cli/dist/src/codegraph/codegraph.d.ts +26 -7
- package/v3/@swarmdo/cli/dist/src/codegraph/codegraph.js +97 -13
- package/v3/@swarmdo/cli/dist/src/codegraph/store.d.ts +8 -1
- package/v3/@swarmdo/cli/dist/src/codegraph/store.js +78 -1
- package/v3/@swarmdo/cli/dist/src/commands/apply.js +17 -4
- package/v3/@swarmdo/cli/dist/src/commands/compact-snapshot.d.ts +17 -0
- package/v3/@swarmdo/cli/dist/src/commands/compact-snapshot.js +133 -0
- package/v3/@swarmdo/cli/dist/src/commands/cycles.js +4 -1
- package/v3/@swarmdo/cli/dist/src/commands/hotspots.js +5 -2
- package/v3/@swarmdo/cli/dist/src/commands/index.js +4 -3
- package/v3/@swarmdo/cli/dist/src/commands/pack.d.ts +8 -0
- package/v3/@swarmdo/cli/dist/src/commands/pack.js +28 -2
- package/v3/@swarmdo/cli/dist/src/commands/release.js +5 -1
- package/v3/@swarmdo/cli/dist/src/commands/testreport.js +2 -1
- package/v3/@swarmdo/cli/dist/src/compact/compact.js +17 -7
- package/v3/@swarmdo/cli/dist/src/compact-snapshot/compact-snapshot.d.ts +58 -0
- package/v3/@swarmdo/cli/dist/src/compact-snapshot/compact-snapshot.js +104 -0
- package/v3/@swarmdo/cli/dist/src/config-lint/lint.js +16 -2
- package/v3/@swarmdo/cli/dist/src/cycles/cycles.d.ts +12 -2
- package/v3/@swarmdo/cli/dist/src/cycles/cycles.js +5 -3
- package/v3/@swarmdo/cli/dist/src/env/env.d.ts +5 -2
- package/v3/@swarmdo/cli/dist/src/env/env.js +24 -3
- package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.d.ts +15 -0
- package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.js +33 -1
- package/v3/@swarmdo/cli/dist/src/init/helpers-generator.js +33 -3
- package/v3/@swarmdo/cli/dist/src/license/license.d.ts +31 -2
- package/v3/@swarmdo/cli/dist/src/license/license.js +133 -11
- package/v3/@swarmdo/cli/dist/src/pack/pack.d.ts +2 -3
- package/v3/@swarmdo/cli/dist/src/pack/pack.js +68 -12
- package/v3/@swarmdo/cli/dist/src/redact/redact.js +14 -2
- package/v3/@swarmdo/cli/dist/src/sbom/sbom.d.ts +16 -0
- package/v3/@swarmdo/cli/dist/src/sbom/sbom.js +33 -3
- package/v3/@swarmdo/cli/dist/src/swarmvector/model-prices.js +12 -2
- package/v3/@swarmdo/cli/dist/src/testreport/testreport.d.ts +14 -0
- package/v3/@swarmdo/cli/dist/src/testreport/testreport.js +82 -9
- package/v3/@swarmdo/cli/dist/src/transcript/export.d.ts +5 -0
- package/v3/@swarmdo/cli/dist/src/transcript/export.js +8 -1
- package/v3/@swarmdo/cli/dist/src/usage/claude-pricing.d.ts +12 -2
- package/v3/@swarmdo/cli/dist/src/usage/claude-pricing.js +45 -13
- package/v3/@swarmdo/cli/dist/src/usage/transcript-usage.js +5 -2
- package/v3/@swarmdo/cli/package.json +2 -2
|
@@ -35,14 +35,24 @@ export const MODEL_PRICES = {
|
|
|
35
35
|
// Mid tier
|
|
36
36
|
'anthropic/claude-haiku-4.5': { in: 1.00, out: 5.00 },
|
|
37
37
|
'openai/gpt-4.1': { in: 2.00, out: 8.00 },
|
|
38
|
-
// Strong tier
|
|
38
|
+
// Strong tier — current-gen Claude (first-party list prices; OpenRouter
|
|
39
|
+
// mirrors them for anthropic/* slugs). Opus dropped to $5/$25 at 4.5 and
|
|
40
|
+
// held there through 4.8 — do NOT price these at the legacy $15/$75.
|
|
41
|
+
'anthropic/claude-sonnet-5': { in: 3.00, out: 15.00 },
|
|
39
42
|
'anthropic/claude-sonnet-4-6': { in: 3.00, out: 15.00 },
|
|
43
|
+
'anthropic/claude-opus-4-8': { in: 5.00, out: 25.00 },
|
|
44
|
+
'anthropic/claude-opus-4-5': { in: 5.00, out: 25.00 },
|
|
45
|
+
'anthropic/claude-fable-5': { in: 10.00, out: 50.00 },
|
|
46
|
+
// Legacy Opus 4.0 kept — its $15/$75 is correct for THAT model, still a
|
|
47
|
+
// valid slug; the current opus tier is priced by claude-opus-4-8 above.
|
|
40
48
|
'anthropic/claude-opus-4': { in: 15.00, out: 75.00 },
|
|
41
49
|
// Tier-label fallbacks (when the trajectory only carries a coarse tier
|
|
42
50
|
// and not a concrete modelId — happens before iter 13 wiring landed).
|
|
51
|
+
// "opus tier" today resolves to Opus 4.8-class ($5/$25); pricing it at the
|
|
52
|
+
// retired $15/$75 made the cost-optimal router avoid opus 3x too eagerly.
|
|
43
53
|
haiku: { in: 1.00, out: 5.00 },
|
|
44
54
|
sonnet: { in: 3.00, out: 15.00 },
|
|
45
|
-
opus: { in:
|
|
55
|
+
opus: { in: 5.00, out: 25.00 },
|
|
46
56
|
inherit: { in: 3.00, out: 15.00 },
|
|
47
57
|
};
|
|
48
58
|
/**
|
|
@@ -28,6 +28,20 @@ export interface TestSummary {
|
|
|
28
28
|
total: number;
|
|
29
29
|
durationMs: number;
|
|
30
30
|
failures: TestFailure[];
|
|
31
|
+
/**
|
|
32
|
+
* TAP `# TODO` tests — "not expected to succeed" per the TAP spec, so a
|
|
33
|
+
* `not ok … # TODO` is a KNOWN-incomplete stub, counted here as a success,
|
|
34
|
+
* never a failure. Absent (0) for JUnit and TODO-free TAP.
|
|
35
|
+
*/
|
|
36
|
+
todo?: number;
|
|
37
|
+
/**
|
|
38
|
+
* True if a TAP `Bail out!` line aborted the run. The counts are then
|
|
39
|
+
* INCOMPLETE — the suite stopped early, so a "0 failed" here does NOT mean
|
|
40
|
+
* the suite passed. Callers/CI must treat this as a failure.
|
|
41
|
+
*/
|
|
42
|
+
bailedOut?: boolean;
|
|
43
|
+
/** Optional reason text following `Bail out!`. */
|
|
44
|
+
bailReason?: string;
|
|
31
45
|
}
|
|
32
46
|
/** Pull a `file:line` out of a stack-trace / assertion message. Pure. */
|
|
33
47
|
export declare function extractFileLine(text: string): {
|
|
@@ -11,16 +11,56 @@
|
|
|
11
11
|
* No XML dependency — well-formed JUnit escapes <>& in attribute values, so a
|
|
12
12
|
* focused tokenizer over <testcase>/<failure> is safe.
|
|
13
13
|
*/
|
|
14
|
-
/**
|
|
14
|
+
/** A numeric character reference → its char, or the original ref if the code
|
|
15
|
+
* point is out of the valid Unicode range (so a malformed entity never throws
|
|
16
|
+
* and leaks literally instead of aborting the parse). Pure. */
|
|
17
|
+
function fromCodePointSafe(n, original) {
|
|
18
|
+
return Number.isInteger(n) && n >= 0 && n <= 0x10ffff ? String.fromCodePoint(n) : original;
|
|
19
|
+
}
|
|
20
|
+
/** Decode the five predefined XML entities plus decimal AND hex numeric
|
|
21
|
+
* character references (`A`, ``). XML 1.0 allows both numeric forms;
|
|
22
|
+
* JUnit stack traces with ANSI colouring escape control chars as hex. Pure. */
|
|
15
23
|
function decodeXml(s) {
|
|
16
24
|
return s
|
|
17
25
|
.replace(/</g, '<')
|
|
18
26
|
.replace(/>/g, '>')
|
|
19
27
|
.replace(/"/g, '"')
|
|
20
28
|
.replace(/'/g, "'")
|
|
21
|
-
.replace(/&#(
|
|
29
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (m, h) => fromCodePointSafe(parseInt(h, 16), m))
|
|
30
|
+
.replace(/&#(\d+);/g, (m, d) => fromCodePointSafe(parseInt(d, 10), m))
|
|
22
31
|
.replace(/&/g, '&'); // last, so &lt; → < not <
|
|
23
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Decode an element's text content, honoring CDATA sections. Text OUTSIDE
|
|
35
|
+
* `<![CDATA[ … ]]>` is entity-decoded; text INSIDE is taken literally (per the
|
|
36
|
+
* XML spec, CDATA content is never entity-expanded). Maven Surefire/Failsafe
|
|
37
|
+
* wrap `<failure>`/`<error>` stack traces in CDATA, so without this the raw
|
|
38
|
+
* `<![CDATA[`/`]]>` markers leak into the failure message. Pure.
|
|
39
|
+
*/
|
|
40
|
+
function decodeXmlContent(s) {
|
|
41
|
+
const OPEN = '<![CDATA[';
|
|
42
|
+
const CLOSE = ']]>';
|
|
43
|
+
if (!s.includes(OPEN))
|
|
44
|
+
return decodeXml(s);
|
|
45
|
+
let out = '';
|
|
46
|
+
let i = 0;
|
|
47
|
+
while (i < s.length) {
|
|
48
|
+
const start = s.indexOf(OPEN, i);
|
|
49
|
+
if (start < 0) {
|
|
50
|
+
out += decodeXml(s.slice(i));
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
out += decodeXml(s.slice(i, start));
|
|
54
|
+
const end = s.indexOf(CLOSE, start + OPEN.length);
|
|
55
|
+
if (end < 0) {
|
|
56
|
+
out += s.slice(start + OPEN.length);
|
|
57
|
+
break;
|
|
58
|
+
} // unterminated → rest is literal
|
|
59
|
+
out += s.slice(start + OPEN.length, end);
|
|
60
|
+
i = end + CLOSE.length;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
24
64
|
/** Extract key="value" / key='value' attributes from a tag's attribute text. Pure. */
|
|
25
65
|
function parseAttrs(attrText) {
|
|
26
66
|
const out = {};
|
|
@@ -62,7 +102,7 @@ export function parseJUnit(xml) {
|
|
|
62
102
|
if (fail) {
|
|
63
103
|
failed++;
|
|
64
104
|
const fAttrs = parseAttrs(fail[2]);
|
|
65
|
-
const inner =
|
|
105
|
+
const inner = decodeXmlContent(fail[3] ?? '').trim();
|
|
66
106
|
const message = fAttrs.message || inner.split('\n')[0] || undefined;
|
|
67
107
|
// Prefer explicit file/line attrs (pytest, some emitters), else sniff the trace.
|
|
68
108
|
const loc = attrs.file
|
|
@@ -82,10 +122,20 @@ export function parseJUnit(xml) {
|
|
|
82
122
|
/** Parse TAP (Test Anything Protocol) output. Pure. */
|
|
83
123
|
export function parseTAP(text) {
|
|
84
124
|
const failures = [];
|
|
85
|
-
let passed = 0, failed = 0, skipped = 0;
|
|
125
|
+
let passed = 0, failed = 0, skipped = 0, todo = 0;
|
|
126
|
+
let bailedOut = false;
|
|
127
|
+
let bailReason;
|
|
86
128
|
const lines = text.split('\n');
|
|
87
129
|
for (let i = 0; i < lines.length; i++) {
|
|
88
130
|
const line = lines[i];
|
|
131
|
+
// `Bail out!` (column 1, per the TAP spec) aborts the run — stop parsing;
|
|
132
|
+
// anything after it is not a valid result.
|
|
133
|
+
const bail = line.match(/^Bail out!(?:\s+(.*))?$/);
|
|
134
|
+
if (bail) {
|
|
135
|
+
bailedOut = true;
|
|
136
|
+
bailReason = bail[1]?.trim() || undefined;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
89
139
|
const m = line.match(/^(ok|not ok)\b\s*(\d+)?\s*-?\s*(.*)$/);
|
|
90
140
|
if (!m)
|
|
91
141
|
continue;
|
|
@@ -98,6 +148,12 @@ export function parseTAP(text) {
|
|
|
98
148
|
skipped++;
|
|
99
149
|
continue;
|
|
100
150
|
}
|
|
151
|
+
// TODO tests are "not expected to succeed" — a `not ok … # TODO` is a known
|
|
152
|
+
// stub, not a real failure (TAP spec). Count it as todo, never a failure.
|
|
153
|
+
if (directive && /todo/i.test(directive[1])) {
|
|
154
|
+
todo++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
101
157
|
if (ok) {
|
|
102
158
|
passed++;
|
|
103
159
|
}
|
|
@@ -131,7 +187,7 @@ export function parseTAP(text) {
|
|
|
131
187
|
failures.push({ suite: '', name: desc || '(unnamed)', message, file, line: lineNo });
|
|
132
188
|
}
|
|
133
189
|
}
|
|
134
|
-
return { passed, failed, skipped, total: passed + failed + skipped, durationMs: 0, failures };
|
|
190
|
+
return { passed, failed, skipped, total: passed + failed + skipped + todo, durationMs: 0, failures, ...(todo && { todo }), ...(bailedOut && { bailedOut, bailReason }) };
|
|
135
191
|
}
|
|
136
192
|
/** Sniff the format from a path extension, then content. Pure. */
|
|
137
193
|
export function detectFormat(content, path) {
|
|
@@ -153,7 +209,7 @@ export function parseTestReport(content, format) {
|
|
|
153
209
|
}
|
|
154
210
|
/** Merge several summaries (multi-file globs). Pure. */
|
|
155
211
|
export function mergeSummaries(list) {
|
|
156
|
-
|
|
212
|
+
const merged = list.reduce((acc, s) => ({
|
|
157
213
|
passed: acc.passed + s.passed,
|
|
158
214
|
failed: acc.failed + s.failed,
|
|
159
215
|
skipped: acc.skipped + s.skipped,
|
|
@@ -161,14 +217,31 @@ export function mergeSummaries(list) {
|
|
|
161
217
|
durationMs: acc.durationMs + s.durationMs,
|
|
162
218
|
failures: acc.failures.concat(s.failures),
|
|
163
219
|
}), { passed: 0, failed: 0, skipped: 0, total: 0, durationMs: 0, failures: [] });
|
|
220
|
+
const todoSum = list.reduce((n, s) => n + (s.todo ?? 0), 0);
|
|
221
|
+
if (todoSum > 0)
|
|
222
|
+
merged.todo = todoSum;
|
|
223
|
+
// Any bailed file taints the whole run; keep the first reason seen.
|
|
224
|
+
const bailed = list.find((s) => s.bailedOut);
|
|
225
|
+
if (bailed) {
|
|
226
|
+
merged.bailedOut = true;
|
|
227
|
+
merged.bailReason = bailed.bailReason;
|
|
228
|
+
}
|
|
229
|
+
return merged;
|
|
164
230
|
}
|
|
165
231
|
/** Human-readable digest. Pure. */
|
|
166
232
|
export function formatSummary(s, opts = {}) {
|
|
167
|
-
const
|
|
168
|
-
|
|
233
|
+
const todoSeg = s.todo ? ` · ${s.todo} todo` : '';
|
|
234
|
+
const head = `${s.passed} passed · ${s.failed} failed · ${s.skipped} skipped${todoSeg} (${s.total} total, ${s.durationMs}ms)`;
|
|
235
|
+
const bailLine = s.bailedOut
|
|
236
|
+
? `⚠ suite ABORTED (Bail out!${s.bailReason ? `: ${s.bailReason}` : ''}) — results incomplete`
|
|
237
|
+
: '';
|
|
238
|
+
if (s.failures.length === 0) {
|
|
239
|
+
if (s.bailedOut)
|
|
240
|
+
return `${bailLine}\n${head}`;
|
|
169
241
|
return head + (s.failed === 0 ? ' ✓' : '');
|
|
242
|
+
}
|
|
170
243
|
const shown = opts.top && opts.top > 0 ? s.failures.slice(0, opts.top) : s.failures;
|
|
171
|
-
const lines = [head, ''];
|
|
244
|
+
const lines = s.bailedOut ? [bailLine, head, ''] : [head, ''];
|
|
172
245
|
for (const f of shown) {
|
|
173
246
|
const where = f.file ? `${f.file}${f.line ? `:${f.line}` : ''}` : '';
|
|
174
247
|
lines.push(`✗ ${f.suite ? f.suite + ' › ' : ''}${f.name}${where ? ` (${where})` : ''}`);
|
|
@@ -45,6 +45,11 @@ export interface RenderOptions {
|
|
|
45
45
|
export declare function cleanUserText(s: string): string;
|
|
46
46
|
/** Flatten tool_result content (string | array of {text}) to text. */
|
|
47
47
|
export declare function contentToText(content: unknown): string;
|
|
48
|
+
/** Count the conversational turns in a rendered body by matching only the
|
|
49
|
+
* exact role-heading lines. Anchored to end-of-line so `### Foo` markdown
|
|
50
|
+
* headings written *inside* a user/assistant message don't inflate the count.
|
|
51
|
+
* Pure. */
|
|
52
|
+
export declare function countRenderedTurns(markdown: string): number;
|
|
48
53
|
/** Render an ordered list of parsed transcript lines into Markdown. */
|
|
49
54
|
export declare function renderTranscriptMarkdown(lines: RawTranscriptLine[], opts?: RenderOptions): string;
|
|
50
55
|
export interface SessionSummary {
|
|
@@ -92,6 +92,13 @@ function renderLine(role, content, opts) {
|
|
|
92
92
|
sections.push(results.join('\n'));
|
|
93
93
|
return sections.join('\n\n');
|
|
94
94
|
}
|
|
95
|
+
/** Count the conversational turns in a rendered body by matching only the
|
|
96
|
+
* exact role-heading lines. Anchored to end-of-line so `### Foo` markdown
|
|
97
|
+
* headings written *inside* a user/assistant message don't inflate the count.
|
|
98
|
+
* Pure. */
|
|
99
|
+
export function countRenderedTurns(markdown) {
|
|
100
|
+
return (markdown.match(/^### (?:🤖 Assistant|👤 User)$/gm) || []).length;
|
|
101
|
+
}
|
|
95
102
|
/** Render an ordered list of parsed transcript lines into Markdown. */
|
|
96
103
|
export function renderTranscriptMarkdown(lines, opts = {}) {
|
|
97
104
|
const resolved = {
|
|
@@ -202,7 +209,7 @@ export function exportSession(idOrLatest, opts = {}) {
|
|
|
202
209
|
const sessionId = sessionIdFromFile(file);
|
|
203
210
|
const project = path.basename(path.dirname(file));
|
|
204
211
|
const body = renderTranscriptMarkdown(lines, opts);
|
|
205
|
-
const turns = (body
|
|
212
|
+
const turns = countRenderedTurns(body);
|
|
206
213
|
const header = `# Claude Code session \`${sessionId}\`\n\n- Project: \`${project}\`\n- Turns: ${turns}\n\n---\n\n`;
|
|
207
214
|
return { markdown: header + body, sessionId, project, turns, file };
|
|
208
215
|
}
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
export interface TranscriptModelPrice {
|
|
17
17
|
in: number;
|
|
18
18
|
out: number;
|
|
19
|
+
/** 5-minute-TTL cache write (1.25× base input) */
|
|
19
20
|
cacheWrite: number;
|
|
21
|
+
/** 1-hour-TTL cache write (2× base input) */
|
|
22
|
+
cacheWrite1h: number;
|
|
20
23
|
cacheRead: number;
|
|
21
24
|
}
|
|
22
25
|
/**
|
|
@@ -26,13 +29,20 @@ export interface TranscriptModelPrice {
|
|
|
26
29
|
* 'claude-sonnet-4-6@20260115' → 'claude-sonnet-4-6' (Vertex)
|
|
27
30
|
*/
|
|
28
31
|
export declare function normalizeTranscriptModelId(raw: string): string;
|
|
29
|
-
/**
|
|
30
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Longest-prefix price lookup; undefined = unpriced (report it, don't guess).
|
|
34
|
+
* Pass the transcript entry's timestamp as `atMs` to get date-ranged promo
|
|
35
|
+
* rates (e.g. Sonnet 5 intro pricing); omitted → sticker price.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveTranscriptPrice(rawModelId: string, atMs?: number): TranscriptModelPrice | undefined;
|
|
31
38
|
/** Token counts for one billed API response. */
|
|
32
39
|
export interface TokenBundle {
|
|
33
40
|
inputTokens: number;
|
|
34
41
|
outputTokens: number;
|
|
42
|
+
/** TOTAL cache-write tokens (5-min + 1-hour). */
|
|
35
43
|
cacheWriteTokens: number;
|
|
44
|
+
/** The 1-hour-TTL SUBSET of cacheWriteTokens (≤ cacheWriteTokens). Default 0 → all writes priced at the 5-min rate. */
|
|
45
|
+
cacheWrite1hTokens?: number;
|
|
36
46
|
cacheReadTokens: number;
|
|
37
47
|
}
|
|
38
48
|
/** USD for one response at the given rates. */
|
|
@@ -16,19 +16,37 @@
|
|
|
16
16
|
* Price families, matched by LONGEST normalized-id prefix so dated ids like
|
|
17
17
|
* `claude-sonnet-4-6-20260115` resolve without per-snapshot entries.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Opus pricing split (#41): Opus 4.0/4.1 were $15/$75, but Opus 4.5 dropped
|
|
20
|
+
* the tier to $5/$25 and 4.6/4.7/4.8 kept it. The bare 'claude-opus-4' entry
|
|
21
|
+
* stays at legacy rates for 4.0/4.1; the longer per-version prefixes win for
|
|
22
|
+
* 4.5+ via longest-prefix matching.
|
|
21
23
|
*/
|
|
22
24
|
const PRICE_FAMILIES = {
|
|
23
|
-
'claude-
|
|
24
|
-
'claude-
|
|
25
|
-
'claude-
|
|
26
|
-
'claude-
|
|
27
|
-
'claude-
|
|
28
|
-
'claude-
|
|
29
|
-
'claude-
|
|
30
|
-
'claude-
|
|
25
|
+
'claude-fable-5': { in: 10, out: 50, cacheWrite: 12.5, cacheWrite1h: 20, cacheRead: 1 },
|
|
26
|
+
'claude-mythos-5': { in: 10, out: 50, cacheWrite: 12.5, cacheWrite1h: 20, cacheRead: 1 },
|
|
27
|
+
'claude-opus-4': { in: 15, out: 75, cacheWrite: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
|
|
28
|
+
'claude-opus-4-5': { in: 5, out: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
29
|
+
'claude-opus-4-6': { in: 5, out: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
30
|
+
'claude-opus-4-7': { in: 5, out: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
31
|
+
'claude-opus-4-8': { in: 5, out: 25, cacheWrite: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
32
|
+
'claude-sonnet-5': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
33
|
+
'claude-sonnet-4': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
34
|
+
'claude-haiku-4': { in: 1, out: 5, cacheWrite: 1.25, cacheWrite1h: 2, cacheRead: 0.1 },
|
|
35
|
+
'claude-3-7-sonnet': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
36
|
+
'claude-3-5-sonnet': { in: 3, out: 15, cacheWrite: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
37
|
+
'claude-3-5-haiku': { in: 0.8, out: 4, cacheWrite: 1, cacheWrite1h: 1.6, cacheRead: 0.08 },
|
|
38
|
+
'claude-3-opus': { in: 15, out: 75, cacheWrite: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
|
|
39
|
+
'claude-3-haiku': { in: 0.25, out: 1.25, cacheWrite: 0.3, cacheWrite1h: 0.5, cacheRead: 0.03 },
|
|
31
40
|
};
|
|
41
|
+
const PROMOS = [
|
|
42
|
+
{
|
|
43
|
+
// Sonnet 5 introductory pricing: $2/$10 per Mtok through 2026-08-31.
|
|
44
|
+
prefix: 'claude-sonnet-5',
|
|
45
|
+
fromMs: Date.UTC(2026, 5, 30), // launched 2026-06-30
|
|
46
|
+
untilMs: Date.UTC(2026, 7, 31, 23, 59, 59, 999),
|
|
47
|
+
price: { in: 2, out: 10, cacheWrite: 2.5, cacheWrite1h: 4, cacheRead: 0.2 },
|
|
48
|
+
},
|
|
49
|
+
];
|
|
32
50
|
/**
|
|
33
51
|
* Reduce a transcript/gateway model id to the bare Anthropic id:
|
|
34
52
|
* 'anthropic/claude-opus-4-8' → 'claude-opus-4-8'
|
|
@@ -47,9 +65,20 @@ export function normalizeTranscriptModelId(raw) {
|
|
|
47
65
|
id = id.replace(/-v\d+$/, ''); // Bedrock '-v2'
|
|
48
66
|
return id;
|
|
49
67
|
}
|
|
50
|
-
/**
|
|
51
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Longest-prefix price lookup; undefined = unpriced (report it, don't guess).
|
|
70
|
+
* Pass the transcript entry's timestamp as `atMs` to get date-ranged promo
|
|
71
|
+
* rates (e.g. Sonnet 5 intro pricing); omitted → sticker price.
|
|
72
|
+
*/
|
|
73
|
+
export function resolveTranscriptPrice(rawModelId, atMs) {
|
|
52
74
|
const id = normalizeTranscriptModelId(rawModelId);
|
|
75
|
+
if (atMs !== undefined) {
|
|
76
|
+
for (const promo of PROMOS) {
|
|
77
|
+
if (id.startsWith(promo.prefix) && atMs >= promo.fromMs && atMs <= promo.untilMs) {
|
|
78
|
+
return promo.price;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
53
82
|
let best;
|
|
54
83
|
let bestLen = 0;
|
|
55
84
|
for (const [prefix, price] of Object.entries(PRICE_FAMILIES)) {
|
|
@@ -62,9 +91,12 @@ export function resolveTranscriptPrice(rawModelId) {
|
|
|
62
91
|
}
|
|
63
92
|
/** USD for one response at the given rates. */
|
|
64
93
|
export function transcriptCostUsd(price, t) {
|
|
94
|
+
const cacheWrite1h = t.cacheWrite1hTokens ?? 0;
|
|
95
|
+
const cacheWrite5m = Math.max(0, t.cacheWriteTokens - cacheWrite1h);
|
|
65
96
|
return ((t.inputTokens * price.in +
|
|
66
97
|
t.outputTokens * price.out +
|
|
67
|
-
|
|
98
|
+
cacheWrite5m * price.cacheWrite +
|
|
99
|
+
cacheWrite1h * price.cacheWrite1h +
|
|
68
100
|
t.cacheReadTokens * price.cacheRead) / 1_000_000);
|
|
69
101
|
}
|
|
70
102
|
//# sourceMappingURL=claude-pricing.js.map
|
|
@@ -177,15 +177,18 @@ function toUsageEvent(line, project, seen, unpriced) {
|
|
|
177
177
|
cacheWriteTokens: usage.cache_creation_input_tokens ?? 0,
|
|
178
178
|
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
|
|
179
179
|
};
|
|
180
|
+
// 1-hour-TTL cache writes cost 2× base input, not the 1.25× 5-min rate. Recent
|
|
181
|
+
// transcripts split them in `cache_creation`; older ones don't (→ all 5-min).
|
|
182
|
+
const cacheWrite1hTokens = usage.cache_creation?.ephemeral_1h_input_tokens ?? 0;
|
|
180
183
|
let costUsd;
|
|
181
184
|
let costSource;
|
|
182
|
-
const price = resolveTranscriptPrice(model);
|
|
185
|
+
const price = resolveTranscriptPrice(model, ts.getTime());
|
|
183
186
|
if (typeof line.costUSD === 'number') {
|
|
184
187
|
costUsd = line.costUSD;
|
|
185
188
|
costSource = 'transcript';
|
|
186
189
|
}
|
|
187
190
|
else if (price) {
|
|
188
|
-
costUsd = transcriptCostUsd(price, tokens);
|
|
191
|
+
costUsd = transcriptCostUsd(price, { ...tokens, cacheWrite1hTokens });
|
|
189
192
|
costSource = 'computed';
|
|
190
193
|
}
|
|
191
194
|
else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmdo/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Swarmdo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"test:pattern-store": "npx tsx src/transfer/store/tests/standalone-test.ts",
|
|
84
84
|
"postinstall": "node ./scripts/postinstall.cjs",
|
|
85
85
|
"build:standalone": "node scripts/build-standalone.mjs",
|
|
86
|
-
"prepublishOnly": "node -e \"if (process.env.npm_command === 'publish') { console.error('\\n@swarmdo/cli must be published from the self-contained staging dir (file:../ siblings do not ship from here):\\n\\n npm run build && npm run build:standalone && npm publish dist-standalone/\\n'); process.exit(1); } // pnpm runs prepublishOnly when packing local directory deps during install
|
|
86
|
+
"prepublishOnly": "node -e \"if (process.env.npm_command === 'publish') { console.error('\\n@swarmdo/cli must be published from the self-contained staging dir (file:../ siblings do not ship from here):\\n\\n npm run build && npm run build:standalone && npm publish dist-standalone/\\n'); process.exit(1); } // pnpm runs prepublishOnly when packing local directory deps during install — that path must not fail\""
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@types/better-sqlite3": "^7.6.0",
|