omnigateway 0.1.6 → 0.1.8

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 (37) hide show
  1. package/README.md +24 -5
  2. package/bin/omni.js +805 -352
  3. package/gateway.js +622 -236
  4. package/package.json +1 -1
  5. package/public/assets/{Chip-Ccqsb_Mq.js → Chip-CM9ZMdr9.js} +2 -2
  6. package/public/assets/{CopyValue-cAwgC1sU.js → CopyValue-WrOTcHwq.js} +4 -4
  7. package/public/assets/{Field-Devm4K46.js → Field-Br6jKPz8.js} +8 -8
  8. package/public/assets/{Lamp-BXSUIAtG.js → Lamp-DO-sRDg2.js} +7 -7
  9. package/public/assets/{Meter-Fn8X7t60.js → Meter-BzMaum0C.js} +2 -2
  10. package/public/assets/{Modal-Dqir9wBz.js → Modal-lhjr949H.js} +8 -8
  11. package/public/assets/Rack-BFWC50ex.js +147 -0
  12. package/public/assets/{Readout-BKpOZIGX.js → Readout-DMJs4gYD.js} +5 -5
  13. package/public/assets/{States-DBzsiHjQ.js → States-BNeCLhZn.js} +10 -10
  14. package/public/assets/{Table-DPDJeeVc.js → Table-nOg_lgEJ.js} +1 -1
  15. package/public/assets/{Toggle-BhViDMm9.js → Toggle-DYoCVD37.js} +3 -3
  16. package/public/assets/_app-lXr06Xli.js +1 -0
  17. package/public/assets/_app.accounts-BTb5vHI_.js +51 -0
  18. package/public/assets/_app.console-DHUClMsI.js +25 -0
  19. package/public/assets/_app.index-DZ--9mKU.js +61 -0
  20. package/public/assets/_app.keys-B-cy-2DS.js +39 -0
  21. package/public/assets/_app.logs-BQjAIT2P.js +33 -0
  22. package/public/assets/{_app.models-CuD-JtGS.js → _app.models-BAmXHlf2.js} +27 -27
  23. package/public/assets/_app.settings-CG8bv_rm.js +34 -0
  24. package/public/assets/{_app.usage-DAyYUfIi.js → _app.usage-BLrQ2hB8.js} +28 -28
  25. package/public/assets/index-UAZ0O5y0.js +170 -0
  26. package/public/assets/{login-DXHM-Noj.js → login-BpUl6C5b.js} +8 -8
  27. package/public/assets/{queries-BMwc6EvM.js → queries-D_-Jj9vt.js} +12 -12
  28. package/public/assets/{trash-2-JF_uX2gE.js → trash-2-DjWP2u19.js} +1 -1
  29. package/public/index.html +2 -2
  30. package/public/assets/Rack-XtfiZ6GC.js +0 -147
  31. package/public/assets/_app-B8lqq_ks.js +0 -1
  32. package/public/assets/_app.accounts-DSMHSYBF.js +0 -51
  33. package/public/assets/_app.index-DXH5sH41.js +0 -61
  34. package/public/assets/_app.keys-DdEyVy_b.js +0 -39
  35. package/public/assets/_app.logs-Camxditb.js +0 -33
  36. package/public/assets/_app.settings-DA1aYcR4.js +0 -16
  37. package/public/assets/index-BtjyVE1i.js +0 -170
package/bin/omni.js CHANGED
@@ -90,6 +90,11 @@ function formatUsd(value) {
90
90
  return `$${value.toFixed(value < 1 ? 4 : 2)}`;
91
91
  }
92
92
 
93
+ // apps/cli/src/run.ts
94
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
95
+ import { homedir as homedir3 } from "os";
96
+ import { dirname as dirname3 } from "path";
97
+
93
98
  // packages/control/src/adminAuth.ts
94
99
  import { hash, verify } from "@node-rs/argon2";
95
100
  var MIN_PASSWORD_LENGTH = 12;
@@ -188,6 +193,23 @@ class GatewayError extends Error {
188
193
  }
189
194
  // packages/ir/src/logger.ts
190
195
  var LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
