@lesscap/baton-cli 0.1.1 → 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 +103 -30
- package/package.json +11 -10
package/dist/baton.mjs
CHANGED
|
@@ -498,6 +498,11 @@ var primed = false;
|
|
|
498
498
|
var primeLogin = (baseUrl) => {
|
|
499
499
|
if (primed) return;
|
|
500
500
|
primed = true;
|
|
501
|
+
const token = process.env.BATON_TOKEN;
|
|
502
|
+
if (token) {
|
|
503
|
+
setAuthHeaders({ authorization: `Bearer ${token}` });
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
501
506
|
const username = process.env.BATON_USER;
|
|
502
507
|
const password = process.env.BATON_PASS;
|
|
503
508
|
if (!username || !password) return;
|
|
@@ -508,7 +513,7 @@ var primeLogin = (baseUrl) => {
|
|
|
508
513
|
headers: { "content-type": "application/json" },
|
|
509
514
|
body: JSON.stringify({ username, password })
|
|
510
515
|
});
|
|
511
|
-
const cookie = (res.headers.
|
|
516
|
+
const cookie = (res.headers.getSetCookie()[0] ?? "").split(";")[0];
|
|
512
517
|
if (res.ok && cookie) setAuthHeaders({ cookie });
|
|
513
518
|
})().catch(() => {
|
|
514
519
|
})
|
|
@@ -572,6 +577,7 @@ var sessionsClient = (baseUrl) => {
|
|
|
572
577
|
}),
|
|
573
578
|
resume: (id) => request(u(`/sessions/${id}/resume`), { method: "POST" }),
|
|
574
579
|
stop: (id) => request(u(`/sessions/${id}/stop`), { method: "POST" }),
|
|
580
|
+
clear: (id) => request(u(`/sessions/${id}/clear`), { method: "POST" }),
|
|
575
581
|
rename: (id, name) => request(u(`/sessions/${id}/rename`), { method: "POST", body: { name } }),
|
|
576
582
|
listByProject: (projectId) => request(u(`/projects/${projectId}/sessions`), { method: "GET" }),
|
|
577
583
|
get: (id) => request(u(`/sessions/${id}`), { method: "GET" }),
|
|
@@ -735,6 +741,12 @@ var resolveBaseUrl = (urlArg, env = process.env) => {
|
|
|
735
741
|
if (cfg?.server) return cfg.server;
|
|
736
742
|
return env.BATON_URL ?? "http://localhost:3280";
|
|
737
743
|
};
|
|
744
|
+
var resolveBaseUrlOrNull = (urlArg, env = process.env) => {
|
|
745
|
+
if (urlArg) return urlArg;
|
|
746
|
+
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
747
|
+
if (cfg?.server) return cfg.server;
|
|
748
|
+
return env.BATON_URL ?? null;
|
|
749
|
+
};
|
|
738
750
|
|
|
739
751
|
// src/util.ts
|
|
740
752
|
var common = {
|
|
@@ -1007,6 +1019,38 @@ var requirement = defineCommand({
|
|
|
1007
1019
|
}
|
|
1008
1020
|
});
|
|
1009
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
|
+
|
|
1010
1054
|
// src/commands/session/create.ts
|
|
1011
1055
|
var sessionCreateCommand = defineCommand({
|
|
1012
1056
|
meta: { name: "create", description: "create + start a session on a worker" },
|
|
@@ -1027,22 +1071,6 @@ var sessionCreateCommand = defineCommand({
|
|
|
1027
1071
|
}
|
|
1028
1072
|
});
|
|
1029
1073
|
|
|
1030
|
-
// src/commands/session/shared.ts
|
|
1031
|
-
import { homedir } from "node:os";
|
|
1032
|
-
import { join as join2 } from "node:path";
|
|
1033
|
-
var defaultWorktreeDir = (env = process.env) => env.BATON_WORKTREE_DIR ?? join2(env.XDG_DATA_HOME ?? join2(env.HOME ?? homedir(), ".local/share"), "baton", "worktrees");
|
|
1034
|
-
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
1035
|
-
var resolveSession = async (client, projectId, handle) => {
|
|
1036
|
-
const asInt = Number(handle);
|
|
1037
|
-
if (Number.isInteger(asInt) && asInt > 0) {
|
|
1038
|
-
const byId = await client.sessions.get(asInt).catch(() => null);
|
|
1039
|
-
if (byId) return { id: byId.id, name: byId.name };
|
|
1040
|
-
}
|
|
1041
|
-
const byName = await client.sessions.findByName(projectId, handle);
|
|
1042
|
-
if (byName) return { id: byName.id, name: byName.name };
|
|
1043
|
-
throw new Error(`session "${handle}" not found in project ${projectId}`);
|
|
1044
|
-
};
|
|
1045
|
-
|
|
1046
1074
|
// src/commands/session/get.ts
|
|
1047
1075
|
var sessionGetCommand = defineCommand({
|
|
1048
1076
|
meta: { name: "get", description: "get a session by int id or name" },
|
|
@@ -1698,6 +1726,17 @@ var readFirstExchange = (agentSessionId) => {
|
|
|
1698
1726
|
}
|
|
1699
1727
|
};
|
|
1700
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
|
+
|
|
1701
1740
|
// src/session/runner/attachments.ts
|
|
1702
1741
|
import { createWriteStream } from "node:fs";
|
|
1703
1742
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
@@ -1738,11 +1777,11 @@ var materializeAttachments = async (opts) => {
|
|
|
1738
1777
|
}
|
|
1739
1778
|
return relPaths;
|
|
1740
1779
|
};
|
|
1741
|
-
var augmentPrompt = (text, relPaths) => {
|
|
1780
|
+
var augmentPrompt = (text, relPaths, labels) => {
|
|
1742
1781
|
if (relPaths.length === 0) return text;
|
|
1743
1782
|
const header = [
|
|
1744
|
-
"[Attached files \u2014 already saved in the working directory:]",
|
|
1745
|
-
...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}`)
|
|
1746
1785
|
].join("\n");
|
|
1747
1786
|
return text.length > 0 ? `${header}
|
|
1748
1787
|
|
|
@@ -1808,10 +1847,15 @@ var streamSdkEvents = async (stdout, worker2) => {
|
|
|
1808
1847
|
};
|
|
1809
1848
|
|
|
1810
1849
|
// src/session/runner/turn.ts
|
|
1811
|
-
var
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
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) => {
|
|
1815
1859
|
log(`[exit] code=${exitCode}`);
|
|
1816
1860
|
const stderrTail = tail.toString();
|
|
1817
1861
|
if (exitCode !== 0) {
|
|
@@ -1846,7 +1890,7 @@ var runTurn = async (config, worker2, msg, resuming, spawnImpl, log = (m) => con
|
|
|
1846
1890
|
fetchImpl
|
|
1847
1891
|
});
|
|
1848
1892
|
log(`[attach] downloaded ${relPaths.length} file(s) \u2192 ${config.worktreePath}/attachments`);
|
|
1849
|
-
text = augmentPrompt(rawText, relPaths);
|
|
1893
|
+
text = augmentPrompt(rawText, relPaths, labelAttachments(attachments));
|
|
1850
1894
|
} catch (err) {
|
|
1851
1895
|
const message = err instanceof Error ? err.message : String(err);
|
|
1852
1896
|
await worker2.emitEvent("turn_error", { message: `attachment download failed: ${message}` });
|
|
@@ -1855,14 +1899,37 @@ var runTurn = async (config, worker2, msg, resuming, spawnImpl, log = (m) => con
|
|
|
1855
1899
|
}
|
|
1856
1900
|
const result = await spawnClaude(config, worker2, text, resuming, spawnImpl, log, envOverlay);
|
|
1857
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);
|
|
1858
1912
|
try {
|
|
1859
|
-
|
|
1860
|
-
|
|
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);
|
|
1861
1926
|
} catch (err) {
|
|
1862
1927
|
const message = err instanceof Error ? err.message : String(err);
|
|
1863
1928
|
log(`[run] ${message}`);
|
|
1864
|
-
await worker2.emitEvent("turn_error", { message, stderrTail:
|
|
1929
|
+
await worker2.emitEvent("turn_error", { message, stderrTail: tail.toString() });
|
|
1865
1930
|
return -1;
|
|
1931
|
+
} finally {
|
|
1932
|
+
clearTimeout(watchdog);
|
|
1866
1933
|
}
|
|
1867
1934
|
};
|
|
1868
1935
|
|
|
@@ -2126,6 +2193,7 @@ var session = defineCommand({
|
|
|
2126
2193
|
create: sessionCreateCommand,
|
|
2127
2194
|
resume: sessionResumeCommand,
|
|
2128
2195
|
stop: sessionStopCommand,
|
|
2196
|
+
clear: sessionClearCommand,
|
|
2129
2197
|
rename: sessionRenameCommand,
|
|
2130
2198
|
rm: sessionRmCommand,
|
|
2131
2199
|
send: sessionSendCommand,
|
|
@@ -2577,8 +2645,13 @@ var worker = defineCommand({
|
|
|
2577
2645
|
...common
|
|
2578
2646
|
},
|
|
2579
2647
|
run: async ({ args }) => {
|
|
2580
|
-
const server =
|
|
2581
|
-
|
|
2648
|
+
const server = resolveBaseUrlOrNull(args.url);
|
|
2649
|
+
if (!server)
|
|
2650
|
+
throw new Error(
|
|
2651
|
+
"no baton server \u2014 pass --url <url> (e.g. https://baton.fmap.dev/api), set BATON_URL, or run `baton init`"
|
|
2652
|
+
);
|
|
2653
|
+
if (!args.json) console.log(`registering against ${server}`);
|
|
2654
|
+
const c = createClient(server);
|
|
2582
2655
|
const hostname = osHostname();
|
|
2583
2656
|
const machineId = readOrCreateMachineId();
|
|
2584
2657
|
const name = args.name ?? hostname;
|
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
|
+
}
|