@bitkyc08/opencodex 2.6.23-preview.20260705 → 2.6.23

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.
@@ -16,7 +16,7 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-DBAhf9Uu.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-Yuh5eZiD.js"></script>
20
20
  <link rel="stylesheet" crossorigin href="/assets/index-BcHhxo1I.css">
21
21
  </head>
22
22
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.6.23-preview.20260705",
3
+ "version": "2.6.23",
4
4
  "description": "Universal provider proxy for OpenAI Codex — use any LLM with Codex CLI/App/SDK",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
package/src/cli-help.ts CHANGED
@@ -61,6 +61,29 @@ const helpEntries: Record<string, HelpEntry> = {
61
61
  usage: "ocx update [--tag latest|preview]",
62
62
  summary: "Update opencodex. Preview installs stay on the preview tag unless overridden.",
63
63
  },
64
+ provider: {
65
+ usage: "ocx provider <list|add|remove|show|set-default>",
66
+ summary: "Non-interactive provider management.",
67
+ details: [
68
+ "Subcommands: list, add <name>, remove <name>, show <name>, set-default <name>",
69
+ "Registry providers are auto-configured by name. Custom providers need --adapter and --base-url.",
70
+ "Run `ocx provider --help` for full usage and examples.",
71
+ ],
72
+ },
73
+ models: {
74
+ usage: "ocx models [--provider <name>] [--json]",
75
+ summary: "List available models from configured providers.",
76
+ details: ["Shows statically configured models. Providers with liveModels may have additional models at runtime."],
77
+ },
78
+ restart: {
79
+ usage: "ocx restart",
80
+ summary: "Stop the proxy and restart it (background). Equivalent to stop + ensure.",
81
+ },
82
+ health: {
83
+ usage: "ocx health [--json]",
84
+ summary: "Check proxy health. Exits 0 if healthy, 1 otherwise.",
85
+ details: ["Use --json for structured output: {ok, pid, port}."],
86
+ },
64
87
  };
65
88
 