196
+ var ANSI2 = /\u001B\[[0-9;]*m/g;
197
+ var HEAD = /^(\S+) (DEBUG|INFO|WARN|ERROR) {1,2}(.*)$/;
198
+ var FIELDS = / {2}(?=[a-zA-Z]+=)/;
199
+ function parseLine(raw) {
200
+ const absent = { raw, at: null, level: null, msg: null };
201
+ const match = HEAD.exec(raw.replace(ANSI2, ""));
202
+ if (match === null)
203
+ return absent;
204
+ const [, instant, label, tail] = match;
205
+ if (instant === undefined || label === undefined || tail === undefined)
206
+ return absent;
207
+ const at = Date.parse(instant);
208
+ if (Number.isNaN(at))
209
+ return absent;
210
+ const msg = tail.split(FIELDS, 1)[0] ?? tail;
211
+ return { raw, at, level: label.toLowerCase(), msg };
212
+ }
191
213
  var noopLogger = {
192
214
  debug: () => {},
193
215
  info: () => {},
@@ -216,6 +238,7 @@ function usageFromPromptTotal(promptTokens, outputTokens, cacheReadTokens, cache
216
238
  }
217
239
  // packages/control/src/config.ts
218
240
  var MIN_KEY_LENGTH = 16;
241
+ var TRUTHY = new Set(["1", "true", "yes", "on"]);
219
242
  var DECIMAL_INTEGER = /^\d+$/;
220
243
  function optionalText(value, fallback) {
221
244
  return value?.trim() || fallback;
@@ -234,6 +257,8 @@ function loadConfig(env) {
234
257
  const derivedBaseUrl = `http://${host}:${port}`;
235
258
  const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
236
259
  const staticDir = env.OMNI_STATIC_DIR?.trim();
260
+ const logFile = env.OMNI_LOG_FILE?.trim();
261
+ const exposeClaudeCodeAliases = TRUTHY.has((env.OMNI_EXPOSE_CLAUDE_CODE_ALIASES ?? "").trim().toLowerCase());
237
262
  const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
238
263
  const logLevel = parseLogLevel(rawLogLevel);
239
264
  return {
@@ -244,9 +269,15 @@ function loadConfig(env) {
244
269
  databasePath: optionalText(env.OMNI_DB_PATH, "./omnigateway.db"),
245
270
  encryptionKey,
246
271
  baseUrl,
247
- staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir
272
+ staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir,
273
+ exposeClaudeCodeAliases,
274
+ logFile: logFile === undefined || logFile.length === 0 ? null : logFile
248
275
  };
249
276
  }
277
+ // packages/providers/src/betas.ts
278
+ var CONTEXT_1M_BETA = "context-1m-2025-08-07";
279
+ var CONTEXT_1M_TOKENS = 1e6;
280
+
250
281
  // packages/providers/src/body.ts
251
282
  var BODY_ORDER = {
252
283
  anthropic: [
@@ -354,6 +385,119 @@ function signAnthropicBody(json) {
354
385
  return json.replace(needle, `cch=${computeCch(json)};`);
355
386
  }
356
387
 
388
+ // packages/providers/src/anthropic/models.ts
389
+ var ANTHROPIC_MODELS = {
390
+ defaultModel: "claude-opus-5",
391
+ models: [
392
+ {
393
+ id: "claude-fable-5",
394
+ label: "Claude Fable 5",
395
+ pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
396
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
397
+ },
398
+ {
399
+ id: "claude-opus-5",
400
+ label: "Claude Opus 5",
401
+ pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
402
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
403
+ },
404
+ {
405
+ id: "claude-sonnet-5",
406
+ label: "Claude Sonnet 5",
407
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
408
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
409
+ },
410
+ {
411
+ id: "claude-haiku-4-5",
412
+ label: "Claude Haiku 4.5",
413
+ pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
414
+ limits: { contextWindow: 200000, maxOutputTokens: 64000 }
415
+ }
416
+ ]
417
+ };
418
+
419
+ // packages/providers/src/kimi/models.ts
420
+ var KIMI_MODELS = {
421
+ defaultModel: "k3-256k",
422
+ models: [
423
+ {
424
+ id: "k3-256k",
425
+ label: "Kimi K3 \u2014 256K",
426
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
427
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
428
+ },
429
+ {
430
+ id: "k3",
431
+ label: "Kimi K3 \u2014 up to 1M",
432
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
433
+ limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
434
+ },
435
+ {
436
+ id: "kimi-for-coding",
437
+ label: "Kimi K2.7 Code",
438
+ pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
439
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
440
+ },
441
+ {
442
+ id: "kimi-for-coding-highspeed",
443
+ label: "Kimi K2.7 Code \u2014 High Speed",
444
+ pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
445
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
446
+ }
447
+ ]
448
+ };
449
+
450
+ // packages/providers/src/openai/models.ts
451
+ var OPENAI_MODELS = {
452
+ defaultModel: "gpt-5.6",
453
+ models: [
454
+ {
455
+ id: "gpt-5.6",
456
+ label: "GPT-5.6 \u2014 routes to Sol",
457
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
458
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
459
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
460
+ },
461
+ {
462
+ id: "gpt-5.6-sol",
463
+ label: "GPT-5.6 Sol \u2014 deepest reasoning",
464
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
465
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
466
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
467
+ },
468
+ {
469
+ id: "gpt-5.6-terra",
470
+ label: "GPT-5.6 Terra \u2014 balanced",
471
+ pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
472
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
473
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
474
+ },
475
+ {
476
+ id: "gpt-5.6-luna",
477
+ label: "GPT-5.6 Luna \u2014 fastest",
478
+ pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
479
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
480
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
481
+ }
482
+ ]
483
+ };
484
+
485
+ // packages/providers/src/catalog.ts
486
+ var PROVIDER_MODEL_CATALOG = {
487
+ anthropic: ANTHROPIC_MODELS,
488
+ openai: OPENAI_MODELS,
489
+ kimi: KIMI_MODELS
490
+ };
491
+ function catalogPricing(provider, model) {
492
+ return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
493
+ }
494
+ function catalogLimits(provider, model, auth = "apiKey") {
495
+ const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
496
+ if (entry === undefined)
497
+ return null;
498
+ return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
499
+ }
500
+
357
501
  // packages/providers/src/http.ts
358
502
  function codeForStatus(status) {
359
503
  if (status === 401 || status === 403)
@@ -669,7 +813,7 @@ async function* decodeAnthropic(messages) {
669
813
  if (cb.type === "text")
670
814
  yield { type: "blockStart", index, block: { type: "text" } };
671
815
  else if (cb.type === "thinking")
672
- yield { type: "blockStart", index, block: { type: "thinking" } };
816
+ yield { type: "blockStart", index, block: { type: "thinking", signed: true } };
673
817
  else if (cb.type === "tool_use")
674
818
  yield {
675
819
  type: "blockStart",
@@ -689,7 +833,7 @@ async function* decodeAnthropic(messages) {
689
833
  index,
690
834
  delta: { type: "thinking", text: delta.thinking ?? "" }
691
835
  };
692
- else if (delta.type === "signature_delta")
836
+ else if (delta.type === "signature_delta" && (delta.signature ?? "") !== "")
693
837
  yield {
694
838
  type: "blockDelta",
695
839
  index,
@@ -761,6 +905,9 @@ function wireCacheControl(c) {
761
905
  return {};
762
906
  return { cache_control: { type: c.type, ...c.ttl === undefined ? {} : { ttl: c.ttl } } };
763
907
  }
908
+ function isReplayableThinking(b) {
909
+ return b.type !== "thinking" || b.signature !== undefined && b.signature !== "";
910
+ }
764
911
  function encodeBlock(b) {
765
912
  const cache = wireCacheControl(cacheControlOf(b));
766
913
  switch (b.type) {
@@ -790,6 +937,16 @@ function encodeSystemTurn(content) {
790
937
  return content.flatMap((b) => b.type === "text" ? [b.text] : []).join(`
791
938
  `);
792
939
  }
940
+ function systemCacheControl(req) {
941
+ const cacheable = req.messages.flatMap((message) => message.content.flatMap((block) => block.type === "thinking" ? [] : [{ role: message.role, block }]));
942
+ const markedSystemBlocks = cacheable.filter(({ role, block }) => role === "system" && cacheControlOf(block) !== undefined);
943
+ const final = markedSystemBlocks.at(-1);
944
+ const promoted = final !== undefined && final.block.type === "text" && cacheable.at(-1) === final ? cacheControlOf(final.block) : undefined;
945
+ return {
946
+ ...promoted === undefined ? {} : { promoted },
947
+ lost: markedSystemBlocks.length > (promoted === undefined ? 0 : 1)
948
+ };
949
+ }
793
950
  function encodeToolChoice(c) {
794
951
  switch (c.type) {
795
952
  case "auto":
@@ -813,18 +970,30 @@ function toWire(req, model, opts) {
813
970
  system = [{ type: "text", text: OAUTH_IDENTITY }, ...system ?? []];
814
971
  note2("anthropic:oauth-system-prefix");
815
972
  }
973
+ const systemCache = systemCacheControl(req);
974
+ if (systemCache.lost)
975
+ note2("anthropic:system-turn-cache-control-dropped");
816
976
  const body = {
817
977
  model,
818
- messages: req.messages.map((m) => {
819
- if (m.role !== "system")
820
- return { role: m.role, content: m.content.map(encodeBlock) };
821
- if (m.content.some((b) => cacheControlOf(b) !== undefined)) {
822
- note2("anthropic:system-turn-cache-control-dropped");
823
- }
824
- return { role: m.role, content: encodeSystemTurn(m.content) };
978
+ messages: req.messages.flatMap((m) => {
979
+ if (m.role !== "system") {
980
+ const replayable = m.content.filter(isReplayableThinking);
981
+ if (replayable.length !== m.content.length)
982
+ note2("anthropic:unsigned-thinking-dropped");
983
+ if (replayable.length === 0)
984
+ return [];
985
+ return [{ role: m.role, content: replayable.map(encodeBlock) }];
986
+ }
987
+ return [{ role: m.role, content: encodeSystemTurn(m.content) }];
825
988
  }),
826
989
  max_tokens: req.maxTokens ?? 4096,
827
- stream: req.stream
990
+ stream: req.stream,
991
+ ...systemCache.promoted === undefined ? {} : {
992
+ cache_control: {
993
+ type: systemCache.promoted.type,
994
+ ...systemCache.promoted.ttl === undefined ? {} : { ttl: systemCache.promoted.ttl }
995
+ }
996
+ }
828
997
  };
829
998
  if (system !== undefined && system.length > 0)
830
999
  body.system = system;
@@ -886,6 +1055,15 @@ var anthropicAdapter = {
886
1055
  ["Accept", "text/event-stream"]
887
1056
  ];
888
1057
  const betas = new Set(req.request.betas ?? []);
1058
+ const notes = [...degradations];
1059
+ if (betas.has(CONTEXT_1M_BETA)) {
1060
+ const limits = catalogLimits("anthropic", req.model, oauth ? "oauth" : "apiKey");
1061
+ const window = limits?.contextWindow;
1062
+ if (window !== undefined && window < CONTEXT_1M_TOKENS) {
1063
+ betas.delete(CONTEXT_1M_BETA);
1064
+ notes.push("anthropic:context-1m-dropped");
1065
+ }
1066
+ }
889
1067
  if (oauth) {
890
1068
  protocol.push(["Authorization", `Bearer ${req.credentials.accessToken}`]);
891
1069
  betas.add(OAUTH_BETA);
@@ -913,121 +1091,9 @@ var anthropicAdapter = {
913
1091
  throw await httpError(res, "anthropic");
914
1092
  if (res.body === null)
915
1093
  throw new GatewayError("UPSTREAM", "empty response body", { provider: "anthropic" });
916
- return { events: decodeAnthropic(parseSse(res.body)), degradations };
1094
+ return { events: decodeAnthropic(parseSse(res.body)), degradations: notes };
917
1095
  }
918
1096
  };
919
- // packages/providers/src/anthropic/models.ts
920
- var ANTHROPIC_MODELS = {
921
- defaultModel: "claude-opus-5",
922
- models: [
923
- {
924
- id: "claude-fable-5",
925
- label: "Claude Fable 5",
926
- pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
927
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
928
- },
929
- {
930
- id: "claude-opus-5",
931
- label: "Claude Opus 5",
932
- pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
933
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
934
- },
935
- {
936
- id: "claude-sonnet-5",
937
- label: "Claude Sonnet 5",
938
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
939
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
940
- },
941
- {
942
- id: "claude-haiku-4-5",
943
- label: "Claude Haiku 4.5",
944
- pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
945
- limits: { contextWindow: 200000, maxOutputTokens: 64000 }
946
- }
947
- ]
948
- };
949
-
950
- // packages/providers/src/kimi/models.ts
951
- var KIMI_MODELS = {
952
- defaultModel: "k3-256k",
953
- models: [
954
- {
955
- id: "k3-256k",
956
- label: "Kimi K3 \u2014 256K",
957
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
958
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
959
- },
960
- {
961
- id: "k3",
962
- label: "Kimi K3 \u2014 up to 1M",
963
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
964
- limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
965
- },
966
- {
967
- id: "kimi-for-coding",
968
- label: "Kimi K2.7 Code",
969
- pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
970
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
971
- },
972
- {
973
- id: "kimi-for-coding-highspeed",
974
- label: "Kimi K2.7 Code \u2014 High Speed",
975
- pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
976
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
977
- }
978
- ]
979
- };
980
-
981
- // packages/providers/src/openai/models.ts
982
- var OPENAI_MODELS = {
983
- defaultModel: "gpt-5.6",
984
- models: [
985
- {
986
- id: "gpt-5.6",
987
- label: "GPT-5.6 \u2014 routes to Sol",
988
- pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
989
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
990
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
991
- },
992
- {
993
- id: "gpt-5.6-sol",
994
- label: "GPT-5.6 Sol \u2014 deepest reasoning",
995
- pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
996
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
997
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
998
- },
999
- {
1000
- id: "gpt-5.6-terra",
1001
- label: "GPT-5.6 Terra \u2014 balanced",
1002
- pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
1003
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1004
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1005
- },
1006
- {
1007
- id: "gpt-5.6-luna",
1008
- label: "GPT-5.6 Luna \u2014 fastest",
1009
- pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
1010
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1011
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1012
- }
1013
- ]
1014
- };
1015
-
1016
- // packages/providers/src/catalog.ts
1017
- var PROVIDER_MODEL_CATALOG = {
1018
- anthropic: ANTHROPIC_MODELS,
1019
- openai: OPENAI_MODELS,
1020
- kimi: KIMI_MODELS
1021
- };
1022
- function catalogPricing(provider, model) {
1023
- return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
1024
- }
1025
- function catalogLimits(provider, model, auth = "apiKey") {
1026
- const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
1027
- if (entry === undefined)
1028
- return null;
1029
- return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
1030
- }
1031
1097
  // packages/providers/src/http-client.ts
1032
1098
  import { request as httpRequest } from "http";
1033
1099
  import { request as httpsRequest } from "https";
@@ -1253,6 +1319,8 @@ function toChatWire(req, model) {
1253
1319
  if (!degradations.includes(d))
1254
1320
  degradations.push(d);
1255
1321
  };
1322
+ if (req.betas?.includes(CONTEXT_1M_BETA))
1323
+ note2("kimi:context-1m-dropped");
1256
1324
  const messages = [];
1257
1325
  const system = req.system?.flatMap((b) => b.type === "text" ? [b.text] : []).join(`
1258
1326
 
@@ -1529,6 +1597,8 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
1529
1597
  if (!degradations.includes(d))
1530
1598
  degradations.push(d);
1531
1599
  };
1600
+ if (req.betas?.includes(CONTEXT_1M_BETA))
1601
+ note2("openai:context-1m-dropped");
1532
1602
  for (const message of req.messages) {
1533
1603
  const parts = [];
1534
1604
  const inlined = message.role === "system";
@@ -2181,6 +2251,60 @@ function createConnectFlows(deps) {
2181
2251
  }
2182
2252
  };
2183
2253
  }
2254
+ // packages/control/src/console.ts
2255
+ var UNIT_NAME = "omnigateway.service";
2256
+ var MAX_CONSOLE_LINES = 500;
2257
+ var DEFAULT_CONSOLE_LINES = 200;
2258
+ function resolveConsoleSource(input) {
2259
+ const path = input.logFile?.trim();
2260
+ if (path !== undefined && path.length > 0)
2261
+ return { kind: "file", path };
2262
+ if (input.unitInstalled)
2263
+ return { kind: "journal", unit: UNIT_NAME, scope: input.scope };
2264
+ return { kind: "none" };
2265
+ }
2266
+ function consoleLimit(requested) {
2267
+ const value = requested === undefined ? DEFAULT_CONSOLE_LINES : Number(requested);
2268
+ if (!Number.isFinite(value))
2269
+ return DEFAULT_CONSOLE_LINES;
2270
+ return Math.floor(Math.min(Math.max(1, value), MAX_CONSOLE_LINES));
2271
+ }
2272
+ function keep(line, query) {
2273
+ if (query.since !== undefined && (line.at === null || line.at <= query.since))
2274
+ return false;
2275
+ if (query.level === undefined || line.level === null)
2276
+ return true;
2277
+ return LEVELS[line.level] >= LEVELS[query.level];
2278
+ }
2279
+ var SCAN_FACTOR = 20;
2280
+ var SCAN_LIMIT = 5000;
2281
+ function scanWidth(query) {
2282
+ const filtered = query.level !== undefined || query.since !== undefined;
2283
+ return filtered ? Math.min(query.lines * SCAN_FACTOR, SCAN_LIMIT) : query.lines;
2284
+ }
2285
+ async function readSource(deps, source, lines) {
2286
+ if (source.kind === "none")
2287
+ return "";
2288
+ if (source.kind === "file")
2289
+ return deps.readFile(source.path, lines) ?? "";
2290
+ const unit = source.scope === "system" ? ["-u", source.unit] : [`--user-unit=${source.unit}`];
2291
+ const result = await deps.run([
2292
+ "journalctl",
2293
+ ...unit,
2294
+ "-n",
2295
+ String(lines),
2296
+ "--no-pager",
2297
+ "--output=cat"
2298
+ ]);
2299
+ return result.code === 0 ? result.stdout : "";
2300
+ }
2301
+ async function readConsole(deps, source, query) {
2302
+ const limited = { ...query, lines: consoleLimit(query.lines) };
2303
+ const text = await readSource(deps, source, scanWidth(limited));
2304
+ const lines = text.split(`
2305
+ `).filter((raw) => raw.trim().length > 0).map(parseLine).filter((line) => keep(line, limited)).slice(-limited.lines);
2306
+ return source.kind === "file" ? { source: "file", path: source.path, lines } : { source: source.kind, lines };
2307
+ }
2184
2308
  // node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
2185
2309
  var exports_external = {};
2186
2310
  __export(exports_external, {
@@ -16473,7 +16597,9 @@ var dryRunSchema = exports_external.object({
16473
16597
  reasoning: exports_external.boolean().default(false)
16474
16598
  }).strict();
16475
16599
  var modelSchema = exports_external.object({
16476
- id: exports_external.string().min(1),
16600
+ id: exports_external.string().min(1).refine((value) => !value.toLowerCase().startsWith("claude/"), {
16601
+ message: 'model id must not start with "claude/": that prefix is reserved for discovery mirrors'
16602
+ }),
16477
16603
  strategy: exports_external.enum(["score", "priority", "roundRobin", "weighted"]),
16478
16604
  isAlias: exports_external.boolean(),
16479
16605
  targets: exports_external.array(exports_external.object({
@@ -17885,11 +18011,65 @@ async function createKey(store, input) {
17885
18011
  async function revokeKey(store, id) {
17886
18012
  await store.keys.revoke(id);
17887
18013
  }
17888
- // packages/control/src/models.ts
17889
- async function listModels(store) {
17890
- return store.config.listModels();
18014
+ // packages/control/src/modelLimits.ts
18015
+ function narrower(a, b) {
18016
+ const context = [a.contextWindow, b.contextWindow].filter((n) => n !== undefined);
18017
+ const output = [a.maxOutputTokens, b.maxOutputTokens].filter((n) => n !== undefined);
18018
+ return {
18019
+ ...context.length === 0 ? {} : { contextWindow: Math.min(...context) },
18020
+ ...output.length === 0 ? {} : { maxOutputTokens: Math.min(...output) }
18021
+ };
17891
18022
  }
17892
- async function getModel(store, id) {
18023
+ function targetLimits(target, auths) {
18024
+ const ways = auths.size === 0 ? ["apiKey"] : [...auths];
18025
+ let listed = {};
18026
+ for (const auth of ways) {
18027
+ const entry = catalogLimits(target.provider, target.model, auth);
18028
+ if (entry === null)
18029
+ continue;
18030
+ listed = narrower(listed, {
18031
+ contextWindow: entry.contextWindow,
18032
+ maxOutputTokens: entry.maxOutputTokens
18033
+ });
18034
+ }
18035
+ const contextWindow = target.contextWindow ?? listed.contextWindow;
18036
+ const maxOutputTokens = target.maxOutputTokens ?? listed.maxOutputTokens;
18037
+ return {
18038
+ ...contextWindow === undefined ? {} : { contextWindow },
18039
+ ...maxOutputTokens === undefined ? {} : { maxOutputTokens }
18040
+ };
18041
+ }
18042
+ function servingAuths(credentials) {
18043
+ const byProvider = new Map;
18044
+ for (const credential of credentials) {
18045
+ if (!credential.enabled)
18046
+ continue;
18047
+ const ways = byProvider.get(credential.provider) ?? new Set;
18048
+ ways.add(credential.authType);
18049
+ byProvider.set(credential.provider, ways);
18050
+ }
18051
+ return byProvider;
18052
+ }
18053
+ function resolveModelLimits(model, credentials) {
18054
+ const auths = servingAuths(credentials);
18055
+ let limits = {};
18056
+ for (const target of model.targets) {
18057
+ limits = narrower(limits, targetLimits(target, auths.get(target.provider) ?? new Set));
18058
+ }
18059
+ return limits;
18060
+ }
18061
+ function modelDisplayName(model) {
18062
+ const only = model.targets.length === 1 ? model.targets[0] : undefined;
18063
+ if (only === undefined)
18064
+ return model.id;
18065
+ const labelled = PROVIDER_MODEL_CATALOG[only.provider]?.models.find((choice) => choice.id === only.model);
18066
+ return labelled?.label ?? model.id;
18067
+ }
18068
+ // packages/control/src/models.ts
18069
+ async function listModels(store) {
18070
+ return store.config.listModels();
18071
+ }
18072
+ async function getModel(store, id) {
17893
18073
  const model = (await store.config.listModels()).find((m) => m.id === id);
17894
18074
  if (model === undefined)
17895
18075
  throw new GatewayError("MODEL_UNAVAILABLE", `no virtual model "${id}"`);
@@ -18241,6 +18421,115 @@ async function putSettings(store, input) {
18241
18421
  await store.config.putSettings(settings);
18242
18422
  return settings;
18243
18423
  }
18424
+ // packages/control/src/setup.ts
18425
+ var KEY_PLACEHOLDER = "<your OmniGateway key>";
18426
+ async function describeModelsForSetup(store) {
18427
+ const models = await listModels(store);
18428
+ const credentials = (await listCredentials(store)).map((credential) => ({
18429
+ provider: credential.provider,
18430
+ authType: credential.authType,
18431
+ enabled: credential.enabled
18432
+ }));
18433
+ return models.map((model) => ({
18434
+ model,
18435
+ limits: resolveModelLimits(model, credentials),
18436
+ label: modelDisplayName(model)
18437
+ }));
18438
+ }
18439
+ function slug(id) {
18440
+ return encodeURIComponent(id).replace(/\./g, "%2E");
18441
+ }
18442
+ function claudeProfiles(described, input) {
18443
+ const apiKey = input.apiKey ?? KEY_PLACEHOLDER;
18444
+ return described.map(({ model, limits }) => {
18445
+ const useMirror = input.discoveryMirrors === true && !/^(?:claude|anthropic)/i.test(model.id);
18446
+ const modelId = useMirror ? `claude/${model.id}` : model.id;
18447
+ const env2 = {
18448
+ ANTHROPIC_BASE_URL: input.baseUrl,
18449
+ ANTHROPIC_AUTH_TOKEN: apiKey,
18450
+ ANTHROPIC_MODEL: modelId,
18451
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1"
18452
+ };
18453
+ if (limits.contextWindow !== undefined && !/^claude-/i.test(modelId)) {
18454
+ env2.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(limits.contextWindow);
18455
+ }
18456
+ return {
18457
+ path: `${slug(model.id)}/settings.json`,
18458
+ contents: `${JSON.stringify({ env: env2 }, null, 2)}
18459
+ `
18460
+ };
18461
+ });
18462
+ }
18463
+ function opencodeConfig(described, input) {
18464
+ const models = {};
18465
+ for (const { model, limits, label: label2 } of described) {
18466
+ const limit = limits.contextWindow === undefined ? undefined : {
18467
+ context: limits.contextWindow,
18468
+ ...limits.maxOutputTokens === undefined ? {} : { output: limits.maxOutputTokens }
18469
+ };
18470
+ models[model.id] = { name: label2, ...limit === undefined ? {} : { limit } };
18471
+ }
18472
+ const contents = `${JSON.stringify({
18473
+ $schema: "https://opencode.ai/config.json",
18474
+ provider: {
18475
+ omnigateway: {
18476
+ npm: "@ai-sdk/openai-compatible",
18477
+ name: "OmniGateway",
18478
+ options: {
18479
+ baseURL: `${input.baseUrl.replace(/\/+$/, "").replace(/(?:\/v1)+$/i, "")}/v1`,
18480
+ apiKey: input.apiKey ?? KEY_PLACEHOLDER
18481
+ },
18482
+ models
18483
+ }
18484
+ }
18485
+ }, null, 2)}
18486
+ `;
18487
+ return { path: "opencode.json", contents };
18488
+ }
18489
+ // packages/control/src/tail.ts
18490
+ import { closeSync, fstatSync, openSync, readSync, statSync } from "fs";
18491
+ var CHUNK = 64 * 1024;
18492
+ var MAX_BYTES = 8 * 1024 * 1024;
18493
+ function tailFile(path, lines) {
18494
+ let fd;
18495
+ try {
18496
+ fd = openSync(path, "r");
18497
+ } catch {
18498
+ return null;
18499
+ }
18500
+ try {
18501
+ const size = fstatSync(fd).size;
18502
+ if (size === 0)
18503
+ return "";
18504
+ const budget = Math.min(size, MAX_BYTES);
18505
+ let read = 0;
18506
+ let newlines = 0;
18507
+ const chunks = [];
18508
+ while (read < budget && newlines <= lines) {
18509
+ const span = Math.min(CHUNK, budget - read);
18510
+ const buffer = Buffer.allocUnsafe(span);
18511
+ const position = size - read - span;
18512
+ readSync(fd, buffer, 0, span, position);
18513
+ chunks.unshift(buffer);
18514
+ read += span;
18515
+ for (const byte of buffer)
18516
+ if (byte === 10)
18517
+ newlines++;
18518
+ }
18519
+ return Buffer.concat(chunks).toString("utf8");
18520
+ } catch {
18521
+ return null;
18522
+ } finally {
18523
+ closeSync(fd);
18524
+ }
18525
+ }
18526
+ function fileExists(path) {
18527
+ try {
18528
+ return statSync(path).isFile();
18529
+ } catch {
18530
+ return false;
18531
+ }
18532
+ }
18244
18533
  // packages/control/src/usage.ts
18245
18534
  var MAX_LOG_LIMIT = 500;
18246
18535
  var DEFAULT_LOG_LIMIT = 100;
@@ -18475,6 +18764,278 @@ async function pollUntilAuthorized(flows, start, deps) {
18475
18764
  }
18476
18765
  }
18477
18766
 
18767
+ // apps/cli/src/service.ts
18768
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
18769
+ import { homedir as homedir2 } from "os";
18770
+ import { dirname, join as join2 } from "path";
18771
+ var UNIT_NAME2 = "omnigateway.service";
18772
+ function unitPath(scope) {
18773
+ return scope === "system" ? join2("/etc/systemd/system", UNIT_NAME2) : join2(homedir2(), ".config", "systemd", "user", UNIT_NAME2);
18774
+ }
18775
+ function systemctlArgs(scope, args) {
18776
+ return scope === "system" ? ["systemctl", ...args] : ["systemctl", "--user", ...args];
18777
+ }
18778
+ function pidFile(stateDir) {
18779
+ return join2(stateDir, "gateway.pid");
18780
+ }
18781
+ function logFile(stateDir) {
18782
+ return join2(stateDir, "gateway.log");
18783
+ }
18784
+ function supervisedLogFile(deps) {
18785
+ const configured = deps.logFile?.trim();
18786
+ return configured !== undefined && configured.length > 0 ? configured : logFile(deps.stateDir);
18787
+ }
18788
+ function defaultStateDir(env2) {
18789
+ const xdg = env2.XDG_STATE_HOME;
18790
+ const base = typeof xdg === "string" && xdg.length > 0 ? xdg : join2(homedir2(), ".local", "state");
18791
+ return join2(base, "omnigateway");
18792
+ }
18793
+ function unitFile(input) {
18794
+ return `[Unit]
18795
+ Description=${input.description ?? "OmniGateway"}
18796
+ After=network-online.target
18797
+ Wants=network-online.target
18798
+
18799
+ [Service]
18800
+ Type=simple
18801
+ WorkingDirectory=${input.root}
18802
+ EnvironmentFile=${join2(input.root, ".env")}
18803
+ ExecStart=${input.bun} ${input.entrypoint}
18804
+ Restart=on-failure
18805
+ RestartSec=2
18806
+
18807
+ [Install]
18808
+ WantedBy=default.target
18809
+ `;
18810
+ }
18811
+ function unitInstalled(deps) {
18812
+ return existsSync2(deps.unitPath);
18813
+ }
18814
+ function readPid(stateDir) {
18815
+ const file2 = pidFile(stateDir);
18816
+ if (!existsSync2(file2))
18817
+ return null;
18818
+ const pid = Number(readFileSync2(file2, "utf8").trim());
18819
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
18820
+ }
18821
+ function livePid(deps) {
18822
+ const pid = readPid(deps.stateDir);
18823
+ if (pid === null)
18824
+ return null;
18825
+ if (deps.alive(pid))
18826
+ return pid;
18827
+ rmSync(pidFile(deps.stateDir), { force: true });
18828
+ return null;
18829
+ }
18830
+ async function status(deps) {
18831
+ if (unitInstalled(deps)) {
18832
+ const active = await deps.run(systemctlArgs(deps.scope, ["is-active", UNIT_NAME2]));
18833
+ const state = active.stdout.trim() || active.stderr.trim() || "unknown";
18834
+ const shown = await deps.run(systemctlArgs(deps.scope, ["show", UNIT_NAME2, "--property=MainPID", "--value"]));
18835
+ const pid2 = Number(shown.stdout.trim());
18836
+ return {
18837
+ supervisor: "systemd",
18838
+ running: state === "active",
18839
+ pid: Number.isInteger(pid2) && pid2 > 0 ? pid2 : null,
18840
+ state,
18841
+ unitPath: deps.unitPath,
18842
+ logFile: null
18843
+ };
18844
+ }
18845
+ const pid = livePid(deps);
18846
+ return {
18847
+ supervisor: pid === null ? "none" : "pidfile",
18848
+ running: pid !== null,
18849
+ pid,
18850
+ state: null,
18851
+ unitPath: null,
18852
+ logFile: supervisedLogFile(deps)
18853
+ };
18854
+ }
18855
+ async function start(deps, input) {
18856
+ if (unitInstalled(deps)) {
18857
+ const result = await deps.run(systemctlArgs(deps.scope, ["start", UNIT_NAME2]));
18858
+ if (result.code !== 0) {
18859
+ throw new Error(result.stderr.trim() || `systemctl start exited ${result.code}`);
18860
+ }
18861
+ return {
18862
+ supervisor: "systemd",
18863
+ pid: null,
18864
+ healthy: await waitForHealth(deps, input.baseUrl)
18865
+ };
18866
+ }
18867
+ const running = livePid(deps);
18868
+ if (running !== null) {
18869
+ return {
18870
+ supervisor: "pidfile",
18871
+ pid: running,
18872
+ healthy: await waitForHealth(deps, input.baseUrl)
18873
+ };
18874
+ }
18875
+ mkdirSync(deps.stateDir, { recursive: true });
18876
+ const file2 = supervisedLogFile(deps);
18877
+ mkdirSync(dirname(file2), { recursive: true });
18878
+ const pid = deps.spawn({
18879
+ argv: input.argv,
18880
+ cwd: deps.root,
18881
+ env: { ...input.env, OMNI_LOG_FILE: file2 },
18882
+ logFile: file2
18883
+ });
18884
+ writeFileSync(pidFile(deps.stateDir), `${pid}
18885
+ `);
18886
+ return { supervisor: "pidfile", pid, healthy: await waitForHealth(deps, input.baseUrl) };
18887
+ }
18888
+ var HEALTH_TIMEOUT_MS = 1e4;
18889
+ var HEALTH_INTERVAL_MS = 200;
18890
+ async function waitForHealth(deps, baseUrl) {
18891
+ const attempts = Math.ceil(HEALTH_TIMEOUT_MS / HEALTH_INTERVAL_MS);
18892
+ for (let attempt = 0;attempt < attempts; attempt++) {
18893
+ if (await deps.probe(baseUrl))
18894
+ return true;
18895
+ await deps.sleep(HEALTH_INTERVAL_MS);
18896
+ }
18897
+ return false;
18898
+ }
18899
+ var STOP_GRACE_MS = 5000;
18900
+ var STOP_INTERVAL_MS = 100;
18901
+ async function stop(deps) {
18902
+ if (unitInstalled(deps)) {
18903
+ const result = await deps.run(systemctlArgs(deps.scope, ["stop", UNIT_NAME2]));
18904
+ if (result.code !== 0) {
18905
+ throw new Error(result.stderr.trim() || `systemctl stop exited ${result.code}`);
18906
+ }
18907
+ return { supervisor: "systemd", stopped: true };
18908
+ }
18909
+ const pid = livePid(deps);
18910
+ if (pid === null)
18911
+ return { supervisor: "none", stopped: false };
18912
+ deps.kill(pid, "SIGTERM");
18913
+ const attempts = Math.ceil(STOP_GRACE_MS / STOP_INTERVAL_MS);
18914
+ for (let attempt = 0;deps.alive(pid); attempt++) {
18915
+ if (attempt >= attempts) {
18916
+ deps.kill(pid, "SIGKILL");
18917
+ break;
18918
+ }
18919
+ await deps.sleep(STOP_INTERVAL_MS);
18920
+ }
18921
+ rmSync(pidFile(deps.stateDir), { force: true });
18922
+ return { supervisor: "pidfile", stopped: true };
18923
+ }
18924
+ async function install(deps, input) {
18925
+ const path = deps.unitPath;
18926
+ if (existsSync2(path) && !input.force) {
18927
+ throw new Error(`${path} already exists; pass --force to replace it`);
18928
+ }
18929
+ mkdirSync(dirname(path), { recursive: true });
18930
+ writeFileSync(path, unitFile({ root: deps.root, bun: input.bun, entrypoint: input.entrypoint }));
18931
+ const reload = await deps.run(systemctlArgs(deps.scope, ["daemon-reload"]));
18932
+ let enabled = false;
18933
+ if (input.enable) {
18934
+ const result = await deps.run(systemctlArgs(deps.scope, ["enable", UNIT_NAME2]));
18935
+ enabled = result.code === 0;
18936
+ }
18937
+ return { path, reloaded: reload.code === 0, enabled };
18938
+ }
18939
+ async function uninstall(deps) {
18940
+ const path = deps.unitPath;
18941
+ if (!existsSync2(path))
18942
+ return { path, removed: false };
18943
+ await deps.run(systemctlArgs(deps.scope, ["disable", "--now", UNIT_NAME2]));
18944
+ rmSync(path, { force: true });
18945
+ await deps.run(systemctlArgs(deps.scope, ["daemon-reload"]));
18946
+ return { path, removed: true };
18947
+ }
18948
+ function consoleSource(deps) {
18949
+ const configured = deps.logFile?.trim();
18950
+ const chosen = configured !== undefined && configured.length > 0 && fileExists(configured) ? configured : unitInstalled(deps) ? undefined : fileExists(supervisedLogFile(deps)) ? supervisedLogFile(deps) : undefined;
18951
+ return {
18952
+ source: resolveConsoleSource({
18953
+ ...chosen === undefined ? {} : { logFile: chosen },
18954
+ unitInstalled: unitInstalled(deps),
18955
+ scope: deps.scope
18956
+ }),
18957
+ deps: {
18958
+ readFile: (path, lines) => tailFile(path, lines),
18959
+ run: deps.run
18960
+ }
18961
+ };
18962
+ }
18963
+ async function serviceLogs(deps, lines) {
18964
+ const { source, deps: readDeps } = consoleSource(deps);
18965
+ const read = await readConsole(readDeps, source, { lines });
18966
+ return read.lines.map((line) => line.raw).join(`
18967
+ `);
18968
+ }
18969
+
18970
+ // apps/cli/src/commands/console.ts
18971
+ var FOLLOW_INTERVAL_MS = 2000;
18972
+ var LEVEL_TONE = {
18973
+ debug: "dim",
18974
+ warn: "yellow",
18975
+ error: "red"
18976
+ };
18977
+ function render(ctx, lines) {
18978
+ return lines.map((line) => {
18979
+ const tone = line.level === null ? undefined : LEVEL_TONE[line.level];
18980
+ return tone === undefined ? line.raw : paint(ctx, tone, line.raw);
18981
+ }).join(`
18982
+ `);
18983
+ }
18984
+ function sourceHint(source) {
18985
+ if (source.kind === "file")
18986
+ return `${source.path} (named by OMNI_LOG_FILE)`;
18987
+ if (source.kind === "journal")
18988
+ return `systemd journal for ${source.unit}`;
18989
+ return "not captured \u2014 run under systemd, or start it with `omni start`";
18990
+ }
18991
+ function empty(source) {
18992
+ if (source.kind !== "none")
18993
+ return `no output yet in ${sourceHint(source)}`;
18994
+ return [
18995
+ "this gateway's output is not being captured, so there is nothing to show.",
18996
+ "run it in the foreground and its output goes to your terminal instead.",
18997
+ "to capture it: `omni service install`, or `omni start`, which redirects",
18998
+ "output to a file and points OMNI_LOG_FILE at it."
18999
+ ].join(`
19000
+ `);
19001
+ }
19002
+ var console_ = {
19003
+ usage: "console [-n N] [--follow] [--level debug|info|warn|error]",
19004
+ summary: "Show the gateway process's own output",
19005
+ options: {
19006
+ number: { type: "string", short: "n" },
19007
+ follow: { type: "boolean" },
19008
+ level: { type: "string" },
19009
+ system: { type: "boolean" }
19010
+ },
19011
+ async run(args, { ctx, writer, service }) {
19012
+ const lines = numberFlag(args.values, "number") ?? 50;
19013
+ const rawLevel = stringFlag(args.values, "level");
19014
+ const level = parseLogLevel(rawLevel);
19015
+ if (rawLevel !== undefined && level === null) {
19016
+ throw new UsageError(`unknown level "${rawLevel}"; use debug, info, warn or error`);
19017
+ }
19018
+ const { source, deps } = consoleSource(service());
19019
+ const query = { lines, ...level === null ? {} : { level } };
19020
+ const first = await readConsole(deps, source, query);
19021
+ emit(ctx, writer, first, () => first.lines.length === 0 ? empty(source) : render(ctx, first.lines));
19022
+ if (!ctx.json && first.lines.length > 0) {
19023
+ note(ctx, writer, paint(ctx, "dim", `reading ${sourceHint(source)}`));
19024
+ }
19025
+ if (boolFlag(args.values, "follow") !== true || ctx.json)
19026
+ return;
19027
+ let seen = first.lines.at(-1)?.at ?? 0;
19028
+ for (;; ) {
19029
+ await Bun.sleep(FOLLOW_INTERVAL_MS);
19030
+ const next = await readConsole(deps, source, { ...query, since: seen });
19031
+ if (next.lines.length === 0)
19032
+ continue;
19033
+ seen = next.lines.at(-1)?.at ?? seen;
19034
+ writer.out(render(ctx, next.lines));
19035
+ }
19036
+ }
19037
+ };
19038
+
18478
19039
  // apps/cli/src/command.ts
18479
19040
  var PROVIDER_TONE = {
18480
19041
  anthropic: "magenta",
@@ -18515,7 +19076,7 @@ var credentialsList = {
18515
19076
  { header: "STATE" },
18516
19077
  { header: "EXPIRES" }
18517
19078
  ], credentials.map((c) => {
18518
- const status = condition(c);
19079
+ const status2 = condition(c);
18519
19080
  return [
18520
19081
  c.id,
18521
19082
  provider(ctx, c.provider),
@@ -18523,7 +19084,7 @@ var credentialsList = {
18523
19084
  c.authType,
18524
19085
  String(c.tier),
18525
19086
  String(c.weight),
18526
- state(ctx, status.ok, status.text),
19087
+ state(ctx, status2.ok, status2.text),
18527
19088
  formatTime(c.expiresAt)
18528
19089
  ];
18529
19090
  }));
@@ -18539,7 +19100,7 @@ var credentialsShow = {
18539
19100
  async run(args, { ctx, writer }) {
18540
19101
  const id = requirePositional(args, 0, "credential id");
18541
19102
  const credential = await findCredential(ctx, id);
18542
- const status = condition(credential);
19103
+ const status2 = condition(credential);
18543
19104
  const data = {
18544
19105
  id: credential.id,
18545
19106
  provider: credential.provider,
@@ -18562,7 +19123,7 @@ var credentialsShow = {
18562
19123
  ["provider", provider(ctx, credential.provider)],
18563
19124
  ["label", credential.label],
18564
19125
  ["auth", credential.authType],
18565
- ["state", state(ctx, status.ok, status.text)],
19126
+ ["state", state(ctx, status2.ok, status2.text)],
18566
19127
  ["tier", String(credential.tier)],
18567
19128
  ["weight", String(credential.weight)],
18568
19129
  ["account", credential.accountEmail ?? "\u2014"],
@@ -18697,12 +19258,12 @@ var credentialsHealth = {
18697
19258
  };
18698
19259
 
18699
19260
  // apps/cli/src/commands/db.ts
18700
- import { existsSync as existsSync2 } from "fs";
19261
+ import { existsSync as existsSync3 } from "fs";
18701
19262
  var dbMigrate = {
18702
19263
  usage: "db migrate",
18703
19264
  summary: "Create or upgrade the database schema",
18704
19265
  async run(_args, { ctx, writer }) {
18705
- const existed = existsSync2(ctx.databasePath);
19266
+ const existed = existsSync3(ctx.databasePath);
18706
19267
  const store = await ctx.store();
18707
19268
  await store.config.getSettings();
18708
19269
  emit(ctx, writer, { path: ctx.databasePath, created: !existed }, () => fields([
@@ -18840,8 +19401,8 @@ var modelsShow = {
18840
19401
  price(target.costPerMTok.cacheRead),
18841
19402
  price(target.costPerMTok.cacheWrite5m),
18842
19403
  price(target.costPerMTok.cacheWrite1h),
18843
- tokens(target.contextWindow),
18844
- tokens(target.maxOutputTokens),
19404
+ tokens2(target.contextWindow),
19405
+ tokens2(target.maxOutputTokens),
18845
19406
  capabilityList(target)
18846
19407
  ]))
18847
19408
  ].join(`
@@ -18851,7 +19412,7 @@ var modelsShow = {
18851
19412
  function price(value) {
18852
19413
  return value === undefined ? "\u2014" : value.toFixed(2);
18853
19414
  }
18854
- function tokens(value) {
19415
+ function tokens2(value) {
18855
19416
  return value === undefined ? "\u2014" : value.toLocaleString("en-US");
18856
19417
  }
18857
19418
  function capabilityList(target) {
@@ -18919,9 +19480,9 @@ var modelsCatalog = {
18919
19480
  price(listed?.cacheRead),
18920
19481
  price(listed?.cacheWrite5m),
18921
19482
  price(listed?.cacheWrite1h),
18922
- tokens(limits?.contextWindow),
18923
- oauth?.contextWindow === limits?.contextWindow ? "\u2014" : tokens(oauth?.contextWindow),
18924
- tokens(limits?.maxOutputTokens)
19483
+ tokens2(limits?.contextWindow),
19484
+ oauth?.contextWindow === limits?.contextWindow ? "\u2014" : tokens2(oauth?.contextWindow),
19485
+ tokens2(limits?.maxOutputTokens)
18925
19486
  ];
18926
19487
  })));
18927
19488
  }
@@ -19030,207 +19591,8 @@ ${candidates}${excluded}`;
19030
19591
  import { existsSync as existsSync5 } from "fs";
19031
19592
 
19032
19593
  // apps/cli/src/runtime.ts
19033
- import { closeSync, existsSync as existsSync4, openSync } from "fs";
19594
+ import { closeSync as closeSync2, existsSync as existsSync4, openSync as openSync2 } from "fs";
19034
19595
  import { join as join3 } from "path";
19035
-
19036
- // apps/cli/src/service.ts
19037
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
19038
- import { homedir as homedir2 } from "os";
19039
- import { dirname, join as join2 } from "path";
19040
- var UNIT_NAME = "omnigateway.service";
19041
- function unitPath(scope) {
19042
- return scope === "system" ? join2("/etc/systemd/system", UNIT_NAME) : join2(homedir2(), ".config", "systemd", "user", UNIT_NAME);
19043
- }
19044
- function systemctlArgs(scope, args) {
19045
- return scope === "system" ? ["systemctl", ...args] : ["systemctl", "--user", ...args];
19046
- }
19047
- function pidFile(stateDir) {
19048
- return join2(stateDir, "gateway.pid");
19049
- }
19050
- function logFile(stateDir) {
19051
- return join2(stateDir, "gateway.log");
19052
- }
19053
- function defaultStateDir(env2) {
19054
- const xdg = env2.XDG_STATE_HOME;
19055
- const base = typeof xdg === "string" && xdg.length > 0 ? xdg : join2(homedir2(), ".local", "state");
19056
- return join2(base, "omnigateway");
19057
- }
19058
- function unitFile(input) {
19059
- return `[Unit]
19060
- Description=${input.description ?? "OmniGateway"}
19061
- After=network-online.target
19062
- Wants=network-online.target
19063
-
19064
- [Service]
19065
- Type=simple
19066
- WorkingDirectory=${input.root}
19067
- EnvironmentFile=${join2(input.root, ".env")}
19068
- ExecStart=${input.bun} ${input.entrypoint}
19069
- Restart=on-failure
19070
- RestartSec=2
19071
-
19072
- [Install]
19073
- WantedBy=default.target
19074
- `;
19075
- }
19076
- function unitInstalled(deps) {
19077
- return existsSync3(deps.unitPath);
19078
- }
19079
- function readPid(stateDir) {
19080
- const file2 = pidFile(stateDir);
19081
- if (!existsSync3(file2))
19082
- return null;
19083
- const pid = Number(readFileSync2(file2, "utf8").trim());
19084
- return Number.isInteger(pid) && pid > 0 ? pid : null;
19085
- }
19086
- function livePid(deps) {
19087
- const pid = readPid(deps.stateDir);
19088
- if (pid === null)
19089
- return null;
19090
- if (deps.alive(pid))
19091
- return pid;
19092
- rmSync(pidFile(deps.stateDir), { force: true });
19093
- return null;
19094
- }
19095
- async function status(deps) {
19096
- if (unitInstalled(deps)) {
19097
- const active = await deps.run(systemctlArgs(deps.scope, ["is-active", UNIT_NAME]));
19098
- const state2 = active.stdout.trim() || active.stderr.trim() || "unknown";
19099
- const shown = await deps.run(systemctlArgs(deps.scope, ["show", UNIT_NAME, "--property=MainPID", "--value"]));
19100
- const pid2 = Number(shown.stdout.trim());
19101
- return {
19102
- supervisor: "systemd",
19103
- running: state2 === "active",
19104
- pid: Number.isInteger(pid2) && pid2 > 0 ? pid2 : null,
19105
- state: state2,
19106
- unitPath: deps.unitPath,
19107
- logFile: null
19108
- };
19109
- }
19110
- const pid = livePid(deps);
19111
- return {
19112
- supervisor: pid === null ? "none" : "pidfile",
19113
- running: pid !== null,
19114
- pid,
19115
- state: null,
19116
- unitPath: null,
19117
- logFile: logFile(deps.stateDir)
19118
- };
19119
- }
19120
- async function start(deps, input) {
19121
- if (unitInstalled(deps)) {
19122
- const result = await deps.run(systemctlArgs(deps.scope, ["start", UNIT_NAME]));
19123
- if (result.code !== 0) {
19124
- throw new Error(result.stderr.trim() || `systemctl start exited ${result.code}`);
19125
- }
19126
- return {
19127
- supervisor: "systemd",
19128
- pid: null,
19129
- healthy: await waitForHealth(deps, input.baseUrl)
19130
- };
19131
- }
19132
- const running = livePid(deps);
19133
- if (running !== null) {
19134
- return {
19135
- supervisor: "pidfile",
19136
- pid: running,
19137
- healthy: await waitForHealth(deps, input.baseUrl)
19138
- };
19139
- }
19140
- mkdirSync(deps.stateDir, { recursive: true });
19141
- const pid = deps.spawn({
19142
- argv: input.argv,
19143
- cwd: deps.root,
19144
- env: input.env,
19145
- logFile: logFile(deps.stateDir)
19146
- });
19147
- writeFileSync(pidFile(deps.stateDir), `${pid}
19148
- `);
19149
- return { supervisor: "pidfile", pid, healthy: await waitForHealth(deps, input.baseUrl) };
19150
- }
19151
- var HEALTH_TIMEOUT_MS = 1e4;
19152
- var HEALTH_INTERVAL_MS = 200;
19153
- async function waitForHealth(deps, baseUrl) {
19154
- const attempts = Math.ceil(HEALTH_TIMEOUT_MS / HEALTH_INTERVAL_MS);
19155
- for (let attempt = 0;attempt < attempts; attempt++) {
19156
- if (await deps.probe(baseUrl))
19157
- return true;
19158
- await deps.sleep(HEALTH_INTERVAL_MS);
19159
- }
19160
- return false;
19161
- }
19162
- var STOP_GRACE_MS = 5000;
19163
- var STOP_INTERVAL_MS = 100;
19164
- async function stop(deps) {
19165
- if (unitInstalled(deps)) {
19166
- const result = await deps.run(systemctlArgs(deps.scope, ["stop", UNIT_NAME]));
19167
- if (result.code !== 0) {
19168
- throw new Error(result.stderr.trim() || `systemctl stop exited ${result.code}`);
19169
- }
19170
- return { supervisor: "systemd", stopped: true };
19171
- }
19172
- const pid = livePid(deps);
19173
- if (pid === null)
19174
- return { supervisor: "none", stopped: false };
19175
- deps.kill(pid, "SIGTERM");
19176
- const attempts = Math.ceil(STOP_GRACE_MS / STOP_INTERVAL_MS);
19177
- for (let attempt = 0;deps.alive(pid); attempt++) {
19178
- if (attempt >= attempts) {
19179
- deps.kill(pid, "SIGKILL");
19180
- break;
19181
- }
19182
- await deps.sleep(STOP_INTERVAL_MS);
19183
- }
19184
- rmSync(pidFile(deps.stateDir), { force: true });
19185
- return { supervisor: "pidfile", stopped: true };
19186
- }
19187
- async function install(deps, input) {
19188
- const path = deps.unitPath;
19189
- if (existsSync3(path) && !input.force) {
19190
- throw new Error(`${path} already exists; pass --force to replace it`);
19191
- }
19192
- mkdirSync(dirname(path), { recursive: true });
19193
- writeFileSync(path, unitFile({ root: deps.root, bun: input.bun, entrypoint: input.entrypoint }));
19194
- const reload = await deps.run(systemctlArgs(deps.scope, ["daemon-reload"]));
19195
- let enabled = false;
19196
- if (input.enable) {
19197
- const result = await deps.run(systemctlArgs(deps.scope, ["enable", UNIT_NAME]));
19198
- enabled = result.code === 0;
19199
- }
19200
- return { path, reloaded: reload.code === 0, enabled };
19201
- }
19202
- async function uninstall(deps) {
19203
- const path = deps.unitPath;
19204
- if (!existsSync3(path))
19205
- return { path, removed: false };
19206
- await deps.run(systemctlArgs(deps.scope, ["disable", "--now", UNIT_NAME]));
19207
- rmSync(path, { force: true });
19208
- await deps.run(systemctlArgs(deps.scope, ["daemon-reload"]));
19209
- return { path, removed: true };
19210
- }
19211
- async function serviceLogs(deps, lines) {
19212
- if (unitInstalled(deps)) {
19213
- const args = deps.scope === "system" ? [] : ["--user"];
19214
- const result = await deps.run([
19215
- "journalctl",
19216
- ...args,
19217
- "-u",
19218
- UNIT_NAME,
19219
- "-n",
19220
- String(lines),
19221
- "--no-pager"
19222
- ]);
19223
- return result.stdout.trimEnd();
19224
- }
19225
- const file2 = logFile(deps.stateDir);
19226
- if (!existsSync3(file2))
19227
- return "";
19228
- return readFileSync2(file2, "utf8").split(`
19229
- `).slice(-lines).join(`
19230
- `).trimEnd();
19231
- }
19232
-
19233
- // apps/cli/src/runtime.ts
19234
19596
  var runCommand = async (argv) => {
19235
19597
  const proc = Bun.spawn([...argv], { stdout: "pipe", stderr: "pipe" });
19236
19598
  const [stdout, stderr, code] = await Promise.all([
@@ -19241,7 +19603,7 @@ var runCommand = async (argv) => {
19241
19603
  return { code, stdout, stderr };
19242
19604
  };
19243
19605
  var spawnDetached = ({ argv, cwd, env: env2, logFile: logFile2 }) => {
19244
- const fd = openSync(logFile2, "a");
19606
+ const fd = openSync2(logFile2, "a");
19245
19607
  try {
19246
19608
  const proc = Bun.spawn([...argv], {
19247
19609
  cwd,
@@ -19253,7 +19615,7 @@ var spawnDetached = ({ argv, cwd, env: env2, logFile: logFile2 }) => {
19253
19615
  proc.unref();
19254
19616
  return proc.pid;
19255
19617
  } finally {
19256
- closeSync(fd);
19618
+ closeSync2(fd);
19257
19619
  }
19258
19620
  };
19259
19621
  async function runForeground(input) {
@@ -19294,11 +19656,13 @@ function gatewayEntrypoint(root, cliDir = import.meta.dir) {
19294
19656
  return candidates.find((path) => existsSync4(path)) ?? null;
19295
19657
  }
19296
19658
  function createServiceDeps(input) {
19659
+ const logFile2 = input.env.OMNI_LOG_FILE?.trim();
19297
19660
  return {
19298
19661
  root: input.root,
19299
19662
  stateDir: defaultStateDir(input.env),
19300
19663
  scope: input.scope,
19301
19664
  unitPath: unitPath(input.scope),
19665
+ ...logFile2 === undefined || logFile2.length === 0 ? {} : { logFile: logFile2 },
19302
19666
  run: runCommand,
19303
19667
  spawn: spawnDetached,
19304
19668
  probe: probeHealth,
@@ -19341,7 +19705,7 @@ var start2 = {
19341
19705
  emit(ctx, writer, result, () => {
19342
19706
  const how = result.supervisor === "systemd" ? "systemd" : `pid ${result.pid}`;
19343
19707
  if (!result.healthy) {
19344
- const where = result.supervisor === "systemd" ? "journalctl -u omnigateway.service" : logFile(deps.stateDir);
19708
+ const where = result.supervisor === "systemd" ? "journalctl -u omnigateway.service" : supervisedLogFile(deps);
19345
19709
  return `${state(ctx, false, "started but /health never answered")} (${how}); see ${where}`;
19346
19710
  }
19347
19711
  return `${state(ctx, true, "running")} (${how}) at ${baseUrl}`;
@@ -19434,7 +19798,8 @@ var doctor = {
19434
19798
  gatewayEntrypoint: gatewayEntrypoint(deps.root),
19435
19799
  unitInstalled: unit,
19436
19800
  supervisor: status2.supervisor,
19437
- running: status2.running
19801
+ running: status2.running,
19802
+ consoleSource: consoleSource(deps).source
19438
19803
  };
19439
19804
  emit(ctx, writer, checks3, () => {
19440
19805
  const ok = (value, text) => state(ctx, value, text);
@@ -19449,7 +19814,8 @@ var doctor = {
19449
19814
  ["config", checks3.configError === null ? ok(true, "ok") : ok(false, checks3.configError)],
19450
19815
  ["entrypoint", checks3.gatewayEntrypoint ?? ok(false, "not found")],
19451
19816
  ["systemd unit", unit ? deps.scope : paint(ctx, "dim", "none")],
19452
- ["gateway", ok(status2.running, status2.running ? "running" : "stopped")]
19817
+ ["gateway", ok(status2.running, status2.running ? "running" : "stopped")],
19818
+ ["console log", sourceHint(checks3.consoleSource)]
19453
19819
  ]);
19454
19820
  });
19455
19821
  }
@@ -19500,6 +19866,78 @@ var settingsSet = {
19500
19866
  }
19501
19867
  };
19502
19868
 
19869
+ // apps/cli/src/commands/setup.ts
19870
+ import { dirname as dirname2, join as join4 } from "path";
19871
+ var OPTIONS = {
19872
+ dir: { type: "string" },
19873
+ key: { type: "string" },
19874
+ "dry-run": { type: "boolean" }
19875
+ };
19876
+ async function described(ctx) {
19877
+ const models = await describeModelsForSetup(await ctx.store());
19878
+ if (models.length === 0) {
19879
+ throw new CliError("no virtual models configured; add one with `omni models put`");
19880
+ }
19881
+ return models;
19882
+ }
19883
+ function baseUrl(ctx) {
19884
+ if (ctx.configError !== null)
19885
+ throw new CliError(ctx.configError);
19886
+ return ctx.config().baseUrl;
19887
+ }
19888
+ function finish(ctx, writer, files, dryRun2, key, write) {
19889
+ if (!dryRun2) {
19890
+ for (const file2 of files)
19891
+ write(file2.path, file2.contents);
19892
+ }
19893
+ emit(ctx, writer, { files }, () => dryRun2 ? files.map((f) => `--- ${f.path}
19894
+ ${f.contents}`).join(`
19895
+ `) : files.map((f) => f.path).join(`
19896
+ `));
19897
+ if (key === undefined) {
19898
+ note(ctx, writer, "no --key given: the files carry a placeholder, so paste your key over it");
19899
+ }
19900
+ }
19901
+ function at(dir, file2) {
19902
+ return { path: join4(dir, file2.path), contents: file2.contents };
19903
+ }
19904
+ var setupClaude = {
19905
+ usage: "setup claude [--dir <path>] [--key <key>] [--dry-run]",
19906
+ summary: "Write a Claude Code profile per virtual model",
19907
+ options: OPTIONS,
19908
+ async run(args, { ctx, writer, setupFs }) {
19909
+ const models = await described(ctx);
19910
+ const dir = stringFlag(args.values, "dir") ?? join4(setupFs.homeDir, ".claude", "profiles");
19911
+ const key = stringFlag(args.values, "key");
19912
+ const dryRun2 = boolFlag(args.values, "dry-run");
19913
+ const files = claudeProfiles(models, {
19914
+ baseUrl: baseUrl(ctx),
19915
+ discoveryMirrors: ctx.config().exposeClaudeCodeAliases,
19916
+ ...key === undefined ? {} : { apiKey: key }
19917
+ }).map((file2) => at(dir, file2));
19918
+ finish(ctx, writer, files, dryRun2, key, setupFs.write);
19919
+ if (!dryRun2) {
19920
+ note(ctx, writer, `run one with: CLAUDE_CONFIG_DIR=${dirname2(files[0]?.path ?? dir)} claude`);
19921
+ }
19922
+ }
19923
+ };
19924
+ var setupOpencode = {
19925
+ usage: "setup opencode [--dir <path>] [--key <key>] [--dry-run]",
19926
+ summary: "Write an opencode.json provider entry for this gateway",
19927
+ options: OPTIONS,
19928
+ async run(args, { ctx, writer, setupFs }) {
19929
+ const models = await described(ctx);
19930
+ const dir = stringFlag(args.values, "dir") ?? setupFs.cwd;
19931
+ const key = stringFlag(args.values, "key");
19932
+ const dryRun2 = boolFlag(args.values, "dry-run");
19933
+ const file2 = opencodeConfig(models, {
19934
+ baseUrl: baseUrl(ctx),
19935
+ ...key === undefined ? {} : { apiKey: key }
19936
+ });
19937
+ finish(ctx, writer, [at(dir, file2)], dryRun2, key, setupFs.write);
19938
+ }
19939
+ };
19940
+
19503
19941
  // apps/cli/src/commands/status.ts
19504
19942
  var WINDOW_ORDER = {
19505
19943
  fiveHour: 0,
@@ -19653,11 +20091,12 @@ var usage = {
19653
20091
  };
19654
20092
  var logs = {
19655
20093
  usage: "logs [-n N] [--follow] [--service]",
19656
- summary: "Show recent requests, or the gateway process's own output",
20094
+ summary: "Show recent requests (--service is an alias for `omni console`)",
19657
20095
  options: {
19658
20096
  number: { type: "string", short: "n" },
19659
20097
  follow: { type: "boolean" },
19660
- service: { type: "boolean" }
20098
+ service: { type: "boolean" },
20099
+ system: { type: "boolean" }
19661
20100
  },
19662
20101
  async run(args, { ctx, writer, service }) {
19663
20102
  const limit = numberFlag(args.values, "number") ?? 20;
@@ -19667,7 +20106,7 @@ var logs = {
19667
20106
  return;
19668
20107
  }
19669
20108
  const store = await ctx.store();
19670
- const render = (rows) => table([
20109
+ const render2 = (rows) => table([
19671
20110
  { header: "AT" },
19672
20111
  { header: "MODEL" },
19673
20112
  { header: "RESOLVED" },
@@ -19685,7 +20124,7 @@ var logs = {
19685
20124
  String(row.durationMs)
19686
20125
  ]));
19687
20126
  const first = await recentLogs(store, limit);
19688
- emit(ctx, writer, { logs: first }, () => first.length === 0 ? "no requests logged yet" : render(first));
20127
+ emit(ctx, writer, { logs: first }, () => first.length === 0 ? "no requests logged yet" : render2(first));
19689
20128
  if (args.values.follow !== true || ctx.json)
19690
20129
  return;
19691
20130
  let seen = first[0]?.at ?? 0;
@@ -19695,7 +20134,7 @@ var logs = {
19695
20134
  if (next.length === 0)
19696
20135
  continue;
19697
20136
  seen = next[0]?.at ?? seen;
19698
- writer.out(render(next.reverse()));
20137
+ writer.out(render2(next.reverse()));
19699
20138
  }
19700
20139
  }
19701
20140
  };
@@ -19708,6 +20147,7 @@ var COMMANDS = {
19708
20147
  restart,
19709
20148
  doctor,
19710
20149
  logs,
20150
+ console: console_,
19711
20151
  usage,
19712
20152
  connect,
19713
20153
  "service install": serviceInstall,
@@ -19732,6 +20172,8 @@ var COMMANDS = {
19732
20172
  "keys revoke": keysRevoke,
19733
20173
  "settings get": settingsGet,
19734
20174
  "settings set": settingsSet,
20175
+ "setup claude": setupClaude,
20176
+ "setup opencode": setupOpencode,
19735
20177
  "admin set-password": adminSetPassword,
19736
20178
  "db migrate": dbMigrate
19737
20179
  };
@@ -19770,7 +20212,10 @@ function resolveCommand(argv) {
19770
20212
 
19771
20213
  // apps/cli/src/help.ts
19772
20214
  var GROUPS = [
19773
- { title: "Gateway", prefixes: ["status", "start", "stop", "restart", "doctor", "logs"] },
20215
+ {
20216
+ title: "Gateway",
20217
+ prefixes: ["status", "start", "stop", "restart", "doctor", "logs", "console"]
20218
+ },
19774
20219
  { title: "Service", prefixes: ["service "] },
19775
20220
  { title: "Accounts", prefixes: ["connect", "credentials "] },
19776
20221
  { title: "Models", prefixes: ["models "] },
@@ -19929,6 +20374,14 @@ async function run(argv, writer, options = {}) {
19929
20374
  ctx,
19930
20375
  writer,
19931
20376
  prompt,
20377
+ setupFs: options.setupFs ?? {
20378
+ homeDir: homedir3(),
20379
+ cwd: process.cwd(),
20380
+ write: (path, contents) => {
20381
+ mkdirSync2(dirname3(path), { recursive: true });
20382
+ writeFileSync2(path, contents);
20383
+ }
20384
+ },
19932
20385
  service: () => options.service?.({ root: ctx.root.root, env: ctx.env }) ?? createServiceDeps({ root: ctx.root.root, env: ctx.env, scope, now: ctx.now }),
19933
20386
  foreground: options.foreground ?? runForeground,
19934
20387
  connect: (store) => options.connect?.(store) ?? createConnectFlows({