@lesscap/baton-cli 0.9.5 → 0.9.7

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 (3) hide show
  1. package/README.md +2 -2
  2. package/dist/baton.mjs +213 -390
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @lesscap/baton-cli
2
2
 
3
3
  The baton CLI: register/run a **worker** on a machine and manage
4
- workspaces/projects/requirements/tasks/sessions against a baton server.
4
+ workspaces/projects/sessions against a baton server.
5
5
 
6
6
  A worker is the thing that actually runs agent sessions: it listens to the
7
7
  server's command stream and, per session, runs Claude or Codex in the worker’s working directory.
@@ -69,7 +69,7 @@ runtime `services/` directory; the run result prints their path.
69
69
 
70
70
  ## Other commands
71
71
 
72
- `baton init | workspace | project | requirement | task | session` — run
72
+ `baton init | workspace | project | host | session` — run
73
73
  `baton <command> --help` for details. The back-office management commands hit
74
74
  gated routes; set `BATON_USER` / `BATON_PASS` to log in transparently (the
75
75
  worker commands above don't need this).
package/dist/baton.mjs CHANGED
@@ -5376,10 +5376,10 @@ var toJson = (data) => JSON.stringify(data, null, 2);
5376
5376
  var fmtWorkspace = (w) => `${w.id} ${w.name}`;
5377
5377
  var fmtWorkerTemplate = (t) => `${t.id} ${t.name} [${t.available ? "available" : "unavailable"}] ${t.agentKinds.join(", ")}`;
5378
5378
  var fmtWorkerCreation = (c) => `${c.id} [${c.status}/${c.step ?? "pending"}] ${c.name} ${c.agentKind}${c.workerId === null ? "" : ` W-${c.workerId}`}${c.error ? ` ${c.error}` : ""}`;
5379
+ var fmtHost = (h) => `${h.id} ${h.name} [${h.online ? "online" : "offline"}] workspaces: ${h.workspaceIds.join(",") || "-"}`;
5380
+ var fmtHostCredential = ({ host: host2, token }) => `${host2.id} ${host2.name}
5381
+ token: ${token}`;
5379
5382
  var fmtProject = (p) => `${p.id} ${p.name}`;
5380
- var fmtRequirement = (r) => `${r.code} [${r.status}] ${r.title}`;
5381
- var fmtTask = (t) => `${t.code} [${t.status}] ${t.title}`;
5382
- var fmtComment = (c) => `${c.workerId !== void 0 ? `worker#${c.workerId}` : "you"} ${c.body}`;
5383
5383
  var fmtSession = (s) => `${s.id} ${s.name} ${s.attached ? "[active]" : "[inactive]"} ${s.cwd ?? "(pending)"}`;
5384
5384
  var fmtWorker = (w) => {
5385
5385
  const offline = w.connected === false ? " [offline]" : "";
@@ -5509,6 +5509,15 @@ var filesClient = (baseUrl) => {
5509
5509
  };
5510
5510
  };
5511
5511
 
5512
+ // src/client/hosts.ts
5513
+ var hostsClient = (baseUrl) => ({
5514
+ create: (name) => request(`${baseUrl}/hosts`, { method: "POST", body: { name } }),
5515
+ list: () => request(`${baseUrl}/hosts`, { method: "GET" }),
5516
+ rotate: (id) => request(`${baseUrl}/hosts/${id}/token`, { method: "POST", body: {} }),
5517
+ assign: (id, workspaceId) => request(`${baseUrl}/hosts/${id}/workspaces/${workspaceId}`, { method: "PUT" }),
5518
+ unassign: (id, workspaceId) => request(`${baseUrl}/hosts/${id}/workspaces/${workspaceId}`, { method: "DELETE" })
5519
+ });
5520
+
5512
5521
  // src/client/loops.ts
5513
5522
  var loopsClient = (baseUrl) => {
5514
5523
  const u = (p) => `${baseUrl}${p}`;
@@ -5535,30 +5544,6 @@ var projectClient = (baseUrl) => {
5535
5544
  };
5536
5545
  };
5537
5546
 
5538
- // src/client/items.ts
5539
- var fetchItemByCode = async (baseUrl, projectId, code, expectKind) => {
5540
- const r = await request(
5541
- `${baseUrl}/projects/${projectId}/items/${encodeURIComponent(code)}`,
5542
- { method: "GET" }
5543
- );
5544
- if (r.kind !== expectKind) throw new Error(`expected ${expectKind} but got ${r.kind} for ${code}`);
5545
- return r.item;
5546
- };
5547
-
5548
- // src/client/requirements.ts
5549
- var requirementClient = (baseUrl) => {
5550
- const u = (p) => `${baseUrl}${p}`;
5551
- return {
5552
- create: (input) => request(u("/requirements"), { method: "POST", body: input }),
5553
- listByProject: (projectId) => request(u(`/projects/${projectId}/requirements`), { method: "GET" }),
5554
- get: (id) => request(u(`/requirements/${id}`), { method: "GET" }),
5555
- getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "requirement"),
5556
- update: (id, patch) => request(u(`/requirements/${id}`), { method: "PATCH", body: patch }),
5557
- setStatus: (id, status) => request(u(`/requirements/${id}`), { method: "PATCH", body: { status } }),
5558
- remove: (id) => request(u(`/requirements/${id}`), { method: "DELETE" })
5559
- };
5560
- };
5561
-
5562
5547
  // src/client/services.ts
5563
5548
  var servicesClient = (baseUrl) => {
5564
5549
  const u = (path) => `${baseUrl}${path}`;
@@ -5693,24 +5678,9 @@ var sessionsClient = (baseUrl) => {
5693
5678
  };
5694
5679
  };
5695
5680
 
