anyray-connect 0.8.0 → 0.9.1
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/dist/tools/copilotChat.js +120 -0
- package/dist/tools/copilotChat.js.map +1 -0
- package/dist/tools/cursor.js +273 -0
- package/dist/tools/cursor.js.map +1 -0
- package/dist/tools/index.js +4 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/manual.js +4 -19
- package/dist/tools/manual.js.map +1 -1
- package/dist/util/appSupport.js +24 -0
- package/dist/util/appSupport.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot Chat adapter — manual, by design.
|
|
3
|
+
*
|
|
4
|
+
* Copilot is two products with very different reach:
|
|
5
|
+
* - Inline completions (the ghost-text suggestions) go to GitHub's own backend
|
|
6
|
+
* and CANNOT be redirected — there is no BYOK seam for them.
|
|
7
|
+
* - Copilot CHAT supports "bring your own key" (BYOK): an OpenAI-compatible
|
|
8
|
+
* custom endpoint you point at any base URL. That part we can route through
|
|
9
|
+
* the gateway. It needs Copilot Business/Enterprise (or VS Code 1.122+, which
|
|
10
|
+
* dropped the GitHub-sign-in requirement for BYOK).
|
|
11
|
+
*
|
|
12
|
+
* Why manual (not an automatic writer like claudeCode/cursor):
|
|
13
|
+
* 1. The config is UI-driven. The custom endpoint is added through the model
|
|
14
|
+
* picker → "Manage Language Models" wizard, which prompts for the URL/key
|
|
15
|
+
* and (when offered) stashes the API key in the OS credential store — not in
|
|
16
|
+
* a file we can script.
|
|
17
|
+
* 2. The on-disk shape (`chatLanguageModels.json`, replacing the now-deprecated
|
|
18
|
+
* `github.copilot.chat.customOAIModels` setting) is still evolving and isn't
|
|
19
|
+
* a documented contract. Blind-writing an app's own state is exactly the
|
|
20
|
+
* corruption risk cursor.ts is paranoid about — so we print precise steps
|
|
21
|
+
* plus a paste-able snippet instead of guessing the file.
|
|
22
|
+
*
|
|
23
|
+
* Attribution: unlike Cursor (no header field), Copilot Chat's custom-endpoint
|
|
24
|
+
* config supports a per-model `requestHeaders` object, so content-free spend
|
|
25
|
+
* attribution rides `x-anyray-metadata` there — same contract as every other
|
|
26
|
+
* adapter.
|
|
27
|
+
*
|
|
28
|
+
* PRIVACY: the snippet/steps carry only the gateway base URL, a
|
|
29
|
+
* placeholder/personal key, and the identity-only `x-anyray-metadata` value.
|
|
30
|
+
* Never prompt/response content.
|
|
31
|
+
*/
|
|
32
|
+
import { fileExists } from '../util/jsonFile.js';
|
|
33
|
+
import { appSupportDir } from '../util/appSupport.js';
|
|
34
|
+
import { metadataHeaderValue } from '../types.js';
|
|
35
|
+
/** VS Code distributions that ship the Copilot Chat extension. */
|
|
36
|
+
const VSCODE_VARIANTS = ['Code', 'Code - Insiders', 'VSCodium'];
|
|
37
|
+
/** The custom-endpoint URL Copilot Chat posts to (OpenAI chat-completions). */
|
|
38
|
+
const chatCompletionsUrl = (target) => `${target.openaiBaseUrl.replace(/\/+$/, '')}/chat/completions`;
|
|
39
|
+
/**
|
|
40
|
+
* The `chatLanguageModels.json` provider object that points Copilot Chat at the
|
|
41
|
+
* gateway. Pure and unit-tested — the snippet we print is exactly this, so the
|
|
42
|
+
* test pins the field shape (vendor/apiType/url/requestHeaders) we tell users to
|
|
43
|
+
* paste. The model `id` is an example the user retargets to a model their
|
|
44
|
+
* gateway actually serves.
|
|
45
|
+
*/
|
|
46
|
+
export const byokModelConfig = (target) => {
|
|
47
|
+
const meta = metadataHeaderValue(target.metadata);
|
|
48
|
+
return {
|
|
49
|
+
name: 'Anyray',
|
|
50
|
+
vendor: 'customendpoint',
|
|
51
|
+
apiType: 'chat-completions',
|
|
52
|
+
apiKey: target.clientKey ?? target.placeholderKey,
|
|
53
|
+
models: [
|
|
54
|
+
{
|
|
55
|
+
id: 'gpt-4.1',
|
|
56
|
+
name: 'Anyray Gateway',
|
|
57
|
+
url: chatCompletionsUrl(target),
|
|
58
|
+
toolCalling: true,
|
|
59
|
+
vision: true,
|
|
60
|
+
maxInputTokens: 128000,
|
|
61
|
+
maxOutputTokens: 16000,
|
|
62
|
+
// Copilot Chat forwards these per-request — the only place attribution
|
|
63
|
+
// can ride, since the wizard has no header field. Omitted when no
|
|
64
|
+
// metadata resolved (a bare header would attribute spend to nobody).
|
|
65
|
+
...(meta ? { requestHeaders: { 'x-anyray-metadata': meta } } : {}),
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
/** The full paste-able `chatLanguageModels.json` array (one provider). */
|
|
71
|
+
export const byokSnippet = (target) => JSON.stringify([byokModelConfig(target)], null, 2);
|
|
72
|
+
/** The manual setup steps shown to the user. */
|
|
73
|
+
const copilotSteps = (target, reason) => {
|
|
74
|
+
const meta = metadataHeaderValue(target.metadata);
|
|
75
|
+
const key = target.clientKey ?? target.placeholderKey;
|
|
76
|
+
return [
|
|
77
|
+
...(reason ? [reason] : []),
|
|
78
|
+
'GitHub Copilot Chat — Bring Your Own Key (chat only; inline completions still go to GitHub and cannot be routed). Needs Copilot Business/Enterprise or VS Code 1.122+.',
|
|
79
|
+
'In VS Code: open the Chat view → the model picker → "Manage Language Models…" → "Add Model" → "OpenAI Compatible / Custom Endpoint", then enter:',
|
|
80
|
+
'(No "Custom Endpoint" option? Update VS Code — the BYOK custom-endpoint flow is recent; Insiders gets it first.)',
|
|
81
|
+
` • Endpoint URL: ${chatCompletionsUrl(target)}`,
|
|
82
|
+
' • API type: chat-completions',
|
|
83
|
+
target.clientKey
|
|
84
|
+
? ` • API key: ${key} (your personal gateway key — keep it private; store it in the OS credential store when VS Code offers)`
|
|
85
|
+
: ` • API key: ${key} (placeholder — the gateway holds the real key)`,
|
|
86
|
+
' • Model id: a model your Anyray gateway serves (ask your admin, e.g. gpt-4.1).',
|
|
87
|
+
meta
|
|
88
|
+
? `VS Code opens chatLanguageModels.json — add a requestHeaders entry to the model so spend is attributed:\n "requestHeaders": { "x-anyray-metadata": ${JSON.stringify(meta)} }`
|
|
89
|
+
: '(No attribution metadata resolved; set --user, or use --invite for a per-user key — it rides an x-anyray-metadata requestHeader.)',
|
|
90
|
+
'Or paste this whole provider object into chatLanguageModels.json (set the key via the credential store if you prefer):',
|
|
91
|
+
byokSnippet(target),
|
|
92
|
+
'Reload VS Code, then pick the Anyray model in the Chat model picker.',
|
|
93
|
+
];
|
|
94
|
+
};
|
|
95
|
+
export const copilotChat = {
|
|
96
|
+
id: 'copilot',
|
|
97
|
+
title: 'GitHub Copilot Chat',
|
|
98
|
+
automatic: false,
|
|
99
|
+
async detect() {
|
|
100
|
+
for (const variant of VSCODE_VARIANTS) {
|
|
101
|
+
const dir = appSupportDir(variant);
|
|
102
|
+
if (await fileExists(dir))
|
|
103
|
+
return { installed: true, detail: dir };
|
|
104
|
+
}
|
|
105
|
+
return { installed: false, detail: 'no VS Code install found' };
|
|
106
|
+
},
|
|
107
|
+
async apply(target, _opts) {
|
|
108
|
+
// Always manual — nothing is written, so dry-run and apply are identical.
|
|
109
|
+
return { status: 'manual', messages: copilotSteps(target) };
|
|
110
|
+
},
|
|
111
|
+
async revert(_opts) {
|
|
112
|
+
return {
|
|
113
|
+
status: 'manual',
|
|
114
|
+
messages: [
|
|
115
|
+
'GitHub Copilot Chat: open the Chat model picker → "Manage Language Models…", remove the "Anyray" custom endpoint, and clear its API key from the OS credential store.',
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=copilotChat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilotChat.js","sourceRoot":"","sources":["../../src/tools/copilotChat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor adapter — automatic where it can be, manual where it can't.
|
|
3
|
+
*
|
|
4
|
+
* Unlike Claude Code (a plain `settings.json`), Cursor keeps its OpenAI
|
|
5
|
+
* override in `state.vscdb` — an undocumented SQLite app-state DB (the VS Code
|
|
6
|
+
* `ItemTable`: key → JSON blob). We drive it through the system `sqlite3` CLI
|
|
7
|
+
* (no native module, works under both Node and the Bun binary), with hard
|
|
8
|
+
* safety rails because it's an app-owned store:
|
|
9
|
+
*
|
|
10
|
+
* 1. Cursor must be CLOSED — it caches state in memory and would clobber our
|
|
11
|
+
* write on exit. If it's running we refuse and print the manual steps.
|
|
12
|
+
* 2. We BACK UP `state.vscdb` before touching it (`.anyray-bak`), so `revert`
|
|
13
|
+
* is a restore and a botched write is recoverable.
|
|
14
|
+
* 3. We only write when we RECOGNIZE Cursor's settings shape. If we can't find
|
|
15
|
+
* it (different Cursor version, or the key is keychain-encrypted and not in
|
|
16
|
+
* the DB at all), we fall back to the manual steps rather than guess — a
|
|
17
|
+
* blind write into an app's own DB is how you corrupt it.
|
|
18
|
+
*
|
|
19
|
+
* The recognized shape is marked ASSUMED below: it must be confirmed against a
|
|
20
|
+
* real Cursor (Settings → Models → set a sentinel base URL/key, quit Cursor,
|
|
21
|
+
* grep state.vscdb) before the automatic path can be trusted. Until then this
|
|
22
|
+
* adapter degrades to exactly the previous manual behavior — safely.
|
|
23
|
+
*
|
|
24
|
+
* PRIVACY: writes only Anyray's base URL + (placeholder/personal) key into
|
|
25
|
+
* Cursor's own config. Never logs the user's existing secrets or prompt content.
|
|
26
|
+
*/
|
|
27
|
+
import { execFileSync } from 'node:child_process';
|
|
28
|
+
import { copyFileSync, existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
29
|
+
import { platform, tmpdir } from 'node:os';
|
|
30
|
+
import { join } from 'node:path';
|
|
31
|
+
import { appSupportDir } from '../util/appSupport.js';
|
|
32
|
+
import { metadataHeaderValue } from '../types.js';
|
|
33
|
+
/** Per-OS Cursor user-data dir (the app-support root that holds globalStorage). */
|
|
34
|
+
const cursorUserDir = () => appSupportDir('Cursor');
|
|
35
|
+
export const stateDbPath = () => join(cursorUserDir(), 'User', 'globalStorage', 'state.vscdb');
|
|
36
|
+
export const backupPath = () => `${stateDbPath()}.anyray-bak`;
|
|
37
|
+
/** True if the `sqlite3` CLI is on PATH (the only external dependency). */
|
|
38
|
+
export const sqliteAvailable = () => {
|
|
39
|
+
try {
|
|
40
|
+
execFileSync('sqlite3', ['--version'], { stdio: 'ignore' });
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/** Best-effort: is Cursor running? A write while it's open would be clobbered. */
|
|
48
|
+
const cursorRunning = () => {
|
|
49
|
+
try {
|
|
50
|
+
if (platform() === 'win32') {
|
|
51
|
+
const out = execFileSync('tasklist', ['/FI', 'IMAGENAME eq Cursor.exe'], {
|
|
52
|
+
encoding: 'utf-8',
|
|
53
|
+
});
|
|
54
|
+
return /Cursor\.exe/i.test(out);
|
|
55
|
+
}
|
|
56
|
+
// `pgrep -x` matches the exact process name; exit 0 ⇒ at least one match.
|
|
57
|
+
execFileSync('pgrep', ['-x', 'Cursor'], { stdio: 'ignore' });
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Non-zero exit (no match) or pgrep/tasklist missing → treat as not running.
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/** Read one ItemTable value (the raw JSON text), or undefined if absent. */
|
|
66
|
+
const readItem = (db, key) => {
|
|
67
|
+
const sql = `SELECT value FROM ItemTable WHERE key=${sqlQuote(key)};`;
|
|
68
|
+
const out = execFileSync('sqlite3', [db, sql], { encoding: 'utf-8' });
|
|
69
|
+
const trimmed = out.replace(/\n$/, '');
|
|
70
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
71
|
+
};
|
|
72
|
+
/** All ItemTable keys (so we can scan for the settings row across versions). */
|
|
73
|
+
const listKeys = (db) => {
|
|
74
|
+
const out = execFileSync('sqlite3', [db, 'SELECT key FROM ItemTable;'], {
|
|
75
|
+
encoding: 'utf-8',
|
|
76
|
+
});
|
|
77
|
+
return out.split('\n').filter((k) => k.length > 0);
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Write a value into an ItemTable row via `readfile()`, so a JSON blob full of
|
|
81
|
+
* quotes never has to be SQL-escaped. The row must already exist (we only
|
|
82
|
+
* UPDATE rows we discovered).
|
|
83
|
+
*/
|
|
84
|
+
const writeItem = (db, key, value) => {
|
|
85
|
+
const dir = mkdtempSync(join(tmpdir(), 'anyray-cursor-'));
|
|
86
|
+
const tmp = join(dir, 'value.json');
|
|
87
|
+
try {
|
|
88
|
+
writeFileSync(tmp, value);
|
|
89
|
+
const sql = `UPDATE ItemTable SET value=readfile(${sqlQuote(tmp)}) WHERE key=${sqlQuote(key)};`;
|
|
90
|
+
execFileSync('sqlite3', [db, sql], { stdio: 'ignore' });
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
rmSync(dir, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
/** Single-quote-escape a string for an inline SQL literal. */
|
|
97
|
+
const sqlQuote = (s) => `'${s.replace(/'/g, "''")}'`;
|
|
98
|
+
/**
|
|
99
|
+
* Recognize Cursor's OpenAI-override settings object by FIELD SHAPE rather than
|
|
100
|
+
* a hardcoded field name, since the exact spelling differs across Cursor
|
|
101
|
+
* versions (and isn't a documented contract). The anchor is a field whose name
|
|
102
|
+
* contains both "openai" and "base"+"url" — strong enough that an unrelated row
|
|
103
|
+
* won't match. When found we patch that field plus, if present, the matching
|
|
104
|
+
* enable flag and api-key field (the key may instead live in the OS keychain,
|
|
105
|
+
* in which case there's nothing to write here and the caller tells the user to
|
|
106
|
+
* paste it once). Returns null when the value isn't the settings object — the
|
|
107
|
+
* caller then falls back to the manual steps rather than write blind.
|
|
108
|
+
*
|
|
109
|
+
* NOTE: still ASSUMED — the patterns are modeled on the expected layout and
|
|
110
|
+
* want one confirmation against a real Cursor (see the module header). The
|
|
111
|
+
* matching LOGIC is unit-tested; only the real field spelling is unverified.
|
|
112
|
+
*/
|
|
113
|
+
const matchKey = (obj, test) => Object.keys(obj).find(test);
|
|
114
|
+
const isBaseUrlField = (k) => /openai/i.test(k) && /base.?url/i.test(k);
|
|
115
|
+
const isApiKeyField = (k) => /openai/i.test(k) && /(api.?)?key/i.test(k) && !/base/i.test(k);
|
|
116
|
+
const isEnabledField = (k) => /openai/i.test(k) && /enabled?$/i.test(k);
|
|
117
|
+
export const patchCursorSettings = (rawValue, target) => {
|
|
118
|
+
let obj;
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse(rawValue);
|
|
121
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
obj = parsed;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const baseUrlKey = matchKey(obj, isBaseUrlField);
|
|
130
|
+
if (!baseUrlKey)
|
|
131
|
+
return null; // not the settings row → caller falls back
|
|
132
|
+
obj[baseUrlKey] = target.openaiBaseUrl;
|
|
133
|
+
const enabledKey = matchKey(obj, isEnabledField);
|
|
134
|
+
if (enabledKey)
|
|
135
|
+
obj[enabledKey] = true;
|
|
136
|
+
const apiKeyKey = matchKey(obj, isApiKeyField);
|
|
137
|
+
if (apiKeyKey)
|
|
138
|
+
obj[apiKeyKey] = target.clientKey ?? target.placeholderKey;
|
|
139
|
+
return { value: JSON.stringify(obj), wroteKey: Boolean(apiKeyKey) };
|
|
140
|
+
};
|
|
141
|
+
/** Find the ItemTable row holding the OpenAI settings, or undefined. */
|
|
142
|
+
const findSettingsRow = (db, target) => {
|
|
143
|
+
for (const key of listKeys(db)) {
|
|
144
|
+
const value = readItem(db, key);
|
|
145
|
+
if (!value)
|
|
146
|
+
continue;
|
|
147
|
+
const patch = patchCursorSettings(value, target);
|
|
148
|
+
if (patch)
|
|
149
|
+
return { key, patch };
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
};
|
|
153
|
+
/** The manual steps — the fallback whenever we can't safely automate. */
|
|
154
|
+
const manualSteps = (target, reason) => {
|
|
155
|
+
const meta = metadataHeaderValue(target.metadata);
|
|
156
|
+
return [
|
|
157
|
+
...(reason ? [reason] : []),
|
|
158
|
+
'Configure Cursor by hand: Cursor → Settings → Models → OpenAI API Key:',
|
|
159
|
+
` • Override OpenAI Base URL: ${target.openaiBaseUrl}`,
|
|
160
|
+
target.clientKey
|
|
161
|
+
? ` • API key: ${target.clientKey} (your personal gateway key — keep it private)`
|
|
162
|
+
: ` • API key: ${target.placeholderKey} (placeholder — the gateway holds the real key)`,
|
|
163
|
+
meta
|
|
164
|
+
? ` • Cursor has no custom-header field, so attribution rides your per-user key (above), not ${meta}.`
|
|
165
|
+
: ' • (No attribution metadata resolved; set --user, or use --invite for a per-user key.)',
|
|
166
|
+
];
|
|
167
|
+
};
|
|
168
|
+
export const cursor = {
|
|
169
|
+
id: 'cursor',
|
|
170
|
+
title: 'Cursor',
|
|
171
|
+
automatic: true,
|
|
172
|
+
async detect() {
|
|
173
|
+
const dir = cursorUserDir();
|
|
174
|
+
const installed = existsSync(dir);
|
|
175
|
+
return {
|
|
176
|
+
installed,
|
|
177
|
+
detail: installed ? stateDbPath() : 'Cursor user dir not found',
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
async apply(target, opts) {
|
|
181
|
+
const db = stateDbPath();
|
|
182
|
+
// Without sqlite3 or the DB, or with Cursor open, we can't safely write —
|
|
183
|
+
// fall back to the manual steps (which always work) instead of failing.
|
|
184
|
+
if (!sqliteAvailable()) {
|
|
185
|
+
return {
|
|
186
|
+
status: 'manual',
|
|
187
|
+
messages: manualSteps(target, 'sqlite3 not found — automatic Cursor config needs it.'),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (!existsSync(db)) {
|
|
191
|
+
return {
|
|
192
|
+
status: 'manual',
|
|
193
|
+
messages: manualSteps(target, `No Cursor state DB at ${db} (open Cursor once first).`),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (cursorRunning()) {
|
|
197
|
+
return {
|
|
198
|
+
status: 'manual',
|
|
199
|
+
messages: manualSteps(target, 'Cursor is running — quit it and re-run, or set it by hand:'),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (opts.dryRun) {
|
|
203
|
+
return {
|
|
204
|
+
status: 'configured',
|
|
205
|
+
messages: [
|
|
206
|
+
`Would back up ${db} and write the gateway base URL + key into Cursor's OpenAI override.`,
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Locate the settings row first; if we can't recognize it, don't touch the
|
|
211
|
+
// DB — fall back to manual (e.g. the key is keychain-encrypted, not in here).
|
|
212
|
+
const found = findSettingsRow(db, target);
|
|
213
|
+
if (!found) {
|
|
214
|
+
return {
|
|
215
|
+
status: 'manual',
|
|
216
|
+
messages: manualSteps(target, "Couldn't locate Cursor's OpenAI settings in its state DB (version difference or an encrypted key) — set it by hand:"),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// Back up, then patch the one row. The backup is what `revert` restores.
|
|
220
|
+
const backup = backupPath();
|
|
221
|
+
try {
|
|
222
|
+
copyFileSync(db, backup);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
return {
|
|
226
|
+
status: 'manual',
|
|
227
|
+
messages: manualSteps(target, `Couldn't back up ${db} (${error instanceof Error ? error.message : String(error)}) — refusing to write; set it by hand:`),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
writeItem(db, found.key, found.patch.value);
|
|
231
|
+
const key = target.clientKey ?? target.placeholderKey;
|
|
232
|
+
return {
|
|
233
|
+
status: 'configured',
|
|
234
|
+
changedFiles: [db],
|
|
235
|
+
messages: [
|
|
236
|
+
`Wrote Cursor's OpenAI override → base URL ${target.openaiBaseUrl} (backup at ${backup}).`,
|
|
237
|
+
found.patch.wroteKey
|
|
238
|
+
? 'Also set the API key.'
|
|
239
|
+
: `Cursor stores the API key outside this DB (keychain) — set it by hand once: Settings → Models → OpenAI API Key = ${key}`,
|
|
240
|
+
'Restart Cursor to pick it up. Note: only chat routes through Anyray — Tab/agent/indexing use Cursor’s own cloud.',
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
},
|
|
244
|
+
async revert(opts) {
|
|
245
|
+
const db = stateDbPath();
|
|
246
|
+
const backup = backupPath();
|
|
247
|
+
if (!existsSync(backup)) {
|
|
248
|
+
return {
|
|
249
|
+
status: 'manual',
|
|
250
|
+
messages: [
|
|
251
|
+
'No Anyray backup found. Revert by hand: Cursor → Settings → Models → clear the custom OpenAI base URL / key.',
|
|
252
|
+
],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (opts.dryRun) {
|
|
256
|
+
return { status: 'reverted', messages: [`Would restore ${db} from ${backup}.`] };
|
|
257
|
+
}
|
|
258
|
+
if (cursorRunning()) {
|
|
259
|
+
return {
|
|
260
|
+
status: 'manual',
|
|
261
|
+
messages: ['Cursor is running — quit it and re-run --revert to restore the backup.'],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
copyFileSync(backup, db);
|
|
265
|
+
rmSync(backup, { force: true });
|
|
266
|
+
return {
|
|
267
|
+
status: 'reverted',
|
|
268
|
+
changedFiles: [db],
|
|
269
|
+
messages: [`Restored Cursor's state DB from the Anyray backup. Restart Cursor.`],
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/tools/cursor.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/index.js
CHANGED
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
import { claudeCode } from './claudeCode.js';
|
|
9
9
|
import { codex } from './codex.js';
|
|
10
10
|
import { shellEnv } from './shellEnv.js';
|
|
11
|
-
import { cursor
|
|
11
|
+
import { cursor } from './cursor.js';
|
|
12
|
+
import { copilotChat } from './copilotChat.js';
|
|
13
|
+
import { windsurf } from './manual.js';
|
|
12
14
|
export const REGISTRY = [
|
|
13
15
|
claudeCode,
|
|
14
16
|
codex,
|
|
15
17
|
cursor,
|
|
18
|
+
copilotChat,
|
|
16
19
|
windsurf,
|
|
17
20
|
shellEnv,
|
|
18
21
|
];
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +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,
|
|
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,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,UAAU;IACV,KAAK;IACL,MAAM;IACN,WAAW;IACX,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"}
|
package/dist/tools/manual.js
CHANGED
|
@@ -4,22 +4,11 @@
|
|
|
4
4
|
* UI steps; we never write their internal state. Honest about the boundary —
|
|
5
5
|
* an automatic adapter can replace one of these later without touching the CLI.
|
|
6
6
|
*/
|
|
7
|
-
import { homedir, platform } from 'node:os';
|
|
8
|
-
import { join } from 'node:path';
|
|
9
7
|
import { fileExists } from '../util/jsonFile.js';
|
|
8
|
+
import { appSupportDir } from '../util/appSupport.js';
|
|
10
9
|
import { metadataHeaderValue } from '../types.js';
|
|
11
10
|
/** 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
|
-
};
|
|
11
|
+
const appSupportDirs = (appDir) => [appSupportDir(appDir)];
|
|
23
12
|
const makeManualAdapter = (spec) => ({
|
|
24
13
|
id: spec.id,
|
|
25
14
|
title: spec.title,
|
|
@@ -58,12 +47,8 @@ const openAiStyleSteps = (settingsLabel) => (target) => {
|
|
|
58
47
|
: ' • (No attribution metadata resolved; set --user to attribute spend.)',
|
|
59
48
|
];
|
|
60
49
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
title: 'Cursor',
|
|
64
|
-
appDir: 'Cursor',
|
|
65
|
-
steps: openAiStyleSteps('Cursor → Settings → Models → OpenAI API Key'),
|
|
66
|
-
});
|
|
50
|
+
// Cursor used to live here as a manual adapter; it's now an automatic adapter
|
|
51
|
+
// in ./cursor.ts (writes Cursor's state.vscdb, with a manual fallback).
|
|
67
52
|
export const windsurf = makeManualAdapter({
|
|
68
53
|
id: 'windsurf',
|
|
69
54
|
title: 'Windsurf',
|
package/dist/tools/manual.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manual.js","sourceRoot":"","sources":["../../src/tools/manual.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"manual.js","sourceRoot":"","sources":["../../src/tools/manual.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AASlD,4EAA4E;AAC5E,MAAM,cAAc,GAAG,CAAC,MAAc,EAAY,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;AAS7E,MAAM,iBAAiB,GAAG,CAAC,IAAgB,EAAe,EAAE,CAAC,CAAC;IAC5D,EAAE,EAAE,IAAI,CAAC,EAAE;IACX,KAAK,EAAE,IAAI,CAAC,KAAK;IACjB,SAAS,EAAE,KAAK;IAEhB,KAAK,CAAC,MAAM;QACV,KAAK,MAAM,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9C,IAAI,MAAM,UAAU,CAAC,GAAG,CAAC;gBAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;QACrE,CAAC;QACD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,YAAY,EAAE,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAqB;QAC/B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAmB;QAC9B,OAAO;YACL,MAAM,EAAE,QAAQ;YAChB,QAAQ,EAAE;gBACR,GAAG,IAAI,CAAC,KAAK,qEAAqE;aACnF;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,CAAC,aAAqB,EAAE,EAAE,CAAC,CAClD,MAAqB,EACX,EAAE;IACZ,MAAM,IAAI,GAAG,mBAAmB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClD,wEAAwE;IACxE,0EAA0E;IAC1E,OAAO;QACL,QAAQ,aAAa,uCAAuC;QAC5D,iBAAiB,MAAM,CAAC,aAAa,EAAE;QACvC,MAAM,CAAC,SAAS;YACd,CAAC,CAAC,iBAAiB,MAAM,CAAC,SAAS,iDAAiD;YACpF,CAAC,CAAC,iBAAiB,MAAM,CAAC,cAAc,kDAAkD;QAC5F,IAAI;YACF,CAAC,CAAC,4DAA4D,IAAI,EAAE;YACpE,CAAC,CAAC,wEAAwE;KAC7E,CAAC;AACJ,CAAC,CAAC;AAEF,8EAA8E;AAC9E,wEAAwE;AACxE,MAAM,CAAC,MAAM,QAAQ,GAAG,iBAAiB,CAAC;IACxC,EAAE,EAAE,UAAU;IACd,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,gBAAgB,CAAC,+CAA+C,CAAC;CACzE,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-OS application support/config directory for a named app folder — the dir
|
|
3
|
+
* an editor or CLI keeps its config under. One place for the platform switch so
|
|
4
|
+
* every adapter that resolves an app dir (Cursor, the manual editors, VS
|
|
5
|
+
* Code/Copilot) agrees on it instead of re-implementing the same three cases.
|
|
6
|
+
*
|
|
7
|
+
* macOS → ~/Library/Application Support/<app>
|
|
8
|
+
* Windows → %APPDATA%\<app> (falls back to ~/AppData/Roaming)
|
|
9
|
+
* Linux/other → ~/.config/<app>
|
|
10
|
+
*/
|
|
11
|
+
import { homedir, platform } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
export const appSupportDir = (app) => {
|
|
14
|
+
const home = homedir();
|
|
15
|
+
switch (platform()) {
|
|
16
|
+
case 'darwin':
|
|
17
|
+
return join(home, 'Library', 'Application Support', app);
|
|
18
|
+
case 'win32':
|
|
19
|
+
return join(process.env.APPDATA ?? join(home, 'AppData', 'Roaming'), app);
|
|
20
|
+
default:
|
|
21
|
+
return join(home, '.config', app);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=appSupport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appSupport.js","sourceRoot":"","sources":["../../src/util/appSupport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAW,EAAU,EAAE;IACnD,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,QAAQ,QAAQ,EAAE,EAAE,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,qBAAqB,EAAE,GAAG,CAAC,CAAC;QAC3D,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC;QAC5E;YACE,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IACtC,CAAC;AACH,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anyray-connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"windsurf"
|
|
24
24
|
],
|
|
25
25
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
26
|
+
"node": ">=24"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist/",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"prepublishOnly": "npm run build && npm test"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "
|
|
40
|
+
"@types/node": "24.13.2",
|
|
41
41
|
"tsx": "4.19.2",
|
|
42
|
-
"typescript": "5.
|
|
42
|
+
"typescript": "5.9.3"
|
|
43
43
|
}
|
|
44
44
|
}
|