anygate 0.5.11 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/{chunk-6AFYEDSU.js → chunk-4N4RDHGZ.js} +21 -6
  2. package/dist/{chunk-6AFYEDSU.js.map → chunk-4N4RDHGZ.js.map} +1 -1
  3. package/dist/{chunk-55RHVPE4.js → chunk-BY4AKT2X.js} +1540 -829
  4. package/dist/chunk-BY4AKT2X.js.map +1 -0
  5. package/dist/{chunk-5UF4TVCM.js → chunk-UE2I2ETX.js} +25 -3
  6. package/dist/chunk-UE2I2ETX.js.map +1 -0
  7. package/dist/{chunk-72WNE2IK.js → chunk-UT3JLF3M.js} +2 -13
  8. package/dist/cli.js +1728 -680
  9. package/dist/cli.js.map +1 -1
  10. package/dist/{command-7AI36ICY.js → command-XIDZGWNR.js} +417 -108
  11. package/dist/command-XIDZGWNR.js.map +1 -0
  12. package/dist/{constants-HE3EOSMH.js → constants-WLMOLKEO.js} +9 -3
  13. package/dist/{provider-templates-MNJZTG3Z.js → provider-templates-336QE7ZV.js} +3 -3
  14. package/dist/registry/data/templates/agentrouter.json +19 -0
  15. package/dist/registry/data/templates/cohere.json +5 -2
  16. package/dist/registry/data/templates/fireworks.json +1 -1
  17. package/dist/registry/data/templates/sambanova.json +8 -7
  18. package/dist/ui/dist/assets/index-Bnth1xGD.js +7 -0
  19. package/dist/ui/dist/assets/index-bNoUNC_v.css +1 -0
  20. package/dist/ui/dist/index.html +2 -2
  21. package/package.json +110 -94
  22. package/dist/chunk-55RHVPE4.js.map +0 -1
  23. package/dist/chunk-5UF4TVCM.js.map +0 -1
  24. package/dist/command-7AI36ICY.js.map +0 -1
  25. package/dist/ui/dist/assets/index-DWAOH_B3.css +0 -1
  26. package/dist/ui/dist/assets/index-qxVU3kMy.js +0 -6
  27. /package/dist/{chunk-72WNE2IK.js.map → chunk-UT3JLF3M.js.map} +0 -0
  28. /package/dist/{constants-HE3EOSMH.js.map → constants-WLMOLKEO.js.map} +0 -0
  29. /package/dist/{provider-templates-MNJZTG3Z.js.map → provider-templates-336QE7ZV.js.map} +0 -0
