@jabezpauls/agentbox 0.1.12 → 0.1.13
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/agentbox.mjs +73 -15
- package/package.json +1 -1
package/dist/agentbox.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// agentbox-cli v0.1.
|
|
2
|
+
// agentbox-cli v0.1.13
|
|
3
3
|
import { createRequire as __agentboxCreateRequire } from "node:module";
|
|
4
4
|
const require = __agentboxCreateRequire(import.meta.url);
|
|
5
5
|
var __create = Object.create;
|
|
@@ -3868,7 +3868,7 @@ function parseSize(raw2) {
|
|
|
3868
3868
|
}
|
|
3869
3869
|
|
|
3870
3870
|
// src/version.ts
|
|
3871
|
-
var VERSION = true ? "v0.1.
|
|
3871
|
+
var VERSION = true ? "v0.1.13" : "0.0.0-dev";
|
|
3872
3872
|
var STANDALONE = typeof __AGENTBOX_STANDALONE__ === "boolean" && __AGENTBOX_STANDALONE__;
|
|
3873
3873
|
var MIN_NODE_MAJOR = 20;
|
|
3874
3874
|
function nodeMajor(version = process.versions.node) {
|
|
@@ -6871,7 +6871,7 @@ function writePrivate2(file, contents, mode = 384) {
|
|
|
6871
6871
|
}
|
|
6872
6872
|
|
|
6873
6873
|
// src/node/control.ts
|
|
6874
|
-
var COMMANDS = /* @__PURE__ */ new Set(["status", "pause", "resume", "leave", "preview", "preview-list", "preview-stop"]);
|
|
6874
|
+
var COMMANDS = /* @__PURE__ */ new Set(["status", "pause", "resume", "leave", "preview", "preview-list", "preview-stop", "passphrase"]);
|
|
6875
6875
|
var PIPE_NAME = /^\\\\\.\\pipe\\agentbox-node-[0-9a-f]{32}$/;
|
|
6876
6876
|
function address(socketPath, platform) {
|
|
6877
6877
|
if (platform !== "win32") return socketPath;
|
|
@@ -7033,6 +7033,7 @@ function decodeControl(payload) {
|
|
|
7033
7033
|
import { createHash as createHash3, createHmac, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
7034
7034
|
var PROTOCOL_VERSION = 1;
|
|
7035
7035
|
var LINK_PATH = "/_gate/node";
|
|
7036
|
+
var MAX_STREAMS_PER_LINK = 256;
|
|
7036
7037
|
var DUPLICATE_BACKOFF_MS = 5 * 6e4;
|
|
7037
7038
|
var NODE_TOKEN_SHAPE = /^abn_[A-Za-z0-9_-]{43}$/;
|
|
7038
7039
|
function tokenDigest(token) {
|
|
@@ -7997,6 +7998,7 @@ var Roots = class {
|
|
|
7997
7998
|
isHidden;
|
|
7998
7999
|
isWriteDenied;
|
|
7999
8000
|
refusePath;
|
|
8001
|
+
holdsHidden;
|
|
8000
8002
|
constructor(opts) {
|
|
8001
8003
|
this.roots = opts.roots.map((r) => ({ id: r.id, path: path13.resolve(r.path), mode: r.mode, stateDir: path13.resolve(r.stateDir) }));
|
|
8002
8004
|
const first = this.roots[0];
|
|
@@ -8008,6 +8010,7 @@ var Roots = class {
|
|
|
8008
8010
|
this.isHidden = opts.isHidden;
|
|
8009
8011
|
this.isWriteDenied = opts.isWriteDenied;
|
|
8010
8012
|
this.refusePath = opts.refusePath;
|
|
8013
|
+
this.holdsHidden = opts.holdsHidden;
|
|
8011
8014
|
}
|
|
8012
8015
|
byId(id) {
|
|
8013
8016
|
const root = this.roots.find((r) => r.id === id);
|
|
@@ -8133,6 +8136,10 @@ var Roots = class {
|
|
|
8133
8136
|
}
|
|
8134
8137
|
}
|
|
8135
8138
|
}
|
|
8139
|
+
/** Refuse to copy an entry that holds a hidden path: the copy would show it. */
|
|
8140
|
+
assertCopyable(ref) {
|
|
8141
|
+
if (this.holdsHidden?.(ref.fs)) throw new FilesError(403, `permission denied: ${ref.abs}`, "denied");
|
|
8142
|
+
}
|
|
8136
8143
|
/** Resolve through every symlink; the destination must stay in the root. */
|
|
8137
8144
|
async target(input) {
|
|
8138
8145
|
const loc = this.locate(input);
|
|
@@ -8808,6 +8815,7 @@ var FileOps = class {
|
|
|
8808
8815
|
async copy(body) {
|
|
8809
8816
|
const src = await this.roots.entry(body.from);
|
|
8810
8817
|
await this.roots.assertMovable(src);
|
|
8818
|
+
this.roots.assertCopyable(src);
|
|
8811
8819
|
const dest = await this.roots.creatable(body.to);
|
|
8812
8820
|
this.roots.assertMutable(dest);
|
|
8813
8821
|
if (dest.abs === src.abs) throw new FilesError(409, "a copy needs a different name", "exists");
|
|
@@ -8872,6 +8880,7 @@ var Trash = class {
|
|
|
8872
8880
|
}
|
|
8873
8881
|
throw err;
|
|
8874
8882
|
}
|
|
8883
|
+
this.roots.assertMutable(ref);
|
|
8875
8884
|
result.trashed.push(await this.put(ref.root, ref.abs, ref.fs));
|
|
8876
8885
|
}
|
|
8877
8886
|
return result;
|
|
@@ -9293,6 +9302,7 @@ var FilesService = class {
|
|
|
9293
9302
|
...opts.caseInsensitive === void 0 ? {} : { caseInsensitive: opts.caseInsensitive },
|
|
9294
9303
|
...opts.isHidden === void 0 ? {} : { isHidden: opts.isHidden },
|
|
9295
9304
|
...opts.isWriteDenied === void 0 ? {} : { isWriteDenied: opts.isWriteDenied },
|
|
9305
|
+
...opts.holdsHidden === void 0 ? {} : { holdsHidden: opts.holdsHidden },
|
|
9296
9306
|
...opts.refusePath === void 0 ? {} : { refusePath: opts.refusePath }
|
|
9297
9307
|
});
|
|
9298
9308
|
this.git = new GitStatusCache(opts.gitTtlMs === void 0 ? {} : { ttlMs: opts.gitTtlMs });
|
|
@@ -9887,6 +9897,7 @@ function refuseWindowsPath(input) {
|
|
|
9887
9897
|
for (const part of rest.split("\\")) {
|
|
9888
9898
|
if (part === "" || part === "." || part === "..") continue;
|
|
9889
9899
|
if (/[. ]$/.test(part)) return "a name ending in a dot or a space is refused";
|
|
9900
|
+
if (/~\d/.test(part)) return "short (8.3) names, with a ~ and a digit, are refused";
|
|
9890
9901
|
if (/^(con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³]|conin\$|conout\$)(\..*)?$/i.test(part)) return "DOS device names are refused";
|
|
9891
9902
|
}
|
|
9892
9903
|
return null;
|
|
@@ -9922,11 +9933,25 @@ function pathRules(o) {
|
|
|
9922
9933
|
};
|
|
9923
9934
|
const gitRuns = (p) => {
|
|
9924
9935
|
const parts = fold(p).split(P.sep);
|
|
9925
|
-
for (let i = 0; i
|
|
9926
|
-
if (parts[i] === ".git"
|
|
9936
|
+
for (let i = 0; i < parts.length; i++) {
|
|
9937
|
+
if (parts[i] === ".git") return true;
|
|
9938
|
+
if (parts[i]?.endsWith(".git") && (parts[i + 1] === "hooks" || parts[i + 1] === "config")) return true;
|
|
9927
9939
|
}
|
|
9928
9940
|
return false;
|
|
9929
9941
|
};
|
|
9942
|
+
const homeF = fold(o.home);
|
|
9943
|
+
const homeBase = homeF.endsWith(P.sep) ? homeF : homeF + P.sep;
|
|
9944
|
+
const inHomeDot = (p) => {
|
|
9945
|
+
const f = fold(p);
|
|
9946
|
+
if (!f.startsWith(homeBase)) return false;
|
|
9947
|
+
const first = f.slice(homeBase.length).split(P.sep)[0] ?? "";
|
|
9948
|
+
return first.startsWith(".") || platform === "win32" && first === "appdata";
|
|
9949
|
+
};
|
|
9950
|
+
const holds = (p, list) => {
|
|
9951
|
+
const f = fold(p);
|
|
9952
|
+
const base = f.endsWith(P.sep) ? f : f + P.sep;
|
|
9953
|
+
return list.some((e) => e.startsWith(base));
|
|
9954
|
+
};
|
|
9930
9955
|
const remote = (p) => platform === "win32" && (p.startsWith("\\\\") || p.startsWith("//"));
|
|
9931
9956
|
return {
|
|
9932
9957
|
platform,
|
|
@@ -9934,7 +9959,8 @@ function pathRules(o) {
|
|
|
9934
9959
|
hidden,
|
|
9935
9960
|
writeDenied,
|
|
9936
9961
|
isHidden: (p) => remote(p) || under(p, hiddenF),
|
|
9937
|
-
isWriteDenied: (p) => remote(p) || under(p, deniedF) || gitRuns(p),
|
|
9962
|
+
isWriteDenied: (p) => remote(p) || under(p, deniedF) || gitRuns(p) || inHomeDot(p) || holds(p, deniedF) || holds(p, hiddenF),
|
|
9963
|
+
holdsHidden: (p) => holds(p, hiddenF),
|
|
9938
9964
|
refusePath: (input) => platform === "win32" ? refuseWindowsPath(input) : null
|
|
9939
9965
|
};
|
|
9940
9966
|
}
|
|
@@ -9956,7 +9982,7 @@ function windowsDocuments(env = process.env) {
|
|
|
9956
9982
|
const out = [];
|
|
9957
9983
|
for (const key of ["User Shell Folders", "Shell Folders"]) {
|
|
9958
9984
|
try {
|
|
9959
|
-
const text = execFileSync("reg.exe", ["query", `HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\${key}`, "/v", "Personal"], {
|
|
9985
|
+
const text = execFileSync(path22.win32.join(env.SystemRoot && path22.win32.isAbsolute(env.SystemRoot) ? env.SystemRoot : "C:\\Windows", "System32", "reg.exe"), ["query", `HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\${key}`, "/v", "Personal"], {
|
|
9960
9986
|
encoding: "utf8",
|
|
9961
9987
|
timeout: 1e4,
|
|
9962
9988
|
windowsHide: true,
|
|
@@ -9988,6 +10014,7 @@ function filesFor(policy, paths2, rules) {
|
|
|
9988
10014
|
caseInsensitive: rules.caseInsensitive,
|
|
9989
10015
|
isHidden: rules.isHidden,
|
|
9990
10016
|
isWriteDenied: rules.isWriteDenied,
|
|
10017
|
+
holdsHidden: rules.holdsHidden,
|
|
9991
10018
|
refusePath: rules.refusePath,
|
|
9992
10019
|
...rules.platform === "win32" ? { apiPaths: { toHost: (p) => toHostPath(p, "win32"), toApi: (p) => toApiPath(p, "win32") } } : {}
|
|
9993
10020
|
});
|
|
@@ -10218,6 +10245,8 @@ var NodeLink = class extends EventEmitter {
|
|
|
10218
10245
|
stateNow = "stopped";
|
|
10219
10246
|
running = false;
|
|
10220
10247
|
paused = false;
|
|
10248
|
+
/** It said `leave`: only then is a close saying "left" taken for one (a gate saying so unasked is a dropped link). */
|
|
10249
|
+
leaving = false;
|
|
10221
10250
|
failures = 0;
|
|
10222
10251
|
failingSince = null;
|
|
10223
10252
|
retryTimer = null;
|
|
@@ -10315,6 +10344,7 @@ var NodeLink = class extends EventEmitter {
|
|
|
10315
10344
|
clearTimeout(timer);
|
|
10316
10345
|
resolve(code2 === 4005);
|
|
10317
10346
|
});
|
|
10347
|
+
this.leaving = true;
|
|
10318
10348
|
ws.send(encodeControl({ type: "leave" }));
|
|
10319
10349
|
});
|
|
10320
10350
|
}
|
|
@@ -10362,7 +10392,7 @@ var NodeLink = class extends EventEmitter {
|
|
|
10362
10392
|
if (!this.running || this.paused || this.ws) return;
|
|
10363
10393
|
this.set("connecting");
|
|
10364
10394
|
const url = `${this.o.box.replace(/^http/, "ws")}${LINK_PATH}`;
|
|
10365
|
-
const ws = new import_websocket.default(url, { headers: { authorization: `Bearer ${this.o.token}` }, perMessageDeflate: false, handshakeTimeout: 2e4, followRedirects: false });
|
|
10395
|
+
const ws = new import_websocket.default(url, { headers: { authorization: `Bearer ${this.o.token}` }, perMessageDeflate: false, handshakeTimeout: 2e4, followRedirects: false, maxPayload: STREAM_WINDOW + 16 });
|
|
10366
10396
|
this.ws = ws;
|
|
10367
10397
|
let welcomed = false;
|
|
10368
10398
|
let stopKeepAlive = () => {
|
|
@@ -10445,7 +10475,7 @@ var NodeLink = class extends EventEmitter {
|
|
|
10445
10475
|
return;
|
|
10446
10476
|
}
|
|
10447
10477
|
if (frame.type === FrameType.open) {
|
|
10448
|
-
if (!welcomed || this.listenerCount("stream") === 0 || streams.has(frame.stream)) {
|
|
10478
|
+
if (!welcomed || this.listenerCount("stream") === 0 || streams.has(frame.stream) || streams.size >= MAX_STREAMS_PER_LINK) {
|
|
10449
10479
|
ws.send(encodeFrame(FrameType.close, frame.stream));
|
|
10450
10480
|
return;
|
|
10451
10481
|
}
|
|
@@ -10460,7 +10490,7 @@ var NodeLink = class extends EventEmitter {
|
|
|
10460
10490
|
if (this.ws !== ws) return;
|
|
10461
10491
|
this.ws = null;
|
|
10462
10492
|
if (!this.running || this.stateNow === "revoked") return;
|
|
10463
|
-
if (code2 === 4005) {
|
|
10493
|
+
if (code2 === 4005 && this.leaving) {
|
|
10464
10494
|
this.stop("left");
|
|
10465
10495
|
return;
|
|
10466
10496
|
}
|
|
@@ -10511,16 +10541,29 @@ function presenceAnswer(key, challenge, sessionId, machineId) {
|
|
|
10511
10541
|
var PresenceGate = class {
|
|
10512
10542
|
constructor(o) {
|
|
10513
10543
|
this.o = o;
|
|
10544
|
+
this.presence = o.presence;
|
|
10514
10545
|
this.key = Buffer.from(o.presence.key, "base64url");
|
|
10515
10546
|
this.now = o.now ?? Date.now;
|
|
10516
10547
|
}
|
|
10517
10548
|
o;
|
|
10518
10549
|
key;
|
|
10550
|
+
presence;
|
|
10519
10551
|
challenges = /* @__PURE__ */ new Map();
|
|
10520
10552
|
grants = /* @__PURE__ */ new Map();
|
|
10521
10553
|
failures = [];
|
|
10522
10554
|
lockedUntil = 0;
|
|
10523
10555
|
now;
|
|
10556
|
+
/**
|
|
10557
|
+
* A new passphrase (`agentbox node passphrase`), taken at once: every
|
|
10558
|
+
* challenge and every grant the old one earned ends, so channels opened on
|
|
10559
|
+
* them close at the next check.
|
|
10560
|
+
*/
|
|
10561
|
+
rekey(presence) {
|
|
10562
|
+
this.presence = presence;
|
|
10563
|
+
this.key = Buffer.from(presence.key, "base64url");
|
|
10564
|
+
this.challenges.clear();
|
|
10565
|
+
this.grants.clear();
|
|
10566
|
+
}
|
|
10524
10567
|
/** Locked until this time, or `null`. */
|
|
10525
10568
|
locked() {
|
|
10526
10569
|
return this.lockedUntil > this.now() ? this.lockedUntil : null;
|
|
@@ -10535,7 +10578,7 @@ var PresenceGate = class {
|
|
|
10535
10578
|
const challenge = randomBytes10(32).toString("base64url");
|
|
10536
10579
|
const expiresAt = t2 + CHALLENGE_MS;
|
|
10537
10580
|
this.challenges.set(challenge, { session, expiresAt });
|
|
10538
|
-
return { challenge, salt: this.
|
|
10581
|
+
return { challenge, salt: this.presence.salt, iterations: this.presence.iterations, expiresAt };
|
|
10539
10582
|
}
|
|
10540
10583
|
answer(session, challenge, answer) {
|
|
10541
10584
|
if (session === null) return { ok: false, error: "no_session" };
|
|
@@ -11775,6 +11818,10 @@ var CONSOLE_EXE = "agentbox.exe";
|
|
|
11775
11818
|
var LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
11776
11819
|
var LOG_FILES = 3;
|
|
11777
11820
|
var RESTART_EXIT = 75;
|
|
11821
|
+
function regExe(env = process.env) {
|
|
11822
|
+
const root = env.SystemRoot && path27.win32.isAbsolute(env.SystemRoot) ? env.SystemRoot : "C:\\Windows";
|
|
11823
|
+
return path27.win32.join(root, "System32", "reg.exe");
|
|
11824
|
+
}
|
|
11778
11825
|
function standalone(execPath, script) {
|
|
11779
11826
|
return script === void 0 || path27.resolve(script) === path27.resolve(execPath);
|
|
11780
11827
|
}
|
|
@@ -11800,7 +11847,7 @@ function runCommandLine(execPath = process.execPath, script = process.argv[1]) {
|
|
|
11800
11847
|
async function installService2(opts) {
|
|
11801
11848
|
const run4 = opts.run ?? runCommand;
|
|
11802
11849
|
const entry = runEntry(opts.gui);
|
|
11803
|
-
const r = await run4(
|
|
11850
|
+
const r = await run4(regExe(), ["add", RUN_KEY, "/v", RUN_VALUE, "/t", "REG_SZ", "/d", entry, "/f"]);
|
|
11804
11851
|
if (r.code !== 0) throw new Error(`reg add ${RUN_KEY} failed: ${(r.stderr || r.stdout).trim() || `exit ${r.code}`}`);
|
|
11805
11852
|
if (opts.start !== false) {
|
|
11806
11853
|
const child = spawn9(opts.gui, ["node", "supervise"], { detached: true, stdio: "ignore", windowsHide: true });
|
|
@@ -11811,14 +11858,14 @@ async function installService2(opts) {
|
|
|
11811
11858
|
return { entry };
|
|
11812
11859
|
}
|
|
11813
11860
|
async function readRunEntry(run4 = runCommand) {
|
|
11814
|
-
const r = await run4(
|
|
11861
|
+
const r = await run4(regExe(), ["query", RUN_KEY, "/v", RUN_VALUE]);
|
|
11815
11862
|
if (r.code !== 0) return null;
|
|
11816
11863
|
return new RegExp(`${RUN_VALUE}\\s+REG_SZ\\s+(.*)`).exec(r.stdout)?.[1]?.trim() ?? null;
|
|
11817
11864
|
}
|
|
11818
11865
|
async function removeService2(opts) {
|
|
11819
11866
|
const run4 = opts.run ?? runCommand;
|
|
11820
11867
|
const had = await readRunEntry(run4) !== null;
|
|
11821
|
-
if (had) await run4(
|
|
11868
|
+
if (had) await run4(regExe(), ["delete", RUN_KEY, "/v", RUN_VALUE, "/f"]);
|
|
11822
11869
|
const pid = supervisorPid(opts.paths);
|
|
11823
11870
|
if (pid !== null && pid !== process.pid) {
|
|
11824
11871
|
try {
|
|
@@ -12509,6 +12556,7 @@ async function runNode(o) {
|
|
|
12509
12556
|
if (await callControl(o.paths.control, o.paths.controlSecret, "status", 2e3) !== null) {
|
|
12510
12557
|
throw new CliError("a node is already running on this machine (see `agentbox node status`)");
|
|
12511
12558
|
}
|
|
12559
|
+
process.chdir(o.paths.dir);
|
|
12512
12560
|
let policy = loadPolicy(o.paths.policy, o.home);
|
|
12513
12561
|
const claude = claudePaths(o.paths);
|
|
12514
12562
|
prepareClaude(claude, { home: o.home, machine: creds.name });
|
|
@@ -12682,6 +12730,14 @@ async function runNode(o) {
|
|
|
12682
12730
|
setPaused(o.paths.policy, false);
|
|
12683
12731
|
link.resume();
|
|
12684
12732
|
return { state: link.state };
|
|
12733
|
+
case "passphrase": {
|
|
12734
|
+
const fresh = readCredentials(o.paths.credentials);
|
|
12735
|
+
if (!fresh) throw new Error("credentials.json cannot be read");
|
|
12736
|
+
presence.rekey(fresh.presence);
|
|
12737
|
+
const closed = server.enforce();
|
|
12738
|
+
o.log(`presence: the machines passphrase changed on this machine; every grant ended${closed ? ` (${closed} channel(s) closed)` : ""}`);
|
|
12739
|
+
return {};
|
|
12740
|
+
}
|
|
12685
12741
|
case "leave": {
|
|
12686
12742
|
const told = await link.leave();
|
|
12687
12743
|
setImmediate(() => finish(EXIT.OK));
|
|
@@ -12691,7 +12747,7 @@ async function runNode(o) {
|
|
|
12691
12747
|
});
|
|
12692
12748
|
link.on("revoked", () => {
|
|
12693
12749
|
fs22.rmSync(o.paths.secrets, { recursive: true, force: true });
|
|
12694
|
-
writeCredentials(o.paths.credentials, { ...creds, revokedAt: now() });
|
|
12750
|
+
writeCredentials(o.paths.credentials, { ...readCredentials(o.paths.credentials) ?? creds, revokedAt: now() });
|
|
12695
12751
|
o.log(`${creds.box} revoked this machine. Its shared secrets are wiped; everything else is kept. Re-join it to use it again.`);
|
|
12696
12752
|
finish(EXIT.OK);
|
|
12697
12753
|
});
|
|
@@ -13059,6 +13115,8 @@ var passphrase = command({
|
|
|
13059
13115
|
} finally {
|
|
13060
13116
|
prompt.close();
|
|
13061
13117
|
}
|
|
13118
|
+
const live = await callControl(np.control, np.controlSecret, "passphrase", 5e3);
|
|
13119
|
+
if (live !== null && live.ok !== true) throw new CliError(`the running node did not take it (${String(live.error ?? "refused")}); restart it`);
|
|
13062
13120
|
ctx.err("This machine now proves presence with the new passphrase.\n");
|
|
13063
13121
|
}
|
|
13064
13122
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jabezpauls/agentbox",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.13",
|
|
5
5
|
"description": "agentbox from your own machine: sign in, attach, move files, mount the workspace.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/jabezpauls/agentbox/blob/main/docs/cli.md",
|