@lesscap/baton-cli 0.2.4 → 0.2.6
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 +75 -28
- package/package.json +2 -1
package/dist/baton.mjs
CHANGED
|
@@ -768,7 +768,11 @@ var common = {
|
|
|
768
768
|
},
|
|
769
769
|
json: { type: "boolean", description: "output JSON" }
|
|
770
770
|
};
|
|
771
|
-
var clientFor = (args) =>
|
|
771
|
+
var clientFor = (args) => {
|
|
772
|
+
const envAuth = process.env.BATON_TOKEN ?? (process.env.BATON_USER && process.env.BATON_PASS);
|
|
773
|
+
const cfgToken = envAuth ? void 0 : loadProjectConfigOrNull(projectConfigPath())?.worker?.apiToken;
|
|
774
|
+
return createClient(resolveBaseUrl(args.url), cfgToken ? { bearer: cfgToken } : void 0);
|
|
775
|
+
};
|
|
772
776
|
var resolveProjectId = (args) => {
|
|
773
777
|
if (args.project !== void 0 && args.project !== "") return Number(args.project);
|
|
774
778
|
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
@@ -783,6 +787,10 @@ var resolveWorkspaceId = (args) => {
|
|
|
783
787
|
"no workspace in scope. pass --workspace <id> or run `baton init` in this directory."
|
|
784
788
|
);
|
|
785
789
|
};
|
|
790
|
+
var parseWorkerHandle = (handle) => {
|
|
791
|
+
const m = handle.trim().match(/^(?:[wW]-)?(\d+)$/);
|
|
792
|
+
return m ? Number(m[1]) : null;
|
|
793
|
+
};
|
|
786
794
|
var parseIssueUrl = (url) => {
|
|
787
795
|
const m = url.match(/^https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/);
|
|
788
796
|
if (!m)
|
|
@@ -863,7 +871,7 @@ var fmtComment = (c) => `${c.workerId !== void 0 ? `worker#${c.workerId}` : "you
|
|
|
863
871
|
var fmtSession = (s) => `${s.id} ${s.name} ${s.attached ? "[active]" : "[inactive]"} ${s.worktreePath ?? "(pending)"}`;
|
|
864
872
|
var fmtWorker = (w) => {
|
|
865
873
|
const offline = w.alive === false ? " [offline]" : "";
|
|
866
|
-
return
|
|
874
|
+
return `W-${w.id} ${w.name} ${w.hostname}${offline}`;
|
|
867
875
|
};
|
|
868
876
|
var renderOne = (item, fmt, json) => json ? toJson(item) : fmt(item);
|
|
869
877
|
var renderList = (items, fmt, json) => {
|
|
@@ -1113,7 +1121,10 @@ var resolveSession = async (client, projectId, handle) => {
|
|
|
1113
1121
|
|
|
1114
1122
|
// src/commands/session/abort.ts
|
|
1115
1123
|
var sessionAbortCommand = defineCommand({
|
|
1116
|
-
meta: {
|
|
1124
|
+
meta: {
|
|
1125
|
+
name: "abort",
|
|
1126
|
+
description: "interrupt the in-flight turn (like Esc); keeps the session"
|
|
1127
|
+
},
|
|
1117
1128
|
args: {
|
|
1118
1129
|
session: { type: "positional", required: true, description: "session int id or name" },
|
|
1119
1130
|
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
@@ -1129,7 +1140,10 @@ var sessionAbortCommand = defineCommand({
|
|
|
1129
1140
|
|
|
1130
1141
|
// src/commands/session/clear.ts
|
|
1131
1142
|
var sessionClearCommand = defineCommand({
|
|
1132
|
-
meta: {
|
|
1143
|
+
meta: {
|
|
1144
|
+
name: "clear",
|
|
1145
|
+
description: "reset the conversation context (keeps the session + worktree)"
|
|
1146
|
+
},
|
|
1133
1147
|
args: {
|
|
1134
1148
|
session: { type: "positional", required: true, description: "session int id or name" },
|
|
1135
1149
|
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
@@ -1148,16 +1162,24 @@ var sessionCreateCommand = defineCommand({
|
|
|
1148
1162
|
meta: { name: "create", description: "create + start a session on a worker" },
|
|
1149
1163
|
args: {
|
|
1150
1164
|
name: { type: "positional", required: true, description: "session name" },
|
|
1151
|
-
worker: {
|
|
1152
|
-
|
|
1165
|
+
worker: {
|
|
1166
|
+
type: "string",
|
|
1167
|
+
description: "worker handle: W-N or int id (default: this machine's worker)"
|
|
1168
|
+
},
|
|
1169
|
+
project: {
|
|
1170
|
+
type: "string",
|
|
1171
|
+
description: "project id (default: the worker's own project, else .baton.json)"
|
|
1172
|
+
},
|
|
1153
1173
|
...common
|
|
1154
1174
|
},
|
|
1155
1175
|
run: async ({ args }) => {
|
|
1156
1176
|
const c = clientFor(args);
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1177
|
+
const workerId = args.worker ? parseWorkerHandle(args.worker) : loadProjectConfigOrNull(projectConfigPath())?.worker?.id ?? null;
|
|
1178
|
+
if (workerId === null)
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
"no worker \u2014 pass --worker <W-N|id> or run `baton worker register` first (names need --project)"
|
|
1181
|
+
);
|
|
1182
|
+
const projectId = args.project !== void 0 && args.project !== "" ? Number(args.project) : args.worker ? (await c.workers.get(workerId)).projectId : resolveProjectId(args);
|
|
1161
1183
|
const s = await c.sessions.create({ projectId, workerId, name: args.name });
|
|
1162
1184
|
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1163
1185
|
}
|
|
@@ -2492,26 +2514,36 @@ import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
|
2492
2514
|
var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
|
|
2493
2515
|
var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NONE";
|
|
2494
2516
|
var sanitizeTitle = (raw) => raw.replace(/[\r\n]+/g, " ").replace(/[*_`~#>]/g, "").replace(/["']/g, "").replace(/^\s*(?:title|session|标题)\s*[::-]\s*/i, "").replace(/^\s*(?:[-*•]|\d+[.)、])\s+/, "").replace(/\s+/g, " ").trim().slice(0, 30).replace(/[\s。.,,、;;::!!??.]+$/, "").trim();
|
|
2495
|
-
var TITLE_TIMEOUT_MS =
|
|
2496
|
-
var
|
|
2497
|
-
const { worktreePath, userText, assistantText, queryFn } = input;
|
|
2498
|
-
if (`${userText}${assistantText}`.trim().length < 4) return null;
|
|
2517
|
+
var TITLE_TIMEOUT_MS = 9e4;
|
|
2518
|
+
var buildPrompt2 = (userText, assistantText) => {
|
|
2499
2519
|
const exchange = [
|
|
2500
2520
|
`User: ${clip(userText, 800)}`,
|
|
2501
2521
|
assistantText ? `Assistant: ${clip(assistantText, 800)}` : ""
|
|
2502
2522
|
].filter(Boolean).join("\n");
|
|
2503
|
-
|
|
2523
|
+
return `Reply with ONLY a short title for this session: a noun phrase like a filename \u2014 at most 6 words or ~12 Chinese characters. It must NOT be a sentence, no punctuation, no markdown, no quotes, and never an opener like "\u597D\u7684"/"\u6536\u5230"/"I'll". Only if the exchange below has no real content at all (e.g. just a greeting or a test ping), reply with exactly NONE.
|
|
2504
2524
|
|
|
2505
2525
|
${exchange}`;
|
|
2526
|
+
};
|
|
2527
|
+
var generateTitle = async (input) => {
|
|
2528
|
+
const { worktreePath, userText, assistantText, queryFn } = input;
|
|
2529
|
+
if (`${userText}${assistantText}`.trim().length < 4) return { kind: "declined" };
|
|
2506
2530
|
const exe = claudeExecutable();
|
|
2507
2531
|
const abort = new AbortController();
|
|
2508
2532
|
const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
|
|
2533
|
+
const stderrTail = [];
|
|
2509
2534
|
let out = "";
|
|
2535
|
+
let resultSeen = false;
|
|
2536
|
+
let failure = "";
|
|
2510
2537
|
try {
|
|
2511
2538
|
const messages = queryFn({
|
|
2512
|
-
prompt,
|
|
2539
|
+
prompt: buildPrompt2(userText, assistantText),
|
|
2513
2540
|
options: {
|
|
2514
2541
|
cwd: worktreePath,
|
|
2542
|
+
// A one-shot summarizer, not an agent: replace the ~18k-token Claude
|
|
2543
|
+
// Code preset system prompt. Inside the agent harness the model
|
|
2544
|
+
// routinely answers NONE to this ask (verified against reclaude);
|
|
2545
|
+
// a plain summarizer persona titles reliably — and ~100× cheaper.
|
|
2546
|
+
systemPrompt: "You title chat sessions.",
|
|
2515
2547
|
// Don't load the worktree's CLAUDE.md / project settings — they steer the
|
|
2516
2548
|
// model into verbose summaries instead of a terse title.
|
|
2517
2549
|
settingSources: [],
|
|
@@ -2519,20 +2551,33 @@ ${exchange}`;
|
|
|
2519
2551
|
allowedTools: [],
|
|
2520
2552
|
includePartialMessages: false,
|
|
2521
2553
|
abortController: abort,
|
|
2554
|
+
stderr: (line) => {
|
|
2555
|
+
if (line.trim()) stderrTail.push(line.trim().slice(0, 200));
|
|
2556
|
+
if (stderrTail.length > 3) stderrTail.shift();
|
|
2557
|
+
},
|
|
2522
2558
|
...exe ? { pathToClaudeCodeExecutable: exe } : {}
|
|
2523
2559
|
}
|
|
2524
2560
|
});
|
|
2525
2561
|
for await (const m of messages) {
|
|
2526
|
-
if (m.type
|
|
2527
|
-
|
|
2562
|
+
if (m.type !== "result") continue;
|
|
2563
|
+
resultSeen = true;
|
|
2564
|
+
const r = m;
|
|
2565
|
+
if (r.subtype === "success" && typeof r.result === "string") out = r.result;
|
|
2566
|
+
else
|
|
2567
|
+
failure = `result ${r.subtype ?? "unknown"}${typeof r.result === "string" ? `: ${r.result.slice(0, 200)}` : ""}`;
|
|
2528
2568
|
}
|
|
2529
|
-
} catch {
|
|
2530
|
-
|
|
2569
|
+
} catch (e) {
|
|
2570
|
+
failure = abort.signal.aborted ? `timeout after ${TITLE_TIMEOUT_MS / 1e3}s` : String(e).slice(0, 300);
|
|
2531
2571
|
} finally {
|
|
2532
2572
|
clearTimeout(timer);
|
|
2533
2573
|
}
|
|
2574
|
+
if (!failure && !resultSeen) failure = "stream ended without a result message";
|
|
2575
|
+
if (failure) {
|
|
2576
|
+
const tail = stderrTail.length > 0 ? ` | stderr: ${stderrTail.join(" / ")}` : "";
|
|
2577
|
+
return { kind: "error", reason: `${failure}${tail}` };
|
|
2578
|
+
}
|
|
2534
2579
|
const title = sanitizeTitle(out);
|
|
2535
|
-
return !title || isDeclined(title) ?
|
|
2580
|
+
return !title || isDeclined(title) ? { kind: "declined" } : { kind: "titled", title };
|
|
2536
2581
|
};
|
|
2537
2582
|
|
|
2538
2583
|
// src/session/worktree.ts
|
|
@@ -2672,15 +2717,17 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2672
2717
|
const onTitle = async (sessionId, agentSessionId, worktreePath) => {
|
|
2673
2718
|
const exchange = readFirstExchange(agentSessionId);
|
|
2674
2719
|
if (!exchange) return log(`title #${sessionId}: no transcript yet, skipping`);
|
|
2675
|
-
const
|
|
2720
|
+
const outcome = await generateTitle({
|
|
2676
2721
|
worktreePath,
|
|
2677
2722
|
userText: exchange.userText,
|
|
2678
2723
|
assistantText: exchange.assistantText,
|
|
2679
2724
|
queryFn: query2
|
|
2680
2725
|
});
|
|
2681
|
-
if (
|
|
2682
|
-
|
|
2683
|
-
|
|
2726
|
+
if (outcome.kind === "error") return log(`title #${sessionId} failed: ${outcome.reason}`);
|
|
2727
|
+
if (outcome.kind === "declined")
|
|
2728
|
+
return log(`title #${sessionId}: not enough to title yet, skipping`);
|
|
2729
|
+
await client.sessions.setName(sessionId, outcome.title, cfg.apiToken);
|
|
2730
|
+
log(`\u270E titled session #${sessionId} \u2192 ${outcome.title}`);
|
|
2684
2731
|
};
|
|
2685
2732
|
const reconcile = async () => {
|
|
2686
2733
|
const owned = (await client.sessions.listByProject(cfg.projectId)).filter(
|
|
@@ -2750,9 +2797,9 @@ var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
|
2750
2797
|
|
|
2751
2798
|
// src/commands/worker.ts
|
|
2752
2799
|
var resolveWorker = async (client, projectId, handle) => {
|
|
2753
|
-
const
|
|
2754
|
-
if (
|
|
2755
|
-
const byId = await client.workers.get(
|
|
2800
|
+
const asId = parseWorkerHandle(handle);
|
|
2801
|
+
if (asId !== null) {
|
|
2802
|
+
const byId = await client.workers.get(asId).catch(() => null);
|
|
2756
2803
|
if (byId) return { id: byId.id, name: byId.name };
|
|
2757
2804
|
}
|
|
2758
2805
|
const byName = await client.workers.findByName(projectId, handle);
|