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,131 @@
1
+ /**
2
+ * Claude Code adapter — fully automatic.
3
+ *
4
+ * Claude Code loads an `env` block from `~/.claude/settings.json` and honors
5
+ * `ANTHROPIC_BASE_URL`, a bearer token via `ANTHROPIC_AUTH_TOKEN`, and extra
6
+ * request headers via `ANTHROPIC_CUSTOM_HEADERS`. We point the base URL at the
7
+ * gateway and stamp `x-anyray-metadata` for content-free spend attribution. We
8
+ * also pin `x-anyray-provider: anthropic` so it works even before an org-wide
9
+ * default provider is configured.
10
+ *
11
+ * Auth depends on the target's `authMode`:
12
+ * - `placeholder` (default): send the dev's personal gateway key
13
+ * (`target.clientKey`, from an invite) when present, else the shared
14
+ * placeholder; the gateway swaps in the server-held provider key (API
15
+ * billing).
16
+ * - `subscription`: write NO auth token, so Claude Code keeps its own seat
17
+ * sign-in (Pro/Max/Team/Enterprise) and sends the OAuth token itself; we add
18
+ * `x-anyray-auth-mode: passthrough` so the gateway forwards that token to
19
+ * Anthropic instead of injecting the org key. Spend stays on the seat. A
20
+ * personal gateway key, if present, rides `x-anyray-api-key`.
21
+ *
22
+ * We only own these keys — any other settings/env the user has are preserved.
23
+ */
24
+ import { homedir } from 'node:os';
25
+ import { join } from 'node:path';
26
+ import { readJson, writeJson, fileExists } from '../util/jsonFile.js';
27
+ import { metadataHeaderValue } from '../types.js';
28
+ const settingsPath = () => join(homedir(), '.claude', 'settings.json');
29
+ const OWNED_KEYS = [
30
+ 'ANTHROPIC_BASE_URL',
31
+ 'ANTHROPIC_AUTH_TOKEN',
32
+ 'ANTHROPIC_CUSTOM_HEADERS',
33
+ ];
34
+ const customHeaders = (meta, subscription, clientKey) => {
35
+ const lines = ['x-anyray-provider: anthropic'];
36
+ if (subscription) {
37
+ lines.push('x-anyray-auth-mode: passthrough');
38
+ // The seat OAuth token owns Authorization, so the personal gateway key
39
+ // rides its own header for the gateway to identify the dev.
40
+ if (clientKey)
41
+ lines.push(`x-anyray-api-key: ${clientKey}`);
42
+ }
43
+ if (meta)
44
+ lines.push(`x-anyray-metadata: ${meta}`);
45
+ return lines.join('\n');
46
+ };
47
+ export const claudeCode = {
48
+ id: 'claude-code',
49
+ title: 'Claude Code',
50
+ automatic: true,
51
+ async detect() {
52
+ const path = join(homedir(), '.claude');
53
+ const installed = await fileExists(path);
54
+ return {
55
+ installed,
56
+ detail: installed ? settingsPath() : 'no ~/.claude directory',
57
+ };
58
+ },
59
+ async apply(target, opts) {
60
+ const path = settingsPath();
61
+ const subscription = target.authMode === 'subscription';
62
+ const meta = metadataHeaderValue(target.metadata);
63
+ const env = {
64
+ ANTHROPIC_BASE_URL: target.anthropicBaseUrl,
65
+ ANTHROPIC_CUSTOM_HEADERS: customHeaders(meta, subscription, target.clientKey),
66
+ // Placeholder mode sends the dev's personal gateway key when one was
67
+ // issued, else the shared placeholder, for the gateway to swap;
68
+ // subscription mode must NOT set one — any ANTHROPIC_AUTH_TOKEN would
69
+ // override the seat's OAuth sign-in and silently move spend onto API
70
+ // billing (the personal key rides x-anyray-api-key there instead).
71
+ ...(subscription
72
+ ? {}
73
+ : { ANTHROPIC_AUTH_TOKEN: target.clientKey ?? target.placeholderKey }),
74
+ };
75
+ const messages = [
76
+ `${path} → env.ANTHROPIC_BASE_URL = ${target.anthropicBaseUrl}`,
77
+ subscription
78
+ ? `Subscription mode: Claude Code keeps your seat sign-in; its OAuth token passes through the gateway (never stored).${target.clientKey
79
+ ? ' Your personal gateway key rides x-anyray-api-key.'
80
+ : ''}`
81
+ : target.clientKey
82
+ ? 'Auth: personal gateway key (written to the config, not shown here)'
83
+ : `Auth: placeholder token (gateway swaps in the server-held key)`,
84
+ `Attribution: ${meta ?? '(none)'}`,
85
+ 'Restart Claude Code (or open a new session) to pick up the change.',
86
+ ];
87
+ if (opts.dryRun)
88
+ return { status: 'configured', messages };
89
+ const settings = await readJson(path);
90
+ const existingEnv = settings.env ?? {};
91
+ settings.env = { ...existingEnv, ...env };
92
+ if (subscription) {
93
+ // A leftover token (ours or hand-set) would defeat seat auth — drop it.
94
+ if (existingEnv.ANTHROPIC_AUTH_TOKEN) {
95
+ messages.push('Removed env.ANTHROPIC_AUTH_TOKEN — it would override your subscription sign-in.');
96
+ }
97
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
98
+ }
99
+ await writeJson(path, settings);
100
+ return { status: 'configured', messages, changedFiles: [path] };
101
+ },
102
+ async revert(opts) {
103
+ const path = settingsPath();
104
+ if (!(await fileExists(path))) {
105
+ return { status: 'not-found', messages: ['nothing to revert'] };
106
+ }
107
+ if (opts.dryRun) {
108
+ return {
109
+ status: 'reverted',
110
+ messages: [`would remove Anyray keys from ${path}`],
111
+ };
112
+ }
113
+ const settings = await readJson(path);
114
+ const env = settings.env;
115
+ if (env) {
116
+ for (const key of OWNED_KEYS)
117
+ delete env[key];
118
+ if (Object.keys(env).length === 0)
119
+ delete settings.env;
120
+ await writeJson(path, settings);
121
+ }
122
+ const messages = [`removed Anyray keys from ${path}`];
123
+ // Revert only deletes our keys; if apply overwrote a pre-existing value
124
+ // (e.g. you already set ANTHROPIC_BASE_URL), point at the backup.
125
+ if (await fileExists(`${path}.anyray-bak`)) {
126
+ messages.push(`Pre-Anyray backup at ${path}.anyray-bak (restore for a full revert).`);
127
+ }
128
+ return { status: 'reverted', messages, changedFiles: [path] };
129
+ },
130
+ };
131
+ //# sourceMappingURL=claudeCode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeCode.js","sourceRoot":"","sources":["../../src/tools/claudeCode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Codex CLI adapter — fully automatic.
3
+ *
4
+ * Codex (OpenAI's CLI) reaches a backend through a `[model_providers.<id>]`
5
+ * entry. We register an `anyray` provider pointed at the gateway and select it
6
+ * with a named profile, so a dev opts in with `codex --profile anyray` while
7
+ * their default Codex config stays untouched.
8
+ *
9
+ * Profile format (Codex 0.139+): a named profile lives in its OWN file,
10
+ * `~/.codex/<name>.config.toml`, with flat top-level keys — NOT an inline
11
+ * `[profiles.<name>]` table in `config.toml` (0.139 rejects the legacy table).
12
+ * We own the whole `anyray.config.toml` file; `config.toml` is never touched
13
+ * except to strip a legacy block left by an older connect (migration). Omitted
14
+ * keys inherit the dev's base `config.toml`.
15
+ *
16
+ * The only supported wire protocol is the OpenAI *Responses* API
17
+ * (`wire_api = "responses"`), so we target the gateway's `/v1/responses`. In
18
+ * placeholder mode auth rides as a static `Authorization` header carrying the
19
+ * dev's personal gateway key (`clientKey`, from an invite) or the shared
20
+ * placeholder (the gateway swaps in the real provider key), and
21
+ * `x-anyray-metadata` carries content-free spend attribution.
22
+ *
23
+ * Subscription mode (`authMode: 'subscription'`, ChatGPT Plus/Pro/Team seats):
24
+ * the provider sets `requires_openai_auth = true` and NO static Authorization
25
+ * header, so Codex attaches its own ChatGPT sign-in per request
26
+ * (`Authorization: Bearer <access token>` + `ChatGPT-Account-ID`) — Codex uses
27
+ * a configured `base_url` verbatim even with ChatGPT auth. The gateway runs in
28
+ * pass-through (`x-anyray-auth-mode: passthrough`) and re-targets the upstream
29
+ * at the ChatGPT Codex backend (`x-anyray-custom-host`), forwarding the
30
+ * account-id and the Codex client headers (`x-anyray-forward-headers`) so the
31
+ * backend still sees a genuine Codex request, and spend stays on the seat. No
32
+ * model line is written (the seat backend only accepts real Codex models), so
33
+ * the profile inherits the dev's working default model from `config.toml`.
34
+ *
35
+ * Set `ANYRAY_CODEX_MODEL` to pin a specific model id, and (placeholder mode
36
+ * only) `ANYRAY_CODEX_PROVIDER` to pin `x-anyray-provider`.
37
+ */
38
+ import { homedir } from 'node:os';
39
+ import { join } from 'node:path';
40
+ import { writeFile, rm } from 'node:fs/promises';
41
+ import { fileExists } from '../util/jsonFile.js';
42
+ import { removeProfileBlock } from '../util/profile.js';
43
+ import { metadataHeaderValue } from '../types.js';
44
+ /** Model Codex sends when ANYRAY_CODEX_MODEL is unset; the gateway's default
45
+ * routing decides the real model/provider, so this is just a required token. */
46
+ const DEFAULT_MODEL = 'anyray-default';
47
+ /** Default intent stamped on Codex traffic so it's filterable (intent:codex)
48
+ * and labelled in the console; override with ANYRAY_CODEX_INTENT(_LABEL) or the
49
+ * global --intent. */
50
+ const DEFAULT_INTENT = 'codex';
51
+ const DEFAULT_INTENT_LABEL = 'Codex CLI';
52
+ /** Where ChatGPT-subscription Codex traffic actually lives — seat OAuth tokens
53
+ * are only valid against this backend, never api.openai.com. */
54
+ const CHATGPT_CODEX_BACKEND = 'https://chatgpt.com/backend-api/codex';
55
+ /** Headers Codex actually sends with ChatGPT auth (observed on the wire) that
56
+ * the backend needs to recognise a genuine Codex client and route the seat;
57
+ * the gateway only forwards headers named in x-anyray-forward-headers. All
58
+ * content-free (account id, client identity/UA, per-session grouping ids). */
59
+ const SUBSCRIPTION_FORWARD_HEADERS = 'chatgpt-account-id,originator,session_id,user-agent,x-codex-turn-metadata,x-codex-window-id,x-client-request-id';
60
+ /** `$CODEX_HOME` overrides the default `~/.codex` (Codex honors it too). */
61
+ const codexHome = () => process.env.CODEX_HOME?.trim() || join(homedir(), '.codex');
62
+ /** Base config (untouched except for legacy-block cleanup) and our own profile
63
+ * file. The profile NAME is the file stem: `anyray.config.toml` → `anyray`. */
64
+ const baseConfigPath = () => join(codexHome(), 'config.toml');
65
+ const profilePath = () => join(codexHome(), 'anyray.config.toml');
66
+ /**
67
+ * The `x-anyray-metadata` value for Codex: the resolved attribution (user/team/
68
+ * session/intent from CLI flags or env) plus a Codex-specific `intent` default
69
+ * so every Codex request is tagged even when the operator didn't pass one. The
70
+ * header is static, so `session` is a stable grouping label, not per-conversation.
71
+ */
72
+ const codexMetadata = (target) => metadataHeaderValue({
73
+ ...target.metadata,
74
+ intent: process.env.ANYRAY_CODEX_INTENT?.trim() ||
75
+ target.metadata.intent ||
76
+ DEFAULT_INTENT,
77
+ intentLabel: process.env.ANYRAY_CODEX_INTENT_LABEL?.trim() ||
78
+ target.metadata.intentLabel ||
79
+ DEFAULT_INTENT_LABEL,
80
+ });
81
+ /** Escape a string for a TOML basic (double-quoted) string. */
82
+ const tomlString = (value) => `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
83
+ /** The model the profile pins, or undefined to inherit the base config's model.
84
+ * Subscription seats must use a real Codex model, so we never pin `anyray-default`
85
+ * there — we inherit the dev's working default instead. */
86
+ const resolveModel = (subscription) => {
87
+ const explicit = process.env.ANYRAY_CODEX_MODEL?.trim();
88
+ if (explicit)
89
+ return explicit;
90
+ return subscription ? undefined : DEFAULT_MODEL;
91
+ };
92
+ /** Full contents of the standalone profile file (flat top-level keys). */
93
+ const profileFile = (target) => {
94
+ const meta = codexMetadata(target);
95
+ const subscription = target.authMode === 'subscription';
96
+ // Subscription traffic is ChatGPT-backend-only — the provider pin is not
97
+ // the operator's call there, so ANYRAY_CODEX_PROVIDER applies only to
98
+ // placeholder mode.
99
+ const provider = subscription
100
+ ? 'openai'
101
+ : process.env.ANYRAY_CODEX_PROVIDER?.trim();
102
+ const model = resolveModel(subscription);
103
+ // Placeholder mode: the dev's personal gateway key (from an invite), else
104
+ // the shared placeholder, is the bearer. Subscription mode: the seat OAuth
105
+ // token owns Authorization, so the personal key rides x-anyray-api-key.
106
+ const headers = subscription
107
+ ? [
108
+ // No static Authorization: requires_openai_auth makes Codex attach its
109
+ // ChatGPT sign-in (Bearer access token + ChatGPT-Account-ID) itself.
110
+ `"x-anyray-auth-mode" = "passthrough"`,
111
+ `"x-anyray-custom-host" = ${tomlString(CHATGPT_CODEX_BACKEND)}`,
112
+ `"x-anyray-forward-headers" = ${tomlString(SUBSCRIPTION_FORWARD_HEADERS)}`,
113
+ ...(target.clientKey
114
+ ? [`"x-anyray-api-key" = ${tomlString(target.clientKey)}`]
115
+ : []),
116
+ ]
117
+ : [
118
+ `"Authorization" = ${tomlString(`Bearer ${target.clientKey ?? target.placeholderKey}`)}`,
119
+ ];
120
+ if (meta)
121
+ headers.push(`"x-anyray-metadata" = ${tomlString(meta)}`);
122
+ if (provider)
123
+ headers.push(`"x-anyray-provider" = ${tomlString(provider)}`);
124
+ const lines = [
125
+ '# Anyray gateway profile for Codex 0.139+ (a standalone profile file).',
126
+ '# Managed by anyray-connect — the whole file is owned by the tool.',
127
+ '# Use with: codex --profile anyray',
128
+ ...(model ? [`model = ${tomlString(model)}`] : []),
129
+ 'model_provider = "anyray"',
130
+ '',
131
+ '[model_providers.anyray]',
132
+ 'name = "Anyray Gateway"',
133
+ `base_url = ${tomlString(target.openaiBaseUrl)}`,
134
+ 'wire_api = "responses"',
135
+ ...(subscription ? ['requires_openai_auth = true'] : []),
136
+ `http_headers = { ${headers.join(', ')} }`,
137
+ ];
138
+ return lines.join('\n') + '\n';
139
+ };
140
+ export const codex = {
141
+ id: 'codex',
142
+ title: 'Codex CLI',
143
+ automatic: true,
144
+ async detect() {
145
+ const home = codexHome();
146
+ const installed = await fileExists(home);
147
+ return {
148
+ installed,
149
+ detail: installed ? profilePath() : `no ${home} directory`,
150
+ };
151
+ },
152
+ async apply(target, opts) {
153
+ const path = profilePath();
154
+ const meta = codexMetadata(target);
155
+ const subscription = target.authMode === 'subscription';
156
+ const model = resolveModel(subscription);
157
+ const messages = [
158
+ `${path} ← standalone Codex profile (model_provider = anyray)`,
159
+ `Base URL: ${target.openaiBaseUrl}/responses (wire_api = responses)`,
160
+ subscription
161
+ ? 'Subscription mode: Codex keeps your ChatGPT sign-in; its OAuth token passes through the gateway to the ChatGPT backend (never stored). Requires being signed in with ChatGPT, not an API key.'
162
+ : `Model: ${model}${process.env.ANYRAY_CODEX_MODEL
163
+ ? ''
164
+ : ' (default — set ANYRAY_CODEX_MODEL to target a specific model)'}`,
165
+ ...(subscription && !process.env.ANYRAY_CODEX_MODEL
166
+ ? [
167
+ 'Model: inherits your config.toml default (the seat backend only accepts real Codex models).',
168
+ ]
169
+ : []),
170
+ `Attribution: ${meta ?? '(none — pass --user)'}`,
171
+ 'Run Codex through the gateway with: codex --profile anyray',
172
+ '(Plain `codex` keeps your existing default config.)',
173
+ ];
174
+ if (opts.dryRun)
175
+ return { status: 'configured', messages };
176
+ // Write our standalone profile file (we own it whole), then strip any
177
+ // legacy inline block a pre-0.139 connect left in config.toml so the two
178
+ // can't collide (Codex 0.139 errors on a leftover [profiles.anyray] table).
179
+ await writeFile(path, profileFile(target));
180
+ const migrated = await removeProfileBlock(baseConfigPath());
181
+ if (migrated) {
182
+ messages.push(`Migrated: removed the legacy [profiles.anyray] block from ${baseConfigPath()}.`);
183
+ }
184
+ return { status: 'configured', messages, changedFiles: [path] };
185
+ },
186
+ async revert(opts) {
187
+ const path = profilePath();
188
+ const hasProfile = await fileExists(path);
189
+ // Also clean up any legacy inline block left in config.toml by old versions.
190
+ const base = baseConfigPath();
191
+ const hasLegacy = (await fileExists(base))
192
+ ? await (async () => {
193
+ if (opts.dryRun) {
194
+ const { readFile } = await import('node:fs/promises');
195
+ return (await readFile(base, 'utf-8')).includes('# >>> anyray connect >>>');
196
+ }
197
+ return removeProfileBlock(base);
198
+ })()
199
+ : false;
200
+ if (!hasProfile && !hasLegacy) {
201
+ return { status: 'not-found', messages: ['nothing to revert'] };
202
+ }
203
+ const changed = [];
204
+ const messages = [];
205
+ if (hasProfile) {
206
+ if (!opts.dryRun)
207
+ await rm(path, { force: true });
208
+ changed.push(path);
209
+ messages.push(opts.dryRun ? `would remove ${path}` : `removed ${path}`);
210
+ }
211
+ if (hasLegacy) {
212
+ changed.push(base);
213
+ messages.push(opts.dryRun
214
+ ? `would remove the legacy Anyray block from ${base}`
215
+ : `removed the legacy Anyray block from ${base}`);
216
+ }
217
+ return { status: 'reverted', messages, changedFiles: changed };
218
+ },
219
+ };
220
+ //# sourceMappingURL=codex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/tools/codex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Tool-adapter registry.
3
+ *
4
+ * Adapters are CODE modules registered here by id. The CLI iterates this list —
5
+ * it never special-cases a tool. To support a new tool: write an adapter under
6
+ * this folder and add one line below. Mirrors the optimizer's strategy REGISTRY.
7
+ */
8
+ import { claudeCode } from './claudeCode.js';
9
+ import { codex } from './codex.js';
10
+ import { shellEnv } from './shellEnv.js';
11
+ import { cursor, windsurf } from './manual.js';
12
+ export const REGISTRY = [
13
+ claudeCode,
14
+ codex,
15
+ cursor,
16
+ windsurf,
17
+ shellEnv,
18
+ ];
19
+ export const byId = (id) => REGISTRY.find((t) => t.id === id);
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,UAAU;IACV,KAAK;IACL,MAAM;IACN,QAAQ;IACR,QAAQ;CACT,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EAAU,EAA2B,EAAE,CAC1D,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Manual adapters for tools whose config lives in app state we shouldn't script
3
+ * (Cursor, Windsurf). We detect whether the app is installed and print precise
4
+ * UI steps; we never write their internal state. Honest about the boundary —
5
+ * an automatic adapter can replace one of these later without touching the CLI.
6
+ */
7
+ import { homedir, platform } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { fileExists } from '../util/jsonFile.js';
10
+ import { metadataHeaderValue } from '../types.js';
11
+ /** Per-OS app-support directories where these editors keep their config. */
12
+ const appSupportDirs = (appDir) => {
13
+ const home = homedir();
14
+ switch (platform()) {
15
+ case 'darwin':
16
+ return [join(home, 'Library', 'Application Support', appDir)];
17
+ case 'win32':
18
+ return [join(process.env.APPDATA ?? join(home, 'AppData', 'Roaming'), appDir)];
19
+ default:
20
+ return [join(home, '.config', appDir)];
21
+ }
22
+ };
23
+ const makeManualAdapter = (spec) => ({
24
+ id: spec.id,
25
+ title: spec.title,
26
+ automatic: false,
27
+ async detect() {
28
+ for (const dir of appSupportDirs(spec.appDir)) {
29
+ if (await fileExists(dir))
30
+ return { installed: true, detail: dir };
31
+ }
32
+ return { installed: false, detail: `${spec.appDir} not found` };
33
+ },
34
+ async apply(target) {
35
+ return { status: 'manual', messages: spec.steps(target) };
36
+ },
37
+ async revert(_opts) {
38
+ return {
39
+ status: 'manual',
40
+ messages: [
41
+ `${spec.title}: clear the custom OpenAI base URL / key in its settings to revert.`,
42
+ ],
43
+ };
44
+ },
45
+ });
46
+ const openAiStyleSteps = (settingsLabel) => (target) => {
47
+ const meta = metadataHeaderValue(target.metadata);
48
+ // Manual tools need the value to paste in, so a personal gateway key is
49
+ // shown in full here — that's the only way it lands in the tool's config.
50
+ return [
51
+ `Open ${settingsLabel} and enable a custom OpenAI base URL:`,
52
+ ` • Base URL: ${target.openaiBaseUrl}`,
53
+ target.clientKey
54
+ ? ` • API key: ${target.clientKey} (your personal gateway key — keep it private)`
55
+ : ` • API key: ${target.placeholderKey} (placeholder — the gateway holds the real key)`,
56
+ meta
57
+ ? ` • If it allows custom headers, add: x-anyray-metadata: ${meta}`
58
+ : ' • (No attribution metadata resolved; set --user to attribute spend.)',
59
+ ];
60
+ };
61
+ export const cursor = makeManualAdapter({
62
+ id: 'cursor',
63
+ title: 'Cursor',
64
+ appDir: 'Cursor',
65
+ steps: openAiStyleSteps('Cursor → Settings → Models → OpenAI API Key'),
66
+ });
67
+ export const windsurf = makeManualAdapter({
68
+ id: 'windsurf',
69
+ title: 'Windsurf',
70
+ appDir: 'Windsurf',
71
+ steps: openAiStyleSteps('Windsurf → Settings → model provider / OpenAI'),
72
+ });
73
+ //# sourceMappingURL=manual.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manual.js","sourceRoot":"","sources":["../../src/tools/manual.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Shell-env adapter — generic fallback for OpenAI/Anthropic SDKs, scripts, and
3
+ * CLIs (e.g. Codex) that read standard env vars.
4
+ *
5
+ * Writes a single managed block into the shell profile with both SDK families'
6
+ * base URLs and placeholder keys. The standard SDKs have no env for custom
7
+ * headers, so per-request `x-anyray-metadata` attribution isn't set here —
8
+ * attribution for these falls back to the gateway's defaults. Tools that DO
9
+ * support headers (Claude Code) have their own adapter.
10
+ */
11
+ import { detectProfilePath, removeProfileBlock, upsertProfileBlock } from '../util/profile.js';
12
+ import { fileExists } from '../util/jsonFile.js';
13
+ import { maskKey } from '../util/redeem.js';
14
+ // Single-quote the values: shells treat single-quoted strings literally, so a
15
+ // `$`, backtick, or double-quote in a URL/key can't trigger expansion or break
16
+ // out of the assignment. (Single quotes themselves can't appear in these.)
17
+ // The exported key is the dev's personal gateway key (from an invite) when
18
+ // present, else the shared placeholder.
19
+ const envLines = (target, key) => [
20
+ `export OPENAI_BASE_URL='${target.openaiBaseUrl}'`,
21
+ `export OPENAI_API_KEY='${key}'`,
22
+ `export ANTHROPIC_BASE_URL='${target.anthropicBaseUrl}'`,
23
+ `export ANTHROPIC_AUTH_TOKEN='${key}'`,
24
+ ];
25
+ export const shellEnv = {
26
+ id: 'shell-env',
27
+ title: 'Shell environment (SDKs, scripts, Codex)',
28
+ automatic: true,
29
+ async detect() {
30
+ const path = detectProfilePath();
31
+ return {
32
+ installed: true, // a profile can always be created
33
+ detail: (await fileExists(path)) ? path : `${path} (will be created)`,
34
+ };
35
+ },
36
+ async apply(target, opts) {
37
+ const path = detectProfilePath();
38
+ const key = target.clientKey ?? target.placeholderKey;
39
+ const lines = envLines(target, key);
40
+ // Console output masks a personal key (the file gets the real one) — a
41
+ // terminal/scrollback should never show the full credential.
42
+ const shownLines = target.clientKey
43
+ ? envLines(target, maskKey(target.clientKey))
44
+ : lines;
45
+ const messages = [
46
+ `${path} ← managed Anyray block`,
47
+ ...shownLines.map((l) => ` ${l}`),
48
+ `Run \`source ${path}\` or open a new terminal to apply.`,
49
+ 'Note: standard SDK env vars carry no per-user header, so attribution',
50
+ 'for these falls back to the gateway default.',
51
+ ];
52
+ if (opts.dryRun)
53
+ return { status: 'configured', messages };
54
+ await upsertProfileBlock(path, lines);
55
+ return { status: 'configured', messages, changedFiles: [path] };
56
+ },
57
+ async revert(opts) {
58
+ const path = detectProfilePath();
59
+ if (opts.dryRun) {
60
+ return {
61
+ status: 'reverted',
62
+ messages: [`would remove the Anyray block from ${path}`],
63
+ };
64
+ }
65
+ const removed = await removeProfileBlock(path);
66
+ return {
67
+ status: removed ? 'reverted' : 'not-found',
68
+ messages: [
69
+ removed
70
+ ? `removed the Anyray block from ${path}`
71
+ : `no Anyray block found in ${path}`,
72
+ ],
73
+ changedFiles: removed ? [path] : undefined,
74
+ };
75
+ },
76
+ };
77
+ //# sourceMappingURL=shellEnv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shellEnv.js","sourceRoot":"","sources":["../../src/tools/shellEnv.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared types for `anyray-connect`.
3
+ *
4
+ * The CLI resolves a single `GatewayTarget` (where the gateway lives + the
5
+ * placeholder key + content-free attribution) and hands it to each registered
6
+ * `ToolAdapter`. Adapters own one tool's config; they never make routing or
7
+ * provider decisions — that's the gateway's job. This mirrors the optimizer's
8
+ * strategy-registry seam: add a tool by adding an adapter, not by branching the
9
+ * orchestrator.
10
+ *
11
+ * PRIVACY: nothing here carries prompt/response content. `metadata` is identity
12
+ * grouping only (user/team/intent/session), matching the gateway's
13
+ * `x-anyray-metadata` contract.
14
+ */
15
+ /** Build the `x-anyray-metadata` header value, or undefined when empty. */
16
+ export const metadataHeaderValue = (metadata) => {
17
+ const entries = Object.entries(metadata).filter(([, v]) => v);
18
+ if (entries.length === 0)
19
+ return undefined;
20
+ return JSON.stringify(Object.fromEntries(entries));
21
+ };
22
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAoGH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,QAA6B,EACT,EAAE;IACtB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;AACrD,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import os from 'node:os';
3
+ const gitConfig = (field) => {
4
+ try {
5
+ const value = execFileSync('git', ['config', '--get', field], {
6
+ encoding: 'utf-8',
7
+ stdio: ['ignore', 'pipe', 'ignore'],
8
+ }).trim();
9
+ return value || undefined;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ };
15
+ const osUsername = () => {
16
+ try {
17
+ return os.userInfo().username || undefined;
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ };
23
+ /** Best-effort display name: git user.name → user.email → OS username. Never throws. */
24
+ export const detectDisplayName = () => gitConfig('user.name') ?? gitConfig('user.email') ?? osUsername() ?? 'unknown';
25
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/util/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,SAAS,GAAG,CAAC,KAAa,EAAsB,EAAE;IACtD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE;YAC5D,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,KAAK,IAAI,SAAS,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,GAAuB,EAAE;IAC1C,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,IAAI,SAAS,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC,CAAC;AAEF,wFAAwF;AACxF,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAW,EAAE,CAC5C,SAAS,CAAC,WAAW,CAAC,IAAI,SAAS,CAAC,YAAY,CAAC,IAAI,UAAU,EAAE,IAAI,SAAS,CAAC"}