@@ -8,6 +8,8 @@ import {
8
8
  checkForUpdates,
9
9
  completeAntigravityExchange,
10
10
  createGatewayModelCatalog,
11
+ detectConflicts,
12
+ emitAppEvent,
11
13
  favoriteProviderDisplayName,
12
14
  fetchProviderCatalog,
13
15
  filterServerModelsByFavorites,
@@ -29,8 +31,8 @@ import {
29
31
  getServerMaskGatewayIds,
30
32
  getUiDebugLogPath,
31
33
  guiCallbackRedirectUri,
32
- init_config,
33
- init_paths,
34
+ isSecretServiceAvailable,
35
+ loadLaunchPresets,
34
36
  loadPreferences,
35
37
  loadRegistry,
36
38
  loadServerModels,
@@ -41,6 +43,7 @@ import {
41
43
  pollXaiDeviceCodeToken,
42
44
  providerOptionsFromCatalog,
43
45
  readBody,
46
+ readFromCredentialStore,
44
47
  recordLaunchFolder,
45
48
  refreshAllProviderModels,
46
49
  refreshProviderModels,
@@ -51,6 +54,7 @@ import {
51
54
  resolveInputTypes,
52
55
  resolveProviderCredential,
53
56
  resolveServerUpstreamApiKey,
57
+ saveLaunchPresets,
54
58
  saveNativeOAuthCredential,
55
59
  savePreferences,
56
60
  saveProviderCredential,
@@ -63,34 +67,41 @@ import {
63
67
  setServerListenMode,
64
68
  setServerMaskGatewayIds,
65
69
  startServer,
70
+ subscribeToAppEvents,
66
71
  summarizeServerProviders,
67
72
  validateCustomEndpointUrl,
68
73
  writeSecureLogLine
69
- } from "./chunk-55RHVPE4.js";
74
+ } from "./chunk-BY4AKT2X.js";
70
75
  import {
71
76
  BACKENDS,
77
+ GATEWAY_PORT,
72
78
  MAX_MODEL_CATALOG
73
- } from "./chunk-5UF4TVCM.js";
79
+ } from "./chunk-UE2I2ETX.js";
74
80
  import {
75
81
  getTemplateById,
76
- listSupportedTemplates,
82
+ listAddableTemplates,
77
83
  listVisibleOAuthTemplates
78
- } from "./chunk-6AFYEDSU.js";
79
- import "./chunk-72WNE2IK.js";
84
+ } from "./chunk-4N4RDHGZ.js";
85
+ import "./chunk-UT3JLF3M.js";
80
86
 
81
87
  // src/ui/command.ts
82
- init_paths();
83
- import { createServer } from "http";
88
+ import { createServer as createServer2 } from "http";
84
89
  import { execSync } from "child_process";
85
- import { readFileSync, readdirSync, writeFileSync as writeFileSync2, unlinkSync, existsSync as existsSync3, mkdirSync } from "fs";
90
+ import {
91
+ readFileSync,
92
+ readdirSync,
93
+ writeFileSync as writeFileSync2,
94
+ unlinkSync,
95
+ existsSync as existsSync3,
96
+ mkdirSync
97
+ } from "fs";
86
98
  import { join as join2 } from "path";
87
99
  import { fileURLToPath } from "url";
88
100
  import { dirname } from "path";
89
- import pc from "picocolors";
101
+ import pc2 from "picocolors";
90
102
  import * as p from "@clack/prompts";
91
103
 
92
104
  // src/apps/shared/native-launcher.ts
93
- init_config();
94
105
  import { chmodSync, existsSync, mkdtempSync, writeFileSync } from "fs";
95
106
  import { homedir } from "os";
96
107
  import { tmpdir } from "os";
@@ -98,10 +109,38 @@ import { join } from "path";
98
109
  var isWindows = process.platform === "win32";
99
110
  var isMac = process.platform === "darwin";
100
111
  var SUPPORTED_APPS = [
101
- { id: "claude", name: "Claude Code CLI", type: "cli", detectId: "claude", gatewayCommand: "claude", installHint: "npm install -g @anthropic-ai/claude-code" },
102
- { id: "codex", name: "Codex CLI", type: "cli", detectId: "codex", gatewayCommand: "codex", installHint: "npm install -g @openai/codex" },
103
- { id: "gemini", name: "Gemini CLI", type: "cli", detectId: "gemini", gatewayCommand: "gemini", installHint: "npm install -g @google/gemini-cli" },
104
- { id: "agy", name: "Antigravity CLI", type: "cli", detectId: "agy", gatewayCommand: "agy", installHint: "npm install -g @google/antigravity-cli" },
112
+ {
113
+ id: "claude",
114
+ name: "Claude Code CLI",
115
+ type: "cli",
116
+ detectId: "claude",
117
+ gatewayCommand: "claude",
118
+ installHint: "npm install -g @anthropic-ai/claude-code"
119
+ },
120
+ {
121
+ id: "codex",
122
+ name: "Codex CLI",
123
+ type: "cli",
124
+ detectId: "codex",
125
+ gatewayCommand: "codex",
126
+ installHint: "npm install -g @openai/codex"
127
+ },
128
+ {
129
+ id: "gemini",
130
+ name: "Gemini CLI",
131
+ type: "cli",
132
+ detectId: "gemini",
133
+ gatewayCommand: "gemini",
134
+ installHint: "npm install -g @google/gemini-cli"
135
+ },
136
+ {
137
+ id: "agy",
138
+ name: "Antigravity CLI",
139
+ type: "cli",
140
+ detectId: "agy",
141
+ gatewayCommand: "agy",
142
+ installHint: "npm install -g @google/antigravity-cli"
143
+ },
105
144
  {
106
145
  id: "antigravity",
107
146
  name: "Antigravity (App)",
@@ -142,30 +181,21 @@ function fallbackPathsForApp(id, platform = process.platform) {
142
181
  const localAppData = process.env["LOCALAPPDATA"] ?? join(homedir(), "AppData", "Local");
143
182
  switch (id) {
144
183
  case "claude":
145
- return windows ? [
146
- join(appData, "npm", "claude.cmd"),
147
- join(appData, "npm", "claude")
148
- ] : [
184
+ return windows ? [join(appData, "npm", "claude.cmd"), join(appData, "npm", "claude")] : [
149
185
  join(homedir(), ".local", "bin", "claude"),
150
186
  join(homedir(), ".npm", "bin", "claude"),
151
187
  "/usr/local/bin/claude",
152
188
  "/opt/homebrew/bin/claude"
153
189
  ];
154
190
  case "codex":
155
- return windows ? [
156
- join(appData, "npm", "codex.cmd"),
157
- join(appData, "npm", "codex")
158
- ] : [
191
+ return windows ? [join(appData, "npm", "codex.cmd"), join(appData, "npm", "codex")] : [
159
192
  join(homedir(), ".local", "bin", "codex"),
160
193
  join(homedir(), ".npm", "bin", "codex"),
161
194
  "/usr/local/bin/codex",
162
195
  "/opt/homebrew/bin/codex"
163
196
  ];
164
197
  case "gemini":
165
- return windows ? [
166
- join(appData, "npm", "gemini.cmd"),
167
- join(appData, "npm", "gemini")
168
- ] : [
198
+ return windows ? [join(appData, "npm", "gemini.cmd"), join(appData, "npm", "gemini")] : [
169
199
  join(homedir(), ".local", "bin", "gemini"),
170
200
  join(homedir(), ".npm", "bin", "gemini"),
171
201
  "/usr/local/bin/gemini",
@@ -186,7 +216,16 @@ function fallbackPathsForApp(id, platform = process.platform) {
186
216
  if (mac) {
187
217
  return [
188
218
  "/Applications/Antigravity IDE.app/Contents/Resources/app/bin/antigravity-ide",
189
- join(homedir(), "Applications", "Antigravity IDE.app", "Contents", "Resources", "app", "bin", "antigravity-ide")
219
+ join(
220
+ homedir(),
221
+ "Applications",
222
+ "Antigravity IDE.app",
223
+ "Contents",
224
+ "Resources",
225
+ "app",
226
+ "bin",
227
+ "antigravity-ide"
228
+ )
190
229
  ];
191
230
  }
192
231
  if (windows) {
@@ -205,15 +244,9 @@ function fallbackPathsForApp(id, platform = process.platform) {
205
244
  ];
206
245
  }
207
246
  if (windows) {
208
- return [
209
- join(localAppData, "Programs", "Antigravity", "Antigravity.exe")
210
- ];
247
+ return [join(localAppData, "Programs", "Antigravity", "Antigravity.exe")];
211
248
  }
212
- return [
213
- "/opt/antigravity/antigravity",
214
- "/usr/local/bin/antigravity",
215
- "/usr/bin/antigravity"
216
- ];
249
+ return ["/opt/antigravity/antigravity", "/usr/local/bin/antigravity", "/usr/bin/antigravity"];
217
250
  case "claude-app":
218
251
  if (mac) {
219
252
  return [
@@ -251,8 +284,8 @@ function getSupportedApp(id) {
251
284
  }
252
285
  function detectApp(id) {
253
286
  const override = getAppPathOverride(id);
254
- if (override) {
255
- return existsSync(override) ? { installed: true, path: override, pathSource: "override" } : { installed: false, path: override, pathSource: "override" };
287
+ if (override && existsSync(override)) {
288
+ return { installed: true, path: override, pathSource: "override" };
256
289
  }
257
290
  const resolvedPath = findBinaryOnPath(id, FALLBACKS[id] ?? [], { verifyWhichResult: true });
258
291
  if (resolvedPath) {
@@ -278,19 +311,23 @@ function getTerminalLaunchCommand(binPath, args, opts = {}) {
278
311
  const dir2 = mkdtempSync(join(tmpdir(), "anygate-launch-"));
279
312
  const scriptPath2 = join(dir2, "launch.command");
280
313
  const displayCommand = opts.displayCommand ?? [binPath, ...args].join(" ");
281
- writeFileSync(scriptPath2, [
282
- "#!/bin/sh",
283
- `trap 'rm -f "$0"; rmdir "$(dirname "$0")" 2>/dev/null' EXIT`,
284
- "clear",
285
- opts.cwd ? `cd ${quoteShellArg(opts.cwd)} || exit 1` : "",
286
- `printf '%s\\n\\n' ${quoteShellArg(`$ ${displayCommand}`)}`,
287
- fullCmd,
288
- "status=$?",
289
- 'printf "\\nanygate session exited with code %s. Press Return to close this window. " "$status"',
290
- "read _",
291
- 'exit "$status"',
292
- ""
293
- ].join("\n"), { encoding: "utf8", mode: 448 });
314
+ writeFileSync(
315
+ scriptPath2,
316
+ [
317
+ "#!/bin/sh",
318
+ `trap 'rm -f "$0"; rmdir "$(dirname "$0")" 2>/dev/null' EXIT`,
319
+ "clear",
320
+ opts.cwd ? `cd ${quoteShellArg(opts.cwd)} || exit 1` : "",
321
+ `printf '%s\\n\\n' ${quoteShellArg(`$ ${displayCommand}`)}`,
322
+ fullCmd,
323
+ "status=$?",
324
+ 'printf "\\nanygate session exited with code %s. Press Return to close this window. " "$status"',
325
+ "read _",
326
+ 'exit "$status"',
327
+ ""
328
+ ].join("\n"),
329
+ { encoding: "utf8", mode: 448 }
330
+ );
294
331
  chmodSync(scriptPath2, 448);
295
332
  return `open -a Terminal ${quoteShellArg(scriptPath2)}`;
296
333
  }
@@ -300,12 +337,10 @@ function getTerminalLaunchCommand(binPath, args, opts = {}) {
300
337
  }
301
338
  const dir = mkdtempSync(join(tmpdir(), "anygate-launch-"));
302
339
  const scriptPath = join(dir, "launch.sh");
303
- writeFileSync(scriptPath, [
304
- "#!/bin/sh",
305
- runCmd,
306
- "exec sh",
307
- ""
308
- ].join("\n"), { encoding: "utf8", mode: 448 });
340
+ writeFileSync(scriptPath, ["#!/bin/sh", runCmd, "exec sh", ""].join("\n"), {
341
+ encoding: "utf8",
342
+ mode: 448
343
+ });
309
344
  chmodSync(scriptPath, 448);
310
345
  const scriptArg = quoteShellArg(scriptPath);
311
346
  return `x-terminal-emulator -e sh ${scriptArg} || gnome-terminal -- sh ${scriptArg} || xterm -e sh ${scriptArg}`;
@@ -326,6 +361,8 @@ function getGatewayLaunchCommand(appId, options = {}) {
326
361
  }
327
362
  if (options.favoritesCatalog) {
328
363
  args.push("--favorites");
364
+ } else if (options.allModels && options.providerId) {
365
+ args.push("--provider", options.providerId, "--all-models");
329
366
  } else if (options.providerId && options.modelId) {
330
367
  args.push("--provider", options.providerId, "--model", options.modelId);
331
368
  } else if (options.providerId || options.modelId) {
@@ -355,21 +392,20 @@ function getSupportedApps() {
355
392
  }
356
393
 
357
394
  // src/ui/api.ts
358
- init_config();
359
395
  import { exec } from "child_process";
360
396
  import { promisify } from "util";
361
397
  import { existsSync as existsSync2, statSync } from "fs";
362
398
  import { randomUUID } from "crypto";
363
399
 
364
400
  // src/ui/server-control.ts
365
- init_config();
366
401
  var running = null;
367
402
  var startInFlight = null;
368
403
  var SAVED_PASSWORD_CACHE_TTL_MS = 3e4;
369
404
  var hasSavedPasswordCache = null;
370
405
  async function hasSavedPasswordCached() {
371
406
  const now = Date.now();
372
- if (hasSavedPasswordCache && hasSavedPasswordCache.expiresAt > now) return hasSavedPasswordCache.value;
407
+ if (hasSavedPasswordCache && hasSavedPasswordCache.expiresAt > now)
408
+ return hasSavedPasswordCache.value;
373
409
  const value = Boolean(await getSavedServerPassword());
374
410
  hasSavedPasswordCache = { value, expiresAt: now + SAVED_PASSWORD_CACHE_TTL_MS };
375
411
  return value;
@@ -384,9 +420,12 @@ function buildModelRows(models, gateway) {
384
420
  }
385
421
  const rows = [];
386
422
  for (const [providerLabel, groupModels] of groups) {
387
- for (const row of buildDedupedModelRows(groupModels, gateway)) rows.push({ providerLabel, ...row });
423
+ for (const row of buildDedupedModelRows(groupModels, gateway))
424
+ rows.push({ providerLabel, ...row });
388
425
  }
389
- return rows.sort((a, b) => a.providerLabel.localeCompare(b.providerLabel) || a.name.localeCompare(b.name));
426
+ return rows.sort(
427
+ (a, b) => a.providerLabel.localeCompare(b.providerLabel) || a.name.localeCompare(b.name)
428
+ );
390
429
  }
391
430
  async function buildSavedConfig() {
392
431
  return {
@@ -416,11 +455,13 @@ async function getServerStatus() {
416
455
  models: modelRows
417
456
  };
418
457
  if (config.listenMode === "network") {
419
- payload.networkUrls = getLocalIps().map(({ name, address }) => ({
420
- name,
421
- anthropicUrl: `http://${address}:${handle.port}/anthropic`,
422
- openaiUrl: `http://${address}:${handle.port}/openai/v1`
423
- }));
458
+ payload.networkUrls = getLocalIps().map(
459
+ ({ name, address }) => ({
460
+ name,
461
+ anthropicUrl: `http://${address}:${handle.port}/anthropic`,
462
+ openaiUrl: `http://${address}:${handle.port}/openai/v1`
463
+ })
464
+ );
424
465
  payload.apiKey = serverPassword ?? void 0;
425
466
  } else {
426
467
  payload.apiKey = "any non-empty value";
@@ -428,7 +469,8 @@ async function getServerStatus() {
428
469
  return payload;
429
470
  }
430
471
  function startGatewayServer(req) {
431
- if (running) return Promise.resolve({ ok: false, error: "Server is already running. Stop it first." });
472
+ if (running)
473
+ return Promise.resolve({ ok: false, error: "Server is already running. Stop it first." });
432
474
  if (startInFlight) return startInFlight;
433
475
  startInFlight = doStartGatewayServer(req).finally(() => {
434
476
  startInFlight = null;
@@ -441,7 +483,10 @@ async function doStartGatewayServer(req) {
441
483
  }
442
484
  const apiKey = await resolveServerUpstreamApiKey();
443
485
  if (!apiKey) {
444
- return { ok: false, error: "No providers configured. Add a provider in Providers & Keys first." };
486
+ return {
487
+ ok: false,
488
+ error: "No providers configured. Add a provider in Providers & Keys first."
489
+ };
445
490
  }
446
491
  let serverPassword = null;
447
492
  if (req.listenMode === "network") {
@@ -463,13 +508,19 @@ async function doStartGatewayServer(req) {
463
508
  try {
464
509
  models = await loadServerModels();
465
510
  } catch (err) {
466
- return { ok: false, error: `Failed to load models: ${err instanceof Error ? err.message : String(err)}` };
511
+ return {
512
+ ok: false,
513
+ error: `Failed to load models: ${err instanceof Error ? err.message : String(err)}`
514
+ };
467
515
  }
468
516
  if (req.exposedProviders) models = filterServerModelsByProviders(models, req.exposedProviders);
469
517
  if (req.favoritesOnly) {
470
518
  const favorites = loadPreferences().favoriteModels ?? [];
471
519
  if (favorites.length === 0) {
472
- return { ok: false, error: "No favorite models configured. Add favorites in the Favorites tab first." };
520
+ return {
521
+ ok: false,
522
+ error: "No favorite models configured. Add favorites in the Favorites tab first."
523
+ };
473
524
  }
474
525
  models = filterServerModelsByFavorites(models, favorites).slice(0, MAX_MODEL_CATALOG);
475
526
  if (models.length === 0) {
@@ -483,11 +534,14 @@ async function doStartGatewayServer(req) {
483
534
  }
484
535
  }
485
536
  if (models.length === 0) {
486
- return { ok: false, error: "No models to expose. Add providers or adjust the exposed-provider filter." };
537
+ return {
538
+ ok: false,
539
+ error: "No models to expose. Add providers or adjust the exposed-provider filter."
540
+ };
487
541
  }
488
542
  setServerFavoritesOnly(req.favoritesOnly);
489
543
  setServerFreeModelsOnly(req.freeModelsOnly);
490
- if (req.exposedProviders) setServerExposedProviders(req.exposedProviders);
544
+ setServerExposedProviders(req.exposedProviders ?? []);
491
545
  setServerMaskGatewayIds(req.maskGatewayIds);
492
546
  setServerListenMode(req.listenMode);
493
547
  const host = req.listenMode === "network" ? "0.0.0.0" : "127.0.0.1";
@@ -532,6 +586,94 @@ async function stopGatewayServer() {
532
586
  return { ok: true, stopped: false };
533
587
  }
534
588
 
589
+ // src/services/doctor.ts
590
+ import pc from "picocolors";
591
+ import { createServer } from "net";
592
+ function nodeMajor() {
593
+ const raw = process.versions.node.split(".")[0] ?? "0";
594
+ return Number.parseInt(raw, 10) || 0;
595
+ }
596
+ function checkPortFree(port) {
597
+ return new Promise((resolve) => {
598
+ const server = createServer();
599
+ server.once("error", () => resolve(false));
600
+ server.listen(port, () => {
601
+ server.close(() => resolve(true));
602
+ });
603
+ const timer = setTimeout(() => resolve(true), 1500);
604
+ if (typeof timer.unref === "function") timer.unref();
605
+ });
606
+ }
607
+ async function collectDoctorReport(opts = {}) {
608
+ const checks = [];
609
+ const major = nodeMajor();
610
+ checks.push({
611
+ id: "node",
612
+ label: "Node.js version",
613
+ ok: major >= 18,
614
+ detail: `v${process.versions.node} (requires \u2265 18)`,
615
+ critical: true
616
+ });
617
+ let keyringOk = false;
618
+ let keyringDetail = "";
619
+ const platform = process.platform;
620
+ if (platform === "darwin") {
621
+ keyringOk = true;
622
+ keyringDetail = "macOS Keychain Service";
623
+ } else if (platform === "win32") {
624
+ keyringOk = true;
625
+ keyringDetail = "Windows Credential Manager";
626
+ } else if (platform === "linux") {
627
+ keyringOk = await isSecretServiceAvailable();
628
+ keyringDetail = keyringOk ? "Secret Service (GNOME Keyring / KWallet)" : "Secret Service daemon unreachable";
629
+ } else {
630
+ keyringDetail = `Unsupported platform (${platform})`;
631
+ }
632
+ checks.push({
633
+ id: "keychain",
634
+ label: "Secure credential storage",
635
+ ok: keyringOk,
636
+ detail: keyringDetail,
637
+ critical: false
638
+ });
639
+ const storedKey = await readFromCredentialStore();
640
+ checks.push({
641
+ id: "opencode-key",
642
+ label: "OpenCode API key",
643
+ ok: Boolean(storedKey || process.env["OPENCODE_API_KEY"]),
644
+ detail: storedKey ? "Configured in secure store" : process.env["OPENCODE_API_KEY"] ? "Configured via process environment" : "Not set (run `anygate --setup`)",
645
+ critical: false
646
+ });
647
+ const conflicts = detectConflicts();
648
+ const conflictNames = conflicts.map((c) => c.name);
649
+ checks.push({
650
+ id: "env-conflicts",
651
+ label: "Environment variable conflicts",
652
+ ok: conflicts.length === 0,
653
+ detail: conflicts.length === 0 ? "Clean" : `Found ${conflicts.length} conflicting var(s): ${conflictNames.join(", ")}`,
654
+ critical: false
655
+ });
656
+ const portFree = await checkPortFree(GATEWAY_PORT);
657
+ const ownedByUs = !portFree && Boolean(opts.gatewayRunning);
658
+ checks.push({
659
+ id: "gateway-port",
660
+ label: `Local gateway port (${GATEWAY_PORT})`,
661
+ ok: portFree || ownedByUs,
662
+ detail: portFree ? "Available" : ownedByUs ? "In use by the anygate gateway" : "In use by another process",
663
+ critical: false
664
+ });
665
+ return {
666
+ ok: !checks.some((c) => c.critical && !c.ok),
667
+ checks,
668
+ nodeVersion: process.versions.node,
669
+ keychain: { available: keyringOk, note: keyringDetail },
670
+ conflictingEnvVars: conflictNames,
671
+ gatewayPort: GATEWAY_PORT,
672
+ gatewayPortAvailable: portFree,
673
+ gatewayPortOwnedByAnygate: ownedByUs
674
+ };
675
+ }
676
+
535
677
  // src/ui/api.ts
536
678
  var execAsync = promisify(exec);
537
679
  var MODELS_TIMEOUT_MS = 3e4;
@@ -569,6 +711,14 @@ function traceUi(opts, message) {
569
711
  writeSecureLogLine(opts.traceLogPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
570
712
  }
571
713
  function notifyServerLifecycle(opts, event) {
714
+ emitAppEvent(
715
+ event.type === "started" ? {
716
+ type: "server",
717
+ running: true,
718
+ listenMode: event.listenMode,
719
+ modelCount: event.modelCount
720
+ } : { type: "server", running: false }
721
+ );
572
722
  try {
573
723
  opts.onServerLifecycle?.(event);
574
724
  } catch {
@@ -607,9 +757,9 @@ function handleUiApiRequest(req, res, opts = {}) {
607
757
  handleAddCustomProvider(req, res);
608
758
  } else if (url === "/api/providers/delete" && req.method === "POST") {
609
759
  handleDeleteProvider(req, res);
610
- } else if (url === "/api/providers/auth/start" && req.method === "POST") {
760
+ } else if ((url === "/api/providers/oauth/start" || url === "/api/providers/auth/start") && req.method === "POST") {
611
761
  handleOAuthStart(req, res);
612
- } else if (url.startsWith("/api/providers/auth/status") && req.method === "GET") {
762
+ } else if ((url.startsWith("/api/providers/oauth/status") || url.startsWith("/api/providers/auth/status")) && req.method === "GET") {
613
763
  handleOAuthStatus(req, res);
614
764
  } else if (url.startsWith("/auth/callback") && req.method === "GET") {
615
765
  handleOAuthCallback(req, res);
@@ -629,12 +779,111 @@ function handleUiApiRequest(req, res, opts = {}) {
629
779
  handleStartServer(req, res, opts);
630
780
  } else if (url === "/api/server/stop" && req.method === "POST") {
631
781
  handleStopServer(res, opts);
782
+ } else if (url === "/api/events" && req.method === "GET") {
783
+ handleEventStream(req, res);
784
+ } else if (url === "/api/presets" && req.method === "GET") {
785
+ handleGetPresets(res);
786
+ } else if (url === "/api/presets" && req.method === "POST") {
787
+ handleSavePresets(req, res);
788
+ } else if (url === "/api/health" && req.method === "GET") {
789
+ handleGetHealth(res);
632
790
  } else if (url.startsWith("/api/analytics") && req.method === "GET") {
633
791
  handleGetAnalytics(res, req);
634
792
  } else {
635
793
  sendJson(res, 404, { error: "Not found" });
636
794
  }
637
795
  }
796
+ var SSE_KEEPALIVE_MS = 25e3;
797
+ function handleEventStream(req, res) {
798
+ res.writeHead(200, {
799
+ "Content-Type": "text/event-stream; charset=utf-8",
800
+ "Cache-Control": "no-cache, no-transform",
801
+ Connection: "keep-alive",
802
+ // Defeats proxy buffering that would otherwise delay events.
803
+ "X-Accel-Buffering": "no"
804
+ });
805
+ const send = (event) => {
806
+ if (!res.writableEnded) res.write(`data: ${JSON.stringify(event)}
807
+
808
+ `);
809
+ };
810
+ res.write(": connected\n\n");
811
+ const unsubscribe = subscribeToAppEvents(send);
812
+ const keepalive = setInterval(() => {
813
+ if (!res.writableEnded) res.write(": keepalive\n\n");
814
+ }, SSE_KEEPALIVE_MS);
815
+ if (typeof keepalive.unref === "function") keepalive.unref();
816
+ const cleanup = () => {
817
+ clearInterval(keepalive);
818
+ unsubscribe();
819
+ };
820
+ req.on("close", cleanup);
821
+ req.on("error", cleanup);
822
+ res.on("close", cleanup);
823
+ }
824
+ function sanitizePreset(raw) {
825
+ if (!raw || typeof raw !== "object") return null;
826
+ const p2 = raw;
827
+ const str = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
828
+ const id = str(p2["id"]);
829
+ const appId = str(p2["appId"]);
830
+ if (!id || !appId) return null;
831
+ const flags = Array.isArray(p2["flags"]) ? p2["flags"].filter((f) => typeof f === "string") : void 0;
832
+ const preset = { id, appId };
833
+ const providerId = str(p2["providerId"]);
834
+ const modelId = str(p2["modelId"]);
835
+ const folder = str(p2["folder"]);
836
+ const label = str(p2["label"]);
837
+ if (providerId) preset.providerId = providerId;
838
+ if (modelId) preset.modelId = modelId;
839
+ if (folder) preset.folder = folder;
840
+ if (label) preset.label = label;
841
+ if (flags?.length) preset.flags = flags;
842
+ return preset;
843
+ }
844
+ function handleGetPresets(res) {
845
+ try {
846
+ sendJson(res, 200, { presets: loadLaunchPresets() });
847
+ } catch (err) {
848
+ sendJson(res, 500, { error: String(err) });
849
+ }
850
+ }
851
+ async function handleSavePresets(req, res) {
852
+ try {
853
+ const body = JSON.parse(await readBody(req));
854
+ if (!Array.isArray(body.presets)) {
855
+ sendJson(res, 400, { error: "presets must be an array" });
856
+ return;
857
+ }
858
+ const seen = /* @__PURE__ */ new Map();
859
+ for (const raw of body.presets) {
860
+ const preset = sanitizePreset(raw);
861
+ if (preset) seen.set(preset.id, preset);
862
+ }
863
+ sendJson(res, 200, { ok: true, presets: saveLaunchPresets([...seen.values()]) });
864
+ } catch (err) {
865
+ sendJson(res, 500, { error: String(err) });
866
+ }
867
+ }
868
+ async function handleGetHealth(res) {
869
+ try {
870
+ const gatewayRunning = (await getServerStatus()).running;
871
+ const report = await collectDoctorReport({ gatewayRunning });
872
+ sendJson(res, 200, {
873
+ ok: report.ok,
874
+ checks: report.checks,
875
+ nodeVersion: report.nodeVersion,
876
+ keychain: report.keychain,
877
+ conflictingEnvVars: report.conflictingEnvVars,
878
+ gatewayPort: report.gatewayPort,
879
+ // Busy-but-ours counts as available to the UI: nothing is blocking a start.
880
+ port17645Available: report.gatewayPortAvailable || report.gatewayPortOwnedByAnygate,
881
+ gatewayPortOwnedByAnygate: report.gatewayPortOwnedByAnygate
882
+ });
883
+ } catch (err) {
884
+ sendJson(res, 500, { error: String(err) });
885
+ }
886
+ }
638
887
  async function handleGetUpdateStatus(res) {
639
888
  sendJson(res, 200, await checkForUpdates());
640
889
  }
@@ -650,7 +899,8 @@ async function handlePostConfig(req, res) {
650
899
  const body = JSON.parse(await readBody(req));
651
900
  const update = {};
652
901
  if (Array.isArray(body.favoriteModels)) update.favoriteModels = body.favoriteModels;
653
- if (Array.isArray(body.antigravityCliFavoriteModels)) update.antigravityCliFavoriteModels = body.antigravityCliFavoriteModels;
902
+ if (Array.isArray(body.antigravityCliFavoriteModels))
903
+ update.antigravityCliFavoriteModels = body.antigravityCliFavoriteModels;
654
904
  if (Object.keys(update).length > 0) savePreferences(update);
655
905
  sendJson(res, 200, { ok: true });
656
906
  } catch (err) {
@@ -671,7 +921,9 @@ async function handleGetModels(res) {
671
921
  try {
672
922
  const catalog = await fetchModelsWithTimeout();
673
923
  const registry = loadRegistry();
674
- const rawCountById = new Map(registry.providers.map((p2) => [p2.id, p2.modelsCache?.models.length ?? 0]));
924
+ const rawCountById = new Map(
925
+ registry.providers.map((p2) => [p2.id, p2.modelsCache?.models.length ?? 0])
926
+ );
675
927
  const providers = catalog.map((p2) => ({
676
928
  id: p2.id,
677
929
  name: p2.name,
@@ -704,7 +956,11 @@ async function handleGetModels(res) {
704
956
  providers.push({
705
957
  id: rp.id,
706
958
  name: rp.name,
707
- favoriteName: favoriteProviderDisplayName({ id: rp.id, name: rp.name, authType: rp.authType }),
959
+ favoriteName: favoriteProviderDisplayName({
960
+ id: rp.id,
961
+ name: rp.name,
962
+ authType: rp.authType
963
+ }),
708
964
  hasKey: true,
709
965
  freeAccess: false,
710
966
  authType: "oauth",
@@ -1030,16 +1286,29 @@ async function handlePostKeys(req, res) {
1030
1286
  }
1031
1287
  }
1032
1288
  var CUSTOM_TEMPLATES = [
1033
- { id: "__custom_openai__", name: "Custom OpenAI-compatible", signupUrl: null, authType: "api", custom: true },
1034
- { id: "__custom_anthropic__", name: "Custom Anthropic-compatible", signupUrl: null, authType: "api", custom: true }
1289
+ {
1290
+ id: "__custom_openai__",
1291
+ name: "Custom OpenAI-compatible",
1292
+ signupUrl: null,
1293
+ authType: "api",
1294
+ custom: true
1295
+ },
1296
+ {
1297
+ id: "__custom_anthropic__",
1298
+ name: "Custom Anthropic-compatible",
1299
+ signupUrl: null,
1300
+ authType: "api",
1301
+ custom: true
1302
+ }
1035
1303
  ];
1036
1304
  function handleGetTemplates(res) {
1037
1305
  const registry = loadRegistry();
1038
1306
  const configured = new Set(registry.providers.map((p2) => p2.id));
1039
- const apiTemplates = listSupportedTemplates().map((t) => ({
1307
+ const apiTemplates = listAddableTemplates(configured).map((t) => ({
1040
1308
  id: t.id,
1041
1309
  name: t.name,
1042
1310
  signupUrl: t.signupUrl ?? null,
1311
+ signupNote: t.signupNote ?? null,
1043
1312
  authType: t.authType,
1044
1313
  anonymousFreeModels: t.anonymousFreeModels ?? false,
1045
1314
  urlPrompt: t.urlPrompt ?? null,
@@ -1098,8 +1367,8 @@ async function handleAddProvider(req, res) {
1098
1367
  sendJson(res, 400, { error: "templateId required" });
1099
1368
  return;
1100
1369
  }
1101
- const { listSupportedTemplates: listSupportedTemplates2 } = await import("./provider-templates-MNJZTG3Z.js");
1102
- const template = listSupportedTemplates2().find((t) => t.id === templateId);
1370
+ const { listSupportedTemplates } = await import("./provider-templates-336QE7ZV.js");
1371
+ const template = listSupportedTemplates().find((t) => t.id === templateId);
1103
1372
  if (!template) {
1104
1373
  sendJson(res, 404, { error: `Template '${templateId}' not found` });
1105
1374
  return;
@@ -1109,22 +1378,26 @@ async function handleAddProvider(req, res) {
1109
1378
  sendJson(res, 400, { error: "key must be a non-empty string" });
1110
1379
  return;
1111
1380
  }
1112
- const keyText = template.apiKeyOptional && !rawKey && !template.anonymousFreeModels ? template.id : rawKey;
1381
+ const keyText = rawKey;
1113
1382
  let baseUrlOverride;
1114
1383
  if (template.urlPrompt) {
1115
- baseUrlOverride = typeof baseUrl === "string" ? baseUrl.trim() : "";
1384
+ baseUrlOverride = (typeof baseUrl === "string" ? baseUrl.trim() : "") || (template.defaultBaseUrl ?? "");
1116
1385
  if (!baseUrlOverride) {
1117
1386
  sendJson(res, 400, { error: "baseUrl required" });
1118
1387
  return;
1119
1388
  }
1120
1389
  const usesHttp = /^http:\/\//i.test(baseUrlOverride);
1121
- const valid = await validateCustomEndpointUrl(baseUrlOverride, { allowInsecureLocal: usesHttp });
1390
+ const valid = await validateCustomEndpointUrl(baseUrlOverride, {
1391
+ allowInsecureLocal: usesHttp
1392
+ });
1122
1393
  if (!valid.ok) {
1123
1394
  sendJson(res, 400, { error: valid.error ?? "Invalid URL", hint: valid.hint });
1124
1395
  return;
1125
1396
  }
1126
1397
  }
1127
- const result = await addProviderFromTemplate(template, keyText, { baseUrl: baseUrlOverride });
1398
+ const result = await addProviderFromTemplate(template, keyText, {
1399
+ baseUrl: baseUrlOverride
1400
+ });
1128
1401
  if (result.added) {
1129
1402
  sendJson(res, 200, { ok: true, name: template.name, count: result.modelCount ?? 0 });
1130
1403
  } else {
@@ -1152,7 +1425,11 @@ async function handleRefreshAll(res) {
1152
1425
  reason: r.reason
1153
1426
  };
1154
1427
  });
1155
- sendJson(res, 200, { ok: true, providers: summary, total: summary.reduce((n, p2) => n + p2.count, 0) });
1428
+ sendJson(res, 200, {
1429
+ ok: true,
1430
+ providers: summary,
1431
+ total: summary.reduce((n, p2) => n + p2.count, 0)
1432
+ });
1156
1433
  } catch (err) {
1157
1434
  sendJson(res, 500, { ok: false, error: String(err) });
1158
1435
  }
@@ -1215,14 +1492,21 @@ async function handleOAuthStart(req, res) {
1215
1492
  const body = JSON.parse(await readBody(req));
1216
1493
  const { providerId } = body;
1217
1494
  if (!providerId || !NATIVE_OAUTH_PROVIDER_IDS.has(providerId)) {
1218
- sendJson(res, 400, { error: `providerId must be one of: ${[...NATIVE_OAUTH_PROVIDER_IDS].join(", ")}` });
1495
+ sendJson(res, 400, {
1496
+ error: `providerId must be one of: ${[...NATIVE_OAUTH_PROVIDER_IDS].join(", ")}`
1497
+ });
1219
1498
  return;
1220
1499
  }
1221
1500
  const sessionId = randomUUID();
1222
1501
  if (providerId === "xai-oauth") {
1223
1502
  const device2 = await requestXaiDeviceCode();
1224
1503
  const url2 = device2.verification_uri_complete ?? device2.verification_uri;
1225
- const session2 = { status: "pending", url: url2, userCode: device2.user_code, providerId };
1504
+ const session2 = {
1505
+ status: "pending",
1506
+ url: url2,
1507
+ userCode: device2.user_code,
1508
+ providerId
1509
+ };
1226
1510
  oauthSessions.set(sessionId, session2);
1227
1511
  pollXaiDeviceCodeToken(device2).then(async (tokens) => {
1228
1512
  await saveNativeOAuthCredential(providerId, tokens);
@@ -1237,7 +1521,12 @@ async function handleOAuthStart(req, res) {
1237
1521
  if (providerId === "github-copilot") {
1238
1522
  const device2 = await requestGithubDeviceCode();
1239
1523
  const url2 = device2.verification_uri;
1240
- const session2 = { status: "pending", url: url2, userCode: device2.user_code, providerId };
1524
+ const session2 = {
1525
+ status: "pending",
1526
+ url: url2,
1527
+ userCode: device2.user_code,
1528
+ providerId
1529
+ };
1241
1530
  oauthSessions.set(sessionId, session2);
1242
1531
  pollGithubDeviceCodeToken(device2).then(async (tokens) => {
1243
1532
  await saveNativeOAuthCredential(providerId, tokens);
@@ -1389,7 +1678,7 @@ var AGY_APP_IDS = /* @__PURE__ */ new Set(["antigravity", "agy", "antigravity-id
1389
1678
  async function handleLaunchApp(req, res, opts) {
1390
1679
  try {
1391
1680
  const body = JSON.parse(await readBody(req));
1392
- const { appId, favorites, favoritesCatalog, cwd } = body;
1681
+ const { appId, favorites, favoritesCatalog, allModels, cwd } = body;
1393
1682
  let { providerId, modelId } = body;
1394
1683
  if (!appId) {
1395
1684
  sendJson(res, 400, { error: "Missing appId" });
@@ -1404,14 +1693,22 @@ async function handleLaunchApp(req, res, opts) {
1404
1693
  sendJson(res, 400, { error: `App ${appId} is not installed on this system.` });
1405
1694
  return;
1406
1695
  }
1407
- if (!favorites && (providerId || modelId) && (!providerId || !modelId)) {
1408
- sendJson(res, 400, { error: "Both providerId and modelId are required to launch a specific anygate model." });
1696
+ if (allModels && !providerId) {
1697
+ sendJson(res, 400, { error: "providerId is required when allModels is true." });
1698
+ return;
1699
+ }
1700
+ if (!allModels && !favorites && (providerId || modelId) && (!providerId || !modelId)) {
1701
+ sendJson(res, 400, {
1702
+ error: "Both providerId and modelId are required to launch a specific anygate model."
1703
+ });
1409
1704
  return;
1410
1705
  }
1411
1706
  const fullCatalog = Boolean(favoritesCatalog);
1412
1707
  if (fullCatalog) {
1413
1708
  providerId = void 0;
1414
1709
  modelId = void 0;
1710
+ } else if (allModels) {
1711
+ modelId = void 0;
1415
1712
  } else if (favorites && !providerId && !modelId) {
1416
1713
  const prefs = loadPreferences();
1417
1714
  const favList = AGY_APP_IDS.has(appId) ? prefs.antigravityCliFavoriteModels ?? [] : prefs.favoriteModels ?? [];
@@ -1436,13 +1733,14 @@ async function handleLaunchApp(req, res, opts) {
1436
1733
  const launchCmd = getGatewayLaunchCommand(appId, {
1437
1734
  providerId,
1438
1735
  modelId,
1736
+ allModels: Boolean(allModels) && Boolean(providerId),
1439
1737
  favoritesCatalog: fullCatalog,
1440
1738
  cwd: launchFolder,
1441
1739
  trace: opts.trace
1442
1740
  });
1443
1741
  traceUi(
1444
1742
  opts,
1445
- `launch app=${appId} provider=${providerId ?? ""} model=${modelId ?? ""} favorites=${Boolean(favorites)} catalog=${fullCatalog} cwd=${launchFolder ?? ""} command=${launchCmd}`
1743
+ `launch app=${appId} provider=${providerId ?? ""} model=${modelId ?? ""} allModels=${Boolean(allModels)} favorites=${Boolean(favorites)} catalog=${fullCatalog} cwd=${launchFolder ?? ""} command=${launchCmd}`
1446
1744
  );
1447
1745
  exec(launchCmd, (err) => {
1448
1746
  if (err) {
@@ -1464,7 +1762,9 @@ async function handleSetAppPath(req, res) {
1464
1762
  return;
1465
1763
  }
1466
1764
  if (path !== null && (typeof path !== "string" || !path.trim())) {
1467
- sendJson(res, 400, { error: "path must be a non-empty string, or null to clear the override." });
1765
+ sendJson(res, 400, {
1766
+ error: "path must be a non-empty string, or null to clear the override."
1767
+ });
1468
1768
  return;
1469
1769
  }
1470
1770
  const trimmed = typeof path === "string" ? path.trim() : null;
@@ -1595,12 +1895,16 @@ async function handleBrowseFolder(res) {
1595
1895
  const { stdout } = await execAsync(`powershell -NoProfile -Sta -EncodedCommand ${encoded}`);
1596
1896
  resultPath = stdout.trim();
1597
1897
  } catch (err) {
1598
- sendJson(res, 500, { error: `Failed to open folder picker: ${err instanceof Error ? err.message : String(err)}` });
1898
+ sendJson(res, 500, {
1899
+ error: `Failed to open folder picker: ${err instanceof Error ? err.message : String(err)}`
1900
+ });
1599
1901
  return;
1600
1902
  }
1601
1903
  } else {
1602
1904
  try {
1603
- const { stdout } = await execAsync('zenity --file-selection --directory --title="Select launch folder"');
1905
+ const { stdout } = await execAsync(
1906
+ 'zenity --file-selection --directory --title="Select launch folder"'
1907
+ );
1604
1908
  resultPath = stdout.trim();
1605
1909
  } catch {
1606
1910
  try {
@@ -1679,7 +1983,10 @@ function checkExistingServer() {
1679
1983
  let processExists = false;
1680
1984
  if (isWindows2) {
1681
1985
  try {
1682
- const output = execSync(`tasklist /FI "PID eq ${pid}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] });
1986
+ const output = execSync(`tasklist /FI "PID eq ${pid}"`, {
1987
+ encoding: "utf8",
1988
+ stdio: ["pipe", "pipe", "ignore"]
1989
+ });
1683
1990
  processExists = output.includes(String(pid));
1684
1991
  } catch {
1685
1992
  processExists = false;
@@ -1718,7 +2025,7 @@ async function runUiCommand(opts = {}) {
1718
2025
  const existing = checkExistingServer();
1719
2026
  if (existing) {
1720
2027
  console.log(`
1721
- ${pc.bold("anygate UI")} already running at ${pc.cyan(existing)}
2028
+ ${pc2.bold("anygate UI")} already running at ${pc2.cyan(existing)}
1722
2029
  `);
1723
2030
  return 0;
1724
2031
  }
@@ -1729,7 +2036,7 @@ async function runUiCommand(opts = {}) {
1729
2036
  const traceLogPath = opts.trace ? getUiDebugLogPath() : void 0;
1730
2037
  const trace = traceLogPath ? makeTraceLogger(traceLogPath) : void 0;
1731
2038
  trace?.("ui server starting");
1732
- const server = createServer((req, res) => {
2039
+ const server = createServer2((req, res) => {
1733
2040
  const url2 = req.url ?? "/";
1734
2041
  res.setHeader("X-Content-Type-Options", "nosniff");
1735
2042
  if (isUiApiRoute(url2)) {
@@ -1796,12 +2103,14 @@ async function runUiCommand(opts = {}) {
1796
2103
  process.on("SIGTERM", () => {
1797
2104
  void handleSignal("SIGTERM");
1798
2105
  });
1799
- console.log(`
1800
- ${pc.bold("anygate UI")} ${pc.cyan(url)}
1801
- ${pc.dim("Press Ctrl+C to stop")}
1802
- `);
2106
+ console.log(
2107
+ `
2108
+ ${pc2.bold("anygate UI")} ${pc2.cyan(url)}
2109
+ ${pc2.dim("Press Ctrl+C to stop")}
2110
+ `
2111
+ );
1803
2112
  if (traceLogPath) {
1804
- console.log(` ${pc.dim(`Trace log: ${traceLogPath}`)}
2113
+ console.log(` ${pc2.dim(`Trace log: ${traceLogPath}`)}
1805
2114
  `);
1806
2115
  trace?.(`ui server listening ${url}`);
1807
2116
  }
@@ -1822,4 +2131,4 @@ export {
1822
2131
  resolveUiShutdownDecision,
1823
2132
  runUiCommand
1824
2133
  };
1825
- //# sourceMappingURL=command-7AI36ICY.js.map
2134
+ //# sourceMappingURL=command-XIDZGWNR.js.map