@lesscap/baton-cli 0.2.3 → 0.2.5

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.
Files changed (2) hide show
  1. package/dist/baton.mjs +163 -18
  2. package/package.json +1 -1
package/dist/baton.mjs CHANGED
@@ -550,6 +550,7 @@ var requirementClient = (baseUrl) => {
550
550
  listByProject: (projectId) => request(u(`/projects/${projectId}/requirements`), { method: "GET" }),
551
551
  get: (id) => request(u(`/requirements/${id}`), { method: "GET" }),
552
552
  getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "requirement"),
553
+ update: (id, patch2) => request(u(`/requirements/${id}`), { method: "PATCH", body: patch2 }),
553
554
  setStatus: (id, status) => request(u(`/requirements/${id}`), { method: "PATCH", body: { status } }),
554
555
  remove: (id) => request(u(`/requirements/${id}`), { method: "DELETE" })
555
556
  };
@@ -622,6 +623,7 @@ var taskClient = (baseUrl) => {
622
623
  listByRequirement: (requirementId) => request(u(`/requirements/${requirementId}/tasks`), { method: "GET" }),
623
624
  get: (id) => request(u(`/tasks/${id}`), { method: "GET" }),
624
625
  getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "task"),
626
+ update: (id, patch2) => request(u(`/tasks/${id}`), { method: "PATCH", body: patch2 }),
625
627
  setStatus: (id, status) => request(u(`/tasks/${id}`), { method: "PATCH", body: { status } }),
626
628
  remove: (id) => request(u(`/tasks/${id}`), { method: "DELETE" }),
627
629
  listComments: (id) => request(u(`/tasks/${id}/comments`), { method: "GET" }),
@@ -766,7 +768,11 @@ var common = {
766
768
  },
767
769
  json: { type: "boolean", description: "output JSON" }
768
770
  };
769
- var clientFor = (args) => createClient(resolveBaseUrl(args.url));
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
+ };
770
776
  var resolveProjectId = (args) => {
771
777
  if (args.project !== void 0 && args.project !== "") return Number(args.project);
772
778
  const cfg = loadProjectConfigOrNull(projectConfigPath());
@@ -781,6 +787,16 @@ var resolveWorkspaceId = (args) => {
781
787
  "no workspace in scope. pass --workspace <id> or run `baton init` in this directory."
782
788
  );
783
789
  };
