borgmcp 1.0.5 → 1.0.7
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/assimilate-cmd.js +39 -497
- package/dist/assimilate-deps.js +3 -177
- package/dist/assimilate-welcome.js +2 -24
- package/dist/auth-env.js +1 -107
- package/dist/auth.js +23 -612
- package/dist/claude.js +11 -281
- package/dist/cli-help.js +29 -50
- package/dist/cli-platform.js +4 -94
- package/dist/codex-app-server.js +4 -228
- package/dist/codex-app-wake.js +2 -122
- package/dist/codex-launch.js +1 -81
- package/dist/codex-remote.js +1 -250
- package/dist/config-utils.js +3 -385
- package/dist/config.js +1 -190
- package/dist/console-prefix.js +1 -86
- package/dist/cube-name.js +1 -65
- package/dist/cubes.js +4 -269
- package/dist/debug.js +1 -71
- package/dist/device-auth.js +1 -167
- package/dist/direct-log.js +1 -11
- package/dist/health-beat.js +1 -168
- package/dist/inbox-monitor.js +1 -129
- package/dist/index.js +26 -1378
- package/dist/lifecycle-log-guard.js +2 -93
- package/dist/list-roles-render.js +6 -39
- package/dist/log-audit.js +3 -186
- package/dist/log-stream.js +9 -848
- package/dist/name-validator.js +1 -22
- package/dist/parse-assimilate-args.js +1 -82
- package/dist/postinstall.js +8 -22
- package/dist/regen-format.js +11 -329
- package/dist/regen.js +5 -83
- package/dist/remote-client.js +1 -695
- package/dist/role-resolver.js +1 -36
- package/dist/role-section.js +8 -208
- package/dist/roster-render.js +3 -96
- package/dist/setup.js +36 -251
- package/dist/shell-escape.js +1 -22
- package/dist/spawn.js +10 -29
- package/dist/stale-version-check.js +1 -102
- package/dist/stream-owner.js +2 -202
- package/dist/stream-status.js +3 -211
- package/dist/subscription-retry.js +1 -23
- package/dist/sync-roles-render.js +3 -118
- package/dist/sync.js +22 -286
- package/dist/templates.js +120 -563
- package/dist/terminal-title.js +1 -68
- package/dist/token-crypto.js +1 -91
- package/dist/token-store.js +1 -222
- package/dist/types.js +0 -5
- package/dist/version.js +2 -78
- package/dist/worktree-lifecycle.js +2 -173
- package/package.json +11 -2
- package/dist/assimilate-cmd.d.ts.map +0 -1
- package/dist/assimilate-cmd.js.map +0 -1
- package/dist/assimilate-deps.d.ts.map +0 -1
- package/dist/assimilate-deps.js.map +0 -1
- package/dist/assimilate-welcome.d.ts.map +0 -1
- package/dist/assimilate-welcome.js.map +0 -1
- package/dist/auth-env.d.ts.map +0 -1
- package/dist/auth-env.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/claude.d.ts.map +0 -1
- package/dist/claude.js.map +0 -1
- package/dist/cli-help.d.ts.map +0 -1
- package/dist/cli-help.js.map +0 -1
- package/dist/cli-platform.d.ts.map +0 -1
- package/dist/cli-platform.js.map +0 -1
- package/dist/codex-app-server.d.ts.map +0 -1
- package/dist/codex-app-server.js.map +0 -1
- package/dist/codex-app-wake.d.ts.map +0 -1
- package/dist/codex-app-wake.js.map +0 -1
- package/dist/codex-launch.d.ts.map +0 -1
- package/dist/codex-launch.js.map +0 -1
- package/dist/codex-remote.d.ts.map +0 -1
- package/dist/codex-remote.js.map +0 -1
- package/dist/config-utils.d.ts.map +0 -1
- package/dist/config-utils.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/console-prefix.d.ts.map +0 -1
- package/dist/console-prefix.js.map +0 -1
- package/dist/cube-name.d.ts.map +0 -1
- package/dist/cube-name.js.map +0 -1
- package/dist/cubes.d.ts.map +0 -1
- package/dist/cubes.js.map +0 -1
- package/dist/debug.d.ts.map +0 -1
- package/dist/debug.js.map +0 -1
- package/dist/device-auth.d.ts.map +0 -1
- package/dist/device-auth.js.map +0 -1
- package/dist/direct-log.d.ts.map +0 -1
- package/dist/direct-log.js.map +0 -1
- package/dist/health-beat.d.ts.map +0 -1
- package/dist/health-beat.js.map +0 -1
- package/dist/inbox-monitor.d.ts.map +0 -1
- package/dist/inbox-monitor.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lifecycle-log-guard.d.ts.map +0 -1
- package/dist/lifecycle-log-guard.js.map +0 -1
- package/dist/list-roles-render.d.ts.map +0 -1
- package/dist/list-roles-render.js.map +0 -1
- package/dist/log-audit.d.ts.map +0 -1
- package/dist/log-audit.js.map +0 -1
- package/dist/log-stream.d.ts.map +0 -1
- package/dist/log-stream.js.map +0 -1
- package/dist/name-validator.d.ts.map +0 -1
- package/dist/name-validator.js.map +0 -1
- package/dist/parse-assimilate-args.d.ts.map +0 -1
- package/dist/parse-assimilate-args.js.map +0 -1
- package/dist/postinstall.d.ts.map +0 -1
- package/dist/postinstall.js.map +0 -1
- package/dist/regen-format.d.ts.map +0 -1
- package/dist/regen-format.js.map +0 -1
- package/dist/regen.d.ts.map +0 -1
- package/dist/regen.js.map +0 -1
- package/dist/remote-client.d.ts.map +0 -1
- package/dist/remote-client.js.map +0 -1
- package/dist/role-resolver.d.ts.map +0 -1
- package/dist/role-resolver.js.map +0 -1
- package/dist/role-section.d.ts.map +0 -1
- package/dist/role-section.js.map +0 -1
- package/dist/roster-render.d.ts.map +0 -1
- package/dist/roster-render.js.map +0 -1
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js.map +0 -1
- package/dist/shell-escape.d.ts.map +0 -1
- package/dist/shell-escape.js.map +0 -1
- package/dist/spawn.d.ts.map +0 -1
- package/dist/spawn.js.map +0 -1
- package/dist/stale-version-check.d.ts.map +0 -1
- package/dist/stale-version-check.js.map +0 -1
- package/dist/stream-owner.d.ts.map +0 -1
- package/dist/stream-owner.js.map +0 -1
- package/dist/stream-status.d.ts.map +0 -1
- package/dist/stream-status.js.map +0 -1
- package/dist/subscription-retry.d.ts.map +0 -1
- package/dist/subscription-retry.js.map +0 -1
- package/dist/sync-roles-render.d.ts.map +0 -1
- package/dist/sync-roles-render.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/templates.d.ts.map +0 -1
- package/dist/templates.js.map +0 -1
- package/dist/terminal-title.d.ts.map +0 -1
- package/dist/terminal-title.js.map +0 -1
- package/dist/token-crypto.d.ts.map +0 -1
- package/dist/token-crypto.js.map +0 -1
- package/dist/token-store.d.ts.map +0 -1
- package/dist/token-store.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
- package/dist/worktree-lifecycle.d.ts.map +0 -1
- package/dist/worktree-lifecycle.js.map +0 -1
package/dist/config.js
CHANGED
|
@@ -1,190 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Secure token storage.
|
|
3
|
-
*
|
|
4
|
-
* The public API (storeIdToken / getIdToken / getRefreshToken / clearTokens /
|
|
5
|
-
* isAuthenticated) is unchanged; what changed in gh#557 is what sits beneath
|
|
6
|
-
* it. Three storage paths, in precedence order:
|
|
7
|
-
*
|
|
8
|
-
* 1. Caller-managed (read-only): if BORG_TOKEN / BORG_TOKEN_FILE supplies an
|
|
9
|
-
* id_token, it's served verbatim — no keychain, no expiry check, no
|
|
10
|
-
* refresh_token. The caller owns the token's lifecycle (CI, containers,
|
|
11
|
-
* `borg --token-file`).
|
|
12
|
-
* 2. OS keychain (default): @napi-rs/keyring — real platform at-rest
|
|
13
|
-
* encryption (macOS Keychain / Windows Credential Vault / libsecret).
|
|
14
|
-
* 3. Encrypted file (fallback): ~/.borg/credentials, AES-256-GCM under a
|
|
15
|
-
* machine-derived key, 0600. Engages only when no keychain is available
|
|
16
|
-
* (headless Linux without Secret Service). Obfuscation-grade — see
|
|
17
|
-
* token-crypto.ts.
|
|
18
|
-
*
|
|
19
|
-
* The persistent backend (2 or 3) is selected once per process and memoized.
|
|
20
|
-
* BORG_TOKEN_STORE=keychain|file forces the choice and skips the probe.
|
|
21
|
-
*/
|
|
22
|
-
import os from 'os';
|
|
23
|
-
import path from 'path';
|
|
24
|
-
import { promises as fsp } from 'fs';
|
|
25
|
-
import { isKeyringAvailable } from './auth-env.js';
|
|
26
|
-
import { deriveMachineKey } from './token-crypto.js';
|
|
27
|
-
import { makeKeychainBackend, makeEncryptedFileBackend, selectTokenBackend, readCallerManagedIdToken, } from './token-store.js';
|
|
28
|
-
const ID_TOKEN_ACCOUNT = 'google-id-token';
|
|
29
|
-
const REFRESH_TOKEN_ACCOUNT = 'google-refresh-token';
|
|
30
|
-
const TOKEN_EXPIRY_ACCOUNT = 'token-expiry';
|
|
31
|
-
/** Where the encrypted-file fallback lives when no keychain is available. */
|
|
32
|
-
function credentialsPath() {
|
|
33
|
-
return path.join(os.homedir(), '.borg', 'credentials');
|
|
34
|
-
}
|
|
35
|
-
/** Production fs adapter for the encrypted-file backend. */
|
|
36
|
-
const nodeFs = {
|
|
37
|
-
readFile: (filePath) => fsp.readFile(filePath, 'utf8'),
|
|
38
|
-
writeFile: async (filePath, data, mode) => {
|
|
39
|
-
// `mode` on writeFile only applies when the file is CREATED; chmod after
|
|
40
|
-
// guarantees 0600 even when rewriting an existing credentials file.
|
|
41
|
-
await fsp.writeFile(filePath, data, { mode });
|
|
42
|
-
await fsp.chmod(filePath, mode);
|
|
43
|
-
},
|
|
44
|
-
mkdir: async (dir, mode) => {
|
|
45
|
-
await fsp.mkdir(dir, { recursive: true, mode });
|
|
46
|
-
},
|
|
47
|
-
// gh#570: atomic write (temp→rename) + O_EXCL lock primitives.
|
|
48
|
-
rename: (from, to) => fsp.rename(from, to),
|
|
49
|
-
createExclusive: async (lockPath, content) => {
|
|
50
|
-
try {
|
|
51
|
-
// 'wx' = O_CREAT | O_EXCL | O_WRONLY → fails with EEXIST if the lock
|
|
52
|
-
// already exists, giving us the atomic acquire primitive.
|
|
53
|
-
await fsp.writeFile(lockPath, content, { flag: 'wx', mode: 0o600 });
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
if (err?.code === 'EEXIST')
|
|
58
|
-
return false;
|
|
59
|
-
throw err;
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
removeFile: async (filePath) => {
|
|
63
|
-
try {
|
|
64
|
-
await fsp.unlink(filePath);
|
|
65
|
-
}
|
|
66
|
-
catch (err) {
|
|
67
|
-
if (err?.code !== 'ENOENT')
|
|
68
|
-
throw err; // silent if already gone
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
fileAgeMs: async (filePath) => {
|
|
72
|
-
try {
|
|
73
|
-
const stat = await fsp.stat(filePath);
|
|
74
|
-
return Date.now() - stat.mtimeMs;
|
|
75
|
-
}
|
|
76
|
-
catch (err) {
|
|
77
|
-
if (err?.code === 'ENOENT')
|
|
78
|
-
return null;
|
|
79
|
-
throw err;
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
/** Map the user-facing BORG_TOKEN_STORE value to a forced backend, if valid. */
|
|
84
|
-
function parseForcedStore(value) {
|
|
85
|
-
const v = value?.trim().toLowerCase();
|
|
86
|
-
if (v === 'keychain')
|
|
87
|
-
return 'keychain';
|
|
88
|
-
if (v === 'file' || v === 'encrypted-file')
|
|
89
|
-
return 'file';
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
// Memoized persistent-backend selection (one keychain probe per process).
|
|
93
|
-
let backendPromise = null;
|
|
94
|
-
function getBackend() {
|
|
95
|
-
if (!backendPromise) {
|
|
96
|
-
backendPromise = selectTokenBackend({
|
|
97
|
-
keyringAvailable: () => isKeyringAvailable(),
|
|
98
|
-
makeKeychain: () => makeKeychainBackend(),
|
|
99
|
-
makeFile: () => makeEncryptedFileBackend({
|
|
100
|
-
filePath: credentialsPath(),
|
|
101
|
-
key: deriveMachineKey({
|
|
102
|
-
hostname: os.hostname(),
|
|
103
|
-
username: os.userInfo().username,
|
|
104
|
-
platform: process.platform,
|
|
105
|
-
}),
|
|
106
|
-
fs: nodeFs,
|
|
107
|
-
}),
|
|
108
|
-
forced: parseForcedStore(process.env.BORG_TOKEN_STORE),
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
return backendPromise;
|
|
112
|
-
}
|
|
113
|
-
/** Caller-managed id_token (BORG_TOKEN / BORG_TOKEN_FILE), or null. */
|
|
114
|
-
function callerManagedIdToken() {
|
|
115
|
-
return readCallerManagedIdToken({
|
|
116
|
-
env: process.env,
|
|
117
|
-
readFile: (filePath) => fsp.readFile(filePath, 'utf8'),
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Store Google OAuth ID token securely in the selected backend.
|
|
122
|
-
*/
|
|
123
|
-
export async function storeIdToken(idToken, expiresAt) {
|
|
124
|
-
const backend = await getBackend();
|
|
125
|
-
await backend.set(ID_TOKEN_ACCOUNT, idToken);
|
|
126
|
-
await backend.set(TOKEN_EXPIRY_ACCOUNT, expiresAt.toString());
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Store Google OAuth refresh token securely in the selected backend.
|
|
130
|
-
*/
|
|
131
|
-
export async function storeRefreshToken(refreshToken) {
|
|
132
|
-
const backend = await getBackend();
|
|
133
|
-
await backend.set(REFRESH_TOKEN_ACCOUNT, refreshToken);
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Retrieve the Google OAuth ID token.
|
|
137
|
-
*
|
|
138
|
-
* A caller-managed token (BORG_TOKEN / BORG_TOKEN_FILE) takes precedence and
|
|
139
|
-
* is returned verbatim — the caller owns its freshness, so the expiry buffer
|
|
140
|
-
* does not apply. Otherwise reads the persistent backend and returns null if
|
|
141
|
-
* not stored or within the 5-minute expiry buffer.
|
|
142
|
-
*/
|
|
143
|
-
export async function getIdToken() {
|
|
144
|
-
const callerManaged = await callerManagedIdToken();
|
|
145
|
-
if (callerManaged)
|
|
146
|
-
return callerManaged;
|
|
147
|
-
const backend = await getBackend();
|
|
148
|
-
const token = await backend.get(ID_TOKEN_ACCOUNT);
|
|
149
|
-
const expiryStr = await backend.get(TOKEN_EXPIRY_ACCOUNT);
|
|
150
|
-
if (!token || !expiryStr) {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
const expiresAt = parseInt(expiryStr, 10);
|
|
154
|
-
const now = Date.now();
|
|
155
|
-
// Check if token is expired (with 5 minute buffer).
|
|
156
|
-
if (expiresAt - now < 5 * 60 * 1000) {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
return token;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Retrieve the Google OAuth refresh token. There is no refresh_token in
|
|
163
|
-
* caller-managed mode (the externally-supplied id_token has no refresh
|
|
164
|
-
* counterpart), so this returns null whenever a caller-managed token is set.
|
|
165
|
-
*/
|
|
166
|
-
export async function getRefreshToken() {
|
|
167
|
-
if (await callerManagedIdToken())
|
|
168
|
-
return null;
|
|
169
|
-
const backend = await getBackend();
|
|
170
|
-
return backend.get(REFRESH_TOKEN_ACCOUNT);
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Clear all stored tokens from the selected backend. Idempotent — clearing
|
|
174
|
-
* an already-empty store is a no-op. Does not touch caller-managed env vars
|
|
175
|
-
* (those are the caller's to manage).
|
|
176
|
-
*/
|
|
177
|
-
export async function clearTokens() {
|
|
178
|
-
const backend = await getBackend();
|
|
179
|
-
await backend.delete(ID_TOKEN_ACCOUNT);
|
|
180
|
-
await backend.delete(REFRESH_TOKEN_ACCOUNT);
|
|
181
|
-
await backend.delete(TOKEN_EXPIRY_ACCOUNT);
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Check if user has valid authentication.
|
|
185
|
-
*/
|
|
186
|
-
export async function isAuthenticated() {
|
|
187
|
-
const token = await getIdToken();
|
|
188
|
-
return token !== null;
|
|
189
|
-
}
|
|
190
|
-
//# sourceMappingURL=config.js.map
|
|
1
|
+
import o from"os";import m from"path";import{promises as r}from"fs";import{isKeyringAvailable as w}from"./auth-env.js";import{deriveMachineKey as p}from"./token-crypto.js";import{makeKeychainBackend as y,makeEncryptedFileBackend as h,selectTokenBackend as g,readCallerManagedIdToken as T}from"./token-store.js";const i="google-id-token",c="google-refresh-token",s="token-expiry";function E(){return m.join(o.homedir(),".borg","credentials")}const F={readFile:e=>r.readFile(e,"utf8"),writeFile:async(e,t,n)=>{await r.writeFile(e,t,{mode:n}),await r.chmod(e,n)},mkdir:async(e,t)=>{await r.mkdir(e,{recursive:!0,mode:t})},rename:(e,t)=>r.rename(e,t),createExclusive:async(e,t)=>{try{return await r.writeFile(e,t,{flag:"wx",mode:384}),!0}catch(n){if(n?.code==="EEXIST")return!1;throw n}},removeFile:async e=>{try{await r.unlink(e)}catch(t){if(t?.code!=="ENOENT")throw t}},fileAgeMs:async e=>{try{const t=await r.stat(e);return Date.now()-t.mtimeMs}catch(t){if(t?.code==="ENOENT")return null;throw t}}};function b(e){const t=e?.trim().toLowerCase();if(t==="keychain")return"keychain";if(t==="file"||t==="encrypted-file")return"file"}let l=null;function a(){return l||(l=g({keyringAvailable:()=>w(),makeKeychain:()=>y(),makeFile:()=>h({filePath:E(),key:p({hostname:o.hostname(),username:o.userInfo().username,platform:process.platform}),fs:F}),forced:b(process.env.BORG_TOKEN_STORE)})),l}function u(){return T({env:process.env,readFile:e=>r.readFile(e,"utf8")})}async function K(e,t){const n=await a();await n.set(i,e),await n.set(s,t.toString())}async function _(e){await(await a()).set(c,e)}async function x(){const e=await u();if(e)return e;const t=await a(),n=await t.get(i),d=await t.get(s);if(!n||!d)return null;const f=parseInt(d,10),k=Date.now();return f-k<300*1e3?null:n}async function R(){return await u()?null:(await a()).get(c)}async function M(){const e=await a();await e.delete(i),await e.delete(c),await e.delete(s)}async function S(){return await x()!==null}export{M as clearTokens,x as getIdToken,R as getRefreshToken,S as isAuthenticated,K as storeIdToken,_ as storeRefreshToken};
|
package/dist/console-prefix.js
CHANGED
|
@@ -1,86 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Drone self-identification prefix for client-emitted console messages.
|
|
3
|
-
*
|
|
4
|
-
* Per gh#25: when a drone session emits a console error (e.g.
|
|
5
|
-
* "Authentication expired. Run: borg assimilate"), the Queen has no way to
|
|
6
|
-
* tell which drone window the message came from without scanning every
|
|
7
|
-
* open terminal. Window title alone (set by terminal-title.ts) is
|
|
8
|
-
* insufficient — the Queen reads the active terminal's output stream,
|
|
9
|
-
* not its title bar.
|
|
10
|
-
*
|
|
11
|
-
* This module exports a one-shot initializer that resolves the prefix
|
|
12
|
-
* from the local cube state cache, plus a synchronous getter that
|
|
13
|
-
* call sites use to wrap each console.error.
|
|
14
|
-
*
|
|
15
|
-
* Format (matches the terminal-title.ts middle-dot convention so
|
|
16
|
-
* surfaces stay internally consistent):
|
|
17
|
-
* `[<drone-label> · <cube-name>]` (assimilated)
|
|
18
|
-
* `[unassimilated · <repo-basename>]` (no cube cached)
|
|
19
|
-
*/
|
|
20
|
-
import { basename } from 'node:path';
|
|
21
|
-
import chalk from 'chalk';
|
|
22
|
-
import { getActiveCube } from './cubes.js';
|
|
23
|
-
let cachedPrefix = null;
|
|
24
|
-
/**
|
|
25
|
-
* Resolve the drone-self-identification prefix from cube state and
|
|
26
|
-
* cache it for subsequent synchronous reads. Idempotent — calling
|
|
27
|
-
* multiple times returns the same value. Falls back silently to the
|
|
28
|
-
* unassimilated shape on any read error so console emission is never
|
|
29
|
-
* blocked.
|
|
30
|
-
*/
|
|
31
|
-
export async function initConsolePrefix() {
|
|
32
|
-
if (cachedPrefix !== null)
|
|
33
|
-
return cachedPrefix;
|
|
34
|
-
try {
|
|
35
|
-
const active = await getActiveCube();
|
|
36
|
-
if (active?.droneLabel && active?.name) {
|
|
37
|
-
cachedPrefix = `[${active.droneLabel} · ${active.name}]`;
|
|
38
|
-
return cachedPrefix;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// Fall through to unassimilated fallback.
|
|
43
|
-
}
|
|
44
|
-
cachedPrefix = `[unassimilated · ${basename(process.cwd())}]`;
|
|
45
|
-
return cachedPrefix;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Synchronous prefix getter. Returns the cached value if initialized,
|
|
49
|
-
* otherwise the unassimilated fallback — safe to call before
|
|
50
|
-
* initConsolePrefix() resolves.
|
|
51
|
-
*/
|
|
52
|
-
export function droneIdPrefix() {
|
|
53
|
-
if (cachedPrefix !== null)
|
|
54
|
-
return cachedPrefix;
|
|
55
|
-
return `[unassimilated · ${basename(process.cwd())}]`;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Prefix + trailing space, styled dim/gray so the prefix is metadata
|
|
59
|
-
* and the message body retains visual emphasis. Use as
|
|
60
|
-
* `${consolePrefix()}<message>`.
|
|
61
|
-
*/
|
|
62
|
-
export function consolePrefix() {
|
|
63
|
-
return chalk.gray(droneIdPrefix()) + ' ';
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Drop-in replacement for `console.error` that prepends the drone
|
|
67
|
-
* self-id prefix. If the first arg is a string, the prefix is
|
|
68
|
-
* concatenated to it; otherwise the prefix is emitted as its own arg
|
|
69
|
-
* (handles the `console.error('label:', value)` shape).
|
|
70
|
-
*/
|
|
71
|
-
export function cerr(...args) {
|
|
72
|
-
if (args.length === 0) {
|
|
73
|
-
console.error(consolePrefix());
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (typeof args[0] === 'string') {
|
|
77
|
-
console.error(consolePrefix() + args[0], ...args.slice(1));
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
console.error(consolePrefix(), ...args);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
export function _resetCachedPrefixForTests() {
|
|
84
|
-
cachedPrefix = null;
|
|
85
|
-
}
|
|
86
|
-
//# sourceMappingURL=console-prefix.js.map
|
|
1
|
+
import{basename as o}from"node:path";import t from"chalk";import{getActiveCube as i}from"./cubes.js";let r=null;async function s(){if(r!==null)return r;try{const e=await i();if(e?.droneLabel&&e?.name)return r=`[${e.droneLabel} \xB7 ${e.name}]`,r}catch{}return r=`[unassimilated \xB7 ${o(process.cwd())}]`,r}function c(){return r!==null?r:`[unassimilated \xB7 ${o(process.cwd())}]`}function n(){return t.gray(c())+" "}function a(...e){if(e.length===0){console.error(n());return}typeof e[0]=="string"?console.error(n()+e[0],...e.slice(1)):console.error(n(),...e)}function m(){r=null}export{m as _resetCachedPrefixForTests,a as cerr,n as consolePrefix,c as droneIdPrefix,s as initConsolePrefix};
|
package/dist/cube-name.js
CHANGED
|
@@ -1,65 +1 @@
|
|
|
1
|
-
const
|
|
2
|
-
const CONTROL_CHAR_RE = /[\x00-\x1F\x7F]/;
|
|
3
|
-
/**
|
|
4
|
-
* Trim + reject control chars + cap length. Returns null on rejection
|
|
5
|
-
* so callers fall through to no-remote derivation.
|
|
6
|
-
*/
|
|
7
|
-
export function sanitizeRemoteUrl(raw) {
|
|
8
|
-
const trimmed = raw.trim();
|
|
9
|
-
if (trimmed.length === 0 || trimmed.length > MAX_URL_LEN)
|
|
10
|
-
return null;
|
|
11
|
-
if (CONTROL_CHAR_RE.test(trimmed))
|
|
12
|
-
return null;
|
|
13
|
-
return trimmed;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Extract the repo name from a git remote URL. Handles SSH/HTTPS/git/file
|
|
17
|
-
* forms and embedded credentials. Returns null when nothing parseable
|
|
18
|
-
* is present.
|
|
19
|
-
*
|
|
20
|
-
* Strategy: strip protocol + credentials, then take the last path segment
|
|
21
|
-
* after the final `/` or `:`, stripping a trailing `.git`.
|
|
22
|
-
*/
|
|
23
|
-
export function parseGitRemote(url) {
|
|
24
|
-
if (!url)
|
|
25
|
-
return null;
|
|
26
|
-
let s = url.replace(/^[a-z]+:\/\//, '');
|
|
27
|
-
s = s.replace(/^[^@\/]*@/, '');
|
|
28
|
-
const lastSep = Math.max(s.lastIndexOf('/'), s.lastIndexOf(':'));
|
|
29
|
-
if (lastSep === -1)
|
|
30
|
-
return null;
|
|
31
|
-
let name = s.slice(lastSep + 1);
|
|
32
|
-
name = name.replace(/\.git$/, '');
|
|
33
|
-
return name.length > 0 ? name : null;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Normalize an arbitrary string into a valid cube name:
|
|
37
|
-
* lowercase, underscores+spaces → hyphens, strip [^a-z0-9-], truncate 64.
|
|
38
|
-
*/
|
|
39
|
-
export function normalizeCubeName(raw) {
|
|
40
|
-
return raw
|
|
41
|
-
.toLowerCase()
|
|
42
|
-
.replace(/[_\s]+/g, '-')
|
|
43
|
-
.replace(/[^a-z0-9-]/g, '')
|
|
44
|
-
.slice(0, 64);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Compose the full derivation: sanitize + parse + normalize, with
|
|
48
|
-
* project-root basename as fallback. Returns null when no valid name
|
|
49
|
-
* can be derived.
|
|
50
|
-
*/
|
|
51
|
-
export function deriveCubeName(projectRoot, gitRemoteUrl) {
|
|
52
|
-
if (gitRemoteUrl) {
|
|
53
|
-
const sanitized = sanitizeRemoteUrl(gitRemoteUrl);
|
|
54
|
-
if (sanitized) {
|
|
55
|
-
const repo = parseGitRemote(sanitized);
|
|
56
|
-
if (repo) {
|
|
57
|
-
const normalized = normalizeCubeName(repo);
|
|
58
|
-
if (normalized.length > 0)
|
|
59
|
-
return normalized;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=cube-name.js.map
|
|
1
|
+
const c=2048,i=/[\x00-\x1F\x7F]/;function o(t){const e=t.trim();return e.length===0||e.length>2048||i.test(e)?null:e}function a(t){if(!t)return null;let e=t.replace(/^[a-z]+:\/\//,"");e=e.replace(/^[^@\/]*@/,"");const r=Math.max(e.lastIndexOf("/"),e.lastIndexOf(":"));if(r===-1)return null;let n=e.slice(r+1);return n=n.replace(/\.git$/,""),n.length>0?n:null}function u(t){return t.toLowerCase().replace(/[_\s]+/g,"-").replace(/[^a-z0-9-]/g,"").slice(0,64)}function s(t,e){if(e){const r=o(e);if(r){const n=a(r);if(n){const l=u(n);if(l.length>0)return l}}}return null}export{s as deriveCubeName,u as normalizeCubeName,a as parseGitRemote,o as sanitizeRemoteUrl};
|
package/dist/cubes.js
CHANGED
|
@@ -1,269 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* label + apiUrl) PER PROJECT in ~/.config/borgmcp/cubes.json. The "project"
|
|
6
|
-
* is identified by walking up from cwd to find a .git directory; if none is
|
|
7
|
-
* found, cwd itself is used as the project key.
|
|
8
|
-
*
|
|
9
|
-
* The session token is a bearer credential for drone-scoped REST endpoints,
|
|
10
|
-
* but it is not as sensitive as the OAuth tokens (which still live in the
|
|
11
|
-
* OS keychain via config.ts) — it only authorizes access within the cube
|
|
12
|
-
* the user has already been admitted to.
|
|
13
|
-
*
|
|
14
|
-
* apiUrl is captured at assimilate time so subprocess invocations (e.g. the
|
|
15
|
-
* SessionStart hook firing borg-regen) don't need BORG_API_URL in their env
|
|
16
|
-
* to know which worker to talk to.
|
|
17
|
-
*/
|
|
18
|
-
import { existsSync } from 'node:fs';
|
|
19
|
-
import { mkdir, readFile, writeFile, unlink } from 'node:fs/promises';
|
|
20
|
-
import { homedir } from 'node:os';
|
|
21
|
-
import { dirname, join, resolve } from 'node:path';
|
|
22
|
-
const CUBES_DIR = join(homedir(), '.config', 'borgmcp');
|
|
23
|
-
const CUBES_FILE = join(CUBES_DIR, 'cubes.json');
|
|
24
|
-
const LAUNCH_FILE = join(CUBES_DIR, 'launch.json');
|
|
25
|
-
const CODEX_WAKE_TARGETS_FILE = join(CUBES_DIR, 'codex-wake-targets.json');
|
|
26
|
-
const INBOX_DIR = join(CUBES_DIR, 'inboxes');
|
|
27
|
-
/**
|
|
28
|
-
* Walk up from cwd looking for a .git directory. If found, return that
|
|
29
|
-
* directory. If not found by filesystem root, return the original cwd.
|
|
30
|
-
* The returned absolute path is the "project key" used to scope cube state.
|
|
31
|
-
*/
|
|
32
|
-
export function findProjectRoot(cwd = process.cwd()) {
|
|
33
|
-
let dir = resolve(cwd);
|
|
34
|
-
while (true) {
|
|
35
|
-
if (existsSync(join(dir, '.git')))
|
|
36
|
-
return dir;
|
|
37
|
-
const parent = dirname(dir);
|
|
38
|
-
if (parent === dir)
|
|
39
|
-
return resolve(cwd); // hit root, fall back to cwd
|
|
40
|
-
dir = parent;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Per-(cube, drone) inbox file path. Each drone gets its own file so that
|
|
45
|
-
* multiple drones in the same cube don't trample each other's writes when
|
|
46
|
-
* they each receive the same long-poll batch. The file lives under a
|
|
47
|
-
* per-cube subdir keyed by cube ID, then by drone ID (a UUID, globally
|
|
48
|
-
* unique).
|
|
49
|
-
*
|
|
50
|
-
* Validates cubeId/droneId as UUIDs before using them in a filesystem
|
|
51
|
-
* path. The values come from cubes.json (populated from server response),
|
|
52
|
-
* so the input is trusted in normal operation — but a regex guard is
|
|
53
|
-
* cheap defense against a corrupted file or future bug that would
|
|
54
|
-
* otherwise let `../` slip through into the inbox path.
|
|
55
|
-
*/
|
|
56
|
-
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
57
|
-
export function inboxPathForDrone(cubeId, droneId) {
|
|
58
|
-
if (!UUID_RE.test(cubeId))
|
|
59
|
-
throw new Error(`Invalid cubeId: ${cubeId}`);
|
|
60
|
-
if (!UUID_RE.test(droneId))
|
|
61
|
-
throw new Error(`Invalid droneId: ${droneId}`);
|
|
62
|
-
return join(INBOX_DIR, cubeId, `${droneId}.log`);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Type guard: true iff the parsed JSON looks like the new schema. Anything
|
|
66
|
-
* else (old single-cube schema, malformed, missing) is treated as "no state".
|
|
67
|
-
*/
|
|
68
|
-
function isCubesFile(data) {
|
|
69
|
-
return (data !== null &&
|
|
70
|
-
typeof data === 'object' &&
|
|
71
|
-
typeof data.projects === 'object' &&
|
|
72
|
-
data.projects !== null &&
|
|
73
|
-
!Array.isArray(data.projects));
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Read the cubes.json file. Returns null if the file does not exist, is
|
|
77
|
-
* unparseable, or is in the old single-cube schema (per the project's no-
|
|
78
|
-
* backward-compat stance, the old shape is treated as absent — it will be
|
|
79
|
-
* overwritten the next time setActiveCube() runs).
|
|
80
|
-
*/
|
|
81
|
-
async function readCubesFile() {
|
|
82
|
-
let raw;
|
|
83
|
-
try {
|
|
84
|
-
raw = await readFile(CUBES_FILE, 'utf8');
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
if (error?.code === 'ENOENT')
|
|
88
|
-
return null;
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
let parsed;
|
|
92
|
-
try {
|
|
93
|
-
parsed = JSON.parse(raw);
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
if (!isCubesFile(parsed))
|
|
99
|
-
return null;
|
|
100
|
-
return parsed;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Write the cubes.json file, ensuring the parent directory exists.
|
|
104
|
-
*/
|
|
105
|
-
async function writeCubesFile(data) {
|
|
106
|
-
await mkdir(dirname(CUBES_FILE), { recursive: true });
|
|
107
|
-
await writeFile(CUBES_FILE, JSON.stringify(data, null, 2) + '\n', {
|
|
108
|
-
mode: 0o600,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
function isLaunchFile(data) {
|
|
112
|
-
return (data !== null &&
|
|
113
|
-
typeof data === 'object' &&
|
|
114
|
-
typeof data.projects === 'object' &&
|
|
115
|
-
data.projects !== null &&
|
|
116
|
-
!Array.isArray(data.projects));
|
|
117
|
-
}
|
|
118
|
-
async function readLaunchFile() {
|
|
119
|
-
let raw;
|
|
120
|
-
try {
|
|
121
|
-
raw = await readFile(LAUNCH_FILE, 'utf8');
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
if (error?.code === 'ENOENT')
|
|
125
|
-
return null;
|
|
126
|
-
throw error;
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
const parsed = JSON.parse(raw);
|
|
130
|
-
return isLaunchFile(parsed) ? parsed : null;
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
async function writeLaunchFile(data) {
|
|
137
|
-
await mkdir(dirname(LAUNCH_FILE), { recursive: true });
|
|
138
|
-
await writeFile(LAUNCH_FILE, JSON.stringify(data, null, 2) + '\n', {
|
|
139
|
-
mode: 0o600,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
function codexWakeTargetKey(cubeId, droneId) {
|
|
143
|
-
if (!UUID_RE.test(cubeId))
|
|
144
|
-
throw new Error(`Invalid cubeId: ${cubeId}`);
|
|
145
|
-
if (!UUID_RE.test(droneId))
|
|
146
|
-
throw new Error(`Invalid droneId: ${droneId}`);
|
|
147
|
-
return `${cubeId}:${droneId}`;
|
|
148
|
-
}
|
|
149
|
-
function isCodexWakeTargetsFile(data) {
|
|
150
|
-
return (data !== null &&
|
|
151
|
-
typeof data === 'object' &&
|
|
152
|
-
typeof data.targets === 'object' &&
|
|
153
|
-
data.targets !== null &&
|
|
154
|
-
!Array.isArray(data.targets));
|
|
155
|
-
}
|
|
156
|
-
async function readCodexWakeTargetsFile() {
|
|
157
|
-
let raw;
|
|
158
|
-
try {
|
|
159
|
-
raw = await readFile(CODEX_WAKE_TARGETS_FILE, 'utf8');
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
if (error?.code === 'ENOENT')
|
|
163
|
-
return null;
|
|
164
|
-
throw error;
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
const parsed = JSON.parse(raw);
|
|
168
|
-
return isCodexWakeTargetsFile(parsed) ? parsed : null;
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
async function writeCodexWakeTargetsFile(data) {
|
|
175
|
-
await mkdir(dirname(CODEX_WAKE_TARGETS_FILE), { recursive: true });
|
|
176
|
-
await writeFile(CODEX_WAKE_TARGETS_FILE, JSON.stringify(data, null, 2) + '\n', {
|
|
177
|
-
mode: 0o600,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Get the currently-active cube for the current project, or null if not
|
|
182
|
-
* assimilated in this project. Entries written by older client versions
|
|
183
|
-
* that lack the `cubeId` field are treated as absent — re-assimilate to
|
|
184
|
-
* refresh.
|
|
185
|
-
*/
|
|
186
|
-
export async function getActiveCube() {
|
|
187
|
-
const data = await readCubesFile();
|
|
188
|
-
if (!data)
|
|
189
|
-
return null;
|
|
190
|
-
const key = findProjectRoot();
|
|
191
|
-
const entry = data.projects[key];
|
|
192
|
-
if (!entry || typeof entry.cubeId !== 'string' || !entry.cubeId)
|
|
193
|
-
return null;
|
|
194
|
-
if (typeof entry.droneId !== 'string' || !entry.droneId)
|
|
195
|
-
return null;
|
|
196
|
-
return entry;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Set the active cube for the current project. Preserves entries for all
|
|
200
|
-
* other projects.
|
|
201
|
-
*/
|
|
202
|
-
export async function setActiveCube(active) {
|
|
203
|
-
const existing = (await readCubesFile()) ?? { projects: {} };
|
|
204
|
-
existing.projects[findProjectRoot()] = active;
|
|
205
|
-
await writeCubesFile(existing);
|
|
206
|
-
}
|
|
207
|
-
export function activeCubeWithFreshRegenIdentity(active, result) {
|
|
208
|
-
const name = result.cube?.name ?? active.name;
|
|
209
|
-
const droneLabel = result.drone?.label ?? active.droneLabel;
|
|
210
|
-
if (name === active.name && droneLabel === active.droneLabel)
|
|
211
|
-
return active;
|
|
212
|
-
return { ...active, name, droneLabel };
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Clear the active cube for the current project. If the projects map
|
|
216
|
-
* becomes empty as a result, remove the file entirely rather than leave
|
|
217
|
-
* an empty {projects:{}} skeleton.
|
|
218
|
-
*/
|
|
219
|
-
export async function clearActiveCube() {
|
|
220
|
-
const existing = await readCubesFile();
|
|
221
|
-
if (!existing)
|
|
222
|
-
return;
|
|
223
|
-
const key = findProjectRoot();
|
|
224
|
-
if (!(key in existing.projects))
|
|
225
|
-
return;
|
|
226
|
-
delete existing.projects[key];
|
|
227
|
-
if (Object.keys(existing.projects).length === 0) {
|
|
228
|
-
try {
|
|
229
|
-
await unlink(CUBES_FILE);
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
if (error?.code !== 'ENOENT')
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
await writeCubesFile(existing);
|
|
238
|
-
}
|
|
239
|
-
export async function getProjectCliPreference() {
|
|
240
|
-
const data = await readLaunchFile();
|
|
241
|
-
if (!data)
|
|
242
|
-
return null;
|
|
243
|
-
const entry = data.projects[findProjectRoot()];
|
|
244
|
-
return entry?.cli === 'claude' || entry?.cli === 'codex' ? entry.cli : null;
|
|
245
|
-
}
|
|
246
|
-
export async function setProjectCliPreference(cli) {
|
|
247
|
-
const existing = (await readLaunchFile()) ?? { projects: {} };
|
|
248
|
-
existing.projects[findProjectRoot()] = { cli };
|
|
249
|
-
await writeLaunchFile(existing);
|
|
250
|
-
}
|
|
251
|
-
export async function setCodexWakeTarget(cubeId, droneId, target) {
|
|
252
|
-
const existing = (await readCodexWakeTargetsFile()) ?? { targets: {} };
|
|
253
|
-
existing.targets[codexWakeTargetKey(cubeId, droneId)] = {
|
|
254
|
-
...target,
|
|
255
|
-
updatedAt: new Date().toISOString(),
|
|
256
|
-
};
|
|
257
|
-
await writeCodexWakeTargetsFile(existing);
|
|
258
|
-
}
|
|
259
|
-
export async function getCodexWakeTarget(cubeId, droneId) {
|
|
260
|
-
const existing = await readCodexWakeTargetsFile();
|
|
261
|
-
if (!existing)
|
|
262
|
-
return null;
|
|
263
|
-
const target = existing.targets[codexWakeTargetKey(cubeId, droneId)];
|
|
264
|
-
if (!target || typeof target.threadId !== 'string' || typeof target.socketPath !== 'string') {
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
return target;
|
|
268
|
-
}
|
|
269
|
-
//# sourceMappingURL=cubes.js.map
|
|
1
|
+
import{existsSync as E}from"node:fs";import{mkdir as l,readFile as f,writeFile as p,unlink as m}from"node:fs/promises";import{homedir as C}from"node:os";import{dirname as c,join as o,resolve as d}from"node:path";const s=o(C(),".config","borgmcp"),a=o(s,"cubes.json"),y=o(s,"launch.json"),w=o(s,"codex-wake-targets.json"),F=o(s,"inboxes");function i(t=process.cwd()){let e=d(t);for(;;){if(E(o(e,".git")))return e;const r=c(e);if(r===e)return d(t);e=r}}const u=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function _(t,e){if(!u.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!u.test(e))throw new Error(`Invalid droneId: ${e}`);return o(F,t,`${e}.log`)}function N(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function g(){let t;try{t=await f(a,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}let e;try{e=JSON.parse(t)}catch{return null}return N(e)?e:null}async function j(t){await l(c(a),{recursive:!0}),await p(a,JSON.stringify(t,null,2)+`
|
|
2
|
+
`,{mode:384})}function I(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function h(){let t;try{t=await f(y,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return I(e)?e:null}catch{return null}}async function O(t){await l(c(y),{recursive:!0}),await p(y,JSON.stringify(t,null,2)+`
|
|
3
|
+
`,{mode:384})}function x(t,e){if(!u.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!u.test(e))throw new Error(`Invalid droneId: ${e}`);return`${t}:${e}`}function k(t){return t!==null&&typeof t=="object"&&typeof t.targets=="object"&&t.targets!==null&&!Array.isArray(t.targets)}async function b(){let t;try{t=await f(w,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return k(e)?e:null}catch{return null}}async function A(t){await l(c(w),{recursive:!0}),await p(w,JSON.stringify(t,null,2)+`
|
|
4
|
+
`,{mode:384})}async function $(){const t=await g();if(!t)return null;const e=i(),r=t.projects[e];return!r||typeof r.cubeId!="string"||!r.cubeId||typeof r.droneId!="string"||!r.droneId?null:r}async function v(t){const e=await g()??{projects:{}};e.projects[i()]=t,await j(e)}function P(t,e){const r=e.cube?.name??t.name,n=e.drone?.label??t.droneLabel;return r===t.name&&n===t.droneLabel?t:{...t,name:r,droneLabel:n}}async function D(){const t=await g();if(!t)return;const e=i();if(e in t.projects){if(delete t.projects[e],Object.keys(t.projects).length===0){try{await m(a)}catch(r){if(r?.code!=="ENOENT")throw r}return}await j(t)}}async function J(){const t=await h();if(!t)return null;const e=t.projects[i()];return e?.cli==="claude"||e?.cli==="codex"?e.cli:null}async function R(t){const e=await h()??{projects:{}};e.projects[i()]={cli:t},await O(e)}async function U(t,e,r){const n=await b()??{targets:{}};n.targets[x(t,e)]={...r,updatedAt:new Date().toISOString()},await A(n)}async function B(t,e){const r=await b();if(!r)return null;const n=r.targets[x(t,e)];return!n||typeof n.threadId!="string"||typeof n.socketPath!="string"?null:n}export{P as activeCubeWithFreshRegenIdentity,D as clearActiveCube,i as findProjectRoot,$ as getActiveCube,B as getCodexWakeTarget,J as getProjectCliPreference,_ as inboxPathForDrone,v as setActiveCube,U as setCodexWakeTarget,R as setProjectCliPreference};
|