anygate 0.6.0 → 0.6.2

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.
@@ -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,6 +31,8 @@ import {
29
31
  getServerMaskGatewayIds,
30
32
  getUiDebugLogPath,
31
33
  guiCallbackRedirectUri,
34
+ isSecretServiceAvailable,
35
+ loadLaunchPresets,
32
36
  loadPreferences,
33
37
  loadRegistry,
34
38
  loadServerModels,
@@ -39,6 +43,7 @@ import {
39
43
  pollXaiDeviceCodeToken,
40
44
  providerOptionsFromCatalog,
41
45
  readBody,
46
+ readFromCredentialStore,
42
47
  recordLaunchFolder,
43
48
  refreshAllProviderModels,
44
49
  refreshProviderModels,
@@ -49,6 +54,7 @@ import {
49
54
  resolveInputTypes,
50
55
  resolveProviderCredential,
51
56
  resolveServerUpstreamApiKey,
57
+ saveLaunchPresets,
52
58
  saveNativeOAuthCredential,
53
59
  savePreferences,
54
60
  saveProviderCredential,
@@ -61,23 +67,25 @@ import {
61
67
  setServerListenMode,
62
68
  setServerMaskGatewayIds,
63
69
  startServer,
70
+ subscribeToAppEvents,
64
71
  summarizeServerProviders,
65
72
  validateCustomEndpointUrl,
66
73
  writeSecureLogLine
67
- } from "./chunk-CRK6YGKY.js";
74
+ } from "./chunk-EMBABL33.js";
68
75
  import {
69
76
  BACKENDS,
77
+ GATEWAY_PORT,
70
78
  MAX_MODEL_CATALOG
71
- } from "./chunk-QLHVQYQN.js";
79
+ } from "./chunk-S5WL3M5G.js";
72
80
  import {
73
81
  getTemplateById,
74
- listSupportedTemplates,
82
+ listAddableTemplates,
75
83
  listVisibleOAuthTemplates
76
- } from "./chunk-VGM6EBG4.js";
84
+ } from "./chunk-4N4RDHGZ.js";
77
85
  import "./chunk-UT3JLF3M.js";
78
86
 
79
87
  // src/ui/command.ts
80
- import { createServer } from "http";
88
+ import { createServer as createServer2 } from "http";
81
89
  import { execSync } from "child_process";
