castle-web-cli 0.4.58 → 0.4.59
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/agent.js +52 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// --force. Claude support can slot in later behind runAgentCli.
|
|
17
17
|
import { execFileSync, spawn } from 'child_process';
|
|
18
18
|
import * as fs from 'fs';
|
|
19
|
+
import * as os from 'os';
|
|
19
20
|
import * as path from 'path';
|
|
20
21
|
import { nanoid } from 'nanoid';
|
|
21
22
|
import { WebSocketServer } from 'ws';
|
|
@@ -187,6 +188,56 @@ function toolActivityLabel(ev) {
|
|
|
187
188
|
return 'running a command';
|
|
188
189
|
return 'working';
|
|
189
190
|
}
|
|
191
|
+
// Castle's agent CLI keys, delivered to the sandbox as a file
|
|
192
|
+
// (~/.castle/keys.json) rather than sandbox-wide env -- so an ambient key can't
|
|
193
|
+
// override a user's own subscription login. Falls back to process.env for
|
|
194
|
+
// older sandboxes that still inject the keys as env.
|
|
195
|
+
const CASTLE_KEYS_PATH = path.join(os.homedir(), '.castle', 'keys.json');
|
|
196
|
+
function castleKeys() {
|
|
197
|
+
try {
|
|
198
|
+
return JSON.parse(fs.readFileSync(CASTLE_KEYS_PATH, 'utf8'));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return {};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const BACKEND_KEY_ENV = {
|
|
205
|
+
claude: 'ANTHROPIC_API_KEY',
|
|
206
|
+
cursor: 'CURSOR_API_KEY',
|
|
207
|
+
};
|
|
208
|
+
// True when the user has their OWN saved auth for this backend -- a /login, or
|
|
209
|
+
// (for cursor, which reuses one auth.json) any saved creds. When so we do NOT
|
|
210
|
+
// inject Castle's key, so their auth is used and billed to them.
|
|
211
|
+
function backendHasSavedAuth(backend) {
|
|
212
|
+
const home = os.homedir();
|
|
213
|
+
if (backend === 'claude') {
|
|
214
|
+
return fs.existsSync(path.join(home, '.claude', '.credentials.json'));
|
|
215
|
+
}
|
|
216
|
+
if (backend === 'cursor') {
|
|
217
|
+
return fs.existsSync(path.join(home, '.config', 'cursor', 'auth.json'));
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
// Env for an agent spawn: inject Castle's key ONLY when the backend has no saved
|
|
222
|
+
// auth of the user's own. This is what lets internal testers run on their own
|
|
223
|
+
// subscription (log in once in the terminal) instead of Castle's key.
|
|
224
|
+
function envForAgentSpawn(backend) {
|
|
225
|
+
const env = { ...process.env };
|
|
226
|
+
const keyName = BACKEND_KEY_ENV[backend];
|
|
227
|
+
if (!keyName)
|
|
228
|
+
return env;
|
|
229
|
+
if (backendHasSavedAuth(backend)) {
|
|
230
|
+
delete env[keyName];
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
const val = castleKeys()[keyName] ?? process.env[keyName];
|
|
234
|
+
if (val)
|
|
235
|
+
env[keyName] = val;
|
|
236
|
+
else
|
|
237
|
+
delete env[keyName];
|
|
238
|
+
}
|
|
239
|
+
return env;
|
|
240
|
+
}
|
|
190
241
|
// One headless agent CLI run (cursor or claude), normalized to the same
|
|
191
242
|
// delta/activity/result hooks. Cursor: assistant events carrying timestamp_ms
|
|
192
243
|
// are text deltas; the trailing assistant event without one repeats the whole
|
|
@@ -196,7 +247,7 @@ function runAgentCli(opts) {
|
|
|
196
247
|
return new Promise((resolve) => {
|
|
197
248
|
const child = spawn(opts.command, opts.args, {
|
|
198
249
|
cwd: opts.cwd,
|
|
199
|
-
env:
|
|
250
|
+
env: envForAgentSpawn(opts.parser),
|
|
200
251
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
201
252
|
});
|
|
202
253
|
opts.children.add(child);
|