@use-aistack/cli 0.10.0 → 0.10.2
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/README.md +1 -0
- package/dist/index.js +628 -297
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,8 +3,352 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
|
+
// ../pricing/src/table.ts
|
|
7
|
+
var PROVIDER_SEPARATOR = ":";
|
|
8
|
+
var LOCAL_PRICING_TABLE_VERSION = "local-no-charge";
|
|
9
|
+
var PROVIDER_VENDOR = {
|
|
10
|
+
anthropic: "anthropic",
|
|
11
|
+
openai: "openai",
|
|
12
|
+
google: "google"
|
|
13
|
+
};
|
|
14
|
+
var LOCAL_PROVIDERS = /* @__PURE__ */ new Set([
|
|
15
|
+
"ollama",
|
|
16
|
+
"lmstudio",
|
|
17
|
+
"llama.cpp",
|
|
18
|
+
"llamacpp",
|
|
19
|
+
"local"
|
|
20
|
+
]);
|
|
21
|
+
var FREE_PERIOD = {
|
|
22
|
+
from: null,
|
|
23
|
+
to: null,
|
|
24
|
+
input: 0,
|
|
25
|
+
output: 0,
|
|
26
|
+
cacheRead: 0,
|
|
27
|
+
cacheWrite5m: 0,
|
|
28
|
+
cacheWrite1h: 0,
|
|
29
|
+
source: LOCAL_PRICING_TABLE_VERSION
|
|
30
|
+
};
|
|
31
|
+
function splitModelKey(modelKey) {
|
|
32
|
+
const at = modelKey.indexOf(PROVIDER_SEPARATOR);
|
|
33
|
+
if (at === -1) return { provider: null, model: modelKey };
|
|
34
|
+
return {
|
|
35
|
+
provider: modelKey.slice(0, at),
|
|
36
|
+
model: modelKey.slice(at + PROVIDER_SEPARATOR.length)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
var key = (slug, provider) => `${provider ?? ""}\0${slug}`;
|
|
40
|
+
var PriceIndex = class {
|
|
41
|
+
periods = /* @__PURE__ */ new Map();
|
|
42
|
+
vendors = /* @__PURE__ */ new Map();
|
|
43
|
+
id;
|
|
44
|
+
constructor(table) {
|
|
45
|
+
this.id = table.id;
|
|
46
|
+
const groups = /* @__PURE__ */ new Map();
|
|
47
|
+
for (const row of table.rows) {
|
|
48
|
+
const k = key(row.modelSlug, row.provider);
|
|
49
|
+
const g = groups.get(k) ?? [];
|
|
50
|
+
g.push(row);
|
|
51
|
+
groups.set(k, g);
|
|
52
|
+
if (row.vendor && row.provider === void 0) {
|
|
53
|
+
this.vendors.set(row.modelSlug, row.vendor);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const [k, rows] of groups) {
|
|
57
|
+
rows.sort((a, b) => a.from - b.from);
|
|
58
|
+
this.periods.set(
|
|
59
|
+
k,
|
|
60
|
+
rows.map((r, i) => ({
|
|
61
|
+
from: r.from === 0 ? null : r.from,
|
|
62
|
+
to: i + 1 < rows.length ? rows[i + 1].from : null,
|
|
63
|
+
input: r.input,
|
|
64
|
+
output: r.output,
|
|
65
|
+
cacheRead: r.cacheRead ?? 0,
|
|
66
|
+
cacheWrite5m: r.cacheWrite5m ?? 0,
|
|
67
|
+
cacheWrite1h: r.cacheWrite1h ?? 0,
|
|
68
|
+
source: r.source
|
|
69
|
+
}))
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** The vendor a bare row belongs to, when the table says. */
|
|
74
|
+
vendorOf(slug) {
|
|
75
|
+
return this.vendors.get(slug) ?? null;
|
|
76
|
+
}
|
|
77
|
+
/** True when the table holds any row at all for this (model, provider). */
|
|
78
|
+
has(slug, provider) {
|
|
79
|
+
return this.periods.has(key(slug, provider));
|
|
80
|
+
}
|
|
81
|
+
/** The periods for exactly this (model, provider). */
|
|
82
|
+
rowsFor(slug, provider) {
|
|
83
|
+
return this.periods.get(key(slug, provider)) ?? [];
|
|
84
|
+
}
|
|
85
|
+
get size() {
|
|
86
|
+
return this.periods.size;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var Pricer = class {
|
|
90
|
+
constructor(layers, vendorHint = () => null) {
|
|
91
|
+
this.layers = layers;
|
|
92
|
+
this.vendorHint = vendorHint;
|
|
93
|
+
}
|
|
94
|
+
/** The ids of the layers, in lookup order. */
|
|
95
|
+
get tableIds() {
|
|
96
|
+
return this.layers.map((l) => l.id);
|
|
97
|
+
}
|
|
98
|
+
vendorOf(slug) {
|
|
99
|
+
for (const layer of this.layers) {
|
|
100
|
+
const v = layer.vendorOf(slug);
|
|
101
|
+
if (v) return v;
|
|
102
|
+
}
|
|
103
|
+
return this.vendorHint(slug);
|
|
104
|
+
}
|
|
105
|
+
firstLayerWith(slug, provider) {
|
|
106
|
+
return this.layers.find((l) => l.has(slug, provider)) ?? null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Every period that applies to a pricing key, or an empty list when none
|
|
110
|
+
* can be cited.
|
|
111
|
+
*
|
|
112
|
+
* A bare key is the vendor's own rate. A local provider is free. A provider
|
|
113
|
+
* with rows of its own uses them. A provider that IS the vendor reaches the
|
|
114
|
+
* vendor's bare rows. Anything else (a gateway, an unknown provider) holds
|
|
115
|
+
* no rate.
|
|
116
|
+
*/
|
|
117
|
+
periodsFor(modelKey) {
|
|
118
|
+
const { provider, model } = splitModelKey(modelKey);
|
|
119
|
+
if (provider === null) {
|
|
120
|
+
return this.firstLayerWith(model, null)?.rowsFor(model, null) ?? [];
|
|
121
|
+
}
|
|
122
|
+
if (LOCAL_PROVIDERS.has(provider)) return [FREE_PERIOD];
|
|
123
|
+
const own = this.firstLayerWith(model, provider);
|
|
124
|
+
if (own) return own.rowsFor(model, provider);
|
|
125
|
+
const vendor = PROVIDER_VENDOR[provider];
|
|
126
|
+
if (!vendor || this.vendorOf(model) !== vendor) return [];
|
|
127
|
+
return this.firstLayerWith(model, null)?.rowsFor(model, null) ?? [];
|
|
128
|
+
}
|
|
129
|
+
isLocal(modelKey) {
|
|
130
|
+
const { provider } = splitModelKey(modelKey);
|
|
131
|
+
return provider !== null && LOCAL_PROVIDERS.has(provider);
|
|
132
|
+
}
|
|
133
|
+
isPriced(modelKey) {
|
|
134
|
+
return this.periodsFor(modelKey).length > 0;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The rate in effect at `atMs`, or `null` when the model is unknown or the
|
|
138
|
+
* timestamp predates every period. A `null` timestamp also yields `null`:
|
|
139
|
+
* inventing a price for an undated record would attribute the wrong rate.
|
|
140
|
+
*/
|
|
141
|
+
priceAt(modelKey, atMs) {
|
|
142
|
+
if (atMs === null) return null;
|
|
143
|
+
for (const p8 of this.periodsFor(modelKey)) {
|
|
144
|
+
if ((p8.from === null || atMs >= p8.from) && (p8.to === null || atMs < p8.to)) {
|
|
145
|
+
return p8;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/** Every rate that applies anywhere inside `[fromMs, toMs]`. */
|
|
151
|
+
periodsInWindow(modelKey, fromMs, toMs) {
|
|
152
|
+
return this.periodsFor(modelKey).filter(
|
|
153
|
+
(p8) => (p8.from === null || p8.from <= toMs) && (p8.to === null || p8.to > fromMs)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The citation for this key: the source of the period in effect at `atMs`,
|
|
158
|
+
* or of the latest period when no time is given. `null` when unpriced.
|
|
159
|
+
*/
|
|
160
|
+
tableFor(modelKey, atMs) {
|
|
161
|
+
const periods = this.periodsFor(modelKey);
|
|
162
|
+
if (periods.length === 0) return null;
|
|
163
|
+
if (atMs !== void 0) return this.priceAt(modelKey, atMs)?.source ?? null;
|
|
164
|
+
return periods[periods.length - 1].source;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function parsePriceTable(body) {
|
|
168
|
+
if (typeof body !== "object" || body === null) return null;
|
|
169
|
+
const b = body;
|
|
170
|
+
if (typeof b.id !== "string" || !Array.isArray(b.rows)) return null;
|
|
171
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
172
|
+
const opt = (v) => num(v) ? v : void 0;
|
|
173
|
+
const rows = [];
|
|
174
|
+
for (const raw of b.rows) {
|
|
175
|
+
const r = raw;
|
|
176
|
+
if (typeof r?.modelSlug !== "string" || r.modelSlug.length === 0 || !num(r.from) || !num(r.input) || !num(r.output) || typeof r.source !== "string") {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const vendor = r.vendor === "anthropic" || r.vendor === "openai" || r.vendor === "google" || r.vendor === "local" ? r.vendor : void 0;
|
|
180
|
+
rows.push({
|
|
181
|
+
modelSlug: r.modelSlug,
|
|
182
|
+
...typeof r.provider === "string" && r.provider.length > 0 ? { provider: r.provider } : {},
|
|
183
|
+
from: r.from,
|
|
184
|
+
input: r.input,
|
|
185
|
+
output: r.output,
|
|
186
|
+
...opt(r.cacheRead) !== void 0 ? { cacheRead: r.cacheRead } : {},
|
|
187
|
+
...opt(r.cacheWrite5m) !== void 0 ? { cacheWrite5m: r.cacheWrite5m } : {},
|
|
188
|
+
...opt(r.cacheWrite1h) !== void 0 ? { cacheWrite1h: r.cacheWrite1h } : {},
|
|
189
|
+
source: r.source,
|
|
190
|
+
...vendor ? { vendor } : {}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (rows.length === 0) return null;
|
|
194
|
+
return { id: b.id, rows };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ../pricing/src/index.ts
|
|
198
|
+
var PRICING_TABLE_VERSION = "anthropic-list-2026-07-25";
|
|
199
|
+
var OPENAI_PRICING_TABLE_VERSION = "openai-list-2026-08-02";
|
|
200
|
+
var GOOGLE_PRICING_TABLE_VERSION = "google-list-2026-08-09";
|
|
201
|
+
var BUNDLED_PRICE_TABLE_ID = "bundled-2026-08-29";
|
|
202
|
+
var CACHE_WRITE_5M_MULTIPLIER = 1.25;
|
|
203
|
+
var CACHE_WRITE_1H_MULTIPLIER = 2;
|
|
204
|
+
var CACHE_READ_MULTIPLIER = 0.1;
|
|
205
|
+
var SONNET_5_INTRO_ENDS_MS = Date.UTC(2026, 8, 1);
|
|
206
|
+
var DEFAULT_CACHE_MULTIPLIERS = {
|
|
207
|
+
write5m: CACHE_WRITE_5M_MULTIPLIER,
|
|
208
|
+
write1h: CACHE_WRITE_1H_MULTIPLIER,
|
|
209
|
+
read: CACHE_READ_MULTIPLIER
|
|
210
|
+
};
|
|
211
|
+
var GOOGLE_CACHE_MULTIPLIERS = {
|
|
212
|
+
write5m: 1,
|
|
213
|
+
write1h: 1,
|
|
214
|
+
read: 0.1
|
|
215
|
+
};
|
|
216
|
+
var anthropic = (periods) => ({
|
|
217
|
+
vendor: "anthropic",
|
|
218
|
+
table: PRICING_TABLE_VERSION,
|
|
219
|
+
periods,
|
|
220
|
+
cache: DEFAULT_CACHE_MULTIPLIERS
|
|
221
|
+
});
|
|
222
|
+
var openai = (periods) => ({
|
|
223
|
+
vendor: "openai",
|
|
224
|
+
table: OPENAI_PRICING_TABLE_VERSION,
|
|
225
|
+
periods,
|
|
226
|
+
cache: DEFAULT_CACHE_MULTIPLIERS
|
|
227
|
+
});
|
|
228
|
+
var google = (periods) => ({
|
|
229
|
+
vendor: "google",
|
|
230
|
+
table: GOOGLE_PRICING_TABLE_VERSION,
|
|
231
|
+
periods,
|
|
232
|
+
cache: GOOGLE_CACHE_MULTIPLIERS
|
|
233
|
+
});
|
|
234
|
+
var flat = (input, output) => [
|
|
235
|
+
{ from: null, input, output }
|
|
236
|
+
];
|
|
237
|
+
var PRICES = {
|
|
238
|
+
"claude-fable-5": anthropic(flat(10, 50)),
|
|
239
|
+
"claude-mythos-5": anthropic(flat(10, 50)),
|
|
240
|
+
"claude-opus-5": anthropic(flat(5, 25)),
|
|
241
|
+
"claude-opus-4-8": anthropic(flat(5, 25)),
|
|
242
|
+
"claude-opus-4-7": anthropic(flat(5, 25)),
|
|
243
|
+
"claude-opus-4-6": anthropic(flat(5, 25)),
|
|
244
|
+
"claude-sonnet-5": anthropic([
|
|
245
|
+
{ from: null, input: 2, output: 10 },
|
|
246
|
+
{ from: SONNET_5_INTRO_ENDS_MS, input: 3, output: 15 }
|
|
247
|
+
]),
|
|
248
|
+
"claude-sonnet-4-6": anthropic(flat(3, 15)),
|
|
249
|
+
"claude-haiku-4-5": anthropic(flat(1, 5)),
|
|
250
|
+
// Fast mode (research preview) - Claude API only, Opus 5 / Opus 4.8 only.
|
|
251
|
+
// Opus 4.7 fast mode was removed, so there is deliberately no 4-7 entry.
|
|
252
|
+
"claude-opus-5#fast": anthropic(flat(10, 50)),
|
|
253
|
+
"claude-opus-4-8#fast": anthropic(flat(10, 50)),
|
|
254
|
+
// OpenAI (Codex) - standard-context tier (<272K; observed context window is
|
|
255
|
+
// 258,400).
|
|
256
|
+
"gpt-5.5": openai(flat(5, 30)),
|
|
257
|
+
"gpt-5.4": openai(flat(2.5, 15)),
|
|
258
|
+
"gpt-5.4-mini": openai(flat(0.75, 4.5)),
|
|
259
|
+
"gpt-5.3-codex": openai(flat(1.75, 14)),
|
|
260
|
+
// The gpt-5.6 family launched 2026-07-29; Terra and Luna were repriced on
|
|
261
|
+
// 2026-07-30 (-20% / -80%). The one-day launch rates are not on the list
|
|
262
|
+
// page and are NOT encoded - a July-29 Terra/Luna record underprices for
|
|
263
|
+
// one day rather than carrying a rate we cannot cite (#72).
|
|
264
|
+
// Sol: models.dev reports $4 / $20 on 2026-08-29; the earlier $5 / $30 is
|
|
265
|
+
// not dated, so the lower rate prices the whole period (lower bound).
|
|
266
|
+
"gpt-5.6-sol": openai(flat(4, 20)),
|
|
267
|
+
"gpt-5.6-terra": openai(flat(2, 12)),
|
|
268
|
+
"gpt-5.6-luna": openai(flat(0.2, 1.2)),
|
|
269
|
+
// NOT on OpenAI's list page - an internal Codex routing label with no
|
|
270
|
+
// official price (openai/codex#20981). Rate is the aggregator consensus
|
|
271
|
+
// ($2.50 / $15.00), scoped in explicitly by ticket #72 because it carries
|
|
272
|
+
// real token volume in Codex rollouts. A priced lane, see PRICED_LANES.
|
|
273
|
+
"codex-auto-review": openai(flat(2.5, 15)),
|
|
274
|
+
// Google (opencode, pi-mono) - Standard tier. Where a model is
|
|
275
|
+
// context-tiered, the <=200K rate is encoded, exactly as the OpenAI rows
|
|
276
|
+
// encode the standard-context tier: the payload carries no per-response
|
|
277
|
+
// context length, so the cheaper side keeps the figure a lower bound.
|
|
278
|
+
"gemini-3.1-pro-preview": google(flat(2, 12)),
|
|
279
|
+
// models.dev reports $0.75 / $3.75 on 2026-08-29 (was $1.5 / $7.5).
|
|
280
|
+
"gemini-3.6-flash": google(flat(0.75, 3.75)),
|
|
281
|
+
"gemini-3.5-flash": google(flat(1.5, 9)),
|
|
282
|
+
"gemini-3-flash-preview": google(flat(0.5, 3)),
|
|
283
|
+
"gemini-2.5-pro": google(flat(1.25, 10)),
|
|
284
|
+
"gemini-2.5-flash": google(flat(0.3, 2.5)),
|
|
285
|
+
// RETIRED from Google's list page by 2026-08-09, and still the largest
|
|
286
|
+
// single block of Google tokens measured in #122. Encoded at its launch
|
|
287
|
+
// rate: real volume, a rate we can name. Announcement rate, <=200K tier.
|
|
288
|
+
"gemini-3-pro-preview": google(flat(2, 12)),
|
|
289
|
+
// A real Anthropic model with no row until #123. Measured in #122 as
|
|
290
|
+
// `claude-opus-4-5-20251101`, which the dated-suffix rule strips to this key.
|
|
291
|
+
"claude-opus-4-5": anthropic(flat(5, 25))
|
|
292
|
+
};
|
|
293
|
+
function bundledPriceTable() {
|
|
294
|
+
const rows = [];
|
|
295
|
+
for (const [modelSlug, entry] of Object.entries(PRICES)) {
|
|
296
|
+
for (const p8 of entry.periods) {
|
|
297
|
+
rows.push({
|
|
298
|
+
modelSlug,
|
|
299
|
+
from: p8.from ?? 0,
|
|
300
|
+
input: p8.input,
|
|
301
|
+
output: p8.output,
|
|
302
|
+
cacheRead: p8.input * entry.cache.read,
|
|
303
|
+
cacheWrite5m: p8.input * entry.cache.write5m,
|
|
304
|
+
cacheWrite1h: p8.input * entry.cache.write1h,
|
|
305
|
+
source: entry.table,
|
|
306
|
+
vendor: entry.vendor
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return { id: BUNDLED_PRICE_TABLE_ID, rows };
|
|
311
|
+
}
|
|
312
|
+
var BUNDLED_INDEX = new PriceIndex(bundledPriceTable());
|
|
313
|
+
var BUNDLED_PRICER = new Pricer([BUNDLED_INDEX]);
|
|
314
|
+
function layeredPricer(table, vendorHint) {
|
|
315
|
+
return new Pricer([new PriceIndex(table), BUNDLED_INDEX], vendorHint);
|
|
316
|
+
}
|
|
317
|
+
var active = BUNDLED_PRICER;
|
|
318
|
+
function setActivePricer(pricer) {
|
|
319
|
+
active = pricer ?? BUNDLED_PRICER;
|
|
320
|
+
}
|
|
321
|
+
function modelKeyFor(provider, model) {
|
|
322
|
+
return `${provider}${PROVIDER_SEPARATOR}${model}`;
|
|
323
|
+
}
|
|
324
|
+
function normalizeModel(model) {
|
|
325
|
+
const { provider, model: bare } = splitModelKey(model);
|
|
326
|
+
const [base, suffix] = bare.split("#");
|
|
327
|
+
const stripped = base.replace(/-\d{8}$/, "");
|
|
328
|
+
const normalized = suffix ? `${stripped}#${suffix}` : stripped;
|
|
329
|
+
return provider === null ? normalized : modelKeyFor(provider, normalized);
|
|
330
|
+
}
|
|
331
|
+
function baseModelId(modelKey) {
|
|
332
|
+
return modelKey.split("#")[0];
|
|
333
|
+
}
|
|
334
|
+
function isPricedModel(modelKey) {
|
|
335
|
+
return active.isPriced(modelKey);
|
|
336
|
+
}
|
|
337
|
+
function pricingTableFor(modelKey, atMs) {
|
|
338
|
+
return active.tableFor(modelKey, atMs);
|
|
339
|
+
}
|
|
340
|
+
function costAtPeriod(p8, t) {
|
|
341
|
+
const M = 1e6;
|
|
342
|
+
return (t.input * p8.input + t.output * p8.output + (t.cacheWrite5m + t.cacheWriteUnsplit) * p8.cacheWrite5m + t.cacheWrite1h * p8.cacheWrite1h + t.cacheRead * p8.cacheRead) / M;
|
|
343
|
+
}
|
|
344
|
+
function apiEquivalentCost(modelKey, t, atMs) {
|
|
345
|
+
const p8 = active.priceAt(modelKey, atMs);
|
|
346
|
+
if (!p8) return null;
|
|
347
|
+
return costAtPeriod(p8, t);
|
|
348
|
+
}
|
|
349
|
+
|
|
6
350
|
// src/version.ts
|
|
7
|
-
var CLI_VERSION = true ? "0.10.
|
|
351
|
+
var CLI_VERSION = true ? "0.10.2" : "0.0.0-dev";
|
|
8
352
|
|
|
9
353
|
// src/api.ts
|
|
10
354
|
var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
|
|
@@ -34,15 +378,19 @@ function failure(what, res) {
|
|
|
34
378
|
}
|
|
35
379
|
return new Error(`${what}: ${res.status}`);
|
|
36
380
|
}
|
|
37
|
-
async function authStart(machineName) {
|
|
381
|
+
async function authStart(machineName, machineNameReadOnly = false, options = {}) {
|
|
38
382
|
const res = await request("/api/cli/auth/start", {
|
|
39
383
|
method: "POST",
|
|
384
|
+
...options.replaceToken ? { headers: authHeaders(options.replaceToken) } : {},
|
|
40
385
|
// `cliVersion` rides along so `cli_login_completed` can report which
|
|
41
386
|
// version linked the machine (#78). The server carries it on the pending
|
|
42
387
|
// session and reads it at the token exchange.
|
|
43
|
-
body: JSON.stringify(
|
|
44
|
-
machineName ? { machineName
|
|
45
|
-
|
|
388
|
+
body: JSON.stringify({
|
|
389
|
+
...machineName ? { machineName } : {},
|
|
390
|
+
...machineNameReadOnly ? { machineNameReadOnly: true } : {},
|
|
391
|
+
cliVersion: CLI_VERSION,
|
|
392
|
+
...options.destinationRequired ? { destinationRequired: true } : {}
|
|
393
|
+
})
|
|
46
394
|
});
|
|
47
395
|
if (!res.ok) throw failure("Auth start failed", res);
|
|
48
396
|
return res.json();
|
|
@@ -145,6 +493,14 @@ async function fetchDayManifest(baseUrl, token) {
|
|
|
145
493
|
}) : [];
|
|
146
494
|
return { retentionDays, aggregateVersion, days };
|
|
147
495
|
}
|
|
496
|
+
async function fetchPriceTable(baseUrl) {
|
|
497
|
+
const res = await fetch(`${baseUrl}/api/prices`, {
|
|
498
|
+
headers: { Accept: "application/json" }
|
|
499
|
+
});
|
|
500
|
+
if (res.status === 404) return null;
|
|
501
|
+
if (!res.ok) throw failure("Price table fetch failed", res);
|
|
502
|
+
return parsePriceTable(await res.json());
|
|
503
|
+
}
|
|
148
504
|
async function setAutoSync(token, flag) {
|
|
149
505
|
const res = await request("/api/cli/auto-sync", {
|
|
150
506
|
method: "POST",
|
|
@@ -208,10 +564,10 @@ function classify(files) {
|
|
|
208
564
|
if (isSingleton) {
|
|
209
565
|
singletons.push(file);
|
|
210
566
|
} else {
|
|
211
|
-
const
|
|
212
|
-
const existing = groups.get(
|
|
567
|
+
const key2 = `${file.group}:${file.source}:${file.type}:${dir}`;
|
|
568
|
+
const existing = groups.get(key2) ?? [];
|
|
213
569
|
existing.push(file);
|
|
214
|
-
groups.set(
|
|
570
|
+
groups.set(key2, existing);
|
|
215
571
|
}
|
|
216
572
|
}
|
|
217
573
|
const items = [];
|
|
@@ -302,7 +658,13 @@ function saveToken(token, userId, serverUrl = BASE_URL, file = CREDENTIALS_FILE)
|
|
|
302
658
|
writeCredentials(file, data);
|
|
303
659
|
}
|
|
304
660
|
var SETTINGS_FILE = join(CONFIG_DIR, "settings.json");
|
|
305
|
-
var DEFAULT_FREQUENCY_HOURS =
|
|
661
|
+
var DEFAULT_FREQUENCY_HOURS = 6;
|
|
662
|
+
var MAX_FREQUENCY_HOURS = 24;
|
|
663
|
+
function normalizeFrequencyHours(value) {
|
|
664
|
+
if (value === void 0 || !Number.isFinite(value))
|
|
665
|
+
return DEFAULT_FREQUENCY_HOURS;
|
|
666
|
+
return Math.min(MAX_FREQUENCY_HOURS, Math.max(1, Math.round(value)));
|
|
667
|
+
}
|
|
306
668
|
function getSettings(file = SETTINGS_FILE) {
|
|
307
669
|
if (!existsSync(file)) return {};
|
|
308
670
|
try {
|
|
@@ -326,13 +688,13 @@ function readProjects(file = PROJECTS_FILE) {
|
|
|
326
688
|
try {
|
|
327
689
|
const raw = JSON.parse(readFileSync(file, "utf-8"));
|
|
328
690
|
const data = {};
|
|
329
|
-
for (const [
|
|
691
|
+
for (const [key2, value] of Object.entries(raw)) {
|
|
330
692
|
if (typeof value === "string") {
|
|
331
|
-
data[
|
|
693
|
+
data[key2] = {};
|
|
332
694
|
} else if (value && typeof value === "object") {
|
|
333
695
|
const excluded = value.excluded;
|
|
334
696
|
const workspaceId = value.workspaceId;
|
|
335
|
-
data[
|
|
697
|
+
data[key2] = {
|
|
336
698
|
...Array.isArray(excluded) ? { excluded } : {},
|
|
337
699
|
...typeof workspaceId === "string" ? { workspaceId } : {}
|
|
338
700
|
};
|
|
@@ -761,11 +1123,11 @@ function resolveSource(entry, mpRepoUrl) {
|
|
|
761
1123
|
}
|
|
762
1124
|
function resolvePluginLinks(installed, marketplaces, manifests) {
|
|
763
1125
|
const out = [];
|
|
764
|
-
for (const [
|
|
765
|
-
const at =
|
|
1126
|
+
for (const [key2, entries] of Object.entries(installed.plugins ?? {})) {
|
|
1127
|
+
const at = key2.lastIndexOf("@");
|
|
766
1128
|
if (at <= 0) continue;
|
|
767
|
-
const pluginName =
|
|
768
|
-
const marketplace =
|
|
1129
|
+
const pluginName = key2.slice(0, at);
|
|
1130
|
+
const marketplace = key2.slice(at + 1);
|
|
769
1131
|
const mpRepoUrl = marketplaceRepoUrl(marketplaces[marketplace]);
|
|
770
1132
|
const entry = manifests[marketplace]?.plugins?.find(
|
|
771
1133
|
(p8) => p8.name === pluginName
|
|
@@ -803,8 +1165,8 @@ function detectInstalledPlugins(pluginsDir = join4(homedir4(), ".claude", "plugi
|
|
|
803
1165
|
if (!installed?.plugins) return [];
|
|
804
1166
|
const marketplaces = readJson3(join4(pluginsDir, "known_marketplaces.json")) ?? {};
|
|
805
1167
|
const manifests = {};
|
|
806
|
-
for (const
|
|
807
|
-
const mp =
|
|
1168
|
+
for (const key2 of Object.keys(installed.plugins)) {
|
|
1169
|
+
const mp = key2.slice(key2.lastIndexOf("@") + 1);
|
|
808
1170
|
if (!mp || manifests[mp]) continue;
|
|
809
1171
|
const installLocation = marketplaces[mp]?.installLocation ?? join4(pluginsDir, "marketplaces", mp);
|
|
810
1172
|
const manifest = readJson3(
|
|
@@ -1352,23 +1714,23 @@ function diffResources(current, existing) {
|
|
|
1352
1714
|
let added = 0;
|
|
1353
1715
|
let changed = 0;
|
|
1354
1716
|
let unchanged = 0;
|
|
1355
|
-
for (const [
|
|
1356
|
-
const prev = existingMap.get(
|
|
1717
|
+
for (const [key2, content] of currentMap) {
|
|
1718
|
+
const prev = existingMap.get(key2);
|
|
1357
1719
|
if (prev === void 0) {
|
|
1358
1720
|
added++;
|
|
1359
|
-
details.push({ name:
|
|
1721
|
+
details.push({ name: key2, status: "added" });
|
|
1360
1722
|
} else if (prev !== content) {
|
|
1361
1723
|
changed++;
|
|
1362
|
-
details.push({ name:
|
|
1724
|
+
details.push({ name: key2, status: "changed" });
|
|
1363
1725
|
} else {
|
|
1364
1726
|
unchanged++;
|
|
1365
1727
|
}
|
|
1366
1728
|
}
|
|
1367
1729
|
let removed = 0;
|
|
1368
|
-
for (const
|
|
1369
|
-
if (!currentMap.has(
|
|
1730
|
+
for (const key2 of existingMap.keys()) {
|
|
1731
|
+
if (!currentMap.has(key2)) {
|
|
1370
1732
|
removed++;
|
|
1371
|
-
details.push({ name:
|
|
1733
|
+
details.push({ name: key2, status: "removed" });
|
|
1372
1734
|
}
|
|
1373
1735
|
}
|
|
1374
1736
|
const linkLabel = (item) => {
|
|
@@ -1388,14 +1750,14 @@ function diffResources(current, existing) {
|
|
|
1388
1750
|
};
|
|
1389
1751
|
const existingLinks = linkMap(existing);
|
|
1390
1752
|
const currentLinks = linkMap(current);
|
|
1391
|
-
for (const [
|
|
1392
|
-
if (!existingLinks.has(
|
|
1753
|
+
for (const [key2, item] of currentLinks) {
|
|
1754
|
+
if (!existingLinks.has(key2)) {
|
|
1393
1755
|
added++;
|
|
1394
1756
|
details.push({ name: linkLabel(item), status: "added" });
|
|
1395
1757
|
}
|
|
1396
1758
|
}
|
|
1397
|
-
for (const [
|
|
1398
|
-
if (!currentLinks.has(
|
|
1759
|
+
for (const [key2, item] of existingLinks) {
|
|
1760
|
+
if (!currentLinks.has(key2)) {
|
|
1399
1761
|
removed++;
|
|
1400
1762
|
details.push({ name: linkLabel(item), status: "removed" });
|
|
1401
1763
|
}
|
|
@@ -1411,186 +1773,6 @@ import { dirname as dirname3, join as join6 } from "path";
|
|
|
1411
1773
|
import { fileURLToPath } from "url";
|
|
1412
1774
|
import * as p3 from "@clack/prompts";
|
|
1413
1775
|
|
|
1414
|
-
// ../pricing/src/index.ts
|
|
1415
|
-
var PRICING_TABLE_VERSION = "anthropic-list-2026-07-25";
|
|
1416
|
-
var OPENAI_PRICING_TABLE_VERSION = "openai-list-2026-08-02";
|
|
1417
|
-
var GOOGLE_PRICING_TABLE_VERSION = "google-list-2026-08-09";
|
|
1418
|
-
var LOCAL_PRICING_TABLE_VERSION = "local-no-charge";
|
|
1419
|
-
var CACHE_WRITE_5M_MULTIPLIER = 1.25;
|
|
1420
|
-
var CACHE_WRITE_1H_MULTIPLIER = 2;
|
|
1421
|
-
var CACHE_READ_MULTIPLIER = 0.1;
|
|
1422
|
-
var PROVIDER_SEPARATOR = ":";
|
|
1423
|
-
var SONNET_5_INTRO_ENDS_MS = Date.UTC(2026, 8, 1);
|
|
1424
|
-
var DEFAULT_CACHE_MULTIPLIERS = {
|
|
1425
|
-
write5m: CACHE_WRITE_5M_MULTIPLIER,
|
|
1426
|
-
write1h: CACHE_WRITE_1H_MULTIPLIER,
|
|
1427
|
-
read: CACHE_READ_MULTIPLIER
|
|
1428
|
-
};
|
|
1429
|
-
var GOOGLE_CACHE_MULTIPLIERS = {
|
|
1430
|
-
write5m: 1,
|
|
1431
|
-
write1h: 1,
|
|
1432
|
-
read: 0.1
|
|
1433
|
-
};
|
|
1434
|
-
var FREE_CACHE_MULTIPLIERS = {
|
|
1435
|
-
write5m: 0,
|
|
1436
|
-
write1h: 0,
|
|
1437
|
-
read: 0
|
|
1438
|
-
};
|
|
1439
|
-
var anthropic = (periods) => ({
|
|
1440
|
-
vendor: "anthropic",
|
|
1441
|
-
table: PRICING_TABLE_VERSION,
|
|
1442
|
-
periods,
|
|
1443
|
-
cache: DEFAULT_CACHE_MULTIPLIERS
|
|
1444
|
-
});
|
|
1445
|
-
var openai = (periods) => ({
|
|
1446
|
-
vendor: "openai",
|
|
1447
|
-
table: OPENAI_PRICING_TABLE_VERSION,
|
|
1448
|
-
periods,
|
|
1449
|
-
cache: DEFAULT_CACHE_MULTIPLIERS
|
|
1450
|
-
});
|
|
1451
|
-
var google = (periods) => ({
|
|
1452
|
-
vendor: "google",
|
|
1453
|
-
table: GOOGLE_PRICING_TABLE_VERSION,
|
|
1454
|
-
periods,
|
|
1455
|
-
cache: GOOGLE_CACHE_MULTIPLIERS
|
|
1456
|
-
});
|
|
1457
|
-
var flat = (input, output) => [
|
|
1458
|
-
{ from: null, to: null, input, output }
|
|
1459
|
-
];
|
|
1460
|
-
var FREE = {
|
|
1461
|
-
vendor: "local",
|
|
1462
|
-
table: LOCAL_PRICING_TABLE_VERSION,
|
|
1463
|
-
periods: flat(0, 0),
|
|
1464
|
-
cache: FREE_CACHE_MULTIPLIERS
|
|
1465
|
-
};
|
|
1466
|
-
var PROVIDER_VENDOR = {
|
|
1467
|
-
anthropic: "anthropic",
|
|
1468
|
-
openai: "openai",
|
|
1469
|
-
google: "google"
|
|
1470
|
-
};
|
|
1471
|
-
var LOCAL_PROVIDERS = /* @__PURE__ */ new Set([
|
|
1472
|
-
"ollama",
|
|
1473
|
-
"lmstudio",
|
|
1474
|
-
"llama.cpp",
|
|
1475
|
-
"llamacpp",
|
|
1476
|
-
"local"
|
|
1477
|
-
]);
|
|
1478
|
-
var PRICES = {
|
|
1479
|
-
"claude-fable-5": anthropic(flat(10, 50)),
|
|
1480
|
-
"claude-mythos-5": anthropic(flat(10, 50)),
|
|
1481
|
-
"claude-opus-5": anthropic(flat(5, 25)),
|
|
1482
|
-
"claude-opus-4-8": anthropic(flat(5, 25)),
|
|
1483
|
-
"claude-opus-4-7": anthropic(flat(5, 25)),
|
|
1484
|
-
"claude-opus-4-6": anthropic(flat(5, 25)),
|
|
1485
|
-
"claude-sonnet-5": anthropic([
|
|
1486
|
-
{ from: null, to: SONNET_5_INTRO_ENDS_MS, input: 2, output: 10 },
|
|
1487
|
-
{ from: SONNET_5_INTRO_ENDS_MS, to: null, input: 3, output: 15 }
|
|
1488
|
-
]),
|
|
1489
|
-
"claude-sonnet-4-6": anthropic(flat(3, 15)),
|
|
1490
|
-
"claude-haiku-4-5": anthropic(flat(1, 5)),
|
|
1491
|
-
// Fast mode (research preview) - Claude API only, Opus 5 / Opus 4.8 only.
|
|
1492
|
-
// Opus 4.7 fast mode was removed, so there is deliberately no 4-7 entry.
|
|
1493
|
-
"claude-opus-5#fast": anthropic(flat(10, 50)),
|
|
1494
|
-
"claude-opus-4-8#fast": anthropic(flat(10, 50)),
|
|
1495
|
-
// OpenAI (Codex) - standard-context tier (<272K; observed context window is
|
|
1496
|
-
// 258,400).
|
|
1497
|
-
"gpt-5.5": openai(flat(5, 30)),
|
|
1498
|
-
"gpt-5.4": openai(flat(2.5, 15)),
|
|
1499
|
-
"gpt-5.4-mini": openai(flat(0.75, 4.5)),
|
|
1500
|
-
"gpt-5.3-codex": openai(flat(1.75, 14)),
|
|
1501
|
-
// The gpt-5.6 family launched 2026-07-29; Terra and Luna were repriced on
|
|
1502
|
-
// 2026-07-30 (-20% / -80%). The one-day launch rates are not on the list
|
|
1503
|
-
// page and are NOT encoded - a July-29 Terra/Luna record underprices for
|
|
1504
|
-
// one day rather than carrying a rate we cannot cite (#72).
|
|
1505
|
-
"gpt-5.6-sol": openai(flat(5, 30)),
|
|
1506
|
-
"gpt-5.6-terra": openai(flat(2, 12)),
|
|
1507
|
-
"gpt-5.6-luna": openai(flat(0.2, 1.2)),
|
|
1508
|
-
// NOT on OpenAI's list page - an internal Codex routing label with no
|
|
1509
|
-
// official price (openai/codex#20981). Rate is the aggregator consensus
|
|
1510
|
-
// ($2.50 / $15.00), scoped in explicitly by ticket #72 because it carries
|
|
1511
|
-
// real token volume in Codex rollouts.
|
|
1512
|
-
"codex-auto-review": openai(flat(2.5, 15)),
|
|
1513
|
-
// Google (opencode, pi-mono) - Standard tier. Where a model is
|
|
1514
|
-
// context-tiered, the <=200K rate is encoded, exactly as the OpenAI rows
|
|
1515
|
-
// encode the standard-context tier: the payload carries no per-response
|
|
1516
|
-
// context length, so the cheaper side keeps the figure a lower bound. Only
|
|
1517
|
-
// the Pro and Flash families are here - image, TTS, embedding, Live and
|
|
1518
|
-
// robotics models are not what a coding harness selects.
|
|
1519
|
-
"gemini-3.1-pro-preview": google(flat(2, 12)),
|
|
1520
|
-
"gemini-3.6-flash": google(flat(1.5, 7.5)),
|
|
1521
|
-
"gemini-3.5-flash": google(flat(1.5, 9)),
|
|
1522
|
-
"gemini-3-flash-preview": google(flat(0.5, 3)),
|
|
1523
|
-
"gemini-2.5-pro": google(flat(1.25, 10)),
|
|
1524
|
-
"gemini-2.5-flash": google(flat(0.3, 2.5)),
|
|
1525
|
-
// RETIRED from Google's list page by 2026-08-09, and still the largest
|
|
1526
|
-
// single block of Google tokens measured in #122 (10.9M, 20.5% of that
|
|
1527
|
-
// machine's total). Encoded at its launch rate on the same footing as
|
|
1528
|
-
// `codex-auto-review`: real volume, a rate we can name, and a note saying
|
|
1529
|
-
// where it came from. Announcement rate, <=200K tier: $2.00 / $12.00.
|
|
1530
|
-
"gemini-3-pro-preview": google(flat(2, 12)),
|
|
1531
|
-
// A real Anthropic model with no row until #123. Measured in #122 as
|
|
1532
|
-
// `claude-opus-4-5-20251101`, which the dated-suffix rule strips to this key.
|
|
1533
|
-
// Same $5/$25 as the rest of the Opus 4 line on the 2026-07-25 list.
|
|
1534
|
-
"claude-opus-4-5": anthropic(flat(5, 25))
|
|
1535
|
-
};
|
|
1536
|
-
function splitModelKey(modelKey) {
|
|
1537
|
-
const at = modelKey.indexOf(PROVIDER_SEPARATOR);
|
|
1538
|
-
if (at === -1) return { provider: null, model: modelKey };
|
|
1539
|
-
return {
|
|
1540
|
-
provider: modelKey.slice(0, at),
|
|
1541
|
-
model: modelKey.slice(at + PROVIDER_SEPARATOR.length)
|
|
1542
|
-
};
|
|
1543
|
-
}
|
|
1544
|
-
function modelKeyFor(provider, model) {
|
|
1545
|
-
return `${provider}${PROVIDER_SEPARATOR}${model}`;
|
|
1546
|
-
}
|
|
1547
|
-
function entryFor(modelKey) {
|
|
1548
|
-
const { provider, model } = splitModelKey(modelKey);
|
|
1549
|
-
if (provider === null) return PRICES[model] ?? null;
|
|
1550
|
-
if (LOCAL_PROVIDERS.has(provider)) return FREE;
|
|
1551
|
-
const vendor = PROVIDER_VENDOR[provider];
|
|
1552
|
-
if (!vendor) return null;
|
|
1553
|
-
const entry = PRICES[model];
|
|
1554
|
-
return entry && entry.vendor === vendor ? entry : null;
|
|
1555
|
-
}
|
|
1556
|
-
function normalizeModel(model) {
|
|
1557
|
-
const { provider, model: bare } = splitModelKey(model);
|
|
1558
|
-
const [base, suffix] = bare.split("#");
|
|
1559
|
-
const stripped = base.replace(/-\d{8}$/, "");
|
|
1560
|
-
const normalized = suffix ? `${stripped}#${suffix}` : stripped;
|
|
1561
|
-
return provider === null ? normalized : modelKeyFor(provider, normalized);
|
|
1562
|
-
}
|
|
1563
|
-
function baseModelId(modelKey) {
|
|
1564
|
-
return modelKey.split("#")[0];
|
|
1565
|
-
}
|
|
1566
|
-
function cacheMultipliersFor(modelKey) {
|
|
1567
|
-
return entryFor(modelKey)?.cache ?? DEFAULT_CACHE_MULTIPLIERS;
|
|
1568
|
-
}
|
|
1569
|
-
function priceAt(modelKey, atMs) {
|
|
1570
|
-
if (atMs === null) return null;
|
|
1571
|
-
const entry = entryFor(modelKey);
|
|
1572
|
-
if (!entry) return null;
|
|
1573
|
-
for (const p8 of entry.periods) {
|
|
1574
|
-
if ((p8.from === null || atMs >= p8.from) && (p8.to === null || atMs < p8.to)) {
|
|
1575
|
-
return p8;
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
return null;
|
|
1579
|
-
}
|
|
1580
|
-
function isPricedModel(modelKey) {
|
|
1581
|
-
return entryFor(modelKey) !== null;
|
|
1582
|
-
}
|
|
1583
|
-
function pricingTableFor(modelKey) {
|
|
1584
|
-
return entryFor(modelKey)?.table ?? null;
|
|
1585
|
-
}
|
|
1586
|
-
function apiEquivalentCost(modelKey, t, atMs) {
|
|
1587
|
-
const p8 = priceAt(modelKey, atMs);
|
|
1588
|
-
if (!p8) return null;
|
|
1589
|
-
const c = cacheMultipliersFor(modelKey);
|
|
1590
|
-
const M = 1e6;
|
|
1591
|
-
return (t.input * p8.input + t.output * p8.output + (t.cacheWrite5m + t.cacheWriteUnsplit) * p8.input * c.write5m + t.cacheWrite1h * p8.input * c.write1h + t.cacheRead * p8.input * c.read) / M;
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
1776
|
// src/harness/shared/aggregate.ts
|
|
1595
1777
|
var asObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
|
|
1596
1778
|
var asStr = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
@@ -2293,10 +2475,10 @@ function addPhaseTotals(into, from) {
|
|
|
2293
2475
|
into[phase] += from[phase] ?? 0;
|
|
2294
2476
|
}
|
|
2295
2477
|
}
|
|
2296
|
-
function sumBy(rows,
|
|
2478
|
+
function sumBy(rows, key2, add, clone) {
|
|
2297
2479
|
const merged = /* @__PURE__ */ new Map();
|
|
2298
2480
|
for (const row of rows) {
|
|
2299
|
-
const k =
|
|
2481
|
+
const k = key2(row);
|
|
2300
2482
|
const held = merged.get(k);
|
|
2301
2483
|
if (held) add(held, row);
|
|
2302
2484
|
else merged.set(k, clone(row));
|
|
@@ -2461,6 +2643,18 @@ function foldHarnessDays(days) {
|
|
|
2461
2643
|
}
|
|
2462
2644
|
return out;
|
|
2463
2645
|
}
|
|
2646
|
+
var MAX_CHANGED_LINES_PER_COMMIT = 4096;
|
|
2647
|
+
function sampleSorted(values, max) {
|
|
2648
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2649
|
+
if (sorted.length <= max) return sorted;
|
|
2650
|
+
const out = [];
|
|
2651
|
+
for (let i = 0; i < max; i++) {
|
|
2652
|
+
out.push(
|
|
2653
|
+
sorted[Math.floor(i * (sorted.length - 1) / (max - 1))]
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
return out;
|
|
2657
|
+
}
|
|
2464
2658
|
function foldGitDays(days) {
|
|
2465
2659
|
const versions = (values) => [...new Set(values)].sort().join(" \xB7 ");
|
|
2466
2660
|
return {
|
|
@@ -2471,7 +2665,10 @@ function foldGitDays(days) {
|
|
|
2471
2665
|
lateNightCommits: days.reduce((sum, d) => sum + d.lateNightCommits, 0),
|
|
2472
2666
|
additions: days.reduce((sum, d) => sum + d.additions, 0),
|
|
2473
2667
|
removals: days.reduce((sum, d) => sum + d.removals, 0),
|
|
2474
|
-
changedLinesPerCommit:
|
|
2668
|
+
changedLinesPerCommit: sampleSorted(
|
|
2669
|
+
days.flatMap((d) => [...d.changedLinesPerCommit]),
|
|
2670
|
+
MAX_CHANGED_LINES_PER_COMMIT
|
|
2671
|
+
),
|
|
2475
2672
|
testFileCommits: days.reduce((sum, d) => sum + d.testFileCommits, 0),
|
|
2476
2673
|
changedLinesByExtension: sumBy(
|
|
2477
2674
|
days.flatMap((d) => d.changedLinesByExtension),
|
|
@@ -3505,7 +3702,7 @@ function buildPayload(input) {
|
|
|
3505
3702
|
[...agg.projectDirs].map((directory) => projectWorkspaceId(directory))
|
|
3506
3703
|
)
|
|
3507
3704
|
].sort();
|
|
3508
|
-
if (projectKeys.length > 1e3 || projectKeys.some((
|
|
3705
|
+
if (projectKeys.length > 1e3 || projectKeys.some((key2) => !/^[A-Za-z0-9_-]{22}$/.test(key2))) {
|
|
3509
3706
|
throw new Error(
|
|
3510
3707
|
"Project workspace identifiers must be 22-character base64url strings"
|
|
3511
3708
|
);
|
|
@@ -3678,8 +3875,8 @@ var emptyPhase = () => ({
|
|
|
3678
3875
|
unknown: 0
|
|
3679
3876
|
});
|
|
3680
3877
|
var finiteNonnegative = (value) => value !== void 0 && Number.isFinite(value) && value > 0 ? value : 0;
|
|
3681
|
-
var bump2 = (map,
|
|
3682
|
-
map.set(
|
|
3878
|
+
var bump2 = (map, key2, amount = 1) => {
|
|
3879
|
+
map.set(key2, (map.get(key2) ?? 0) + amount);
|
|
3683
3880
|
};
|
|
3684
3881
|
var utcDateOf2 = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
3685
3882
|
var PHASE_RANK = {
|
|
@@ -3788,11 +3985,11 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3788
3985
|
const webSearchesByDate = /* @__PURE__ */ new Map();
|
|
3789
3986
|
const eventDates = /* @__PURE__ */ new Set();
|
|
3790
3987
|
let finished;
|
|
3791
|
-
const getSession = (
|
|
3792
|
-
let state = sessions.get(
|
|
3988
|
+
const getSession = (key2) => {
|
|
3989
|
+
let state = sessions.get(key2);
|
|
3793
3990
|
if (!state) {
|
|
3794
3991
|
state = sessionState();
|
|
3795
|
-
sessions.set(
|
|
3992
|
+
sessions.set(key2, state);
|
|
3796
3993
|
}
|
|
3797
3994
|
return state;
|
|
3798
3995
|
};
|
|
@@ -3950,8 +4147,8 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3950
4147
|
}
|
|
3951
4148
|
for (const date of eventDates) {
|
|
3952
4149
|
const day = dayOf(date);
|
|
3953
|
-
for (const [
|
|
3954
|
-
bump2(day.activity,
|
|
4150
|
+
for (const [key2, events] of eventCells.get(date) ?? []) {
|
|
4151
|
+
bump2(day.activity, key2, events);
|
|
3955
4152
|
}
|
|
3956
4153
|
if (harness !== "pi-mono") {
|
|
3957
4154
|
day.hasWebSearches = true;
|
|
@@ -3980,12 +4177,12 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
3980
4177
|
{ ts: child.lastTs ?? child.firstTs ?? 0, delta: -1 }
|
|
3981
4178
|
]);
|
|
3982
4179
|
boundaries.sort((a, b) => a.ts - b.ts || b.delta - a.delta);
|
|
3983
|
-
let
|
|
4180
|
+
let active2 = 0;
|
|
3984
4181
|
for (const boundary of boundaries) {
|
|
3985
|
-
|
|
4182
|
+
active2 += boundary.delta;
|
|
3986
4183
|
day.delegation.widestFanOut = Math.max(
|
|
3987
4184
|
day.delegation.widestFanOut,
|
|
3988
|
-
|
|
4185
|
+
active2
|
|
3989
4186
|
);
|
|
3990
4187
|
}
|
|
3991
4188
|
}
|
|
@@ -4055,8 +4252,8 @@ function createHarnessWorkflowReducer(harness, localSources = createWorkflowLoca
|
|
|
4055
4252
|
}
|
|
4056
4253
|
} : {},
|
|
4057
4254
|
...day.hasDelegation ? { delegation: day.delegation } : {},
|
|
4058
|
-
activity: [...day.activity].map(([
|
|
4059
|
-
const [weekdayUtc, hourUtc] =
|
|
4255
|
+
activity: [...day.activity].map(([key2, events]) => {
|
|
4256
|
+
const [weekdayUtc, hourUtc] = key2.split(":").map(Number);
|
|
4060
4257
|
return {
|
|
4061
4258
|
weekdayUtc: weekdayUtc ?? 0,
|
|
4062
4259
|
hourUtc: hourUtc ?? 0,
|
|
@@ -5649,23 +5846,23 @@ function countUsage(agg, fold, rec, msgTsMs, usageRaw, modelKey, tsMs, priceable
|
|
|
5649
5846
|
if (total === 0) return "none";
|
|
5650
5847
|
const id = asStr(rec.id);
|
|
5651
5848
|
if (id) {
|
|
5652
|
-
const
|
|
5653
|
-
if (fold.seenUsage.has(
|
|
5849
|
+
const key3 = `${id}:${asStr(rec.timestamp) ?? ""}:${msgTsMs}:${total}`;
|
|
5850
|
+
if (fold.seenUsage.has(key3)) {
|
|
5654
5851
|
agg.continuationsFolded++;
|
|
5655
5852
|
return "duplicate";
|
|
5656
5853
|
}
|
|
5657
|
-
fold.seenUsage.add(
|
|
5854
|
+
fold.seenUsage.add(key3);
|
|
5658
5855
|
} else {
|
|
5659
5856
|
agg.unkeyedResponses++;
|
|
5660
5857
|
}
|
|
5661
5858
|
if (tsMs === null) agg.untimestampedResponses++;
|
|
5662
5859
|
agg.distinctResponses++;
|
|
5663
|
-
const
|
|
5860
|
+
const key2 = modelKey ?? "(unknown)";
|
|
5664
5861
|
addModelUsage(
|
|
5665
5862
|
agg,
|
|
5666
|
-
|
|
5863
|
+
key2,
|
|
5667
5864
|
counts,
|
|
5668
|
-
priceable ? apiEquivalentCost(
|
|
5865
|
+
priceable ? apiEquivalentCost(key2, counts, tsMs) : null,
|
|
5669
5866
|
1,
|
|
5670
5867
|
{ tsMs }
|
|
5671
5868
|
);
|
|
@@ -6187,12 +6384,29 @@ function proposedMachineName(read = hostname) {
|
|
|
6187
6384
|
return void 0;
|
|
6188
6385
|
}
|
|
6189
6386
|
}
|
|
6190
|
-
|
|
6387
|
+
function requestedMachineLabel(label) {
|
|
6388
|
+
const trimmed = label.trim();
|
|
6389
|
+
if (!isDisplaySafeName(trimmed)) {
|
|
6390
|
+
throw new Error(
|
|
6391
|
+
"Machine label must be 64 characters or fewer and contain only printable characters."
|
|
6392
|
+
);
|
|
6393
|
+
}
|
|
6394
|
+
return trimmed;
|
|
6395
|
+
}
|
|
6396
|
+
async function performLogin(options = {}) {
|
|
6191
6397
|
const s = p5.spinner();
|
|
6192
6398
|
s.start("Starting authentication...");
|
|
6193
6399
|
let session;
|
|
6194
6400
|
try {
|
|
6195
|
-
|
|
6401
|
+
const requestedLabel = options.label === void 0 ? void 0 : requestedMachineLabel(options.label);
|
|
6402
|
+
session = await authStart(
|
|
6403
|
+
requestedLabel ?? proposedMachineName(),
|
|
6404
|
+
requestedLabel !== void 0,
|
|
6405
|
+
{
|
|
6406
|
+
...options.replaceToken ? { replaceToken: options.replaceToken } : {},
|
|
6407
|
+
...options.destinationRequired ? { destinationRequired: true } : {}
|
|
6408
|
+
}
|
|
6409
|
+
);
|
|
6196
6410
|
s.stop("Session created");
|
|
6197
6411
|
} catch (err) {
|
|
6198
6412
|
s.stop("Failed to start authentication");
|
|
@@ -6234,9 +6448,9 @@ async function performLogin() {
|
|
|
6234
6448
|
p5.log.error("Authentication timed out after 3 minutes. Please try again.");
|
|
6235
6449
|
return false;
|
|
6236
6450
|
}
|
|
6237
|
-
async function loginCommand() {
|
|
6451
|
+
async function loginCommand(options = {}) {
|
|
6238
6452
|
intro2("login");
|
|
6239
|
-
if (!await performLogin()) {
|
|
6453
|
+
if (!await performLogin(options)) {
|
|
6240
6454
|
outroError("error");
|
|
6241
6455
|
process.exit(1);
|
|
6242
6456
|
}
|
|
@@ -6430,6 +6644,7 @@ function autoSyncHookInstalled(file = CLAUDE_SETTINGS_FILE) {
|
|
|
6430
6644
|
var NOT_LINKED = "This machine is not linked to an aistack account, and the auto-sync permission lives on your stack. Run `npx @use-aistack/cli sync` first.";
|
|
6431
6645
|
var NOTHING_TO_TRIGGER = `No Claude Code or Codex session on this machine in the last ${DEFAULT_WINDOW_DAYS} days, so nothing would trigger an auto-sync. Nothing was changed.`;
|
|
6432
6646
|
async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {}) {
|
|
6647
|
+
frequencyHours = normalizeFrequencyHours(frequencyHours);
|
|
6433
6648
|
const detected = await (deps.detectedImpl ?? detectedAdapters)();
|
|
6434
6649
|
if (detected.length === 0) {
|
|
6435
6650
|
return { ok: false, message: NOTHING_TO_TRIGGER };
|
|
@@ -6480,7 +6695,9 @@ async function disableAutoSync(deps = {}) {
|
|
|
6480
6695
|
autoSyncAnswered: true,
|
|
6481
6696
|
autoSync: {
|
|
6482
6697
|
enabled: false,
|
|
6483
|
-
frequencyHours:
|
|
6698
|
+
frequencyHours: normalizeFrequencyHours(
|
|
6699
|
+
settings.autoSync?.frequencyHours
|
|
6700
|
+
)
|
|
6484
6701
|
}
|
|
6485
6702
|
},
|
|
6486
6703
|
deps.settingsFile
|
|
@@ -6568,20 +6785,20 @@ async function offerAutoSyncOptIn(deps = {}) {
|
|
|
6568
6785
|
const detected = await (deps.detectedImpl ?? detectedAdapters)();
|
|
6569
6786
|
if (detected.length === 0) return false;
|
|
6570
6787
|
const answer = await p6.select({
|
|
6571
|
-
message: "Keep this stack fresh automatically?",
|
|
6788
|
+
message: "Keep this stack fresh automatically every 6 hours?",
|
|
6572
6789
|
options: [
|
|
6573
|
-
{
|
|
6574
|
-
value: "later",
|
|
6575
|
-
label: "Not now",
|
|
6576
|
-
hint: "this question will not come back"
|
|
6577
|
-
},
|
|
6578
6790
|
{
|
|
6579
6791
|
value: "enable",
|
|
6580
6792
|
label: "Enable",
|
|
6581
|
-
hint: `a silent
|
|
6793
|
+
hint: `a silent sync at most every 6 hours when a ${harnessListLabel(detected)} session starts`
|
|
6794
|
+
},
|
|
6795
|
+
{
|
|
6796
|
+
value: "later",
|
|
6797
|
+
label: "No thanks",
|
|
6798
|
+
hint: "you can enable it later"
|
|
6582
6799
|
}
|
|
6583
6800
|
],
|
|
6584
|
-
initialValue: "
|
|
6801
|
+
initialValue: "enable"
|
|
6585
6802
|
});
|
|
6586
6803
|
if (p6.isCancel(answer)) return true;
|
|
6587
6804
|
if (answer === "enable") {
|
|
@@ -7046,8 +7263,8 @@ function extractGitWorkflow(options) {
|
|
|
7046
7263
|
date,
|
|
7047
7264
|
...rest,
|
|
7048
7265
|
changedLinesByExtension: [...extensionLines].map(([extension, changedLines]) => ({ extension, changedLines })).sort((a, b) => a.extension.localeCompare(b.extension)),
|
|
7049
|
-
weekdayHourCells: [...cells].map(([
|
|
7050
|
-
const [weekdayUtc, hourUtc] =
|
|
7266
|
+
weekdayHourCells: [...cells].map(([key2, commits]) => {
|
|
7267
|
+
const [weekdayUtc, hourUtc] = key2.split(":").map(Number);
|
|
7051
7268
|
return {
|
|
7052
7269
|
weekdayUtc: weekdayUtc ?? 0,
|
|
7053
7270
|
hourUtc: hourUtc ?? 0,
|
|
@@ -7254,10 +7471,18 @@ function payloadBlock(payload, width, ownWindow, stats) {
|
|
|
7254
7471
|
`${days} active day${days === 1 ? "" : "s"}`,
|
|
7255
7472
|
`${fmtTokens(payload.activity.totalTokens)} tokens`
|
|
7256
7473
|
];
|
|
7257
|
-
out.push(
|
|
7258
|
-
if (payload.activity.totalTokens === 0)
|
|
7474
|
+
out.push(`${label.toUpperCase()} ${payload.activity.sessions}`);
|
|
7475
|
+
if (payload.activity.totalTokens === 0) {
|
|
7476
|
+
out.push(`usage ${totals.slice(1).join(" \xB7 ")}`);
|
|
7477
|
+
return out;
|
|
7478
|
+
}
|
|
7259
7479
|
out.push(
|
|
7260
|
-
|
|
7480
|
+
...wrapRow(
|
|
7481
|
+
"usage ",
|
|
7482
|
+
" ".repeat(LABEL_WIDTH),
|
|
7483
|
+
`${totals.slice(1).join(" \xB7 ")} \xB7 ${usd === null ? "cost not published" : `${fmtUSD(usd)} at API prices`}`,
|
|
7484
|
+
width
|
|
7485
|
+
)
|
|
7261
7486
|
);
|
|
7262
7487
|
if (ownWindow) {
|
|
7263
7488
|
out.push(
|
|
@@ -7273,24 +7498,29 @@ function payloadBlock(payload, width, ownWindow, stats) {
|
|
|
7273
7498
|
if (stats) {
|
|
7274
7499
|
out.push(...scanNoteLines(stats, harnessLabel2(payload.harness.name)));
|
|
7275
7500
|
}
|
|
7276
|
-
const indent = " ".repeat(LABEL_WIDTH);
|
|
7277
7501
|
const shown = payload.models.filter((m) => m.tokenShare >= MODEL_ROLLUP);
|
|
7278
7502
|
const rolled = payload.models.filter((m) => m.tokenShare < MODEL_ROLLUP);
|
|
7279
|
-
const
|
|
7280
|
-
const
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
out.push(row(i, m.id, m.tokenShare, dollars));
|
|
7284
|
-
});
|
|
7503
|
+
const entry = (name, share, dollars) => `${name} ${fmtPct(share)}${usd !== null && dollars !== void 0 ? ` ${fmtUSD(dollars)}` : ""}`;
|
|
7504
|
+
const entries = shown.map(
|
|
7505
|
+
(m) => entry(m.id, m.tokenShare, m.apiEquivalentUSD)
|
|
7506
|
+
);
|
|
7285
7507
|
if (rolled.length > 0) {
|
|
7286
7508
|
const priced = rolled.every((m) => m.apiEquivalentUSD !== void 0);
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
row(
|
|
7290
|
-
shown.length,
|
|
7509
|
+
entries.push(
|
|
7510
|
+
entry(
|
|
7291
7511
|
`+${rolled.length} more`,
|
|
7292
7512
|
rolled.reduce((a, m) => a + m.tokenShare, 0),
|
|
7293
|
-
|
|
7513
|
+
priced ? rolled.reduce((a, m) => a + (m.apiEquivalentUSD ?? 0), 0) : void 0
|
|
7514
|
+
)
|
|
7515
|
+
);
|
|
7516
|
+
}
|
|
7517
|
+
if (entries.length > 0) {
|
|
7518
|
+
out.push(
|
|
7519
|
+
...wrapRow(
|
|
7520
|
+
"models ",
|
|
7521
|
+
" ".repeat(LABEL_WIDTH),
|
|
7522
|
+
entries.join(" \xB7 "),
|
|
7523
|
+
width
|
|
7294
7524
|
)
|
|
7295
7525
|
);
|
|
7296
7526
|
}
|
|
@@ -7323,6 +7553,7 @@ function payloadBlock(payload, width, ownWindow, stats) {
|
|
|
7323
7553
|
}
|
|
7324
7554
|
return out;
|
|
7325
7555
|
}
|
|
7556
|
+
var DIVIDER = "\u2500".repeat(40);
|
|
7326
7557
|
var MODEL_ROLLUP = 0.01;
|
|
7327
7558
|
var PHASE_ORDER = ["scout", "build", "verify", "handoff", "unknown"];
|
|
7328
7559
|
function workflowBlock(workflowDays, utcOffsetMinutes, host) {
|
|
@@ -7385,8 +7616,6 @@ function buildGateSummary(ctx) {
|
|
|
7385
7616
|
const { payloads } = body;
|
|
7386
7617
|
const host = baseUrl.replace(/^https?:\/\//, "");
|
|
7387
7618
|
const out = [];
|
|
7388
|
-
out.push("from your machine \xB7 sync preview");
|
|
7389
|
-
out.push("");
|
|
7390
7619
|
if (config.stack === null) {
|
|
7391
7620
|
out.push("to (no linked stack; publish is unavailable)");
|
|
7392
7621
|
} else {
|
|
@@ -7395,26 +7624,39 @@ function buildGateSummary(ctx) {
|
|
|
7395
7624
|
);
|
|
7396
7625
|
}
|
|
7397
7626
|
out.push(
|
|
7398
|
-
`searched ${HARNESS_ADAPTERS.map((a) => harnessLabel2(a.name).toLowerCase()).join(", ")}
|
|
7627
|
+
`searched ${HARNESS_ADAPTERS.map((a) => harnessLabel2(a.name).toLowerCase()).join(", ")}`
|
|
7399
7628
|
);
|
|
7400
7629
|
const windows = new Set(
|
|
7401
7630
|
payloads.map(
|
|
7402
7631
|
(p8) => `${p8.window.days} days \xB7 ${p8.window.from} \u2192 ${p8.window.to}`
|
|
7403
7632
|
)
|
|
7404
7633
|
);
|
|
7405
|
-
if (windows.size === 1)
|
|
7634
|
+
if (windows.size === 1) {
|
|
7635
|
+
out.push(
|
|
7636
|
+
`window ${[...windows][0]}${body.cliVersion ? ` \xB7 aistack ${body.cliVersion}` : ""}`
|
|
7637
|
+
);
|
|
7638
|
+
}
|
|
7639
|
+
const cited = [
|
|
7640
|
+
...new Set(
|
|
7641
|
+
payloads.flatMap(
|
|
7642
|
+
(p8) => p8.models.flatMap((m) => m.pricingTable ? [m.pricingTable] : [])
|
|
7643
|
+
)
|
|
7644
|
+
)
|
|
7645
|
+
];
|
|
7646
|
+
if (ctx.prices && cited.length > 0) {
|
|
7647
|
+
const origin = ctx.prices.origin === "served" ? `${ctx.prices.id} from ${host}` : `${ctx.prices.id} (bundled; the server table was unavailable)`;
|
|
7648
|
+
out.push(`prices ${origin} \xB7 cites ${cited.join(", ")}`);
|
|
7649
|
+
}
|
|
7406
7650
|
const width = wrapWidth(ctx.width);
|
|
7407
7651
|
for (const payload of payloads) {
|
|
7408
7652
|
const stats = ctx.scanStats?.[payload.harness.name];
|
|
7409
|
-
out.push("");
|
|
7653
|
+
out.push("", DIVIDER, "");
|
|
7410
7654
|
out.push(...payloadBlock(payload, width, windows.size > 1, stats));
|
|
7411
7655
|
}
|
|
7412
|
-
|
|
7656
|
+
out.push("", DIVIDER, "", "ALSO PUBLISHING");
|
|
7413
7657
|
if (body.measuredDays) {
|
|
7414
|
-
out.push("");
|
|
7415
7658
|
out.push(...daysBlock(body.measuredDays, ctx.days));
|
|
7416
7659
|
}
|
|
7417
|
-
out.push("");
|
|
7418
7660
|
const workflowDays = (body.measuredDays?.days ?? []).flatMap(
|
|
7419
7661
|
(d) => d.workflow ? [d.workflow] : []
|
|
7420
7662
|
);
|
|
@@ -7429,28 +7671,23 @@ function buildGateSummary(ctx) {
|
|
|
7429
7671
|
}
|
|
7430
7672
|
const n = payloads.reduce((a, p8) => a + withheldCount(p8), 0);
|
|
7431
7673
|
if (n > 0) {
|
|
7432
|
-
out.push("");
|
|
7433
|
-
out.push(`kept private: ${n} name${n === 1 ? "" : "s"}`);
|
|
7434
7674
|
const rows = keptPrivateRows(keptPrivate);
|
|
7435
7675
|
const shown = rows.slice(0, KEPT_PRIVATE_ROWS_SHOWN);
|
|
7436
|
-
const
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
}
|
|
7440
|
-
if (rows.length > shown.length) {
|
|
7441
|
-
out.push(` ...${rows.length - shown.length} more`);
|
|
7442
|
-
}
|
|
7676
|
+
const examples = shown.map((r) => r.names > 1 ? `${r.label} \xD7${r.names}` : r.label).join(", ");
|
|
7677
|
+
const more = rows.length > shown.length ? `, ...${rows.length - shown.length} more` : "";
|
|
7678
|
+
out.push(`private ${n} name${n === 1 ? "" : "s"} \xB7 ${examples}${more}`);
|
|
7443
7679
|
if (body.keptPrivate !== void 0 && config.stack !== null) {
|
|
7444
|
-
out.push(` publish them at ${host}/stacks/${config.stack.slug}/changes`);
|
|
7445
7680
|
out.push(
|
|
7446
|
-
|
|
7681
|
+
` they go up for you to review at ${host}/stacks/${config.stack.slug}/changes`
|
|
7682
|
+
);
|
|
7683
|
+
out.push(
|
|
7684
|
+
" (turn off: Review kept-private names, on your stack)"
|
|
7447
7685
|
);
|
|
7448
7686
|
} else {
|
|
7449
|
-
out.push("
|
|
7687
|
+
out.push(" they stay on this machine");
|
|
7450
7688
|
}
|
|
7451
7689
|
}
|
|
7452
7690
|
if (body.autoSync !== void 0) {
|
|
7453
|
-
out.push("");
|
|
7454
7691
|
out.push(
|
|
7455
7692
|
`auto-sync ${body.autoSync.enabled ? `on, about every ${body.autoSync.frequencyHours}h` : "off"}`
|
|
7456
7693
|
);
|
|
@@ -7480,6 +7717,22 @@ async function stageSync(deps) {
|
|
|
7480
7717
|
const windowDays = deps.windowDays ?? DEFAULT_WINDOW_DAYS;
|
|
7481
7718
|
const projectWorkspaceId = deps.getProjectWorkspaceIdImpl ?? getProjectWorkspaceId;
|
|
7482
7719
|
const fetchManifest = deps.fetchManifestImpl ?? fetchDayManifest;
|
|
7720
|
+
const fetchPrices = deps.fetchPricesImpl ?? fetchPriceTable;
|
|
7721
|
+
let prices = {
|
|
7722
|
+
id: BUNDLED_PRICE_TABLE_ID,
|
|
7723
|
+
origin: "bundled"
|
|
7724
|
+
};
|
|
7725
|
+
try {
|
|
7726
|
+
const table = await fetchPrices(deps.baseUrl);
|
|
7727
|
+
if (table) {
|
|
7728
|
+
setActivePricer(layeredPricer(table));
|
|
7729
|
+
prices = { id: table.id, origin: "served" };
|
|
7730
|
+
} else {
|
|
7731
|
+
setActivePricer(null);
|
|
7732
|
+
}
|
|
7733
|
+
} catch {
|
|
7734
|
+
setActivePricer(null);
|
|
7735
|
+
}
|
|
7483
7736
|
const { config, source } = await loadConfig({
|
|
7484
7737
|
baseUrl: deps.baseUrl,
|
|
7485
7738
|
...token ? { token } : {}
|
|
@@ -7502,8 +7755,8 @@ async function stageSync(deps) {
|
|
|
7502
7755
|
const usageScans = [];
|
|
7503
7756
|
const sinceMs = windowStartMs(now, windowDays);
|
|
7504
7757
|
const daysSinceMs = windowStartMs(now, retentionDays);
|
|
7505
|
-
const
|
|
7506
|
-
for (const adapter of
|
|
7758
|
+
const active2 = await adapters(sinceMs);
|
|
7759
|
+
for (const adapter of active2) {
|
|
7507
7760
|
const { aggregate, stats } = await adapter.scan({ sinceMs });
|
|
7508
7761
|
scanStats[adapter.name] = stats;
|
|
7509
7762
|
built.push(
|
|
@@ -7519,7 +7772,7 @@ async function stageSync(deps) {
|
|
|
7519
7772
|
})
|
|
7520
7773
|
);
|
|
7521
7774
|
}
|
|
7522
|
-
for (const adapter of
|
|
7775
|
+
for (const adapter of active2) {
|
|
7523
7776
|
const { aggregate, workflow: workflow2, workflowLocal } = await adapter.scan({
|
|
7524
7777
|
sinceMs: daysSinceMs
|
|
7525
7778
|
});
|
|
@@ -7559,7 +7812,7 @@ async function stageSync(deps) {
|
|
|
7559
7812
|
config,
|
|
7560
7813
|
settings.autoSync,
|
|
7561
7814
|
deps.trigger,
|
|
7562
|
-
|
|
7815
|
+
active2.length > 0 ? {
|
|
7563
7816
|
aggregateVersion: MEASURED_DAYS_V1,
|
|
7564
7817
|
utcOffsetMinutes: workflow?.utcOffsetMinutes ?? machineUtcOffsetMinutes(),
|
|
7565
7818
|
days: days.send
|
|
@@ -7576,6 +7829,7 @@ async function stageSync(deps) {
|
|
|
7576
7829
|
baseUrl: deps.baseUrl,
|
|
7577
7830
|
scanStats,
|
|
7578
7831
|
days,
|
|
7832
|
+
prices,
|
|
7579
7833
|
// The real terminal, so the inventory rows break where this window ends
|
|
7580
7834
|
// (#217). A pipe reports nothing and the preview falls back to 80.
|
|
7581
7835
|
width: process.stdout.columns
|
|
@@ -7586,7 +7840,7 @@ async function stageSync(deps) {
|
|
|
7586
7840
|
} else if (token === null) {
|
|
7587
7841
|
blockedReason = "This machine is not linked. Run `npx @use-aistack/cli login` first.";
|
|
7588
7842
|
} else if (config.stack === null) {
|
|
7589
|
-
blockedReason = source === "bundled" ? "Could not fetch your settings from aistack, so the destination stack is unknown. Publish needs it. Check the network and preview again." :
|
|
7843
|
+
blockedReason = source === "bundled" ? "Could not fetch your settings from aistack, so the destination stack is unknown. Publish needs it. Check the network and preview again." : "This machine has no destination stack. Run `npx @use-aistack/cli sync` in an interactive terminal to choose one.";
|
|
7590
7844
|
}
|
|
7591
7845
|
return {
|
|
7592
7846
|
id: stageId(bodyJson),
|
|
@@ -7599,7 +7853,8 @@ async function stageSync(deps) {
|
|
|
7599
7853
|
token,
|
|
7600
7854
|
stagedAt: now,
|
|
7601
7855
|
blockedReason,
|
|
7602
|
-
days
|
|
7856
|
+
days,
|
|
7857
|
+
prices
|
|
7603
7858
|
};
|
|
7604
7859
|
}
|
|
7605
7860
|
|
|
@@ -7631,7 +7886,7 @@ async function runAutoSync(deps) {
|
|
|
7631
7886
|
appendLogLine(logFile, `${stamp} skipped - auto-sync is not enabled`);
|
|
7632
7887
|
return;
|
|
7633
7888
|
}
|
|
7634
|
-
const frequencyHours = config.frequencyHours
|
|
7889
|
+
const frequencyHours = normalizeFrequencyHours(config.frequencyHours);
|
|
7635
7890
|
const state = settings.autoSyncState ?? {};
|
|
7636
7891
|
const lastRunAt = state.lastRunAt ?? 0;
|
|
7637
7892
|
if (now - lastRunAt < frequencyHours * 36e5) return;
|
|
@@ -7650,6 +7905,18 @@ async function runAutoSync(deps) {
|
|
|
7650
7905
|
if (loaded.config.autoSync?.enabled === false) {
|
|
7651
7906
|
revoked = true;
|
|
7652
7907
|
} else {
|
|
7908
|
+
const serverFrequency = loaded.config.autoSync?.frequencyHours;
|
|
7909
|
+
if (loaded.config.autoSync?.enabled === true && serverFrequency !== void 0 && normalizeFrequencyHours(serverFrequency) !== frequencyHours) {
|
|
7910
|
+
saveSettings(
|
|
7911
|
+
{
|
|
7912
|
+
autoSync: {
|
|
7913
|
+
enabled: true,
|
|
7914
|
+
frequencyHours: normalizeFrequencyHours(serverFrequency)
|
|
7915
|
+
}
|
|
7916
|
+
},
|
|
7917
|
+
settingsFile
|
|
7918
|
+
);
|
|
7919
|
+
}
|
|
7653
7920
|
const staged = await stage({
|
|
7654
7921
|
baseUrl: deps.baseUrl,
|
|
7655
7922
|
now: () => now,
|
|
@@ -7761,21 +8028,70 @@ async function syncCommand(options = {}) {
|
|
|
7761
8028
|
process.exitCode = 1;
|
|
7762
8029
|
return;
|
|
7763
8030
|
}
|
|
7764
|
-
|
|
8031
|
+
let token = getToken();
|
|
8032
|
+
if (token === null) {
|
|
8033
|
+
p7.log.message("This machine needs a destination stack. Linking it now.");
|
|
8034
|
+
if (!await performLogin({ destinationRequired: true })) {
|
|
8035
|
+
outroError("login failed. Nothing was sent.");
|
|
8036
|
+
process.exitCode = 1;
|
|
8037
|
+
return;
|
|
8038
|
+
}
|
|
8039
|
+
token = getToken();
|
|
8040
|
+
}
|
|
8041
|
+
if (token === null) {
|
|
8042
|
+
outroError("login completed without a saved credential. Nothing was sent.");
|
|
8043
|
+
process.exitCode = 1;
|
|
8044
|
+
return;
|
|
8045
|
+
}
|
|
8046
|
+
let loaded = await loadSyncConfig({ baseUrl: BASE_URL, token });
|
|
8047
|
+
if (loaded.source === "bundled") {
|
|
8048
|
+
outroError(
|
|
8049
|
+
"Could not fetch your settings from aistack, so the destination stack is unknown. Check the network and sync again."
|
|
8050
|
+
);
|
|
8051
|
+
process.exitCode = 1;
|
|
8052
|
+
return;
|
|
8053
|
+
}
|
|
8054
|
+
if (loaded.config.stack === null) {
|
|
7765
8055
|
p7.log.message(
|
|
7766
|
-
"This machine is not linked to
|
|
8056
|
+
"This machine is not linked to a destination stack. Opening aistack so you can choose one."
|
|
7767
8057
|
);
|
|
7768
|
-
if (!await performLogin(
|
|
7769
|
-
|
|
8058
|
+
if (!await performLogin({
|
|
8059
|
+
destinationRequired: true,
|
|
8060
|
+
replaceToken: token
|
|
8061
|
+
})) {
|
|
8062
|
+
outroError("linking failed. Nothing was sent.");
|
|
8063
|
+
process.exitCode = 1;
|
|
8064
|
+
return;
|
|
8065
|
+
}
|
|
8066
|
+
token = getToken();
|
|
8067
|
+
if (token === null) {
|
|
8068
|
+
outroError(
|
|
8069
|
+
"linking completed without a saved credential. Nothing was sent."
|
|
8070
|
+
);
|
|
8071
|
+
process.exitCode = 1;
|
|
8072
|
+
return;
|
|
8073
|
+
}
|
|
8074
|
+
loaded = await loadSyncConfig({ baseUrl: BASE_URL, token });
|
|
8075
|
+
if (loaded.source === "bundled" || loaded.config.stack === null) {
|
|
8076
|
+
outroError(
|
|
8077
|
+
"The destination stack could not be confirmed. Nothing was sent."
|
|
8078
|
+
);
|
|
7770
8079
|
process.exitCode = 1;
|
|
7771
8080
|
return;
|
|
7772
8081
|
}
|
|
8082
|
+
p7.log.success(`Linked this machine to ${loaded.config.stack.name}`);
|
|
7773
8083
|
}
|
|
8084
|
+
const destinationToken = token;
|
|
8085
|
+
const destinationConfig = loaded;
|
|
7774
8086
|
const s = p7.spinner();
|
|
7775
8087
|
s.start("Scanning local agent transcripts");
|
|
7776
8088
|
let staged;
|
|
7777
8089
|
try {
|
|
7778
|
-
staged = await stageSync({
|
|
8090
|
+
staged = await stageSync({
|
|
8091
|
+
baseUrl: BASE_URL,
|
|
8092
|
+
getTokenImpl: () => destinationToken,
|
|
8093
|
+
loadConfigImpl: async () => destinationConfig
|
|
8094
|
+
});
|
|
7779
8095
|
} catch (e) {
|
|
7780
8096
|
s.stop("Scan failed");
|
|
7781
8097
|
outroError(e instanceof Error ? e.message : String(e));
|
|
@@ -7783,7 +8099,7 @@ async function syncCommand(options = {}) {
|
|
|
7783
8099
|
return;
|
|
7784
8100
|
}
|
|
7785
8101
|
s.stop("Scan complete");
|
|
7786
|
-
p7.log.message(staged.summary.split("\n").join("\n"));
|
|
8102
|
+
p7.log.message(staged.summary.split("\n").map(styleSummaryLine).join("\n"));
|
|
7787
8103
|
if (staged.blockedReason !== null) {
|
|
7788
8104
|
outroError(staged.blockedReason);
|
|
7789
8105
|
process.exitCode = 1;
|
|
@@ -7830,6 +8146,21 @@ async function syncCommand(options = {}) {
|
|
|
7830
8146
|
process.exitCode = 1;
|
|
7831
8147
|
}
|
|
7832
8148
|
}
|
|
8149
|
+
function styleSummaryLine(line) {
|
|
8150
|
+
if (line.startsWith("\u2500")) return dim(line);
|
|
8151
|
+
const section2 = /^([A-Z][A-Z0-9 .-]+?)( \d+)?$/.exec(line);
|
|
8152
|
+
if (section2) return `${bold(section2[1] ?? "")}${dim(section2[2] ?? "")}`;
|
|
8153
|
+
const labelled = /^([a-z-]+)( +)(.*)$/.exec(line);
|
|
8154
|
+
if (labelled) {
|
|
8155
|
+
const [, label = "", gap = "", rest = ""] = labelled;
|
|
8156
|
+
const body = label === "skipped" ? yellow(rest) : rest.replace(/≈\$[\d,]+/g, (m) => lime(m));
|
|
8157
|
+
return `${dim(label)}${gap}${body}`;
|
|
8158
|
+
}
|
|
8159
|
+
const sub = /^( {2}[a-z]+ +)(.*)$/.exec(line);
|
|
8160
|
+
if (sub) return `${lime(sub[1] ?? "")}${dim(sub[2] ?? "")}`;
|
|
8161
|
+
if (/^ {10}\S/.test(line)) return dim(line);
|
|
8162
|
+
return line;
|
|
8163
|
+
}
|
|
7833
8164
|
|
|
7834
8165
|
// src/sync/server.ts
|
|
7835
8166
|
var SERVER_NAME = "aistack";
|
|
@@ -8085,7 +8416,7 @@ function runStdioSyncServer(deps) {
|
|
|
8085
8416
|
// src/index.ts
|
|
8086
8417
|
var program = new Command();
|
|
8087
8418
|
program.name("aistack").description("Measure and share your AI stack from your terminal").version(CLI_VERSION);
|
|
8088
|
-
program.command("login").description("Authenticate with AI Stack").action(loginCommand);
|
|
8419
|
+
program.command("login").description("Authenticate with AI Stack").option("--label <label>", "Set the machine label").action((options) => loginCommand(options));
|
|
8089
8420
|
program.command("collect").description("Scan and upload AI config files from your project").option("--no-global", "Exclude global config files (~/.claude, etc.)").action((options) => collectCommand({ global: options.global ?? true }));
|
|
8090
8421
|
program.command("create").description("Download and write your stack's AI config files").action(createCommand);
|
|
8091
8422
|
program.command("mcp").description(
|
|
@@ -8102,7 +8433,7 @@ program.command("sync").description("Scan, preview, and publish measured usage (
|
|
|
8102
8433
|
"silent background sync; 'on' asks your stack for the permission and installs the SessionStart hooks, 'off' revokes both"
|
|
8103
8434
|
).option(
|
|
8104
8435
|
"--every <hours>",
|
|
8105
|
-
"with --auto on: hours between auto-syncs (default
|
|
8436
|
+
"with --auto on: hours between auto-syncs (default 6)"
|
|
8106
8437
|
).action((options) => syncCommand(options));
|
|
8107
8438
|
program.command("connect").description("Install the in-session sync surface (MCP server + Skill)").argument("<harness>", 'the harness to connect ("claude")').action(connectCommand);
|
|
8108
8439
|
program.parse();
|