@jacobbd/relay-ai 0.4.1 → 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 +4 -8
- package/dist/{chunk-2GUFTECU.js → chunk-XCB2K4GI.js} +161 -33
- package/dist/chunk-XCB2K4GI.js.map +1 -0
- package/dist/cli.js +12 -2
- package/dist/cli.js.map +1 -1
- package/dist/ui/public/app.js +50 -0
- package/dist/ui/public/index.html +18 -1
- package/dist/ui/public/style.css +82 -1
- package/dist/{ui-command-64SLLWW4.js → ui-command-JG3BFVSL.js} +51 -10
- package/dist/ui-command-JG3BFVSL.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-2GUFTECU.js.map +0 -1
- package/dist/ui-command-64SLLWW4.js.map +0 -1
package/README.md
CHANGED
|
@@ -15,15 +15,11 @@
|
|
|
15
15
|
>
|
|
16
16
|
> <a href="https://buymeacoffee.com/jacobbd"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="42"></a>
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
<a href="https://youtu.be/IvsUPHLhX0o">
|
|
20
|
-
<img src="assets/demo-part1-thumbnail.png" alt="relay-ai demo — installation, configuration, Claude Code, Claude Cowork & Claude Code Desktop (Part 1)" width="100%">
|
|
21
|
-
</a>
|
|
22
|
-
</p>
|
|
18
|
+
📺 **Watch the Demos**
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
| **Claude Code / Cowork / Desktop** | **Codex CLI & Desktop App** | **Gemini CLI** | **v0.4.1: UI & Antigravity** |
|
|
21
|
+
|:---:|:---:|:---:|:---:|
|
|
22
|
+
| [](https://youtu.be/IvsUPHLhX0o) | [](https://youtu.be/42oiOB8IAu4) | [](https://www.youtube.com/watch?v=g7JKvqOHJl4) | [](https://www.youtube.com/watch?v=8vXJ0LfpdoY) |
|
|
27
23
|
|
|
28
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.
|
|
29
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.
|
|
14
|
+
version: "0.4.4",
|
|
15
15
|
publishConfig: {
|
|
16
16
|
access: "public"
|
|
17
17
|
},
|
|
@@ -7287,10 +7287,12 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
7287
7287
|
sendJson(res, 200, anthropicResponse);
|
|
7288
7288
|
}
|
|
7289
7289
|
} catch (err) {
|
|
7290
|
-
const message =
|
|
7290
|
+
const message = formatUpstreamError(err);
|
|
7291
7291
|
plog(`sdk error npm=${model.npm} upstream=${upstreamModelId(model)}: ${message}`);
|
|
7292
|
-
if (!res.headersSent)
|
|
7293
|
-
|
|
7292
|
+
if (!res.headersSent) {
|
|
7293
|
+
const status = upstreamHttpStatus(err, message);
|
|
7294
|
+
sendJson(res, status === 500 ? 502 : status, { error: { message } });
|
|
7295
|
+
} else res.end();
|
|
7294
7296
|
}
|
|
7295
7297
|
return;
|
|
7296
7298
|
}
|
|
@@ -7340,10 +7342,12 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
7340
7342
|
sendJson(res, 200, response);
|
|
7341
7343
|
}
|
|
7342
7344
|
} catch (err) {
|
|
7343
|
-
const message =
|
|
7345
|
+
const message = formatUpstreamError(err);
|
|
7344
7346
|
plog(`sdk error npm=${model.npm} upstream=${upstreamModelId(model)}: ${message}`);
|
|
7345
|
-
if (!res.headersSent)
|
|
7346
|
-
|
|
7347
|
+
if (!res.headersSent) {
|
|
7348
|
+
const status = upstreamHttpStatus(err, message);
|
|
7349
|
+
sendJson(res, status === 500 ? 502 : status, { error: { message } });
|
|
7350
|
+
} else res.end();
|
|
7347
7351
|
}
|
|
7348
7352
|
}
|
|
7349
7353
|
function lookupModel(res, catalog, modelId) {
|
|
@@ -8075,6 +8079,128 @@ async function runServerCommand(options = {}) {
|
|
|
8075
8079
|
return 0;
|
|
8076
8080
|
}
|
|
8077
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
|
+
|
|
8078
8204
|
// src/favorite-provider-display.ts
|
|
8079
8205
|
var OAUTH_FAVORITE_NAMES = {
|
|
8080
8206
|
"claude-code": "Claude Code OAuth (Anthropic subscription)",
|
|
@@ -8095,16 +8221,16 @@ function favoriteProviderDisplayName(provider) {
|
|
|
8095
8221
|
import { execSync as execSync2, spawn as spawn2 } from "child_process";
|
|
8096
8222
|
import { existsSync as existsSync10 } from "fs";
|
|
8097
8223
|
import { homedir as homedir7 } from "os";
|
|
8098
|
-
import { join as
|
|
8224
|
+
import { join as join11 } from "path";
|
|
8099
8225
|
var isWindows2 = process.platform === "win32";
|
|
8100
8226
|
var OPENCODE_FALLBACK_PATHS = isWindows2 ? [
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
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")
|
|
8104
8230
|
] : [
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8231
|
+
join11(homedir7(), ".opencode", "bin", "opencode"),
|
|
8232
|
+
join11(homedir7(), ".local", "bin", "opencode"),
|
|
8233
|
+
join11(homedir7(), ".npm", "bin", "opencode"),
|
|
8108
8234
|
"/usr/local/bin/opencode",
|
|
8109
8235
|
"/opt/homebrew/bin/opencode"
|
|
8110
8236
|
];
|
|
@@ -9785,7 +9911,7 @@ ${pc6.bold("Device code (works on SSH/VPS):")}
|
|
|
9785
9911
|
import { execSync as execSync3, spawn as spawn4 } from "child_process";
|
|
9786
9912
|
import { existsSync as existsSync11, readdirSync, statSync as statSync3 } from "fs";
|
|
9787
9913
|
import { homedir as homedir8 } from "os";
|
|
9788
|
-
import { join as
|
|
9914
|
+
import { join as join12 } from "path";
|
|
9789
9915
|
import * as p6 from "@clack/prompts";
|
|
9790
9916
|
var CODEX_BUNDLE_ID = "com.openai.codex";
|
|
9791
9917
|
var DARWIN_APP_NAMES = ["ChatGPT", "Codex"];
|
|
@@ -9804,33 +9930,33 @@ function runPowerShell(script) {
|
|
|
9804
9930
|
function darwinAppCandidates() {
|
|
9805
9931
|
return DARWIN_APP_NAMES.flatMap((name) => [
|
|
9806
9932
|
`/Applications/${name}.app`,
|
|
9807
|
-
|
|
9933
|
+
join12(homedir8(), "Applications", `${name}.app`)
|
|
9808
9934
|
]);
|
|
9809
9935
|
}
|
|
9810
9936
|
function winLocalAppData() {
|
|
9811
|
-
return process.env.LOCALAPPDATA ??
|
|
9937
|
+
return process.env.LOCALAPPDATA ?? join12(homedir8(), "AppData", "Local");
|
|
9812
9938
|
}
|
|
9813
9939
|
function winCodexExeCandidates() {
|
|
9814
9940
|
const local = winLocalAppData();
|
|
9815
9941
|
const bases = WIN_APP_NAMES.flatMap((name) => [
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
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)
|
|
9821
9947
|
]);
|
|
9822
|
-
bases.push(
|
|
9948
|
+
bases.push(join12(local, "openai-codex-electron"), join12(local, "openai-chatgpt-electron"));
|
|
9823
9949
|
const out = [];
|
|
9824
9950
|
for (const base of bases) {
|
|
9825
9951
|
for (const name of WIN_APP_NAMES) {
|
|
9826
|
-
out.push(
|
|
9952
|
+
out.push(join12(base, `${name}.exe`));
|
|
9827
9953
|
}
|
|
9828
9954
|
try {
|
|
9829
9955
|
if (existsSync11(base)) {
|
|
9830
9956
|
for (const dir of readdirSync(base)) {
|
|
9831
9957
|
if (dir.startsWith("app-")) {
|
|
9832
9958
|
for (const name of WIN_APP_NAMES) {
|
|
9833
|
-
out.push(
|
|
9959
|
+
out.push(join12(base, dir, `${name}.exe`));
|
|
9834
9960
|
}
|
|
9835
9961
|
}
|
|
9836
9962
|
}
|
|
@@ -10007,7 +10133,7 @@ function codexAppInstallHint() {
|
|
|
10007
10133
|
import { execSync as execSync4, spawn as spawn5 } from "child_process";
|
|
10008
10134
|
import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
|
|
10009
10135
|
import { homedir as homedir9 } from "os";
|
|
10010
|
-
import { join as
|
|
10136
|
+
import { join as join13 } from "path";
|
|
10011
10137
|
import * as p7 from "@clack/prompts";
|
|
10012
10138
|
var CLAUDE_BUNDLE_ID = "com.anthropic.claudefordesktop";
|
|
10013
10139
|
function claudeAppSupported() {
|
|
@@ -10024,26 +10150,26 @@ function runPowerShell2(script) {
|
|
|
10024
10150
|
function darwinAppCandidates2() {
|
|
10025
10151
|
return [
|
|
10026
10152
|
"/Applications/Claude.app",
|
|
10027
|
-
|
|
10153
|
+
join13(homedir9(), "Applications", "Claude.app")
|
|
10028
10154
|
];
|
|
10029
10155
|
}
|
|
10030
10156
|
function winLocalAppData2() {
|
|
10031
|
-
return process.env.LOCALAPPDATA ??
|
|
10157
|
+
return process.env.LOCALAPPDATA ?? join13(homedir9(), "AppData", "Local");
|
|
10032
10158
|
}
|
|
10033
10159
|
function winClaudeExeCandidates() {
|
|
10034
10160
|
const local = winLocalAppData2();
|
|
10035
10161
|
const bases = [
|
|
10036
|
-
|
|
10037
|
-
|
|
10162
|
+
join13(local, "Programs", "Claude"),
|
|
10163
|
+
join13(local, "Claude")
|
|
10038
10164
|
];
|
|
10039
10165
|
const out = [];
|
|
10040
10166
|
for (const base of bases) {
|
|
10041
|
-
out.push(
|
|
10167
|
+
out.push(join13(base, "Claude.exe"));
|
|
10042
10168
|
try {
|
|
10043
10169
|
if (existsSync12(base)) {
|
|
10044
10170
|
for (const name of readdirSync2(base)) {
|
|
10045
10171
|
if (name.startsWith("app-")) {
|
|
10046
|
-
out.push(
|
|
10172
|
+
out.push(join13(base, name, "Claude.exe"));
|
|
10047
10173
|
}
|
|
10048
10174
|
}
|
|
10049
10175
|
}
|
|
@@ -10381,6 +10507,8 @@ export {
|
|
|
10381
10507
|
loadServerModels,
|
|
10382
10508
|
resolveServerUpstreamApiKey,
|
|
10383
10509
|
runServerCommand,
|
|
10510
|
+
checkForUpdates,
|
|
10511
|
+
formatUpdateNotification,
|
|
10384
10512
|
favoriteProviderDisplayName,
|
|
10385
10513
|
addProviderFromTemplate,
|
|
10386
10514
|
removeProviderFromRegistry,
|
|
@@ -10404,4 +10532,4 @@ export {
|
|
|
10404
10532
|
quitClaudeAppGracefully,
|
|
10405
10533
|
launchOrRestartClaudeApp
|
|
10406
10534
|
};
|
|
10407
|
-
//# sourceMappingURL=chunk-
|
|
10535
|
+
//# sourceMappingURL=chunk-XCB2K4GI.js.map
|