@lesscap/baton-cli 0.9.4 → 0.9.5
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/baton.mjs +132 -11
- package/package.json +12 -11
- package/LICENSE +0 -21
package/dist/baton.mjs
CHANGED
|
@@ -2286,7 +2286,7 @@ var require_websocket = __commonJS({
|
|
|
2286
2286
|
var net = __require("net");
|
|
2287
2287
|
var tls = __require("tls");
|
|
2288
2288
|
var { randomBytes, createHash: createHash2 } = __require("crypto");
|
|
2289
|
-
var { Duplex, Readable:
|
|
2289
|
+
var { Duplex, Readable: Readable4 } = __require("stream");
|
|
2290
2290
|
var { URL: URL2 } = __require("url");
|
|
2291
2291
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
2292
2292
|
var Receiver2 = require_receiver();
|
|
@@ -5437,7 +5437,9 @@ var rawRequest = async (url, init2) => {
|
|
|
5437
5437
|
const res = await fetch(url, {
|
|
5438
5438
|
method: init2.method,
|
|
5439
5439
|
headers: { ...authHeaders, ...init2.headers ?? {} },
|
|
5440
|
-
...init2.body !== void 0 ? { body: init2.body } : {}
|
|
5440
|
+
...init2.body !== void 0 ? { body: init2.body } : {},
|
|
5441
|
+
...init2.body instanceof ReadableStream ? { duplex: "half" } : {},
|
|
5442
|
+
...init2.signal ? { signal: init2.signal } : {}
|
|
5441
5443
|
});
|
|
5442
5444
|
if (!res.ok)
|
|
5443
5445
|
throw new HttpError(res.status, `${init2.method} ${url} \u2192 ${res.status}: ${await res.text()}`);
|
|
@@ -5493,9 +5495,19 @@ var primeLogin = (baseUrl) => {
|
|
|
5493
5495
|
};
|
|
5494
5496
|
|
|
5495
5497
|
// src/client/files.ts
|
|
5496
|
-
var filesClient = (baseUrl) =>
|
|
5497
|
-
|
|
5498
|
-
|
|
5498
|
+
var filesClient = (baseUrl) => {
|
|
5499
|
+
const downloadUrl = (requestId) => `${baseUrl}/workers/me/files/downloads/${encodeURIComponent(requestId)}`;
|
|
5500
|
+
return {
|
|
5501
|
+
report: (result) => request(`${baseUrl}/workers/me/files/results`, { method: "PUT", body: result }),
|
|
5502
|
+
uploadDownload: (requestId, size, body, signal) => rawRequest(downloadUrl(requestId), {
|
|
5503
|
+
method: "PUT",
|
|
5504
|
+
body,
|
|
5505
|
+
headers: { "content-type": "application/octet-stream", "content-length": String(size) },
|
|
5506
|
+
...signal ? { signal } : {}
|
|
5507
|
+
}),
|
|
5508
|
+
rejectDownload: (requestId, failure) => request(downloadUrl(requestId), { method: "PUT", body: failure })
|
|
5509
|
+
};
|
|
5510
|
+
};
|
|
5499
5511
|
|
|
5500
5512
|
// src/client/loops.ts
|
|
5501
5513
|
var loopsClient = (baseUrl) => {
|
|
@@ -6776,6 +6788,8 @@ var mediaTypeIn = (types3) => (path) => {
|
|
|
6776
6788
|
};
|
|
6777
6789
|
var imageMediaType = mediaTypeIn(IMAGE_MEDIA_TYPES);
|
|
6778
6790
|
var rawMediaType = mediaTypeIn(RAW_MEDIA_TYPES);
|
|
6791
|
+
var FILE_DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024;
|
|
6792
|
+
var FILE_DOWNLOAD_TIMEOUT_MS = 3e5;
|
|
6779
6793
|
var normalizeFilePath = (value, allowRoot = false) => {
|
|
6780
6794
|
if (value === "") return allowRoot ? "" : null;
|
|
6781
6795
|
if (value.includes("\0") || value.includes("\\") || value.startsWith("/")) return null;
|
|
@@ -10131,6 +10145,10 @@ var discoverBranches = async (baseDir) => {
|
|
|
10131
10145
|
}).sort((a, b) => rank(a) - rank(b) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
10132
10146
|
};
|
|
10133
10147
|
|
|
10148
|
+
// src/worker/files/commit.ts
|
|
10149
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
10150
|
+
import { Readable as Readable2 } from "node:stream";
|
|
10151
|
+
|
|
10134
10152
|
// src/worker/files/common.ts
|
|
10135
10153
|
var FileError = class extends Error {
|
|
10136
10154
|
constructor(code, message) {
|
|
@@ -10140,6 +10158,10 @@ var FileError = class extends Error {
|
|
|
10140
10158
|
code;
|
|
10141
10159
|
};
|
|
10142
10160
|
var notFound = (path) => new FileError("file_not_found", `not found: ${path}`);
|
|
10161
|
+
var checkDownloadSize = (path, size) => {
|
|
10162
|
+
if (size > FILE_DOWNLOAD_MAX_BYTES)
|
|
10163
|
+
throw new FileError("file_too_large", `too large to download (${size} bytes): ${path}`);
|
|
10164
|
+
};
|
|
10143
10165
|
var checkedPath = (path, allowRoot) => {
|
|
10144
10166
|
const rel = normalizeFilePath(path, allowRoot);
|
|
10145
10167
|
if (rel === null) throw new FileError("invalid_request", `invalid path: ${path}`);
|
|
@@ -10282,11 +10304,38 @@ var readRawCommitFile = async (repo, commit, path) => {
|
|
|
10282
10304
|
const bytes = await runGit(repo, ["cat-file", "blob", entry.oid], FILE_RAW_MAX_BYTES + 1);
|
|
10283
10305
|
return rawPayload(rel, mediaType, bytes);
|
|
10284
10306
|
};
|
|
10307
|
+
var openCommitDownload = async (repo, commit, path, signal) => {
|
|
10308
|
+
const rel = checkedPath(path, false);
|
|
10309
|
+
const entry = await regularEntry(repo, commit, rel);
|
|
10310
|
+
checkDownloadSize(rel, entry.size);
|
|
10311
|
+
const child = spawn2("git", ["--literal-pathspecs", "-C", repo, "cat-file", "blob", entry.oid], {
|
|
10312
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
10313
|
+
signal,
|
|
10314
|
+
env: { ...process.env, LC_ALL: "C", GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" }
|
|
10315
|
+
});
|
|
10316
|
+
const exited = new Promise((resolve4) => {
|
|
10317
|
+
child.once("error", resolve4);
|
|
10318
|
+
child.once(
|
|
10319
|
+
"close",
|
|
10320
|
+
(code, killed) => resolve4(code === 0 ? null : new Error(`git cat-file exited with ${code ?? killed}`))
|
|
10321
|
+
);
|
|
10322
|
+
});
|
|
10323
|
+
const body = Readable2.toWeb(child.stdout).pipeThrough(
|
|
10324
|
+
new TransformStream({
|
|
10325
|
+
async flush() {
|
|
10326
|
+
const failure = await exited;
|
|
10327
|
+
if (failure) throw failure;
|
|
10328
|
+
}
|
|
10329
|
+
})
|
|
10330
|
+
);
|
|
10331
|
+
return { size: entry.size, body, close: () => child.kill() };
|
|
10332
|
+
};
|
|
10285
10333
|
|
|
10286
10334
|
// src/worker/files/disk.ts
|
|
10287
10335
|
import { constants as constants2 } from "node:fs";
|
|
10288
10336
|
import { open as open2, readdir, realpath as realpath3, stat as stat3 } from "node:fs/promises";
|
|
10289
10337
|
import { isAbsolute as isAbsolute4, join as join15, relative, sep } from "node:path";
|
|
10338
|
+
import { Readable as Readable3 } from "node:stream";
|
|
10290
10339
|
var isMissing = (error) => ["ENOENT", "ENOTDIR", "ELOOP"].includes(error.code ?? "");
|
|
10291
10340
|
var childPath = (parent, name) => parent ? `${parent}/${name}` : name;
|
|
10292
10341
|
var inside = (root, real) => {
|
|
@@ -10407,6 +10456,72 @@ var readRawFile = async (root, path) => {
|
|
|
10407
10456
|
if ("oversize" in read) throw new FileError("unsupported", `too large to serve raw: ${file.path}`);
|
|
10408
10457
|
return rawPayload(file.path, mediaType, read.bytes);
|
|
10409
10458
|
};
|
|
10459
|
+
var openDiskDownload = async (root, path, signal) => {
|
|
10460
|
+
const file = await resolveInside(root, path, false);
|
|
10461
|
+
const handle = await open2(file.real, constants2.O_RDONLY | constants2.O_NONBLOCK);
|
|
10462
|
+
try {
|
|
10463
|
+
const opened = await handle.stat();
|
|
10464
|
+
if (!opened.isFile()) throw notFound(file.path);
|
|
10465
|
+
checkDownloadSize(file.path, opened.size);
|
|
10466
|
+
const stream = handle.createReadStream({ signal });
|
|
10467
|
+
return {
|
|
10468
|
+
size: opened.size,
|
|
10469
|
+
body: Readable3.toWeb(stream),
|
|
10470
|
+
close: () => stream.destroy()
|
|
10471
|
+
};
|
|
10472
|
+
} catch (error) {
|
|
10473
|
+
await handle.close();
|
|
10474
|
+
throw error;
|
|
10475
|
+
}
|
|
10476
|
+
};
|
|
10477
|
+
|
|
10478
|
+
// src/worker/files/download.ts
|
|
10479
|
+
var exactLength = (size) => {
|
|
10480
|
+
let seen = 0;
|
|
10481
|
+
return new TransformStream({
|
|
10482
|
+
transform(chunk, controller) {
|
|
10483
|
+
seen += chunk.byteLength;
|
|
10484
|
+
if (seen > size) throw new Error(`file grew past its ${size} bytes while downloading`);
|
|
10485
|
+
controller.enqueue(chunk);
|
|
10486
|
+
},
|
|
10487
|
+
flush() {
|
|
10488
|
+
if (seen !== size) throw new Error(`file ended at ${seen} of its ${size} bytes`);
|
|
10489
|
+
}
|
|
10490
|
+
});
|
|
10491
|
+
};
|
|
10492
|
+
var openSource = async (baseDir, branch, path, signal) => {
|
|
10493
|
+
if (branch.worktree !== null) return openDiskDownload(branch.worktree, path, signal);
|
|
10494
|
+
if (branch.commit === null)
|
|
10495
|
+
throw new FileError("branch_not_found", `branch ${branch.name} is empty`);
|
|
10496
|
+
return openCommitDownload(baseDir, branch.commit, path, signal);
|
|
10497
|
+
};
|
|
10498
|
+
var openFileDownload = async (baseDir, query3, signal) => {
|
|
10499
|
+
const branches = await discoverBranches(baseDir);
|
|
10500
|
+
const branch = branches.find((candidate) => candidate.name === query3.branch);
|
|
10501
|
+
if (!branch) throw new FileError("branch_not_found", `branch ${query3.branch} does not exist`);
|
|
10502
|
+
const source = await openSource(baseDir, branch, query3.path, signal);
|
|
10503
|
+
return { ...source, body: source.body.pipeThrough(exactLength(source.size)) };
|
|
10504
|
+
};
|
|
10505
|
+
var failureOf = (error) => {
|
|
10506
|
+
if (error instanceof FileError) return { ok: false, code: error.code, message: error.message };
|
|
10507
|
+
console.error("[files] opening a download failed", error);
|
|
10508
|
+
return { ok: false, code: "unavailable", message: "the worker could not read the file" };
|
|
10509
|
+
};
|
|
10510
|
+
var sendFileDownload = async (baseDir, command, client, signal) => {
|
|
10511
|
+
const bounded = AbortSignal.any([signal, AbortSignal.timeout(FILE_DOWNLOAD_TIMEOUT_MS)]);
|
|
10512
|
+
let source;
|
|
10513
|
+
try {
|
|
10514
|
+
source = await openFileDownload(baseDir, command.query, bounded);
|
|
10515
|
+
} catch (error) {
|
|
10516
|
+
await client.rejectDownload(command.requestId, failureOf(error));
|
|
10517
|
+
return;
|
|
10518
|
+
}
|
|
10519
|
+
try {
|
|
10520
|
+
await client.uploadDownload(command.requestId, source.size, source.body, bounded);
|
|
10521
|
+
} finally {
|
|
10522
|
+
source.close();
|
|
10523
|
+
}
|
|
10524
|
+
};
|
|
10410
10525
|
|
|
10411
10526
|
// src/worker/files/handler.ts
|
|
10412
10527
|
var DEFAULT_CONCURRENCY = 4;
|
|
@@ -10534,7 +10649,7 @@ import { stat as stat4 } from "node:fs/promises";
|
|
|
10534
10649
|
import { join as join17 } from "node:path";
|
|
10535
10650
|
|
|
10536
10651
|
// src/worker/service-process.ts
|
|
10537
|
-
import { spawn as
|
|
10652
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
10538
10653
|
import { closeSync, existsSync as existsSync6, openSync } from "node:fs";
|
|
10539
10654
|
import { dirname as dirname4, join as join16 } from "node:path";
|
|
10540
10655
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -10554,7 +10669,7 @@ var startServiceProcess = (input) => {
|
|
|
10554
10669
|
const logFd = openSync(input.logPath, "a");
|
|
10555
10670
|
let wrapper;
|
|
10556
10671
|
try {
|
|
10557
|
-
wrapper =
|
|
10672
|
+
wrapper = spawn3(process.execPath, [binPath(), "service", "child"], {
|
|
10558
10673
|
detached: false,
|
|
10559
10674
|
stdio: ["ignore", logFd, logFd, "ipc"],
|
|
10560
10675
|
env: process.env
|
|
@@ -10704,7 +10819,7 @@ var createServiceSupervisor = (deps) => {
|
|
|
10704
10819
|
};
|
|
10705
10820
|
|
|
10706
10821
|
// src/worker/session-supervisor.ts
|
|
10707
|
-
import { spawn as
|
|
10822
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
10708
10823
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10709
10824
|
import { existsSync as existsSync7 } from "node:fs";
|
|
10710
10825
|
import { mkdir as mkdir6, rm as rm3, stat as stat5 } from "node:fs/promises";
|
|
@@ -11009,7 +11124,7 @@ var createSessionSupervisor = (deps) => {
|
|
|
11009
11124
|
if (children.has(sessionId)) return;
|
|
11010
11125
|
await stopRecord(sessionId);
|
|
11011
11126
|
const runnerToken = randomUUID2();
|
|
11012
|
-
const child =
|
|
11127
|
+
const child = spawn4(
|
|
11013
11128
|
process.execPath,
|
|
11014
11129
|
[binPath2(), "session", "run", String(sessionId), "--runner-token", runnerToken],
|
|
11015
11130
|
{
|
|
@@ -11201,7 +11316,7 @@ var createSessionSupervisor = (deps) => {
|
|
|
11201
11316
|
|
|
11202
11317
|
// src/worker/terminal-manager.ts
|
|
11203
11318
|
import { statSync as statSync2 } from "node:fs";
|
|
11204
|
-
import { spawn as
|
|
11319
|
+
import { spawn as spawn5 } from "node-pty";
|
|
11205
11320
|
|
|
11206
11321
|
// ../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/wrapper.mjs
|
|
11207
11322
|
var import_stream2 = __toESM(require_stream(), 1);
|
|
@@ -11266,7 +11381,7 @@ var createTerminalManager = (deps) => {
|
|
|
11266
11381
|
return;
|
|
11267
11382
|
}
|
|
11268
11383
|
const command = ptyCommand(cfg.agentKind, agentSessionId);
|
|
11269
|
-
const term =
|
|
11384
|
+
const term = spawn5(command.file, command.args, {
|
|
11270
11385
|
name: "xterm-color",
|
|
11271
11386
|
cols: 80,
|
|
11272
11387
|
rows: 24,
|
|
@@ -11356,6 +11471,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
11356
11471
|
hasChild: (id) => supervisor.has(id)
|
|
11357
11472
|
});
|
|
11358
11473
|
const files = createFileQueryRunner(cwd);
|
|
11474
|
+
const downloads = new AbortController();
|
|
11359
11475
|
const services = createServiceSupervisor({
|
|
11360
11476
|
sendReport: (report) => client.services.report(report),
|
|
11361
11477
|
cfg,
|
|
@@ -11413,6 +11529,10 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
11413
11529
|
void services.stop(cmd.requestId, cmd.name).catch((err) => log(`service stop failed: ${String(err)}`));
|
|
11414
11530
|
else if (cmd.cmd === "files.query")
|
|
11415
11531
|
void files(cmd.query).then((reply) => client.files.report({ requestId: cmd.requestId, ...reply })).catch((err) => log(`files query failed: ${String(err)}`));
|
|
11532
|
+
else if (cmd.cmd === "files.download")
|
|
11533
|
+
void sendFileDownload(cwd, cmd, client.files, downloads.signal).catch(
|
|
11534
|
+
(err) => log(`files download failed: ${String(err)}`)
|
|
11535
|
+
);
|
|
11416
11536
|
} catch (error) {
|
|
11417
11537
|
log(`invalid command: ${String(error)}`);
|
|
11418
11538
|
}
|
|
@@ -11426,6 +11546,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
11426
11546
|
signal.addEventListener("abort", () => resolve4(), { once: true });
|
|
11427
11547
|
});
|
|
11428
11548
|
es.close();
|
|
11549
|
+
downloads.abort();
|
|
11429
11550
|
liveness.stop();
|
|
11430
11551
|
services.killAll();
|
|
11431
11552
|
await supervisor.killAll();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lesscap/baton-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -15,15 +15,23 @@
|
|
|
15
15
|
"scripts",
|
|
16
16
|
"skills"
|
|
17
17
|
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"baton": "tsx src/index.ts",
|
|
20
|
+
"build": "node build.mjs && chmod +x dist/baton.mjs",
|
|
21
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
22
|
+
"prepublishOnly": "pnpm build",
|
|
23
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
24
|
+
"test": "tsx --test 'src/**/*.test.ts'"
|
|
25
|
+
},
|
|
18
26
|
"devDependencies": {
|
|
27
|
+
"@baton/shared": "workspace:*",
|
|
19
28
|
"@types/node": "^24.19.2",
|
|
20
29
|
"@types/ws": "^8.18.1",
|
|
21
30
|
"citty": "^0.2.2",
|
|
22
31
|
"esbuild": "^0.28.2",
|
|
23
32
|
"eventsource": "^4.1.0",
|
|
24
33
|
"tsx": "^4.23.15",
|
|
25
|
-
"typescript": "^7.0.2"
|
|
26
|
-
"@baton/shared": "0.0.0"
|
|
34
|
+
"typescript": "^7.0.2"
|
|
27
35
|
},
|
|
28
36
|
"dependencies": {
|
|
29
37
|
"@anthropic-ai/claude-agent-sdk": "^0.3.316",
|
|
@@ -33,12 +41,5 @@
|
|
|
33
41
|
"mime": "^4.1.0",
|
|
34
42
|
"node-pty": "^1.1.0",
|
|
35
43
|
"ws": "^8.21.0"
|
|
36
|
-
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"baton": "tsx src/index.ts",
|
|
39
|
-
"build": "node build.mjs && chmod +x dist/baton.mjs",
|
|
40
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
41
|
-
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
42
|
-
"test": "tsx --test 'src/**/*.test.ts'"
|
|
43
44
|
}
|
|
44
|
-
}
|
|
45
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 bencode
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|