@lesscap/baton-cli 0.2.1 → 0.2.3
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 +77 -34
- package/package.json +1 -1
package/dist/baton.mjs
CHANGED
|
@@ -587,9 +587,13 @@ var sessionsClient = (baseUrl) => {
|
|
|
587
587
|
const matches = all.filter((s) => s.name === name);
|
|
588
588
|
return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
|
|
589
589
|
},
|
|
590
|
-
sendMessage: (id, text, attachments) => request(u(`/sessions/${id}/messages`), {
|
|
590
|
+
sendMessage: (id, text, attachments, planMode) => request(u(`/sessions/${id}/messages`), {
|
|
591
591
|
method: "POST",
|
|
592
|
-
body:
|
|
592
|
+
body: {
|
|
593
|
+
text,
|
|
594
|
+
...attachments && attachments.length > 0 ? { attachments } : {},
|
|
595
|
+
...planMode ? { planMode: true } : {}
|
|
596
|
+
}
|
|
593
597
|
}),
|
|
594
598
|
// Raw-body upload: the file streams as the request body (no multipart), so
|
|
595
599
|
// a Blob backed by a file on disk uploads without buffering. filename rides
|
|
@@ -734,6 +738,11 @@ var viewWorker = (cfg) => {
|
|
|
734
738
|
apiToken: worker2.apiToken
|
|
735
739
|
};
|
|
736
740
|
};
|
|
741
|
+
var worktreeConfig = (cfg) => ({
|
|
742
|
+
server: cfg.server,
|
|
743
|
+
project: cfg.projectId,
|
|
744
|
+
worker: { id: cfg.workerId, name: cfg.name, machineId: cfg.machineId, apiToken: cfg.apiToken }
|
|
745
|
+
});
|
|
737
746
|
|
|
738
747
|
// src/config.ts
|
|
739
748
|
var resolveBaseUrl = (urlArg, env = process.env) => {
|
|
@@ -1655,14 +1664,22 @@ function getBaseURL() {
|
|
|
1655
1664
|
|
|
1656
1665
|
// src/session/runner.ts
|
|
1657
1666
|
import { spawnSync } from "node:child_process";
|
|
1658
|
-
import { query
|
|
1667
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
1659
1668
|
|
|
1660
1669
|
// src/session/runner/log.ts
|
|
1661
1670
|
var maskedEnvKeys = (env) => env ? Object.keys(env).sort().join(", ") : "(none)";
|
|
1662
1671
|
|
|
1663
1672
|
// src/session/runner/sdk-env.ts
|
|
1673
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1674
|
+
import { isAbsolute } from "node:path";
|
|
1664
1675
|
var buildSdkEnv = (overlay) => overlay ? { ...process.env, ...overlay } : void 0;
|
|
1665
1676
|
var claudeExecutable = () => process.env.BATON_CLAUDE_BIN;
|
|
1677
|
+
var additionalDirs = () => {
|
|
1678
|
+
const raw = process.env.BATON_ADD_DIRS;
|
|
1679
|
+
if (!raw) return void 0;
|
|
1680
|
+
const dirs = raw.split(":").map((s) => s.trim()).filter((s) => s !== "" && isAbsolute(s) && existsSync3(s));
|
|
1681
|
+
return dirs.length > 0 ? dirs : void 0;
|
|
1682
|
+
};
|
|
1666
1683
|
|
|
1667
1684
|
// src/session/runner/transcript.ts
|
|
1668
1685
|
import { readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -1787,19 +1804,22 @@ ${text}` : header;
|
|
|
1787
1804
|
};
|
|
1788
1805
|
|
|
1789
1806
|
// src/session/runner/query.ts
|
|
1790
|
-
|
|
1791
|
-
var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay) => {
|
|
1807
|
+
var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay, planMode = false) => {
|
|
1792
1808
|
const env = buildSdkEnv(envOverlay);
|
|
1793
1809
|
const exe = claudeExecutable();
|
|
1810
|
+
const addDirs = additionalDirs();
|
|
1811
|
+
if (addDirs) log(`[add-dir] ${addDirs.join(", ")}`);
|
|
1812
|
+
if (planMode) log("[plan] read-only planning turn");
|
|
1794
1813
|
const options = {
|
|
1795
1814
|
cwd: config.worktreePath,
|
|
1796
|
-
permissionMode: "bypassPermissions",
|
|
1815
|
+
permissionMode: planMode ? "plan" : "bypassPermissions",
|
|
1797
1816
|
includePartialMessages: false,
|
|
1798
1817
|
abortController,
|
|
1799
1818
|
stderr: (line) => log(`[claude] ${line.trimEnd()}`),
|
|
1800
1819
|
...resuming ? { resume: config.agentSessionId } : { sessionId: config.agentSessionId },
|
|
1801
1820
|
...env ? { env } : {},
|
|
1802
|
-
...exe ? { pathToClaudeCodeExecutable: exe } : {}
|
|
1821
|
+
...exe ? { pathToClaudeCodeExecutable: exe } : {},
|
|
1822
|
+
...addDirs ? { additionalDirectories: addDirs } : {}
|
|
1803
1823
|
};
|
|
1804
1824
|
return queryFn({ prompt: text, options });
|
|
1805
1825
|
};
|
|
@@ -1858,6 +1878,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
1858
1878
|
const payload = msg.payload;
|
|
1859
1879
|
const rawText = typeof payload?.text === "string" ? payload.text : "";
|
|
1860
1880
|
const attachments = Array.isArray(payload?.attachments) ? payload.attachments : [];
|
|
1881
|
+
const planMode = payload?.planMode === true;
|
|
1861
1882
|
if (rawText.length === 0 && attachments.length === 0) {
|
|
1862
1883
|
await worker2.emitEvent("turn_error", { message: "user_message missing text and attachments" });
|
|
1863
1884
|
return -1;
|
|
@@ -1873,15 +1894,15 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
1873
1894
|
const abort = new AbortController();
|
|
1874
1895
|
const ceiling = turnTimeoutMs();
|
|
1875
1896
|
let timer;
|
|
1876
|
-
const timeout = new Promise((
|
|
1877
|
-
timer = setTimeout(() =>
|
|
1897
|
+
const timeout = new Promise((resolve2) => {
|
|
1898
|
+
timer = setTimeout(() => resolve2(TIMEOUT), ceiling);
|
|
1878
1899
|
});
|
|
1879
|
-
const interrupted = new Promise((
|
|
1880
|
-
if (externalSignal?.aborted)
|
|
1881
|
-
else externalSignal?.addEventListener("abort", () =>
|
|
1900
|
+
const interrupted = new Promise((resolve2) => {
|
|
1901
|
+
if (externalSignal?.aborted) resolve2(ABORTED);
|
|
1902
|
+
else externalSignal?.addEventListener("abort", () => resolve2(ABORTED), { once: true });
|
|
1882
1903
|
});
|
|
1883
1904
|
try {
|
|
1884
|
-
const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay);
|
|
1905
|
+
const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay, planMode);
|
|
1885
1906
|
const consume = streamSdkEvents(messages, worker2);
|
|
1886
1907
|
void consume.catch(() => {
|
|
1887
1908
|
});
|
|
@@ -1896,7 +1917,9 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
1896
1917
|
if (outcome === TIMEOUT) {
|
|
1897
1918
|
log(`[watchdog] turn exceeded ${ceiling}ms \u2014 aborting claude`);
|
|
1898
1919
|
abort.abort();
|
|
1899
|
-
await worker2.emitEvent("turn_error", {
|
|
1920
|
+
await worker2.emitEvent("turn_error", {
|
|
1921
|
+
message: `turn exceeded ${ceiling}ms \u2014 claude aborted`
|
|
1922
|
+
});
|
|
1900
1923
|
await worker2.emitEvent("turn_complete", {});
|
|
1901
1924
|
return -1;
|
|
1902
1925
|
}
|
|
@@ -1930,9 +1953,9 @@ var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onInterrupt, onRea
|
|
|
1930
1953
|
es.onerror = () => log("sse error (eventsource will retry)");
|
|
1931
1954
|
return es;
|
|
1932
1955
|
};
|
|
1933
|
-
var waitForAbort = (signal) => new Promise((
|
|
1934
|
-
if (signal.aborted) return
|
|
1935
|
-
signal.addEventListener("abort", () =>
|
|
1956
|
+
var waitForAbort = (signal) => new Promise((resolve2) => {
|
|
1957
|
+
if (signal.aborted) return resolve2();
|
|
1958
|
+
signal.addEventListener("abort", () => resolve2(), { once: true });
|
|
1936
1959
|
});
|
|
1937
1960
|
var shouldReap = (lastActivity, now, busy, queueLen, idleMs) => !busy && queueLen === 0 && now - lastActivity >= idleMs;
|
|
1938
1961
|
var idleMsFromEnv = () => Number(process.env.BATON_SESSION_IDLE_MS) || 30 * 6e4;
|
|
@@ -1945,7 +1968,7 @@ var restoreTty = () => {
|
|
|
1945
1968
|
};
|
|
1946
1969
|
var runDaemon = async (config, deps, signal) => {
|
|
1947
1970
|
const log = deps.log ?? ((m) => console.log(`[#${config.sessionId} ${config.name}] ${m}`));
|
|
1948
|
-
const qf = deps.queryFn ??
|
|
1971
|
+
const qf = deps.queryFn ?? query;
|
|
1949
1972
|
const ESCtor = deps.eventSourceImpl ?? EventSource;
|
|
1950
1973
|
log(`bin: ${claudeExecutable() ?? "(sdk bundled)"} cwd: ${config.worktreePath}`);
|
|
1951
1974
|
log(`runtime env keys: ${maskedEnvKeys(deps.env)}`);
|
|
@@ -2015,12 +2038,12 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2015
2038
|
);
|
|
2016
2039
|
const idleMs = idleMsFromEnv();
|
|
2017
2040
|
let idleTimer;
|
|
2018
|
-
const reaped = new Promise((
|
|
2041
|
+
const reaped = new Promise((resolve2) => {
|
|
2019
2042
|
idleTimer = setInterval(
|
|
2020
2043
|
() => {
|
|
2021
2044
|
if (shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
|
|
2022
2045
|
log("idle \u2014 shutting down (resumes on next message)");
|
|
2023
|
-
|
|
2046
|
+
resolve2();
|
|
2024
2047
|
}
|
|
2025
2048
|
},
|
|
2026
2049
|
Math.min(idleMs, 3e4)
|
|
@@ -2341,10 +2364,10 @@ import { hostname as osHostname } from "node:os";
|
|
|
2341
2364
|
// src/worker/daemon.ts
|
|
2342
2365
|
import { spawn } from "node:child_process";
|
|
2343
2366
|
import { randomUUID } from "node:crypto";
|
|
2344
|
-
import { existsSync as
|
|
2367
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2345
2368
|
import { dirname as dirname2, join as join5 } from "node:path";
|
|
2346
2369
|
import { fileURLToPath } from "node:url";
|
|
2347
|
-
import { query as
|
|
2370
|
+
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
2348
2371
|
|
|
2349
2372
|
// src/session/runner/title.ts
|
|
2350
2373
|
var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
|
|
@@ -2391,10 +2414,10 @@ ${exchange}`;
|
|
|
2391
2414
|
|
|
2392
2415
|
// src/session/worktree.ts
|
|
2393
2416
|
import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
2394
|
-
import { existsSync as
|
|
2395
|
-
import { dirname } from "node:path";
|
|
2417
|
+
import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3 } from "node:fs";
|
|
2418
|
+
import { dirname, isAbsolute as isAbsolute2, resolve } from "node:path";
|
|
2396
2419
|
var createWorktree = (input) => {
|
|
2397
|
-
if (
|
|
2420
|
+
if (existsSync4(input.worktreePath))
|
|
2398
2421
|
throw new Error(`worktree path already exists: ${input.worktreePath}`);
|
|
2399
2422
|
mkdirSync(dirname(input.worktreePath), { recursive: true });
|
|
2400
2423
|
const branch = `baton/${input.sessionCode}`;
|
|
@@ -2415,8 +2438,26 @@ var repoHeadBranch = (repo) => {
|
|
|
2415
2438
|
const branch = r.status === 0 ? r.stdout.trim() : "";
|
|
2416
2439
|
return branch || "main";
|
|
2417
2440
|
};
|
|
2441
|
+
var ensureExcluded = (repo, pattern) => {
|
|
2442
|
+
try {
|
|
2443
|
+
const r = spawnSync2("git", ["-C", repo, "rev-parse", "--git-path", "info/exclude"], {
|
|
2444
|
+
stdio: "pipe",
|
|
2445
|
+
encoding: "utf8"
|
|
2446
|
+
});
|
|
2447
|
+
if (r.status !== 0) return;
|
|
2448
|
+
const rel = r.stdout.trim();
|
|
2449
|
+
const path = isAbsolute2(rel) ? rel : resolve(repo, rel);
|
|
2450
|
+
const current = existsSync4(path) ? readFileSync3(path, "utf8") : "";
|
|
2451
|
+
if (current.split("\n").includes(pattern)) return;
|
|
2452
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
2453
|
+
const sep = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
2454
|
+
appendFileSync(path, `${sep}${pattern}
|
|
2455
|
+
`, "utf8");
|
|
2456
|
+
} catch {
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2418
2459
|
var removeWorktree = (repo, worktreePath) => {
|
|
2419
|
-
if (!
|
|
2460
|
+
if (!existsSync4(worktreePath)) return;
|
|
2420
2461
|
try {
|
|
2421
2462
|
execFileSync("git", ["-C", repo, "worktree", "remove", "--force", worktreePath], {
|
|
2422
2463
|
stdio: "pipe"
|
|
@@ -2429,7 +2470,7 @@ var removeWorktree = (repo, worktreePath) => {
|
|
|
2429
2470
|
var binPath = () => {
|
|
2430
2471
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
2431
2472
|
const devShim = join5(here, "..", "..", "bin", "baton.mjs");
|
|
2432
|
-
return
|
|
2473
|
+
return existsSync5(devShim) ? devShim : fileURLToPath(import.meta.url);
|
|
2433
2474
|
};
|
|
2434
2475
|
var runWorkerDaemon = async (cfg, client, signal) => {
|
|
2435
2476
|
const repo = process.cwd();
|
|
@@ -2484,6 +2525,8 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2484
2525
|
await client.sessions.materialize(sessionId, { agentSessionId, worktreePath }, cfg.apiToken);
|
|
2485
2526
|
log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
|
|
2486
2527
|
}
|
|
2528
|
+
ensureExcluded(repo, PROJECT_CONFIG_NAME);
|
|
2529
|
+
saveProjectConfig(join5(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
|
|
2487
2530
|
spawnChild(sessionId, worktreePath);
|
|
2488
2531
|
};
|
|
2489
2532
|
const onStop = (sessionId) => {
|
|
@@ -2510,7 +2553,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2510
2553
|
worktreePath,
|
|
2511
2554
|
userText: exchange.userText,
|
|
2512
2555
|
assistantText: exchange.assistantText,
|
|
2513
|
-
queryFn:
|
|
2556
|
+
queryFn: query2
|
|
2514
2557
|
});
|
|
2515
2558
|
if (!title) return log(`title #${sessionId}: not enough to title yet, skipping`);
|
|
2516
2559
|
await client.sessions.setName(sessionId, title, cfg.apiToken);
|
|
@@ -2555,9 +2598,9 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2555
2598
|
};
|
|
2556
2599
|
es.onerror = () => log("command stream error (eventsource will retry)");
|
|
2557
2600
|
log(`listening for commands (server ${cfg.server}, repo ${repo})`);
|
|
2558
|
-
await new Promise((
|
|
2559
|
-
if (signal.aborted) return
|
|
2560
|
-
signal.addEventListener("abort", () =>
|
|
2601
|
+
await new Promise((resolve2) => {
|
|
2602
|
+
if (signal.aborted) return resolve2();
|
|
2603
|
+
signal.addEventListener("abort", () => resolve2(), { once: true });
|
|
2561
2604
|
});
|
|
2562
2605
|
es.close();
|
|
2563
2606
|
clearInterval(hb);
|
|
@@ -2566,13 +2609,13 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2566
2609
|
|
|
2567
2610
|
// src/worker/machine-id.ts
|
|
2568
2611
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2569
|
-
import { existsSync as
|
|
2612
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2570
2613
|
import { homedir as homedir3 } from "node:os";
|
|
2571
2614
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
2572
2615
|
var machineIdPath = (env = process.env) => join6(env.XDG_DATA_HOME ?? join6(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
|
|
2573
2616
|
var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
2574
|
-
if (
|
|
2575
|
-
const s =
|
|
2617
|
+
if (existsSync6(path)) {
|
|
2618
|
+
const s = readFileSync4(path, "utf8").trim();
|
|
2576
2619
|
if (s) return s;
|
|
2577
2620
|
}
|
|
2578
2621
|
const id = randomUUID2();
|