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.
- package/README.md +95 -0
- package/dist/cli.js +383 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/target.js +56 -0
- package/dist/target.js.map +1 -0
- package/dist/tools/claudeCode.js +131 -0
- package/dist/tools/claudeCode.js.map +1 -0
- package/dist/tools/codex.js +220 -0
- package/dist/tools/codex.js.map +1 -0
- package/dist/tools/index.js +20 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/manual.js +73 -0
- package/dist/tools/manual.js.map +1 -0
- package/dist/tools/shellEnv.js +77 -0
- package/dist/tools/shellEnv.js.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/dist/util/identity.js +25 -0
- package/dist/util/identity.js.map +1 -0
- package/dist/util/jsonFile.js +41 -0
- package/dist/util/jsonFile.js.map +1 -0
- package/dist/util/log.js +19 -0
- package/dist/util/log.js.map +1 -0
- package/dist/util/persist.js +27 -0
- package/dist/util/persist.js.map +1 -0
- package/dist/util/preflight.js +41 -0
- package/dist/util/preflight.js.map +1 -0
- package/dist/util/profile.js +54 -0
- package/dist/util/profile.js.map +1 -0
- package/dist/util/redeem.js +82 -0
- package/dist/util/redeem.js.map +1 -0
- package/package.json +44 -0
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAQxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD;iFACiF;AACjF,MAAM,aAAa,GAAG,gBAAgB,CAAC;AAEvC;;uBAEuB;AACvB,MAAM,cAAc,GAAG,OAAO,CAAC;AAC/B,MAAM,oBAAoB,GAAG,WAAW,CAAC;AAEzC;iEACiE;AACjE,MAAM,qBAAqB,GAAG,uCAAuC,CAAC;AAEtE;;;+EAG+E;AAC/E,MAAM,4BAA4B,GAChC,iHAAiH,CAAC;AAEpH,4EAA4E;AAC5E,MAAM,SAAS,GAAG,GAAW,EAAE,CAC7B,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAE9D;gFACgF;AAChF,MAAM,cAAc,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,CAAC;AACtE,MAAM,WAAW,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,oBAAoB,CAAC,CAAC;AAE1E;;;;;GAKG;AACH,MAAM,aAAa,GAAG,CAAC,MAAqB,EAAsB,EAAE,CAClE,mBAAmB,CAAC;IAClB,GAAG,MAAM,CAAC,QAAQ;IAClB,MAAM,EACJ,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE;QACvC,MAAM,CAAC,QAAQ,CAAC,MAAM;QACtB,cAAc;IAChB,WAAW,EACT,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,IAAI,EAAE;QAC7C,MAAM,CAAC,QAAQ,CAAC,WAAW;QAC3B,oBAAoB;CACvB,CAAC,CAAC;AAEL,+DAA+D;AAC/D,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;AAE3D;;4DAE4D;AAC5D,MAAM,YAAY,GAAG,CAAC,YAAqB,EAAsB,EAAE;IACjE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,CAAC;IACxD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,OAAO,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;AAClD,CAAC,CAAC;AAEF,0EAA0E;AAC1E,MAAM,WAAW,GAAG,CAAC,MAAqB,EAAU,EAAE;IACpD,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,KAAK,cAAc,CAAC;IACxD,yEAAyE;IACzE,sEAAsE;IACtE,oBAAoB;IACpB,MAAM,QAAQ,GAAG,YAAY;QAC3B,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,IAAI,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAEzC,0EAA0E;IAC1E,2EAA2E;IAC3E,wEAAwE;IACxE,MAAM,OAAO,GAAa,YAAY;QACpC,CAAC,CAAC;YACE,uEAAuE;YACvE,qEAAqE;YACrE,sCAAsC;YACtC,4BAA4B,UAAU,CAAC,qBAAqB,CAAC,EAAE;YAC/D,gCAAgC,UAAU,CAAC,4BAA4B,CAAC,EAAE;YAC1E,GAAG,CAAC,MAAM,CAAC,SAAS;gBAClB,CAAC,CAAC,CAAC,wBAAwB,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC1D,CAAC,CAAC,EAAE,CAAC;SACR;QACH,CAAC,CAAC;YACE,qBAAqB,UAAU,CAC7B,UAAU,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,cAAc,EAAE,CACtD,EAAE;SACJ,CAAC;IACN,IAAI,IAAI;QAAE,OAAO,CAAC,IAAI,CAAC,yBAAyB,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpE,IAAI,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,yBAAyB,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE5E,MAAM,KAAK,GAAG;QACZ,wEAAwE;QACxE,oEAAoE;QACpE,qCAAqC;QACrC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,2BAA2B;QAC3B,EAAE;QACF,0BAA0B;QAC1B,yBAAyB;QACzB,cAAc,UAAU,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE;QAChD,wBAAwB;QACxB,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,oBAAoB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;KAC3C,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAgB;IAChC,EAAE,EAAE,OAAO;IACX,KAAK,EAAE,WAAW;IAClB,SAAS,EAAE,IAAI;IAEf,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO;YACL,SAAS;YACT,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,YAAY;SAC3D,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAqB,EAAE,IAAkB;QACnD,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,KAAK,cAAc,CAAC;QACxD,MAAM,KAAK,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QAEzC,MAAM,QAAQ,GAAG;YACf,GAAG,IAAI,uDAAuD;YAC9D,aAAa,MAAM,CAAC,aAAa,oCAAoC;YACrE,YAAY;gBACV,CAAC,CAAC,+LAA+L;gBACjM,CAAC,CAAC,UAAU,KAAK,GACb,OAAO,CAAC,GAAG,CAAC,kBAAkB;oBAC5B,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,iEACN,EAAE;YACN,GAAG,CAAC,YAAY,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB;gBACjD,CAAC,CAAC;oBACE,6FAA6F;iBAC9F;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,gBAAgB,IAAI,IAAI,sBAAsB,EAAE;YAChD,6DAA6D;YAC7D,qDAAqD;SACtD,CAAC;QACF,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;QAE3D,sEAAsE;QACtE,yEAAyE;QACzE,4EAA4E;QAC5E,MAAM,SAAS,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,cAAc,EAAE,CAAC,CAAC;QAC5D,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,IAAI,CACX,6DAA6D,cAAc,EAAE,GAAG,CACjF,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAkB;QAC7B,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;QAC3B,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;QAC1C,6EAA6E;QAC7E,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;gBAChB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;oBAChB,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;oBACtD,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAC7C,0BAA0B,CAC3B,CAAC;gBACJ,CAAC;gBACD,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;YAClC,CAAC,CAAC,EAAE;YACN,CAAC,CAAC,KAAK,CAAC;QAEV,IAAI,CAAC,UAAU,IAAI,CAAC,SAAS,EAAE,CAAC;YAC9B,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAClE,CAAC;QAED,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,QAAQ,CAAC,IAAI,CACX,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CACzD,CAAC;QACJ,CAAC;QACD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,QAAQ,CAAC,IAAI,CACX,IAAI,CAAC,MAAM;gBACT,CAAC,CAAC,6CAA6C,IAAI,EAAE;gBACrD,CAAC,CAAC,wCAAwC,IAAI,EAAE,CACnD,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;IACjE,CAAC;CACF,CAAC"}
|
|
@@ -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"}
|