@agent-proxy/gateway 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -12,8 +12,8 @@ var __export = (target, all) => {
12
12
  };
13
13
 
14
14
  // src/cli.ts
15
- import fs21 from "fs";
16
- import path15 from "path";
15
+ import fs24 from "fs";
16
+ import path17 from "path";
17
17
  import { createHash as createHash3 } from "crypto";
18
18
  import { execFileSync as execFileSync5 } from "child_process";
19
19
  import bytes2 from "bytes";
@@ -8512,6 +8512,7 @@ function modelCatalogResponse(catalog, clientVersion, owner, prefix = "cliproxy/
8512
8512
  data: catalog.models.map((model) => ({
8513
8513
  id: model.slug,
8514
8514
  object: "model",
8515
+ ...typeof model.display_name === "string" && model.display_name ? { display_name: model.display_name, name: model.display_name } : {},
8515
8516
  owned_by: registry.match(model.slug)?.plugin.id ?? (owner === "mixed" ? model.slug.startsWith(prefix) ? "cliproxy" : "openai" : owner)
8516
8517
  }))
8517
8518
  });
@@ -9356,14 +9357,452 @@ function updateCodexModelCatalog2(paths, config, dependencies = {}, options = {}
9356
9357
  managedFiles: managedRuntimeCatalogFiles(paths)
9357
9358
  });
9358
9359
  }
9359
- // src/webui.ts
9360
+
9361
+ // src/models-push.ts
9362
+ import fs18 from "fs";
9363
+ function pushTimestamp(date = new Date) {
9364
+ return date.toISOString().replace(/[-:]/g, "").slice(0, 15).replace("T", "");
9365
+ }
9366
+ function writeIfChanged(file, contents) {
9367
+ if (fs18.existsSync(file) && fs18.readFileSync(file, "utf8") === contents)
9368
+ return;
9369
+ const backup = `${file}.bak-agent-proxy-${pushTimestamp()}`;
9370
+ fs18.copyFileSync(file, backup);
9371
+ atomicWrite(file, contents);
9372
+ return backup;
9373
+ }
9374
+ function writeAllOrRollback(entries) {
9375
+ const written = [];
9376
+ try {
9377
+ for (const { file, contents } of entries) {
9378
+ const backup = writeIfChanged(file, contents);
9379
+ if (backup !== undefined)
9380
+ written.push({ file, backup });
9381
+ }
9382
+ } catch (error) {
9383
+ const unrestored = [];
9384
+ for (const { file, backup } of written) {
9385
+ try {
9386
+ fs18.copyFileSync(backup, file);
9387
+ } catch {
9388
+ unrestored.push(file);
9389
+ }
9390
+ }
9391
+ if (unrestored.length === 0)
9392
+ throw error;
9393
+ const message = error instanceof Error ? error.message : String(error);
9394
+ throw new Error(`${message}; failed to restore: ${unrestored.join(", ")} (their backups remain beside the files)`);
9395
+ }
9396
+ return written.map((item) => item.backup);
9397
+ }
9398
+ function modelReasoningEfforts(entry) {
9399
+ const raw = entry.supported_reasoning_levels;
9400
+ if (!Array.isArray(raw))
9401
+ return [];
9402
+ const efforts = [];
9403
+ for (const item of raw) {
9404
+ const effort = typeof item === "string" ? item : item && typeof item === "object" && typeof item.effort === "string" ? item.effort : undefined;
9405
+ if (typeof effort === "string" && effort && !efforts.includes(effort))
9406
+ efforts.push(effort);
9407
+ }
9408
+ return efforts;
9409
+ }
9410
+ function modelContextWindow(entry) {
9411
+ for (const key of ["context_window", "max_context_window"]) {
9412
+ const value = entry[key];
9413
+ if (typeof value === "number" && Number.isFinite(value) && value > 0)
9414
+ return value;
9415
+ }
9416
+ return;
9417
+ }
9418
+ function modelInputModalities(entry) {
9419
+ const raw = entry.input_modalities;
9420
+ if (!Array.isArray(raw))
9421
+ return ["text"];
9422
+ const modalities = raw.filter((item) => item === "text" || item === "image");
9423
+ return modalities.length > 0 ? [...new Set(modalities)] : ["text"];
9424
+ }
9425
+ async function pushModelsToTargets(options) {
9426
+ for (const failure of options.failures) {
9427
+ console.log(`WARNING: ${failure}; its models are not included in this push.`);
9428
+ }
9429
+ const scoped = filterExcludedModels({ models: [...options.entries] }, options.config.excludedModels, isInstalledPluginModel).models;
9430
+ if (scoped.length === 0) {
9431
+ throw new Error("model push aborted; no local adapter models to push" + " (enable an adapter via config --zcode on / --codebuddy on / \u2026 and refresh its catalog first)");
9432
+ }
9433
+ const known = options.targets.map((target) => target.id);
9434
+ const requested = options.targetOption === undefined ? known : [...new Set(options.targetOption.split(",").map((id) => id.trim().toLowerCase()).filter(Boolean))];
9435
+ if (requested.length === 0)
9436
+ throw new Error("--push requires at least one target name (or no value to push every target)");
9437
+ const selected = requested.map((id) => {
9438
+ const target = options.targets.find((candidate) => candidate.id === id);
9439
+ if (!target)
9440
+ throw new Error(`unknown --push target "${id}"; available targets: ${known.join(", ")}`);
9441
+ return target;
9442
+ });
9443
+ const failed = [];
9444
+ for (const target of selected) {
9445
+ try {
9446
+ target.assertInstalled();
9447
+ const outcome = target.push(scoped);
9448
+ console.log(outcome.summary);
9449
+ for (const note of outcome.notes)
9450
+ console.log(` ${note}`);
9451
+ } catch (error) {
9452
+ failed.push(target.id);
9453
+ const message = error instanceof Error ? error.message : String(error);
9454
+ console.log(`WARNING: ${target.id} push failed: ${message}`);
9455
+ }
9456
+ }
9457
+ if (failed.length > 0)
9458
+ throw new Error(`model push failed for: ${failed.join(", ")} (see warnings above)`);
9459
+ }
9460
+
9461
+ // src/zcode-client-sync.ts
9462
+ import fs19 from "fs";
9463
+ import os3 from "os";
9464
+ import path13 from "path";
9465
+ import { randomUUID as randomUUID5 } from "crypto";
9466
+ import { isZcodeModel } from "@agent-proxy/zcode/catalog";
9467
+ var ZCODE_PROVIDER_DISPLAY_NAME = "AgentProxy";
9468
+ function isManagedProviderName(name) {
9469
+ return typeof name === "string" && name.toLowerCase().replace(/-/g, "") === "agentproxy";
9470
+ }
9471
+ var ZCODE_API_KEY_PLACEHOLDER = "EMPTY";
9472
+ var ZCODE_PROVIDER_GROUP = "standard-personal";
9473
+ function resolveZcodeClientPaths(env = process.env) {
9474
+ const home = env.ZCODE_HOME || path13.join(env.HOME || os3.homedir(), ".zcode");
9475
+ const v2 = path13.join(home, "v2");
9476
+ return { home, v2, configFile: path13.join(v2, "config.json"), providerConfigFile: path13.join(v2, "provider_config.json") };
9477
+ }
9478
+ function synthesizeZcodeModelDefinition(entry) {
9479
+ const definition = {};
9480
+ definition.name = typeof entry.display_name === "string" && entry.display_name ? entry.display_name : entry.slug;
9481
+ const efforts = modelReasoningEfforts(entry);
9482
+ if (efforts.length > 0) {
9483
+ const declared = typeof entry.default_reasoning_level === "string" ? entry.default_reasoning_level : undefined;
9484
+ definition.reasoning = {
9485
+ enabled: true,
9486
+ variants: [...efforts],
9487
+ defaultVariant: declared && efforts.includes(declared) ? declared : efforts[efforts.length - 1]
9488
+ };
9489
+ }
9490
+ const context = modelContextWindow(entry);
9491
+ if (context !== undefined)
9492
+ definition.limit = { context };
9493
+ definition.modalities = { input: modelInputModalities(entry), output: ["text"] };
9494
+ definition.zcode = { modalitiesConfigured: true, modified: true };
9495
+ return definition;
9496
+ }
9497
+ function isRecord3(value) {
9498
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9499
+ }
9500
+ function readJsonFile(file) {
9501
+ const parsed = JSON.parse(fs19.readFileSync(file, "utf8"));
9502
+ if (!isRecord3(parsed))
9503
+ throw new Error(`${file} does not contain a JSON object`);
9504
+ return parsed;
9505
+ }
9506
+ function pushZcodeModels(models, options) {
9507
+ const paths = resolveZcodeClientPaths(options.env);
9508
+ const pushModels = models.filter((model) => !isZcodeModel(model.slug));
9509
+ if (pushModels.length === 0) {
9510
+ throw new Error("zcode push aborted; no models remain after excluding ZCode plugin models");
9511
+ }
9512
+ const providerDoc = readJsonFile(paths.providerConfigFile);
9513
+ const configDoc = readJsonFile(paths.configFile);
9514
+ const providerRules = providerDoc.config?.providerConfigRules?.providerRules;
9515
+ if (!Array.isArray(providerRules)) {
9516
+ throw new Error(`${paths.providerConfigFile}: config.providerConfigRules.providerRules is missing or not an array`);
9517
+ }
9518
+ const modelRules = providerDoc.config?.modelConfigRules?.providerModelRules;
9519
+ if (!Array.isArray(modelRules)) {
9520
+ throw new Error(`${paths.providerConfigFile}: config.modelConfigRules.providerModelRules is missing or not an array`);
9521
+ }
9522
+ configDoc.provider ??= {};
9523
+ const existingRule = providerRules.find((item) => isRecord3(item) && isManagedProviderName(item.providerName));
9524
+ const baseURL = `http://${options.config.host}:${options.config.port}`;
9525
+ let rule = existingRule;
9526
+ let providerId = rule && typeof rule.providerId === "string" ? rule.providerId : undefined;
9527
+ if (providerId === undefined) {
9528
+ providerId = randomUUID5();
9529
+ rule = {
9530
+ providerId,
9531
+ providerName: ZCODE_PROVIDER_DISPLAY_NAME,
9532
+ config: {
9533
+ group: ZCODE_PROVIDER_GROUP,
9534
+ access: { type: "api-key", apiKey: ZCODE_API_KEY_PLACEHOLDER },
9535
+ api: { type: "anthropic-messages", baseUrl: baseURL },
9536
+ personalModelIds: [],
9537
+ modelOrder: []
9538
+ }
9539
+ };
9540
+ providerRules.push(rule);
9541
+ if (Array.isArray(providerDoc.config?.providerOrder) && !providerDoc.config.providerOrder.includes(providerId)) {
9542
+ providerDoc.config.providerOrder.push(providerId);
9543
+ }
9544
+ } else {
9545
+ const managedRule = rule;
9546
+ if (Array.isArray(providerDoc.config?.providerOrder) && !providerDoc.config.providerOrder.includes(providerId)) {
9547
+ providerDoc.config.providerOrder.push(providerId);
9548
+ }
9549
+ if (managedRule.providerName !== ZCODE_PROVIDER_DISPLAY_NAME)
9550
+ managedRule.providerName = ZCODE_PROVIDER_DISPLAY_NAME;
9551
+ }
9552
+ if (!isRecord3(rule.config))
9553
+ throw new Error(`${paths.providerConfigFile}: managed provider rule has no config object`);
9554
+ if (rule.config.group !== ZCODE_PROVIDER_GROUP)
9555
+ rule.config.group = ZCODE_PROVIDER_GROUP;
9556
+ let entry = configDoc.provider[providerId];
9557
+ if (!isRecord3(entry)) {
9558
+ entry = {
9559
+ name: ZCODE_PROVIDER_DISPLAY_NAME,
9560
+ kind: "anthropic",
9561
+ options: { apiKey: ZCODE_API_KEY_PLACEHOLDER, apiKeyRequired: true, baseURL },
9562
+ source: "custom",
9563
+ models: {}
9564
+ };
9565
+ configDoc.provider[providerId] = entry;
9566
+ } else if (entry.name !== ZCODE_PROVIDER_DISPLAY_NAME) {
9567
+ entry.name = ZCODE_PROVIDER_DISPLAY_NAME;
9568
+ }
9569
+ const deletedModels = isRecord3(entry.zcode) ? entry.zcode.deletedModels : undefined;
9570
+ const deleted = new Set((Array.isArray(deletedModels) ? deletedModels : []).filter((item) => typeof item === "string").map((slug) => slug.toLowerCase()));
9571
+ const newSlugs = pushModels.filter((model) => !deleted.has(model.slug.toLowerCase())).map((model) => model.slug);
9572
+ if (newSlugs.length === 0) {
9573
+ throw new Error("zcode push aborted; every remaining model is hidden by ZCode deletedModels");
9574
+ }
9575
+ const newSet = new Set(newSlugs.map((slug) => slug.toLowerCase()));
9576
+ const bySlug = new Map(pushModels.map((model) => [model.slug.toLowerCase(), model]));
9577
+ const oldOrder = Array.isArray(rule.config.modelOrder) ? rule.config.modelOrder.filter((item) => typeof item === "string") : [];
9578
+ const oldSet = new Set(oldOrder.map((slug) => slug.toLowerCase()));
9579
+ const addedCount = newSlugs.filter((slug) => !oldSet.has(slug.toLowerCase())).length;
9580
+ const removedCount = oldOrder.filter((slug) => !newSet.has(slug.toLowerCase())).length;
9581
+ const unchangedCount = newSlugs.length - addedCount;
9582
+ rule.config.modelOrder = [...newSlugs];
9583
+ rule.config.personalModelIds = [...newSlugs];
9584
+ const syncedRules = newSlugs.map((slug) => {
9585
+ const model = bySlug.get(slug.toLowerCase());
9586
+ const context = modelContextWindow(model);
9587
+ return {
9588
+ modelId: slug,
9589
+ providerId,
9590
+ config: {
9591
+ properties: {
9592
+ ...context === undefined ? {} : { contextWindow: context },
9593
+ inputFormat: { supportsImage: modelInputModalities(model).includes("image") }
9594
+ }
9595
+ }
9596
+ };
9597
+ });
9598
+ providerDoc.config.modelConfigRules.providerModelRules = [
9599
+ ...modelRules.filter((item) => !(isRecord3(item) && item.providerId === providerId)),
9600
+ ...syncedRules
9601
+ ];
9602
+ const manualRules = providerDoc.config.modelConfigRules.manualProviderModelRules;
9603
+ if (Array.isArray(manualRules)) {
9604
+ providerDoc.config.modelConfigRules.manualProviderModelRules = manualRules.filter((item) => !(isRecord3(item) && item.providerId === providerId));
9605
+ }
9606
+ entry.models = Object.fromEntries(newSlugs.map((slug) => [
9607
+ slug,
9608
+ synthesizeZcodeModelDefinition(bySlug.get(slug.toLowerCase()))
9609
+ ]));
9610
+ const pendingWrites = [
9611
+ { file: paths.providerConfigFile, contents: `${JSON.stringify(providerDoc, null, 2)}
9612
+ ` },
9613
+ { file: paths.configFile, contents: `${JSON.stringify(configDoc, null, 2)}
9614
+ ` }
9615
+ ];
9616
+ const displayOrderFile = path13.join(paths.v2, "model-provider-display-order.json");
9617
+ if (fs19.existsSync(displayOrderFile)) {
9618
+ try {
9619
+ const displayOrder = JSON.parse(fs19.readFileSync(displayOrderFile, "utf8"));
9620
+ if (Array.isArray(displayOrder.providerIds) && !displayOrder.providerIds.includes(providerId)) {
9621
+ displayOrder.providerIds.push(providerId);
9622
+ displayOrder.updatedAt = Date.now();
9623
+ pendingWrites.push({ file: displayOrderFile, contents: `${JSON.stringify(displayOrder, null, 2)}
9624
+ ` });
9625
+ }
9626
+ } catch {}
9627
+ }
9628
+ const backups = writeAllOrRollback(pendingWrites);
9629
+ return {
9630
+ summary: backups.length === 0 ? `zcode: provider "${ZCODE_PROVIDER_DISPLAY_NAME}" already up to date (${newSlugs.length} models)` : `zcode: ${newSlugs.length} models pushed to provider "${ZCODE_PROVIDER_DISPLAY_NAME}"` + ` (+${addedCount} added, -${removedCount} removed, ${unchangedCount} unchanged)`,
9631
+ notes: [
9632
+ ...backups.map((backup) => `backup: ${backup}`),
9633
+ "note: the gateway /v1/messages endpoint currently converts only OpenCode Zen models end-to-end;" + " models from other sources may fail over this provider",
9634
+ "restart ZCode to refresh its model list"
9635
+ ]
9636
+ };
9637
+ }
9638
+ function createZcodePushTarget(options) {
9639
+ return {
9640
+ id: "zcode",
9641
+ assertInstalled() {
9642
+ const paths = resolveZcodeClientPaths(options.env);
9643
+ if (!fs19.existsSync(paths.configFile) || !fs19.existsSync(paths.providerConfigFile)) {
9644
+ throw new Error(`ZCode is not installed (expected ${paths.v2}/config.json and provider_config.json);` + ` install ZCode or point ZCODE_HOME at its config directory`);
9645
+ }
9646
+ },
9647
+ push(models) {
9648
+ return pushZcodeModels(models, options);
9649
+ }
9650
+ };
9651
+ }
9652
+
9653
+ // src/dsh-client-sync.ts
9360
9654
  import fs20 from "fs";