82
90
  import {
83
91
  readFileSync,
@@ -90,7 +98,7 @@ import {
90
98
  import { join as join2 } from "path";
91
99
  import { fileURLToPath } from "url";
92
100
  import { dirname } from "path";
93
- import pc from "picocolors";
101
+ import pc2 from "picocolors";
94
102
  import * as p from "@clack/prompts";
95
103
 
96
104
  // src/apps/shared/native-launcher.ts
@@ -276,8 +284,8 @@ function getSupportedApp(id) {
276
284
  }
277
285
  function detectApp(id) {
278
286
  const override = getAppPathOverride(id);
279
- if (override) {
280
- 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" };
281
289
  }
282
290
  const resolvedPath = findBinaryOnPath(id, FALLBACKS[id] ?? [], { verifyWhichResult: true });
283
291
  if (resolvedPath) {
@@ -353,6 +361,8 @@ function getGatewayLaunchCommand(appId, options = {}) {
353
361
  }
354
362
  if (options.favoritesCatalog) {
355
363
  args.push("--favorites");
364
+ } else if (options.allModels && options.providerId) {
365
+ args.push("--provider", options.providerId, "--all-models");
356
366
  } else if (options.providerId && options.modelId) {
357
367
  args.push("--provider", options.providerId, "--model", options.modelId);
358
368
  } else if (options.providerId || options.modelId) {
@@ -531,7 +541,7 @@ async function doStartGatewayServer(req) {
531
541
  }
532
542
  setServerFavoritesOnly(req.favoritesOnly);
533
543
  setServerFreeModelsOnly(req.freeModelsOnly);
534
- if (req.exposedProviders) setServerExposedProviders(req.exposedProviders);
544
+ setServerExposedProviders(req.exposedProviders ?? []);
535
545
  setServerMaskGatewayIds(req.maskGatewayIds);
536
546
  setServerListenMode(req.listenMode);
537
547
  const host = req.listenMode === "network" ? "0.0.0.0" : "127.0.0.1";
@@ -576,6 +586,94 @@ async function stopGatewayServer() {
576
586
  return { ok: true, stopped: false };
577
587
  }
578
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
+
579
677
  // src/ui/api.ts
580
678
  var execAsync = promisify(exec);
581
679
  var MODELS_TIMEOUT_MS = 3e4;
@@ -613,6 +711,14 @@ function traceUi(opts, message) {
613
711
  writeSecureLogLine(opts.traceLogPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${message}`);
614
712
  }
615
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
+ );
616
722
  try {
617
723
  opts.onServerLifecycle?.(event);
618
724
  } catch {
@@ -651,9 +757,9 @@ function handleUiApiRequest(req, res, opts = {}) {
651
757
  handleAddCustomProvider(req, res);
652
758
  } else if (url === "/api/providers/delete" && req.method === "POST") {
653
759
  handleDeleteProvider(req, res);
654
- } 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") {
655
761
  handleOAuthStart(req, res);
656
- } 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") {
657
763
  handleOAuthStatus(req, res);
658
764
  } else if (url.startsWith("/auth/callback") && req.method === "GET") {
659
765
  handleOAuthCallback(req, res);
@@ -673,12 +779,111 @@ function handleUiApiRequest(req, res, opts = {}) {
673
779
  handleStartServer(req, res, opts);
674
780
  } else if (url === "/api/server/stop" && req.method === "POST") {
675
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);
676
790
  } else if (url.startsWith("/api/analytics") && req.method === "GET") {
677
791
  handleGetAnalytics(res, req);
678
792
  } else {
679
793
  sendJson(res, 404, { error: "Not found" });
680
794
  }
681
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
+ }
682
887
  async function handleGetUpdateStatus(res) {
683
888
  sendJson(res, 200, await checkForUpdates());
684
889
  }
@@ -1099,7 +1304,7 @@ var CUSTOM_TEMPLATES = [
1099
1304
  function handleGetTemplates(res) {
1100
1305
  const registry = loadRegistry();
1101
1306
  const configured = new Set(registry.providers.map((p2) => p2.id));
1102
- const apiTemplates = listSupportedTemplates().map((t) => ({
1307
+ const apiTemplates = listAddableTemplates(configured).map((t) => ({
1103
1308
  id: t.id,
1104
1309
  name: t.name,
1105
1310
  signupUrl: t.signupUrl ?? null,
@@ -1162,8 +1367,8 @@ async function handleAddProvider(req, res) {
1162
1367
  sendJson(res, 400, { error: "templateId required" });
1163
1368
  return;
1164
1369
  }
1165
- const { listSupportedTemplates: listSupportedTemplates2 } = await import("./provider-templates-CRQJII3Z.js");
1166
- 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);
1167
1372
  if (!template) {
1168
1373
  sendJson(res, 404, { error: `Template '${templateId}' not found` });
1169
1374
  return;
@@ -1173,10 +1378,10 @@ async function handleAddProvider(req, res) {
1173
1378
  sendJson(res, 400, { error: "key must be a non-empty string" });
1174
1379
  return;
1175
1380
  }
1176
- const keyText = template.apiKeyOptional && !rawKey && !template.anonymousFreeModels ? template.id : rawKey;
1381
+ const keyText = rawKey;
1177
1382
  let baseUrlOverride;
1178
1383
  if (template.urlPrompt) {
1179
- baseUrlOverride = typeof baseUrl === "string" ? baseUrl.trim() : "";
1384
+ baseUrlOverride = (typeof baseUrl === "string" ? baseUrl.trim() : "") || (template.defaultBaseUrl ?? "");
1180
1385
  if (!baseUrlOverride) {
1181
1386
  sendJson(res, 400, { error: "baseUrl required" });
1182
1387
  return;
@@ -1473,7 +1678,7 @@ var AGY_APP_IDS = /* @__PURE__ */ new Set(["antigravity", "agy", "antigravity-id
1473
1678
  async function handleLaunchApp(req, res, opts) {
1474
1679
  try {
1475
1680
  const body = JSON.parse(await readBody(req));
1476
- const { appId, favorites, favoritesCatalog, cwd } = body;
1681
+ const { appId, favorites, favoritesCatalog, allModels, cwd } = body;
1477
1682
  let { providerId, modelId } = body;
1478
1683
  if (!appId) {
1479
1684
  sendJson(res, 400, { error: "Missing appId" });
@@ -1488,7 +1693,11 @@ async function handleLaunchApp(req, res, opts) {
1488
1693
  sendJson(res, 400, { error: `App ${appId} is not installed on this system.` });
1489
1694
  return;
1490
1695
  }
1491
- if (!favorites && (providerId || modelId) && (!providerId || !modelId)) {
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)) {
1492
1701
  sendJson(res, 400, {
1493
1702
  error: "Both providerId and modelId are required to launch a specific anygate model."
1494
1703
  });
@@ -1498,6 +1707,8 @@ async function handleLaunchApp(req, res, opts) {
1498
1707
  if (fullCatalog) {
1499
1708
  providerId = void 0;
1500
1709
  modelId = void 0;
1710
+ } else if (allModels) {
1711
+ modelId = void 0;
1501
1712
  } else if (favorites && !providerId && !modelId) {
1502
1713
  const prefs = loadPreferences();
1503
1714
  const favList = AGY_APP_IDS.has(appId) ? prefs.antigravityCliFavoriteModels ?? [] : prefs.favoriteModels ?? [];
@@ -1522,13 +1733,14 @@ async function handleLaunchApp(req, res, opts) {
1522
1733
  const launchCmd = getGatewayLaunchCommand(appId, {
1523
1734
  providerId,
1524
1735
  modelId,
1736
+ allModels: Boolean(allModels) && Boolean(providerId),
1525
1737
  favoritesCatalog: fullCatalog,
1526
1738
  cwd: launchFolder,
1527
1739
  trace: opts.trace
1528
1740
  });
1529
1741
  traceUi(
1530
1742
  opts,
1531
- `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}`
1532
1744
  );
1533
1745
  exec(launchCmd, (err) => {
1534
1746
  if (err) {
@@ -1813,7 +2025,7 @@ async function runUiCommand(opts = {}) {
1813
2025
  const existing = checkExistingServer();
1814
2026
  if (existing) {
1815
2027
  console.log(`
1816
- ${pc.bold("anygate UI")} already running at ${pc.cyan(existing)}
2028
+ ${pc2.bold("anygate UI")} already running at ${pc2.cyan(existing)}
1817
2029
  `);
1818
2030
  return 0;
1819
2031
  }
@@ -1824,7 +2036,7 @@ async function runUiCommand(opts = {}) {
1824
2036
  const traceLogPath = opts.trace ? getUiDebugLogPath() : void 0;
1825
2037
  const trace = traceLogPath ? makeTraceLogger(traceLogPath) : void 0;
1826
2038
  trace?.("ui server starting");
1827
- const server = createServer((req, res) => {
2039
+ const server = createServer2((req, res) => {
1828
2040
  const url2 = req.url ?? "/";
1829
2041
  res.setHeader("X-Content-Type-Options", "nosniff");
1830
2042
  if (isUiApiRoute(url2)) {
@@ -1893,12 +2105,12 @@ async function runUiCommand(opts = {}) {
1893
2105
  });
1894
2106
  console.log(
1895
2107
  `
1896
- ${pc.bold("anygate UI")} ${pc.cyan(url)}
1897
- ${pc.dim("Press Ctrl+C to stop")}
2108
+ ${pc2.bold("anygate UI")} ${pc2.cyan(url)}
2109
+ ${pc2.dim("Press Ctrl+C to stop")}
1898
2110
  `
1899
2111
  );
1900
2112
  if (traceLogPath) {
1901
- console.log(` ${pc.dim(`Trace log: ${traceLogPath}`)}
2113
+ console.log(` ${pc2.dim(`Trace log: ${traceLogPath}`)}
1902
2114
  `);
1903
2115
  trace?.(`ui server listening ${url}`);
1904
2116
  }
@@ -1919,4 +2131,4 @@ export {
1919
2131
  resolveUiShutdownDecision,
1920
2132
  runUiCommand
1921
2133
  };
1922
- //# sourceMappingURL=command-PFFQI5YC.js.map
2134
+ //# sourceMappingURL=command-N3R2ZVVS.js.map