@danypops/jittor 0.10.0 → 0.11.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.
Files changed (42) hide show
  1. package/README.md +8 -5
  2. package/extension/src/benchmark-tui.ts +4 -0
  3. package/extension/src/capabilities/codex-recovery.ts +127 -0
  4. package/extension/src/capabilities/http-headers.ts +5 -0
  5. package/extension/src/capabilities/local-run-telemetry.ts +104 -0
  6. package/extension/src/capabilities/provider-response-telemetry.ts +96 -0
  7. package/extension/src/footer.ts +10 -53
  8. package/extension/src/index.ts +92 -272
  9. package/extension/src/session-identity.ts +20 -0
  10. package/extension/src/tui.ts +20 -11
  11. package/package.json +1 -1
  12. package/src/adapters/sqlite-metric-store.ts +11 -2
  13. package/src/adapters/sqlite-session-identity-store.ts +45 -0
  14. package/src/cli-commands/benchmarks.ts +140 -0
  15. package/src/cli-commands/compaction.ts +17 -0
  16. package/src/cli-commands/context.ts +49 -0
  17. package/src/cli-commands/metrics.ts +296 -0
  18. package/src/cli-commands/op.ts +40 -0
  19. package/src/cli-commands/route-args.ts +15 -0
  20. package/src/cli-commands/router.ts +207 -0
  21. package/src/cli-commands/service-daemon.ts +72 -0
  22. package/src/cli-commands/session.ts +42 -0
  23. package/src/cli-commands/support.ts +33 -0
  24. package/src/cli.ts +42 -769
  25. package/src/constants.ts +7 -0
  26. package/src/daemon.ts +13 -3
  27. package/src/db.ts +15 -1
  28. package/src/operations/benchmark-operations.ts +12 -0
  29. package/src/operations/context-operations.ts +30 -0
  30. package/src/operations/metrics-operations.ts +77 -0
  31. package/src/operations/model-ranking-operations.ts +16 -0
  32. package/src/operations/router-operations.ts +19 -0
  33. package/src/operations/session-identity-operations.ts +15 -0
  34. package/src/operations/session-scope.ts +31 -0
  35. package/src/operations/types.ts +3 -0
  36. package/src/ports/metric-store.ts +2 -0
  37. package/src/ports/router-controller.ts +9 -9
  38. package/src/ports/session-identity-store.ts +5 -0
  39. package/src/providers/telemetry-sources.ts +2 -1
  40. package/src/router.ts +124 -67
  41. package/src/service.ts +60 -118
  42. package/src/session-identity-service.ts +55 -0
package/src/cli.ts CHANGED
@@ -1,100 +1,28 @@
1
1
  #!/usr/bin/env bun
2
- import { execFileSync } from "node:child_process";
3
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
- import { homedir } from "node:os";
5
- import { dirname, join } from "node:path";
6
2
  import { fileURLToPath } from "node:url";
7
- import {
8
- BENCHMARK_MAX_QUERY_LIMIT,
9
- CLI_AVAILABLE_ROUTES_MAX,
10
- CLI_METRICS_HUMAN_MAX_ROWS,
11
- HUMAN_TEXT_FIELD_MAX_CHARACTERS,
12
- MAX_QUERY_LIMIT,
13
- MODEL_RANKING_DEFAULT_CONTEXT_WEIGHT,
14
- MODEL_RANKING_DEFAULT_COST_WEIGHT,
15
- MODEL_RANKING_DEFAULT_LATENCY_WEIGHT,
16
- MODEL_RANKING_DEFAULT_QUALITY_WEIGHT,
17
- MODEL_RANKING_DEFAULT_RELIABILITY_WEIGHT,
18
- MODEL_RANKING_MAX_SOURCES,
19
- SYSTEMD_UNIT_NAME,
20
- MAX_USAGE_BUCKETS,
21
- USAGE_MAX_DISTINCT_SCOPES,
22
- } from "./constants.ts";
23
- import { connectJittorClient, type JittorClient } from "./client.ts";
3
+ import { connectJittorClient } from "./client.ts";
24
4
  import { serveMain } from "./daemon.ts";