790
+ var parseWorkerHandle = (handle) => {
791
+ const m = handle.trim().match(/^(?:[wW]-)?(\d+)$/);
792
+ return m ? Number(m[1]) : null;
793
+ };
794
+ var parseIssueUrl = (url) => {
795
+ const m = url.match(/^https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/);
796
+ if (!m)
797
+ throw new Error(`expected a GitHub issue url (https://github.com/o/r/issues/N), got "${url}"`);
798
+ return { source: "github", number: Number(m[1]), url: url.split(/[?#]/)[0] };
799
+ };
784
800
  var splitCsv = (s) => s ? s.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
785
801
 
786
802
  // src/commands/init.ts
@@ -855,7 +871,7 @@ var fmtComment = (c) => `${c.workerId !== void 0 ? `worker#${c.workerId}` : "you
855
871
  var fmtSession = (s) => `${s.id} ${s.name} ${s.attached ? "[active]" : "[inactive]"} ${s.worktreePath ?? "(pending)"}`;
856
872
  var fmtWorker = (w) => {
857
873
  const offline = w.alive === false ? " [offline]" : "";
858
- return `${w.id} ${w.name} ${w.hostname}${offline}`;
874
+ return `W-${w.id} ${w.name} ${w.hostname}${offline}`;
859
875
  };
860
876
  var renderOne = (item, fmt, json) => json ? toJson(item) : fmt(item);
861
877
  var renderList = (items, fmt, json) => {
@@ -940,6 +956,7 @@ var project = defineCommand({
940
956
 
941
957
  // src/commands/requirement.ts
942
958
  var createRequirement = (c, input, json) => c.requirements.create(input).then((r) => renderOne(r, fmtRequirement, json));
959
+ var updateRequirement = (c, id, patch2, json) => c.requirements.update(id, patch2).then((r) => renderOne(r, fmtRequirement, json));
943
960
  var listRequirements = (c, projectId, json) => c.requirements.listByProject(projectId).then((rs) => renderList(rs, fmtRequirement, json));
944
961
  var getRequirement = (c, id, json) => c.requirements.get(id).then((r) => renderOne(r, fmtRequirement, json));
945
962
  var setRequirementStatus = (c, id, status, json) => c.requirements.setStatus(id, status).then((r) => renderOne(r, fmtRequirement, json));
@@ -955,6 +972,7 @@ var requirement = defineCommand({
955
972
  project: { type: "string", description: "project id (overrides .baton.json)" },
956
973
  desc: { type: "string", description: "description" },
957
974
  body: { type: "string", description: "requirement body (markdown)" },
975
+ github: { type: "string", description: "link a GitHub issue url (light association)" },
958
976
  ...common
959
977
  },
960
978
  run: async ({ args }) => {
@@ -965,13 +983,69 @@ var requirement = defineCommand({
965
983
  projectId: resolveProjectId(args),
966
984
  title: args.title,
967
985
  description: args.desc,
968
- body: args.body
986
+ body: args.body,
987
+ external: args.github ? parseIssueUrl(args.github) : void 0
969
988
  },
970
989
  Boolean(args.json)
971
990
  )
972
991
  );
973
992
  }
974
993
  }),
994
+ update: defineCommand({
995
+ meta: { name: "update", description: "update a requirement title / body" },
996
+ args: {
997
+ code: { type: "positional", required: true, description: "requirement code (R-N)" },
998
+ title: { type: "string", description: "new title" },
999
+ body: { type: "string", description: "new body (markdown)" },
1000
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1001
+ ...common
1002
+ },
1003
+ run: async ({ args }) => {
1004
+ const patch2 = {
1005
+ ...args.title !== void 0 ? { title: args.title } : {},
1006
+ ...args.body !== void 0 ? { body: args.body } : {}
1007
+ };
1008
+ if (Object.keys(patch2).length === 0) throw new Error("pass --title and/or --body");
1009
+ const c = clientFor(args);
1010
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
1011
+ console.log(await updateRequirement(c, id, patch2, Boolean(args.json)));
1012
+ }
1013
+ }),
1014
+ link: defineCommand({
1015
+ meta: { name: "link", description: "link a requirement to a GitHub issue url" },
1016
+ args: {
1017
+ code: { type: "positional", required: true, description: "requirement code (R-N)" },
1018
+ // Named `issue` (not `url`) — `url` would collide with the common --url flag.
1019
+ issue: { type: "positional", required: true, description: "GitHub issue url" },
1020
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1021
+ ...common
1022
+ },
1023
+ run: async ({ args }) => {
1024
+ const c = clientFor(args);
1025
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
1026
+ console.log(
1027
+ await updateRequirement(
1028
+ c,
1029
+ id,
1030
+ { external: parseIssueUrl(args.issue) },
1031
+ Boolean(args.json)
1032
+ )
1033
+ );
1034
+ }
1035
+ }),
1036
+ unlink: defineCommand({
1037
+ meta: { name: "unlink", description: "clear a requirement\u2019s GitHub issue association" },
1038
+ args: {
1039
+ code: { type: "positional", required: true, description: "requirement code (R-N)" },
1040
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1041
+ ...common
1042
+ },
1043
+ run: async ({ args }) => {
1044
+ const c = clientFor(args);
1045
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
1046
+ console.log(await updateRequirement(c, id, { external: null }, Boolean(args.json)));
1047
+ }
1048
+ }),
975
1049
  ls: defineCommand({
976
1050
  meta: { name: "ls", description: "list requirements in a project" },
977
1051
  args: {
@@ -1047,7 +1121,10 @@ var resolveSession = async (client, projectId, handle) => {
1047
1121
 
1048
1122
  // src/commands/session/abort.ts
1049
1123
  var sessionAbortCommand = defineCommand({
1050
- meta: { name: "abort", description: "interrupt the in-flight turn (like Esc); keeps the session" },
1124
+ meta: {
1125
+ name: "abort",
1126
+ description: "interrupt the in-flight turn (like Esc); keeps the session"
1127
+ },
1051
1128
  args: {
1052
1129
  session: { type: "positional", required: true, description: "session int id or name" },
1053
1130
  project: { type: "string", description: "project id (overrides .baton.json)" },
@@ -1063,7 +1140,10 @@ var sessionAbortCommand = defineCommand({
1063
1140
 
1064
1141
  // src/commands/session/clear.ts
1065
1142
  var sessionClearCommand = defineCommand({
1066
- meta: { name: "clear", description: "reset the conversation context (keeps the session + worktree)" },
1143
+ meta: {
1144
+ name: "clear",
1145
+ description: "reset the conversation context (keeps the session + worktree)"
1146
+ },
1067
1147
  args: {
1068
1148
  session: { type: "positional", required: true, description: "session int id or name" },
1069
1149
  project: { type: "string", description: "project id (overrides .baton.json)" },
@@ -1082,16 +1162,24 @@ var sessionCreateCommand = defineCommand({
1082
1162
  meta: { name: "create", description: "create + start a session on a worker" },
1083
1163
  args: {
1084
1164
  name: { type: "positional", required: true, description: "session name" },
1085
- worker: { type: "string", description: "worker id (default: this machine's worker)" },
1086
- project: { type: "string", description: "project id (overrides .baton.json)" },
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
+ },
1087
1173
  ...common
1088
1174
  },
1089
1175
  run: async ({ args }) => {
1090
1176
  const c = clientFor(args);
1091
- const projectId = resolveProjectId(args);
1092
- const workerId = args.worker ? Number(args.worker) : loadProjectConfigOrNull(projectConfigPath())?.worker?.id;
1093
- if (!workerId)
1094
- throw new Error("no worker id \u2014 pass --worker or run `baton worker register` first");
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);
1095
1183
  const s = await c.sessions.create({ projectId, workerId, name: args.name });
1096
1184
  console.log(renderOne(s, fmtSession, Boolean(args.json)));
1097
1185
  }
@@ -2222,6 +2310,7 @@ var session = defineCommand({
2222
2310
 
2223
2311
  // src/commands/task.ts
2224
2312
  var createTask = (c, input, json) => c.tasks.create(input).then((t) => renderOne(t, fmtTask, json));
2313
+ var updateTask = (c, id, patch2, json) => c.tasks.update(id, patch2).then((t) => renderOne(t, fmtTask, json));
2225
2314
  var listTasks = (c, requirementId, json) => c.tasks.listByRequirement(requirementId).then((ts) => renderList(ts, fmtTask, json));
2226
2315
  var getTask = (c, id, json) => c.tasks.get(id).then((t) => renderOne(t, fmtTask, json));
2227
2316
  var setTaskStatus = (c, id, status, json) => c.tasks.setStatus(id, status).then((t) => renderOne(t, fmtTask, json));
@@ -2241,6 +2330,7 @@ var task = defineCommand({
2241
2330
  project: { type: "string", description: "project id (overrides .baton.json)" },
2242
2331
  body: { type: "string", description: "task body (markdown)" },
2243
2332
  deps: { type: "string", description: "comma-separated dependency task codes (T-N,T-N)" },
2333
+ github: { type: "string", description: "link a GitHub issue url (light association)" },
2244
2334
  ...common
2245
2335
  },
2246
2336
  run: async ({ args }) => {
@@ -2258,13 +2348,64 @@ var task = defineCommand({
2258
2348
  requirementId,
2259
2349
  title: args.title,
2260
2350
  body: args.body,
2261
- dependsOn: dependsOn.length ? dependsOn : void 0
2351
+ dependsOn: dependsOn.length ? dependsOn : void 0,
2352
+ external: args.github ? parseIssueUrl(args.github) : void 0
2262
2353
  },
2263
2354
  Boolean(args.json)
2264
2355
  )
2265
2356
  );
2266
2357
  }
2267
2358
  }),
2359
+ update: defineCommand({
2360
+ meta: { name: "update", description: "update a task title / body" },
2361
+ args: {
2362
+ code: { type: "positional", required: true, description: "task code (T-N)" },
2363
+ title: { type: "string", description: "new title" },
2364
+ body: { type: "string", description: "new body (markdown)" },
2365
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2366
+ ...common
2367
+ },
2368
+ run: async ({ args }) => {
2369
+ const patch2 = {
2370
+ ...args.title !== void 0 ? { title: args.title } : {},
2371
+ ...args.body !== void 0 ? { body: args.body } : {}
2372
+ };
2373
+ if (Object.keys(patch2).length === 0) throw new Error("pass --title and/or --body");
2374
+ const c = clientFor(args);
2375
+ const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
2376
+ console.log(await updateTask(c, id, patch2, Boolean(args.json)));
2377
+ }
2378
+ }),
2379
+ link: defineCommand({
2380
+ meta: { name: "link", description: "link a task to a GitHub issue url" },
2381
+ args: {
2382
+ code: { type: "positional", required: true, description: "task code (T-N)" },
2383
+ // Named `issue` (not `url`) — `url` would collide with the common --url flag.
2384
+ issue: { type: "positional", required: true, description: "GitHub issue url" },
2385
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2386
+ ...common
2387
+ },
2388
+ run: async ({ args }) => {
2389
+ const c = clientFor(args);
2390
+ const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
2391
+ console.log(
2392
+ await updateTask(c, id, { external: parseIssueUrl(args.issue) }, Boolean(args.json))
2393
+ );
2394
+ }
2395
+ }),
2396
+ unlink: defineCommand({
2397
+ meta: { name: "unlink", description: "clear a task\u2019s GitHub issue association" },
2398
+ args: {
2399
+ code: { type: "positional", required: true, description: "task code (T-N)" },
2400
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2401
+ ...common
2402
+ },
2403
+ run: async ({ args }) => {
2404
+ const c = clientFor(args);
2405
+ const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
2406
+ console.log(await updateTask(c, id, { external: null }, Boolean(args.json)));
2407
+ }
2408
+ }),
2268
2409
  ls: defineCommand({
2269
2410
  meta: { name: "ls", description: "list tasks in a requirement" },
2270
2411
  args: {
@@ -2294,7 +2435,7 @@ var task = defineCommand({
2294
2435
  "set-status": defineCommand({
2295
2436
  meta: {
2296
2437
  name: "set-status",
2297
- description: "set task status (todo|in_progress|done|failed|cancelled)"
2438
+ description: "set task status (todo|in_progress|blocked|done|failed|cancelled)"
2298
2439
  },
2299
2440
  args: {
2300
2441
  code: { type: "positional", required: true, description: "task code (T-N)" },
@@ -2372,7 +2513,7 @@ import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
2372
2513
  // src/session/runner/title.ts
2373
2514
  var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
2374
2515
  var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NONE";
2375
- 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();
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();
2376
2517
  var TITLE_TIMEOUT_MS = 3e4;
2377
2518
  var generateTitle = async (input) => {
2378
2519
  const { worktreePath, userText, assistantText, queryFn } = input;
@@ -2381,7 +2522,8 @@ var generateTitle = async (input) => {
2381
2522
  `User: ${clip(userText, 800)}`,
2382
2523
  assistantText ? `Assistant: ${clip(assistantText, 800)}` : ""
2383
2524
  ].filter(Boolean).join("\n");
2384
- 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:
2525
+ const prompt = `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". If it is too thin or generic to title meaningfully, reply with exactly NONE.
2526
+
2385
2527
  ${exchange}`;
2386
2528
  const exe = claudeExecutable();
2387
2529
  const abort = new AbortController();
@@ -2392,6 +2534,9 @@ ${exchange}`;
2392
2534
  prompt,
2393
2535
  options: {
2394
2536
  cwd: worktreePath,
2537
+ // Don't load the worktree's CLAUDE.md / project settings — they steer the
2538
+ // model into verbose summaries instead of a terse title.
2539
+ settingSources: [],
2395
2540
  permissionMode: "bypassPermissions",
2396
2541
  allowedTools: [],
2397
2542
  includePartialMessages: false,
@@ -2627,9 +2772,9 @@ var readOrCreateMachineId = (path = machineIdPath()) => {
2627
2772
 
2628
2773
  // src/commands/worker.ts
2629
2774
  var resolveWorker = async (client, projectId, handle) => {
2630
- const asInt = Number(handle);
2631
- if (Number.isInteger(asInt) && asInt > 0) {
2632
- const byId = await client.workers.get(asInt).catch(() => null);
2775
+ const asId = parseWorkerHandle(handle);
2776
+ if (asId !== null) {
2777
+ const byId = await client.workers.get(asId).catch(() => null);
2633
2778
  if (byId) return { id: byId.id, name: byId.name };
2634
2779
  }
2635
2780
  const byName = await client.workers.findByName(projectId, handle);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lesscap/baton-cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "description": "baton CLI — register/run a worker, manage projects/requirements/tasks/sessions",
6
6
  "bin": {