9655
+ import os4 from "os";
9361
9656
  import path14 from "path";
9657
+ import YAML, { isMap, isSeq, YAMLMap, YAMLSeq } from "yaml";
9658
+ var DSH_PROVIDER_KEY = "agent-proxy";
9659
+ var DSH_PROVIDER_DISPLAY_NAME = "AgentProxy";
9660
+ var DSH_PROVIDER_API = "anthropic-messages";
9661
+ var DSH_API_KEY_ENV = "AGENT_PROXY_API_KEY";
9662
+ var DSH_LLM_ENTRY_ID = "llm-pi-ai";
9663
+ var DSH_PATCH_FILE = "cordis.patch.yml";
9664
+ function resolveDshClientPaths(env = process.env) {
9665
+ const home = env.DSH_HOME || path14.join(env.HOME || os4.homedir(), ".dsh");
9666
+ return { home, profilesDir: path14.join(home, "profiles") };
9667
+ }
9668
+ var DSH_EFFORT_KEYS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
9669
+ function dshReasoningEfforts(efforts) {
9670
+ const mapped = {};
9671
+ for (const effort of efforts) {
9672
+ const key = effort === "none" ? "off" : effort;
9673
+ if (!DSH_EFFORT_KEYS.includes(key))
9674
+ continue;
9675
+ if (!(key in mapped))
9676
+ mapped[key] = effort;
9677
+ }
9678
+ return mapped;
9679
+ }
9680
+ function synthesizeDshModel(entry) {
9681
+ const model = {
9682
+ id: entry.slug,
9683
+ name: typeof entry.display_name === "string" && entry.display_name ? entry.display_name : entry.slug
9684
+ };
9685
+ const context = modelContextWindow(entry);
9686
+ if (context !== undefined)
9687
+ model.contextWindow = context;
9688
+ model.input = modelInputModalities(entry);
9689
+ const efforts = modelReasoningEfforts(entry);
9690
+ if (efforts.length > 0) {
9691
+ const reasoningEfforts = dshReasoningEfforts(efforts);
9692
+ if (Object.keys(reasoningEfforts).length > 0)
9693
+ model.reasoningEfforts = reasoningEfforts;
9694
+ }
9695
+ return model;
9696
+ }
9697
+ function parseDshPatch(file, profile) {
9698
+ const raw = fs20.readFileSync(file, "utf8");
9699
+ const doc = YAML.parseDocument(raw);
9700
+ if (doc.errors.length > 0) {
9701
+ const first = doc.errors[0];
9702
+ throw new Error(`${file} is not valid YAML (${first.message}); fix or restore it (see its .bak-* backups), then rerun models --push`);
9703
+ }
9704
+ if (doc.contents === null)
9705
+ doc.contents = new YAMLSeq;
9706
+ if (!isSeq(doc.contents))
9707
+ throw new Error(`${file}: expected a top-level YAML array of patch entries`);
9708
+ const documents = [];
9709
+ for (const item of doc.contents.items) {
9710
+ if (isMap(item) && item.toJS(doc).id === DSH_LLM_ENTRY_ID)
9711
+ documents.push({ file, profile, doc, entry: item });
9712
+ }
9713
+ return documents;
9714
+ }
9715
+ function pushDshModels(models, options) {
9716
+ const paths = resolveDshClientPaths(options.env);
9717
+ if (!fs20.existsSync(paths.profilesDir)) {
9718
+ throw new Error(`dsh profiles directory is missing (${paths.profilesDir}); reinstall dsh or point DSH_HOME at it`);
9719
+ }
9720
+ const profileNames = fs20.readdirSync(paths.profilesDir, { withFileTypes: true }).filter((item) => item.isDirectory()).map((item) => item.name).sort();
9721
+ const touched = [];
9722
+ for (const profile of profileNames) {
9723
+ const file = path14.join(paths.profilesDir, profile, DSH_PATCH_FILE);
9724
+ if (!fs20.existsSync(file))
9725
+ continue;
9726
+ touched.push(...parseDshPatch(file, profile));
9727
+ }
9728
+ if (touched.length === 0) {
9729
+ throw new Error(`dsh is installed but no profile has an "${DSH_LLM_ENTRY_ID}" patch entry under ${paths.profilesDir};` + " configure any custom provider inside dsh once so the patch layer exists, then rerun models --push");
9730
+ }
9731
+ const baseURL = `http://${options.config.host}:${options.config.port}`;
9732
+ const createdProfiles = [];
9733
+ const pendingWrites = [];
9734
+ for (const { file, profile, doc, entry } of touched) {
9735
+ const before = doc.toString();
9736
+ let config;
9737
+ const rawConfig = entry.get("config");
9738
+ if (isMap(rawConfig))
9739
+ config = rawConfig;
9740
+ else {
9741
+ config = new YAMLMap;
9742
+ entry.set("config", config);
9743
+ }
9744
+ let providers;
9745
+ const rawProviders = config.get("providers");
9746
+ if (isMap(rawProviders))
9747
+ providers = rawProviders;
9748
+ else {
9749
+ providers = new YAMLMap;
9750
+ config.set("providers", providers);
9751
+ }
9752
+ let provider;
9753
+ const rawProvider = providers.get(DSH_PROVIDER_KEY);
9754
+ if (isMap(rawProvider))
9755
+ provider = rawProvider;
9756
+ else {
9757
+ provider = doc.createNode({
9758
+ displayName: DSH_PROVIDER_DISPLAY_NAME,
9759
+ apiKeyEnv: DSH_API_KEY_ENV,
9760
+ api: DSH_PROVIDER_API,
9761
+ baseURL
9762
+ });
9763
+ providers.set(DSH_PROVIDER_KEY, provider);
9764
+ createdProfiles.push(profile);
9765
+ }
9766
+ provider.set("models", doc.createNode(models.map(synthesizeDshModel)));
9767
+ const after = doc.toString();
9768
+ if (after === before)
9769
+ continue;
9770
+ pendingWrites.push({ file, contents: after });
9771
+ }
9772
+ const backups = writeAllOrRollback(pendingWrites);
9773
+ const managedProfiles = [...new Set(touched.map((item) => item.profile))].join(", ");
9774
+ return {
9775
+ summary: backups.length === 0 ? `dsh: provider "${DSH_PROVIDER_KEY}" already up to date (${models.length} models, profiles: ${managedProfiles})` : `dsh: ${models.length} models pushed to provider "${DSH_PROVIDER_KEY}"` + ` (${DSH_PROVIDER_DISPLAY_NAME}, ${DSH_PROVIDER_API}; profiles: ${managedProfiles})`,
9776
+ notes: [
9777
+ ...createdProfiles.length > 0 ? [`provider "${DSH_PROVIDER_KEY}" created in: ${createdProfiles.join(", ")}` + ` (apiKey is a placeholder; the gateway ignores client keys \u2014 its plugins authorize upstreams themselves)`] : [],
9778
+ ...backups.map((backup) => `backup: ${backup}`),
9779
+ "note: the gateway /v1/messages endpoint currently converts only OpenCode Zen models end-to-end;" + " models from other sources may fail over this provider",
9780
+ "restart DeepSeek Harness (dsh) to refresh its model list"
9781
+ ]
9782
+ };
9783
+ }
9784
+ function createDshPushTarget(options) {
9785
+ return {
9786
+ id: "dsh",
9787
+ assertInstalled() {
9788
+ const paths = resolveDshClientPaths(options.env);
9789
+ if (!fs20.existsSync(paths.home)) {
9790
+ throw new Error(`dsh (DeepSeek Harness) is not installed (no ${paths.home}); install dsh or point DSH_HOME at it`);
9791
+ }
9792
+ },
9793
+ push(models) {
9794
+ return pushDshModels(models, options);
9795
+ }
9796
+ };
9797
+ }
9798
+ // src/webui.ts
9799
+ import fs23 from "fs";
9800
+ import path16 from "path";
9362
9801
  import { randomBytes } from "crypto";