25
- import type { BenchmarkQuery, BenchmarkQueryResult, BenchmarkRefreshResult } from "./domain/benchmark.ts";
26
- import type { ModelRecommendationInput } from "./domain/model-ranking-service.ts";
27
- import type { ModelCandidate, ModelRankingResult, ScopeAuthority, UtilityWeights } from "./domain/model-ranking.ts";
28
- import { TASK_DOMAINS, TASK_TYPES, type ModelTaskDomain, type ModelTaskType } from "./domain/model-observation.ts";
29
- import type { ContextAssessment } from "./domain/context-telemetry.ts";
30
- import { METRIC_UNITS, type MetricObservation, type MetricQuery, type MetricUnit, type StoredMetricObservation } from "./domain/metric.ts";
31
- import type { CompactionDurationEstimate } from "./domain/context-telemetry.ts";
32
- import type { TaskCostSummary } from "./domain/task-cost.ts";
33
- import type { PolicyDecision, Route } from "./policy.ts";
34
- import type { RouteOverride, RouterStatus, TelemetryPollResult } from "./ports/router-controller.ts";
35
- import { EXPECTED_OPERATION_NAMES, type OperationInputs, type OperationName, type OperationOutputs } from "./service.ts";
36
- import { resolveJittorPaths } from "./state.ts";
37
-
38
- export interface SystemdUnitOptions {
39
- bunBin: string;
40
- cliPath: string;
41
- codexAuthFile?: string;
42
- openRouterBenchmarks?: boolean;
43
- }
44
-
45
- export function renderSystemdUnit(options: SystemdUnitOptions): string {
46
- return `[Unit]
47
- Description=Jittor token optimizing router
48
- After=default.target network-online.target
49
- Wants=network-online.target
50
-
51
- [Service]
52
- Type=simple
53
- ExecStart=${options.bunBin} ${options.cliPath} serve
54
- ${options.codexAuthFile ? `Environment="JITTOR_CODEX_AUTH_FILE=${options.codexAuthFile.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"\n` : ""}${options.openRouterBenchmarks ? "Environment=JITTOR_OPENROUTER_BENCHMARKS=1\n" : ""}Restart=always
55
- RestartSec=2
56
- NoNewPrivileges=true
57
- PrivateTmp=true
58
-
59
- [Install]
60
- WantedBy=default.target
61
- `;
62
- }
63
-
64
- export interface CliDependencies {
65
- client: Pick<JittorClient, "call">;
66
- stdout(line: string): void;
67
- stderr(line: string): void;
68
- systemctl(...args: string[]): void;
69
- installService(): void;
70
- serve(): void;
71
- }
72
-
73
- function systemctl(...args: string[]): void {
74
- execFileSync("systemctl", ["--user", ...args], { stdio: "inherit" });
75
- }
76
-
77
- function installService(): void {
78
- const unitPath = resolveJittorPaths().systemdUnit;
79
- mkdirSync(dirname(unitPath), { recursive: true });
80
- const codexAuthFile = join(process.env["CODEX_HOME"] ?? join(homedir(), ".codex"), "auth.json");
81
- writeFileSync(unitPath, renderSystemdUnit({
82
- bunBin: process.execPath,
83
- cliPath: fileURLToPath(import.meta.url),
84
- ...(existsSync(codexAuthFile) ? { codexAuthFile } : {}),
85
- openRouterBenchmarks: process.env["JITTOR_OPENROUTER_BENCHMARKS"] === "1",
86
- }));
87
- systemctl("daemon-reload");
88
- systemctl("enable", SYSTEMD_UNIT_NAME);
89
- systemctl("restart", SYSTEMD_UNIT_NAME);
90
- }
5
+ import type { CliDependencies } from "./cli-commands/support.ts";
6
+ import { installService, renderSystemdUnit, systemctl, runServiceCommand, SERVICE_USAGE_LINES } from "./cli-commands/service-daemon.ts";
7
+ import { runSessionCommand, SESSION_USAGE_LINES } from "./cli-commands/session.ts";
8
+ import { runMetricsCommand, METRICS_USAGE_LINES, formatMetricsQuery, formatCostByTask } from "./cli-commands/metrics.ts";
9
+ import { runTelemetryCommand, runRouterCommand, ROUTER_USAGE_LINES, formatRouterStatus } from "./cli-commands/router.ts";
10
+ import { runCompactionCommand } from "./cli-commands/compaction.ts";
11
+ import { runOpCommand, OP_USAGE_LINES } from "./cli-commands/op.ts";
12
+ import { runBenchmarksCommand, BENCHMARKS_USAGE_LINES } from "./cli-commands/benchmarks.ts";
13
+ import { runContextCommand, CONTEXT_USAGE_LINES, formatContextAssessment } from "./cli-commands/context.ts";
14
+
15
+ // Re-exported for external callers (tests, daemon.ts's systemd-unit test) that import these
16
+ // directly from cli.ts rather than reaching into src/cli-commands/*.
17
+ export type { CliDependencies };
18
+ export { renderSystemdUnit, formatMetricsQuery, formatCostByTask, formatRouterStatus, formatContextAssessment };
91
19
 
92
20
  const DEFAULT_DEPENDENCIES: CliDependencies = {
93
21
  get client() { return connectJittorClient(); },
94
22
  stdout: console.log,
95
23
  stderr: console.error,
96
24
  systemctl,
97
- installService,
25
+ installService: () => installService(fileURLToPath(import.meta.url)),
98
26
  serve: serveMain,
99
27
  };
100
28
 
@@ -102,694 +30,39 @@ function usage(stderr: (line: string) => void): number {
102
30
  stderr([
103
31
  "Usage: jittor <command> [options]",
104
32
  " serve",
105
- " service <install|start|stop|restart|status|checkpoint>",
106
- " context [--since <ms>] [--until <ms>] [--json]",
107
- " benchmarks <status|refresh|list|rank> [options] [--json]",
108
- " metrics record --source <s> --scope <s> --metric <s> --value <number|null> --unit <unit> [--observed-at <ms>] [--attributes <json>] [--json]",
109
- " metrics query [--source <s>] [--scope <s>] [--metric <s>] [--since <ms>] [--until <ms>] [--limit <n>] [--order asc|desc] [--json]",
110
- " metrics prune --before <ms> [--force] [--json] (force required if before is newer than 24h ago)",
111
- ` metrics distinct-scopes --source <s> --since <ms> --until <ms> [--limit 1..${USAGE_MAX_DISTINCT_SCOPES}] [--json]`,
112
- ` metrics usage-series --source <s> --since <ms> --until <ms> --bucket-size-ms <ms> --bucket-count 1..${MAX_USAGE_BUCKETS} [--scope-limit 1..${USAGE_MAX_DISTINCT_SCOPES}] [--json]`,
113
- " metrics cost-by-task --since <ms> --until <ms> [--json]",
114
- " telemetry poll [--json]",
115
- " compaction estimate [--json]",
116
- " router <status|decide|pause|resume|clear-override> [--json]",
117
- " router override --route <provider/model@thinking> [--expires-at <ms>] [--json]",
118
- " router current-route --route <provider/model@thinking> [--json]",
119
- " router available-routes [--route <provider/model@thinking> ...] [--json]",
120
- " op <operation> [--input <json>]",
33
+ ...SERVICE_USAGE_LINES,
34
+ ...CONTEXT_USAGE_LINES,
35
+ ...BENCHMARKS_USAGE_LINES,
36
+ ...METRICS_USAGE_LINES,
37
+ ...ROUTER_USAGE_LINES,
38
+ ...SESSION_USAGE_LINES,
39
+ ...OP_USAGE_LINES,
121
40
  ].join("\n"));
122
41
  return 2;
123
42
  }
124
43
 
