@cruxy/cli 1.8.1 → 1.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/README.md +1 -1
- package/dist/agent/loop.js +16 -1
- package/dist/agent/session.js +62 -9
- package/dist/approval/classify.js +170 -40
- package/dist/approval/prompt.js +52 -6
- package/dist/approval/service.js +1 -11
- package/dist/budget/session-budget.js +10 -1
- package/dist/checkpoint/coverage.js +147 -4
- package/dist/cli/commands/limits.js +76 -0
- package/dist/cli/commands/login.js +18 -5
- package/dist/cli/commands/pr.js +10 -1
- package/dist/cli/commands/rollback.js +10 -2
- package/dist/cli/commands/run.js +55 -5
- package/dist/cli/commands/sessions.js +156 -0
- package/dist/cli/program.js +4 -0
- package/dist/cli/repl.js +25 -0
- package/dist/cli/session-factory.js +31 -10
- package/dist/config/credential-lifetime.js +42 -0
- package/dist/config/credentials.js +66 -0
- package/dist/config/schema.js +141 -9
- package/dist/constants.js +12 -2
- package/dist/errors/boundary.js +4 -4
- package/dist/errors/constructors.js +136 -57
- package/dist/errors/types.js +18 -0
- package/dist/index.js +27 -1
- package/dist/jobs/manager.js +269 -17
- package/dist/limits/cache.js +21 -5
- package/dist/mcp/client.js +16 -0
- package/dist/onboarding/flow.js +121 -6
- package/dist/onboarding/steps.js +112 -0
- package/dist/render/limits-report.js +213 -0
- package/dist/render/limits-view.js +125 -0
- package/dist/sandbox/service.js +9 -0
- package/dist/sandbox/types.js +15 -0
- package/dist/session/index.js +3 -1
- package/dist/session/list.js +20 -6
- package/dist/session/log.js +120 -21
- package/dist/session/prune.js +106 -0
- package/dist/session/resume.js +5 -0
- package/dist/subagent/orchestrator.js +71 -31
- package/dist/subagent/spawn-tool.js +11 -4
- package/dist/tools/schema-depth.js +18 -0
- package/dist/tui/limits-panel.js +62 -30
- package/dist/usage/collect.js +20 -1
- package/dist/usage/summary.js +48 -1
- package/dist/usage/types.js +27 -0
- package/package.json +2 -2
package/dist/tui/limits-panel.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compactTokens } from "../render/units.js";
|
|
2
|
+
import { buildLimitsSummary, } from "../render/limits-view.js";
|
|
2
3
|
import { bindingWindow, usedFraction } from "../limits/index.js";
|
|
3
4
|
/**
|
|
4
5
|
* The limits panel (P9): what this credential may spend, and how much is left.
|
|
@@ -20,6 +21,13 @@ import { bindingWindow, usedFraction } from "../limits/index.js";
|
|
|
20
21
|
* the fraction the bar fills IS the fraction the next request is judged against.
|
|
21
22
|
* A bar is the right claim for a real measurement, and only for one.
|
|
22
23
|
*
|
|
24
|
+
* WHAT IT NO LONGER DECIDES ALONE (cli#138). Which facts a bucket may state, and
|
|
25
|
+
* the words for the ones that are sentences, now come from
|
|
26
|
+
* `render/limits-view.ts` — because `cruxy limits` states the same facts outside
|
|
27
|
+
* a session and two copies of "no pool cap" are two places it can be softened.
|
|
28
|
+
* What stays here is everything that is a WIDTH decision: one bar rather than
|
|
29
|
+
* two, which window earns it, and the labels that fit 24 columns.
|
|
30
|
+
*
|
|
23
31
|
* The corollary is the rule the rest of this file exists to keep: NO CAP, NO
|
|
24
32
|
* FRACTION. An enterprise pool reports `{enforced: false}` and a developer key
|
|
25
33
|
* reports `metered: true`; neither has an allowance, so neither gets a bar, a
|
|
@@ -39,14 +47,15 @@ export function bar(theme, fraction, cells = BAR_CELLS) {
|
|
|
39
47
|
theme.glyph.barEmpty.repeat(cells - filled));
|
|
40
48
|
}
|
|
41
49
|
/**
|
|
42
|
-
* The styler for a window's
|
|
50
|
+
* The styler for a window's tone. `warn` and `danger` are the two the user can
|
|
43
51
|
* act on, and they are different acts — one is "wrap up", the other is "you are
|
|
44
|
-
* already being refused" — so they are not merged into one warning.
|
|
52
|
+
* already being refused" — so they are not merged into one warning. The tone
|
|
53
|
+
* itself is resolved once, in the shared builder.
|
|
45
54
|
*/
|
|
46
|
-
function
|
|
47
|
-
if (
|
|
55
|
+
function toneStyle(theme, tone) {
|
|
56
|
+
if (tone === "danger")
|
|
48
57
|
return theme.danger;
|
|
49
|
-
if (
|
|
58
|
+
if (tone === "warn")
|
|
50
59
|
return theme.warning;
|
|
51
60
|
return theme.strong;
|
|
52
61
|
}
|
|
@@ -108,22 +117,31 @@ const STALE_AFTER_MS = 5 * 60_000;
|
|
|
108
117
|
* tokens, so it must take the smaller REMAINDER, which is sometimes the other
|
|
109
118
|
* window. `limits/reduce.ts` works the disagreement through.
|
|
110
119
|
*/
|
|
111
|
-
function poolLines(theme,
|
|
120
|
+
function poolLines(theme, summary, now) {
|
|
121
|
+
const pool = summary.budget;
|
|
122
|
+
if (pool.kind !== "pool")
|
|
123
|
+
return [];
|
|
112
124
|
const binding = bindingWindow(pool.monthly, pool.burst);
|
|
113
125
|
// Unreachable via `reduceLimits` (a pool with no readable window reduces to
|
|
114
126
|
// `unknown`), but the type permits it and inventing a bar is the one thing
|
|
115
|
-
// this panel must never do on the way to being defensive.
|
|
127
|
+
// this panel must never do on the way to being defensive. The words are the
|
|
128
|
+
// builder's, so `cruxy limits` says the same thing about the same absence.
|
|
116
129
|
if (!binding)
|
|
117
|
-
return [theme.muted("figures not reported")];
|
|
130
|
+
return [theme.muted(summary.noAllowance ?? "figures not reported")];
|
|
118
131
|
const { window: w, name } = binding;
|
|
119
132
|
const fraction = usedFraction(w) ?? 0;
|
|
120
|
-
|
|
133
|
+
// The tone the shared builder resolved for this window, found by key rather
|
|
134
|
+
// than recomputed — one place decides what `low` and `blocked` mean.
|
|
135
|
+
const tone = summary.windows.find((m) => m.key === name)?.tone ?? "normal";
|
|
136
|
+
const style = toneStyle(theme, tone);
|
|
121
137
|
const lines = [
|
|
122
138
|
`${style(bar(theme, fraction))} ${style(pct(fraction))}`,
|
|
123
139
|
`${compactTokens(w.used)} / ${compactTokens(w.cap)} ${name}`,
|
|
124
140
|
];
|
|
125
141
|
// The window that is NOT binding, so both dimensions are visible — a user
|
|
126
|
-
// whose burst is tight still needs to know the month is nearly gone too.
|
|
142
|
+
// whose burst is tight still needs to know the month is nearly gone too. The
|
|
143
|
+
// rail shows it as a bare percentage because it has room for one bar; `cruxy
|
|
144
|
+
// limits` draws both in full, which is the whole reason layout stayed here.
|
|
127
145
|
const other = name === "burst" ? pool.monthly : pool.burst;
|
|
128
146
|
const otherName = name === "burst" ? "month" : "burst";
|
|
129
147
|
const until = untilLabel(w.resetsAt, now);
|
|
@@ -139,6 +157,8 @@ function poolLines(theme, pool, now) {
|
|
|
139
157
|
// Only when it is actionable. A blocked model list is the answer to "why did
|
|
140
158
|
// that request fail"; mira's remaining requests are the answer to "what can I
|
|
141
159
|
// still run" — and neither question is being asked while the pool is healthy.
|
|
160
|
+
// A WIDTH decision, not a shape one: `cruxy limits` has room to state both
|
|
161
|
+
// whenever they exist, and does.
|
|
142
162
|
if (pool.blockedModels.length > 0) {
|
|
143
163
|
lines.push(theme.danger(`no ${pool.blockedModels.join(" ")}`));
|
|
144
164
|
}
|
|
@@ -161,27 +181,26 @@ function agoLabel(ms) {
|
|
|
161
181
|
}
|
|
162
182
|
/** The body for a ready reading, chosen by the budget shape. */
|
|
163
183
|
function readingLines(theme, reading, now) {
|
|
184
|
+
const summary = buildLimitsSummary(reading, now);
|
|
164
185
|
const lines = [];
|
|
165
|
-
|
|
166
|
-
switch (budget.kind) {
|
|
186
|
+
switch (summary.budget.kind) {
|
|
167
187
|
case "pool":
|
|
168
|
-
lines.push(...poolLines(theme,
|
|
188
|
+
lines.push(...poolLines(theme, summary, now));
|
|
169
189
|
break;
|
|
170
190
|
// Enterprise. NO fraction, and no consolation bar drawn at 0% either: a full
|
|
171
191
|
// green bar and "no limit" are read the same way at a glance, and only one
|
|
172
192
|
// of them is true.
|
|
173
193
|
case "unenforced":
|
|
174
|
-
lines.push(theme.strong(
|
|
175
|
-
lines.push(theme.muted("pool not enforced"));
|
|
194
|
+
lines.push(theme.strong(summary.tier));
|
|
176
195
|
break;
|
|
177
196
|
// A developer key: billed per token, gated by nothing it draws down. Saying
|
|
178
197
|
// "no pool" explicitly matters here — an empty budget section would read as
|
|
179
198
|
// a figure that failed to load rather than as a figure that does not exist.
|
|
180
199
|
case "metered":
|
|
181
|
-
lines.push(`${theme.strong(
|
|
182
|
-
lines.push(theme.muted("no pool cap"));
|
|
200
|
+
lines.push(`${theme.strong(summary.tier)}${theme.muted(" metered")}`);
|
|
183
201
|
break;
|
|
184
202
|
case "credits": {
|
|
203
|
+
const budget = summary.budget;
|
|
185
204
|
const fraction = budget.granted > 0
|
|
186
205
|
? Math.min(1, Math.max(0, budget.used / budget.granted))
|
|
187
206
|
: undefined;
|
|
@@ -199,30 +218,34 @@ function readingLines(theme, reading, now) {
|
|
|
199
218
|
// facts it DOES have are real and server-resolved, so both are shown; what
|
|
200
219
|
// it does not have, it declines to imply.
|
|
201
220
|
case "unknown":
|
|
202
|
-
lines.push(`${theme.strong(
|
|
203
|
-
lines.push(theme.muted("budget not reported"));
|
|
221
|
+
lines.push(`${theme.strong(summary.tier)}${theme.muted(` ${theme.glyph.sep} ${summary.bucket}`)}`);
|
|
204
222
|
break;
|
|
205
223
|
}
|
|
224
|
+
// Why there is nothing to draw, in the builder's words. Skipped for `pool`,
|
|
225
|
+
// which either drew something or already said so itself.
|
|
226
|
+
if (summary.budget.kind !== "pool" && summary.noAllowance) {
|
|
227
|
+
lines.push(theme.muted(summary.noAllowance));
|
|
228
|
+
}
|
|
229
|
+
// What this credential could have and does not (cli#263) — an offer, so it
|
|
230
|
+
// reads as availability to the `--paste` user who is on an apikey on purpose
|
|
231
|
+
// and can correctly ignore it.
|
|
232
|
+
for (const note of summary.notes)
|
|
233
|
+
lines.push(theme.muted(note));
|
|
206
234
|
// Rate and spend caps hang off every bucket, so they are appended once here
|
|
207
235
|
// rather than repeated per branch. They are the ONLY ceilings a metered key
|
|
208
236
|
// has — and they appear only when they exist, which is why they can never
|
|
209
237
|
// stand in for the pool a metered key does not have.
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
lines.push(theme.muted(`${remaining} / ${limit} rpm`));
|
|
213
|
-
}
|
|
214
|
-
if (reading.keySpendCap) {
|
|
215
|
-
lines.push(spendCapLine(theme, "key", reading.keySpendCap));
|
|
238
|
+
if (summary.rpm) {
|
|
239
|
+
lines.push(theme.muted(`${summary.rpm.remaining} / ${summary.rpm.limit} rpm`));
|
|
216
240
|
}
|
|
217
|
-
|
|
218
|
-
lines.push(spendCapLine(theme,
|
|
241
|
+
for (const { label, cap } of summary.spendCaps) {
|
|
242
|
+
lines.push(spendCapLine(theme, label, cap));
|
|
219
243
|
}
|
|
220
244
|
// The cache keeps the last good reading through a failed refresh, so the panel
|
|
221
245
|
// owes the user the age of what it is showing rather than the impression that
|
|
222
246
|
// it is live.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
lines.push(theme.muted(`as of ${agoLabel(age)} ago`));
|
|
247
|
+
if (summary.ageMs > STALE_AFTER_MS) {
|
|
248
|
+
lines.push(theme.muted(`as of ${agoLabel(summary.ageMs)} ago`));
|
|
226
249
|
}
|
|
227
250
|
return lines;
|
|
228
251
|
}
|
|
@@ -241,6 +264,15 @@ export function limitsPanelLines(theme, state, now = Date.now()) {
|
|
|
241
264
|
switch (state.reason) {
|
|
242
265
|
case "unauthenticated":
|
|
243
266
|
return [theme.muted("not signed in"), theme.muted("run cruxy login")];
|
|
267
|
+
case "expired":
|
|
268
|
+
// The same remedy as "not signed in", but a different fact — and the
|
|
269
|
+
// fact is the point. "Sign-in expired" tells someone their setup was
|
|
270
|
+
// right and simply aged out; "not signed in" invites them to go looking
|
|
271
|
+
// for what they configured wrong.
|
|
272
|
+
return [
|
|
273
|
+
theme.warning("sign-in expired"),
|
|
274
|
+
theme.muted("run cruxy login"),
|
|
275
|
+
];
|
|
244
276
|
case "unsupported":
|
|
245
277
|
// The gateway answered — it simply has no limits to report. Sending this
|
|
246
278
|
// user to debug their network would be the wrong errand entirely.
|
package/dist/usage/collect.js
CHANGED
|
@@ -30,9 +30,23 @@ export function accumulateCacheTokens(acc, ev) {
|
|
|
30
30
|
const systemClock = () => new Date().toISOString();
|
|
31
31
|
export class UsageCollector {
|
|
32
32
|
now;
|
|
33
|
+
origin;
|
|
33
34
|
entries = [];
|
|
34
|
-
|
|
35
|
+
/**
|
|
36
|
+
* `origin` is the DEFAULT stamped on entries that don't carry one of their own
|
|
37
|
+
* (cli#244) — the run this collector belongs to, e.g. `"turn"` for a turn's
|
|
38
|
+
* collector. A per-request `origin` always wins, because one collector can
|
|
39
|
+
* legitimately hold requests of two kinds: a turn's collector receives its own
|
|
40
|
+
* loop's requests AND the requests of every subagent that turn spawned, and
|
|
41
|
+
* those must not read as the same thing.
|
|
42
|
+
*
|
|
43
|
+
* Left unset the entries carry no origin at all — which is what an older
|
|
44
|
+
* build's entries look like, and is the honest shape for a collector that
|
|
45
|
+
* genuinely doesn't know.
|
|
46
|
+
*/
|
|
47
|
+
constructor(now = systemClock, origin) {
|
|
35
48
|
this.now = now;
|
|
49
|
+
this.origin = origin;
|
|
36
50
|
}
|
|
37
51
|
/**
|
|
38
52
|
* Record one completed request. When `req.usage` is absent the entry's token
|
|
@@ -41,6 +55,7 @@ export class UsageCollector {
|
|
|
41
55
|
*/
|
|
42
56
|
record(req) {
|
|
43
57
|
const u = req.usage;
|
|
58
|
+
const origin = req.origin ?? this.origin;
|
|
44
59
|
this.entries.push({
|
|
45
60
|
tier: req.tier,
|
|
46
61
|
inputTokens: u?.input_tokens,
|
|
@@ -75,6 +90,10 @@ export class UsageCollector {
|
|
|
75
90
|
...(req.reasoningEffort !== undefined
|
|
76
91
|
? { reasoningEffort: req.reasoningEffort }
|
|
77
92
|
: {}),
|
|
93
|
+
// Per-request first, collector default second, ABSENT third — absence is
|
|
94
|
+
// a real state (an older build's entries), so an unset default writes no
|
|
95
|
+
// key rather than a placeholder that groups would then count.
|
|
96
|
+
...(origin !== undefined ? { origin } : {}),
|
|
78
97
|
at: this.now(),
|
|
79
98
|
});
|
|
80
99
|
}
|
package/dist/usage/summary.js
CHANGED
|
@@ -29,6 +29,10 @@ export function summarizeRuns(runs) {
|
|
|
29
29
|
let requestsWithoutUsage = 0;
|
|
30
30
|
let requestsWithoutWeight = 0;
|
|
31
31
|
let requestsWithoutCost = 0;
|
|
32
|
+
// Keyed by the entry's own origin, and entries WITHOUT one are counted nowhere
|
|
33
|
+
// (cli#244). Defaulting them to `turn` would relabel every entry an older
|
|
34
|
+
// build wrote as something the file never said it was.
|
|
35
|
+
const requestsByOrigin = {};
|
|
32
36
|
// Every distinct currency the gateway stated. Summing across two of them would
|
|
33
37
|
// require an exchange rate we don't have and would never be told, so the set
|
|
34
38
|
// is kept and a >1 outcome withholds the total rather than guessing.
|
|
@@ -37,6 +41,9 @@ export function summarizeRuns(runs) {
|
|
|
37
41
|
for (const run of runs) {
|
|
38
42
|
for (const e of run.entries) {
|
|
39
43
|
requests++;
|
|
44
|
+
if (e.origin !== undefined) {
|
|
45
|
+
requestsByOrigin[e.origin] = (requestsByOrigin[e.origin] ?? 0) + 1;
|
|
46
|
+
}
|
|
40
47
|
const known = e.inputTokens !== undefined || e.outputTokens !== undefined;
|
|
41
48
|
if (!known)
|
|
42
49
|
requestsWithoutUsage++;
|
|
@@ -114,6 +121,7 @@ export function summarizeRuns(runs) {
|
|
|
114
121
|
requestsWithoutCost,
|
|
115
122
|
requestsWithoutUsage,
|
|
116
123
|
requests,
|
|
124
|
+
requestsByOrigin,
|
|
117
125
|
runCount: runs.length,
|
|
118
126
|
};
|
|
119
127
|
}
|
|
@@ -210,10 +218,49 @@ export function renderSummary(summary, t) {
|
|
|
210
218
|
if (summary.totalWeightedTokens !== undefined && weightShortfall > 0) {
|
|
211
219
|
parts.push(t.muted(`${weightShortfall} not weighted`));
|
|
212
220
|
}
|
|
221
|
+
//
|
|
222
|
+
// Cost has a THIRD case the other two don't: total absence. A partial
|
|
223
|
+
// shortfall explains itself (`2 without cost` beside a figure), but when NO
|
|
224
|
+
// request carries one there is no figure to hang the count on and the cost
|
|
225
|
+
// segment simply isn't emitted — so cost vanished with nothing said (cli#248).
|
|
226
|
+
//
|
|
227
|
+
// Gated on `requestsWithoutCost === requests`, NOT on `totalCost === undefined`.
|
|
228
|
+
// Mixed currencies ALSO withhold the total (see `summarizeRuns`) while costs
|
|
229
|
+
// genuinely exist, so keying on the total would print "cost not reported" next
|
|
230
|
+
// to "cost omitted: mixed currencies" — two contradictory claims about the same
|
|
231
|
+
// requests. The request count is the fact; the total is a rendering of it.
|
|
232
|
+
//
|
|
233
|
+
// The subtraction below is load-bearing in both branches: it is what stops this
|
|
234
|
+
// from restating `requestsWithoutUsage`. A request that reported nothing at all
|
|
235
|
+
// was already explained above, and is not separately news for having also
|
|
236
|
+
// carried no cost — so a store where every request reported nothing says
|
|
237
|
+
// nothing here, rather than saying it twice.
|
|
213
238
|
const costShortfall = summary.requestsWithoutCost - summary.requestsWithoutUsage;
|
|
214
|
-
if (summary.
|
|
239
|
+
if (summary.requestsWithoutCost === summary.requests) {
|
|
240
|
+
// No count: it is every request, and a number here would read as a partial.
|
|
241
|
+
if (costShortfall > 0)
|
|
242
|
+
parts.push(t.muted("cost not reported"));
|
|
243
|
+
}
|
|
244
|
+
else if (summary.totalCost !== undefined && costShortfall > 0) {
|
|
215
245
|
parts.push(t.muted(`${costShortfall} without cost`));
|
|
216
246
|
}
|
|
247
|
+
// Delegated + between-turns spend, broken out (cli#244). Folding a subagent's
|
|
248
|
+
// requests into its spawning turn's record is what makes `/usage` agree with
|
|
249
|
+
// `/budget`, but it also means `requests` stops matching "requests this
|
|
250
|
+
// conversation made" as the user counts them — three children at the ceiling
|
|
251
|
+
// can be most of the line. Naming the non-`turn` origins is what keeps the
|
|
252
|
+
// bigger number legible instead of merely bigger.
|
|
253
|
+
//
|
|
254
|
+
// `turn` is omitted deliberately: it is the unmarked case, and so are the
|
|
255
|
+
// entries carrying no origin at all (an older build's), which is why this
|
|
256
|
+
// reads `requestsByOrigin` rather than subtracting from `requests` — a
|
|
257
|
+
// subtraction would attribute every historical entry to whatever was left.
|
|
258
|
+
const delegated = Object.entries(summary.requestsByOrigin)
|
|
259
|
+
.filter(([origin]) => origin !== "turn")
|
|
260
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
261
|
+
.map(([origin, n]) => `${origin} ${n}`);
|
|
262
|
+
if (delegated.length > 0)
|
|
263
|
+
parts.push(t.muted(delegated.join(", ")));
|
|
217
264
|
// Two gateways quoting two currencies: refuse to add them, and say why rather
|
|
218
265
|
// than just dropping the cost silently.
|
|
219
266
|
if (summary.costMixedCurrency) {
|
package/dist/usage/types.js
CHANGED
|
@@ -110,6 +110,33 @@ export const UsageEntrySchema = z
|
|
|
110
110
|
* gateway saying so writes the literal `"none"`.
|
|
111
111
|
*/
|
|
112
112
|
reasoningEffort: z.string().optional(),
|
|
113
|
+
/**
|
|
114
|
+
* WHAT DROVE THIS REQUEST (cli#244). One of:
|
|
115
|
+
*
|
|
116
|
+
* - `"turn"` — the user's own turn: the main loop, its compaction, and (in
|
|
117
|
+
* plan mode) the propose + execution steps. The default for anything a
|
|
118
|
+
* turn's collector records without saying otherwise.
|
|
119
|
+
* - `"subagent"` — a delegated child. Folded into the SPAWNING TURN's
|
|
120
|
+
* record rather than getting one of its own, so `runCount` keeps meaning
|
|
121
|
+
* "turns" and a fan-out of three does not become four runs.
|
|
122
|
+
* - `"job"` — a background job. A job can start after the turn that
|
|
123
|
+
* launched it has returned, so it has no parent record to join and gets
|
|
124
|
+
* one of its own.
|
|
125
|
+
* - `"compact"` — a manually forced `/compact` (cli#254). Also between
|
|
126
|
+
* turns, also its own record. NOT `"turn"`: it is not one, and calling it
|
|
127
|
+
* one would make `runCount` count it as a turn the user never took.
|
|
128
|
+
*
|
|
129
|
+
* ABSENT MEANS "WRITTEN BY A BUILD BEFORE THIS", NOT "turn". Anything that
|
|
130
|
+
* groups by origin needs to leave unattributed entries out of every bucket
|
|
131
|
+
* rather than defaulting them into one — a historical entry relabelled as a
|
|
132
|
+
* turn is a claim the file never made. {@link UsageSummary.requestsByOrigin}
|
|
133
|
+
* does exactly that; see its note.
|
|
134
|
+
*
|
|
135
|
+
* A STRING, NOT A `z.enum`, for the reason {@link tier} and `routingMode`
|
|
136
|
+
* already are: an origin a future build introduces should render as itself
|
|
137
|
+
* here rather than fail the parse and cost the user the whole file.
|
|
138
|
+
*/
|
|
139
|
+
origin: z.string().optional(),
|
|
113
140
|
/** ISO-8601 timestamp the request completed. */
|
|
114
141
|
at: z.string(),
|
|
115
142
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cruxy/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "an agentic coding CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"undici": "^6.21.0",
|
|
37
37
|
"zod": "^3.23.8",
|
|
38
38
|
"zod-to-json-schema": "^3.23.5",
|
|
39
|
-
"@cruxy/sdk": "0.
|
|
39
|
+
"@cruxy/sdk": "0.8.0"
|
|
40
40
|
},
|
|
41
41
|
"optionalDependencies": {
|
|
42
42
|
"better-sqlite3": "^12.11.1"
|