9363
9802
 
9364
9803
  // src/app-server.ts
9365
- import fs18 from "fs";
9366
- import path13 from "path";
9804
+ import fs21 from "fs";
9805
+ import path15 from "path";
9367
9806
  import { execFileSync as execFileSync4 } from "child_process";
9368
9807
  import { dlopen } from "bun:ffi";
9369
9808
  var GLOBAL_OPTIONS_WITH_VALUE = new Set([
@@ -9524,22 +9963,22 @@ function listLinuxProcesses() {
9524
9963
  const uid = process.getuid?.();
9525
9964
  if (uid === undefined)
9526
9965
  throw new Error("Current user ID is unavailable");
9527
- const entries = fs18.readdirSync("/proc", { withFileTypes: true });
9966
+ const entries = fs21.readdirSync("/proc", { withFileTypes: true });
9528
9967
  const processes = [];
9529
9968
  for (const entry of entries) {
9530
9969
  if (!entry.isDirectory() || !/^\d+$/.test(entry.name))
9531
9970
  continue;
9532
9971
  const root = `/proc/${entry.name}`;
9533
9972
  try {
9534
- const status = fs18.readFileSync(`${root}/status`, "utf8");
9973
+ const status = fs21.readFileSync(`${root}/status`, "utf8");
9535
9974
  const owner = status.match(/^Uid:\s+(\d+)/m)?.[1];
9536
9975
  if (owner !== String(uid))
9537
9976
  continue;
9538
- const executable = fs18.readlinkSync(`${root}/exe`);
9977
+ const executable = fs21.readlinkSync(`${root}/exe`);
9539
9978
  if (!isCodexExecutable(executable) && !/^codex-code-mode-host(?:\.exe)?$/i.test(executableName(executable)))
9540
9979
  continue;
9541
- const args = fs18.readFileSync(`${root}/cmdline`).toString("utf8").split("\x00").filter(Boolean);
9542
- const stat = fs18.readFileSync(`${root}/stat`, "utf8");
9980
+ const args = fs21.readFileSync(`${root}/cmdline`).toString("utf8").split("\x00").filter(Boolean);
9981
+ const stat = fs21.readFileSync(`${root}/stat`, "utf8");
9543
9982
  const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
9544
9983
  const startedAt = fields[19];
9545
9984
  if (!startedAt || args.length === 0)
@@ -9584,9 +10023,9 @@ function parseWindowsCommandLine(commandLine) {
9584
10023
  }
9585
10024
  function windowsSystemPath(...segments) {
9586
10025
  const systemRoot = process.env.SystemRoot || process.env.windir;
9587
- if (!systemRoot || !path13.win32.isAbsolute(systemRoot))
10026
+ if (!systemRoot || !path15.win32.isAbsolute(systemRoot))
9588
10027
  throw new Error("Windows system root is unavailable");
9589
- return path13.win32.join(systemRoot, ...segments);
10028
+ return path15.win32.join(systemRoot, ...segments);
9590
10029
  }
9591
10030
  function listWindowsProcesses() {
9592
10031
  const powershell = windowsSystemPath("System32", "WindowsPowerShell", "v1.0", "powershell.exe");
@@ -9638,7 +10077,7 @@ function terminateSystemProcess(pid) {
9638
10077
  }
9639
10078
  try {
9640
10079
  const taskkill = windowsSystemPath("System32", "taskkill.exe");
9641
- if (!fs18.existsSync(taskkill))
10080
+ if (!fs21.existsSync(taskkill))
9642
10081
  throw new Error("taskkill.exe not found");
9643
10082
  execFileSync4(taskkill, ["/PID", String(pid), "/T", "/F"], { stdio: "ignore" });
9644
10083
  } catch {
@@ -9713,9 +10152,9 @@ async function stopCodexAppServers(runtime = SYSTEM_RUNTIME) {
9713
10152
  }
9714
10153
 
9715
10154
  // src/config-update.ts
9716
- import fs19 from "fs";
10155
+ import fs22 from "fs";
9717
10156
  import bytes from "bytes";
9718
- import { isZcodeModel, zcodeStaticModelPrefixes } from "@agent-proxy/zcode/catalog";
10157
+ import { isZcodeModel as isZcodeModel2, zcodeStaticModelPrefixes } from "@agent-proxy/zcode/catalog";
9719
10158
  import { isCodebuddyAccountValue } from "@agent-proxy/codebuddy/credentials";
9720
10159
  import { CODEBUDDY_PREFIX, isCodebuddyModel, WORKBUDDY_PREFIX, codebuddyModelPrefixes } from "@agent-proxy/codebuddy/catalog";
9721
10160
  import { isQoderModel, QODER_CN_PREFIX, QODER_INTL_PREFIX } from "@agent-proxy/qoder/catalog";
@@ -9904,7 +10343,7 @@ function expandExcludedModelGroups(groupsInput, definitions) {
9904
10343
  return normalizeExcludedModels(rules);
9905
10344
  }
9906
10345
  function applySelectedModelsPatch(paths, selectedModels, syncState = true) {
9907
- if (!fs19.existsSync(paths.gatewayConfig))
10346
+ if (!fs22.existsSync(paths.gatewayConfig))
9908
10347
  throw new Error("Gateway is not installed");
9909
10348
  const config = readGatewayConfigFile(paths.gatewayConfig);
9910
10349
  const before = Array.isArray(config.selectedModels) ? config.selectedModels : [];
@@ -9914,8 +10353,8 @@ function applySelectedModelsPatch(paths, selectedModels, syncState = true) {
9914
10353
  config.selectedModels = selectedModels;
9915
10354
  validateConfiguredPlugins(config);
9916
10355
  writeGatewayConfigFile(paths.gatewayConfig, config);
9917
- if (syncState && fs19.existsSync(paths.stateFile)) {
9918
- const state = JSON.parse(fs19.readFileSync(paths.stateFile, "utf8"));
10356
+ if (syncState && fs22.existsSync(paths.stateFile)) {
10357
+ const state = JSON.parse(fs22.readFileSync(paths.stateFile, "utf8"));
9919
10358
  state.config = config;
9920
10359
  atomicWrite(paths.stateFile, `${JSON.stringify(state, null, 2)}
9921
10360
  `);
@@ -9932,7 +10371,7 @@ function applyWebUiConfigPatch(paths, patch, syncState = true, { write = true }
9932
10371
  if (!SUPPORTED_PATCH_FIELDS.has(key))
9933
10372
  throw new Error(`Unsupported field: ${key}`);
9934
10373
  }
9935
- if (!fs19.existsSync(paths.gatewayConfig))
10374
+ if (!fs22.existsSync(paths.gatewayConfig))
9936
10375
  throw new Error("Gateway is not installed");
9937
10376
  const config = readGatewayConfigFile(paths.gatewayConfig);
9938
10377
  const before = config;
@@ -10033,8 +10472,8 @@ function applyWebUiConfigPatch(paths, patch, syncState = true, { write = true }
10033
10472
  if (!write)
10034
10473
  return { config, applied };
10035
10474
  writeGatewayConfigFile(paths.gatewayConfig, config);
10036
- if (syncState && fs19.existsSync(paths.stateFile)) {
10037
- const state = JSON.parse(fs19.readFileSync(paths.stateFile, "utf8"));
10475
+ if (syncState && fs22.existsSync(paths.stateFile)) {
10476
+ const state = JSON.parse(fs22.readFileSync(paths.stateFile, "utf8"));
10038
10477
  state.config = config;
10039
10478
  atomicWrite(paths.stateFile, `${JSON.stringify(state, null, 2)}
10040
10479
  `);
@@ -10043,20 +10482,20 @@ function applyWebUiConfigPatch(paths, patch, syncState = true, { write = true }
10043
10482
  return { config, applied };
10044
10483
  }
10045
10484
  function markPendingRestart(stateFile) {
10046
- if (!fs19.existsSync(stateFile))
10485
+ if (!fs22.existsSync(stateFile))
10047
10486
  return;
10048
10487
  try {
10049
- const state = JSON.parse(fs19.readFileSync(stateFile, "utf8"));
10488
+ const state = JSON.parse(fs22.readFileSync(stateFile, "utf8"));
10050
10489
  state.pendingRestart = true;
10051
10490
  atomicWrite(stateFile, `${JSON.stringify(state, null, 2)}
10052
10491
  `);
10053
10492
  } catch {}
10054
10493
  }
10055
10494
  function clearPendingRestart(stateFile) {
10056
- if (!fs19.existsSync(stateFile))
10495
+ if (!fs22.existsSync(stateFile))
10057
10496
  return;
10058
10497
  try {
10059
- const state = JSON.parse(fs19.readFileSync(stateFile, "utf8"));
10498
+ const state = JSON.parse(fs22.readFileSync(stateFile, "utf8"));
10060
10499
  if (state.pendingRestart !== true)
10061
10500
  return;
10062
10501
  state.pendingRestart = false;
@@ -10082,14 +10521,14 @@ var REQUEST_LOG_TAIL_BYTES = 64 * 1024;
10082
10521
  var REQUEST_LOG_DEFAULT_PAGE_SIZE = 100;
10083
10522
  var REQUEST_LOG_MAX_PAGE_SIZE = 500;
10084
10523
  var RESTART_DELAY_MS = 400;
10085
- var UI_HTML_PATH = path14.resolve(import.meta.dir, "../dist/ui/index.html");
10524
+ var UI_HTML_PATH = path16.resolve(import.meta.dir, "../dist/ui/index.html");
10086
10525
  function webUiContextForInstance(configPath, defaultPaths) {
10087
10526
  const resolved = realPathOrResolve(configPath);
10088
10527
  if (resolved === realPathOrResolve(defaultPaths.gatewayConfig))
10089
10528
  return { paths: defaultPaths };
10090
10529
  return {
10091
10530
  paths: {
10092
- ...resolvePaths({ HOME: defaultPaths.home, CODEX_HOME: defaultPaths.codexHome }, path14.dirname(resolved)),
10531
+ ...resolvePaths({ HOME: defaultPaths.home, CODEX_HOME: defaultPaths.codexHome }, path16.dirname(resolved)),
10093
10532
  gatewayConfig: resolved
10094
10533
  },
10095
10534
  instanceOnly: true
@@ -10100,7 +10539,7 @@ function isLoopbackHost(host) {
10100
10539
  }
10101
10540
  function ensureUiToken(file) {
10102
10541
  try {
10103
- const existing = fs20.readFileSync(file, "utf8").trim();
10542
+ const existing = fs23.readFileSync(file, "utf8").trim();
10104
10543
  if (existing)
10105
10544
  return existing;
10106
10545
  } catch {}
@@ -10111,7 +10550,7 @@ function ensureUiToken(file) {
10111
10550
  }
10112
10551
  function readUiToken(file) {
10113
10552
  try {
10114
- const value = fs20.readFileSync(file, "utf8").trim();
10553
+ const value = fs23.readFileSync(file, "utf8").trim();
10115
10554
  return value || null;
10116
10555
  } catch {
10117
10556
  return null;
@@ -10127,22 +10566,22 @@ var TAIL_EXPAND_CAP_BYTES = 1024 * 1024;
10127
10566
  function tailFile(file, maxBytes) {
10128
10567
  let stat;
10129
10568
  try {
10130
- stat = fs20.statSync(file);
10569
+ stat = fs23.statSync(file);
10131
10570
  } catch {
10132
10571
  return null;
10133
10572
  }
10134
10573
  try {
10135
10574
  if (stat.size <= maxBytes) {
10136
- return { text: fs20.readFileSync(file, "utf8"), truncated: false };
10575
+ return { text: fs23.readFileSync(file, "utf8"), truncated: false };
10137
10576
  }
10138
- const fd = fs20.openSync(file, "r");
10577
+ const fd = fs23.openSync(file, "r");
10139
10578
  try {
10140
10579
  let window = maxBytes;
10141
10580
  for (;; ) {
10142
10581
  const start = Math.max(0, stat.size - window);
10143
10582
  const length = stat.size - start;
10144
10583
  const buffer = Buffer.alloc(length);
10145
- fs20.readSync(fd, buffer, 0, length, start);
10584
+ fs23.readSync(fd, buffer, 0, length, start);
10146
10585
  const text = buffer.toString("utf8");
10147
10586
  const firstNewline = text.indexOf(`
10148
10587
  `);
@@ -10164,7 +10603,7 @@ function tailFile(file, maxBytes) {
10164
10603
  window = Math.min(stat.size, window * 2);
10165
10604
  }
10166
10605
  } finally {
10167
- fs20.closeSync(fd);
10606
+ fs23.closeSync(fd);
10168
10607
  }
10169
10608
  } catch {
10170
10609
  return null;
@@ -10200,7 +10639,7 @@ function uiHtmlResponse(ctx) {
10200
10639
  const uiHtmlPath = ctx.uiHtmlPath ?? UI_HTML_PATH;
10201
10640
  let html;
10202
10641
  try {
10203
- html = fs20.readFileSync(uiHtmlPath, "utf8");
10642
+ html = fs23.readFileSync(uiHtmlPath, "utf8");
10204
10643
  } catch {
10205
10644
  return Response.json({
10206
10645
  error: {
@@ -10231,7 +10670,7 @@ function faviconResponse() {
10231
10670
  }
10232
10671
  function readCodexConfigManaged(stateFile) {
10233
10672
  try {
10234
- const state = JSON.parse(fs20.readFileSync(stateFile, "utf8"));
10673
+ const state = JSON.parse(fs23.readFileSync(stateFile, "utf8"));
10235
10674
  return state.codexConfigManaged !== false;
10236
10675
  } catch {
10237
10676
  return true;
@@ -10239,10 +10678,10 @@ function readCodexConfigManaged(stateFile) {
10239
10678
  }
10240
10679
  function manualCodexConfigGuidance(paths, config) {
10241
10680
  const gatewayBaseUrl = `http://${config.host}:${config.port}${MOUNT_PATH}`;
10242
- const source = fs20.existsSync(paths.configToml) ? fs20.readFileSync(paths.configToml, "utf8") : "";
10681
+ const source = fs23.existsSync(paths.configToml) ? fs23.readFileSync(paths.configToml, "utf8") : "";
10243
10682
  let staticCatalogAvailable = false;
10244
10683
  try {
10245
- const catalog = JSON.parse(fs20.readFileSync(codexCatalogFile(paths), "utf8"));
10684
+ const catalog = JSON.parse(fs23.readFileSync(codexCatalogFile(paths), "utf8"));
10246
10685
  staticCatalogAvailable = Array.isArray(catalog.models) && catalog.models.length > 0;
10247
10686
  } catch {}
10248
10687
  const staticCatalogActive = config.upstreamOnly === true && staticCatalogAvailable;
@@ -10329,7 +10768,7 @@ function configResponse(paths, providerDeps, instanceOnly = false) {
10329
10768
  excludedOther
10330
10769
  },
10331
10770
  detected: {
10332
- zcode: zcodeConfigPresent(providerDeps?.zcodeHome ?? path14.join(paths.home, ".zcode")),
10771
+ zcode: zcodeConfigPresent(providerDeps?.zcodeHome ?? path16.join(paths.home, ".zcode")),
10333
10772
  codebuddy: codebuddyCredentialsPresent(codebuddyAuthDir),
10334
10773
  codebuddyAccountLabel: accountLabel ?? null,
10335
10774
  codebuddyAccounts,
@@ -10384,13 +10823,13 @@ function requestLogsResponse(config, paths, url) {
10384
10823
  const dir = requestLogDir(paths);
10385
10824
  let names;
10386
10825
  try {
10387
- names = fs20.readdirSync(dir);
10826
+ names = fs23.readdirSync(dir);
10388
10827
  } catch {
10389
10828
  return Response.json({ files: [], total: 0, offset: 0, limit: REQUEST_LOG_DEFAULT_PAGE_SIZE, logging });
10390
10829
  }
10391
10830
  const all = names.filter((name) => isRequestLogName(name)).flatMap((name) => {
10392
10831
  try {
10393
- const stat = fs20.statSync(path14.join(dir, name));
10832
+ const stat = fs23.statSync(path16.join(dir, name));
10394
10833
  return [{
10395
10834
  name,
10396
10835
  size: stat.size,
@@ -10448,8 +10887,8 @@ function modelSaveRollback(paths, config) {
10448
10887
  codexCatalogFile(paths),
10449
10888
  paths.configToml
10450
10889
  ])];
10451
- const previous = files.map((file) => ({ file, source: fs20.existsSync(file) ? fs20.readFileSync(file) : undefined }));
10452
- const readToml = () => fs20.existsSync(paths.configToml) ? fs20.readFileSync(paths.configToml, "utf8") : undefined;
10890
+ const previous = files.map((file) => ({ file, source: fs23.existsSync(file) ? fs23.readFileSync(file) : undefined }));
10891
+ const readToml = () => fs23.existsSync(paths.configToml) ? fs23.readFileSync(paths.configToml, "utf8") : undefined;
10453
10892
  let expectedToml = readToml();
10454
10893
  const restore = () => {
10455
10894
  for (const { file, source } of previous) {
@@ -10457,8 +10896,8 @@ function modelSaveRollback(paths, config) {
10457
10896
  continue;
10458
10897
  if (source)
10459
10898
  atomicWrite(file, source.toString("utf8"));
10460
- else if (fs20.existsSync(file))
10461
- fs20.unlinkSync(file);
10899
+ else if (fs23.existsSync(file))
10900
+ fs23.unlinkSync(file);
10462
10901
  }
10463
10902
  logConfigChange(paths.stdoutLog, {
10464
10903
  command: "webui model rollback",
@@ -10579,7 +11018,7 @@ async function handleWebUiRequestCore(request, config, ctx, port) {
10579
11018
  }
10580
11019
  const structural = applied.some((change) => change.field !== "excludedModels");
10581
11020
  const modelsChanged = selection !== undefined || applied.some((change) => change.field === "excludedModels" || change.field === "codebuddyAccount" || next.upstreamOnly === true && ["zcode", "codebuddy", "qoder", "agy", "opencodeZen"].includes(change.field));
10582
- const restarting = structural && managesService && fs20.existsSync(ctx.paths.launchAgent);
11021
+ const restarting = structural && managesService && fs23.existsSync(ctx.paths.launchAgent);
10583
11022
  if (restarting) {
10584
11023
  markPendingRestart(ctx.paths.stateFile);
10585
11024
  (ctx.scheduleRestart ?? defaultScheduleRestart)(ctx.paths);
@@ -10736,7 +11175,7 @@ Usage:
10736
11175
  agent-proxy start|stop
10737
11176
  agent-proxy restart [--restart-codex]
10738
11177
  agent-proxy serve [--config PATH]
10739
- agent-proxy models [--sync] [--select SELECTOR] [--exclude [PATTERNS]] [--restart-codex]
11178
+ agent-proxy models [--sync] [--select SELECTOR] [--exclude [PATTERNS]] [--push [TARGETS]] [--restart-codex]
10740
11179
  agent-proxy config [--zcode on|off] [--codebuddy on|off] [--qoder on|off] [--agy on|off] [--opencode-zen on|off] [--log on|off] [--debug on|off] [--max-request-logs N] [--max-log-size SIZE]
10741
11180
  agent-proxy codebuddy --switch
10742
11181
  agent-proxy web
@@ -10791,6 +11230,19 @@ Models:
10791
11230
  compatibility model (space toggles exclusion, enter
10792
11231
  saves)
10793
11232
  --model-merge-json URL update the cached models.json override
11233
+ --push [TARGETS] push the local agent plugin models into client tools;
11234
+ a checkbox picker confirms the list first (every model
11235
+ preselected \u2014 uncheck what you do not want and press
11236
+ Enter; requires a terminal) and the managed
11237
+ provider's model list is then fully replaced by ID
11238
+ (manual additions inside the managed provider are
11239
+ removed); upstream cliproxy/newapi and official models
11240
+ are not pushed (authorization); the zcode plugin's own
11241
+ models are never pushed to zcode; each tool gets a
11242
+ managed "agent-proxy" provider created on first push;
11243
+ targets: zcode, dsh; without a value every target is
11244
+ pushed; each modified file is backed up first; always
11245
+ runs after --sync/--exclude
10794
11246
  --restart-codex stop Codex app-server after sync or exclusion changes
10795
11247
  to refresh the model picker; active tasks may error
10796
11248
  and require recovery or reopening
@@ -10873,6 +11325,15 @@ function parseArgs(args) {
10873
11325
  }
10874
11326
  continue;
10875
11327
  }
11328
+ if (key === "push") {
11329
+ if (next === undefined || next.startsWith("--"))
11330
+ options.push = true;
11331
+ else {
11332
+ options.push = next;
11333
+ i += 1;
11334
+ }
11335
+ continue;
11336
+ }
10876
11337
  if (next === undefined || next.startsWith("--"))
10877
11338
  throw new Error(`--${key} requires a value`);
10878
11339
  options[key] = next;
@@ -10905,7 +11366,7 @@ function confirmInstallOverwrite() {
10905
11366
  let bytes3 = 0;
10906
11367
  try {
10907
11368
  while (bytes3 < buffer.length) {
10908
- const read = fs21.readSync(0, buffer, bytes3, buffer.length - bytes3, null);
11369
+ const read = fs24.readSync(0, buffer, bytes3, buffer.length - bytes3, null);
10909
11370
  if (read <= 0)
10910
11371
  break;
10911
11372
  bytes3 += read;
@@ -10923,7 +11384,7 @@ function promptModifyCodexConfig() {
10923
11384
  let bytes3 = 0;
10924
11385
  try {
10925
11386
  while (bytes3 < buffer.length) {
10926
- const read = fs21.readSync(0, buffer, bytes3, buffer.length - bytes3, null);
11387
+ const read = fs24.readSync(0, buffer, bytes3, buffer.length - bytes3, null);
10927
11388
  if (read <= 0)
10928
11389
  break;
10929
11390
  bytes3 += read;
@@ -11063,13 +11524,13 @@ function timestamp() {
11063
11524
  return new Date().toISOString().replace(/[-:]/g, "").slice(0, 15).replace("T", "");
11064
11525
  }
11065
11526
  function backupConfig(file) {
11066
- fs21.mkdirSync(path15.dirname(file), { recursive: true });
11067
- const existed = fs21.existsSync(file);
11527
+ fs24.mkdirSync(path17.dirname(file), { recursive: true });
11528
+ const existed = fs24.existsSync(file);
11068
11529
  const backup = `${file}.bak-cliproxy-gateway-${timestamp()}`;
11069
11530
  if (existed)
11070
- fs21.copyFileSync(file, backup);
11531
+ fs24.copyFileSync(file, backup);
11071
11532
  else
11072
- fs21.writeFileSync(backup, "", { mode: 384 });
11533
+ fs24.writeFileSync(backup, "", { mode: 384 });
11073
11534
  return { existed, backup };
11074
11535
  }
11075
11536
  function hash2(contents) {
@@ -11077,10 +11538,10 @@ function hash2(contents) {
11077
11538
  }
11078
11539
  function restoreBackup(file, record) {
11079
11540
  if (record.existed) {
11080
- fs21.mkdirSync(path15.dirname(file), { recursive: true });
11081
- fs21.copyFileSync(record.backup, file);
11541
+ fs24.mkdirSync(path17.dirname(file), { recursive: true });
11542
+ fs24.copyFileSync(record.backup, file);
11082
11543
  } else {
11083
- fs21.rmSync(file, { force: true });
11544
+ fs24.rmSync(file, { force: true });
11084
11545
  }
11085
11546
  }
11086
11547
  function restoreManagedCodexToml(paths, state, currentToml) {
@@ -11090,12 +11551,12 @@ function restoreManagedCodexToml(paths, state, currentToml) {
11090
11551
  if (state.installedConfigHash !== undefined && hash2(currentToml) === state.installedConfigHash) {
11091
11552
  restoreBackup(paths.configToml, state.configBackup);
11092
11553
  } else {
11093
- const backupToml = fs21.readFileSync(state.configBackup.backup, "utf8");
11554
+ const backupToml = fs24.readFileSync(state.configBackup.backup, "utf8");
11094
11555
  atomicWrite(paths.configToml, restoreRootTomlKeys(currentToml, backupToml, MANAGED_CONFIG_KEYS));
11095
11556
  }
11096
11557
  }
11097
11558
  function loadJson(file) {
11098
- return JSON.parse(fs21.readFileSync(file, "utf8"));
11559
+ return JSON.parse(fs24.readFileSync(file, "utf8"));
11099
11560
  }
11100
11561
  function writeJson(file, value) {
11101
11562
  atomicWrite(file, `${JSON.stringify(value, null, 2)}
@@ -11140,13 +11601,13 @@ function removeManagedRuntimeFiles(paths, options = {}) {
11140
11601
  ...options.preserveGatewayConfig ? [] : [paths.gatewayConfig],
11141
11602
  ...options.preserveState ? [] : [paths.stateFile],
11142
11603
  ...managedRuntimeCatalogFiles(paths),
11143
- path15.join(paths.runtimeHome, "catalog-metadata.json"),
11604
+ path17.join(paths.runtimeHome, "catalog-metadata.json"),
11144
11605
  paths.modelMergeFile,
11145
11606
  paths.stdoutLog,
11146
- path15.join(paths.runtimeHome, "webui.log"),
11147
- path15.join(paths.runtimeHome, LEGACY_STDERR_LOG)
11607
+ path17.join(paths.runtimeHome, "webui.log"),
11608
+ path17.join(paths.runtimeHome, LEGACY_STDERR_LOG)
11148
11609
  ]) {
11149
- fs21.rmSync(file, { force: true });
11610
+ fs24.rmSync(file, { force: true });
11150
11611
  }
11151
11612
  }
11152
11613
  async function waitForHealth(url, attempts = 100, expectedMarker = instanceMarker(resolvePaths().runtimeHome)) {
@@ -11221,7 +11682,7 @@ function recordConfigAudit(command, config, before, paths, extraChanges = []) {
11221
11682
  }
11222
11683
  async function restartGatewayOnce(paths, config) {
11223
11684
  const markPending = (pending) => {
11224
- if (!fs21.existsSync(paths.stateFile))
11685
+ if (!fs24.existsSync(paths.stateFile))
11225
11686
  return;
11226
11687
  const state = loadJson(paths.stateFile);
11227
11688
  state.pendingRestart = pending;
@@ -11311,9 +11772,9 @@ async function installCurrent(options, completeMigration) {
11311
11772
  requireMacOS();
11312
11773
  requireBun();
11313
11774
  const requestedUpstreamOnly = upstreamOnlyOption(options);
11314
- const previousConfig = fs21.existsSync(resolvePaths().gatewayConfig) ? loadGatewayConfig(resolvePaths().gatewayConfig) : undefined;
11775
+ const previousConfig = fs24.existsSync(resolvePaths().gatewayConfig) ? loadGatewayConfig(resolvePaths().gatewayConfig) : undefined;
11315
11776
  const upstreamOnly = options["upstream-only"] !== undefined || options["cpa-only"] !== undefined ? requestedUpstreamOnly : previousConfig?.upstreamOnly ?? requestedUpstreamOnly;
11316
- const priorInstallState = fs21.existsSync(resolvePaths().stateFile) ? loadJson(resolvePaths().stateFile) : undefined;
11777
+ const priorInstallState = fs24.existsSync(resolvePaths().stateFile) ? loadJson(resolvePaths().stateFile) : undefined;
11317
11778
  let manualCodexConfig = options["manual-codex-config"] === true || priorInstallState?.codexConfigManaged === false;
11318
11779
  if (options["manual-codex-config"] === true && upstreamOnly && resolveCodexConfigConflict({ skipPrompt: options.yes === true }) === "managed") {
11319
11780
  manualCodexConfig = false;
@@ -11321,15 +11782,15 @@ async function installCurrent(options, completeMigration) {
11321
11782
  }
11322
11783
  const paths = resolvePaths();
11323
11784
  assertCodexHomeAvailable(paths, manualCodexConfig);
11324
- const switching = fs21.existsSync(paths.stateFile);
11785
+ const switching = fs24.existsSync(paths.stateFile);
11325
11786
  const portValue = stringOption(options, "port");
11326
11787
  const port = portValue ? Number(portValue) : undefined;
11327
11788
  if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535)) {
11328
11789
  throw new Error("Invalid port");
11329
11790
  }
11330
- const currentToml = fs21.existsSync(paths.configToml) ? fs21.readFileSync(paths.configToml, "utf8") : "";
11791
+ const currentToml = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
11331
11792
  const modelMergeJson = stringOption(options, "model-merge-json");
11332
- const previousGatewayConfig = fs21.existsSync(paths.gatewayConfig) ? fs21.readFileSync(paths.gatewayConfig, "utf8") : undefined;
11793
+ const previousGatewayConfig = fs24.existsSync(paths.gatewayConfig) ? fs24.readFileSync(paths.gatewayConfig, "utf8") : undefined;
11333
11794
  const currentGatewayConfig = previousGatewayConfig === undefined ? {} : loadGatewayConfig(paths.gatewayConfig);
11334
11795
  const overrides = {};
11335
11796
  if (port !== undefined)
@@ -11373,7 +11834,7 @@ async function installCurrent(options, completeMigration) {
11373
11834
  });
11374
11835
  const { modelsConfigFile, rules } = await loadModelOverrideRules(paths, config, Boolean(modelMergeJson));
11375
11836
  const explicitSelect = stringOption(options, "select") !== undefined;
11376
- const reuseCatalog = switching && !explicitSelect && !Boolean(modelMergeJson) && fs21.existsSync(configuredCatalogFile(paths, config));
11837
+ const reuseCatalog = switching && !explicitSelect && !Boolean(modelMergeJson) && fs24.existsSync(configuredCatalogFile(paths, config));
11377
11838
  let selectedModels;
11378
11839
  const selectNone = isSelectNone(installSelector);
11379
11840
  const proxyCatalog = selectNone ? { models: [] } : await fetchUpstreamCatalog(config.upstreamBaseUrl, apiKey, configuredUpstreamType(config), upstreamClientVersion(paths, configuredUpstreamType(config)), newapiCatalogOptions(configuredUpstreamType(config), rules));
@@ -11396,14 +11857,14 @@ async function installCurrent(options, completeMigration) {
11396
11857
  }
11397
11858
  config.selectedModels = selectedModels;
11398
11859
  const { patchedToml: modelCatalogToml } = manualCodexConfig ? { patchedToml: currentToml } : applyModelCatalogToml(currentToml, upstreamOnly, paths, codexCatalogFile(paths), managedRuntimeCatalogFiles(paths));
11399
- const previousState = switching ? fs21.readFileSync(paths.stateFile, "utf8") : undefined;
11860
+ const previousState = switching ? fs24.readFileSync(paths.stateFile, "utf8") : undefined;
11400
11861
  const priorState = switching ? loadJson(paths.stateFile) : undefined;
11401
11862
  const previousManaged = priorState ? isCodexConfigManaged(priorState) && priorState.uninstallClientRestored !== true : true;
11402
11863
  let configBackup;
11403
11864
  let rollbackCodexClaim;
11404
11865
  let installationTouched = false;
11405
11866
  let launchInstalled = false;
11406
- const previousPlist = fs21.existsSync(paths.launchAgent) ? fs21.readFileSync(paths.launchAgent, "utf8") : undefined;
11867
+ const previousPlist = fs24.existsSync(paths.launchAgent) ? fs24.readFileSync(paths.launchAgent, "utf8") : undefined;
11407
11868
  const previousServiceLoaded = previousPlist !== undefined && launchAgentStatus(gatewayServiceLabel(paths.instanceSuffix)) !== null;
11408
11869
  let launchTouched = false;
11409
11870
  let tomlUnchanged = false;
@@ -11475,7 +11936,7 @@ async function installCurrent(options, completeMigration) {
11475
11936
  }
11476
11937
  writeJson(paths.stateFile, state);
11477
11938
  }
11478
- const cliPath = fs21.realpathSync(process.argv[1]);
11939
+ const cliPath = fs24.realpathSync(process.argv[1]);
11479
11940
  launchTouched = true;
11480
11941
  installLaunchAgent({
11481
11942
  bunPath: process.execPath,
@@ -11571,7 +12032,7 @@ async function installCurrent(options, completeMigration) {
11571
12032
  }
11572
12033
  console.log("Codex ChatGPT OAuth was not modified.");
11573
12034
  if (manualCodexConfig) {
11574
- const currentTomlAfterInstall = fs21.existsSync(paths.configToml) ? fs21.readFileSync(paths.configToml, "utf8") : "";
12035
+ const currentTomlAfterInstall = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
11575
12036
  const configuredCatalog = readRootTomlString(currentTomlAfterInstall, "model_catalog_json");
11576
12037
  const staticCatalogActive = upstreamOnly;
11577
12038
  console.log("WARNING: --manual-codex-config keeps ~/.codex/config.toml untouched. Configure it manually:");
@@ -11600,7 +12061,7 @@ async function installCurrent(options, completeMigration) {
11600
12061
  async function uninstall(options) {
11601
12062
  requireMacOS();
11602
12063
  const paths = resolvePaths();
11603
- if (!fs21.existsSync(paths.stateFile))
12064
+ if (!fs24.existsSync(paths.stateFile))
11604
12065
  throw new Error("No managed installation found");
11605
12066
  const state = loadJson(paths.stateFile);
11606
12067
  const restoresClient = isCodexConfigManaged(state) && state.uninstallClientRestored !== true;
@@ -11608,12 +12069,12 @@ async function uninstall(options) {
11608
12069
  assertCodexHomeAvailable(paths, false);
11609
12070
  uninstallLaunchAgent(paths.launchAgent);
11610
12071
  uninstallLaunchAgent(paths.webUiLaunchAgent);
11611
- const legacyCatalogFile = path15.join(paths.codexHome, "cliproxy-catalog.json");
12072
+ const legacyCatalogFile = path17.join(paths.codexHome, "cliproxy-catalog.json");
11612
12073
  if (restoresClient) {
11613
- const currentToml = fs21.existsSync(paths.configToml) ? fs21.readFileSync(paths.configToml, "utf8") : "";
12074
+ const currentToml = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
11614
12075
  restoreManagedCodexToml(paths, state, currentToml);
11615
12076
  if (readRootTomlString(currentToml, "model_catalog_json") === legacyCatalogFile) {
11616
- fs21.rmSync(legacyCatalogFile, { force: true });
12077
+ fs24.rmSync(legacyCatalogFile, { force: true });
11617
12078
  }
11618
12079
  invalidateModelsCache(paths.modelsCacheFile);
11619
12080
  state.uninstallClientRestored = true;
@@ -11622,7 +12083,7 @@ async function uninstall(options) {
11622
12083
  deleteApiKey();
11623
12084
  removeManagedRuntimeFiles(paths, { preserveGatewayConfig: true, preserveState: true });
11624
12085
  releaseCodexHome(paths);
11625
- fs21.rmSync(paths.stateFile, { force: true });
12086
+ fs24.rmSync(paths.stateFile, { force: true });
11626
12087
  if (isCodexConfigManaged(state)) {
11627
12088
  console.log("Uninstalled. Managed config.toml values were restored; config.json was preserved.");
11628
12089
  } else {
@@ -11637,7 +12098,7 @@ function configuredSelectedModels(paths, config) {
11637
12098
  if (Array.isArray(config.selectedModels))
11638
12099
  return config.selectedModels;
11639
12100
  const catalogFile = configuredCatalogFile(paths, config);
11640
- if (!fs21.existsSync(catalogFile))
12101
+ if (!fs24.existsSync(catalogFile))
11641
12102
  return [];
11642
12103
  const prefix = configuredModelPrefix(config);
11643
12104
  try {
@@ -11662,7 +12123,7 @@ async function collectCompatibleModels(config, dependencies = {}, { includeUpstr
11662
12123
  });
11663
12124
  if (includeUpstream) {
11664
12125
  try {
11665
- const proxy = JSON.parse(fs21.readFileSync(configuredCatalogFile(paths, config), "utf8"));
12126
+ const proxy = JSON.parse(fs24.readFileSync(configuredCatalogFile(paths, config), "utf8"));
11666
12127
  if (!Array.isArray(proxy.models))
11667
12128
  throw new Error("catalog file does not contain a models array");
11668
12129
  const prefix = config.upstreamOnly === true ? "" : configuredModelPrefix(config);
@@ -11757,26 +12218,65 @@ async function excludeModels(paths, config, option, restartCodex = false, depend
11757
12218
  }
11758
12219
  async function withModelFileRollback(paths, catalogPath, action) {
11759
12220
  const files = [...new Set([paths.gatewayConfig, paths.stateFile, catalogPath, codexCatalogFile(paths), paths.configToml])];
11760
- const saved = files.map((file) => ({ file, contents: fs21.existsSync(file) ? fs21.readFileSync(file, "utf8") : undefined }));
12221
+ const saved = files.map((file) => ({ file, contents: fs24.existsSync(file) ? fs24.readFileSync(file, "utf8") : undefined }));
11761
12222
  try {
11762
12223
  return await action();
11763
12224
  } catch (error) {
11764
12225
  for (const { file, contents } of saved) {
11765
12226
  if (contents !== undefined) {
11766
- if (!fs21.existsSync(file) || fs21.readFileSync(file, "utf8") !== contents)
12227
+ if (!fs24.existsSync(file) || fs24.readFileSync(file, "utf8") !== contents)
11767
12228
  atomicWrite(file, contents);
11768
- } else if (fs21.existsSync(file))
11769
- fs21.rmSync(file, { force: true });
12229
+ } else if (fs24.existsSync(file))
12230
+ fs24.rmSync(file, { force: true });
11770
12231
  }
11771
12232
  throw error;
11772
12233
  }
11773
12234
  }
12235
+ async function runModelsPush(targetOption, config, dependencies = {}) {
12236
+ const targets = [
12237
+ createZcodePushTarget({ config }),
12238
+ createDshPushTarget({ config })
12239
+ ];
12240
+ if (targetOption !== undefined) {
12241
+ const known = targets.map((target) => target.id);
12242
+ for (const id of [...new Set(targetOption.split(",").map((value) => value.trim().toLowerCase()).filter(Boolean))]) {
12243
+ if (!known.includes(id))
12244
+ throw new Error(`unknown --push target "${id}"; available targets: ${known.join(", ")}`);
12245
+ }
12246
+ }
12247
+ const { entries, failures } = await collectAdapterModels(config, {}, { refresh: false });
12248
+ const scoped = filterExcludedModels({ models: [...entries] }, config.excludedModels, isInstalledPluginModel).models;
12249
+ if (scoped.length === 0) {
12250
+ throw new Error("model push aborted; no local adapter models to push" + " (enable an adapter via config --zcode on / --codebuddy on / \u2026 and refresh its catalog first)");
12251
+ }
12252
+ const input = dependencies.input ?? process.stdin;
12253
+ const output = dependencies.output ?? process.stdout;
12254
+ if (!input.isTTY || !output.isTTY) {
12255
+ throw new Error("models --push requires an interactive terminal to confirm the model list" + " (every model is preselected and Enter pushes them all)");
12256
+ }
12257
+ const selectedSlugs = await chooseModels({
12258
+ availableModels: scoped,
12259
+ currentSelection: scoped.map((model) => model.slug),
12260
+ requireNonEmpty: true,
12261
+ input,
12262
+ output,
12263
+ title: "Local plugin models - space toggles, Enter pushes"
12264
+ });
12265
+ const selectedSet = new Set(selectedSlugs);
12266
+ await pushModelsToTargets({
12267
+ targetOption,
12268
+ entries: scoped.filter((model) => selectedSet.has(model.slug)),
12269
+ failures,
12270
+ config,
12271
+ targets
12272
+ });
12273
+ }
11774
12274
  async function models(options) {
11775
12275
  const restartCodex = options["restart-codex"] === true;
11776
12276
  const selector = stringOption(options, "select");
11777
12277
  const modelMergeJson = stringOption(options, "model-merge-json");
11778
12278
  const paths = resolvePaths();
11779
- if (!fs21.existsSync(paths.gatewayConfig))
12279
+ if (!fs24.existsSync(paths.gatewayConfig))
11780
12280
  throw new Error("Gateway is not installed");
11781
12281
  const config = loadGatewayConfig(paths.gatewayConfig);
11782
12282
  const auditBefore = { ...config };
@@ -11784,22 +12284,31 @@ async function models(options) {
11784
12284
  if (modelMergeJson)
11785
12285
  config.modelMergeJson = modelMergeJson;
11786
12286
  const currentSelection = configuredSelectedModels(paths, config);
12287
+ const pushDeferred = async () => {
12288
+ if (options.push !== undefined) {
12289
+ await runModelsPush(options.push === true ? undefined : String(options.push), config);
12290
+ }
12291
+ };
11787
12292
  if (options.exclude !== undefined) {
11788
12293
  if (options.sync === true)
11789
12294
  throw new Error("--exclude cannot be combined with --sync");
11790
12295
  if (options.select !== undefined)
11791
12296
  throw new Error("--select cannot be combined with --exclude");
11792
12297
  await excludeModels(paths, config, options.exclude, restartCodex);
12298
+ await pushDeferred();
11793
12299
  return;
11794
12300
  }
11795
12301
  if (options.sync !== true) {
11796
- printCurrentModels(config, currentSelection);
11797
- await printCompatibleModels(config);
12302
+ if (options.push === undefined) {
12303
+ printCurrentModels(config, currentSelection);
12304
+ await printCompatibleModels(config);
12305
+ }
12306
+ await pushDeferred();
11798
12307
  return;
11799
12308
  }
11800
- const manualCodexConfig = fs21.existsSync(paths.stateFile) && !isCodexConfigManaged(loadJson(paths.stateFile));
11801
- const source = fs21.existsSync(paths.configToml) ? fs21.readFileSync(paths.configToml, "utf8") : "";
11802
- const legacyCatalogFile = path15.join(paths.codexHome, "cliproxy-catalog.json");
12309
+ const manualCodexConfig = fs24.existsSync(paths.stateFile) && !isCodexConfigManaged(loadJson(paths.stateFile));
12310
+ const source = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
12311
+ const legacyCatalogFile = path17.join(paths.codexHome, "cliproxy-catalog.json");
11803
12312
  if (!manualCodexConfig) {
11804
12313
  applyModelCatalogToml(source, upstreamOnly, paths, codexCatalogFile(paths), managedRuntimeCatalogFiles(paths));
11805
12314
  }
@@ -11824,7 +12333,7 @@ async function models(options) {
11824
12333
  const result2 = await rebuildCatalog(paths, config, selectedProxyModels, modelsConfigFile);
11825
12334
  config.selectedModels = selectedModels;
11826
12335
  writeGatewayConfig(paths.gatewayConfig, config);
11827
- if (fs21.existsSync(paths.stateFile)) {
12336
+ if (fs24.existsSync(paths.stateFile)) {
11828
12337
  const state = loadJson(paths.stateFile);
11829
12338
  state.config = config;
11830
12339
  writeJson(paths.stateFile, state);
@@ -11845,16 +12354,17 @@ async function models(options) {
11845
12354
  } else {
11846
12355
  console.log("Dynamic catalog synced; Codex refreshes /models periodically, but the current model picker may require --restart-codex.");
11847
12356
  }
11848
- return;
12357
+ } else {
12358
+ await refreshCodexAppServer();
11849
12359
  }
11850
- await refreshCodexAppServer();
12360
+ await pushDeferred();
11851
12361
  }
11852
12362
  async function status() {
11853
12363
  const paths = resolvePaths();
11854
- const installed = fs21.existsSync(paths.stateFile);
11855
- const config = fs21.existsSync(paths.gatewayConfig) ? loadGatewayConfig(paths.gatewayConfig) : undefined;
12364
+ const installed = fs24.existsSync(paths.stateFile);
12365
+ const config = fs24.existsSync(paths.gatewayConfig) ? loadGatewayConfig(paths.gatewayConfig) : undefined;
11856
12366
  const service = process.platform === "darwin" ? launchAgentStatus(gatewayServiceLabel(paths.instanceSuffix)) : null;
11857
- const source = fs21.existsSync(paths.configToml) ? fs21.readFileSync(paths.configToml, "utf8") : "";
12367
+ const source = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
11858
12368
  const configuredBaseUrl = readRootTomlString(source, "openai_base_url");
11859
12369
  const configuredCatalog = readRootTomlString(source, "model_catalog_json");
11860
12370
  const configuredRealtimeWsBaseUrl = readRootTomlString(source, "experimental_realtime_ws_base_url");
@@ -11882,7 +12392,7 @@ async function status() {
11882
12392
  modelCatalogJson: configuredCatalog,
11883
12393
  authJsonModified: false,
11884
12394
  cachedCatalogPath: configuredCatalogFile(paths, config ?? {}),
11885
- cachedCatalogPresent: fs21.existsSync(configuredCatalogFile(paths, config ?? {})),
12395
+ cachedCatalogPresent: fs24.existsSync(configuredCatalogFile(paths, config ?? {})),
11886
12396
  upstreamOnly: config?.upstreamOnly === true,
11887
12397
  upstreamType: config?.upstreamType ?? "cliproxy",
11888
12398
  codexClientVersion: resolveCodexClientVersion(paths.upstreamModelsCacheFile)
@@ -11892,10 +12402,10 @@ function serve(options) {
11892
12402
  requireBun();
11893
12403
  const paths = resolvePaths();
11894
12404
  const configPath = realPathOrResolve(stringOption(options, "config") || paths.gatewayConfig);
11895
- if (!fs21.existsSync(configPath))
12405
+ if (!fs24.existsSync(configPath))
11896
12406
  throw new Error(`Gateway config not found: ${configPath}`);
11897
12407
  const config = loadGatewayConfig(configPath);
11898
- const isProductionInstance = configPath === realPathOrResolve(path15.join(paths.runtimeHome, "config.json"));
12408
+ const isProductionInstance = configPath === realPathOrResolve(path17.join(paths.runtimeHome, "config.json"));
11899
12409
  const processLog = isProductionInstance ? { file: paths.stdoutLog, maxBytes: config.maxGatewayLogBytes ?? 0 } : undefined;
11900
12410
  startGateway(config, loadRealtimeProviderMode(paths.configToml), paths.upstreamModelsCacheFile, { codexModelsCacheFile: paths.modelsCacheFile }, processLog, { codexModelsCacheFile: paths.modelsCacheFile }, { codexModelsCacheFile: paths.modelsCacheFile }, { codexModelsCacheFile: paths.modelsCacheFile }, { codexModelsCacheFile: paths.modelsCacheFile });
11901
12411
  if (isProductionInstance)
@@ -11927,7 +12437,7 @@ async function waitForWebUi(port, timeoutMs = 1e4) {
11927
12437
  throw new Error(`Web UI did not come up on port ${port} within ${timeoutMs / 1000}s`);
11928
12438
  }
11929
12439
  function webUiLogPath(paths) {
11930
- return path15.join(paths.runtimeHome, "webui.log");
12440
+ return path17.join(paths.runtimeHome, "webui.log");
11931
12441
  }
11932
12442
  function runWebUiForeground(paths, config, openBrowser) {
11933
12443
  const ctx = webUiContextForInstance(paths.gatewayConfig, paths);
@@ -11963,7 +12473,7 @@ async function ensureGatewayRunning(paths, config) {
11963
12473
  async function startWebUiService(paths, config) {
11964
12474
  startWebUiLaunchAgent(paths.webUiLaunchAgent, {
11965
12475
  bunPath: process.execPath,
11966
- cliPath: fs21.realpathSync(process.argv[1]),
12476
+ cliPath: fs24.realpathSync(process.argv[1]),
11967
12477
  codexHome: paths.codexHome,
11968
12478
  logPath: webUiLogPath(paths),
11969
12479
  label: webUiServiceLabel(paths.instanceSuffix),
@@ -11979,7 +12489,7 @@ async function webCommand(options) {
11979
12489
  if (process.env.CODEX_CLIPROXY_UI_SERVICE === "1") {
11980
12490
  requireBun();
11981
12491
  const paths2 = resolvePaths();
11982
- if (!fs21.existsSync(paths2.gatewayConfig)) {
12492
+ if (!fs24.existsSync(paths2.gatewayConfig)) {
11983
12493
  throw new Error(`Gateway config not found: ${paths2.gatewayConfig}`);
11984
12494
  }
11985
12495
  const config2 = loadGatewayConfig(paths2.gatewayConfig);
@@ -11991,7 +12501,7 @@ async function webCommand(options) {
11991
12501
  }
11992
12502
  requireMacOS();
11993
12503
  const paths = resolvePaths();
11994
- if (!fs21.existsSync(paths.stateFile))
12504
+ if (!fs24.existsSync(paths.stateFile))
11995
12505
  throw new Error("Gateway is not installed");
11996
12506
  const config = loadGatewayConfig(paths.gatewayConfig);
11997
12507
  if (!isLoopbackHost(config.host)) {
@@ -12067,7 +12577,7 @@ async function promptCodebuddyAccountSelection(dependencies = {}) {
12067
12577
  const items = [
12068
12578
  {
12069
12579
  value: "auto",
12070
- label: `${current?.label ?? "no login yet"} (auto, follows the most recently refreshed login)`,
12580
+ label: current ? `Auto(${current.label})` : "Auto",
12071
12581
  selectable: true
12072
12582
  },
12073
12583
  ...entries.filter((entry) => entry.file !== resolved).map((entry) => ({
@@ -12117,7 +12627,7 @@ async function writeConfigAndRestart(paths, config, auditBefore, command, applie
12117
12627
  validateConfiguredPlugins(config);
12118
12628
  const persist = async () => {
12119
12629
  writeGatewayConfig(paths.gatewayConfig, config);
12120
- if (fs21.existsSync(paths.stateFile)) {
12630
+ if (fs24.existsSync(paths.stateFile)) {
12121
12631
  const state = loadJson(paths.stateFile);
12122
12632
  state.config = config;
12123
12633
  writeJson(paths.stateFile, state);
@@ -12141,7 +12651,7 @@ async function writeConfigAndRestart(paths, config, auditBefore, command, applie
12141
12651
  console.log(result.loaded ? "Gateway model configuration reloaded." : "Models saved; the gateway will load them when started.");
12142
12652
  return;
12143
12653
  }
12144
- if (fs21.existsSync(paths.launchAgent)) {
12654
+ if (fs24.existsSync(paths.launchAgent)) {
12145
12655
  await restartGatewayOnce(paths, config);
12146
12656
  console.log("Gateway restarted to apply the new configuration.");
12147
12657
  } else {
@@ -12161,7 +12671,7 @@ async function configCommand(options) {
12161
12671
  const maxLogsOption = stringOption(options, "max-request-logs");
12162
12672
  const maxLogSizeOption = stringOption(options, "max-log-size");
12163
12673
  const paths = resolvePaths();
12164
- if (!fs21.existsSync(paths.gatewayConfig))
12674
+ if (!fs24.existsSync(paths.gatewayConfig))
12165
12675
  throw new Error("Gateway is not installed");
12166
12676
  const config = loadGatewayConfig(paths.gatewayConfig);
12167
12677
  const auditBefore = { ...config };
@@ -12201,9 +12711,9 @@ async function configCommand(options) {
12201
12711
  config.upstreamOnly = upstreamOnlyTarget;
12202
12712
  applied.push(`Default upstream-only routing ${upstreamOnlyTarget ? "enabled" : "disabled"}.`);
12203
12713
  }
12204
- const managesCodex = !fs21.existsSync(paths.stateFile) || isCodexConfigManaged(loadJson(paths.stateFile));
12714
+ const managesCodex = !fs24.existsSync(paths.stateFile) || isCodexConfigManaged(loadJson(paths.stateFile));
12205
12715
  if (managesCodex) {
12206
- const source = fs21.existsSync(paths.configToml) ? fs21.readFileSync(paths.configToml, "utf8") : "";
12716
+ const source = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
12207
12717
  applyModelCatalogToml(source, config.upstreamOnly === true, paths, codexCatalogFile(paths), managedRuntimeCatalogFiles(paths));
12208
12718
  }
12209
12719
  if (zcodeTarget !== undefined) {
@@ -12242,8 +12752,8 @@ async function configCommand(options) {
12242
12752
  config.debug = debugTarget;
12243
12753
  applied.push(`Debug dump ${debugTarget ? "enabled" : "disabled"} ` + `(upstream error request bodies go to: ${requestLogDir(paths)}).`);
12244
12754
  }
12245
- const previousConfig = fs21.readFileSync(paths.gatewayConfig, "utf8");
12246
- const previousState = fs21.existsSync(paths.stateFile) ? fs21.readFileSync(paths.stateFile, "utf8") : undefined;
12755
+ const previousConfig = fs24.readFileSync(paths.gatewayConfig, "utf8");
12756
+ const previousState = fs24.existsSync(paths.stateFile) ? fs24.readFileSync(paths.stateFile, "utf8") : undefined;
12247
12757
  try {
12248
12758
  await withModelFileRollback(paths, configuredCatalogFile(paths, config), async () => {
12249
12759
  await writeConfigAndRestart(paths, config, auditBefore, "config", applied, {
@@ -12252,14 +12762,14 @@ async function configCommand(options) {
12252
12762
  });
12253
12763
  const changesModels = upstreamOnlyTarget !== undefined || zcodeTarget !== undefined || codebuddyTarget !== undefined || qoderTarget !== undefined || agyTarget !== undefined || opencodeZenTarget !== undefined;
12254
12764
  if (changesModels) {
12255
- await updateCodexModelCatalog2(paths, config, {}, { refreshAdapters: !fs21.existsSync(paths.launchAgent) });
12765
+ await updateCodexModelCatalog2(paths, config, {}, { refreshAdapters: !fs24.existsSync(paths.launchAgent) });
12256
12766
  }
12257
12767
  });
12258
12768
  } catch (error) {
12259
12769
  atomicWrite(paths.gatewayConfig, previousConfig);
12260
12770
  if (previousState !== undefined)
12261
12771
  atomicWrite(paths.stateFile, previousState);
12262
- if (fs21.existsSync(paths.launchAgent)) {
12772
+ if (fs24.existsSync(paths.launchAgent)) {
12263
12773
  try {
12264
12774
  await restartGatewayOnce(paths, loadGatewayConfig(paths.gatewayConfig));
12265
12775
  } catch {
@@ -12274,7 +12784,7 @@ async function configCommand(options) {
12274
12784
  async function codebuddyCommand(dependencies = {}) {
12275
12785
  requireMacOS();
12276
12786
  const paths = resolvePaths();
12277
- if (!fs21.existsSync(paths.gatewayConfig))
12787
+ if (!fs24.existsSync(paths.gatewayConfig))
12278
12788
  throw new Error("Gateway is not installed");
12279
12789
  const config = loadGatewayConfig(paths.gatewayConfig);
12280
12790
  const auditBefore = { ...config };
@@ -12283,6 +12793,10 @@ async function codebuddyCommand(dependencies = {}) {
12283
12793
  console.log("CodeBuddy account switch cancelled; no account change was saved.");
12284
12794
  return;
12285
12795
  }
12796
+ if (selection === (config.codebuddyAccount ?? "auto")) {
12797
+ console.log("CodeBuddy account unchanged; nothing to save.");
12798
+ return;
12799
+ }
12286
12800
  config.codebuddyAccount = selection;
12287
12801
  await writeConfigAndRestart(paths, config, auditBefore, "codebuddy --switch", [
12288
12802
  codebuddyAccountApplied(selection)
@@ -12291,7 +12805,7 @@ async function codebuddyCommand(dependencies = {}) {
12291
12805
  async function controlGateway(action, restartCodex = false) {
12292
12806
  requireMacOS();
12293
12807
  const paths = resolvePaths();
12294
- if (!fs21.existsSync(paths.stateFile))
12808
+ if (!fs24.existsSync(paths.stateFile))
12295
12809
  throw new Error("Gateway is not installed");
12296
12810
  if (action === "stop") {
12297
12811
  stopLaunchAgent(paths.launchAgent);
@@ -12303,7 +12817,7 @@ async function controlGateway(action, restartCodex = false) {
12303
12817
  if (action === "start") {
12304
12818
  startLaunchAgent(paths.launchAgent, gatewayServiceLabel(paths.instanceSuffix));
12305
12819
  } else {
12306
- const source = fs21.existsSync(paths.configToml) ? fs21.readFileSync(paths.configToml, "utf8") : "";
12820
+ const source = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
12307
12821
  const gatewayBaseUrl = `http://${config.host}:${config.port}${MOUNT_PATH}`;
12308
12822
  const state = loadJson(paths.stateFile);
12309
12823
  if (isCodexConfigManaged(state)) {
@@ -12335,15 +12849,15 @@ function ownsManagedCodexConfig(paths, state, legacyBaseUrl) {
12335
12849
  const owner = codexOwnerRuntimeHome(paths);
12336
12850
  if (owner !== undefined)
12337
12851
  return owner === realPathOrResolve(paths.runtimeHome);
12338
- if (!fs21.existsSync(paths.configToml))
12852
+ if (!fs24.existsSync(paths.configToml))
12339
12853
  return false;
12340
- const currentUrl = readRootTomlString(fs21.readFileSync(paths.configToml, "utf8"), "openai_base_url");
12854
+ const currentUrl = readRootTomlString(fs24.readFileSync(paths.configToml, "utf8"), "openai_base_url");
12341
12855
  return currentUrl === state.gatewayBaseUrl || currentUrl === legacyBaseUrl;
12342
12856
  }
12343
12857
  function syncGatewayConfigFile(paths, configFile = paths.gatewayConfig) {
12344
- if (!fs21.existsSync(configFile))
12858
+ if (!fs24.existsSync(configFile))
12345
12859
  return;
12346
- const managesState = realPathOrResolve(configFile) === realPathOrResolve(path15.join(paths.runtimeHome, "config.json"));
12860
+ const managesState = realPathOrResolve(configFile) === realPathOrResolve(path17.join(paths.runtimeHome, "config.json"));
12347
12861
  const raw = loadJson(configFile);
12348
12862
  if (!isJsonObject(raw))
12349
12863
  throw new Error(`Gateway config must be a JSON object: ${configFile}`);
@@ -12384,11 +12898,11 @@ function syncGatewayConfigFile(paths, configFile = paths.gatewayConfig) {
12384
12898
  if (legacyMount && legacyMount !== MOUNT_PATH) {
12385
12899
  const gatewayBaseUrl = `http://${current.host}:${current.port}${MOUNT_PATH}`;
12386
12900
  const legacyBaseUrl = `http://${current.host}:${current.port}${legacyMount}`;
12387
- const state = managesState && fs21.existsSync(paths.stateFile) ? loadJson(paths.stateFile) : undefined;
12901
+ const state = managesState && fs24.existsSync(paths.stateFile) ? loadJson(paths.stateFile) : undefined;
12388
12902
  if (state?.uninstallClientRestored === true) {} else if (state && isCodexConfigManaged(state)) {
12389
12903
  if (ownsManagedCodexConfig(paths, state, legacyBaseUrl)) {
12390
- if (fs21.existsSync(paths.configToml)) {
12391
- const source = fs21.readFileSync(paths.configToml, "utf8");
12904
+ if (fs24.existsSync(paths.configToml)) {
12905
+ const source = fs24.readFileSync(paths.configToml, "utf8");
12392
12906
  const patched = managedCodexServiceToml(source, gatewayBaseUrl);
12393
12907
  if (patched !== source)
12394
12908
  atomicWrite(paths.configToml, patched);
@@ -12416,7 +12930,7 @@ function syncGatewayConfigFile(paths, configFile = paths.gatewayConfig) {
12416
12930
  if (current.configVersion === GATEWAY_CONFIG_VERSION) {
12417
12931
  if (dirty) {
12418
12932
  writeGatewayConfig(configFile, current);
12419
- if (managesState && fs21.existsSync(paths.stateFile)) {
12933
+ if (managesState && fs24.existsSync(paths.stateFile)) {
12420
12934
  const state = loadJson(paths.stateFile);
12421
12935
  state.config = current;
12422
12936
  writeJson(paths.stateFile, state);
@@ -12436,7 +12950,7 @@ function syncGatewayConfigFile(paths, configFile = paths.gatewayConfig) {
12436
12950
  });
12437
12951
  }
12438
12952
  writeGatewayConfig(configFile, merged.config);
12439
- if (managesState && fs21.existsSync(paths.stateFile)) {
12953
+ if (managesState && fs24.existsSync(paths.stateFile)) {
12440
12954
  const state = loadJson(paths.stateFile);
12441
12955
  state.version = 4;
12442
12956
  state.config = merged.config;
@@ -12455,7 +12969,7 @@ var COMMAND_OPTIONS = {
12455
12969
  uninstall: ["restart-codex"],
12456
12970
  restart: ["restart-codex"],
12457
12971
  serve: ["config"],
12458
- models: ["sync", "select", "restart-codex", "model-merge-json", "exclude"],
12972
+ models: ["sync", "select", "restart-codex", "model-merge-json", "exclude", "push"],
12459
12973
  config: ["upstream-only", "restart-codex", "zcode", "codebuddy", "qoder", "agy", "opencode-zen", "log", "debug", "max-request-logs", "max-log-size"],
12460
12974
  codebuddy: ["switch"],
12461
12975
  web: ["start", "daemon", "status", "stop", "restart"]
@@ -12506,18 +13020,18 @@ async function runCli(args) {
12506
13020
  }
12507
13021
  const configOption = command === "serve" ? stringOption(options, "config") : undefined;
12508
13022
  const configPath = configOption ? realPathOrResolve(configOption) : undefined;
12509
- let paths = configPath ? { ...resolvePaths(process.env, path15.dirname(configPath)), gatewayConfig: configPath } : resolvePaths();
13023
+ let paths = configPath ? { ...resolvePaths(process.env, path17.dirname(configPath)), gatewayConfig: configPath } : resolvePaths();
12510
13024
  const migration = command === "install" ? prepareDefaultInstallMigration(paths) : undefined;
12511
13025
  paths = migration?.paths ?? resolveInstalledCodexPaths(paths);
12512
13026
  return runWithInstancePaths(paths, async () => {
12513
- if (fs21.existsSync(paths.stateFile)) {
13027
+ if (fs24.existsSync(paths.stateFile)) {
12514
13028
  const state = loadJson(paths.stateFile);
12515
13029
  if (state.uninstallClientRestored === true && !["install", "uninstall", "status"].includes(command)) {
12516
13030
  throw new Error("An uninstall is unfinished; rerun uninstall or install --yes before managing this instance");
12517
13031
  }
12518
13032
  }
12519
13033
  const writesClient = command === "restart" || command === "codebuddy" || command === "models" && (options.sync === true || options.exclude !== undefined) || command === "config" && Object.keys(options).length > 0;
12520
- if (writesClient && fs21.existsSync(paths.stateFile)) {
13034
+ if (writesClient && fs24.existsSync(paths.stateFile)) {
12521
13035
  const state = loadJson(paths.stateFile);
12522
13036
  if (isCodexConfigManaged(state))
12523
13037
  assertCodexHomeAvailable(paths, false);