5696
- // src/client/tasks.ts
5697
- var taskClient = (baseUrl) => {
5698
- const u = (p) => `${baseUrl}${p}`;
5699
- return {
5700
- create: (input) => request(u("/tasks"), { method: "POST", body: input }),
5701
- listByRequirement: (requirementId) => request(u(`/requirements/${requirementId}/tasks`), { method: "GET" }),
5702
- get: (id) => request(u(`/tasks/${id}`), { method: "GET" }),
5703
- getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "task"),
5704
- update: (id, patch) => request(u(`/tasks/${id}`), { method: "PATCH", body: patch }),
5705
- setStatus: (id, status) => request(u(`/tasks/${id}`), { method: "PATCH", body: { status } }),
5706
- remove: (id) => request(u(`/tasks/${id}`), { method: "DELETE" }),
5707
- listComments: (id) => request(u(`/tasks/${id}/comments`), { method: "GET" }),
5708
- addComment: (id, body, workerId) => request(u(`/tasks/${id}/comments`), { method: "POST", body: { body, workerId } })
5709
- };
5710
- };
5711
-
5712
5681
  // src/client/worker-creations.ts
5713
5682
  var workerCreationsClient = (baseUrl) => ({
5683
+ hosts: (projectId) => request(`${baseUrl}/projects/${projectId}/hosts`, { method: "GET" }),
5714
5684
  templates: (projectId) => request(`${baseUrl}/projects/${projectId}/worker-templates`, { method: "GET" }),
5715
5685
  create: (projectId, input) => request(`${baseUrl}/projects/${projectId}/worker-creations`, { method: "POST", body: input }),
5716
5686
  listByProject: (projectId) => request(`${baseUrl}/projects/${projectId}/worker-creations`, { method: "GET" }),
@@ -5758,11 +5728,10 @@ var createClient = (baseUrl, opts) => {
5758
5728
  };
5759
5729
  var clientFromBase = (baseUrl) => ({
5760
5730
  workerCreations: workerCreationsClient(baseUrl),
5731
+ hosts: hostsClient(baseUrl),
5761
5732
  artifacts: artifactsClient(baseUrl),
5762
5733
  workspaces: workspaceClient(baseUrl),
5763
5734
  projects: projectClient(baseUrl),
5764
- requirements: requirementClient(baseUrl),
5765
- tasks: taskClient(baseUrl),
5766
5735
  sessions: sessionsClient(baseUrl),
5767
5736
  services: servicesClient(baseUrl),
5768
5737
  files: filesClient(baseUrl),
@@ -7114,6 +7083,87 @@ var channel = defineCommand({
7114
7083
  }
7115
7084
  });
7116
7085
 
7086
+ // src/commands/host.ts
7087
+ var findHost = (hosts, handle) => {
7088
+ const host2 = hosts.find((item) => item.name === handle || String(item.id) === handle);
7089
+ if (!host2) throw new Error(`host not found: ${handle}`);
7090
+ return host2;
7091
+ };
7092
+ var adminHost = async (c, handle) => findHost(await c.hosts.list(), handle);
7093
+ var positiveId = (raw) => {
7094
+ const id = Number(raw);
7095
+ if (!Number.isSafeInteger(id) || id <= 0) throw new Error("workspace must be a positive id");
7096
+ return id;
7097
+ };
7098
+ var hostArg = {
7099
+ host: { type: "positional", required: true, description: "host name or id" }
7100
+ };
7101
+ var workspaceArg = {
7102
+ workspace: { type: "string", required: true, description: "workspace id" }
7103
+ };
7104
+ var host = defineCommand({
7105
+ meta: { name: "host", description: "manage machines and which workspaces can use them (admin)" },
7106
+ subCommands: {
7107
+ add: defineCommand({
7108
+ meta: { name: "add", description: "add a host; prints its Provisioner token once" },
7109
+ args: {
7110
+ name: { type: "positional", required: true, description: "host name" },
7111
+ workspace: { type: "string", description: "also assign it to this workspace id" },
7112
+ ...common
7113
+ },
7114
+ run: async ({ args }) => {
7115
+ const c = clientFor(args);
7116
+ const workspaceId = args.workspace === void 0 ? null : positiveId(args.workspace);
7117
+ const credential = await c.hosts.create(args.name);
7118
+ console.log(renderOne(credential, fmtHostCredential, Boolean(args.json)));
7119
+ if (workspaceId !== null) await c.hosts.assign(credential.host.id, workspaceId);
7120
+ }
7121
+ }),
7122
+ ls: defineCommand({
7123
+ meta: {
7124
+ name: "ls",
7125
+ description: "list hosts, whether they are online, and their workspaces"
7126
+ },
7127
+ args: { ...common },
7128
+ run: async ({ args }) => {
7129
+ console.log(renderList(await clientFor(args).hosts.list(), fmtHost, Boolean(args.json)));
7130
+ }
7131
+ }),
7132
+ assign: defineCommand({
7133
+ meta: { name: "assign", description: "let a workspace create Workers on this host" },
7134
+ args: { ...hostArg, ...workspaceArg, ...common },
7135
+ run: async ({ args }) => {
7136
+ const c = clientFor(args);
7137
+ const { id, name } = await adminHost(c, args.host);
7138
+ await c.hosts.assign(id, positiveId(args.workspace));
7139
+ console.log(`assigned ${name} to workspace ${args.workspace}`);
7140
+ }
7141
+ }),
7142
+ unassign: defineCommand({
7143
+ meta: {
7144
+ name: "unassign",
7145
+ description: "stop new Workers on this host for a workspace; existing ones keep running"
7146
+ },
7147
+ args: { ...hostArg, ...workspaceArg, ...common },
7148
+ run: async ({ args }) => {
7149
+ const c = clientFor(args);
7150
+ const { id, name } = await adminHost(c, args.host);
7151
+ await c.hosts.unassign(id, positiveId(args.workspace));
7152
+ console.log(`unassigned ${name} from workspace ${args.workspace}`);
7153
+ }
7154
+ }),
7155
+ rotate: defineCommand({
7156
+ meta: { name: "rotate", description: "replace a host token; the old one stops working" },
7157
+ args: { ...hostArg, ...common },
7158
+ run: async ({ args }) => {
7159
+ const c = clientFor(args);
7160
+ const { id } = await adminHost(c, args.host);
7161
+ console.log(renderOne(await c.hosts.rotate(id), fmtHostCredential, Boolean(args.json)));
7162
+ }
7163
+ })
7164
+ }
7165
+ });
7166
+
7117
7167
  // src/commands/init.ts
7118
7168
  import { existsSync as existsSync2 } from "node:fs";
7119
7169
  import * as readline from "node:readline/promises";
@@ -7467,9 +7517,9 @@ var environment = (value) => {
7467
7517
  var templates = (value) => {
7468
7518
  if (!object(value)) throw new Error("templates must be an object");
7469
7519
  return Object.fromEntries(
7470
- WORKER_TEMPLATES.map((template) => {
7520
+ WORKER_TEMPLATES.filter((template) => value[template.id] !== void 0).map((template) => {
7471
7521
  const raw = value[template.id];
7472
- if (!object(raw)) throw new Error(`template ${template.id} must be configured`);
7522
+ if (!object(raw)) throw new Error(`template ${template.id} must be an object`);
7473
7523
  const workerEnv2 = environment(raw.workerEnv);
7474
7524
  if (template.id === "mini-codex") {
7475
7525
  if (raw.pi !== void 0) throw new Error("mini-codex does not support pi");
@@ -7563,7 +7613,7 @@ var createProvisionerClient = (config) => {
7563
7613
  import { execFile as execFile3 } from "node:child_process";
7564
7614
  import { lstat, mkdir as mkdir3, realpath, writeFile as writeFile2 } from "node:fs/promises";
7565
7615
  import { hostname } from "node:os";
7566
- import { join as join5 } from "node:path";
7616
+ import { dirname as dirname2, join as join5 } from "node:path";
7567
7617
  import { setTimeout as setTimeout2 } from "node:timers/promises";
7568
7618
  import { promisify as promisify3 } from "node:util";
7569
7619
 
@@ -7634,11 +7684,17 @@ var startAgent = async (config, agent, signal) => {
7634
7684
  throw new Error(`launchd start failed; inspect ${plist} and ${agent.log}`);
7635
7685
  }
7636
7686
  };
7637
- var launchWorkerAgent = async (config, creation, repoPath, workerEnv2, signal) => startAgent(
7687
+ var launchWorkerAgent = async (config, creation, repoPath, configPath, workerEnv2, signal) => startAgent(
7638
7688
  config,
7639
7689
  {
7640
7690
  label: `dev.fmap.baton-worker-creation-${creation.id}`,
7641
- args: [config.nodePath ?? process.execPath, config.cliPath, "worker", "run"],
7691
+ args: [
7692
+ config.nodePath ?? process.execPath,
7693
+ config.cliPath,
7694
+ "worker",
7695
+ "run",
7696
+ ...configPath === null ? [] : ["--config", configPath]
7697
+ ],
7642
7698
  cwd: repoPath,
7643
7699
  env: {
7644
7700
  PATH: workerEnv2.PATH ?? process.env.PATH ?? "/usr/bin:/bin",
@@ -7668,12 +7724,17 @@ var installProvisionerAgent = async (config, configPath) => startAgent(config, {
7668
7724
  // src/provisioner/worker-template.ts
7669
7725
  import { mkdir as mkdir2, writeFile } from "node:fs/promises";
7670
7726
  import { join as join4 } from "node:path";
7671
- var workerTemplateEnvironment = async (config, creation, state) => {
7727
+ var requireWorkerTemplate = (config, creation) => {
7672
7728
  if (!workerTemplateAgentKinds(creation.templateId).includes(creation.agentKind))
7673
7729
  throw new Error("agentKind is not supported by this template");
7674
- if (creation.templateId === "mini-local") return config.workerEnv;
7730
+ if (creation.templateId === "mini-local") return null;
7675
7731
  const profile = config.templates[creation.templateId];
7676
- if (!profile) throw new Error(`template ${creation.templateId} is not configured`);
7732
+ if (!profile) throw new Error(`template ${creation.templateId} is not configured on this host`);
7733
+ return profile;
7734
+ };
7735
+ var workerTemplateEnvironment = async (config, creation, state) => {
7736
+ const profile = requireWorkerTemplate(config, creation);
7737
+ if (!profile) return config.workerEnv;
7677
7738
  if (creation.agentKind !== "pi") return profile.workerEnv;
7678
7739
  if (!profile.pi) throw new Error("Pi provider and model are not configured");
7679
7740
  const agentDir = join4(state, "pi");
@@ -7699,13 +7760,20 @@ var directory = async (path) => {
7699
7760
  const info = await lstat(path);
7700
7761
  if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`unsafe directory: ${path}`);
7701
7762
  };
7702
- var provisionWorker = async (config, creation, client, signal) => {
7703
- const repoPath = join5(
7704
- config.projectsRoot,
7705
- `project-${creation.projectId}`,
7706
- `creation-${creation.id}`
7707
- );
7708
- const progress = (step) => client.report(creation.id, { status: "running", step, repoPath }, signal);
7763
+ var gitIn = async (config, repoPath, args, signal, failure = "Git repository initialization failed") => {
7764
+ try {
7765
+ const { stdout } = await promisify3(execFile3)("git", ["-C", repoPath, ...args], {
7766
+ env: commandEnvironment(config),
7767
+ signal,
7768
+ timeout: 1e4
7769
+ });
7770
+ return stdout;
7771
+ } catch (error) {
7772
+ signal.throwIfAborted();
7773
+ throw new Error(failure, { cause: error });
7774
+ }
7775
+ };
7776
+ var initRepository = async (config, creation, repoPath, progress, signal) => {
7709
7777
  await progress("directory");
7710
7778
  signal.throwIfAborted();
7711
7779
  await mkdir3(config.projectsRoot, { recursive: true, mode: 448 });
@@ -7715,33 +7783,52 @@ var provisionWorker = async (config, creation, client, signal) => {
7715
7783
  mode: 448
7716
7784
  });
7717
7785
  await progress("git");
7718
- const git2 = async (args) => {
7719
- try {
7720
- await promisify3(execFile3)("git", ["-C", repoPath, ...args], {
7721
- env: commandEnvironment(config),
7722
- signal,
7723
- timeout: 1e4
7724
- });
7725
- } catch (error) {
7726
- signal.throwIfAborted();
7727
- throw new Error("Git repository initialization failed", { cause: error });
7728
- }
7729
- };
7730
- await git2(["init", "-b", "main"]);
7786
+ await gitIn(config, repoPath, ["init", "-b", "main"], signal);
7731
7787
  await writeFile2(join5(repoPath, ".git/info/exclude"), "\n.baton.json\n", { flag: "a" });
7732
- await git2([
7733
- "-c",
7734
- "user.name=Baton",
7735
- "-c",
7736
- "user.email=baton@localhost",
7737
- "commit",
7738
- "--allow-empty",
7739
- "-m",
7740
- "chore: initialize repository"
7741
- ]);
7788
+ await gitIn(
7789
+ config,
7790
+ repoPath,
7791
+ [
7792
+ "-c",
7793
+ "user.name=Baton",
7794
+ "-c",
7795
+ "user.email=baton@localhost",
7796
+ "commit",
7797
+ "--allow-empty",
7798
+ "-m",
7799
+ "chore: initialize repository"
7800
+ ],
7801
+ signal
7802
+ );
7803
+ };
7804
+ var adoptRepository = async (config, creation, repoPath, progress, signal) => {
7805
+ await progress("directory");
7806
+ signal.throwIfAborted();
7807
+ const real = await realpath(repoPath);
7808
+ const project2 = join5(await realpath(config.projectsRoot), `project-${creation.projectId}`);
7809
+ if (dirname2(real) !== project2 || !(await lstat(real)).isDirectory())
7810
+ throw new Error(`unsafe directory: ${repoPath}`);
7811
+ await progress("git");
7812
+ const top = await gitIn(
7813
+ config,
7814
+ real,
7815
+ ["rev-parse", "--show-toplevel"],
7816
+ signal,
7817
+ `not a Git repository: ${repoPath}`
7818
+ );
7819
+ if (await realpath(top.trim()) !== real)
7820
+ throw new Error(`not the root of a Git work tree: ${repoPath}`);
7821
+ };
7822
+ var provisionWorker = async (config, creation, client, signal) => {
7823
+ requireWorkerTemplate(config, creation);
7824
+ const shared = creation.repoPath !== null;
7825
+ const repoPath = creation.repoPath ?? join5(config.projectsRoot, `project-${creation.projectId}`, `creation-${creation.id}`);
7826
+ const progress = (step) => client.report(creation.id, { status: "running", step, repoPath }, signal);
7827
+ await (shared ? adoptRepository : initRepository)(config, creation, repoPath, progress, signal);
7742
7828
  await progress("register");
7743
7829
  const result = await client.register(creation.id, hostname(), signal);
7744
7830
  const state = workerStatePath(config, creation);
7831
+ const root = await realpath(config.projectsRoot);
7745
7832
  await directory(join5(root, ".worker-state"));
7746
7833
  await directory(join5(root, ".worker-state", `project-${creation.projectId}`));
7747
7834
  await mkdir3(state, { mode: 448 });
@@ -7760,15 +7847,13 @@ var provisionWorker = async (config, creation, client, signal) => {
7760
7847
  machineId: result.worker.machineId,
7761
7848
  apiToken: result.apiToken
7762
7849
  });
7763
- await writeFile2(join5(repoPath, ".baton.json"), `${JSON.stringify(cfg, null, 2)}
7764
- `, {
7765
- flag: "wx",
7766
- mode: 384
7767
- });
7850
+ const configPath = shared ? join5(state, "worker.json") : join5(repoPath, ".baton.json");
7851
+ await writeFile2(configPath, `${JSON.stringify(cfg, null, 2)}
7852
+ `, { flag: "wx", mode: 384 });
7768
7853
  await progress("start");
7769
7854
  signal.throwIfAborted();
7770
7855
  const workerEnv2 = await workerTemplateEnvironment(config, creation, state);
7771
- await launchWorkerAgent(config, creation, repoPath, workerEnv2, signal);
7856
+ await launchWorkerAgent(config, creation, repoPath, shared ? configPath : null, workerEnv2, signal);
7772
7857
  await progress("connect");
7773
7858
  const deadline = Date.now() + 6e4;
7774
7859
  while (Date.now() < deadline) {
@@ -8119,118 +8204,6 @@ var relay = defineCommand({
8119
8204
  subCommands: { host: hostCommand, listen: listenCommand2, send: sendCommand2 }
8120
8205
  });
8121
8206
 
8122
- // src/commands/requirement.ts
8123
- var createRequirement = (c, input, json) => c.requirements.create(input).then((r) => renderOne(r, fmtRequirement, json));
8124
- var updateRequirement = (c, id, patch, json) => c.requirements.update(id, patch).then((r) => renderOne(r, fmtRequirement, json));
8125
- var listRequirements = (c, projectId, json) => c.requirements.listByProject(projectId).then((rs) => renderList(rs, fmtRequirement, json));
8126
- var getRequirement = (c, id, json) => c.requirements.get(id).then((r) => renderOne(r, fmtRequirement, json));
8127
- var setRequirementStatus = (c, id, status, json) => c.requirements.setStatus(id, status).then((r) => renderOne(r, fmtRequirement, json));
8128
- var removeRequirement = (c, id, label, json) => c.requirements.remove(id).then(() => removed("requirement", label, json));
8129
- var resolveByCode = async (c, projectId, code) => (await c.requirements.getByCode(projectId, code)).id;
8130
- var requirement = defineCommand({
8131
- meta: { name: "requirement", description: "manage requirements" },
8132
- subCommands: {
8133
- create: defineCommand({
8134
- meta: { name: "create", description: "create a requirement" },
8135
- args: {
8136
- title: { type: "positional", required: true },
8137
- project: { type: "string", description: "project id (overrides .baton.json)" },
8138
- desc: { type: "string", description: "description" },
8139
- body: { type: "string", description: "requirement body (markdown)" },
8140
- ...common
8141
- },
8142
- run: async ({ args }) => {
8143
- console.log(
8144
- await createRequirement(
8145
- clientFor(args),
8146
- {
8147
- projectId: resolveProjectId(args),
8148
- title: args.title,
8149
- description: args.desc,
8150
- body: args.body
8151
- },
8152
- Boolean(args.json)
8153
- )
8154
- );
8155
- }
8156
- }),
8157
- update: defineCommand({
8158
- meta: { name: "update", description: "update a requirement title / body" },
8159
- args: {
8160
- code: { type: "positional", required: true, description: "requirement code (R-N)" },
8161
- title: { type: "string", description: "new title" },
8162
- body: { type: "string", description: "new body (markdown)" },
8163
- project: { type: "string", description: "project id (overrides .baton.json)" },
8164
- ...common
8165
- },
8166
- run: async ({ args }) => {
8167
- const patch = {
8168
- ...args.title !== void 0 ? { title: args.title } : {},
8169
- ...args.body !== void 0 ? { body: args.body } : {}
8170
- };
8171
- if (Object.keys(patch).length === 0) throw new Error("pass --title and/or --body");
8172
- const c = clientFor(args);
8173
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
8174
- console.log(await updateRequirement(c, id, patch, Boolean(args.json)));
8175
- }
8176
- }),
8177
- ls: defineCommand({
8178
- meta: { name: "ls", description: "list requirements in a project" },
8179
- args: {
8180
- project: { type: "string", description: "project id (overrides .baton.json)" },
8181
- ...common
8182
- },
8183
- run: async ({ args }) => {
8184
- console.log(
8185
- await listRequirements(clientFor(args), resolveProjectId(args), Boolean(args.json))
8186
- );
8187
- }
8188
- }),
8189
- get: defineCommand({
8190
- meta: { name: "get", description: "get a requirement by code (R-N)" },
8191
- args: {
8192
- code: { type: "positional", required: true, description: "requirement code, e.g. R-1" },
8193
- project: { type: "string", description: "project id (overrides .baton.json)" },
8194
- ...common
8195
- },
8196
- run: async ({ args }) => {
8197
- const c = clientFor(args);
8198
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
8199
- console.log(await getRequirement(c, id, Boolean(args.json)));
8200
- }
8201
- }),
8202
- "set-status": defineCommand({
8203
- meta: { name: "set-status", description: "set requirement status (active|done|cancelled)" },
8204
- args: {
8205
- code: { type: "positional", required: true, description: "requirement code (R-N)" },
8206
- status: { type: "positional", required: true },
8207
- project: { type: "string", description: "project id (overrides .baton.json)" },
8208
- ...common
8209
- },
8210
- run: async ({ args }) => {
8211
- const c = clientFor(args);
8212
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
8213
- console.log(
8214
- await setRequirementStatus(c, id, args.status, Boolean(args.json))
8215
- );
8216
- }
8217
- }),
8218
- rm: defineCommand({
8219
- meta: { name: "rm", description: "delete a requirement by code" },
8220
- args: {
8221
- code: { type: "positional", required: true },
8222
- project: { type: "string", description: "project id (overrides .baton.json)" },
8223
- ...common
8224
- },
8225
- run: async ({ args }) => {
8226
- const c = clientFor(args);
8227
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
8228
- console.log(await removeRequirement(c, id, args.code, Boolean(args.json)));
8229
- }
8230
- })
8231
- }
8232
- });
8233
-
8234
8207
  // src/commands/service.ts
8235
8208
  import { resolve as resolve2 } from "node:path";
8236
8209
 
@@ -9848,178 +9821,19 @@ var session = defineCommand({
9848
9821
  }
9849
9822
  });
9850
9823
 
9851
- // src/commands/task.ts
9852
- var createTask = (c, input, json) => c.tasks.create(input).then((t) => renderOne(t, fmtTask, json));
9853
- var updateTask = (c, id, patch, json) => c.tasks.update(id, patch).then((t) => renderOne(t, fmtTask, json));
9854
- var listTasks = (c, requirementId, json) => c.tasks.listByRequirement(requirementId).then((ts) => renderList(ts, fmtTask, json));
9855
- var getTask = (c, id, json) => c.tasks.get(id).then((t) => renderOne(t, fmtTask, json));
9856
- var setTaskStatus = (c, id, status, json) => c.tasks.setStatus(id, status).then((t) => renderOne(t, fmtTask, json));
9857
- var removeTask = (c, id, label, json) => c.tasks.remove(id).then(() => removed("task", label, json));
9858
- var addTaskComment = (c, taskId, body, workerId, json) => c.tasks.addComment(taskId, body, workerId).then((x) => renderOne(x, fmtComment, json));
9859
- var listTaskComments = (c, taskId, json) => c.tasks.listComments(taskId).then((xs) => renderList(xs, fmtComment, json));
9860
- var resolveReqByCode = async (c, projectId, code) => (await c.requirements.getByCode(projectId, code)).id;
9861
- var resolveTaskByCode = async (c, projectId, code) => (await c.tasks.getByCode(projectId, code)).id;
9862
- var task = defineCommand({
9863
- meta: { name: "task", description: "manage tasks" },
9864
- subCommands: {
9865
- create: defineCommand({
9866
- meta: { name: "create", description: "create a task" },
9867
- args: {
9868
- title: { type: "positional", required: true },
9869
- requirement: { type: "string", required: true, description: "requirement code (R-N)" },
9870
- project: { type: "string", description: "project id (overrides .baton.json)" },
9871
- body: { type: "string", description: "task body (markdown)" },
9872
- deps: { type: "string", description: "comma-separated dependency task codes (T-N,T-N)" },
9873
- ...common
9874
- },
9875
- run: async ({ args }) => {
9876
- const c = clientFor(args);
9877
- const projectId = resolveProjectId(args);
9878
- const requirementId = await resolveReqByCode(c, projectId, args.requirement);
9879
- const depCodes = splitCsv(args.deps) ?? [];
9880
- const dependsOn = await Promise.all(
9881
- depCodes.map((code) => resolveTaskByCode(c, projectId, code))
9882
- );
9883
- console.log(
9884
- await createTask(
9885
- c,
9886
- {
9887
- requirementId,
9888
- title: args.title,
9889
- body: args.body,
9890
- dependsOn: dependsOn.length ? dependsOn : void 0
9891
- },
9892
- Boolean(args.json)
9893
- )
9894
- );
9895
- }
9896
- }),
9897
- update: defineCommand({
9898
- meta: { name: "update", description: "update a task title / body" },
9899
- args: {
9900
- code: { type: "positional", required: true, description: "task code (T-N)" },
9901
- title: { type: "string", description: "new title" },
9902
- body: { type: "string", description: "new body (markdown)" },
9903
- project: { type: "string", description: "project id (overrides .baton.json)" },
9904
- ...common
9905
- },
9906
- run: async ({ args }) => {
9907
- const patch = {
9908
- ...args.title !== void 0 ? { title: args.title } : {},
9909
- ...args.body !== void 0 ? { body: args.body } : {}
9910
- };
9911
- if (Object.keys(patch).length === 0) throw new Error("pass --title and/or --body");
9912
- const c = clientFor(args);
9913
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
9914
- console.log(await updateTask(c, id, patch, Boolean(args.json)));
9915
- }
9916
- }),
9917
- ls: defineCommand({
9918
- meta: { name: "ls", description: "list tasks in a requirement" },
9919
- args: {
9920
- requirement: { type: "string", required: true, description: "requirement code (R-N)" },
9921
- project: { type: "string", description: "project id (overrides .baton.json)" },
9922
- ...common
9923
- },
9924
- run: async ({ args }) => {
9925
- const c = clientFor(args);
9926
- const reqId = await resolveReqByCode(c, resolveProjectId(args), args.requirement);
9927
- console.log(await listTasks(c, reqId, Boolean(args.json)));
9928
- }
9929
- }),
9930
- get: defineCommand({
9931
- meta: { name: "get", description: "get a task by code (T-N)" },
9932
- args: {
9933
- code: { type: "positional", required: true },
9934
- project: { type: "string", description: "project id (overrides .baton.json)" },
9935
- ...common
9936
- },
9937
- run: async ({ args }) => {
9938
- const c = clientFor(args);
9939
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
9940
- console.log(await getTask(c, id, Boolean(args.json)));
9941
- }
9942
- }),
9943
- "set-status": defineCommand({
9944
- meta: {
9945
- name: "set-status",
9946
- description: "set task status (todo|in_progress|blocked|done|failed|cancelled)"
9947
- },
9948
- args: {
9949
- code: { type: "positional", required: true, description: "task code (T-N)" },
9950
- status: { type: "positional", required: true },
9951
- project: { type: "string", description: "project id (overrides .baton.json)" },
9952
- ...common
9953
- },
9954
- run: async ({ args }) => {
9955
- const c = clientFor(args);
9956
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
9957
- console.log(await setTaskStatus(c, id, args.status, Boolean(args.json)));
9958
- }
9959
- }),
9960
- rm: defineCommand({
9961
- meta: { name: "rm", description: "delete a task by code" },
9962
- args: {
9963
- code: { type: "positional", required: true },
9964
- project: { type: "string", description: "project id (overrides .baton.json)" },
9965
- ...common
9966
- },
9967
- run: async ({ args }) => {
9968
- const c = clientFor(args);
9969
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
9970
- console.log(await removeTask(c, id, args.code, Boolean(args.json)));
9971
- }
9972
- }),
9973
- comment: defineCommand({
9974
- meta: { name: "comment", description: "append-only task comments" },
9975
- subCommands: {
9976
- add: defineCommand({
9977
- meta: { name: "add", description: "add a comment to a task" },
9978
- args: {
9979
- code: { type: "positional", required: true, description: "task code (T-N)" },
9980
- body: { type: "positional", required: true, description: "comment text (markdown)" },
9981
- worker: { type: "string", description: "attribute to a worker id (omit = human)" },
9982
- project: { type: "string", description: "project id (overrides .baton.json)" },
9983
- ...common
9984
- },
9985
- run: async ({ args }) => {
9986
- const c = clientFor(args);
9987
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
9988
- const workerId = args.worker ? Number(args.worker) : void 0;
9989
- console.log(await addTaskComment(c, id, args.body, workerId, Boolean(args.json)));
9990
- }
9991
- }),
9992
- ls: defineCommand({
9993
- meta: { name: "ls", description: "list a task\u2019s comments in order" },
9994
- args: {
9995
- code: { type: "positional", required: true, description: "task code (T-N)" },
9996
- project: { type: "string", description: "project id (overrides .baton.json)" },
9997
- ...common
9998
- },
9999
- run: async ({ args }) => {
10000
- const c = clientFor(args);
10001
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
10002
- console.log(await listTaskComments(c, id, Boolean(args.json)));
10003
- }
10004
- })
10005
- }
10006
- })
10007
- }
10008
- });
10009
-
10010
9824
  // src/commands/worker/index.ts
10011
9825
  import { hostname as osHostname } from "node:os";
10012
9826
 
10013
9827
  // src/worker/bundled-skills.ts
10014
9828
  import { spawnSync as spawnSync2 } from "node:child_process";
10015
9829
  import { cpSync, existsSync as existsSync5 } from "node:fs";
10016
- import { dirname as dirname3, join as join14 } from "node:path";
9830
+ import { dirname as dirname4, join as join14 } from "node:path";
10017
9831
  import { fileURLToPath } from "node:url";
10018
9832
 
10019
9833
  // src/worker/git-exclude.ts
10020
9834
  import { spawnSync } from "node:child_process";
10021
9835
  import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "node:fs";
10022
- import { dirname as dirname2, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
9836
+ import { dirname as dirname3, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
10023
9837
  var ensureExcluded = (cwd, pattern) => {
10024
9838
  const result = spawnSync("git", ["-C", cwd, "rev-parse", "--git-path", "info/exclude"], {
10025
9839
  encoding: "utf8",
@@ -10034,7 +9848,7 @@ var ensureExcluded = (cwd, pattern) => {
10034
9848
  const path = isAbsolute3(location) ? location : resolve3(cwd, location);
10035
9849
  const current = existsSync4(path) ? readFileSync5(path, "utf8") : "";
10036
9850
  if (current.split("\n").includes(pattern)) return;
10037
- mkdirSync3(dirname2(path), { recursive: true });
9851
+ mkdirSync3(dirname3(path), { recursive: true });
10038
9852
  appendFileSync(path, `${current && !current.endsWith("\n") ? "\n" : ""}${pattern}
10039
9853
  `);
10040
9854
  };
@@ -10043,7 +9857,7 @@ var ensureExcluded = (cwd, pattern) => {
10043
9857
  var SKILL_NAME = "baton-publish-artifact";
10044
9858
  var TARGETS = [`.claude/skills/${SKILL_NAME}`, `.agents/skills/${SKILL_NAME}`];
10045
9859
  var bundledSkillPath = () => {
10046
- const here = dirname3(fileURLToPath(import.meta.url));
9860
+ const here = dirname4(fileURLToPath(import.meta.url));
10047
9861
  const candidates = [
10048
9862
  join14(here, "..", "skills", SKILL_NAME),
10049
9863
  join14(here, "..", "..", "skills", SKILL_NAME)
@@ -10651,7 +10465,7 @@ import { join as join17 } from "node:path";
10651
10465
  // src/worker/service-process.ts
10652
10466
  import { spawn as spawn3 } from "node:child_process";
10653
10467
  import { closeSync, existsSync as existsSync6, openSync } from "node:fs";
10654
- import { dirname as dirname4, join as join16 } from "node:path";
10468
+ import { dirname as dirname5, join as join16 } from "node:path";
10655
10469
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10656
10470
  var START_TIMEOUT_MS = 1e4;
10657
10471
  var isWrapperMessage = (message) => {
@@ -10661,7 +10475,7 @@ var isWrapperMessage = (message) => {
10661
10475
  return value.type === "error" && typeof value.error === "string";
10662
10476
  };
10663
10477
  var binPath = () => {
10664
- const here = dirname4(fileURLToPath2(import.meta.url));
10478
+ const here = dirname5(fileURLToPath2(import.meta.url));
10665
10479
  const devShim = join16(here, "..", "..", "bin", "baton.mjs");
10666
10480
  return existsSync6(devShim) ? devShim : fileURLToPath2(import.meta.url);
10667
10481
  };
@@ -10823,7 +10637,7 @@ import { spawn as spawn4 } from "node:child_process";
10823
10637
  import { randomUUID as randomUUID2 } from "node:crypto";
10824
10638
  import { existsSync as existsSync7 } from "node:fs";
10825
10639
  import { mkdir as mkdir6, rm as rm3, stat as stat5 } from "node:fs/promises";
10826
- import { dirname as dirname5, join as join21 } from "node:path";
10640
+ import { dirname as dirname6, join as join21 } from "node:path";
10827
10641
  import { fileURLToPath as fileURLToPath3 } from "node:url";
10828
10642
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
10829
10643
 
@@ -11086,7 +10900,7 @@ var stopRecordedRunner = async (record, expected, graceMs = 5e3) => {
11086
10900
 
11087
10901
  // src/worker/session-supervisor.ts
11088
10902
  var binPath2 = () => {
11089
- const here = dirname5(fileURLToPath3(import.meta.url));
10903
+ const here = dirname6(fileURLToPath3(import.meta.url));
11090
10904
  const devShim = join21(here, "..", "..", "bin", "baton.mjs");
11091
10905
  return existsSync7(devShim) ? devShim : fileURLToPath3(import.meta.url);
11092
10906
  };
@@ -11332,7 +11146,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
11332
11146
  import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
11333
11147
  import { createRequire as createModuleRequire } from "node:module";
11334
11148
  import { homedir as homedir5 } from "node:os";
11335
- import { dirname as dirname6, join as join22 } from "node:path";
11149
+ import { dirname as dirname7, join as join22 } from "node:path";
11336
11150
  import { fileURLToPath as fileURLToPath4 } from "node:url";
11337
11151
  var MAX_TERMINALS = 10;
11338
11152
  var claudePtyCommand = (agentSessionId, hasJsonl, env = process.env) => ({
@@ -11353,7 +11167,7 @@ var piPtyCommand = (agentSessionId, env = process.env) => {
11353
11167
  const override = env.BATON_PI_BIN?.trim();
11354
11168
  if (override) return { file: override, args };
11355
11169
  const entry = fileURLToPath4(import.meta.resolve("@earendil-works/pi-coding-agent"));
11356
- return { file: process.execPath, args: [join22(dirname6(entry), "bundle", "cli.js"), ...args] };
11170
+ return { file: process.execPath, args: [join22(dirname7(entry), "bundle", "cli.js"), ...args] };
11357
11171
  };
11358
11172
  var hasSessionJsonl = (agentSessionId) => {
11359
11173
  const root = join22(homedir5(), ".claude", "projects");
@@ -11558,7 +11372,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
11558
11372
  import { randomUUID as randomUUID3 } from "node:crypto";
11559
11373
  import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
11560
11374
  import { homedir as homedir6 } from "node:os";
11561
- import { dirname as dirname7, join as join23 } from "node:path";
11375
+ import { dirname as dirname8, join as join23 } from "node:path";
11562
11376
  var machineIdPath = (env = process.env) => join23(env.XDG_DATA_HOME ?? join23(env.HOME ?? homedir6(), ".local/share"), "baton", "machine-id");
11563
11377
  var readOrCreateMachineId = (path = machineIdPath()) => {
11564
11378
  if (existsSync9(path)) {
@@ -11566,7 +11380,7 @@ var readOrCreateMachineId = (path = machineIdPath()) => {
11566
11380
  if (s) return s;
11567
11381
  }
11568
11382
  const id = randomUUID3();
11569
- mkdirSync5(dirname7(path), { recursive: true });
11383
+ mkdirSync5(dirname8(path), { recursive: true });
11570
11384
  writeFileSync5(path, `${id}
11571
11385
  `, "utf8");
11572
11386
  return id;
@@ -11576,7 +11390,7 @@ var readOrCreateMachineId = (path = machineIdPath()) => {
11576
11390
  var projectArg = {
11577
11391
  project: { type: "string", description: "project id (overrides .baton.json)" }
11578
11392
  };
11579
- var positiveId = (raw) => {
11393
+ var positiveId2 = (raw) => {
11580
11394
  const id = Number(raw);
11581
11395
  if (!Number.isSafeInteger(id) || id <= 0) throw new Error("positive id required");
11582
11396
  return id;
@@ -11585,12 +11399,12 @@ var workerTemplatesCommand = defineCommand({
11585
11399
  meta: { name: "templates", description: "list worker creation templates for a project" },
11586
11400
  args: { ...projectArg, ...common },
11587
11401
  run: async ({ args }) => {
11588
- const list = await clientFor(args).workerCreations.templates(positiveId(resolveProjectId(args)));
11402
+ const list = await clientFor(args).workerCreations.templates(positiveId2(resolveProjectId(args)));
11589
11403
  console.log(renderList(list, fmtWorkerTemplate, Boolean(args.json)));
11590
11404
  }
11591
11405
  });
11592
11406
  var workerCreateCommand = defineCommand({
11593
- meta: { name: "create", description: "request a worker from the mini template (asynchronous)" },
11407
+ meta: { name: "create", description: "request a worker from a template (asynchronous)" },
11594
11408
  args: {
11595
11409
  ...projectArg,
11596
11410
  ...common,
@@ -11600,7 +11414,15 @@ var workerCreateCommand = defineCommand({
11600
11414
  description: "mini-glm-codeplan | mini-dashscope-codeplan | mini-codex"
11601
11415
  },
11602
11416
  name: { type: "string", required: true, description: "worker name" },
11603
- agentKind: { type: "string", description: AGENT_KINDS.join(", ") }
11417
+ agentKind: { type: "string", description: AGENT_KINDS.join(", ") },
11418
+ repoPath: {
11419
+ type: "string",
11420
+ description: "share an existing worker's directory in this project (default: a new one)"
11421
+ },
11422
+ host: {
11423
+ type: "string",
11424
+ description: "host name or id (default: the workspace's only host)"
11425
+ }
11604
11426
  },
11605
11427
  run: async ({ args }) => {
11606
11428
  if (!isWorkerTemplateId(args.template)) throw new Error("invalid worker template");
@@ -11610,14 +11432,16 @@ var workerCreateCommand = defineCommand({
11610
11432
  throw new Error("agentKind is not supported by this template");
11611
11433
  const name = args.name.trim();
11612
11434
  if (!name) throw new Error("worker name required");
11613
- const creation = await clientFor(args).workerCreations.create(
11614
- positiveId(resolveProjectId(args)),
11615
- {
11616
- templateId: args.template,
11617
- name,
11618
- agentKind
11619
- }
11620
- );
11435
+ const client = clientFor(args);
11436
+ const projectId = positiveId2(resolveProjectId(args));
11437
+ const host2 = args.host ? findHost(await client.workerCreations.hosts(projectId), args.host) : null;
11438
+ const creation = await client.workerCreations.create(projectId, {
11439
+ templateId: args.template,
11440
+ name,
11441
+ agentKind,
11442
+ ...args.repoPath ? { repoPath: args.repoPath } : {},
11443
+ ...host2 ? { hostId: host2.id } : {}
11444
+ });
11621
11445
  console.log(renderOne(creation, fmtWorkerCreation, Boolean(args.json)));
11622
11446
  }
11623
11447
  });
@@ -11628,7 +11452,7 @@ var workerCreationCommand = defineCommand({
11628
11452
  args: { ...projectArg, ...common },
11629
11453
  run: async ({ args }) => {
11630
11454
  const list = await clientFor(args).workerCreations.listByProject(
11631
- positiveId(resolveProjectId(args))
11455
+ positiveId2(resolveProjectId(args))
11632
11456
  );
11633
11457
  console.log(renderList(list, fmtWorkerCreation, Boolean(args.json)));
11634
11458
  }
@@ -11636,7 +11460,7 @@ var workerCreationCommand = defineCommand({
11636
11460
  get: defineCommand({
11637
11461
  args: { ...common, id: { type: "positional", required: true } },
11638
11462
  run: async ({ args }) => {
11639
- const creation = await clientFor(args).workerCreations.get(positiveId(args.id));
11463
+ const creation = await clientFor(args).workerCreations.get(positiveId2(args.id));
11640
11464
  console.log(renderOne(creation, fmtWorkerCreation, Boolean(args.json)));
11641
11465
  }
11642
11466
  })
@@ -11928,13 +11752,14 @@ var workspace = defineCommand({
11928
11752
  var main = defineCommand({
11929
11753
  meta: {
11930
11754
  name: "baton",
11931
- description: "baton \u2014 run a worker daemon; manage projects / requirements / tasks / sessions"
11755
+ description: "baton \u2014 run a worker daemon; manage projects / sessions"
11932
11756
  },
11933
11757
  // Order matters: high-level verbs first so --help shows them on top.
11934
11758
  subCommands: {
11935
11759
  init,
11936
11760
  worker,
11937
11761
  provisioner,
11762
+ host,
11938
11763
  session,
11939
11764
  service,
11940
11765
  artifact,
@@ -11943,9 +11768,7 @@ var main = defineCommand({
11943
11768
  relay,
11944
11769
  channel,
11945
11770
  workspace,
11946
- project,
11947
- requirement,
11948
- task
11771
+ project
11949
11772
  }
11950
11773
  });
11951
11774
  runMain(main);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lesscap/baton-cli",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"