@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.
Files changed (3) hide show
  1. package/dist/baton.mjs +132 -11
  2. package/package.json +12 -11
  3. 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: Readable2 } = __require("stream");
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
- report: (result) => request(`${baseUrl}/workers/me/files/results`, { method: "PUT", body: result })
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 spawn2 } from "node:child_process";
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 = spawn2(process.execPath, [binPath(), "service", "child"], {
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 spawn3 } from "node:child_process";
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 = spawn3(
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 spawn4 } from "node-pty";
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 = spawn4(command.file, command.args, {
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.4",
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.