@lesscap/baton-cli 0.9.4 → 0.9.6

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 +219 -64
  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;
@@ -7549,7 +7563,7 @@ var createProvisionerClient = (config) => {
7549
7563
  import { execFile as execFile3 } from "node:child_process";
7550
7564
  import { lstat, mkdir as mkdir3, realpath, writeFile as writeFile2 } from "node:fs/promises";
7551
7565
  import { hostname } from "node:os";
7552
- import { join as join5 } from "node:path";
7566
+ import { dirname as dirname2, join as join5 } from "node:path";
7553
7567
  import { setTimeout as setTimeout2 } from "node:timers/promises";
7554
7568
  import { promisify as promisify3 } from "node:util";
7555
7569
 
@@ -7620,11 +7634,17 @@ var startAgent = async (config, agent, signal) => {
7620
7634
  throw new Error(`launchd start failed; inspect ${plist} and ${agent.log}`);
7621
7635
  }
7622
7636
  };
7623
- var launchWorkerAgent = async (config, creation, repoPath, workerEnv2, signal) => startAgent(
7637
+ var launchWorkerAgent = async (config, creation, repoPath, configPath, workerEnv2, signal) => startAgent(
7624
7638
  config,
7625
7639
  {
7626
7640
  label: `dev.fmap.baton-worker-creation-${creation.id}`,
7627
- args: [config.nodePath ?? process.execPath, config.cliPath, "worker", "run"],
7641
+ args: [
7642
+ config.nodePath ?? process.execPath,
7643
+ config.cliPath,
7644
+ "worker",
7645
+ "run",
7646
+ ...configPath === null ? [] : ["--config", configPath]
7647
+ ],
7628
7648
  cwd: repoPath,
7629
7649
  env: {
7630
7650
  PATH: workerEnv2.PATH ?? process.env.PATH ?? "/usr/bin:/bin",
@@ -7685,13 +7705,20 @@ var directory = async (path) => {
7685
7705
  const info = await lstat(path);
7686
7706
  if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`unsafe directory: ${path}`);
7687
7707
  };
7688
- var provisionWorker = async (config, creation, client, signal) => {
7689
- const repoPath = join5(
7690
- config.projectsRoot,
7691
- `project-${creation.projectId}`,
7692
- `creation-${creation.id}`
7693
- );
7694
- const progress = (step) => client.report(creation.id, { status: "running", step, repoPath }, signal);
7708
+ var gitIn = async (config, repoPath, args, signal, failure = "Git repository initialization failed") => {
7709
+ try {
7710
+ const { stdout } = await promisify3(execFile3)("git", ["-C", repoPath, ...args], {
7711
+ env: commandEnvironment(config),
7712
+ signal,
7713
+ timeout: 1e4
7714
+ });
7715
+ return stdout;
7716
+ } catch (error) {
7717
+ signal.throwIfAborted();
7718
+ throw new Error(failure, { cause: error });
7719
+ }
7720
+ };
7721
+ var initRepository = async (config, creation, repoPath, progress, signal) => {
7695
7722
  await progress("directory");
7696
7723
  signal.throwIfAborted();
7697
7724
  await mkdir3(config.projectsRoot, { recursive: true, mode: 448 });
@@ -7701,33 +7728,51 @@ var provisionWorker = async (config, creation, client, signal) => {
7701
7728
  mode: 448
7702
7729
  });
7703
7730
  await progress("git");
7704
- const git2 = async (args) => {
7705
- try {
7706
- await promisify3(execFile3)("git", ["-C", repoPath, ...args], {
7707
- env: commandEnvironment(config),
7708
- signal,
7709
- timeout: 1e4
7710
- });
7711
- } catch (error) {
7712
- signal.throwIfAborted();
7713
- throw new Error("Git repository initialization failed", { cause: error });
7714
- }
7715
- };
7716
- await git2(["init", "-b", "main"]);
7731
+ await gitIn(config, repoPath, ["init", "-b", "main"], signal);
7717
7732
  await writeFile2(join5(repoPath, ".git/info/exclude"), "\n.baton.json\n", { flag: "a" });
7718
- await git2([
7719
- "-c",
7720
- "user.name=Baton",
7721
- "-c",
7722
- "user.email=baton@localhost",
7723
- "commit",
7724
- "--allow-empty",
7725
- "-m",
7726
- "chore: initialize repository"
7727
- ]);
7733
+ await gitIn(
7734
+ config,
7735
+ repoPath,
7736
+ [
7737
+ "-c",
7738
+ "user.name=Baton",
7739
+ "-c",
7740
+ "user.email=baton@localhost",
7741
+ "commit",
7742
+ "--allow-empty",
7743
+ "-m",
7744
+ "chore: initialize repository"
7745
+ ],
7746
+ signal
7747
+ );
7748
+ };
7749
+ var adoptRepository = async (config, creation, repoPath, progress, signal) => {
7750
+ await progress("directory");
7751
+ signal.throwIfAborted();
7752
+ const real = await realpath(repoPath);
7753
+ const project2 = join5(await realpath(config.projectsRoot), `project-${creation.projectId}`);
7754
+ if (dirname2(real) !== project2 || !(await lstat(real)).isDirectory())
7755
+ throw new Error(`unsafe directory: ${repoPath}`);
7756
+ await progress("git");
7757
+ const top = await gitIn(
7758
+ config,
7759
+ real,
7760
+ ["rev-parse", "--show-toplevel"],
7761
+ signal,
7762
+ `not a Git repository: ${repoPath}`
7763
+ );
7764
+ if (await realpath(top.trim()) !== real)
7765
+ throw new Error(`not the root of a Git work tree: ${repoPath}`);
7766
+ };
7767
+ var provisionWorker = async (config, creation, client, signal) => {
7768
+ const shared = creation.repoPath !== null;
7769
+ const repoPath = creation.repoPath ?? join5(config.projectsRoot, `project-${creation.projectId}`, `creation-${creation.id}`);
7770
+ const progress = (step) => client.report(creation.id, { status: "running", step, repoPath }, signal);
7771
+ await (shared ? adoptRepository : initRepository)(config, creation, repoPath, progress, signal);
7728
7772
  await progress("register");
7729
7773
  const result = await client.register(creation.id, hostname(), signal);
7730
7774
  const state = workerStatePath(config, creation);
7775
+ const root = await realpath(config.projectsRoot);
7731
7776
  await directory(join5(root, ".worker-state"));
7732
7777
  await directory(join5(root, ".worker-state", `project-${creation.projectId}`));
7733
7778
  await mkdir3(state, { mode: 448 });
@@ -7746,15 +7791,13 @@ var provisionWorker = async (config, creation, client, signal) => {
7746
7791
  machineId: result.worker.machineId,
7747
7792
  apiToken: result.apiToken
7748
7793
  });
7749
- await writeFile2(join5(repoPath, ".baton.json"), `${JSON.stringify(cfg, null, 2)}
7750
- `, {
7751
- flag: "wx",
7752
- mode: 384
7753
- });
7794
+ const configPath = shared ? join5(state, "worker.json") : join5(repoPath, ".baton.json");
7795
+ await writeFile2(configPath, `${JSON.stringify(cfg, null, 2)}
7796
+ `, { flag: "wx", mode: 384 });
7754
7797
  await progress("start");
7755
7798
  signal.throwIfAborted();
7756
7799
  const workerEnv2 = await workerTemplateEnvironment(config, creation, state);
7757
- await launchWorkerAgent(config, creation, repoPath, workerEnv2, signal);
7800
+ await launchWorkerAgent(config, creation, repoPath, shared ? configPath : null, workerEnv2, signal);
7758
7801
  await progress("connect");
7759
7802
  const deadline = Date.now() + 6e4;
7760
7803
  while (Date.now() < deadline) {
@@ -9999,13 +10042,13 @@ import { hostname as osHostname } from "node:os";
9999
10042
  // src/worker/bundled-skills.ts
10000
10043
  import { spawnSync as spawnSync2 } from "node:child_process";
10001
10044
  import { cpSync, existsSync as existsSync5 } from "node:fs";
10002
- import { dirname as dirname3, join as join14 } from "node:path";
10045
+ import { dirname as dirname4, join as join14 } from "node:path";
10003
10046
  import { fileURLToPath } from "node:url";
10004
10047
 
10005
10048
  // src/worker/git-exclude.ts
10006
10049
  import { spawnSync } from "node:child_process";
10007
10050
  import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "node:fs";
10008
- import { dirname as dirname2, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
10051
+ import { dirname as dirname3, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
10009
10052
  var ensureExcluded = (cwd, pattern) => {
10010
10053
  const result = spawnSync("git", ["-C", cwd, "rev-parse", "--git-path", "info/exclude"], {
10011
10054
  encoding: "utf8",
@@ -10020,7 +10063,7 @@ var ensureExcluded = (cwd, pattern) => {
10020
10063
  const path = isAbsolute3(location) ? location : resolve3(cwd, location);
10021
10064
  const current = existsSync4(path) ? readFileSync5(path, "utf8") : "";
10022
10065
  if (current.split("\n").includes(pattern)) return;
10023
- mkdirSync3(dirname2(path), { recursive: true });
10066
+ mkdirSync3(dirname3(path), { recursive: true });
10024
10067
  appendFileSync(path, `${current && !current.endsWith("\n") ? "\n" : ""}${pattern}
10025
10068
  `);
10026
10069
  };
@@ -10029,7 +10072,7 @@ var ensureExcluded = (cwd, pattern) => {
10029
10072
  var SKILL_NAME = "baton-publish-artifact";
10030
10073
  var TARGETS = [`.claude/skills/${SKILL_NAME}`, `.agents/skills/${SKILL_NAME}`];
10031
10074
  var bundledSkillPath = () => {
10032
- const here = dirname3(fileURLToPath(import.meta.url));
10075
+ const here = dirname4(fileURLToPath(import.meta.url));
10033
10076
  const candidates = [
10034
10077
  join14(here, "..", "skills", SKILL_NAME),
10035
10078
  join14(here, "..", "..", "skills", SKILL_NAME)
@@ -10131,6 +10174,10 @@ var discoverBranches = async (baseDir) => {
10131
10174
  }).sort((a, b) => rank(a) - rank(b) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
10132
10175
  };
10133
10176
 
10177
+ // src/worker/files/commit.ts
10178
+ import { spawn as spawn2 } from "node:child_process";
10179
+ import { Readable as Readable2 } from "node:stream";
10180
+
10134
10181
  // src/worker/files/common.ts
10135
10182
  var FileError = class extends Error {
10136
10183
  constructor(code, message) {
@@ -10140,6 +10187,10 @@ var FileError = class extends Error {
10140
10187
  code;
10141
10188
  };
10142
10189
  var notFound = (path) => new FileError("file_not_found", `not found: ${path}`);
10190
+ var checkDownloadSize = (path, size) => {
10191
+ if (size > FILE_DOWNLOAD_MAX_BYTES)
10192
+ throw new FileError("file_too_large", `too large to download (${size} bytes): ${path}`);
10193
+ };
10143
10194
  var checkedPath = (path, allowRoot) => {
10144
10195
  const rel = normalizeFilePath(path, allowRoot);
10145
10196
  if (rel === null) throw new FileError("invalid_request", `invalid path: ${path}`);
@@ -10282,11 +10333,38 @@ var readRawCommitFile = async (repo, commit, path) => {
10282
10333
  const bytes = await runGit(repo, ["cat-file", "blob", entry.oid], FILE_RAW_MAX_BYTES + 1);
10283
10334
  return rawPayload(rel, mediaType, bytes);
10284
10335
  };
10336
+ var openCommitDownload = async (repo, commit, path, signal) => {
10337
+ const rel = checkedPath(path, false);
10338
+ const entry = await regularEntry(repo, commit, rel);
10339
+ checkDownloadSize(rel, entry.size);
10340
+ const child = spawn2("git", ["--literal-pathspecs", "-C", repo, "cat-file", "blob", entry.oid], {
10341
+ stdio: ["ignore", "pipe", "ignore"],
10342
+ signal,
10343
+ env: { ...process.env, LC_ALL: "C", GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" }
10344
+ });
10345
+ const exited = new Promise((resolve4) => {
10346
+ child.once("error", resolve4);
10347
+ child.once(
10348
+ "close",
10349
+ (code, killed) => resolve4(code === 0 ? null : new Error(`git cat-file exited with ${code ?? killed}`))
10350
+ );
10351
+ });
10352
+ const body = Readable2.toWeb(child.stdout).pipeThrough(
10353
+ new TransformStream({
10354
+ async flush() {
10355
+ const failure = await exited;
10356
+ if (failure) throw failure;
10357
+ }
10358
+ })
10359
+ );
10360
+ return { size: entry.size, body, close: () => child.kill() };
10361
+ };
10285
10362
 
10286
10363
  // src/worker/files/disk.ts
10287
10364
  import { constants as constants2 } from "node:fs";
10288
10365
  import { open as open2, readdir, realpath as realpath3, stat as stat3 } from "node:fs/promises";
10289
10366
  import { isAbsolute as isAbsolute4, join as join15, relative, sep } from "node:path";
10367
+ import { Readable as Readable3 } from "node:stream";
10290
10368
  var isMissing = (error) => ["ENOENT", "ENOTDIR", "ELOOP"].includes(error.code ?? "");
10291
10369
  var childPath = (parent, name) => parent ? `${parent}/${name}` : name;
10292
10370
  var inside = (root, real) => {
@@ -10407,6 +10485,72 @@ var readRawFile = async (root, path) => {
10407
10485
  if ("oversize" in read) throw new FileError("unsupported", `too large to serve raw: ${file.path}`);
10408
10486
  return rawPayload(file.path, mediaType, read.bytes);
10409
10487
  };
10488
+ var openDiskDownload = async (root, path, signal) => {
10489
+ const file = await resolveInside(root, path, false);
10490
+ const handle = await open2(file.real, constants2.O_RDONLY | constants2.O_NONBLOCK);
10491
+ try {
10492
+ const opened = await handle.stat();
10493
+ if (!opened.isFile()) throw notFound(file.path);
10494
+ checkDownloadSize(file.path, opened.size);
10495
+ const stream = handle.createReadStream({ signal });
10496
+ return {
10497
+ size: opened.size,
10498
+ body: Readable3.toWeb(stream),
10499
+ close: () => stream.destroy()
10500
+ };
10501
+ } catch (error) {
10502
+ await handle.close();
10503
+ throw error;
10504
+ }
10505
+ };
10506
+
10507
+ // src/worker/files/download.ts
10508
+ var exactLength = (size) => {
10509
+ let seen = 0;
10510
+ return new TransformStream({
10511
+ transform(chunk, controller) {
10512
+ seen += chunk.byteLength;
10513
+ if (seen > size) throw new Error(`file grew past its ${size} bytes while downloading`);
10514
+ controller.enqueue(chunk);
10515
+ },
10516
+ flush() {
10517
+ if (seen !== size) throw new Error(`file ended at ${seen} of its ${size} bytes`);
10518
+ }
10519
+ });
10520
+ };
10521
+ var openSource = async (baseDir, branch, path, signal) => {
10522
+ if (branch.worktree !== null) return openDiskDownload(branch.worktree, path, signal);
10523
+ if (branch.commit === null)
10524
+ throw new FileError("branch_not_found", `branch ${branch.name} is empty`);
10525
+ return openCommitDownload(baseDir, branch.commit, path, signal);
10526
+ };
10527
+ var openFileDownload = async (baseDir, query3, signal) => {
10528
+ const branches = await discoverBranches(baseDir);
10529
+ const branch = branches.find((candidate) => candidate.name === query3.branch);
10530
+ if (!branch) throw new FileError("branch_not_found", `branch ${query3.branch} does not exist`);
10531
+ const source = await openSource(baseDir, branch, query3.path, signal);
10532
+ return { ...source, body: source.body.pipeThrough(exactLength(source.size)) };
10533
+ };
10534
+ var failureOf = (error) => {
10535
+ if (error instanceof FileError) return { ok: false, code: error.code, message: error.message };
10536
+ console.error("[files] opening a download failed", error);
10537
+ return { ok: false, code: "unavailable", message: "the worker could not read the file" };
10538
+ };
10539
+ var sendFileDownload = async (baseDir, command, client, signal) => {
10540
+ const bounded = AbortSignal.any([signal, AbortSignal.timeout(FILE_DOWNLOAD_TIMEOUT_MS)]);
10541
+ let source;
10542
+ try {
10543
+ source = await openFileDownload(baseDir, command.query, bounded);
10544
+ } catch (error) {
10545
+ await client.rejectDownload(command.requestId, failureOf(error));
10546
+ return;
10547
+ }
10548
+ try {
10549
+ await client.uploadDownload(command.requestId, source.size, source.body, bounded);
10550
+ } finally {
10551
+ source.close();
10552
+ }
10553
+ };
10410
10554
 
10411
10555
  // src/worker/files/handler.ts
10412
10556
  var DEFAULT_CONCURRENCY = 4;
@@ -10534,9 +10678,9 @@ import { stat as stat4 } from "node:fs/promises";
10534
10678
  import { join as join17 } from "node:path";
10535
10679
 
10536
10680
  // src/worker/service-process.ts
10537
- import { spawn as spawn2 } from "node:child_process";
10681
+ import { spawn as spawn3 } from "node:child_process";
10538
10682
  import { closeSync, existsSync as existsSync6, openSync } from "node:fs";
10539
- import { dirname as dirname4, join as join16 } from "node:path";
10683
+ import { dirname as dirname5, join as join16 } from "node:path";
10540
10684
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10541
10685
  var START_TIMEOUT_MS = 1e4;
10542
10686
  var isWrapperMessage = (message) => {
@@ -10546,7 +10690,7 @@ var isWrapperMessage = (message) => {
10546
10690
  return value.type === "error" && typeof value.error === "string";
10547
10691
  };
10548
10692
  var binPath = () => {
10549
- const here = dirname4(fileURLToPath2(import.meta.url));
10693
+ const here = dirname5(fileURLToPath2(import.meta.url));
10550
10694
  const devShim = join16(here, "..", "..", "bin", "baton.mjs");
10551
10695
  return existsSync6(devShim) ? devShim : fileURLToPath2(import.meta.url);
10552
10696
  };
@@ -10554,7 +10698,7 @@ var startServiceProcess = (input) => {
10554
10698
  const logFd = openSync(input.logPath, "a");
10555
10699
  let wrapper;
10556
10700
  try {
10557
- wrapper = spawn2(process.execPath, [binPath(), "service", "child"], {
10701
+ wrapper = spawn3(process.execPath, [binPath(), "service", "child"], {
10558
10702
  detached: false,
10559
10703
  stdio: ["ignore", logFd, logFd, "ipc"],
10560
10704
  env: process.env
@@ -10704,11 +10848,11 @@ var createServiceSupervisor = (deps) => {
10704
10848
  };
10705
10849
 
10706
10850
  // src/worker/session-supervisor.ts
10707
- import { spawn as spawn3 } from "node:child_process";
10851
+ import { spawn as spawn4 } from "node:child_process";
10708
10852
  import { randomUUID as randomUUID2 } from "node:crypto";
10709
10853
  import { existsSync as existsSync7 } from "node:fs";
10710
10854
  import { mkdir as mkdir6, rm as rm3, stat as stat5 } from "node:fs/promises";
10711
- import { dirname as dirname5, join as join21 } from "node:path";
10855
+ import { dirname as dirname6, join as join21 } from "node:path";
10712
10856
  import { fileURLToPath as fileURLToPath3 } from "node:url";
10713
10857
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
10714
10858
 
@@ -10971,7 +11115,7 @@ var stopRecordedRunner = async (record, expected, graceMs = 5e3) => {
10971
11115
 
10972
11116
  // src/worker/session-supervisor.ts
10973
11117
  var binPath2 = () => {
10974
- const here = dirname5(fileURLToPath3(import.meta.url));
11118
+ const here = dirname6(fileURLToPath3(import.meta.url));
10975
11119
  const devShim = join21(here, "..", "..", "bin", "baton.mjs");
10976
11120
  return existsSync7(devShim) ? devShim : fileURLToPath3(import.meta.url);
10977
11121
  };
@@ -11009,7 +11153,7 @@ var createSessionSupervisor = (deps) => {
11009
11153
  if (children.has(sessionId)) return;
11010
11154
  await stopRecord(sessionId);
11011
11155
  const runnerToken = randomUUID2();
11012
- const child = spawn3(
11156
+ const child = spawn4(
11013
11157
  process.execPath,
11014
11158
  [binPath2(), "session", "run", String(sessionId), "--runner-token", runnerToken],
11015
11159
  {
@@ -11201,7 +11345,7 @@ var createSessionSupervisor = (deps) => {
11201
11345
 
11202
11346
  // src/worker/terminal-manager.ts
11203
11347
  import { statSync as statSync2 } from "node:fs";
11204
- import { spawn as spawn4 } from "node-pty";
11348
+ import { spawn as spawn5 } from "node-pty";
11205
11349
 
11206
11350
  // ../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/wrapper.mjs
11207
11351
  var import_stream2 = __toESM(require_stream(), 1);
@@ -11217,7 +11361,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
11217
11361
  import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
11218
11362
  import { createRequire as createModuleRequire } from "node:module";
11219
11363
  import { homedir as homedir5 } from "node:os";
11220
- import { dirname as dirname6, join as join22 } from "node:path";
11364
+ import { dirname as dirname7, join as join22 } from "node:path";
11221
11365
  import { fileURLToPath as fileURLToPath4 } from "node:url";
11222
11366
  var MAX_TERMINALS = 10;
11223
11367
  var claudePtyCommand = (agentSessionId, hasJsonl, env = process.env) => ({
@@ -11238,7 +11382,7 @@ var piPtyCommand = (agentSessionId, env = process.env) => {
11238
11382
  const override = env.BATON_PI_BIN?.trim();
11239
11383
  if (override) return { file: override, args };
11240
11384
  const entry = fileURLToPath4(import.meta.resolve("@earendil-works/pi-coding-agent"));
11241
- return { file: process.execPath, args: [join22(dirname6(entry), "bundle", "cli.js"), ...args] };
11385
+ return { file: process.execPath, args: [join22(dirname7(entry), "bundle", "cli.js"), ...args] };
11242
11386
  };
11243
11387
  var hasSessionJsonl = (agentSessionId) => {
11244
11388
  const root = join22(homedir5(), ".claude", "projects");
@@ -11266,7 +11410,7 @@ var createTerminalManager = (deps) => {
11266
11410
  return;
11267
11411
  }
11268
11412
  const command = ptyCommand(cfg.agentKind, agentSessionId);
11269
- const term = spawn4(command.file, command.args, {
11413
+ const term = spawn5(command.file, command.args, {
11270
11414
  name: "xterm-color",
11271
11415
  cols: 80,
11272
11416
  rows: 24,
@@ -11356,6 +11500,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
11356
11500
  hasChild: (id) => supervisor.has(id)
11357
11501
  });
11358
11502
  const files = createFileQueryRunner(cwd);
11503
+ const downloads = new AbortController();
11359
11504
  const services = createServiceSupervisor({
11360
11505
  sendReport: (report) => client.services.report(report),
11361
11506
  cfg,
@@ -11413,6 +11558,10 @@ var runWorkerDaemon = async (cfg, client, signal) => {
11413
11558
  void services.stop(cmd.requestId, cmd.name).catch((err) => log(`service stop failed: ${String(err)}`));
11414
11559
  else if (cmd.cmd === "files.query")
11415
11560
  void files(cmd.query).then((reply) => client.files.report({ requestId: cmd.requestId, ...reply })).catch((err) => log(`files query failed: ${String(err)}`));
11561
+ else if (cmd.cmd === "files.download")
11562
+ void sendFileDownload(cwd, cmd, client.files, downloads.signal).catch(
11563
+ (err) => log(`files download failed: ${String(err)}`)
11564
+ );
11416
11565
  } catch (error) {
11417
11566
  log(`invalid command: ${String(error)}`);
11418
11567
  }
@@ -11426,6 +11575,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
11426
11575
  signal.addEventListener("abort", () => resolve4(), { once: true });
11427
11576
  });
11428
11577
  es.close();
11578
+ downloads.abort();
11429
11579
  liveness.stop();
11430
11580
  services.killAll();
11431
11581
  await supervisor.killAll();
@@ -11437,7 +11587,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
11437
11587
  import { randomUUID as randomUUID3 } from "node:crypto";
11438
11588
  import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
11439
11589
  import { homedir as homedir6 } from "node:os";
11440
- import { dirname as dirname7, join as join23 } from "node:path";
11590
+ import { dirname as dirname8, join as join23 } from "node:path";
11441
11591
  var machineIdPath = (env = process.env) => join23(env.XDG_DATA_HOME ?? join23(env.HOME ?? homedir6(), ".local/share"), "baton", "machine-id");
11442
11592
  var readOrCreateMachineId = (path = machineIdPath()) => {
11443
11593
  if (existsSync9(path)) {
@@ -11445,7 +11595,7 @@ var readOrCreateMachineId = (path = machineIdPath()) => {
11445
11595
  if (s) return s;
11446
11596
  }
11447
11597
  const id = randomUUID3();
11448
- mkdirSync5(dirname7(path), { recursive: true });
11598
+ mkdirSync5(dirname8(path), { recursive: true });
11449
11599
  writeFileSync5(path, `${id}
11450
11600
  `, "utf8");
11451
11601
  return id;
@@ -11479,7 +11629,11 @@ var workerCreateCommand = defineCommand({
11479
11629
  description: "mini-glm-codeplan | mini-dashscope-codeplan | mini-codex"
11480
11630
  },
11481
11631
  name: { type: "string", required: true, description: "worker name" },
11482
- agentKind: { type: "string", description: AGENT_KINDS.join(", ") }
11632
+ agentKind: { type: "string", description: AGENT_KINDS.join(", ") },
11633
+ repoPath: {
11634
+ type: "string",
11635
+ description: "share an existing worker's directory in this project (default: a new one)"
11636
+ }
11483
11637
  },
11484
11638
  run: async ({ args }) => {
11485
11639
  if (!isWorkerTemplateId(args.template)) throw new Error("invalid worker template");
@@ -11494,7 +11648,8 @@ var workerCreateCommand = defineCommand({
11494
11648
  {
11495
11649
  templateId: args.template,
11496
11650
  name,
11497
- agentKind
11651
+ agentKind,
11652
+ ...args.repoPath ? { repoPath: args.repoPath } : {}
11498
11653
  }
11499
11654
  );
11500
11655
  console.log(renderOne(creation, fmtWorkerCreation, Boolean(args.json)));
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.6",
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.