@lesscap/baton-cli 0.1.2 → 0.2.0
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 +169 -174
- package/package.json +13 -9
package/dist/baton.mjs
CHANGED
|
@@ -577,6 +577,7 @@ var sessionsClient = (baseUrl) => {
|
|
|
577
577
|
}),
|
|
578
578
|
resume: (id) => request(u(`/sessions/${id}/resume`), { method: "POST" }),
|
|
579
579
|
stop: (id) => request(u(`/sessions/${id}/stop`), { method: "POST" }),
|
|
580
|
+
clear: (id) => request(u(`/sessions/${id}/clear`), { method: "POST" }),
|
|
580
581
|
rename: (id, name) => request(u(`/sessions/${id}/rename`), { method: "POST", body: { name } }),
|
|
581
582
|
listByProject: (projectId) => request(u(`/projects/${projectId}/sessions`), { method: "GET" }),
|
|
582
583
|
get: (id) => request(u(`/sessions/${id}`), { method: "GET" }),
|
|
@@ -1018,6 +1019,38 @@ var requirement = defineCommand({
|
|
|
1018
1019
|
}
|
|
1019
1020
|
});
|
|
1020
1021
|
|
|
1022
|
+
// src/commands/session/shared.ts
|
|
1023
|
+
import { homedir } from "node:os";
|
|
1024
|
+
import { join as join2 } from "node:path";
|
|
1025
|
+
var defaultWorktreeDir = (env = process.env) => env.BATON_WORKTREE_DIR ?? join2(env.XDG_DATA_HOME ?? join2(env.HOME ?? homedir(), ".local/share"), "baton", "worktrees");
|
|
1026
|
+
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
1027
|
+
var resolveSession = async (client, projectId, handle) => {
|
|
1028
|
+
const asInt = Number(handle);
|
|
1029
|
+
if (Number.isInteger(asInt) && asInt > 0) {
|
|
1030
|
+
const byId = await client.sessions.get(asInt).catch(() => null);
|
|
1031
|
+
if (byId) return { id: byId.id, name: byId.name };
|
|
1032
|
+
}
|
|
1033
|
+
const byName = await client.sessions.findByName(projectId, handle);
|
|
1034
|
+
if (byName) return { id: byName.id, name: byName.name };
|
|
1035
|
+
throw new Error(`session "${handle}" not found in project ${projectId}`);
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
// src/commands/session/clear.ts
|
|
1039
|
+
var sessionClearCommand = defineCommand({
|
|
1040
|
+
meta: { name: "clear", description: "reset the conversation context (keeps the session + worktree)" },
|
|
1041
|
+
args: {
|
|
1042
|
+
session: { type: "positional", required: true, description: "session int id or name" },
|
|
1043
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
1044
|
+
...common
|
|
1045
|
+
},
|
|
1046
|
+
run: async ({ args }) => {
|
|
1047
|
+
const c = clientFor(args);
|
|
1048
|
+
const handle = await resolveSession(c, resolveProjectId(args), args.session);
|
|
1049
|
+
const s = await c.sessions.clear(handle.id);
|
|
1050
|
+
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1021
1054
|
// src/commands/session/create.ts
|
|
1022
1055
|
var sessionCreateCommand = defineCommand({
|
|
1023
1056
|
meta: { name: "create", description: "create + start a session on a worker" },
|
|
@@ -1038,22 +1071,6 @@ var sessionCreateCommand = defineCommand({
|
|
|
1038
1071
|
}
|
|
1039
1072
|
});
|
|
1040
1073
|
|
|
1041
|
-
// src/commands/session/shared.ts
|
|
1042
|
-
import { homedir } from "node:os";
|
|
1043
|
-
import { join as join2 } from "node:path";
|
|
1044
|
-
var defaultWorktreeDir = (env = process.env) => env.BATON_WORKTREE_DIR ?? join2(env.XDG_DATA_HOME ?? join2(env.HOME ?? homedir(), ".local/share"), "baton", "worktrees");
|
|
1045
|
-
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
1046
|
-
var resolveSession = async (client, projectId, handle) => {
|
|
1047
|
-
const asInt = Number(handle);
|
|
1048
|
-
if (Number.isInteger(asInt) && asInt > 0) {
|
|
1049
|
-
const byId = await client.sessions.get(asInt).catch(() => null);
|
|
1050
|
-
if (byId) return { id: byId.id, name: byId.name };
|
|
1051
|
-
}
|
|
1052
|
-
const byName = await client.sessions.findByName(projectId, handle);
|
|
1053
|
-
if (byName) return { id: byName.id, name: byName.name };
|
|
1054
|
-
throw new Error(`session "${handle}" not found in project ${projectId}`);
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
1074
|
// src/commands/session/get.ts
|
|
1058
1075
|
var sessionGetCommand = defineCommand({
|
|
1059
1076
|
meta: { name: "get", description: "get a session by int id or name" },
|
|
@@ -1620,34 +1637,15 @@ function getBaseURL() {
|
|
|
1620
1637
|
}
|
|
1621
1638
|
|
|
1622
1639
|
// src/session/runner.ts
|
|
1623
|
-
import {
|
|
1640
|
+
import { spawnSync } from "node:child_process";
|
|
1641
|
+
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
1624
1642
|
|
|
1625
1643
|
// src/session/runner/log.ts
|
|
1626
|
-
var claudeBin = () => process.env.BATON_CLAUDE_BIN ?? "claude";
|
|
1627
|
-
var buildClaudeArgs = (claudeSessionId, text, resuming) => [
|
|
1628
|
-
"--print",
|
|
1629
|
-
resuming ? "--resume" : "--session-id",
|
|
1630
|
-
claudeSessionId,
|
|
1631
|
-
"--output-format",
|
|
1632
|
-
"stream-json",
|
|
1633
|
-
"--verbose",
|
|
1634
|
-
"--dangerously-skip-permissions",
|
|
1635
|
-
text
|
|
1636
|
-
];
|
|
1637
|
-
var STDERR_TAIL_BYTES = 2048;
|
|
1638
|
-
var tailBuffer = (cap = STDERR_TAIL_BYTES) => {
|
|
1639
|
-
let buf = "";
|
|
1640
|
-
return {
|
|
1641
|
-
append(chunk) {
|
|
1642
|
-
buf = (buf + chunk).slice(-cap);
|
|
1643
|
-
},
|
|
1644
|
-
toString() {
|
|
1645
|
-
return buf;
|
|
1646
|
-
}
|
|
1647
|
-
};
|
|
1648
|
-
};
|
|
1649
1644
|
var maskedEnvKeys = (env) => env ? Object.keys(env).sort().join(", ") : "(none)";
|
|
1650
|
-
|
|
1645
|
+
|
|
1646
|
+
// src/session/runner/sdk-env.ts
|
|
1647
|
+
var buildSdkEnv = (overlay) => overlay ? { ...process.env, ...overlay } : void 0;
|
|
1648
|
+
var claudeExecutable = () => process.env.BATON_CLAUDE_BIN;
|
|
1651
1649
|
|
|
1652
1650
|
// src/session/runner/transcript.ts
|
|
1653
1651
|
import { readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -1709,6 +1707,17 @@ var readFirstExchange = (agentSessionId) => {
|
|
|
1709
1707
|
}
|
|
1710
1708
|
};
|
|
1711
1709
|
|
|
1710
|
+
// ../shared/src/domain/attachment.ts
|
|
1711
|
+
var isImageAttachment = (att) => att.contentType.startsWith("image/");
|
|
1712
|
+
var labelAttachments = (attachments) => {
|
|
1713
|
+
const counters = { image: 0, file: 0 };
|
|
1714
|
+
return attachments.map((att) => {
|
|
1715
|
+
const kind = isImageAttachment(att) ? "image" : "file";
|
|
1716
|
+
counters[kind] += 1;
|
|
1717
|
+
return `${kind}-${counters[kind]}`;
|
|
1718
|
+
});
|
|
1719
|
+
};
|
|
1720
|
+
|
|
1712
1721
|
// src/session/runner/attachments.ts
|
|
1713
1722
|
import { createWriteStream } from "node:fs";
|
|
1714
1723
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
@@ -1749,96 +1758,84 @@ var materializeAttachments = async (opts) => {
|
|
|
1749
1758
|
}
|
|
1750
1759
|
return relPaths;
|
|
1751
1760
|
};
|
|
1752
|
-
var augmentPrompt = (text, relPaths) => {
|
|
1761
|
+
var augmentPrompt = (text, relPaths, labels) => {
|
|
1753
1762
|
if (relPaths.length === 0) return text;
|
|
1754
1763
|
const header = [
|
|
1755
|
-
"[Attached files \u2014 already saved in the working directory:]",
|
|
1756
|
-
...relPaths.map((p) => `- ${p}`)
|
|
1764
|
+
"[Attached files \u2014 already saved in the working directory. Reference them by label:]",
|
|
1765
|
+
...relPaths.map((p, i) => `- {${labels[i]}} ${p}`)
|
|
1757
1766
|
].join("\n");
|
|
1758
1767
|
return text.length > 0 ? `${header}
|
|
1759
1768
|
|
|
1760
1769
|
${text}` : header;
|
|
1761
1770
|
};
|
|
1762
1771
|
|
|
1763
|
-
// src/session/runner/
|
|
1764
|
-
import
|
|
1765
|
-
var
|
|
1766
|
-
const
|
|
1767
|
-
const
|
|
1768
|
-
const
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
} catch (err) {
|
|
1780
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1781
|
-
log(`[spawn] failed: ${message}`);
|
|
1782
|
-
await worker2.emitEvent("turn_error", { message: `spawn failed: ${message}` });
|
|
1783
|
-
return null;
|
|
1784
|
-
}
|
|
1785
|
-
if (!child.stdout) {
|
|
1786
|
-
log("[spawn] no stdout from claude \u2014 aborting turn");
|
|
1787
|
-
await worker2.emitEvent("turn_error", { message: "no stdout from claude" });
|
|
1788
|
-
return null;
|
|
1789
|
-
}
|
|
1790
|
-
const tail = tailBuffer();
|
|
1791
|
-
if (child.stderr) {
|
|
1792
|
-
const errLines = createInterface2({ input: child.stderr });
|
|
1793
|
-
void (async () => {
|
|
1794
|
-
for await (const line of errLines) {
|
|
1795
|
-
tail.append(`${line}
|
|
1796
|
-
`);
|
|
1797
|
-
log(`[claude] ${line}`);
|
|
1798
|
-
}
|
|
1799
|
-
})();
|
|
1800
|
-
}
|
|
1801
|
-
return { child, tail };
|
|
1772
|
+
// src/session/runner/query.ts
|
|
1773
|
+
import "@anthropic-ai/claude-agent-sdk";
|
|
1774
|
+
var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay) => {
|
|
1775
|
+
const env = buildSdkEnv(envOverlay);
|
|
1776
|
+
const exe = claudeExecutable();
|
|
1777
|
+
const options = {
|
|
1778
|
+
cwd: config.worktreePath,
|
|
1779
|
+
permissionMode: "bypassPermissions",
|
|
1780
|
+
includePartialMessages: false,
|
|
1781
|
+
abortController,
|
|
1782
|
+
stderr: (line) => log(`[claude] ${line.trimEnd()}`),
|
|
1783
|
+
...resuming ? { resume: config.agentSessionId } : { sessionId: config.agentSessionId },
|
|
1784
|
+
...env ? { env } : {},
|
|
1785
|
+
...exe ? { pathToClaudeCodeExecutable: exe } : {}
|
|
1786
|
+
};
|
|
1787
|
+
return queryFn({ prompt: text, options });
|
|
1802
1788
|
};
|
|
1803
1789
|
|
|
1804
1790
|
// src/session/runner/stream.ts
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
if (!trimmed) continue;
|
|
1811
|
-
let payload;
|
|
1812
|
-
try {
|
|
1813
|
-
payload = JSON.parse(trimmed);
|
|
1814
|
-
} catch {
|
|
1815
|
-
payload = { raw: trimmed };
|
|
1816
|
-
}
|
|
1817
|
-
await worker2.emitEvent("sdk_event", payload);
|
|
1791
|
+
var streamSdkEvents = async (messages, worker2) => {
|
|
1792
|
+
let result = null;
|
|
1793
|
+
for await (const message of messages) {
|
|
1794
|
+
await worker2.emitEvent("sdk_event", message);
|
|
1795
|
+
if (message.type === "result") result = toTurnResult(message);
|
|
1818
1796
|
}
|
|
1797
|
+
return result;
|
|
1798
|
+
};
|
|
1799
|
+
var toTurnResult = (message) => {
|
|
1800
|
+
const rec = message;
|
|
1801
|
+
const subtype = typeof rec.subtype === "string" ? rec.subtype : "unknown";
|
|
1802
|
+
const isError = rec.is_error === true || subtype !== "success";
|
|
1803
|
+
const resultText = typeof rec.result === "string" ? rec.result : "";
|
|
1804
|
+
return { subtype, isError, resultText };
|
|
1819
1805
|
};
|
|
1820
1806
|
|
|
1821
1807
|
// src/session/runner/turn.ts
|
|
1822
|
-
var
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
)
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1808
|
+
var turnTimeoutMs = () => Number(process.env.BATON_TURN_TIMEOUT_MS) || 30 * 6e4;
|
|
1809
|
+
var TIMEOUT = /* @__PURE__ */ Symbol("turn-timeout");
|
|
1810
|
+
var finalizeTurn = async (result, worker2, log) => {
|
|
1811
|
+
if (!result) {
|
|
1812
|
+
log("[exit] stream ended with no result");
|
|
1813
|
+
await worker2.emitEvent("turn_error", { message: "claude produced no result" });
|
|
1814
|
+
await worker2.emitEvent("turn_complete", {});
|
|
1815
|
+
return -1;
|
|
1816
|
+
}
|
|
1817
|
+
log(`[exit] subtype=${result.subtype} isError=${result.isError}`);
|
|
1818
|
+
if (result.isError) {
|
|
1829
1819
|
await worker2.emitEvent("turn_error", {
|
|
1830
|
-
message: `claude
|
|
1831
|
-
|
|
1832
|
-
stderrTail
|
|
1820
|
+
message: result.resultText || `claude result: ${result.subtype}`,
|
|
1821
|
+
subtype: result.subtype
|
|
1833
1822
|
});
|
|
1834
1823
|
}
|
|
1835
|
-
await worker2.emitEvent("turn_complete", {
|
|
1836
|
-
|
|
1837
|
-
|
|
1824
|
+
await worker2.emitEvent("turn_complete", { subtype: result.subtype });
|
|
1825
|
+
return result.isError ? 1 : 0;
|
|
1826
|
+
};
|
|
1827
|
+
var buildPrompt = async (config, rawText, attachments, log, fetchImpl) => {
|
|
1828
|
+
if (attachments.length === 0) return rawText;
|
|
1829
|
+
const relPaths = await materializeAttachments({
|
|
1830
|
+
worktreePath: config.worktreePath,
|
|
1831
|
+
serverBase: config.server,
|
|
1832
|
+
attachments,
|
|
1833
|
+
fetchImpl
|
|
1838
1834
|
});
|
|
1839
|
-
|
|
1835
|
+
log(`[attach] downloaded ${relPaths.length} file(s) \u2192 ${config.worktreePath}/attachments`);
|
|
1836
|
+
return augmentPrompt(rawText, relPaths, labelAttachments(attachments));
|
|
1840
1837
|
};
|
|
1841
|
-
var runTurn = async (config, worker2, msg, resuming,
|
|
1838
|
+
var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => console.log(m), envOverlay, fetchImpl) => {
|
|
1842
1839
|
await worker2.emitEvent("turn_start", { messageId: msg.id });
|
|
1843
1840
|
const payload = msg.payload;
|
|
1844
1841
|
const rawText = typeof payload?.text === "string" ? payload.text : "";
|
|
@@ -1847,33 +1844,42 @@ var runTurn = async (config, worker2, msg, resuming, spawnImpl, log = (m) => con
|
|
|
1847
1844
|
await worker2.emitEvent("turn_error", { message: "user_message missing text and attachments" });
|
|
1848
1845
|
return -1;
|
|
1849
1846
|
}
|
|
1850
|
-
let text
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
fetchImpl
|
|
1858
|
-
});
|
|
1859
|
-
log(`[attach] downloaded ${relPaths.length} file(s) \u2192 ${config.worktreePath}/attachments`);
|
|
1860
|
-
text = augmentPrompt(rawText, relPaths);
|
|
1861
|
-
} catch (err) {
|
|
1862
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1863
|
-
await worker2.emitEvent("turn_error", { message: `attachment download failed: ${message}` });
|
|
1864
|
-
return -1;
|
|
1865
|
-
}
|
|
1847
|
+
let text;
|
|
1848
|
+
try {
|
|
1849
|
+
text = await buildPrompt(config, rawText, attachments, log, fetchImpl);
|
|
1850
|
+
} catch (err) {
|
|
1851
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1852
|
+
await worker2.emitEvent("turn_error", { message: `attachment download failed: ${message}` });
|
|
1853
|
+
return -1;
|
|
1866
1854
|
}
|
|
1867
|
-
const
|
|
1868
|
-
|
|
1855
|
+
const abort = new AbortController();
|
|
1856
|
+
const ceiling = turnTimeoutMs();
|
|
1857
|
+
let timer;
|
|
1858
|
+
const timeout = new Promise((resolve) => {
|
|
1859
|
+
timer = setTimeout(() => resolve(TIMEOUT), ceiling);
|
|
1860
|
+
});
|
|
1869
1861
|
try {
|
|
1870
|
-
|
|
1871
|
-
|
|
1862
|
+
const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay);
|
|
1863
|
+
const consume = streamSdkEvents(messages, worker2);
|
|
1864
|
+
void consume.catch(() => {
|
|
1865
|
+
});
|
|
1866
|
+
const outcome = await Promise.race([consume, timeout]);
|
|
1867
|
+
if (outcome === TIMEOUT) {
|
|
1868
|
+
log(`[watchdog] turn exceeded ${ceiling}ms \u2014 aborting claude`);
|
|
1869
|
+
abort.abort();
|
|
1870
|
+
await worker2.emitEvent("turn_error", { message: `turn exceeded ${ceiling}ms \u2014 claude aborted` });
|
|
1871
|
+
await worker2.emitEvent("turn_complete", {});
|
|
1872
|
+
return -1;
|
|
1873
|
+
}
|
|
1874
|
+
return await finalizeTurn(outcome, worker2, log);
|
|
1872
1875
|
} catch (err) {
|
|
1873
1876
|
const message = err instanceof Error ? err.message : String(err);
|
|
1874
1877
|
log(`[run] ${message}`);
|
|
1875
|
-
await worker2.emitEvent("turn_error", { message
|
|
1878
|
+
await worker2.emitEvent("turn_error", { message });
|
|
1879
|
+
await worker2.emitEvent("turn_complete", {});
|
|
1876
1880
|
return -1;
|
|
1881
|
+
} finally {
|
|
1882
|
+
clearTimeout(timer);
|
|
1877
1883
|
}
|
|
1878
1884
|
};
|
|
1879
1885
|
|
|
@@ -1908,9 +1914,9 @@ var restoreTty = () => {
|
|
|
1908
1914
|
};
|
|
1909
1915
|
var runDaemon = async (config, deps, signal) => {
|
|
1910
1916
|
const log = deps.log ?? ((m) => console.log(`[#${config.sessionId} ${config.name}] ${m}`));
|
|
1911
|
-
const
|
|
1917
|
+
const qf = deps.queryFn ?? query2;
|
|
1912
1918
|
const ESCtor = deps.eventSourceImpl ?? EventSource;
|
|
1913
|
-
log(`bin: ${
|
|
1919
|
+
log(`bin: ${claudeExecutable() ?? "(sdk bundled)"} cwd: ${config.worktreePath}`);
|
|
1914
1920
|
log(`runtime env keys: ${maskedEnvKeys(deps.env)}`);
|
|
1915
1921
|
const state = {
|
|
1916
1922
|
firstSpawnDone: findTranscriptPath(config.agentSessionId) !== null,
|
|
@@ -1933,7 +1939,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
1933
1939
|
deps.worker,
|
|
1934
1940
|
msg,
|
|
1935
1941
|
resuming,
|
|
1936
|
-
|
|
1942
|
+
qf,
|
|
1937
1943
|
log,
|
|
1938
1944
|
deps.env,
|
|
1939
1945
|
deps.fetchImpl
|
|
@@ -2137,6 +2143,7 @@ var session = defineCommand({
|
|
|
2137
2143
|
create: sessionCreateCommand,
|
|
2138
2144
|
resume: sessionResumeCommand,
|
|
2139
2145
|
stop: sessionStopCommand,
|
|
2146
|
+
clear: sessionClearCommand,
|
|
2140
2147
|
rename: sessionRenameCommand,
|
|
2141
2148
|
rm: sessionRmCommand,
|
|
2142
2149
|
send: sessionSendCommand,
|
|
@@ -2288,11 +2295,12 @@ var task = defineCommand({
|
|
|
2288
2295
|
import { hostname as osHostname } from "node:os";
|
|
2289
2296
|
|
|
2290
2297
|
// src/worker/daemon.ts
|
|
2291
|
-
import { spawn
|
|
2298
|
+
import { spawn } from "node:child_process";
|
|
2292
2299
|
import { randomUUID } from "node:crypto";
|
|
2293
2300
|
import { existsSync as existsSync4 } from "node:fs";
|
|
2294
2301
|
import { dirname as dirname2, join as join5 } from "node:path";
|
|
2295
2302
|
import { fileURLToPath } from "node:url";
|
|
2303
|
+
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
2296
2304
|
|
|
2297
2305
|
// src/session/runner/title.ts
|
|
2298
2306
|
var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
|
|
@@ -2300,7 +2308,7 @@ var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NON
|
|
|
2300
2308
|
var sanitizeTitle = (raw) => raw.replace(/[\r\n]+/g, " ").replace(/["'`]/g, "").replace(/^\s*(title|session)\s*[:-]\s*/i, "").replace(/\s+/g, " ").trim().slice(0, 40).trim();
|
|
2301
2309
|
var TITLE_TIMEOUT_MS = 3e4;
|
|
2302
2310
|
var generateTitle = async (input) => {
|
|
2303
|
-
const { worktreePath, userText, assistantText,
|
|
2311
|
+
const { worktreePath, userText, assistantText, queryFn } = input;
|
|
2304
2312
|
if (`${userText}${assistantText}`.trim().length < 4) return null;
|
|
2305
2313
|
const exchange = [
|
|
2306
2314
|
`User: ${clip(userText, 800)}`,
|
|
@@ -2308,44 +2316,31 @@ var generateTitle = async (input) => {
|
|
|
2308
2316
|
].filter(Boolean).join("\n");
|
|
2309
2317
|
const prompt = `Reply with ONLY a 2-5 word title (no quotes, no punctuation) summarizing this coding session. If it is too short or generic to title meaningfully, reply with exactly NONE:
|
|
2310
2318
|
${exchange}`;
|
|
2311
|
-
const
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
};
|
|
2320
|
-
let child;
|
|
2321
|
-
try {
|
|
2322
|
-
child = spawnImpl(claudeBin(), ["--print", "--dangerously-skip-permissions", prompt], {
|
|
2319
|
+
const exe = claudeExecutable();
|
|
2320
|
+
const abort = new AbortController();
|
|
2321
|
+
const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
|
|
2322
|
+
let out = "";
|
|
2323
|
+
try {
|
|
2324
|
+
const messages = queryFn({
|
|
2325
|
+
prompt,
|
|
2326
|
+
options: {
|
|
2323
2327
|
cwd: worktreePath,
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
}
|
|
2330
|
-
const timer = setTimeout(() => {
|
|
2331
|
-
try {
|
|
2332
|
-
child.kill();
|
|
2333
|
-
} catch {
|
|
2328
|
+
permissionMode: "bypassPermissions",
|
|
2329
|
+
allowedTools: [],
|
|
2330
|
+
includePartialMessages: false,
|
|
2331
|
+
abortController: abort,
|
|
2332
|
+
...exe ? { pathToClaudeCodeExecutable: exe } : {}
|
|
2334
2333
|
}
|
|
2335
|
-
finish("");
|
|
2336
|
-
}, TITLE_TIMEOUT_MS);
|
|
2337
|
-
child.stdout?.on("data", (d) => {
|
|
2338
|
-
buf += String(d);
|
|
2339
|
-
});
|
|
2340
|
-
child.on("exit", () => {
|
|
2341
|
-
clearTimeout(timer);
|
|
2342
|
-
finish(buf);
|
|
2343
|
-
});
|
|
2344
|
-
child.on("error", () => {
|
|
2345
|
-
clearTimeout(timer);
|
|
2346
|
-
finish("");
|
|
2347
2334
|
});
|
|
2348
|
-
|
|
2335
|
+
for await (const m of messages) {
|
|
2336
|
+
if (m.type === "result" && typeof m.result === "string")
|
|
2337
|
+
out = m.result;
|
|
2338
|
+
}
|
|
2339
|
+
} catch {
|
|
2340
|
+
return null;
|
|
2341
|
+
} finally {
|
|
2342
|
+
clearTimeout(timer);
|
|
2343
|
+
}
|
|
2349
2344
|
const title = sanitizeTitle(out);
|
|
2350
2345
|
return !title || isDeclined(title) ? null : title;
|
|
2351
2346
|
};
|
|
@@ -2411,7 +2406,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2411
2406
|
};
|
|
2412
2407
|
const spawnChild = (sessionId, worktreePath) => {
|
|
2413
2408
|
if (children.has(sessionId)) return;
|
|
2414
|
-
const child =
|
|
2409
|
+
const child = spawn(process.execPath, [binPath(), "session", "run", String(sessionId)], {
|
|
2415
2410
|
detached: true,
|
|
2416
2411
|
stdio: "inherit",
|
|
2417
2412
|
env: {
|
|
@@ -2471,7 +2466,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2471
2466
|
worktreePath,
|
|
2472
2467
|
userText: exchange.userText,
|
|
2473
2468
|
assistantText: exchange.assistantText,
|
|
2474
|
-
|
|
2469
|
+
queryFn: query3
|
|
2475
2470
|
});
|
|
2476
2471
|
if (!title) return log(`title #${sessionId}: not enough to title yet, skipping`);
|
|
2477
2472
|
await client.sessions.setName(sessionId, title, cfg.apiToken);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lesscap/baton-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "baton CLI — register/run a worker, manage projects/requirements/tasks/sessions",
|
|
6
6
|
"bin": {
|
|
@@ -9,19 +9,23 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"baton": "tsx src/index.ts",
|
|
14
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --packages=bundle --external:@anthropic-ai/claude-agent-sdk --banner:js=\"#!/usr/bin/env node\" --outfile=dist/baton.mjs && chmod +x dist/baton.mjs",
|
|
15
|
+
"prepublishOnly": "pnpm build",
|
|
16
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
17
|
+
"test": "tsx --test 'src/**/*.test.ts'"
|
|
18
|
+
},
|
|
12
19
|
"devDependencies": {
|
|
20
|
+
"@baton/shared": "workspace:*",
|
|
13
21
|
"@types/node": "^25.9.1",
|
|
14
22
|
"citty": "^0.2.2",
|
|
15
23
|
"esbuild": "^0.28.0",
|
|
16
24
|
"eventsource": "^4.1.0",
|
|
17
25
|
"tsx": "^4.22.4",
|
|
18
|
-
"typescript": "^6.0.3"
|
|
19
|
-
"@baton/shared": "0.0.0"
|
|
26
|
+
"typescript": "^6.0.3"
|
|
20
27
|
},
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --packages=bundle --banner:js=\"#!/usr/bin/env node\" --outfile=dist/baton.mjs && chmod +x dist/baton.mjs",
|
|
24
|
-
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
25
|
-
"test": "tsx --test 'src/**/*.test.ts'"
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.161"
|
|
26
30
|
}
|
|
27
|
-
}
|
|
31
|
+
}
|