anyray-connect 0.1.0

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.
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Read/merge/write a JSON config file without clobbering the user's other keys.
3
+ *
4
+ * A first write backs the original up to `<file>.anyray-bak` so a user can
5
+ * always recover their pre-Anyray settings. Merges are shallow-by-section: we
6
+ * only touch the keys we own and leave everything else intact.
7
+ */
8
+ import { mkdir, readFile, writeFile, access } from 'node:fs/promises';
9
+ import { dirname } from 'node:path';
10
+ export const fileExists = async (path) => {
11
+ try {
12
+ await access(path);
13
+ return true;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ };
19
+ /** Parse a JSON file, returning `{}` if it's missing and throwing if it's malformed. */
20
+ export const readJson = async (path) => {
21
+ if (!(await fileExists(path)))
22
+ return {};
23
+ const raw = await readFile(path, 'utf-8');
24
+ if (!raw.trim())
25
+ return {};
26
+ const parsed = JSON.parse(raw);
27
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
28
+ throw new Error(`${path} is not a JSON object`);
29
+ }
30
+ return parsed;
31
+ };
32
+ /** Write JSON with a trailing newline, creating parent dirs and a one-time backup. */
33
+ export const writeJson = async (path, data) => {
34
+ await mkdir(dirname(path), { recursive: true });
35
+ const backup = `${path}.anyray-bak`;
36
+ if ((await fileExists(path)) && !(await fileExists(backup))) {
37
+ await writeFile(backup, await readFile(path, 'utf-8'));
38
+ }
39
+ await writeFile(path, `${JSON.stringify(data, null, 2)}\n`);
40
+ };
41
+ //# sourceMappingURL=jsonFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonFile.js","sourceRoot":"","sources":["../../src/util/jsonFile.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,IAAY,EAAoB,EAAE;IACjE,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC,CAAC;AAEF,wFAAwF;AACxF,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAC3B,IAAY,EACsB,EAAE;IACpC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACzC,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1C,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,uBAAuB,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,MAAiC,CAAC;AAC3C,CAAC,CAAC;AAEF,sFAAsF;AACtF,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,IAAY,EACZ,IAA6B,EACd,EAAE;IACjB,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,GAAG,IAAI,aAAa,CAAC;IACpC,IAAI,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAC5D,MAAM,SAAS,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC9D,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Tiny console formatting helpers. No color dependency — ANSI is emitted only
3
+ * when stdout is a TTY so piped/redirected output stays clean.
4
+ */
5
+ const tty = Boolean(process.stdout.isTTY);
6
+ const wrap = (code, s) => tty ? `\x1b[${code}m${s}\x1b[0m` : s;
7
+ export const bold = (s) => wrap('1', s);
8
+ export const dim = (s) => wrap('2', s);
9
+ export const green = (s) => wrap('32', s);
10
+ export const yellow = (s) => wrap('33', s);
11
+ export const red = (s) => wrap('31', s);
12
+ export const cyan = (s) => wrap('36', s);
13
+ export const info = (msg) => console.log(msg);
14
+ export const heading = (msg) => console.log(`\n${bold(msg)}`);
15
+ export const note = (msg) => console.log(dim(msg));
16
+ export const ok = (msg) => console.log(`${green('✓')} ${msg}`);
17
+ export const warn = (msg) => console.log(`${yellow('!')} ${msg}`);
18
+ export const fail = (msg) => console.log(`${red('✗')} ${msg}`);
19
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/util/log.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1C,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,CAAS,EAAU,EAAE,CAC/C,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAEvC,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACxD,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACvD,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC1D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACxD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAEzD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC5D,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACjE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAW,EAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;AAC7E,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;AAChF,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { writeFile, readFile, chmod } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ const profilePath = () => join(homedir(), '.anyray');
5
+ /** Read ~/.anyray; returns {} on missing or corrupt file. */
6
+ export const loadProfile = async () => {
7
+ try {
8
+ const raw = await readFile(profilePath(), 'utf-8');
9
+ const parsed = JSON.parse(raw);
10
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
11
+ return {};
12
+ return parsed;
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ };
18
+ /** Write ~/.anyray (mode 600) — replaced, not merged, so cleared values stay cleared. */
19
+ export const saveProfile = async (profile) => {
20
+ const clean = Object.fromEntries(Object.entries(profile).filter(([, v]) => v !== undefined));
21
+ await writeFile(profilePath(), `${JSON.stringify(clean, null, 2)}\n`, {
22
+ mode: 0o600,
23
+ });
24
+ // mode only applies on creation — tighten a pre-existing file too.
25
+ await chmod(profilePath(), 0o600);
26
+ };
27
+ //# sourceMappingURL=persist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.js","sourceRoot":"","sources":["../../src/util/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC,MAAM,WAAW,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AAE7D,6DAA6D;AAC7D,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,IAA4B,EAAE;IAC5D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAC;QACtF,OAAO,MAAuB,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC,CAAC;AAEF,yFAAyF;AACzF,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAsB,EAAiB,EAAE;IACzE,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAC9B,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAC1C,CAAC;IACnB,MAAM,SAAS,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QACpE,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,mEAAmE;IACnE,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Gateway reachability preflight.
3
+ *
4
+ * Before we repoint a working coding tool (e.g. Claude Code) at the gateway, we
5
+ * confirm the gateway actually answers. Otherwise a single run silently breaks
6
+ * the tool — it ends up pointed at a dead URL with no obvious way back. This is
7
+ * the one mistake a fleet-rollout on-ramp can't make.
8
+ *
9
+ * We probe `<origin>/v1/models` and treat *any* HTTP response — including 401/
10
+ * 404 — as "reachable": the gateway is up and talking, which is all we need to
11
+ * verify before writing config. Only a transport failure (DNS, refused, timeout)
12
+ * counts as unreachable.
13
+ */
14
+ const DEFAULT_TIMEOUT_MS = 3000;
15
+ export const checkGateway = async (origin, opts = {}) => {
16
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
17
+ const url = `${origin}/v1/models`;
18
+ const controller = new AbortController();
19
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
20
+ try {
21
+ const res = await fetch(url, {
22
+ method: 'GET',
23
+ signal: controller.signal,
24
+ });
25
+ // Any response means the gateway is up and speaking HTTP — auth/route
26
+ // errors are fine here; we only need to know it's alive.
27
+ return { reachable: true, detail: `responded ${res.status}` };
28
+ }
29
+ catch (error) {
30
+ const reason = error instanceof Error && error.name === 'AbortError'
31
+ ? `no response within ${timeoutMs}ms`
32
+ : error instanceof Error
33
+ ? error.message
34
+ : String(error);
35
+ return { reachable: false, detail: reason };
36
+ }
37
+ finally {
38
+ clearTimeout(timer);
39
+ }
40
+ };
41
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../src/util/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAahC,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,MAAc,EACd,OAAyB,EAAE,EACD,EAAE;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACvD,MAAM,GAAG,GAAG,GAAG,MAAM,YAAY,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,sEAAsE;QACtE,yDAAyD;QACzD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;IAChE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GACV,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;YACnD,CAAC,CAAC,sBAAsB,SAAS,IAAI;YACrC,CAAC,CAAC,KAAK,YAAY,KAAK;gBACtB,CAAC,CAAC,KAAK,CAAC,OAAO;gBACf,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC9C,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Manage an idempotent, clearly-marked block inside a shell profile.
3
+ *
4
+ * We never edit the user's existing lines — we own exactly the region between
5
+ * our begin/end markers. Re-running replaces that region; reverting removes it.
6
+ */
7
+ import { readFile, writeFile, access } from 'node:fs/promises';
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ const BEGIN = '# >>> anyray connect >>>';
11
+ const END = '# <<< anyray connect <<<';
12
+ /** The shell profile we should manage, based on $SHELL (falls back to ~/.profile). */
13
+ export const detectProfilePath = () => {
14
+ const home = homedir();
15
+ const shell = process.env.SHELL || '';
16
+ if (shell.includes('zsh'))
17
+ return join(home, '.zshrc');
18
+ if (shell.includes('bash'))
19
+ return join(home, '.bashrc');
20
+ return join(home, '.profile');
21
+ };
22
+ const readIfExists = async (path) => {
23
+ try {
24
+ await access(path);
25
+ return await readFile(path, 'utf-8');
26
+ }
27
+ catch {
28
+ return '';
29
+ }
30
+ };
31
+ /** Strip any existing anyray block (and the blank line that precedes it). */
32
+ const stripBlock = (content) => {
33
+ const pattern = new RegExp(`\\n*${escape(BEGIN)}[\\s\\S]*?${escape(END)}\\n?`);
34
+ return content.replace(pattern, '');
35
+ };
36
+ const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
37
+ /** Write `lines` into the managed block, replacing any prior block. Returns the path. */
38
+ export const upsertProfileBlock = async (path, lines) => {
39
+ const existing = await readIfExists(path);
40
+ const base = stripBlock(existing).replace(/\s*$/, '');
41
+ const block = [BEGIN, ...lines, END].join('\n');
42
+ const next = base ? `${base}\n\n${block}\n` : `${block}\n`;
43
+ await writeFile(path, next);
44
+ return path;
45
+ };
46
+ /** Remove the managed block. Returns true if a block was present. */
47
+ export const removeProfileBlock = async (path) => {
48
+ const existing = await readIfExists(path);
49
+ if (!existing.includes(BEGIN))
50
+ return false;
51
+ await writeFile(path, stripBlock(existing).replace(/\s*$/, '') + '\n');
52
+ return true;
53
+ };
54
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/util/profile.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,KAAK,GAAG,0BAA0B,CAAC;AACzC,MAAM,GAAG,GAAG,0BAA0B,CAAC;AAEvC,sFAAsF;AACtF,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAW,EAAE;IAC5C,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IACtC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACvD,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACzD,OAAO,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAChC,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,KAAK,EAAE,IAAY,EAAmB,EAAE;IAC3D,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC,CAAC;AAEF,6EAA6E;AAC7E,MAAM,UAAU,GAAG,CAAC,OAAe,EAAU,EAAE;IAC7C,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,aAAa,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/E,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAE/E,yFAAyF;AACzF,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,IAAY,EACZ,KAAe,EACE,EAAE;IACnB,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC;IAC3D,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,qEAAqE;AACrE,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EAAE,IAAY,EAAoB,EAAE;IACzE,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IACvE,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Invite redemption — exchange a single-use invite token for the dev's
3
+ * personal gateway key.
4
+ *
5
+ * Contract (gateway-side): `POST <origin>/connect/redeem` with
6
+ * `{"invite": "ari_..."}` → 200 `{"key": "ark_...", "user": "<email>",
7
+ * "team": "<team|absent>"}`, or 400 `{"status":"failure","message":"..."}`.
8
+ *
9
+ * PRIVACY/SECRECY: the invite token and the returned key are credentials —
10
+ * they must never be logged or embedded in error messages. Errors here carry
11
+ * only the URL, status, and the gateway's own failure message.
12
+ */
13
+ const REDEEM_TIMEOUT_MS = 10_000;
14
+ /** Mask a key for display: keep a short `xxx_` prefix + the last 4 chars. */
15
+ export const maskKey = (key) => {
16
+ if (key.length <= 4)
17
+ return '…';
18
+ const prefix = /^([a-z0-9]{2,6}_)/i.exec(key)?.[1] ?? '';
19
+ return key.length > prefix.length + 4
20
+ ? `${prefix}…${key.slice(-4)}`
21
+ : `…${key.slice(-4)}`;
22
+ };
23
+ const optionalString = (value) => typeof value === 'string' && value.trim() ? value.trim() : undefined;
24
+ /** The gateway's failure body is `{status, message}` — surface its message. */
25
+ const failureMessage = async (res) => {
26
+ try {
27
+ const body = (await res.json());
28
+ const message = optionalString(body?.message);
29
+ if (message)
30
+ return message;
31
+ }
32
+ catch {
33
+ // Non-JSON error body — fall through to the status line.
34
+ }
35
+ return `gateway responded ${res.status}`;
36
+ };
37
+ export const redeemInvite = async (origin, invite, opts = {}) => {
38
+ const timeoutMs = opts.timeoutMs ?? REDEEM_TIMEOUT_MS;
39
+ const url = `${origin}/connect/redeem`;
40
+ const controller = new AbortController();
41
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
42
+ let res;
43
+ try {
44
+ res = await fetch(url, {
45
+ method: 'POST',
46
+ headers: { 'content-type': 'application/json' },
47
+ body: JSON.stringify({ invite }),
48
+ signal: controller.signal,
49
+ });
50
+ }
51
+ catch (error) {
52
+ const reason = error instanceof Error && error.name === 'AbortError'
53
+ ? `no response within ${timeoutMs}ms`
54
+ : error instanceof Error
55
+ ? error.message
56
+ : String(error);
57
+ throw new Error(`could not reach the gateway at ${url}: ${reason}`);
58
+ }
59
+ finally {
60
+ clearTimeout(timer);
61
+ }
62
+ if (!res.ok) {
63
+ throw new Error(`invite redemption failed: ${await failureMessage(res)}`);
64
+ }
65
+ let body;
66
+ try {
67
+ body = (await res.json());
68
+ }
69
+ catch {
70
+ throw new Error('invite redemption failed: the gateway returned a malformed response');
71
+ }
72
+ const key = optionalString(body?.key);
73
+ if (!key) {
74
+ throw new Error('invite redemption failed: the gateway response is missing the key');
75
+ }
76
+ return {
77
+ key,
78
+ user: optionalString(body.user),
79
+ team: optionalString(body.team),
80
+ };
81
+ };
82
+ //# sourceMappingURL=redeem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redeem.js","sourceRoot":"","sources":["../../src/util/redeem.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "anyray-connect",
3
+ "version": "0.1.0",
4
+ "description": "Anyray connect — points local coding tools (Claude Code, Cursor, Windsurf, SDKs) at the Anyray gateway by writing their base URL + a placeholder key. The gateway stays the brain; this is just the on-ramp.",
5
+ "type": "module",
6
+ "bin": {
7
+ "anyray-connect": "dist/index.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "license": "MIT",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/anyrayHQ/monorepo.git",
14
+ "directory": "connect"
15
+ },
16
+ "homepage": "https://docs.anyray.ai",
17
+ "keywords": [
18
+ "anyray",
19
+ "ai-gateway",
20
+ "llm",
21
+ "claude-code",
22
+ "cursor",
23
+ "windsurf"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "files": [
29
+ "dist/",
30
+ "README.md"
31
+ ],
32
+ "scripts": {
33
+ "dev": "tsx src/index.ts",
34
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
35
+ "start": "node dist/index.js",
36
+ "test": "node --import tsx --test \"src/**/*.test.ts\"",
37
+ "prepublishOnly": "npm run build && npm test"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "20.14.0",
41
+ "tsx": "4.19.2",
42
+ "typescript": "5.6.3"
43
+ }
44
+ }