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/agent-prompts.js
CHANGED
|
@@ -6,6 +6,20 @@
|
|
|
6
6
|
// Router calls are stateless (a fresh cursor-agent print run each time) so the
|
|
7
7
|
// transcript is the only memory.
|
|
8
8
|
const TRANSCRIPT_LIMIT = 40;
|
|
9
|
+
// Byte ceiling on the replayed transcript, applied AFTER TRANSCRIPT_LIMIT.
|
|
10
|
+
// The count cap above bounds how many messages replay, not how big they are --
|
|
11
|
+
// forty long ones clear 128KB easily. That matters because the whole prompt is
|
|
12
|
+
// handed to the backend CLI as a single argv entry, and Linux caps one argument
|
|
13
|
+
// at MAX_ARG_STRLEN (128KB): past it, spawn() throws E2BIG and the turn dies
|
|
14
|
+
// before it starts. Since the transcript is the only term here that grows
|
|
15
|
+
// without bound (it accumulates for the life of the deck), it's the one that
|
|
16
|
+
// needs a byte budget.
|
|
17
|
+
//
|
|
18
|
+
// Sized to leave room for everything else the prompt carries: the rules
|
|
19
|
+
// (~10KB), the deck's quick reference (~13KB), the file tree, the smith-only
|
|
20
|
+
// deck contents (ROUTER_DECK_CONTENTS_BUDGET, 40KB), the task board, and this
|
|
21
|
+
// turn's instruction.
|
|
22
|
+
const TRANSCRIPT_BYTE_BUDGET = 32 * 1024;
|
|
9
23
|
const ROUTER_RULES = `You are Castle's create assistant: the fast conversational router for a game-making session. The deck (game project) lives in the current directory and runs live in a pane right next to this chat.
|
|
10
24
|
|
|
11
25
|
What a deck is: a normal web project served by vite -- index.html plus plain JS/JSX modules, with real npm dependencies (more can be installed), the castle-web-sdk package, and usually a kit framework whose engine, behaviors, scenes, editors, and drawings are ordinary files in this directory. The web platform is fully available (DOM, canvas, npm libraries like react, three, etc.). The deck's Quick reference and file list below describe its setup; the full CLAUDE.md / AGENTS.md has deeper detail. NEVER claim something is impossible or unsupported on the platform without checking that context (or, for specifics it doesn't cover, the deck's files) first.
|
|
@@ -66,20 +80,55 @@ Conversation style:
|
|
|
66
80
|
- The user sees a live task board above the chat -- never re-announce task status yourself.
|
|
67
81
|
- Spawning a task does NOT apply the change -- tasks run for minutes and finish on the board. Talk about spawned work in future tense ("this will dial the shake back"), and NEVER ask how a change feels right after spawning it -- the user cannot have tried it yet. Save "how is it?" for things whose task already finished.
|
|
68
82
|
- The user playtests in the pane beside this chat; finished work shows up there after a reload.`;
|
|
83
|
+
function renderTranscriptLine(m) {
|
|
84
|
+
if (m.role === "user")
|
|
85
|
+
return `user: ${m.text}`;
|
|
86
|
+
const label = m.interrupted
|
|
87
|
+
? "you (interrupted draft -- not a complete reply)"
|
|
88
|
+
: "you";
|
|
89
|
+
return `${label}: ${m.text}`;
|
|
90
|
+
}
|
|
91
|
+
// Cut a line to fit `budget` BYTES without splitting a multi-byte character
|
|
92
|
+
// (a half-written character would render as a replacement glyph mid-sentence).
|
|
93
|
+
function truncateToBytes(line, budget) {
|
|
94
|
+
const buf = Buffer.from(line, "utf8");
|
|
95
|
+
if (buf.byteLength <= budget)
|
|
96
|
+
return line;
|
|
97
|
+
return new TextDecoder("utf8", { fatal: false, ignoreBOM: true })
|
|
98
|
+
.decode(buf.subarray(0, budget))
|
|
99
|
+
.replace(/�+$/, "");
|
|
100
|
+
}
|
|
69
101
|
function renderTranscript(messages) {
|
|
70
102
|
const recent = messages.slice(-TRANSCRIPT_LIMIT);
|
|
71
103
|
if (recent.length === 0)
|
|
72
104
|
return "(no conversation yet)";
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
105
|
+
// Newest-first, keeping WHOLE messages until the budget is spent: the recent
|
|
106
|
+
// exchanges are the ones a reply actually depends on, and half-including a
|
|
107
|
+
// message would read as the user having said something they didn't.
|
|
108
|
+
const kept = [];
|
|
109
|
+
let bytes = 0;
|
|
110
|
+
for (let i = recent.length - 1; i >= 0; i -= 1) {
|
|
111
|
+
const line = renderTranscriptLine(recent[i]);
|
|
112
|
+
// +2 for the "\n\n" this line will be joined with.
|
|
113
|
+
const size = Buffer.byteLength(line, "utf8") + 2;
|
|
114
|
+
if (bytes + size > TRANSCRIPT_BYTE_BUDGET) {
|
|
115
|
+
// One message bigger than the whole budget (a huge paste) still has to
|
|
116
|
+
// yield something -- an empty transcript would strand the router with no
|
|
117
|
+
// idea what was just asked. Truncate that one rather than drop it, so the
|
|
118
|
+
// bound genuinely holds no matter what a single message contains.
|
|
119
|
+
if (kept.length === 0) {
|
|
120
|
+
kept.push(truncateToBytes(line, TRANSCRIPT_BYTE_BUDGET));
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
kept.unshift(line);
|
|
125
|
+
bytes += size;
|
|
126
|
+
}
|
|
127
|
+
const elided = recent.length - kept.length;
|
|
128
|
+
if (elided <= 0)
|
|
129
|
+
return kept.join("\n\n");
|
|
130
|
+
const plural = elided === 1 ? "message" : "messages";
|
|
131
|
+
return `(${elided} earlier ${plural} trimmed to keep this prompt within its size limit)\n\n${kept.join("\n\n")}`;
|
|
83
132
|
}
|
|
84
133
|
function renderTasks(tasks) {
|
|
85
134
|
if (tasks.length === 0)
|
package/dist/agent.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// Backend CLI: cursor-agent in headless print mode (stream-json). The router
|
|
15
15
|
// runs with --mode ask (read-only at the CLI level); task agents run with
|
|
16
16
|
// --force. Claude support can slot in later behind runAgentCli.
|
|
17
|
-
import { execFileSync, spawn } from "child_process";
|
|
17
|
+
import { execFileSync, spawn, } from "child_process";
|
|
18
18
|
import * as fs from "fs";
|
|
19
19
|
import * as os from "os";
|
|
20
20
|
import * as path from "path";
|
|
@@ -26,7 +26,7 @@ import { checkOpenrouterKey, checkOpenrouterModel, openrouterCatalogEntry, prime
|
|
|
26
26
|
import { classifyProviderError, failureCopy, setReaderTimeZone, } from "./agent-failures.js";
|
|
27
27
|
import { fetchBudget, meteringHeaders, newAgentSessionId, reportCursorRun, withCustomHeaders, } from "./metering.js";
|
|
28
28
|
import { anthropicKeyHelperCommand, claudeHasSavedLogin, cursorAuthPath, cursorHasUserLogin, resolveAnthropicAuth, userKey, ANTHROPIC_CREDENTIAL_ENV, ANTHROPIC_PROXY_ENV, CASTLE_USER_KEYS_PATH, } from "./byo-auth.js";
|
|
29
|
-
import { accountsSnapshot, loginProviderFor, writeCredential, } from "./byo-accounts.js";
|
|
29
|
+
import { accountsSnapshot, loginProviderFor, watchCredentials, writeCredential, } from "./byo-accounts.js";
|
|
30
30
|
import { cancelLogin, logout, startLogin, submitLoginCode, } from "./byo-login.js";
|
|
31
31
|
import { runAgentNative } from "./native/loop.js";
|
|
32
32
|
import { createPlaytestBrowserManager } from "./native/playtest-browser.js";
|
|
@@ -1341,11 +1341,33 @@ function makeAgentEventHandler(opts, state) {
|
|
|
1341
1341
|
// end with a result event carrying the canonical final text.
|
|
1342
1342
|
function runAgentCli(opts) {
|
|
1343
1343
|
return new Promise((resolve) => {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1344
|
+
// spawn() throws SYNCHRONOUSLY for the failures the OS rejects at exec
|
|
1345
|
+
// time -- in practice E2BIG, when the prompt argv exceeds Linux's
|
|
1346
|
+
// MAX_ARG_STRLEN (128KB per argument). That throw escapes this executor
|
|
1347
|
+
// and rejects the promise, so it never reaches the child.on("error")
|
|
1348
|
+
// handler below and never becomes an AgentFailure: the turn dies
|
|
1349
|
+
// unclassified and the composer spins forever with nothing shown. Settle
|
|
1350
|
+
// it here in the same shape that handler uses so it lands on the normal
|
|
1351
|
+
// "spawn" copy instead. The declared type mirrors the stdio tuple below:
|
|
1352
|
+
// stdin ignored, stdout/stderr piped.
|
|
1353
|
+
let child;
|
|
1354
|
+
try {
|
|
1355
|
+
child = spawn(opts.command, opts.args, {
|
|
1356
|
+
cwd: opts.cwd,
|
|
1357
|
+
env: opts.env,
|
|
1358
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
catch (err) {
|
|
1362
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1363
|
+
resolve({
|
|
1364
|
+
ok: false,
|
|
1365
|
+
finalText: "",
|
|
1366
|
+
error: `could not run ${opts.command}: ${message}`,
|
|
1367
|
+
failure: { kind: "spawn", detail: `${opts.command}: ${message}` },
|
|
1368
|
+
});
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1349
1371
|
opts.children.add(child);
|
|
1350
1372
|
opts.onSpawn?.(child.pid);
|
|
1351
1373
|
const log = opts.logPath
|
|
@@ -3022,25 +3044,73 @@ function applyAgentSettings(incoming, ctx) {
|
|
|
3022
3044
|
ctx.broadcast({ type: "settings", settings });
|
|
3023
3045
|
void broadcastSettingsWarnings(ctx);
|
|
3024
3046
|
}
|
|
3025
|
-
//
|
|
3026
|
-
//
|
|
3027
|
-
//
|
|
3028
|
-
|
|
3029
|
-
//
|
|
3030
|
-
//
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3047
|
+
// Belt to watchCredentials' braces: a directory that didn't exist at boot has
|
|
3048
|
+
// no watcher on it, and an inotify watch can be lost. Slow, because the watch is
|
|
3049
|
+
// what makes this feel live -- this only has to stop a miss from lasting.
|
|
3050
|
+
const ACCOUNTS_POLL_MS = 15_000;
|
|
3051
|
+
// The editor's view of which credentials exist, kept live. Unlike settings --
|
|
3052
|
+
// which nothing outside this process writes -- a credential changes behind the
|
|
3053
|
+
// editor's back all the time: `claude /login` or `cursor-agent logout` in the
|
|
3054
|
+
// terminal panel, a hand-edited user-keys.json, an agent run that refreshes an
|
|
3055
|
+
// OAuth token. Push those, or the popover reports whatever was true when the
|
|
3056
|
+
// page loaded and a terminal sign-in reads as having done nothing.
|
|
3057
|
+
//
|
|
3058
|
+
// The snapshot carries presence and a hint only; no frame from here ever moves
|
|
3059
|
+
// a key value toward a browser.
|
|
3060
|
+
function createAccountsFeed(opts) {
|
|
3061
|
+
let last = "";
|
|
3062
|
+
// Tracked apart from the whole snapshot so a sign-in's phase transitions
|
|
3063
|
+
// (starting -> awaiting-code -> ...) don't each cost a budget fetch: they
|
|
3064
|
+
// move `login`, never a credential.
|
|
3065
|
+
let lastProviders = "";
|
|
3066
|
+
function push(error) {
|
|
3067
|
+
const accounts = accountsSnapshot();
|
|
3068
|
+
const serialized = JSON.stringify(accounts);
|
|
3069
|
+
const providers = JSON.stringify(accounts.providers);
|
|
3070
|
+
if (providers !== lastProviders) {
|
|
3071
|
+
lastProviders = providers;
|
|
3072
|
+
opts.onCredentialChange();
|
|
3073
|
+
}
|
|
3074
|
+
// A refusal always goes out. It is the only answer a rejected write gets,
|
|
3075
|
+
// and a rejected write leaves the snapshot identical by definition -- so
|
|
3076
|
+
// deduping on the snapshot alone would swallow exactly the frame that
|
|
3077
|
+
// carries the reason.
|
|
3078
|
+
if (serialized === last && !error)
|
|
3079
|
+
return;
|
|
3080
|
+
last = serialized;
|
|
3081
|
+
opts.broadcast({
|
|
3082
|
+
type: "accounts",
|
|
3083
|
+
accounts,
|
|
3084
|
+
...(error ? { accountsError: error } : {}),
|
|
3085
|
+
});
|
|
3086
|
+
}
|
|
3087
|
+
function snapshot() {
|
|
3088
|
+
const accounts = accountsSnapshot();
|
|
3089
|
+
last = JSON.stringify(accounts);
|
|
3090
|
+
lastProviders = JSON.stringify(accounts.providers);
|
|
3091
|
+
return accounts;
|
|
3092
|
+
}
|
|
3093
|
+
const stopWatch = watchCredentials(() => push());
|
|
3094
|
+
const timer = setInterval(() => {
|
|
3095
|
+
if (opts.hasClients())
|
|
3096
|
+
push();
|
|
3097
|
+
}, ACCOUNTS_POLL_MS);
|
|
3098
|
+
timer.unref?.();
|
|
3099
|
+
return {
|
|
3100
|
+
snapshot,
|
|
3101
|
+
push,
|
|
3102
|
+
stop: () => {
|
|
3103
|
+
stopWatch();
|
|
3104
|
+
clearInterval(timer);
|
|
3105
|
+
},
|
|
3106
|
+
};
|
|
3037
3107
|
}
|
|
3038
3108
|
function applyCredentialChange(msg, ctx) {
|
|
3039
3109
|
const clearing = msg.type === "clear-credential";
|
|
3040
3110
|
if (!clearing && typeof msg.value !== "string")
|
|
3041
3111
|
return;
|
|
3042
3112
|
const result = writeCredential(msg.id, clearing ? null : msg.value);
|
|
3043
|
-
|
|
3113
|
+
ctx.pushAccounts(result.ok
|
|
3044
3114
|
? undefined
|
|
3045
3115
|
: { id: typeof msg.id === "string" ? msg.id : "", message: result.message });
|
|
3046
3116
|
}
|
|
@@ -3518,6 +3588,11 @@ export function createAgentServer(opts) {
|
|
|
3518
3588
|
hasClients: () => clients.size > 0,
|
|
3519
3589
|
castlePaid: () => anyRoleIsCastlePaid(settings),
|
|
3520
3590
|
});
|
|
3591
|
+
const accountsFeed = createAccountsFeed({
|
|
3592
|
+
broadcast,
|
|
3593
|
+
hasClients: () => clients.size > 0,
|
|
3594
|
+
onCredentialChange: () => usageFeed.refresh(),
|
|
3595
|
+
});
|
|
3521
3596
|
const applySettings = (incoming) => {
|
|
3522
3597
|
applyAgentSettings(incoming, { settings, settingsPath, broadcast });
|
|
3523
3598
|
// A backend switch can change who pays (a cursor role always spends
|
|
@@ -3595,7 +3670,7 @@ export function createAgentServer(opts) {
|
|
|
3595
3670
|
running: routerQueue.isRunning(),
|
|
3596
3671
|
queued: routerQueue.queuedSnippets(),
|
|
3597
3672
|
usage: usageFeed.latest(),
|
|
3598
|
-
accounts:
|
|
3673
|
+
accounts: accountsFeed.snapshot(),
|
|
3599
3674
|
};
|
|
3600
3675
|
socket.send(JSON.stringify(hello));
|
|
3601
3676
|
// A newly attached client is the one moment the cached value may be stale
|
|
@@ -3638,12 +3713,12 @@ export function createAgentServer(opts) {
|
|
|
3638
3713
|
}
|
|
3639
3714
|
else if (msg.type === "set-credential" ||
|
|
3640
3715
|
msg.type === "clear-credential") {
|
|
3641
|
-
applyCredentialChange(msg, {
|
|
3716
|
+
applyCredentialChange(msg, { pushAccounts: accountsFeed.push });
|
|
3642
3717
|
}
|
|
3643
3718
|
else if (msg.type === "account-login") {
|
|
3644
3719
|
const provider = loginProviderFor(msg.id);
|
|
3645
3720
|
if (provider)
|
|
3646
|
-
startLogin(provider, () =>
|
|
3721
|
+
startLogin(provider, () => accountsFeed.push());
|
|
3647
3722
|
}
|
|
3648
3723
|
else if (msg.type === "account-login-code" && typeof msg.value === "string") {
|
|
3649
3724
|
submitLoginCode(msg.value);
|
|
@@ -3654,7 +3729,7 @@ export function createAgentServer(opts) {
|
|
|
3654
3729
|
else if (msg.type === "account-logout") {
|
|
3655
3730
|
const provider = loginProviderFor(msg.id);
|
|
3656
3731
|
if (provider)
|
|
3657
|
-
logout(provider, () =>
|
|
3732
|
+
logout(provider, () => accountsFeed.push());
|
|
3658
3733
|
}
|
|
3659
3734
|
else if (msg.type === "client-timezone" && typeof msg.timeZone === "string") {
|
|
3660
3735
|
setReaderTimeZone(msg.timeZone);
|
|
@@ -3690,6 +3765,7 @@ export function createAgentServer(opts) {
|
|
|
3690
3765
|
}
|
|
3691
3766
|
stopChildRegistry();
|
|
3692
3767
|
usageFeed.stop();
|
|
3768
|
+
accountsFeed.stop();
|
|
3693
3769
|
wss.close();
|
|
3694
3770
|
void playtestBrowserManager.shutdown();
|
|
3695
3771
|
}
|
package/dist/byo-accounts.d.ts
CHANGED
|
@@ -18,6 +18,23 @@ export interface AccountsState {
|
|
|
18
18
|
login: LoginState | null;
|
|
19
19
|
}
|
|
20
20
|
export declare function accountsSnapshot(): AccountsState;
|
|
21
|
+
/**
|
|
22
|
+
* Call `onChange` when a credential this module reports on changes on disk.
|
|
23
|
+
*
|
|
24
|
+
* The editor is not the only writer: `claude /login` and `cursor-agent login`
|
|
25
|
+
* in the terminal panel, an agent run that refreshes an OAuth token, and a
|
|
26
|
+
* hand-edited `user-keys.json` all change the answer behind its back. Without
|
|
27
|
+
* this the account state is whatever was true when the page loaded, so a
|
|
28
|
+
* terminal sign-in looks like it did nothing until a reload.
|
|
29
|
+
*
|
|
30
|
+
* Directories, never the credential files themselves: the CLIs replace those by
|
|
31
|
+
* rename, which leaves a file watch holding the old inode and silently deaf --
|
|
32
|
+
* the same reason castle-sandboxes persists these paths as directory symlinks.
|
|
33
|
+
* A directory that doesn't exist yet (no `~/.castle` until the first key) simply
|
|
34
|
+
* isn't watched; the caller's poll is what covers that, and losing a watcher
|
|
35
|
+
* costs freshness, never correctness.
|
|
36
|
+
*/
|
|
37
|
+
export declare function watchCredentials(onChange: () => void): () => void;
|
|
21
38
|
export type CredentialWriteResult = {
|
|
22
39
|
ok: true;
|
|
23
40
|
changed: boolean;
|
package/dist/byo-accounts.js
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Split from byo-auth.ts deliberately. That module is the RESOLVER -- pure,
|
|
6
6
|
// read-only, and imported by the terminal shim's helper on EVERY `claude` run
|
|
7
|
-
// (see
|
|
7
|
+
// (see installCliShims), so it must stay free of anything stateful. This one
|
|
8
8
|
// is allowed to write. The login subprocesses live in byo-login.ts; this module
|
|
9
9
|
// only reports their state as part of one snapshot.
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import * as os from "os";
|
|
12
12
|
import * as path from "path";
|
|
13
|
-
import { CASTLE_USER_KEYS_PATH, claudeHasSavedLogin, cursorHasUserLogin, readUserKeys, } from "./byo-auth.js";
|
|
13
|
+
import { CASTLE_USER_KEYS_PATH, claudeCredentialsPath, claudeHasSavedLogin, cursorAuthPath, cursorHasUserLogin, readUserKeys, } from "./byo-auth.js";
|
|
14
14
|
import { activeLogin } from "./byo-login.js";
|
|
15
15
|
import { inCastleSandbox } from "./metering.js";
|
|
16
16
|
// Providers, not credentials: Anthropic is reachable by EITHER a key or a
|
|
@@ -90,6 +90,60 @@ export function accountsSnapshot() {
|
|
|
90
90
|
login: activeLogin(),
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
+
// Several events land for one credential change (a rename is a delete plus a
|
|
94
|
+
// create, and the CLIs write neighbouring files in the same breath); one
|
|
95
|
+
// snapshot after the burst is what the editor wants.
|
|
96
|
+
const WATCH_DEBOUNCE_MS = 250;
|
|
97
|
+
/**
|
|
98
|
+
* Call `onChange` when a credential this module reports on changes on disk.
|
|
99
|
+
*
|
|
100
|
+
* The editor is not the only writer: `claude /login` and `cursor-agent login`
|
|
101
|
+
* in the terminal panel, an agent run that refreshes an OAuth token, and a
|
|
102
|
+
* hand-edited `user-keys.json` all change the answer behind its back. Without
|
|
103
|
+
* this the account state is whatever was true when the page loaded, so a
|
|
104
|
+
* terminal sign-in looks like it did nothing until a reload.
|
|
105
|
+
*
|
|
106
|
+
* Directories, never the credential files themselves: the CLIs replace those by
|
|
107
|
+
* rename, which leaves a file watch holding the old inode and silently deaf --
|
|
108
|
+
* the same reason castle-sandboxes persists these paths as directory symlinks.
|
|
109
|
+
* A directory that doesn't exist yet (no `~/.castle` until the first key) simply
|
|
110
|
+
* isn't watched; the caller's poll is what covers that, and losing a watcher
|
|
111
|
+
* costs freshness, never correctness.
|
|
112
|
+
*/
|
|
113
|
+
export function watchCredentials(onChange) {
|
|
114
|
+
const dirs = new Set([
|
|
115
|
+
claudeCredentialsPath(),
|
|
116
|
+
cursorAuthPath(os.homedir()),
|
|
117
|
+
CASTLE_USER_KEYS_PATH,
|
|
118
|
+
].map((p) => path.dirname(p)));
|
|
119
|
+
let timer = null;
|
|
120
|
+
const fire = () => {
|
|
121
|
+
if (timer)
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
timer = setTimeout(onChange, WATCH_DEBOUNCE_MS);
|
|
124
|
+
timer.unref?.();
|
|
125
|
+
};
|
|
126
|
+
const watchers = [];
|
|
127
|
+
for (const dir of dirs) {
|
|
128
|
+
try {
|
|
129
|
+
const w = fs.watch(dir, fire);
|
|
130
|
+
// A watch that dies later (the directory is removed, inotify runs out)
|
|
131
|
+
// must not take the serve with it -- fs.watch reports those as an error
|
|
132
|
+
// event, which is fatal when nothing is listening.
|
|
133
|
+
w.on("error", () => undefined);
|
|
134
|
+
watchers.push(w);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
/* not watchable here */
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return () => {
|
|
141
|
+
if (timer)
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
for (const w of watchers)
|
|
144
|
+
w.close();
|
|
145
|
+
};
|
|
146
|
+
}
|
|
93
147
|
// Generous -- provider key formats are not ours to predict -- but bounded, so a
|
|
94
148
|
// stray paste of a whole file can't land in the credential store.
|
|
95
149
|
const MAX_KEY_LENGTH = 500;
|
package/dist/byo-auth.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export type OpenrouterAuth = {
|
|
|
17
17
|
mode: "proxy";
|
|
18
18
|
key: string;
|
|
19
19
|
};
|
|
20
|
+
export declare function claudeCredentialsPath(): string;
|
|
20
21
|
export declare function claudeHasSavedLogin(): boolean;
|
|
21
22
|
export declare function cursorAuthPath(home: string): string;
|
|
22
23
|
export declare function cursorHasUserLogin(home: string): boolean;
|
|
@@ -25,5 +26,10 @@ export declare const ANTHROPIC_CREDENTIAL_ENV: readonly ["ANTHROPIC_API_KEY", "A
|
|
|
25
26
|
export declare const ANTHROPIC_PROXY_ENV: readonly ["ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_CUSTOM_HEADERS", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN_HELPER", "CLAUDE_CODE_OAUTH_TOKEN"];
|
|
26
27
|
export declare function anthropicKeyHelperCommand(): string;
|
|
27
28
|
export declare function envForUserShell(base: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
28
|
-
export declare
|
|
29
|
-
export
|
|
29
|
+
export declare const SHIMMED_CLIS: readonly ["claude", "cursor-agent"];
|
|
30
|
+
export type ShimmedCli = (typeof SHIMMED_CLIS)[number];
|
|
31
|
+
export declare function claudeShellEnvScript(env: NodeJS.ProcessEnv, args?: readonly string[]): string;
|
|
32
|
+
export declare function cursorShellEnvScript(env: NodeJS.ProcessEnv): string;
|
|
33
|
+
export declare function shimEnvScript(cli: string, env: NodeJS.ProcessEnv, shimDir: string, args?: readonly string[]): string;
|
|
34
|
+
export declare function ensureClaudeOnboarded(): void;
|
|
35
|
+
export declare function installCliShims(deckDir: string): string | null;
|