@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.
- package/dist/baton.mjs +219 -64
- 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;
|
|
@@ -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: [
|
|
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
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
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
|
-
|
|
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
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
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
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
10681
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
10538
10682
|
import { closeSync, existsSync as existsSync6, openSync } from "node:fs";
|
|
10539
|
-
import { dirname as
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
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.
|
|
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.
|