@lesscap/baton-cli 0.2.0 → 0.2.2
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 +71 -17
- package/package.json +1 -1
package/dist/baton.mjs
CHANGED
|
@@ -578,6 +578,7 @@ var sessionsClient = (baseUrl) => {
|
|
|
578
578
|
resume: (id) => request(u(`/sessions/${id}/resume`), { method: "POST" }),
|
|
579
579
|
stop: (id) => request(u(`/sessions/${id}/stop`), { method: "POST" }),
|
|
580
580
|
clear: (id) => request(u(`/sessions/${id}/clear`), { method: "POST" }),
|
|
581
|
+
abort: (id) => request(u(`/sessions/${id}/abort`), { method: "POST" }),
|
|
581
582
|
rename: (id, name) => request(u(`/sessions/${id}/rename`), { method: "POST", body: { name } }),
|
|
582
583
|
listByProject: (projectId) => request(u(`/projects/${projectId}/sessions`), { method: "GET" }),
|
|
583
584
|
get: (id) => request(u(`/sessions/${id}`), { method: "GET" }),
|
|
@@ -1035,6 +1036,22 @@ var resolveSession = async (client, projectId, handle) => {
|
|
|
1035
1036
|
throw new Error(`session "${handle}" not found in project ${projectId}`);
|
|
1036
1037
|
};
|
|
1037
1038
|
|
|
1039
|
+
// src/commands/session/abort.ts
|
|
1040
|
+
var sessionAbortCommand = defineCommand({
|
|
1041
|
+
meta: { name: "abort", description: "interrupt the in-flight turn (like Esc); keeps the session" },
|
|
1042
|
+
args: {
|
|
1043
|
+
session: { type: "positional", required: true, description: "session int id or name" },
|
|
1044
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
1045
|
+
...common
|
|
1046
|
+
},
|
|
1047
|
+
run: async ({ args }) => {
|
|
1048
|
+
const c = clientFor(args);
|
|
1049
|
+
const handle = await resolveSession(c, resolveProjectId(args), args.session);
|
|
1050
|
+
const s = await c.sessions.abort(handle.id);
|
|
1051
|
+
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1038
1055
|
// src/commands/session/clear.ts
|
|
1039
1056
|
var sessionClearCommand = defineCommand({
|
|
1040
1057
|
meta: { name: "clear", description: "reset the conversation context (keeps the session + worktree)" },
|
|
@@ -1638,14 +1655,22 @@ function getBaseURL() {
|
|
|
1638
1655
|
|
|
1639
1656
|
// src/session/runner.ts
|
|
1640
1657
|
import { spawnSync } from "node:child_process";
|
|
1641
|
-
import { query
|
|
1658
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
1642
1659
|
|
|
1643
1660
|
// src/session/runner/log.ts
|
|
1644
1661
|
var maskedEnvKeys = (env) => env ? Object.keys(env).sort().join(", ") : "(none)";
|
|
1645
1662
|
|
|
1646
1663
|
// src/session/runner/sdk-env.ts
|
|
1664
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1665
|
+
import { isAbsolute } from "node:path";
|
|
1647
1666
|
var buildSdkEnv = (overlay) => overlay ? { ...process.env, ...overlay } : void 0;
|
|
1648
1667
|
var claudeExecutable = () => process.env.BATON_CLAUDE_BIN;
|
|
1668
|
+
var additionalDirs = () => {
|
|
1669
|
+
const raw = process.env.BATON_ADD_DIRS;
|
|
1670
|
+
if (!raw) return void 0;
|
|
1671
|
+
const dirs = raw.split(":").map((s) => s.trim()).filter((s) => s !== "" && isAbsolute(s) && existsSync3(s));
|
|
1672
|
+
return dirs.length > 0 ? dirs : void 0;
|
|
1673
|
+
};
|
|
1649
1674
|
|
|
1650
1675
|
// src/session/runner/transcript.ts
|
|
1651
1676
|
import { readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -1770,10 +1795,11 @@ ${text}` : header;
|
|
|
1770
1795
|
};
|
|
1771
1796
|
|
|
1772
1797
|
// src/session/runner/query.ts
|
|
1773
|
-
import "@anthropic-ai/claude-agent-sdk";
|
|
1774
1798
|
var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay) => {
|
|
1775
1799
|
const env = buildSdkEnv(envOverlay);
|
|
1776
1800
|
const exe = claudeExecutable();
|
|
1801
|
+
const addDirs = additionalDirs();
|
|
1802
|
+
if (addDirs) log(`[add-dir] ${addDirs.join(", ")}`);
|
|
1777
1803
|
const options = {
|
|
1778
1804
|
cwd: config.worktreePath,
|
|
1779
1805
|
permissionMode: "bypassPermissions",
|
|
@@ -1782,7 +1808,8 @@ var startQuery = (config, text, resuming, queryFn, abortController, log, envOver
|
|
|
1782
1808
|
stderr: (line) => log(`[claude] ${line.trimEnd()}`),
|
|
1783
1809
|
...resuming ? { resume: config.agentSessionId } : { sessionId: config.agentSessionId },
|
|
1784
1810
|
...env ? { env } : {},
|
|
1785
|
-
...exe ? { pathToClaudeCodeExecutable: exe } : {}
|
|
1811
|
+
...exe ? { pathToClaudeCodeExecutable: exe } : {},
|
|
1812
|
+
...addDirs ? { additionalDirectories: addDirs } : {}
|
|
1786
1813
|
};
|
|
1787
1814
|
return queryFn({ prompt: text, options });
|
|
1788
1815
|
};
|
|
@@ -1807,6 +1834,7 @@ var toTurnResult = (message) => {
|
|
|
1807
1834
|
// src/session/runner/turn.ts
|
|
1808
1835
|
var turnTimeoutMs = () => Number(process.env.BATON_TURN_TIMEOUT_MS) || 30 * 6e4;
|
|
1809
1836
|
var TIMEOUT = /* @__PURE__ */ Symbol("turn-timeout");
|
|
1837
|
+
var ABORTED = /* @__PURE__ */ Symbol("turn-aborted");
|
|
1810
1838
|
var finalizeTurn = async (result, worker2, log) => {
|
|
1811
1839
|
if (!result) {
|
|
1812
1840
|
log("[exit] stream ended with no result");
|
|
@@ -1835,7 +1863,7 @@ var buildPrompt = async (config, rawText, attachments, log, fetchImpl) => {
|
|
|
1835
1863
|
log(`[attach] downloaded ${relPaths.length} file(s) \u2192 ${config.worktreePath}/attachments`);
|
|
1836
1864
|
return augmentPrompt(rawText, relPaths, labelAttachments(attachments));
|
|
1837
1865
|
};
|
|
1838
|
-
var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => console.log(m), envOverlay, fetchImpl) => {
|
|
1866
|
+
var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => console.log(m), envOverlay, fetchImpl, externalSignal) => {
|
|
1839
1867
|
await worker2.emitEvent("turn_start", { messageId: msg.id });
|
|
1840
1868
|
const payload = msg.payload;
|
|
1841
1869
|
const rawText = typeof payload?.text === "string" ? payload.text : "";
|
|
@@ -1858,12 +1886,23 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
1858
1886
|
const timeout = new Promise((resolve) => {
|
|
1859
1887
|
timer = setTimeout(() => resolve(TIMEOUT), ceiling);
|
|
1860
1888
|
});
|
|
1889
|
+
const interrupted = new Promise((resolve) => {
|
|
1890
|
+
if (externalSignal?.aborted) resolve(ABORTED);
|
|
1891
|
+
else externalSignal?.addEventListener("abort", () => resolve(ABORTED), { once: true });
|
|
1892
|
+
});
|
|
1861
1893
|
try {
|
|
1862
1894
|
const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay);
|
|
1863
1895
|
const consume = streamSdkEvents(messages, worker2);
|
|
1864
1896
|
void consume.catch(() => {
|
|
1865
1897
|
});
|
|
1866
|
-
const outcome = await Promise.race([consume, timeout]);
|
|
1898
|
+
const outcome = await Promise.race([consume, timeout, interrupted]);
|
|
1899
|
+
if (outcome === ABORTED) {
|
|
1900
|
+
log("[abort] interrupted by user");
|
|
1901
|
+
abort.abort();
|
|
1902
|
+
await worker2.emitEvent("turn_error", { message: "interrupted by user" });
|
|
1903
|
+
await worker2.emitEvent("turn_complete", {});
|
|
1904
|
+
return -1;
|
|
1905
|
+
}
|
|
1867
1906
|
if (outcome === TIMEOUT) {
|
|
1868
1907
|
log(`[watchdog] turn exceeded ${ceiling}ms \u2014 aborting claude`);
|
|
1869
1908
|
abort.abort();
|
|
@@ -1884,7 +1923,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
1884
1923
|
};
|
|
1885
1924
|
|
|
1886
1925
|
// src/session/runner.ts
|
|
1887
|
-
var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onReady) => {
|
|
1926
|
+
var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onInterrupt, onReady) => {
|
|
1888
1927
|
const es = new ESCtor(url);
|
|
1889
1928
|
es.onopen = onReady;
|
|
1890
1929
|
es.onmessage = (e) => {
|
|
@@ -1893,6 +1932,8 @@ var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onReady) => {
|
|
|
1893
1932
|
if (seen.has(ev.sequence)) return;
|
|
1894
1933
|
seen.add(ev.sequence);
|
|
1895
1934
|
if (ev.type === "user_message") onUserMessage(ev);
|
|
1935
|
+
else if (ev.type === "system" && ev.payload?.action === "interrupt")
|
|
1936
|
+
onInterrupt();
|
|
1896
1937
|
} catch {
|
|
1897
1938
|
}
|
|
1898
1939
|
};
|
|
@@ -1914,7 +1955,7 @@ var restoreTty = () => {
|
|
|
1914
1955
|
};
|
|
1915
1956
|
var runDaemon = async (config, deps, signal) => {
|
|
1916
1957
|
const log = deps.log ?? ((m) => console.log(`[#${config.sessionId} ${config.name}] ${m}`));
|
|
1917
|
-
const qf = deps.queryFn ??
|
|
1958
|
+
const qf = deps.queryFn ?? query;
|
|
1918
1959
|
const ESCtor = deps.eventSourceImpl ?? EventSource;
|
|
1919
1960
|
log(`bin: ${claudeExecutable() ?? "(sdk bundled)"} cwd: ${config.worktreePath}`);
|
|
1920
1961
|
log(`runtime env keys: ${maskedEnvKeys(deps.env)}`);
|
|
@@ -1925,6 +1966,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
1925
1966
|
const pendingQueue = [];
|
|
1926
1967
|
let busy = false;
|
|
1927
1968
|
let lastActivity = Date.now();
|
|
1969
|
+
let currentTurn;
|
|
1928
1970
|
const drain = async () => {
|
|
1929
1971
|
if (busy) return;
|
|
1930
1972
|
busy = true;
|
|
@@ -1934,6 +1976,8 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
1934
1976
|
if (!msg) break;
|
|
1935
1977
|
const resuming = state.firstSpawnDone;
|
|
1936
1978
|
log(`\u25B6 msg #${msg.sequence} (${resuming ? "resume" : "first"})`);
|
|
1979
|
+
const turnAbort = new AbortController();
|
|
1980
|
+
currentTurn = turnAbort;
|
|
1937
1981
|
const code = await runTurn(
|
|
1938
1982
|
config,
|
|
1939
1983
|
deps.worker,
|
|
@@ -1942,8 +1986,10 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
1942
1986
|
qf,
|
|
1943
1987
|
log,
|
|
1944
1988
|
deps.env,
|
|
1945
|
-
deps.fetchImpl
|
|
1989
|
+
deps.fetchImpl,
|
|
1990
|
+
turnAbort.signal
|
|
1946
1991
|
);
|
|
1992
|
+
currentTurn = void 0;
|
|
1947
1993
|
state.firstSpawnDone = true;
|
|
1948
1994
|
lastActivity = Date.now();
|
|
1949
1995
|
log(`\u2714 msg #${msg.sequence}${code === 0 ? "" : ` (exit ${code})`}`);
|
|
@@ -1964,6 +2010,13 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
1964
2010
|
pendingQueue.push(ev);
|
|
1965
2011
|
void drain();
|
|
1966
2012
|
},
|
|
2013
|
+
() => {
|
|
2014
|
+
lastActivity = Date.now();
|
|
2015
|
+
if (currentTurn) {
|
|
2016
|
+
log("interrupt \u2014 aborting current turn");
|
|
2017
|
+
currentTurn.abort();
|
|
2018
|
+
}
|
|
2019
|
+
},
|
|
1967
2020
|
// Report active only now that we're subscribed — before this, a message
|
|
1968
2021
|
// sent right after spawn would be missed (no replay on the live stream).
|
|
1969
2022
|
() => {
|
|
@@ -2144,6 +2197,7 @@ var session = defineCommand({
|
|
|
2144
2197
|
resume: sessionResumeCommand,
|
|
2145
2198
|
stop: sessionStopCommand,
|
|
2146
2199
|
clear: sessionClearCommand,
|
|
2200
|
+
abort: sessionAbortCommand,
|
|
2147
2201
|
rename: sessionRenameCommand,
|
|
2148
2202
|
rm: sessionRmCommand,
|
|
2149
2203
|
send: sessionSendCommand,
|
|
@@ -2297,10 +2351,10 @@ import { hostname as osHostname } from "node:os";
|
|
|
2297
2351
|
// src/worker/daemon.ts
|
|
2298
2352
|
import { spawn } from "node:child_process";
|
|
2299
2353
|
import { randomUUID } from "node:crypto";
|
|
2300
|
-
import { existsSync as
|
|
2354
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2301
2355
|
import { dirname as dirname2, join as join5 } from "node:path";
|
|
2302
2356
|
import { fileURLToPath } from "node:url";
|
|
2303
|
-
import { query as
|
|
2357
|
+
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
2304
2358
|
|
|
2305
2359
|
// src/session/runner/title.ts
|
|
2306
2360
|
var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
|
|
@@ -2347,10 +2401,10 @@ ${exchange}`;
|
|
|
2347
2401
|
|
|
2348
2402
|
// src/session/worktree.ts
|
|
2349
2403
|
import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
2350
|
-
import { existsSync as
|
|
2404
|
+
import { existsSync as existsSync4, mkdirSync } from "node:fs";
|
|
2351
2405
|
import { dirname } from "node:path";
|
|
2352
2406
|
var createWorktree = (input) => {
|
|
2353
|
-
if (
|
|
2407
|
+
if (existsSync4(input.worktreePath))
|
|
2354
2408
|
throw new Error(`worktree path already exists: ${input.worktreePath}`);
|
|
2355
2409
|
mkdirSync(dirname(input.worktreePath), { recursive: true });
|
|
2356
2410
|
const branch = `baton/${input.sessionCode}`;
|
|
@@ -2372,7 +2426,7 @@ var repoHeadBranch = (repo) => {
|
|
|
2372
2426
|
return branch || "main";
|
|
2373
2427
|
};
|
|
2374
2428
|
var removeWorktree = (repo, worktreePath) => {
|
|
2375
|
-
if (!
|
|
2429
|
+
if (!existsSync4(worktreePath)) return;
|
|
2376
2430
|
try {
|
|
2377
2431
|
execFileSync("git", ["-C", repo, "worktree", "remove", "--force", worktreePath], {
|
|
2378
2432
|
stdio: "pipe"
|
|
@@ -2385,7 +2439,7 @@ var removeWorktree = (repo, worktreePath) => {
|
|
|
2385
2439
|
var binPath = () => {
|
|
2386
2440
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
2387
2441
|
const devShim = join5(here, "..", "..", "bin", "baton.mjs");
|
|
2388
|
-
return
|
|
2442
|
+
return existsSync5(devShim) ? devShim : fileURLToPath(import.meta.url);
|
|
2389
2443
|
};
|
|
2390
2444
|
var runWorkerDaemon = async (cfg, client, signal) => {
|
|
2391
2445
|
const repo = process.cwd();
|
|
@@ -2466,7 +2520,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2466
2520
|
worktreePath,
|
|
2467
2521
|
userText: exchange.userText,
|
|
2468
2522
|
assistantText: exchange.assistantText,
|
|
2469
|
-
queryFn:
|
|
2523
|
+
queryFn: query2
|
|
2470
2524
|
});
|
|
2471
2525
|
if (!title) return log(`title #${sessionId}: not enough to title yet, skipping`);
|
|
2472
2526
|
await client.sessions.setName(sessionId, title, cfg.apiToken);
|
|
@@ -2522,12 +2576,12 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2522
2576
|
|
|
2523
2577
|
// src/worker/machine-id.ts
|
|
2524
2578
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2525
|
-
import { existsSync as
|
|
2579
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2526
2580
|
import { homedir as homedir3 } from "node:os";
|
|
2527
2581
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
2528
2582
|
var machineIdPath = (env = process.env) => join6(env.XDG_DATA_HOME ?? join6(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
|
|
2529
2583
|
var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
2530
|
-
if (
|
|
2584
|
+
if (existsSync6(path)) {
|
|
2531
2585
|
const s = readFileSync3(path, "utf8").trim();
|
|
2532
2586
|
if (s) return s;
|
|
2533
2587
|
}
|