125
- function parseContextArgs(args: string[]): { input: { since?: number; until?: number }; json: boolean } | null {
126
- const input: { since?: number; until?: number } = {};
127
- let json = false;
128
- for (let index = 0; index < args.length; index += 1) {
129
- const argument = args[index];
130
- if (argument === "--json") { json = true; continue; }
131
- if (argument !== "--since" && argument !== "--until") return null;
132
- const raw = args[++index];
133
- const value = raw === undefined ? Number.NaN : Number(raw);
134
- if (!Number.isSafeInteger(value) || value < 0) return null;
135
- if (argument === "--since") input.since = value;
136
- else input.until = value;
137
- }
138
- if (input.since !== undefined && input.until !== undefined && input.until < input.since) return null;
139
- return { input, json };
140
- }
141
-
142
- function parseRoute(raw: string | undefined): Route | null {
143
- if (raw === undefined) return null;
144
- return parseCandidate(raw);
145
- }
146
-
147
- interface MetricsRecordArgs { input: MetricObservation; json: boolean }
148
-
149
- function parseMetricsRecordArgs(args: string[]): MetricsRecordArgs | null {
150
- let json = false;
151
- let source: string | undefined;
152
- let scope: string | undefined;
153
- let metric: string | undefined;
154
- let value: number | null | undefined;
155
- let unit: MetricUnit | undefined;
156
- let observedAt: number | undefined;
157
- let attributes: Record<string, unknown> | undefined;
158
- for (let index = 0; index < args.length; index += 1) {
159
- const argument = args[index];
160
- if (argument === "--json") { json = true; continue; }
161
- if (!["--source", "--scope", "--metric", "--value", "--unit", "--observed-at", "--attributes"].includes(argument ?? "")) return null;
162
- const raw = args[++index];
163
- if (raw === undefined || raw.length === 0) return null;
164
- if (argument === "--source") source = raw;
165
- else if (argument === "--scope") scope = raw;
166
- else if (argument === "--metric") metric = raw;
167
- else if (argument === "--value") {
168
- if (raw.toLowerCase() === "null") value = null;
169
- else {
170
- const parsed = Number(raw);
171
- if (!Number.isFinite(parsed)) return null;
172
- value = parsed;
173
- }
174
- } else if (argument === "--unit") {
175
- if (!METRIC_UNITS.includes(raw as MetricUnit)) return null;
176
- unit = raw as MetricUnit;
177
- } else if (argument === "--observed-at") {
178
- const parsed = Number(raw);
179
- if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
180
- observedAt = parsed;
181
- } else {
182
- try {
183
- const parsed = JSON.parse(raw);
184
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
185
- attributes = parsed as Record<string, unknown>;
186
- } catch {
187
- return null;
188
- }
189
- }
190
- }
191
- if (source === undefined || scope === undefined || metric === undefined || value === undefined || unit === undefined) return null;
192
- return {
193
- json,
194
- input: {
195
- source, scope, metric, value, unit,
196
- observedAt: observedAt ?? Date.now(),
197
- ...(attributes ? { attributes } : {}),
198
- },
199
- };
200
- }
201
-
202
- interface MetricsQueryArgs { input: MetricQuery; json: boolean }
203
-
204
- function parseMetricsQueryArgs(args: string[]): MetricsQueryArgs | null {
205
- let json = false;
206
- const input: MetricQuery = {};
207
- for (let index = 0; index < args.length; index += 1) {
208
- const argument = args[index];
209
- if (argument === "--json") { json = true; continue; }
210
- if (!["--source", "--scope", "--metric", "--since", "--until", "--limit", "--order"].includes(argument ?? "")) return null;
211
- const raw = args[++index];
212
- if (raw === undefined || raw.length === 0) return null;
213
- if (argument === "--source") input.source = raw;
214
- else if (argument === "--scope") input.scope = raw;
215
- else if (argument === "--metric") input.metric = raw;
216
- else if (argument === "--order") {
217
- if (raw !== "asc" && raw !== "desc") return null;
218
- input.order = raw;
219
- } else {
220
- const parsed = Number(raw);
221
- if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
222
- if (argument === "--since") input.since = parsed;
223
- else if (argument === "--until") input.until = parsed;
224
- else {
225
- if (parsed < 1 || parsed > MAX_QUERY_LIMIT) return null;
226
- input.limit = parsed;
227
- }
228
- }
229
- }
230
- if (input.since !== undefined && input.until !== undefined && input.until < input.since) return null;
231
- return { input, json };
232
- }
233
-
234
- interface MetricsDistinctScopesArgs { input: { source: string; since: number; until: number; limit?: number }; json: boolean }
235
-
236
- function parseMetricsDistinctScopesArgs(args: string[]): MetricsDistinctScopesArgs | null {
237
- let json = false;
238
- let source: string | undefined;
239
- let since: number | undefined;
240
- let until: number | undefined;
241
- let limit: number | undefined;
242
- for (let index = 0; index < args.length; index += 1) {
243
- const argument = args[index];
244
- if (argument === "--json") { json = true; continue; }
245
- if (!["--source", "--since", "--until", "--limit"].includes(argument ?? "")) return null;
246
- const raw = args[++index];
247
- if (raw === undefined || raw.length === 0) return null;
248
- if (argument === "--source") { source = raw; continue; }
249
- const parsed = Number(raw);
250
- if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
251
- if (argument === "--since") since = parsed;
252
- else if (argument === "--until") until = parsed;
253
- else {
254
- if (parsed < 1 || parsed > USAGE_MAX_DISTINCT_SCOPES) return null;
255
- limit = parsed;
256
- }
257
- }
258
- if (source === undefined || since === undefined || until === undefined || until < since) return null;
259
- return { input: { source, since, until, ...(limit === undefined ? {} : { limit }) }, json };
260
- }
261
-
262
- interface MetricsUsageSeriesArgs { input: { source: string; since: number; until: number; bucketSizeMs: number; bucketCount: number; scopeLimit?: number }; json: boolean }
263
-
264
- function parseMetricsUsageSeriesArgs(args: string[]): MetricsUsageSeriesArgs | null {
265
- let json = false;
266
- let source: string | undefined;
267
- let since: number | undefined;
268
- let until: number | undefined;
269
- let bucketSizeMs: number | undefined;
270
- let bucketCount: number | undefined;
271
- let scopeLimit: number | undefined;
272
- for (let index = 0; index < args.length; index += 1) {
273
- const argument = args[index];
274
- if (argument === "--json") { json = true; continue; }
275
- if (!(["--source", "--since", "--until", "--bucket-size-ms", "--bucket-count", "--scope-limit"].includes(argument ?? ""))) return null;
276
- const raw = args[++index];
277
- if (raw === undefined || raw.length === 0) return null;
278
- if (argument === "--source") { source = raw; continue; }
279
- const parsed = Number(raw);
280
- if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
281
- if (argument === "--since") since = parsed;
282
- else if (argument === "--until") until = parsed;
283
- else if (argument === "--bucket-size-ms") { if (parsed < 1) return null; bucketSizeMs = parsed; }
284
- else if (argument === "--bucket-count") { if (parsed < 1 || parsed > MAX_USAGE_BUCKETS) return null; bucketCount = parsed; }
285
- else { if (parsed < 1 || parsed > USAGE_MAX_DISTINCT_SCOPES) return null; scopeLimit = parsed; }
286
- }
287
- if (source === undefined || since === undefined || until === undefined || until < since || bucketSizeMs === undefined || bucketCount === undefined) return null;
288
- return { input: { source, since, until, bucketSizeMs, bucketCount, ...(scopeLimit === undefined ? {} : { scopeLimit }) }, json };
289
- }
290
-
291
- interface CostByTaskArgs { input: { since: number; until: number }; json: boolean }
292
-
293
- function parseCostByTaskArgs(args: string[]): CostByTaskArgs | null {
294
- let json = false;
295
- let since: number | undefined;
296
- let until: number | undefined;
297
- for (let index = 0; index < args.length; index += 1) {
298
- const argument = args[index];
299
- if (argument === "--json") { json = true; continue; }
300
- if (!["--since", "--until"].includes(argument ?? "")) return null;
301
- const raw = args[++index];
302
- const parsed = Number(raw);
303
- if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
304
- if (argument === "--since") since = parsed;
305
- else until = parsed;
306
- }
307
- if (since === undefined || until === undefined || until < since) return null;
308
- return { input: { since, until }, json };
309
- }
310
-
311
- interface MetricsPruneArgs { input: { before: number; force?: boolean }; json: boolean }
312
-
313
- function parseMetricsPruneArgs(args: string[]): MetricsPruneArgs | null {
314
- let json = false;
315
- let force = false;
316
- let before: number | undefined;
317
- for (let index = 0; index < args.length; index += 1) {
318
- const argument = args[index];
319
- if (argument === "--json") { json = true; continue; }
320
- if (argument === "--force") { force = true; continue; }
321
- if (argument !== "--before") return null;
322
- const raw = args[++index];
323
- const parsed = Number(raw);
324
- if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
325
- before = parsed;
326
- }
327
- if (before === undefined) return null;
328
- return { input: { before, ...(force ? { force } : {}) }, json };
329
- }
330
-
331
- interface RouterOverrideArgs { input: RouteOverride; json: boolean }
332
-
333
- function parseRouterOverrideArgs(args: string[]): RouterOverrideArgs | null {
334
- let json = false;
335
- let route: Route | null = null;
336
- let expiresAt: number | null = null;
337
- for (let index = 0; index < args.length; index += 1) {
338
- const argument = args[index];
339
- if (argument === "--json") { json = true; continue; }
340
- if (!["--route", "--expires-at"].includes(argument ?? "")) return null;
341
- const raw = args[++index];
342
- if (raw === undefined || raw.length === 0) return null;
343
- if (argument === "--route") {
344
- route = parseRoute(raw);
345
- if (!route) return null;
346
- } else {
347
- const parsed = Number(raw);
348
- if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
349
- expiresAt = parsed;
350
- }
351
- }
352
- if (!route) return null;
353
- return { input: { route, expiresAt }, json };
354
- }
355
-
356
- interface RouterRouteArgs { input: Route; json: boolean }
357
-
358
- function parseRouterRouteArgs(args: string[]): RouterRouteArgs | null {
359
- let json = false;
360
- let route: Route | null = null;
361
- for (let index = 0; index < args.length; index += 1) {
362
- const argument = args[index];
363
- if (argument === "--json") { json = true; continue; }
364
- if (argument !== "--route") return null;
365
- const raw = args[++index];
366
- if (raw === undefined) return null;
367
- route = parseRoute(raw);
368
- if (!route) return null;
369
- }
370
- if (!route) return null;
371
- return { input: route, json };
372
- }
373
-
374
- interface RouterAvailableRoutesArgs { input: { routes: Route[] }; json: boolean }
375
-
376
- function parseRouterAvailableRoutesArgs(args: string[]): RouterAvailableRoutesArgs | null {
377
- let json = false;
378
- const routes: Route[] = [];
379
- for (let index = 0; index < args.length; index += 1) {
380
- const argument = args[index];
381
- if (argument === "--json") { json = true; continue; }
382
- if (argument !== "--route") return null;
383
- const raw = args[++index];
384
- if (raw === undefined) return null;
385
- const route = parseRoute(raw);
386
- if (!route) return null;
387
- if (routes.length >= CLI_AVAILABLE_ROUTES_MAX) return null;
388
- routes.push(route);
389
- }
390
- return { input: { routes }, json };
391
- }
392
-
393
- function parseJsonOnlyArgs(args: string[]): { json: boolean } | null {
394
- let json = false;
395
- for (const argument of args) {
396
- if (argument !== "--json") return null;
397
- json = true;
398
- }
399
- return { json };
400
- }
401
-
402
- function parseOpArgs(args: string[]): { operation: OperationName; input: Record<string, unknown> } | null {
403
- const [operation, ...rest] = args;
404
- if (operation === undefined || !EXPECTED_OPERATION_NAMES.includes(operation as OperationName)) return null;
405
- let input: Record<string, unknown> = {};
406
- for (let index = 0; index < rest.length; index += 1) {
407
- if (rest[index] !== "--input") return null;
408
- const raw = rest[++index];
409
- if (raw === undefined) return null;
410
- try {
411
- const parsed = JSON.parse(raw);
412
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
413
- input = parsed as Record<string, unknown>;
414
- } catch {
415
- return null;
416
- }
417
- }
418
- return { operation: operation as OperationName, input };
419
- }
420
-
421
- interface BenchmarkArgs {
422
- action: "status" | "refresh" | "list" | "rank";
423
- json: boolean;
424
- force: boolean;
425
- query?: BenchmarkQuery;
426
- recommendation?: ModelRecommendationInput;
427
- }
428
-
429
- function parseCandidate(raw: string): ModelCandidate | null {
430
- const separator = raw.indexOf("/");
431
- const thinkingSeparator = raw.lastIndexOf("@");
432
- if (separator <= 0 || thinkingSeparator <= separator + 1 || thinkingSeparator === raw.length - 1) return null;
433
- return { provider: raw.slice(0, separator), model: raw.slice(separator + 1, thinkingSeparator), thinking: raw.slice(thinkingSeparator + 1) };
434
- }
435
-
436
- function parseBenchmarkArgs(action: string | undefined, args: string[]): BenchmarkArgs | null {
437
- if (action !== "status" && action !== "refresh" && action !== "list" && action !== "rank") return null;
438
- let json = false;
439
- let force = false;
440
- const query: Partial<BenchmarkQuery> = {};
441
- const candidates: ModelCandidate[] = [];
442
- const sourceIds: string[] = [];
443
- let scopeAuthority: ScopeAuthority = "available-models";
444
- let domain: ModelTaskDomain = "general";
445
- let type: ModelTaskType = "general";
446
- let budgetPressure = 0;
447
- const weights: UtilityWeights = {
448
- quality: MODEL_RANKING_DEFAULT_QUALITY_WEIGHT, cost: MODEL_RANKING_DEFAULT_COST_WEIGHT,
449
- latency: MODEL_RANKING_DEFAULT_LATENCY_WEIGHT, context: MODEL_RANKING_DEFAULT_CONTEXT_WEIGHT,
450
- reliability: MODEL_RANKING_DEFAULT_RELIABILITY_WEIGHT,
451
- };
452
- for (let index = 0; index < args.length; index += 1) {
453
- const argument = args[index];
454
- if (argument === "--json") { json = true; continue; }
455
- if (argument === "--force" && action === "refresh") { force = true; continue; }
456
- const allowed = action === "list" ? ["--source", "--model", "--dimension", "--limit"]
457
- : action === "rank" ? ["--candidate", "--source", "--domain", "--type", "--scope", "--budget", "--weight-quality", "--weight-cost", "--weight-latency", "--weight-context", "--weight-reliability"] : [];
458
- if (!allowed.includes(argument ?? "")) return null;
459
- const raw = args[++index];
460
- if (raw === undefined || raw.length === 0) return null;
461
- if (action === "list") {
462
- if (argument === "--limit") {
463
- const limit = Number(raw);
464
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > BENCHMARK_MAX_QUERY_LIMIT) return null;
465
- query.limit = limit;
466
- } else if (argument === "--source") query.sourceId = raw;
467
- else if (argument === "--model") query.model = raw;
468
- else query.dimension = raw;
469
- continue;
470
- }
471
- if (argument === "--candidate") {
472
- const candidate = parseCandidate(raw);
473
- if (!candidate) return null;
474
- candidates.push(candidate);
475
- } else if (argument === "--source") sourceIds.push(raw);
476
- else if (argument === "--domain") {
477
- if (!TASK_DOMAINS.includes(raw as ModelTaskDomain)) return null;
478
- domain = raw as ModelTaskDomain;
479
- } else if (argument === "--type") {
480
- if (!TASK_TYPES.includes(raw as ModelTaskType)) return null;
481
- type = raw as ModelTaskType;
482
- } else if (argument === "--scope") {
483
- if (raw !== "exact-session" && raw !== "available-models") return null;
484
- scopeAuthority = raw;
485
- } else if (argument === "--budget") budgetPressure = Number(raw);
486
- else {
487
- const weight = Number(raw);
488
- if (!Number.isFinite(weight) || weight < 0 || weight > 10) return null;
489
- weights[argument!.slice("--weight-".length) as keyof UtilityWeights] = weight;
490
- }
491
- }
492
- if (action === "list" && query.sourceId === undefined) return null;
493
- if (action === "rank" && (candidates.length === 0 || sourceIds.length > MODEL_RANKING_MAX_SOURCES || !Number.isFinite(budgetPressure) || budgetPressure < 0 || budgetPressure > 2)) return null;
494
- return {
495
- action, json, force,
496
- ...(action === "list" ? { query: query as BenchmarkQuery } : {}),
497
- ...(action === "rank" ? { recommendation: { candidates, sourceIds: [...new Set(sourceIds)], scopeAuthority, domain, type, budgetPressure, weights } } : {}),
498
- };
499
- }
500
-
501
- function value(value: number | null, suffix = ""): string {
502
- return value === null ? "unknown" : `${Math.round(value).toLocaleString()}${suffix}`;
503
- }
504
-
505
- export function formatBenchmarkStatus(result: BenchmarkRefreshResult): string {
506
- if (result.sources.length === 0) return "Benchmark sources: none configured";
507
- return ["Benchmark sources:", ...result.sources.map((source) => {
508
- const state = source.ok === null ? "not refreshed" : source.ok ? "ready" : "refresh failed";
509
- return `- ${source.id}: ${state} · ${source.observations.toLocaleString()} observations · ${source.hasEvidence ? "evidence retained" : "no evidence"}`;
510
- })].join("\n");
511
- }
512
-
513
- function humanField(value: string): string {
514
- return value.length <= HUMAN_TEXT_FIELD_MAX_CHARACTERS ? value : `${value.slice(0, HUMAN_TEXT_FIELD_MAX_CHARACTERS - 1)}…`;
515
- }
516
-
517
- export function formatBenchmarkQuery(result: BenchmarkQueryResult): string {
518
- return [
519
- `Benchmark evidence: ${humanField(result.sourceId)} · ${result.completeness} · ${result.freshness} · ${result.observations.length.toLocaleString()} observations`,
520
- ...result.observations.map((observation) => `- ${humanField(observation.model.canonical)} · ${humanField(observation.dimension)} ${observation.value.toLocaleString()} ${observation.unit} · ${humanField(observation.provenance.publisher)} · confidence ${(observation.provenance.confidence * 100).toFixed(0)}%`),
521
- ].join("\n");
522
- }
523
-
524
- export function formatModelRanking(result: ModelRankingResult): string {
525
- return [
526
- `Model ranking: ${result.completeness} · scope ${result.scopeAuthority}${result.scopeWarning ? " · advisory only" : ""}`,
527
- ...result.ranked.map((item, index) => `${index + 1}. ${humanField(item.identity)} · utility ${item.utility === null ? "unknown" : item.utility.toFixed(3)} · confidence ${(item.confidence * 100).toFixed(0)}%`),
528
- ...(result.scopeWarning ? [result.scopeWarning] : []),
529
- ].join("\n");
530
- }
531
-
532
- export function formatContextAssessment(summary: ContextAssessment): string {
533
- return [
534
- `Context assessment: ${summary.completeness}`,
535
- `Papyrus injection: ${summary.injection.runs} runs · avg ${value(summary.injection.averageCharacters, " chars")} · p95 ${value(summary.injection.p95Characters, " chars")} · max ${value(summary.injection.maxCharacters, " chars")}`,
536
- `Injection mix: rules ${summary.injection.ruleCharacters.toLocaleString()} chars · tasks ${summary.injection.taskCharacters.toLocaleString()} chars · estimated ${summary.injection.estimatedTokens.toLocaleString()} tokens · unchanged ${summary.injection.unchangedRate === null ? "unknown" : `${(summary.injection.unchangedRate * 100).toFixed(1)}%`}`,
537
- `Compactions: ${summary.compaction.completed} completed · ${summary.compaction.aborted} aborted · avg ${value(summary.compaction.averageDurationMs, "ms")} · ${summary.compaction.perRun === null ? "unknown" : summary.compaction.perRun.toFixed(3)} per agent run · ${summary.compaction.perTurn === null ? "unknown" : summary.compaction.perTurn.toFixed(3)} per turn`,
538
- `Between compactions: ${value(summary.compaction.averageTurnsBetween, " turns")} · ${value(summary.compaction.averageProviderTokensBetween, " provider tokens")} · ${value(summary.compaction.averageCacheReadTokensBetween, " cache-read tokens")}`,
539
- `Reasons: threshold ${summary.compaction.reasons.threshold} · overflow ${summary.compaction.reasons.overflow} · manual ${summary.compaction.reasons.manual}`,
540
- ].join("\n");
541
- }
542
-
543
- function formatRoute(route: Route): string {
544
- return `${humanField(route.provider)}/${humanField(route.model)} · ${humanField(route.thinking)}`;
545
- }
546
-
547
- export function formatMetricsQuery(rows: StoredMetricObservation[]): string {
548
- if (rows.length === 0) return "Metrics: no observations matched";
549
- const shown = rows.slice(0, CLI_METRICS_HUMAN_MAX_ROWS);
550
- const lines = [
551
- `Metrics: ${rows.length.toLocaleString()} observation(s)${rows.length > shown.length ? ` (showing first ${shown.length})` : ""}`,
552
- ...shown.map((row) => `- ${humanField(row.source)}/${humanField(row.scope)}/${humanField(row.metric)} = ${row.value === null ? "null" : row.value} ${row.unit} @ ${new Date(row.observedAt).toISOString()}`),
553
- ];
554
- return lines.join("\n");
555
- }
556
-
557
- export function formatMetricsDistinctScopes(scopes: string[]): string {
558
- if (scopes.length === 0) return "Scopes: none matched";
559
- return [`Scopes: ${scopes.length.toLocaleString()}`, ...scopes.map((scope) => `- ${humanField(scope)}`)].join("\n");
560
- }
561
-
562
- export function formatMetricsUsageSeries(result: { rows: Array<{ scope: string; metric: string; bucketIndex: number; sum: number }>; truncated: boolean }): string {
563
- if (result.rows.length === 0) return `Usage series: no data${result.truncated ? " (scope limit reached)" : ""}`;
564
- const lines = [`Usage series: ${result.rows.length.toLocaleString()} bucket(s)${result.truncated ? " (scope limit reached)" : ""}`];
565
- for (const row of result.rows) lines.push(`- ${humanField(row.scope)}/${humanField(row.metric)} bucket ${row.bucketIndex}: ${row.sum.toLocaleString()}`);
566
- return lines.join("\n");
567
- }
568
-
569
- function formatUsdAmount(amount: number): string {
570
- return `$${amount.toFixed(Math.abs(amount) < 0.01 && amount !== 0 ? 4 : 2)}`;
571
- }
572
-
573
- export function formatCostByTask(summary: TaskCostSummary): string {
574
- const lines = [
575
- `Cost by task: ${summary.entries.length.toLocaleString()} task(s)${summary.truncated ? " (query limit reached; totals are a lower bound)" : ""}`,
576
- ...summary.entries.flatMap((entry) => [
577
- `- ${humanField(entry.taskId)}: ${formatUsdAmount(entry.costUsd)} · ↑${entry.inputTokens.toLocaleString()} ↓${entry.outputTokens.toLocaleString()} R${entry.cacheReadTokens.toLocaleString()} W${entry.cacheWriteTokens.toLocaleString()}`,
578
- ...entry.byModel.map((model) => ` · ${humanField(model.provider)}/${humanField(model.model)} (${humanField(model.thinking)}): ${formatUsdAmount(model.costUsd)} · ↑${model.inputTokens.toLocaleString()} ↓${model.outputTokens.toLocaleString()} R${model.cacheReadTokens.toLocaleString()} W${model.cacheWriteTokens.toLocaleString()}`),
579
- ]),
580
- `Unattributed spend (no task was focused): ${formatUsdAmount(summary.unattributedCostUsd)}`,
581
- ];
582
- return lines.join("\n");
583
- }
584
-
585
- export function formatTelemetryPoll(result: TelemetryPollResult): string {
586
- if (result.sources.length === 0) return "Telemetry: no sources configured";
587
- return ["Telemetry:", ...result.sources.map((source) => {
588
- const freshness = !source.ok ? `failed${source.error ? ` (${humanField(source.error)})` : ""}` : "ok";
589
- return `- ${humanField(source.id)} (${humanField(source.provider)}): ${freshness} · ${source.metrics} metric(s)`;
590
- })].join("\n");
591
- }
592
-
593
- export function formatRouterStatus(status: RouterStatus): string {
594
- const lines = [
595
- `Router: ${status.ready ? "ready" : "not ready"}${status.paused ? " · paused" : ""}`,
596
- `Current route: ${status.currentRoute ? formatRoute(status.currentRoute) : "none"}`,
597
- `Available routes: ${status.availableRoutes.length.toLocaleString()}`,
598
- `Override: ${status.override ? `${formatRoute(status.override.route)}${status.override.expiresAt === null ? "" : ` (expires ${new Date(status.override.expiresAt).toISOString()})`}` : "none"}`,
599
- ];
600
- if (status.lastDecision) lines.push(`Last decision: ${status.lastDecision.action} · pressure ${Number.isFinite(status.lastDecision.pressure) ? status.lastDecision.pressure.toFixed(3) : "∞"} · ${humanField(status.lastDecision.reason)}`);
601
- lines.push(formatTelemetryPoll({ sources: status.sources, observedAt: Date.now() }));
602
- return lines.join("\n");
603
- }
604
-
605
- export function formatCompactionEstimate(estimate: CompactionDurationEstimate): string {
606
- if (estimate.confidence === "cold-start" || estimate.ms === null) {
607
- return `Compaction duration: cold-start (${estimate.sampleSize.toLocaleString()} sample(s), not enough evidence yet)`;
608
- }
609
- return `Compaction duration: ~${estimate.ms.toLocaleString()}ms learned from ${estimate.sampleSize.toLocaleString()} sample(s)`;
610
- }
611
-
612
- export function formatPolicyDecision(decision: PolicyDecision): string {
613
- const lines = [`Decision: ${decision.action} · pressure ${Number.isFinite(decision.pressure) ? decision.pressure.toFixed(3) : "∞"} · ${humanField(decision.reason)}`];
614
- if (decision.route) lines.push(`Route: ${formatRoute(decision.route)}`);
615
- if (decision.delayMs !== undefined) lines.push(`Delay: ${decision.delayMs}ms`);
616
- return lines.join("\n");
617
- }
618
-
619
- async function callAndPrint<Name extends OperationName>(
620
- deps: CliDependencies,
621
- operation: Name,
622
- input: OperationInputs[Name],
623
- json: boolean,
624
- formatHuman: (result: OperationOutputs[Name]) => string,
625
- ): Promise<number> {
626
- try {
627
- const result = await deps.client.call(operation, input);
628
- deps.stdout(json ? JSON.stringify(result) : formatHuman(result));
629
- return 0;
630
- } catch (error) {
631
- deps.stderr(error instanceof Error ? error.message : String(error));
632
- return 1;
633
- }
634
- }
635
-
44
+ /**
45
+ * Composes one command dispatcher per capability module (metrics, router+telemetry+compaction,
46
+ * session identity, benchmarks, context, service lifecycle, the raw op escape hatch) instead of
47
+ * a single switch that used to combine every command's own argument parsing, validation, and
48
+ * presentation in one place. Each module owns its own usage lines and command handler; this
49
+ * function only routes the top-level command word and forwards `usage` for the shared "not
50
+ * recognized" fallback.
51
+ */
636
52
  export async function runCli(args: string[], deps: CliDependencies = DEFAULT_DEPENDENCIES): Promise<number> {
637
53
  const [command, action, ...rest] = args;
54
+ const fail = () => usage(deps.stderr);
638
55
  if (command === "serve") { deps.serve(); return 0; }
639
- if (command === "metrics") {
640
- if (action === "record") {
641
- const parsed = parseMetricsRecordArgs(rest);
642
- if (!parsed) return usage(deps.stderr);
643
- return callAndPrint(deps, "metrics.record", parsed.input, parsed.json, (row) => formatMetricsQuery([row]));
644
- }
645
- if (action === "query") {
646
- const parsed = parseMetricsQueryArgs(rest);
647
- if (!parsed) return usage(deps.stderr);
648
- return callAndPrint(deps, "metrics.query", parsed.input, parsed.json, formatMetricsQuery);
649
- }
650
- if (action === "prune") {
651
- const parsed = parseMetricsPruneArgs(rest);
652
- if (!parsed) return usage(deps.stderr);
653
- return callAndPrint(deps, "metrics.prune", parsed.input, parsed.json, (result) => `Pruned ${result.deleted.toLocaleString()} observation(s)`);
654
- }
655
- if (action === "distinct-scopes") {
656
- const parsed = parseMetricsDistinctScopesArgs(rest);
657
- if (!parsed) return usage(deps.stderr);
658
- return callAndPrint(deps, "metrics.distinct_scopes", parsed.input, parsed.json, formatMetricsDistinctScopes);
659
- }
660
- if (action === "usage-series") {
661
- const parsed = parseMetricsUsageSeriesArgs(rest);
662
- if (!parsed) return usage(deps.stderr);
663
- return callAndPrint(deps, "metrics.usage_series", parsed.input, parsed.json, formatMetricsUsageSeries);
664
- }
665
- if (action === "cost-by-task") {
666
- const parsed = parseCostByTaskArgs(rest);
667
- if (!parsed) return usage(deps.stderr);
668
- return callAndPrint(deps, "metrics.cost_by_task", parsed.input, parsed.json, formatCostByTask);
669
- }
670
- return usage(deps.stderr);
671
- }
672
- if (command === "telemetry") {
673
- if (action !== "poll") return usage(deps.stderr);
674
- const parsed = parseJsonOnlyArgs(rest);
675
- if (!parsed) return usage(deps.stderr);
676
- return callAndPrint(deps, "telemetry.poll", {}, parsed.json, formatTelemetryPoll);
677
- }
678
- if (command === "compaction") {
679
- if (action !== "estimate") return usage(deps.stderr);
680
- const parsed = parseJsonOnlyArgs(rest);
681
- if (!parsed) return usage(deps.stderr);
682
- return callAndPrint(deps, "compaction.estimate", {}, parsed.json, formatCompactionEstimate);
683
- }
684
- if (command === "router") {
685
- switch (action) {
686
- case "status": {
687
- const parsed = parseJsonOnlyArgs(rest);
688
- if (!parsed) return usage(deps.stderr);
689
- return callAndPrint(deps, "router.status", {}, parsed.json, formatRouterStatus);
690
- }
691
- case "decide": {
692
- const parsed = parseJsonOnlyArgs(rest);
693
- if (!parsed) return usage(deps.stderr);
694
- return callAndPrint(deps, "router.decide", {}, parsed.json, formatPolicyDecision);
695
- }
696
- case "pause": {
697
- const parsed = parseJsonOnlyArgs(rest);
698
- if (!parsed) return usage(deps.stderr);
699
- return callAndPrint(deps, "router.pause", {}, parsed.json, formatRouterStatus);
700
- }
701
- case "resume": {
702
- const parsed = parseJsonOnlyArgs(rest);
703
- if (!parsed) return usage(deps.stderr);
704
- return callAndPrint(deps, "router.resume", {}, parsed.json, formatRouterStatus);
705
- }
706
- case "clear-override": {
707
- const parsed = parseJsonOnlyArgs(rest);
708
- if (!parsed) return usage(deps.stderr);
709
- return callAndPrint(deps, "router.clear_override", {}, parsed.json, formatRouterStatus);
710
- }
711
- case "override": {
712
- const parsed = parseRouterOverrideArgs(rest);
713
- if (!parsed) return usage(deps.stderr);
714
- return callAndPrint(deps, "router.override", parsed.input, parsed.json, formatRouterStatus);
715
- }
716
- case "current-route": {
717
- const parsed = parseRouterRouteArgs(rest);
718
- if (!parsed) return usage(deps.stderr);
719
- return callAndPrint(deps, "router.current_route", parsed.input, parsed.json, formatRouterStatus);
720
- }
721
- case "available-routes": {
722
- const parsed = parseRouterAvailableRoutesArgs(rest);
723
- if (!parsed) return usage(deps.stderr);
724
- return callAndPrint(deps, "router.available_routes", parsed.input, parsed.json, formatRouterStatus);
725
- }
726
- default: return usage(deps.stderr);
727
- }
728
- }
729
- if (command === "op") {
730
- const parsed = parseOpArgs(action === undefined ? [] : [action, ...rest]);
731
- if (!parsed) return usage(deps.stderr);
732
- try {
733
- // The escape hatch dispatches a dynamically named operation; OperationInputs/OperationOutputs
734
- // are only known statically per literal operation name, so this one call site is intentionally
735
- // untyped at the boundary. parseOpArgs already restricts `operation` to EXPECTED_OPERATION_NAMES.
736
- const call = deps.client.call as (operation: OperationName, input: Record<string, unknown>) => Promise<unknown>;
737
- const result = await call(parsed.operation, parsed.input);
738
- deps.stdout(JSON.stringify(result));
739
- return 0;
740
- } catch (error) {
741
- deps.stderr(error instanceof Error ? error.message : String(error));
742
- return 1;
743
- }
744
- }
745
- if (command === "benchmarks") {
746
- const parsed = parseBenchmarkArgs(action, rest);
747
- if (!parsed) return usage(deps.stderr);
748
- try {
749
- if (parsed.action === "list") {
750
- const result = await deps.client.call("benchmark.query", parsed.query!);
751
- deps.stdout(parsed.json ? JSON.stringify(result) : formatBenchmarkQuery(result));
752
- } else if (parsed.action === "rank") {
753
- const result = await deps.client.call("models.rank", parsed.recommendation!);
754
- deps.stdout(parsed.json ? JSON.stringify(result) : formatModelRanking(result));
755
- } else {
756
- const result = parsed.action === "refresh"
757
- ? await deps.client.call("benchmark.refresh", { force: parsed.force })
758
- : await deps.client.call("benchmark.status", {});
759
- deps.stdout(parsed.json ? JSON.stringify(result) : formatBenchmarkStatus(result));
760
- }
761
- return 0;
762
- } catch (error) {
763
- deps.stderr(error instanceof Error ? error.message : String(error));
764
- return 1;
765
- }
766
- }
767
- if (command === "context") {
768
- const parsed = parseContextArgs([...(action === undefined ? [] : [action]), ...rest]);
769
- if (!parsed) return usage(deps.stderr);
770
- try {
771
- const summary = await deps.client.call("context.assess", parsed.input);
772
- deps.stdout(parsed.json ? JSON.stringify(summary) : formatContextAssessment(summary));
773
- return 0;
774
- } catch (error) {
775
- deps.stderr(error instanceof Error ? error.message : String(error));
776
- return 1;
777
- }
778
- }
779
- if (command !== "service") return usage(deps.stderr);
780
- switch (action) {
781
- case "install": deps.installService(); return 0;
782
- case "start": deps.systemctl("start", SYSTEMD_UNIT_NAME); return 0;
783
- case "stop": deps.systemctl("stop", SYSTEMD_UNIT_NAME); return 0;
784
- case "restart": deps.systemctl("restart", SYSTEMD_UNIT_NAME); return 0;
785
- case "status": deps.systemctl("status", SYSTEMD_UNIT_NAME); return 0;
786
- case "checkpoint": {
787
- const parsed = parseJsonOnlyArgs(rest);
788
- if (!parsed) return usage(deps.stderr);
789
- return callAndPrint(deps, "service.checkpoint", {}, parsed.json, () => "Checkpoint complete");
790
- }
791
- default: return usage(deps.stderr);
792
- }
56
+ if (command === "session") return runSessionCommand(action, rest, deps, fail);
57
+ if (command === "metrics") return runMetricsCommand(action, rest, deps, fail);
58
+ if (command === "telemetry") return runTelemetryCommand(action, rest, deps, fail);
59
+ if (command === "compaction") return runCompactionCommand(action, rest, deps, fail);
60
+ if (command === "router") return runRouterCommand(action, rest, deps, fail);
61
+ if (command === "op") return runOpCommand(action, rest, deps, fail);
62
+ if (command === "benchmarks") return runBenchmarksCommand(action, rest, deps, fail);
63
+ if (command === "context") return runContextCommand(action, rest, deps, fail);
64
+ if (command === "service") return runServiceCommand(action, rest, deps, fail);
65
+ return fail();
793
66
  }
794
67
 
795
68
  export async function main(args: string[] = process.argv.slice(2)): Promise<void> {