@lesscap/baton-cli 0.1.2 → 0.1.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 +84 -27
- package/package.json +11 -10
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" },
|
|
@@ -1709,6 +1726,17 @@ var readFirstExchange = (agentSessionId) => {
|
|
|
1709
1726
|
}
|
|
1710
1727
|
};
|
|
1711
1728
|
|
|
1729
|
+
// ../shared/src/domain/attachment.ts
|
|
1730
|
+
var isImageAttachment = (att) => att.contentType.startsWith("image/");
|
|
1731
|
+
var labelAttachments = (attachments) => {
|
|
1732
|
+
const counters = { image: 0, file: 0 };
|
|
1733
|
+
return attachments.map((att) => {
|
|
1734
|
+
const kind = isImageAttachment(att) ? "image" : "file";
|
|
1735
|
+
counters[kind] += 1;
|
|
1736
|
+
return `${kind}-${counters[kind]}`;
|
|
1737
|
+
});
|
|
1738
|
+
};
|
|
1739
|
+
|
|
1712
1740
|
// src/session/runner/attachments.ts
|
|
1713
1741
|
import { createWriteStream } from "node:fs";
|
|
1714
1742
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
@@ -1749,11 +1777,11 @@ var materializeAttachments = async (opts) => {
|
|
|
1749
1777
|
}
|
|
1750
1778
|
return relPaths;
|
|
1751
1779
|
};
|
|
1752
|
-
var augmentPrompt = (text, relPaths) => {
|
|
1780
|
+
var augmentPrompt = (text, relPaths, labels) => {
|
|
1753
1781
|
if (relPaths.length === 0) return text;
|
|
1754
1782
|
const header = [
|
|
1755
|
-
"[Attached files \u2014 already saved in the working directory:]",
|
|
1756
|
-
...relPaths.map((p) => `- ${p}`)
|
|
1783
|
+
"[Attached files \u2014 already saved in the working directory. Reference them by label:]",
|
|
1784
|
+
...relPaths.map((p, i) => `- {${labels[i]}} ${p}`)
|
|
1757
1785
|
].join("\n");
|
|
1758
1786
|
return text.length > 0 ? `${header}
|
|
1759
1787
|
|
|
@@ -1819,10 +1847,15 @@ var streamSdkEvents = async (stdout, worker2) => {
|
|
|
1819
1847
|
};
|
|
1820
1848
|
|
|
1821
1849
|
// src/session/runner/turn.ts
|
|
1822
|
-
var
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1850
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1851
|
+
var turnTimeoutMs = () => Number(process.env.BATON_TURN_TIMEOUT_MS) || 30 * 6e4;
|
|
1852
|
+
var DRAIN_GRACE_MS = 3e3;
|
|
1853
|
+
var waitExit = (child) => {
|
|
1854
|
+
if (typeof child.exitCode === "number") return Promise.resolve(child.exitCode);
|
|
1855
|
+
if (typeof child.signalCode === "string") return Promise.resolve(-1);
|
|
1856
|
+
return new Promise((resolve) => child.once("exit", (code) => resolve(code ?? -1)));
|
|
1857
|
+
};
|
|
1858
|
+
var finalizeTurn = async (exitCode, tail, worker2, log) => {
|
|
1826
1859
|
log(`[exit] code=${exitCode}`);
|
|
1827
1860
|
const stderrTail = tail.toString();
|
|
1828
1861
|
if (exitCode !== 0) {
|
|
@@ -1857,7 +1890,7 @@ var runTurn = async (config, worker2, msg, resuming, spawnImpl, log = (m) => con
|
|
|
1857
1890
|
fetchImpl
|
|
1858
1891
|
});
|
|
1859
1892
|
log(`[attach] downloaded ${relPaths.length} file(s) \u2192 ${config.worktreePath}/attachments`);
|
|
1860
|
-
text = augmentPrompt(rawText, relPaths);
|
|
1893
|
+
text = augmentPrompt(rawText, relPaths, labelAttachments(attachments));
|
|
1861
1894
|
} catch (err) {
|
|
1862
1895
|
const message = err instanceof Error ? err.message : String(err);
|
|
1863
1896
|
await worker2.emitEvent("turn_error", { message: `attachment download failed: ${message}` });
|
|
@@ -1866,14 +1899,37 @@ var runTurn = async (config, worker2, msg, resuming, spawnImpl, log = (m) => con
|
|
|
1866
1899
|
}
|
|
1867
1900
|
const result = await spawnClaude(config, worker2, text, resuming, spawnImpl, log, envOverlay);
|
|
1868
1901
|
if (!result) return -1;
|
|
1902
|
+
const { child, tail } = result;
|
|
1903
|
+
const stdout = child.stdout;
|
|
1904
|
+
const exited = waitExit(child);
|
|
1905
|
+
let timedOut = false;
|
|
1906
|
+
const ceiling = turnTimeoutMs();
|
|
1907
|
+
const watchdog = setTimeout(() => {
|
|
1908
|
+
timedOut = true;
|
|
1909
|
+
log(`[watchdog] turn exceeded ${ceiling}ms \u2014 killing claude`);
|
|
1910
|
+
child.kill("SIGKILL");
|
|
1911
|
+
}, ceiling);
|
|
1869
1912
|
try {
|
|
1870
|
-
|
|
1871
|
-
|
|
1913
|
+
const streaming = streamSdkEvents(stdout, worker2);
|
|
1914
|
+
void streaming.catch(() => {
|
|
1915
|
+
});
|
|
1916
|
+
await Promise.race([streaming, exited.then(() => delay(DRAIN_GRACE_MS))]);
|
|
1917
|
+
stdout.destroy();
|
|
1918
|
+
const exitCode = await exited;
|
|
1919
|
+
if (timedOut) {
|
|
1920
|
+
await worker2.emitEvent("turn_error", {
|
|
1921
|
+
message: `turn exceeded ${ceiling}ms \u2014 claude killed`,
|
|
1922
|
+
stderrTail: tail.toString()
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
return await finalizeTurn(exitCode, tail, worker2, log);
|
|
1872
1926
|
} catch (err) {
|
|
1873
1927
|
const message = err instanceof Error ? err.message : String(err);
|
|
1874
1928
|
log(`[run] ${message}`);
|
|
1875
|
-
await worker2.emitEvent("turn_error", { message, stderrTail:
|
|
1929
|
+
await worker2.emitEvent("turn_error", { message, stderrTail: tail.toString() });
|
|
1876
1930
|
return -1;
|
|
1931
|
+
} finally {
|
|
1932
|
+
clearTimeout(watchdog);
|
|
1877
1933
|
}
|
|
1878
1934
|
};
|
|
1879
1935
|
|
|
@@ -2137,6 +2193,7 @@ var session = defineCommand({
|
|
|
2137
2193
|
create: sessionCreateCommand,
|
|
2138
2194
|
resume: sessionResumeCommand,
|
|
2139
2195
|
stop: sessionStopCommand,
|
|
2196
|
+
clear: sessionClearCommand,
|
|
2140
2197
|
rename: sessionRenameCommand,
|
|
2141
2198
|
rm: sessionRmCommand,
|
|
2142
2199
|
send: sessionSendCommand,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lesscap/baton-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "baton CLI — register/run a worker, manage projects/requirements/tasks/sessions",
|
|
6
6
|
"bin": {
|
|
@@ -9,19 +9,20 @@
|
|
|
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 --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"
|
|
20
|
-
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"baton": "tsx src/index.ts",
|
|
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'"
|
|
26
|
+
"typescript": "^6.0.3"
|
|
26
27
|
}
|
|
27
|
-
}
|
|
28
|
+
}
|