66
89
  function packageVersion(): string {
@@ -96,6 +119,10 @@ Usage:
96
119
  ocx logout <provider> Remove a stored OAuth login
97
120
  ocx gui Open the opencodex dashboard
98
121
  ocx update [--tag <tag>] Update opencodex (keeps preview installs on @preview)
122
+ ocx restart Stop and restart the proxy
123
+ ocx health [--json] Check proxy health (exit 0=healthy, 1=not)
124
+ ocx provider <sub> Manage providers (list|add|remove|show|set-default)
125
+ ocx models [--json] List available models from configured providers
99
126
  ocx help [command] Show help
100
127
  ocx --version | -v Print version
101
128
 
@@ -0,0 +1,138 @@
1
+ /**
2
+ * `ocx models` subcommand — list available models from configured providers.
3
+ *
4
+ * Usage:
5
+ * ocx models [--provider <name>] [--json]
6
+ */
7
+ import { hasOwnProvider, loadConfig } from "./config";
8
+ import type { OcxConfig } from "./types";
9
+
10
+ interface ModelEntry {
11
+ provider: string;
12
+ model: string;
13
+ isDefault: boolean;
14
+ contextWindow: number | null;
15
+ inputModalities: string[] | null;
16
+ reasoningEfforts: string[] | null;
17
+ }
18
+
19
+ function collectModels(config: OcxConfig, providerFilter?: string): ModelEntry[] {
20
+ const entries: ModelEntry[] = [];
21
+ const providers = providerFilter
22
+ ? { [providerFilter]: config.providers[providerFilter] }
23
+ : config.providers;
24
+
25
+ for (const [provName, prov] of Object.entries(providers)) {
26
+ if (!prov) continue;
27
+ const seen = new Set<string>();
28
+ const contextWindows = prov.modelContextWindows ?? {};
29
+ const inputModalities = prov.modelInputModalities ?? {};
30
+ const reasoningEfforts = prov.modelReasoningEfforts ?? {};
31
+ const globalContext = prov.contextWindow ?? null;
32
+
33
+ const addModel = (model: string, isDefault: boolean) => {
34
+ if (seen.has(model)) return;
35
+ seen.add(model);
36
+
37
+ const noVision = prov.noVisionModels?.includes(model);
38
+ const modalities = inputModalities[model] ?? (noVision ? ["text"] : null);
39
+ const efforts = reasoningEfforts[model] ?? prov.reasoningEfforts ?? null;
40
+
41
+ entries.push({
42
+ provider: provName,
43
+ model,
44
+ isDefault,
45
+ contextWindow: contextWindows[model] ?? globalContext,
46
+ inputModalities: modalities,
47
+ reasoningEfforts: efforts,
48
+ });
49
+ };
50
+
51
+ // defaultModel first
52
+ if (prov.defaultModel) addModel(prov.defaultModel, true);
53
+
54
+ // models array
55
+ if (prov.models) {
56
+ for (const m of prov.models) addModel(m, m === prov.defaultModel);
57
+ }
58
+ }
59
+
60
+ return entries;
61
+ }
62
+
63
+ function consumeFlag(args: string[], flag: string): boolean {
64
+ const idx = args.indexOf(flag);
65
+ if (idx === -1) return false;
66
+ args.splice(idx, 1);
67
+ return true;
68
+ }
69
+
70
+ function consumeFlagValue(args: string[], flag: string): string | undefined {
71
+ const idx = args.indexOf(flag);
72
+ if (idx === -1 || idx + 1 >= args.length) return undefined;
73
+ const value = args[idx + 1];
74
+ args.splice(idx, 2);
75
+ return value;
76
+ }
77
+
78
+ export function handleModels(args: string[]): void {
79
+ const restArgs = [...args];
80
+ const wantsJson = consumeFlag(restArgs, "--json");
81
+ const providerFilter = consumeFlagValue(restArgs, "--provider");
82
+
83
+ if (restArgs.length > 0) {
84
+ const unknown = restArgs.filter(a => a.startsWith("-"));
85
+ if (unknown.length > 0) {
86
+ console.error(`Unknown flag(s): ${unknown.join(", ")}`);
87
+ } else {
88
+ console.error(`Unexpected argument(s): ${restArgs.join(", ")}`);
89
+ }
90
+ console.error("Usage: ocx models [--provider <name>] [--json]");
91
+ process.exit(1);
92
+ }
93
+
94
+ const config = loadConfig();
95
+
96
+ if (providerFilter && !hasOwnProvider(config.providers, providerFilter)) {
97
+ console.error(`Provider "${providerFilter}" is not configured. See: ocx provider list`);
98
+ process.exit(1);
99
+ }
100
+
101
+ const models = collectModels(config, providerFilter ?? undefined);
102
+
103
+ if (wantsJson) {
104
+ console.log(JSON.stringify({
105
+ models,
106
+ note: "Static config models only. Providers with liveModels=true may have additional models at runtime.",
107
+ }, null, 2));
108
+ return;
109
+ }
110
+
111
+ if (models.length === 0) {
112
+ console.log("No models found in configured providers.");
113
+ if (!providerFilter) console.log("Providers may discover models dynamically at runtime (liveModels).");
114
+ return;
115
+ }
116
+
117
+ // Group by provider
118
+ const byProvider = new Map<string, ModelEntry[]>();
119
+ for (const entry of models) {
120
+ const list = byProvider.get(entry.provider) ?? [];
121
+ list.push(entry);
122
+ byProvider.set(entry.provider, list);
123
+ }
124
+
125
+ for (const [provName, provModels] of byProvider) {
126
+ const isDefaultProv = provName === config.defaultProvider ? " (default provider)" : "";
127
+ console.log(`${provName}${isDefaultProv}:`);
128
+ for (const m of provModels) {
129
+ const marker = m.isDefault ? " *" : "";
130
+ const ctx = m.contextWindow ? ` (${Math.round(m.contextWindow / 1000)}k)` : "";
131
+ console.log(` ${m.model}${marker}${ctx}`);
132
+ }
133
+ console.log();
134
+ }
135
+
136
+ console.log("* = default model for provider");
137
+ console.log("Note: providers with liveModels may have additional models at runtime.");
138
+ }
@@ -0,0 +1,414 @@
1
+ /**
2
+ * `ocx provider` subcommand — non-interactive provider management.
3
+ *
4
+ * Subcommands:
5
+ * list List configured and available registry providers
6
+ * add <name> Add a provider from the registry or with custom flags
7
+ * remove <name> Remove a configured provider
8
+ * show <name> Show provider config details (secrets masked)
9
+ * set-default <name> Change the default provider
10
+ */
11
+ import { hasOwnProvider, isValidProviderName, loadConfig, saveConfig } from "./config";
12
+ import { hasHelpFlag } from "./cli-help";
13
+ import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "./providers/registry";
14
+ import { providerConfigSeed } from "./providers/derive";
15
+ import type { OcxProviderConfig } from "./types";
16
+ import { findLiveProxy } from "./proxy-liveness";
17
+ import { syncModelsToCodex } from "./codex-sync";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Arg helpers
21
+ // ---------------------------------------------------------------------------
22
+
23
+ function consumeFlag(args: string[], flag: string): boolean {
24
+ const idx = args.indexOf(flag);
25
+ if (idx === -1) return false;
26
+ args.splice(idx, 1);
27
+ return true;
28
+ }
29
+
30
+ function consumeFlagValue(args: string[], flag: string): string | undefined {
31
+ const idx = args.indexOf(flag);
32
+ if (idx === -1 || idx + 1 >= args.length) return undefined;
33
+ const value = args[idx + 1];
34
+ args.splice(idx, 2);
35
+ return value;
36
+ }
37
+
38
+ /** Reject any leftover args (unknown flags or trailing values). */
39
+ function rejectUnknownArgs(args: string[], usage: string): void {
40
+ if (args.length === 0) return;
41
+ const unknown = args.filter(a => a.startsWith("-"));
42
+ if (unknown.length > 0) {
43
+ console.error(`Unknown flag(s): ${unknown.join(", ")}`);
44
+ } else {
45
+ console.error(`Unexpected argument(s): ${args.join(", ")}`);
46
+ }
47
+ console.error(usage);
48
+ process.exit(1);
49
+ }
50
+
51
+ function maskSecret(value: string): string {
52
+ if (value.length <= 8) return "****";
53
+ return `${value.slice(0, 4)}****${value.slice(-4)}`;
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Validation helper (F1 fix: validate before saveConfig)
58
+ // ---------------------------------------------------------------------------
59
+
60
+ function validateAndSave(config: ReturnType<typeof loadConfig>): void {
61
+ if (!config.providers || Object.keys(config.providers).length === 0) {
62
+ console.error("Error: config would have no providers. Aborting.");
63
+ process.exit(1);
64
+ }
65
+ if (!hasOwnProvider(config.providers, config.defaultProvider)) {
66
+ console.error(`Error: defaultProvider "${config.defaultProvider}" does not exist in providers. Aborting.`);
67
+ process.exit(1);
68
+ }
69
+ saveConfig(config);
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // provider list
74
+ // ---------------------------------------------------------------------------
75
+
76
+ function handleList(args: string[]): void {
77
+ const wantsJson = consumeFlag(args, "--json");
78
+ rejectUnknownArgs(args, "Usage: ocx provider list [--json]");
79
+
80
+ const config = loadConfig();
81
+ const configured = Object.keys(config.providers);
82
+
83
+ if (wantsJson) {
84
+ const entries = configured.map(name => {
85
+ const prov = config.providers[name];
86
+ const registryEntry = getProviderRegistryEntry(name);
87
+ return {
88
+ name,
89
+ adapter: prov.adapter,
90
+ baseUrl: prov.baseUrl,
91
+ authMode: prov.authMode ?? "key",
92
+ defaultModel: prov.defaultModel ?? null,
93
+ isDefault: name === config.defaultProvider,
94
+ source: registryEntry ? "registry" : "custom",
95
+ models: prov.models ?? [],
96
+ };
97
+ });
98
+ console.log(JSON.stringify({ configured: entries, registryCount: PROVIDER_REGISTRY.length }, null, 2));
99
+ return;
100
+ }
101
+
102
+ console.log("Configured providers:\n");
103
+ for (const name of configured) {
104
+ const prov = config.providers[name];
105
+ const isDefault = name === config.defaultProvider ? " (default)" : "";
106
+ const registryEntry = getProviderRegistryEntry(name);
107
+ const source = registryEntry ? "" : " [custom]";
108
+ const model = prov.defaultModel ? ` model=${prov.defaultModel}` : "";
109
+ console.log(` ${name}${isDefault}${source} adapter=${prov.adapter}${model}`);
110
+ }
111
+
112
+ const available = PROVIDER_REGISTRY.filter(e => !configured.includes(e.id));
113
+ if (available.length > 0) {
114
+ console.log(`\nAvailable from registry (${available.length}):\n`);
115
+ for (const entry of available) {
116
+ const auth = entry.authKind === "forward" ? "chatgpt-login" : entry.authKind;
117
+ console.log(` ${entry.id.padEnd(24)} ${entry.label} (${auth})`);
118
+ }
119
+ console.log(`\nAdd with: ocx provider add <name> [--api-key <key>]`);
120
+ }
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // provider add
125
+ // ---------------------------------------------------------------------------
126
+
127
+ const ADD_USAGE = "Usage: ocx provider add <name> [--adapter <adapter>] [--base-url <url>] [--api-key <key>] [--default-model <model>] [--set-default] [--force] [--json] [--sync]";
128
+
129
+ async function handleAdd(args: string[]): Promise<void> {
130
+ const name = args[0];
131
+ if (!name || name.startsWith("-")) {
132
+ console.error(ADD_USAGE);
133
+ process.exit(1);
134
+ }
135
+
136
+ if (!isValidProviderName(name)) {
137
+ console.error(`Invalid provider name: "${name}". Use letters, numbers, dots, underscores, or hyphens.`);
138
+ process.exit(1);
139
+ }
140
+
141
+ const restArgs = args.slice(1);
142
+ const force = consumeFlag(restArgs, "--force");
143
+ const setDefault = consumeFlag(restArgs, "--set-default");
144
+ const wantsJson = consumeFlag(restArgs, "--json");
145
+ const wantsSync = consumeFlag(restArgs, "--sync");
146
+ const apiKey = consumeFlagValue(restArgs, "--api-key");
147
+ const adapter = consumeFlagValue(restArgs, "--adapter");
148
+ const baseUrl = consumeFlagValue(restArgs, "--base-url");
149
+ const defaultModel = consumeFlagValue(restArgs, "--default-model");
150
+ rejectUnknownArgs(restArgs, ADD_USAGE);
151
+
152
+ const config = loadConfig();
153
+
154
+ if (hasOwnProvider(config.providers, name) && !force) {
155
+ console.error(`Provider "${name}" already exists. Use --force to overwrite.`);
156
+ process.exit(1);
157
+ }
158
+
159
+ let provConfig: OcxProviderConfig;
160
+ const registryEntry = getProviderRegistryEntry(name);
161
+
162
+ if (registryEntry) {
163
+ provConfig = providerConfigSeed(registryEntry);
164
+ if (apiKey) {
165
+ if (registryEntry.authKind === "forward") {
166
+ console.warn(`Warning: provider "${name}" uses ChatGPT login (forward auth); --api-key is ignored.`);
167
+ } else if (registryEntry.authKind === "oauth") {
168
+ console.warn(`Warning: provider "${name}" uses OAuth auth; --api-key is ignored. Run: ocx login ${name}`);
169
+ } else {
170
+ provConfig.apiKey = apiKey;
171
+ }
172
+ }
173
+ if (defaultModel) provConfig.defaultModel = defaultModel;
174
+ if (adapter) provConfig.adapter = adapter;
175
+ if (baseUrl) provConfig.baseUrl = baseUrl;
176
+ } else {
177
+ if (!adapter || !baseUrl) {
178
+ console.error(`Provider "${name}" is not in the registry. --adapter and --base-url are required.`);
179
+ console.error("Usage: ocx provider add <name> --adapter <adapter> --base-url <url> [--api-key <key>]");
180
+ process.exit(1);
181
+ }
182
+ provConfig = {
183
+ adapter,
184
+ baseUrl,
185
+ ...(apiKey ? { apiKey } : {}),
186
+ ...(defaultModel ? { defaultModel } : {}),
187
+ };
188
+ }
189
+
190
+ config.providers[name] = provConfig;
191
+ if (setDefault) config.defaultProvider = name;
192
+
193
+ validateAndSave(config);
194
+
195
+ if (wantsJson) {
196
+ console.log(JSON.stringify({
197
+ action: "added",
198
+ provider: name,
199
+ adapter: provConfig.adapter,
200
+ baseUrl: provConfig.baseUrl,
201
+ defaultModel: provConfig.defaultModel ?? null,
202
+ isDefault: config.defaultProvider === name,
203
+ source: registryEntry ? "registry" : "custom",
204
+ needsSync: true,
205
+ }, null, 2));
206
+ return;
207
+ }
208
+
209
+ if (wantsSync) {
210
+ const live = await findLiveProxy();
211
+ if (live) {
212
+ await syncModelsToCodex(live.port).catch(e => {
213
+ console.error(`Warning: sync failed: ${e instanceof Error ? e.message : String(e)}`);
214
+ });
215
+ }
216
+ }
217
+
218
+ const registryLabel = registryEntry ? ` (${registryEntry.label})` : "";
219
+ console.log(`✅ Provider "${name}"${registryLabel} added.`);
220
+ if (setDefault) console.log(` Set as default provider.`);
221
+ if (registryEntry?.authKind === "oauth") {
222
+ console.log(` Authenticate with: ocx login ${name}`);
223
+ }
224
+ if (registryEntry?.authKind === "key" && !apiKey) {
225
+ const envKey = `${name.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
226
+ console.log(` Set API key with: ocx provider add ${name} --api-key <key> --force`);
227
+ console.log(` Or set env var: ${envKey}`);
228
+ }
229
+ if (wantsSync) {
230
+ console.log(` Models synced to Codex.`);
231
+ } else {
232
+ console.log(` Apply to Codex: ocx sync`);
233
+ }
234
+ }
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // provider remove
238
+ // ---------------------------------------------------------------------------
239
+
240
+ function handleRemove(args: string[]): void {
241
+ const restArgs = [...args];
242
+ const wantsJson = consumeFlag(restArgs, "--json");
243
+ const name = restArgs[0];
244
+ if (!name || name.startsWith("-")) {
245
+ console.error("Usage: ocx provider remove <name> [--json]");
246
+ process.exit(1);
247
+ }
248
+ rejectUnknownArgs(restArgs.slice(1), "Usage: ocx provider remove <name> [--json]");
249
+
250
+ const config = loadConfig();
251
+ if (!hasOwnProvider(config.providers, name)) {
252
+ console.error(`Provider "${name}" is not configured.`);
253
+ process.exit(1);
254
+ }
255
+
256
+ if (name === config.defaultProvider) {
257
+ console.error(`Cannot remove "${name}" — it is the default provider. Change the default first: ocx provider set-default <other>`);
258
+ process.exit(1);
259
+ }
260
+
261
+ if (Object.keys(config.providers).length <= 1) {
262
+ console.error("Cannot remove the last provider.");
263
+ process.exit(1);
264
+ }
265
+
266
+ delete config.providers[name];
267
+ validateAndSave(config);
268
+
269
+
270
+ if (wantsJson) {
271
+ console.log(JSON.stringify({
272
+ action: "removed",
273
+ provider: name,
274
+ remainingProviders: Object.keys(config.providers),
275
+ defaultProvider: config.defaultProvider,
276
+ needsSync: true,
277
+ }, null, 2));
278
+ return;
279
+ }
280
+
281
+ console.log(`✅ Provider "${name}" removed.`);
282
+ }
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // provider show
286
+ // ---------------------------------------------------------------------------
287
+
288
+ function handleShow(args: string[]): void {
289
+ const restArgs = [...args];
290
+ const wantsJson = consumeFlag(restArgs, "--json");
291
+ const name = restArgs[0];
292
+ if (!name || name.startsWith("-")) {
293
+ console.error("Usage: ocx provider show <name> [--json]");
294
+ process.exit(1);
295
+ }
296
+ rejectUnknownArgs(restArgs.slice(1), "Usage: ocx provider show <name> [--json]");
297
+
298
+ const config = loadConfig();
299
+ if (!hasOwnProvider(config.providers, name)) {
300
+ console.error(`Provider "${name}" is not configured.`);
301
+ process.exit(1);
302
+ }
303
+
304
+ const prov = config.providers[name];
305
+ const display = { ...prov, ...(prov.apiKey ? { apiKey: maskSecret(prov.apiKey) } : {}) };
306
+
307
+ if (wantsJson) {
308
+ console.log(JSON.stringify({ name, isDefault: name === config.defaultProvider, ...display }, null, 2));
309
+ return;
310
+ }
311
+
312
+ console.log(`Provider: ${name}${name === config.defaultProvider ? " (default)" : ""}`);
313
+ console.log(` adapter: ${display.adapter}`);
314
+ console.log(` baseUrl: ${display.baseUrl}`);
315
+ if (display.authMode) console.log(` authMode: ${display.authMode}`);
316
+ if (display.apiKey) console.log(` apiKey: ${display.apiKey}`);
317
+ if (display.defaultModel) console.log(` defaultModel: ${display.defaultModel}`);
318
+ if (display.models?.length) console.log(` models: ${display.models.join(", ")}`);
319
+ }
320
+
321
+ // ---------------------------------------------------------------------------
322
+ // provider set-default
323
+ // ---------------------------------------------------------------------------
324
+
325
+ function handleSetDefault(args: string[]): void {
326
+ const restArgs = [...args];
327
+ const wantsJson = consumeFlag(restArgs, "--json");
328
+ const name = restArgs[0];
329
+ if (!name || name.startsWith("-")) {
330
+ console.error("Usage: ocx provider set-default <name> [--json]");
331
+ process.exit(1);
332
+ }
333
+ rejectUnknownArgs(restArgs.slice(1), "Usage: ocx provider set-default <name> [--json]");
334
+
335
+ const config = loadConfig();
336
+ if (!hasOwnProvider(config.providers, name)) {
337
+ console.error(`Provider "${name}" is not configured. Add it first: ocx provider add ${name}`);
338
+ process.exit(1);
339
+ }
340
+
341
+ if (config.defaultProvider === name) {
342
+ if (wantsJson) {
343
+ console.log(JSON.stringify({ action: "noop", provider: name, defaultProvider: name, needsSync: false }, null, 2));
344
+ } else {
345
+ console.log(`"${name}" is already the default provider.`);
346
+ }
347
+ return;
348
+ }
349
+
350
+ config.defaultProvider = name;
351
+ validateAndSave(config);
352
+
353
+
354
+ if (wantsJson) {
355
+ console.log(JSON.stringify({ action: "set-default", provider: name, defaultProvider: name, needsSync: true }, null, 2));
356
+ return;
357
+ }
358
+
359
+ console.log(`✅ Default provider set to "${name}".`);
360
+ }
361
+
362
+ // ---------------------------------------------------------------------------
363
+ // Router (F2 fix: handle help flags internally, like service/codex-shim)
364
+ // ---------------------------------------------------------------------------
365
+
366
+ const PROVIDER_USAGE = `Usage: ocx provider <subcommand>
367
+
368
+ Subcommands:
369
+ list List configured and available providers
370
+ add <name> Add a provider (registry or custom)
371
+ remove <name> Remove a configured provider
372
+ show <name> Show provider config details
373
+ set-default <name> Change the default provider
374
+
375
+ Examples:
376
+ ocx provider list
377
+ ocx provider add anthropic --api-key sk-ant-...
378
+ ocx provider add my-ollama --adapter openai-chat --base-url http://localhost:11434/v1
379
+ ocx provider show anthropic --json
380
+ ocx provider set-default anthropic
381
+ ocx provider remove my-ollama`;
382
+
383
+ export async function handleProviderCommand(args: string[]): Promise<void> {
384
+ const sub = args[0];
385
+
386
+ if (!sub || sub === "help" || hasHelpFlag(args)) {
387
+ console.log(PROVIDER_USAGE);
388
+ process.exit(0);
389
+ }
390
+
391
+ const subArgs = args.slice(1);
392
+
393
+ switch (sub) {
394
+ case "list":
395
+ handleList(subArgs);
396
+ break;
397
+ case "add":
398
+ await handleAdd(subArgs);
399
+ break;
400
+ case "remove":
401
+ handleRemove(subArgs);
402
+ break;
403
+ case "show":
404
+ handleShow(subArgs);
405
+ break;
406
+ case "set-default":
407
+ handleSetDefault(subArgs);
408
+ break;
409
+ default:
410
+ console.error(`Unknown provider subcommand: ${sub}`);
411
+ console.error(PROVIDER_USAGE);
412
+ process.exit(1);
413
+ }
414
+ }
package/src/cli.ts CHANGED
@@ -537,7 +537,33 @@ switch (command) {
537
537
  runGuiUpdateWorker(jobId, channel, args[3] === "restart");
538
538
  break;
539
539
  }
540
- case "help":
540
+ case "restart": {
541
+ await handleStop();
542
+ await handleEnsure();
543
+ break;
544
+ }
545
+ case "health": {
546
+ const healthArgs = args.slice(1);
547
+ const wantsHealthJson = healthArgs.includes("--json");
548
+ const live = await findLiveProxy();
549
+ if (wantsHealthJson) {
550
+ console.log(JSON.stringify({ ok: !!live, pid: live?.pid ?? null, port: live?.port ?? null }));
551
+ } else {
552
+ console.log(live ? `Proxy healthy (PID ${live.pid}, port ${live.port})` : "Proxy not healthy");
553
+ }
554
+ process.exit(live ? 0 : 1);
555
+ }
556
+ case "provider": {
557
+ const { handleProviderCommand } = await import("./cli-provider");
558
+ await handleProviderCommand(args.slice(1));
559
+ break;
560
+ }
561
+ case "models": {
562
+ const { handleModels } = await import("./cli-models");
563
+ handleModels(args.slice(1));
564
+ break;
565
+ }
566
+ case "help":
541
567
  case "--help":
542
568
  case "-h":
543
569
  case undefined: