@jacobbd/relay-ai 0.4.3 → 0.4.4

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/README.md CHANGED
@@ -17,9 +17,9 @@
17
17
 
18
18
  📺 **Watch the Demos**
19
19
 
20
- | **Claude Code / Cowork / Desktop** | **Codex CLI & Desktop App** | **Gemini CLI** |
21
- |:---:|:---:|:---:|
22
- | [![Claude Demo](https://img.youtube.com/vi/IvsUPHLhX0o/mqdefault.jpg)](https://youtu.be/IvsUPHLhX0o) | [![Codex Demo](https://img.youtube.com/vi/42oiOB8IAu4/mqdefault.jpg)](https://youtu.be/42oiOB8IAu4) | [![Gemini Demo](https://img.youtube.com/vi/g7JKvqOHJl4/mqdefault.jpg)](https://www.youtube.com/watch?v=g7JKvqOHJl4) |
20
+ | **Claude Code / Cowork / Desktop** | **Codex CLI & Desktop App** | **Gemini CLI** | **v0.4.1: UI & Antigravity** |
21
+ |:---:|:---:|:---:|:---:|
22
+ | [![Claude Demo](https://img.youtube.com/vi/IvsUPHLhX0o/mqdefault.jpg)](https://youtu.be/IvsUPHLhX0o) | [![Codex Demo](https://img.youtube.com/vi/42oiOB8IAu4/mqdefault.jpg)](https://youtu.be/42oiOB8IAu4) | [![Gemini Demo](https://img.youtube.com/vi/g7JKvqOHJl4/mqdefault.jpg)](https://www.youtube.com/watch?v=g7JKvqOHJl4) | [![UI & Antigravity Demo](https://img.youtube.com/vi/8vXJ0LfpdoY/mqdefault.jpg)](https://www.youtube.com/watch?v=8vXJ0LfpdoY) |
23
23
 
24
24
  **relay-ai** is an interactive CLI — and now a **visual launcher** — that connects AI coding tools to any provider and runs local API gateways on your machine. It supports **Claude Code**, **Claude Desktop (Cowork + Code)**, the **OpenAI Codex CLI**, the **ChatGPT desktop app in Codex mode (macOS + Windows)**, **Google Gemini CLI**, and experimental **Antigravity CLI / IDE** support.
25
25
 
@@ -11,7 +11,7 @@ import { join } from "path";
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "@jacobbd/relay-ai",
14
- version: "0.4.3",
14
+ version: "0.4.4",
15
15
  publishConfig: {
16
16
  access: "public"
17
17
  },
@@ -8079,6 +8079,128 @@ async function runServerCommand(options = {}) {
8079
8079
  return 0;
8080
8080
  }
8081
8081
 
8082
+ // src/update-check.ts
8083
+ import {
8084
+ chmodSync as chmodSync5,
8085
+ mkdirSync as mkdirSync6,
8086
+ readFileSync as readFileSync10,
8087
+ renameSync as renameSync3,
8088
+ unlinkSync as unlinkSync2,
8089
+ writeFileSync as writeFileSync5
8090
+ } from "fs";
8091
+ import { join as join10 } from "path";
8092
+ var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
8093
+ var UPDATE_CHECK_TIMEOUT_MS = 2e3;
8094
+ var UPDATE_COMMAND = "npm install -g @jacobbd/relay-ai@latest";
8095
+ var REGISTRY_URL = "https://registry.npmjs.org/@jacobbd%2Frelay-ai/latest";
8096
+ var SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
8097
+ function parseVersion(version) {
8098
+ const match = SEMVER_PATTERN.exec(version);
8099
+ if (!match) return null;
8100
+ return {
8101
+ core: [Number(match[1]), Number(match[2]), Number(match[3])],
8102
+ prerelease: match[4]?.split(".") ?? []
8103
+ };
8104
+ }
8105
+ function comparePrerelease(current, latest) {
8106
+ if (current.length === 0 || latest.length === 0) {
8107
+ if (current.length === latest.length) return 0;
8108
+ return current.length === 0 ? -1 : 1;
8109
+ }
8110
+ const length = Math.max(current.length, latest.length);
8111
+ for (let i = 0; i < length; i++) {
8112
+ const currentPart = current[i];
8113
+ const latestPart = latest[i];
8114
+ if (currentPart === void 0) return 1;
8115
+ if (latestPart === void 0) return -1;
8116
+ if (currentPart === latestPart) continue;
8117
+ const currentNumber = /^\d+$/.test(currentPart) ? Number(currentPart) : null;
8118
+ const latestNumber = /^\d+$/.test(latestPart) ? Number(latestPart) : null;
8119
+ if (currentNumber !== null && latestNumber !== null) return latestNumber > currentNumber ? 1 : -1;
8120
+ if (currentNumber !== null) return 1;
8121
+ if (latestNumber !== null) return -1;
8122
+ return latestPart > currentPart ? 1 : -1;
8123
+ }
8124
+ return 0;
8125
+ }
8126
+ function isNewerVersion(currentVersion, latestVersion) {
8127
+ const current = parseVersion(currentVersion);
8128
+ const latest = parseVersion(latestVersion);
8129
+ if (!current || !latest) return false;
8130
+ for (let i = 0; i < current.core.length; i++) {
8131
+ if (current.core[i] === latest.core[i]) continue;
8132
+ return latest.core[i] > current.core[i];
8133
+ }
8134
+ return comparePrerelease(current.prerelease, latest.prerelease) > 0;
8135
+ }
8136
+ function cachePath() {
8137
+ return join10(getAppHome(), "update-check.json");
8138
+ }
8139
+ function readFreshCache(now) {
8140
+ try {
8141
+ const parsed = JSON.parse(readFileSync10(cachePath(), "utf8"));
8142
+ if (typeof parsed.latestVersion !== "string" || !parseVersion(parsed.latestVersion)) return null;
8143
+ if (typeof parsed.checkedAt !== "number" || !Number.isFinite(parsed.checkedAt)) return null;
8144
+ const age = now - parsed.checkedAt;
8145
+ if (age < 0 || age >= UPDATE_CHECK_TTL_MS) return null;
8146
+ return { latestVersion: parsed.latestVersion, checkedAt: parsed.checkedAt };
8147
+ } catch {
8148
+ return null;
8149
+ }
8150
+ }
8151
+ function writeCache(cache) {
8152
+ const directory = getAppHome();
8153
+ const path = cachePath();
8154
+ const temporaryPath = `${path}.${process.pid}.tmp`;
8155
+ try {
8156
+ mkdirSync6(directory, { recursive: true, mode: 448 });
8157
+ writeFileSync5(temporaryPath, `${JSON.stringify(cache)}
8158
+ `, { mode: 384 });
8159
+ renameSync3(temporaryPath, path);
8160
+ try {
8161
+ chmodSync5(path, 384);
8162
+ } catch {
8163
+ }
8164
+ } catch {
8165
+ try {
8166
+ unlinkSync2(temporaryPath);
8167
+ } catch {
8168
+ }
8169
+ }
8170
+ }
8171
+ function statusFor(latestVersion) {
8172
+ return {
8173
+ currentVersion: VERSION,
8174
+ latestVersion,
8175
+ updateAvailable: latestVersion !== null && isNewerVersion(VERSION, latestVersion)
8176
+ };
8177
+ }
8178
+ async function checkForUpdates(options = {}) {
8179
+ const now = options.now ?? Date.now();
8180
+ const cached = readFreshCache(now);
8181
+ if (cached) return statusFor(cached.latestVersion);
8182
+ const controller = new AbortController();
8183
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? UPDATE_CHECK_TIMEOUT_MS);
8184
+ try {
8185
+ const response = await (options.fetchImpl ?? fetch)(REGISTRY_URL, {
8186
+ headers: { Accept: "application/json", "User-Agent": `relay-ai/${VERSION}` },
8187
+ signal: controller.signal
8188
+ });
8189
+ if (!response.ok) return statusFor(null);
8190
+ const body = await response.json();
8191
+ if (typeof body.version !== "string" || !parseVersion(body.version)) return statusFor(null);
8192
+ writeCache({ latestVersion: body.version, checkedAt: now });
8193
+ return statusFor(body.version);
8194
+ } catch {
8195
+ return statusFor(null);
8196
+ } finally {
8197
+ clearTimeout(timer);
8198
+ }
8199
+ }
8200
+ function formatUpdateNotification(currentVersion, latestVersion) {
8201
+ return `\u{1F514} Update available: ${currentVersion} \u2192 ${latestVersion}. Run ${UPDATE_COMMAND} to update.`;
8202
+ }
8203
+
8082
8204
  // src/favorite-provider-display.ts
8083
8205
  var OAUTH_FAVORITE_NAMES = {
8084
8206
  "claude-code": "Claude Code OAuth (Anthropic subscription)",
@@ -8099,16 +8221,16 @@ function favoriteProviderDisplayName(provider) {
8099
8221
  import { execSync as execSync2, spawn as spawn2 } from "child_process";
8100
8222
  import { existsSync as existsSync10 } from "fs";
8101
8223
  import { homedir as homedir7 } from "os";
8102
- import { join as join10 } from "path";
8224
+ import { join as join11 } from "path";
8103
8225
  var isWindows2 = process.platform === "win32";
8104
8226
  var OPENCODE_FALLBACK_PATHS = isWindows2 ? [
8105
- join10(process.env["APPDATA"] ?? homedir7(), "npm", "opencode.cmd"),
8106
- join10(process.env["APPDATA"] ?? homedir7(), "npm", "opencode"),
8107
- join10(homedir7(), "AppData", "Roaming", "npm", "opencode.cmd")
8227
+ join11(process.env["APPDATA"] ?? homedir7(), "npm", "opencode.cmd"),
8228
+ join11(process.env["APPDATA"] ?? homedir7(), "npm", "opencode"),
8229
+ join11(homedir7(), "AppData", "Roaming", "npm", "opencode.cmd")
8108
8230
  ] : [
8109
- join10(homedir7(), ".opencode", "bin", "opencode"),
8110
- join10(homedir7(), ".local", "bin", "opencode"),
8111
- join10(homedir7(), ".npm", "bin", "opencode"),
8231
+ join11(homedir7(), ".opencode", "bin", "opencode"),
8232
+ join11(homedir7(), ".local", "bin", "opencode"),
8233
+ join11(homedir7(), ".npm", "bin", "opencode"),
8112
8234
  "/usr/local/bin/opencode",
8113
8235
  "/opt/homebrew/bin/opencode"
8114
8236
  ];
@@ -9789,7 +9911,7 @@ ${pc6.bold("Device code (works on SSH/VPS):")}
9789
9911
  import { execSync as execSync3, spawn as spawn4 } from "child_process";
9790
9912
  import { existsSync as existsSync11, readdirSync, statSync as statSync3 } from "fs";
9791
9913
  import { homedir as homedir8 } from "os";
9792
- import { join as join11 } from "path";
9914
+ import { join as join12 } from "path";
9793
9915
  import * as p6 from "@clack/prompts";
9794
9916
  var CODEX_BUNDLE_ID = "com.openai.codex";
9795
9917
  var DARWIN_APP_NAMES = ["ChatGPT", "Codex"];
@@ -9808,33 +9930,33 @@ function runPowerShell(script) {
9808
9930
  function darwinAppCandidates() {
9809
9931
  return DARWIN_APP_NAMES.flatMap((name) => [
9810
9932
  `/Applications/${name}.app`,
9811
- join11(homedir8(), "Applications", `${name}.app`)
9933
+ join12(homedir8(), "Applications", `${name}.app`)
9812
9934
  ]);
9813
9935
  }
9814
9936
  function winLocalAppData() {
9815
- return process.env.LOCALAPPDATA ?? join11(homedir8(), "AppData", "Local");
9937
+ return process.env.LOCALAPPDATA ?? join12(homedir8(), "AppData", "Local");
9816
9938
  }
9817
9939
  function winCodexExeCandidates() {
9818
9940
  const local = winLocalAppData();
9819
9941
  const bases = WIN_APP_NAMES.flatMap((name) => [
9820
- join11(local, "Programs", name),
9821
- join11(local, "Programs", `OpenAI ${name}`),
9822
- join11(local, name),
9823
- join11(local, `OpenAI ${name}`),
9824
- join11(local, "OpenAI", name)
9942
+ join12(local, "Programs", name),
9943
+ join12(local, "Programs", `OpenAI ${name}`),
9944
+ join12(local, name),
9945
+ join12(local, `OpenAI ${name}`),
9946
+ join12(local, "OpenAI", name)
9825
9947
  ]);
9826
- bases.push(join11(local, "openai-codex-electron"), join11(local, "openai-chatgpt-electron"));
9948
+ bases.push(join12(local, "openai-codex-electron"), join12(local, "openai-chatgpt-electron"));
9827
9949
  const out = [];
9828
9950
  for (const base of bases) {
9829
9951
  for (const name of WIN_APP_NAMES) {
9830
- out.push(join11(base, `${name}.exe`));
9952
+ out.push(join12(base, `${name}.exe`));
9831
9953
  }
9832
9954
  try {
9833
9955
  if (existsSync11(base)) {
9834
9956
  for (const dir of readdirSync(base)) {
9835
9957
  if (dir.startsWith("app-")) {
9836
9958
  for (const name of WIN_APP_NAMES) {
9837
- out.push(join11(base, dir, `${name}.exe`));
9959
+ out.push(join12(base, dir, `${name}.exe`));
9838
9960
  }
9839
9961
  }
9840
9962
  }
@@ -10011,7 +10133,7 @@ function codexAppInstallHint() {
10011
10133
  import { execSync as execSync4, spawn as spawn5 } from "child_process";
10012
10134
  import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
10013
10135
  import { homedir as homedir9 } from "os";
10014
- import { join as join12 } from "path";
10136
+ import { join as join13 } from "path";
10015
10137
  import * as p7 from "@clack/prompts";
10016
10138
  var CLAUDE_BUNDLE_ID = "com.anthropic.claudefordesktop";
10017
10139
  function claudeAppSupported() {
@@ -10028,26 +10150,26 @@ function runPowerShell2(script) {
10028
10150
  function darwinAppCandidates2() {
10029
10151
  return [
10030
10152
  "/Applications/Claude.app",
10031
- join12(homedir9(), "Applications", "Claude.app")
10153
+ join13(homedir9(), "Applications", "Claude.app")
10032
10154
  ];
10033
10155
  }
10034
10156
  function winLocalAppData2() {
10035
- return process.env.LOCALAPPDATA ?? join12(homedir9(), "AppData", "Local");
10157
+ return process.env.LOCALAPPDATA ?? join13(homedir9(), "AppData", "Local");
10036
10158
  }
10037
10159
  function winClaudeExeCandidates() {
10038
10160
  const local = winLocalAppData2();
10039
10161
  const bases = [
10040
- join12(local, "Programs", "Claude"),
10041
- join12(local, "Claude")
10162
+ join13(local, "Programs", "Claude"),
10163
+ join13(local, "Claude")
10042
10164
  ];
10043
10165
  const out = [];
10044
10166
  for (const base of bases) {
10045
- out.push(join12(base, "Claude.exe"));
10167
+ out.push(join13(base, "Claude.exe"));
10046
10168
  try {
10047
10169
  if (existsSync12(base)) {
10048
10170
  for (const name of readdirSync2(base)) {
10049
10171
  if (name.startsWith("app-")) {
10050
- out.push(join12(base, name, "Claude.exe"));
10172
+ out.push(join13(base, name, "Claude.exe"));
10051
10173
  }
10052
10174
  }
10053
10175
  }
@@ -10385,6 +10507,8 @@ export {
10385
10507
  loadServerModels,
10386
10508
  resolveServerUpstreamApiKey,
10387
10509
  runServerCommand,
10510
+ checkForUpdates,
10511
+ formatUpdateNotification,
10388
10512
  favoriteProviderDisplayName,
10389
10513
  addProviderFromTemplate,
10390
10514
  removeProviderFromRegistry,
@@ -10408,4 +10532,4 @@ export {
10408
10532
  quitClaudeAppGracefully,
10409
10533
  launchOrRestartClaudeApp
10410
10534
  };
10411
- //# sourceMappingURL=chunk-VSXPAZX4.js.map
10535
+ //# sourceMappingURL=chunk-XCB2K4GI.js.map