castle-web-cli 0.4.107 → 0.4.109
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-prompts.js +59 -10
- package/dist/agent.js +100 -24
- package/dist/byo-accounts.d.ts +17 -0
- package/dist/byo-accounts.js +56 -2
- package/dist/byo-auth.d.ts +8 -2
- package/dist/byo-auth.js +188 -61
- package/dist/cli-shim-env.js +13 -0
- package/dist/ide.js +2 -2
- package/dist/shell/assets/index-CYG9z07_.js +144 -0
- package/dist/shell/index.html +1 -1
- package/kits/physics-2d/castle.json +1 -1
- package/kits/physics-2d/engine/ui.jsx +96 -15
- package/kits/physics-2d/engine/ui.module.css +18 -2
- package/package.json +1 -1
- package/dist/claude-shim-env.js +0 -6
- package/dist/shell/assets/index-B5mHPsrX.js +0 -144
- /package/dist/{claude-shim-env.d.ts → cli-shim-env.d.ts} +0 -0
package/dist/byo-auth.js
CHANGED
|
@@ -14,6 +14,7 @@ import * as fs from "fs";
|
|
|
14
14
|
import * as os from "os";
|
|
15
15
|
import * as path from "path";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
|
+
import { inCastleSandbox } from "./metering.js";
|
|
17
18
|
const DIST_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
// A user's OWN provider credentials, kept SEPARATE from Castle's keys.json so
|
|
19
20
|
// castle-www's per-serve re-sync of keys.json (cloudSandbox.ts syncCastleKeys)
|
|
@@ -55,23 +56,43 @@ export function userKey(envName) {
|
|
|
55
56
|
const trimmed = v.trim();
|
|
56
57
|
return trimmed ? trimmed : null;
|
|
57
58
|
}
|
|
59
|
+
// CLAUDE_CONFIG_DIR moves BOTH of claude's own files, and the sandbox sets it
|
|
60
|
+
// (castle-sandboxes points it at the directory it persists, so `.claude.json` --
|
|
61
|
+
// which holds the account record and the onboarding state -- lands inside the
|
|
62
|
+
// symlink alongside the credential rather than beside it). Reading os.homedir()
|
|
63
|
+
// unconditionally would mean reading a path claude has been told not to use.
|
|
64
|
+
function claudeConfigDir() {
|
|
65
|
+
const dir = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
66
|
+
return dir ? dir : null;
|
|
67
|
+
}
|
|
68
|
+
// CASTLE_CLAUDE_CREDENTIALS_PATH overrides the location so the QA battery can
|
|
69
|
+
// isolate from a developer's REAL ~/.claude login -- which, since this gates
|
|
70
|
+
// proxy-vs-direct routing, would otherwise flip every plain-claude scenario to
|
|
71
|
+
// "direct" on a logged-in machine. Mirrors the CASTLE_KEYS_PATH seam.
|
|
72
|
+
export function claudeCredentialsPath() {
|
|
73
|
+
const override = process.env.CASTLE_CLAUDE_CREDENTIALS_PATH;
|
|
74
|
+
if (override !== undefined)
|
|
75
|
+
return override;
|
|
76
|
+
const dir = claudeConfigDir();
|
|
77
|
+
return dir
|
|
78
|
+
? path.join(dir, ".credentials.json")
|
|
79
|
+
: path.join(os.homedir(), ".claude", ".credentials.json");
|
|
80
|
+
}
|
|
58
81
|
// KNOWN GAP (macOS): a false negative for most logged-in users. `claude /login`
|
|
59
82
|
// stores credentials in the KEYCHAIN there, not in ~/.claude/.credentials.json,
|
|
60
83
|
// so this returns false and the run stays on Castle's proxy even though the
|
|
61
84
|
// user has a perfectly good subscription login the CLI would have used. Left
|
|
62
|
-
// alone deliberately -- reading the Keychain
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// authoritative, so the gap doesn't bite.
|
|
85
|
+
// alone deliberately -- reading the Keychain changes who pays for a run, which
|
|
86
|
+
// is a product decision, not a cleanup. In a Linux sandbox (the case that
|
|
87
|
+
// matters for BYO routing) the file IS authoritative, so the gap doesn't bite.
|
|
66
88
|
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
89
|
+
// `claude auth status --json` would close it: it reads the Keychain and reports
|
|
90
|
+
// `authMethod` ("claude.ai" for a real login, "oauth_token" for an env
|
|
91
|
+
// ANTHROPIC_AUTH_TOKEN -- so it means nothing unless Castle's pair is stripped
|
|
92
|
+
// first). It is a subprocess, and this is called per `claude` run and per
|
|
93
|
+
// account snapshot, so taking it costs a spawn on both.
|
|
71
94
|
export function claudeHasSavedLogin() {
|
|
72
|
-
|
|
73
|
-
path.join(os.homedir(), ".claude", ".credentials.json");
|
|
74
|
-
return fs.existsSync(credPath);
|
|
95
|
+
return fs.existsSync(claudeCredentialsPath());
|
|
75
96
|
}
|
|
76
97
|
// cursor-agent rewrites ~/.config/cursor/auth.json on every successful run,
|
|
77
98
|
// including Castle's own CURSOR_API_KEY runs -- so the file existing does NOT
|
|
@@ -149,12 +170,12 @@ function shellQuote(value) {
|
|
|
149
170
|
export function anthropicKeyHelperCommand() {
|
|
150
171
|
return `${shellQuote(process.execPath)} ${shellQuote(path.join(DIST_DIR, "anthropic-key-helper.js"))}`;
|
|
151
172
|
}
|
|
152
|
-
// Where the shell's inherited
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
173
|
+
// Where the shell's inherited Castle credential goes when one of the user's own
|
|
174
|
+
// displaces it. envForUserShell answers "whose credential" once, but the shims
|
|
175
|
+
// re-answer it per run and may need to put Castle's back (the user deleted their
|
|
176
|
+
// key, or signed out, mid-session) -- which they can only do if the values
|
|
177
|
+
// survived somewhere. Nothing reads these names by accident, so stashing doesn't
|
|
178
|
+
// weaken the guarantee below.
|
|
158
179
|
const PROXY_STASH_PREFIX = "CASTLE_PTY_";
|
|
159
180
|
// Env for the editor's PTY terminal. The container env the serve inherits
|
|
160
181
|
// carries the llm-proxy pair, and Claude Code ranks ANTHROPIC_AUTH_TOKEN ABOVE
|
|
@@ -163,8 +184,8 @@ const PROXY_STASH_PREFIX = "CASTLE_PTY_";
|
|
|
163
184
|
// ANTHROPIC_API_KEY or another auth source is set", their login unused and
|
|
164
185
|
// their session billed to Castle.
|
|
165
186
|
//
|
|
166
|
-
// This is the shell's STARTING state only; the per-run answer is the
|
|
167
|
-
// (see
|
|
187
|
+
// This is the shell's STARTING state only; the per-run answer is the shims'
|
|
188
|
+
// (see installCliShims). It still matters on its own, because anything else
|
|
168
189
|
// the user runs in that terminal -- a script that calls claude by absolute
|
|
169
190
|
// path, an SDK program reading ANTHROPIC_API_KEY -- must not find Castle's
|
|
170
191
|
// token sitting in the environment while the user has a credential of their own.
|
|
@@ -175,17 +196,26 @@ const PROXY_STASH_PREFIX = "CASTLE_PTY_";
|
|
|
175
196
|
// person is there to answer it.
|
|
176
197
|
export function envForUserShell(base) {
|
|
177
198
|
const env = { ...base };
|
|
199
|
+
const stash = (name) => {
|
|
200
|
+
const inherited = env[name];
|
|
201
|
+
if (inherited !== undefined)
|
|
202
|
+
env[PROXY_STASH_PREFIX + name] = inherited;
|
|
203
|
+
delete env[name];
|
|
204
|
+
};
|
|
178
205
|
const anthropic = resolveAnthropicAuth();
|
|
179
206
|
if (anthropic.mode !== "proxy") {
|
|
180
|
-
for (const name of ANTHROPIC_PROXY_ENV)
|
|
181
|
-
|
|
182
|
-
if (inherited !== undefined)
|
|
183
|
-
env[PROXY_STASH_PREFIX + name] = inherited;
|
|
184
|
-
delete env[name];
|
|
185
|
-
}
|
|
207
|
+
for (const name of ANTHROPIC_PROXY_ENV)
|
|
208
|
+
stash(name);
|
|
186
209
|
if (anthropic.mode === "user-key")
|
|
187
210
|
env.ANTHROPIC_API_KEY = anthropic.key;
|
|
188
211
|
}
|
|
212
|
+
// The same withholding envForAgentSpawn does for a cursor run: Castle's key
|
|
213
|
+
// would let cursor-agent consider itself authenticated and never look at the
|
|
214
|
+
// user's own OAuth session, so their terminal would keep spending Castle's
|
|
215
|
+
// budget after they signed in. Cursor has no key of its own to put back --
|
|
216
|
+
// user-keys.json's CURSOR_API_KEY is described but not yet injected anywhere.
|
|
217
|
+
if (cursorHasUserLogin(os.homedir()))
|
|
218
|
+
stash("CURSOR_API_KEY");
|
|
189
219
|
const openrouter = userKey("OPENROUTER_API_KEY");
|
|
190
220
|
if (openrouter) {
|
|
191
221
|
env.OPENROUTER_API_KEY = openrouter;
|
|
@@ -205,10 +235,10 @@ export function envForUserShell(base) {
|
|
|
205
235
|
// still prints "claude.ai connectors are disabled...". Restarting claude, or
|
|
206
236
|
// the panel, changed nothing -- only restarting the serve did.
|
|
207
237
|
//
|
|
208
|
-
// So the decision moves to a
|
|
209
|
-
//
|
|
210
|
-
// execs the real binary. A login taken thirty seconds ago
|
|
211
|
-
// next run, in the same shell.
|
|
238
|
+
// So the decision moves to a shim first on the terminal's PATH -- one per CLI
|
|
239
|
+
// that has a credential to decide: it resolves at invocation time, fixes up the
|
|
240
|
+
// environment, and execs the real binary. A login taken thirty seconds ago
|
|
241
|
+
// applies to the very next run, in the same shell.
|
|
212
242
|
//
|
|
213
243
|
// Caveat: PATH ordering is not ours to guarantee. A login shell can reorder it
|
|
214
244
|
// (macOS /etc/zprofile runs path_helper, which demotes an inherited entry below
|
|
@@ -224,15 +254,18 @@ function realpath(p) {
|
|
|
224
254
|
return path.resolve(p);
|
|
225
255
|
}
|
|
226
256
|
}
|
|
227
|
-
// The
|
|
257
|
+
// The CLIs a shim is installed for. Both take a credential from the environment
|
|
258
|
+
// that Castle may be supplying, so both need the decision re-made per run.
|
|
259
|
+
export const SHIMMED_CLIS = ["claude", "cursor-agent"];
|
|
260
|
+
// The real binary the shim should exec: the first executable one on `pathValue`
|
|
228
261
|
// that isn't the shim itself. Skipping by resolved directory (not by string) is
|
|
229
262
|
// what keeps the shim from exec'ing itself forever.
|
|
230
|
-
function
|
|
263
|
+
function findRealBin(pathValue, shimDir, name) {
|
|
231
264
|
const shimReal = realpath(shimDir);
|
|
232
265
|
for (const entry of (pathValue ?? "").split(path.delimiter)) {
|
|
233
266
|
if (!entry || realpath(entry) === shimReal)
|
|
234
267
|
continue;
|
|
235
|
-
const candidate = path.join(entry,
|
|
268
|
+
const candidate = path.join(entry, name);
|
|
236
269
|
try {
|
|
237
270
|
fs.accessSync(candidate, fs.constants.X_OK);
|
|
238
271
|
if (fs.statSync(candidate).isFile())
|
|
@@ -244,14 +277,30 @@ function findRealClaude(pathValue, shimDir) {
|
|
|
244
277
|
}
|
|
245
278
|
return null;
|
|
246
279
|
}
|
|
280
|
+
// `claude /login` -- the slash command as an ARGUMENT, which Claude Code runs at
|
|
281
|
+
// startup. This run is about signing in, so it is given no credential of
|
|
282
|
+
// Castle's at all: measured, an inherited ANTHROPIC_AUTH_TOKEN outranks whatever
|
|
283
|
+
// the login stores, so the session that just signed in would still spend
|
|
284
|
+
// Castle's budget and still print "claude.ai connectors are disabled" -- the
|
|
285
|
+
// thing the user ran /login to stop. Stripped, the run opens "Not logged in",
|
|
286
|
+
// the login lands with nothing above it, and that same session is on the user's
|
|
287
|
+
// account.
|
|
288
|
+
//
|
|
289
|
+
// Only reachable for `claude X` typed as a command. A `/login` typed INSIDE a
|
|
290
|
+
// running claude cannot be helped by anything out here: its environment was
|
|
291
|
+
// fixed at exec and no one can change it from outside, so that session stays on
|
|
292
|
+
// whatever it started with and the NEXT run picks the login up.
|
|
293
|
+
function isLoginRun(args) {
|
|
294
|
+
return args.some((a) => a.trim() === "/login");
|
|
295
|
+
}
|
|
247
296
|
// Shell statements the shim evals before exec'ing claude: the environment that
|
|
248
297
|
// makes THIS run use the credential resolveAnthropicAuth picks right now.
|
|
249
298
|
// Emitting `unset` rather than an empty value matters -- Claude Code treats an
|
|
250
299
|
// empty ANTHROPIC_BASE_URL as configured and fails to reach anything.
|
|
251
|
-
export function claudeShellEnvScript(env,
|
|
300
|
+
export function claudeShellEnvScript(env, args = []) {
|
|
252
301
|
const lines = [];
|
|
253
302
|
const auth = resolveAnthropicAuth();
|
|
254
|
-
if (auth.mode === "proxy") {
|
|
303
|
+
if (auth.mode === "proxy" && !isLoginRun(args)) {
|
|
255
304
|
for (const name of ANTHROPIC_PROXY_ENV) {
|
|
256
305
|
const stashed = env[PROXY_STASH_PREFIX + name];
|
|
257
306
|
if (stashed !== undefined && env[name] === undefined) {
|
|
@@ -262,55 +311,133 @@ export function claudeShellEnvScript(env, shimDir) {
|
|
|
262
311
|
else {
|
|
263
312
|
for (const name of ANTHROPIC_PROXY_ENV)
|
|
264
313
|
lines.push(`unset ${name}`);
|
|
265
|
-
|
|
314
|
+
// Not on a login run: a key sitting in the environment is one more thing
|
|
315
|
+
// ranked above the login being made, and this run exists to make it.
|
|
316
|
+
if (auth.mode === "user-key" && !isLoginRun(args)) {
|
|
266
317
|
lines.push(`ANTHROPIC_API_KEY=${shellQuote(auth.key)}; export ANTHROPIC_API_KEY`);
|
|
267
318
|
}
|
|
268
319
|
}
|
|
269
|
-
|
|
270
|
-
if (real)
|
|
271
|
-
lines.push(`CASTLE_REAL_CLAUDE=${shellQuote(real)}`);
|
|
272
|
-
return lines.length > 0 ? lines.join("\n") + "\n" : "";
|
|
320
|
+
return lines.join("\n");
|
|
273
321
|
}
|
|
274
|
-
// The
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
322
|
+
// The cursor half. Simpler, because there is one credential and no proxy pair:
|
|
323
|
+
// Castle's CURSOR_API_KEY is either withheld (the user has their own OAuth
|
|
324
|
+
// session, which cursor-agent would otherwise never look at) or put back from
|
|
325
|
+
// the stash when they sign out again.
|
|
326
|
+
export function cursorShellEnvScript(env) {
|
|
327
|
+
if (cursorHasUserLogin(os.homedir()))
|
|
328
|
+
return "unset CURSOR_API_KEY";
|
|
329
|
+
const stashed = env[PROXY_STASH_PREFIX + "CURSOR_API_KEY"];
|
|
330
|
+
if (stashed !== undefined && env.CURSOR_API_KEY === undefined) {
|
|
331
|
+
return `CURSOR_API_KEY=${shellQuote(stashed)}; export CURSOR_API_KEY`;
|
|
332
|
+
}
|
|
333
|
+
return "";
|
|
334
|
+
}
|
|
335
|
+
export function shimEnvScript(cli, env, shimDir, args = []) {
|
|
336
|
+
const lines = cli === "cursor-agent"
|
|
337
|
+
? cursorShellEnvScript(env)
|
|
338
|
+
: claudeShellEnvScript(env, args);
|
|
339
|
+
const real = findRealBin(env.PATH, shimDir, cli);
|
|
340
|
+
const all = [lines, real ? `CASTLE_REAL_BIN=${shellQuote(real)}` : ""].filter((l) => l !== "");
|
|
341
|
+
return all.length > 0 ? all.join("\n") + "\n" : "";
|
|
342
|
+
}
|
|
343
|
+
// The baked CASTLE_REAL_BIN is a fallback, not the answer: if the helper runs,
|
|
344
|
+
// its own resolution (against the SHELL's PATH, which a login shell may have
|
|
345
|
+
// rewritten) replaces it. It only survives when the helper failed to run at all,
|
|
346
|
+
// and then it is the difference between the old static behavior and a terminal
|
|
347
|
+
// where the CLI is suddenly missing.
|
|
348
|
+
//
|
|
349
|
+
// "$@" reaches the helper so it can see arguments the decision turns on --
|
|
350
|
+
// `claude /login` (see isLoginRun). The shell expands it into separate words
|
|
351
|
+
// inside the substitution, so nothing in it is re-parsed.
|
|
352
|
+
function shimScript(shimDir, cli) {
|
|
280
353
|
const helper = [
|
|
281
354
|
shellQuote(process.execPath),
|
|
282
|
-
shellQuote(path.join(DIST_DIR, "
|
|
355
|
+
shellQuote(path.join(DIST_DIR, "cli-shim-env.js")),
|
|
283
356
|
shellQuote(shimDir),
|
|
357
|
+
shellQuote(cli),
|
|
284
358
|
].join(" ");
|
|
285
359
|
return [
|
|
286
360
|
"#!/bin/sh",
|
|
287
|
-
"# Generated by `castle-web serve` -- see
|
|
288
|
-
`
|
|
289
|
-
`eval "$(${helper})"`,
|
|
290
|
-
'if [ -z "$
|
|
291
|
-
|
|
361
|
+
"# Generated by `castle-web serve` -- see installCliShims in castle-web-cli.",
|
|
362
|
+
`CASTLE_REAL_BIN=${shellQuote(findRealBin(process.env.PATH, shimDir, cli) ?? "")}`,
|
|
363
|
+
`eval "$(${helper} "$@")"`,
|
|
364
|
+
'if [ -z "$CASTLE_REAL_BIN" ]; then',
|
|
365
|
+
` echo "castle-web: ${cli} is not installed on this PATH" >&2`,
|
|
292
366
|
" exit 127",
|
|
293
367
|
"fi",
|
|
294
|
-
'exec "$
|
|
368
|
+
'exec "$CASTLE_REAL_BIN" "$@"',
|
|
295
369
|
"",
|
|
296
370
|
].join("\n");
|
|
297
371
|
}
|
|
372
|
+
// Claude Code's first-run onboarding asks for a login even when one is already
|
|
373
|
+
// stored. Measured against 2.1.220 through a real pty: with onboarding
|
|
374
|
+
// incomplete, a `.credentials.json` -- with or without a matching `oauthAccount`
|
|
375
|
+
// in `.claude.json` -- still lands on "Select login method", while the same
|
|
376
|
+
// credential with onboarding complete goes straight through. What normally
|
|
377
|
+
// carries a sandbox past that step is Castle's injected ANTHROPIC_AUTH_TOKEN, an
|
|
378
|
+
// env credential, which is exactly what this shim takes away the moment the user
|
|
379
|
+
// has one of their own. So a user who signs in from the editor BEFORE ever
|
|
380
|
+
// opening the terminal is asked by their first `claude` to sign in again, and
|
|
381
|
+
// pays a second OAuth round trip for a login they already completed.
|
|
382
|
+
//
|
|
383
|
+
// Marking onboarding done is what makes claude accept the stored login --
|
|
384
|
+
// `hasCompletedOnboarding` alone is enough, measured, so no version flag is
|
|
385
|
+
// written and there is nothing to drift. Kept narrow deliberately: only in a
|
|
386
|
+
// sandbox, only once the user actually has a credential of their own, only when
|
|
387
|
+
// onboarding has not already run. A developer's own machine is never touched,
|
|
388
|
+
// and neither is claude's first-run experience for anyone on Castle's account.
|
|
389
|
+
export function ensureClaudeOnboarded() {
|
|
390
|
+
if (!inCastleSandbox())
|
|
391
|
+
return;
|
|
392
|
+
if (resolveAnthropicAuth().mode === "proxy")
|
|
393
|
+
return;
|
|
394
|
+
const dir = claudeConfigDir();
|
|
395
|
+
const file = dir
|
|
396
|
+
? path.join(dir, ".claude.json")
|
|
397
|
+
: path.join(os.homedir(), ".claude.json");
|
|
398
|
+
let config = {};
|
|
399
|
+
try {
|
|
400
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
401
|
+
// Not ours to repair: a file we can't read is one claude may still be able
|
|
402
|
+
// to, and replacing it wholesale would cost the user their project history.
|
|
403
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
404
|
+
return;
|
|
405
|
+
config = parsed;
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
/* no file yet -- the normal case in a sandbox nobody has run claude in */
|
|
409
|
+
}
|
|
410
|
+
if (config.hasCompletedOnboarding === true)
|
|
411
|
+
return;
|
|
412
|
+
config.hasCompletedOnboarding = true;
|
|
413
|
+
try {
|
|
414
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
415
|
+
const staged = `${file}.castle-staged`;
|
|
416
|
+
fs.writeFileSync(staged, JSON.stringify(config, null, 2) + "\n");
|
|
417
|
+
fs.renameSync(staged, file);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
/* claude asks again; nothing else depends on this */
|
|
421
|
+
}
|
|
422
|
+
}
|
|
298
423
|
// Returns the directory to put first on the terminal's PATH, or null when there
|
|
299
|
-
// is
|
|
424
|
+
// is none to install -- a failure here costs the per-run re-resolution, so it
|
|
300
425
|
// degrades to envForUserShell's spawn-time answer rather than breaking the shell.
|
|
301
|
-
export function
|
|
426
|
+
export function installCliShims(deckDir) {
|
|
302
427
|
if (process.platform === "win32")
|
|
303
428
|
return null;
|
|
304
429
|
const dir = path.join(deckDir, ".castle", "shims");
|
|
305
430
|
try {
|
|
306
431
|
fs.mkdirSync(dir, { recursive: true });
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
432
|
+
for (const cli of SHIMMED_CLIS) {
|
|
433
|
+
// Write-then-rename, because /bin/sh reads a script incrementally: a
|
|
434
|
+
// second serve reinstalling over this deck while a run is going through
|
|
435
|
+
// the shim would otherwise pull the file out from under that sh mid-read.
|
|
436
|
+
const staged = path.join(dir, `${cli}.staged`);
|
|
437
|
+
fs.writeFileSync(staged, shimScript(dir, cli));
|
|
438
|
+
fs.chmodSync(staged, 0o755);
|
|
439
|
+
fs.renameSync(staged, path.join(dir, cli));
|
|
440
|
+
}
|
|
314
441
|
return dir;
|
|
315
442
|
}
|
|
316
443
|
catch {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Printed for the editor terminal's shims to eval: the environment this
|
|
2
|
+
// particular run of this particular CLI should have, plus the real binary to
|
|
3
|
+
// exec (see installCliShims in byo-auth.ts for why the decision can't live in
|
|
4
|
+
// the shell's own environment).
|
|
5
|
+
//
|
|
6
|
+
// argv is `<shimDir> <cli> [the run's own arguments...]`.
|
|
7
|
+
import { ensureClaudeOnboarded, shimEnvScript } from "./byo-auth.js";
|
|
8
|
+
const [shimDir = "", cli = "", ...args] = process.argv.slice(2);
|
|
9
|
+
// Its own call rather than a side effect of building the script: this WRITES,
|
|
10
|
+
// and shimEnvScript is read-only by contract.
|
|
11
|
+
if (cli === "claude")
|
|
12
|
+
ensureClaudeOnboarded();
|
|
13
|
+
process.stdout.write(shimEnvScript(cli, process.env, shimDir, args));
|
package/dist/ide.js
CHANGED
|
@@ -15,7 +15,7 @@ import headlessPkg from "@xterm/headless";
|
|
|
15
15
|
import { SerializeAddon } from "@xterm/addon-serialize";
|
|
16
16
|
import { WebSocketServer } from "ws";
|
|
17
17
|
import { IMPORTS_DIR, importStatuses, updateImport } from "./imports.js";
|
|
18
|
-
import { envForUserShell,
|
|
18
|
+
import { envForUserShell, installCliShims } from "./byo-auth.js";
|
|
19
19
|
const HeadlessTerminal = headlessPkg.Terminal;
|
|
20
20
|
const DIST_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
21
21
|
// The bundled shell app (vite build output). `/` serves its index.html and
|
|
@@ -651,7 +651,7 @@ export function createIdeServer(opts) {
|
|
|
651
651
|
let session = null;
|
|
652
652
|
function spawnSession() {
|
|
653
653
|
const { command, args } = defaultShell();
|
|
654
|
-
const shimDir =
|
|
654
|
+
const shimDir = installCliShims(deckDir);
|
|
655
655
|
const screen = new HeadlessTerminal({
|
|
656
656
|
allowProposedApi: true,
|
|
657
657
|
cols: INITIAL_COLS,
|