@lesscap/baton-cli 0.9.6 → 0.9.8

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 +1208 -2048
  3. package/package.json +1 -1
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),
@@ -5883,11 +5852,6 @@ var clientFor = (args) => {
5883
5852
  const auth = resolveAuth(process.env, fileToken);
5884
5853
  return createClient(baseUrl, auth && auth !== "cookie" ? auth : void 0);
5885
5854
  };
5886
- var resolveBearer = () => {
5887
- const fileToken = loadProjectConfigOrNull(projectConfigPath())?.worker?.apiToken;
5888
- const auth = resolveAuth(process.env, fileToken);
5889
- return auth && auth !== "cookie" ? auth.bearer : void 0;
5890
- };
5891
5855
  var resolveProjectId = (args) => {
5892
5856
  if (args.project !== void 0 && args.project !== "") return Number(args.project);
5893
5857
  if (process.env.BATON_PROJECT_ID !== void 0)
@@ -5908,7 +5872,6 @@ var parseWorkerHandle = (handle) => {
5908
5872
  const m = handle.trim().match(/^(?:[wW]-)?(\d+)$/);
5909
5873
  return m ? Number(m[1]) : null;
5910
5874
  };
5911
- var splitCsv = (s) => s ? s.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
5912
5875
  var parseDuration = (s) => {
5913
5876
  const m = s.trim().match(/^(\d+)\s*([smhd]?)$/);
5914
5877
  if (!m) throw new Error(`bad interval "${s}" \u2014 use e.g. 90s, 30m, 2h, 1d`);
@@ -5990,758 +5953,412 @@ var artifact = defineCommand({
5990
5953
  subCommands: { publish: publishCommand, ls: listCommand, rm: removeCommand }
5991
5954
  });
5992
5955
 
5993
- // ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js
5994
- var ParseError = class extends Error {
5995
- constructor(message, options) {
5996
- super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
5997
- }
5956
+ // src/commands/host.ts
5957
+ var findHost = (hosts, handle) => {
5958
+ const host2 = hosts.find((item) => item.name === handle || String(item.id) === handle);
5959
+ if (!host2) throw new Error(`host not found: ${handle}`);
5960
+ return host2;
5998
5961
  };
5999
- var LF = 10;
6000
- var CR = 13;
6001
- var SPACE = 32;
6002
- function noop(_arg) {
6003
- }
6004
- function createParser(config) {
6005
- if (typeof config == "function")
6006
- throw new TypeError(
6007
- "`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
6008
- );
6009
- const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
6010
- let pendingFragmentsLength = 0, isFirstChunk = true, id, data = "", dataLines = 0, eventType, terminated = false;
6011
- function feed(chunk) {
6012
- if (terminated)
6013
- throw new Error(
6014
- "Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
6015
- );
6016
- if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
6017
- const trailing2 = processLines(chunk);
6018
- trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
6019
- return;
6020
- }
6021
- if (chunk.indexOf(`
6022
- `) === -1 && chunk.indexOf("\r") === -1) {
6023
- pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
6024
- return;
6025
- }
6026
- pendingFragments.push(chunk);
6027
- const input = pendingFragments.join("");
6028
- pendingFragments.length = 0, pendingFragmentsLength = 0;
6029
- const trailing = processLines(input);
6030
- trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
6031
- }
6032
- function checkBufferSize() {
6033
- maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
6034
- new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
6035
- type: "max-buffer-size-exceeded"
6036
- })
6037
- )));
6038
- }
6039
- function processLines(chunk) {
6040
- let searchIndex = 0;
6041
- if (chunk.indexOf("\r") === -1) {
6042
- let lfIndex = chunk.indexOf(`
6043
- `, searchIndex);
6044
- for (; lfIndex !== -1; ) {
6045
- if (searchIndex === lfIndex) {
6046
- dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
6047
- `, searchIndex);
6048
- continue;
6049
- }
6050
- const firstCharCode = chunk.charCodeAt(searchIndex);
6051
- if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
6052
- const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);
6053
- if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
6054
- onEvent({ id, event: eventType, data: value }), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`
6055
- `, searchIndex);
6056
- continue;
6057
- }
6058
- data = dataLines === 0 ? value : `${data}
6059
- ${value}`, dataLines++;
6060
- } else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(
6061
- chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,
6062
- lfIndex
6063
- ) || void 0 : parseLine(chunk, searchIndex, lfIndex);
6064
- searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
6065
- `, searchIndex);
5962
+ var adminHost = async (c, handle) => findHost(await c.hosts.list(), handle);
5963
+ var positiveId = (raw) => {
5964
+ const id = Number(raw);
5965
+ if (!Number.isSafeInteger(id) || id <= 0) throw new Error("workspace must be a positive id");
5966
+ return id;
5967
+ };
5968
+ var hostArg = {
5969
+ host: { type: "positional", required: true, description: "host name or id" }
5970
+ };
5971
+ var workspaceArg = {
5972
+ workspace: { type: "string", required: true, description: "workspace id" }
5973
+ };
5974
+ var host = defineCommand({
5975
+ meta: { name: "host", description: "manage machines and which workspaces can use them (admin)" },
5976
+ subCommands: {
5977
+ add: defineCommand({
5978
+ meta: { name: "add", description: "add a host; prints its Provisioner token once" },
5979
+ args: {
5980
+ name: { type: "positional", required: true, description: "host name" },
5981
+ workspace: { type: "string", description: "also assign it to this workspace id" },
5982
+ ...common
5983
+ },
5984
+ run: async ({ args }) => {
5985
+ const c = clientFor(args);
5986
+ const workspaceId = args.workspace === void 0 ? null : positiveId(args.workspace);
5987
+ const credential = await c.hosts.create(args.name);
5988
+ console.log(renderOne(credential, fmtHostCredential, Boolean(args.json)));
5989
+ if (workspaceId !== null) await c.hosts.assign(credential.host.id, workspaceId);
6066
5990
  }
6067
- return chunk.slice(searchIndex);
6068
- }
6069
- for (; searchIndex < chunk.length; ) {
6070
- const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
6071
- `, searchIndex);
6072
- let lineEnd = -1;
6073
- if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1)
6074
- break;
6075
- parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;
6076
- }
6077
- return chunk.slice(searchIndex);
6078
- }
6079
- function parseLine(chunk, start, end) {
6080
- if (start === end) {
6081
- dispatchEvent();
6082
- return;
6083
- }
6084
- const firstCharCode = chunk.charCodeAt(start);
6085
- if (isDataPrefix(chunk, start, firstCharCode)) {
6086
- const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);
6087
- data = dataLines === 0 ? value2 : `${data}
6088
- ${value2}`, dataLines++;
6089
- return;
6090
- }
6091
- if (isEventPrefix(chunk, start, firstCharCode)) {
6092
- eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
6093
- return;
6094
- }
6095
- if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
6096
- const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
6097
- id = value2.includes("\0") ? void 0 : value2;
6098
- return;
6099
- }
6100
- if (firstCharCode === 58) {
6101
- if (onComment) {
6102
- const line2 = chunk.slice(start, end);
6103
- onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
5991
+ }),
5992
+ ls: defineCommand({
5993
+ meta: {
5994
+ name: "ls",
5995
+ description: "list hosts, whether they are online, and their workspaces"
5996
+ },
5997
+ args: { ...common },
5998
+ run: async ({ args }) => {
5999
+ console.log(renderList(await clientFor(args).hosts.list(), fmtHost, Boolean(args.json)));
6104
6000
  }
6105
- return;
6106
- }
6107
- const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":");
6108
- if (fieldSeparatorIndex === -1) {
6109
- processField(line, "", line);
6110
- return;
6111
- }
6112
- const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
6113
- processField(field, value, line);
6114
- }
6115
- function processField(field, value, line) {
6116
- switch (field) {
6117
- case "event":
6118
- eventType = value || void 0;
6119
- break;
6120
- case "data":
6121
- data = dataLines === 0 ? value : `${data}
6122
- ${value}`, dataLines++;
6123
- break;
6124
- case "id":
6125
- id = value.includes("\0") ? void 0 : value;
6126
- break;
6127
- case "retry":
6128
- /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
6129
- new ParseError(`Invalid \`retry\` value: "${value}"`, {
6130
- type: "invalid-retry",
6131
- value,
6132
- line
6133
- })
6134
- );
6135
- break;
6136
- default:
6137
- onError(
6138
- new ParseError(
6139
- `Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
6140
- { type: "unknown-field", field, value, line }
6141
- )
6142
- );
6143
- break;
6144
- }
6145
- }
6146
- function dispatchEvent() {
6147
- dataLines > 0 && onEvent({
6148
- id,
6149
- event: eventType,
6150
- data
6151
- }), id = void 0, data = "", dataLines = 0, eventType = void 0;
6152
- }
6153
- function reset(options = {}) {
6154
- if (options.consume && pendingFragments.length > 0) {
6155
- const incompleteLine = pendingFragments.join("");
6156
- parseLine(incompleteLine, 0, incompleteLine.length);
6157
- }
6158
- isFirstChunk = true, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = false;
6001
+ }),
6002
+ assign: defineCommand({
6003
+ meta: { name: "assign", description: "let a workspace create Workers on this host" },
6004
+ args: { ...hostArg, ...workspaceArg, ...common },
6005
+ run: async ({ args }) => {
6006
+ const c = clientFor(args);
6007
+ const { id, name } = await adminHost(c, args.host);
6008
+ await c.hosts.assign(id, positiveId(args.workspace));
6009
+ console.log(`assigned ${name} to workspace ${args.workspace}`);
6010
+ }
6011
+ }),
6012
+ unassign: defineCommand({
6013
+ meta: {
6014
+ name: "unassign",
6015
+ description: "stop new Workers on this host for a workspace; existing ones keep running"
6016
+ },
6017
+ args: { ...hostArg, ...workspaceArg, ...common },
6018
+ run: async ({ args }) => {
6019
+ const c = clientFor(args);
6020
+ const { id, name } = await adminHost(c, args.host);
6021
+ await c.hosts.unassign(id, positiveId(args.workspace));
6022
+ console.log(`unassigned ${name} from workspace ${args.workspace}`);
6023
+ }
6024
+ }),
6025
+ rotate: defineCommand({
6026
+ meta: { name: "rotate", description: "replace a host token; the old one stops working" },
6027
+ args: { ...hostArg, ...common },
6028
+ run: async ({ args }) => {
6029
+ const c = clientFor(args);
6030
+ const { id } = await adminHost(c, args.host);
6031
+ console.log(renderOne(await c.hosts.rotate(id), fmtHostCredential, Boolean(args.json)));
6032
+ }
6033
+ })
6159
6034
  }
6160
- return { feed, reset };
6161
- }
6162
- function isDataPrefix(chunk, i, firstCharCode) {
6163
- return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
6164
- }
6165
- function isEventPrefix(chunk, i, firstCharCode) {
6166
- return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
6167
- }
6035
+ });
6168
6036
 
6169
- // ../../node_modules/.pnpm/eventsource@4.1.0/node_modules/eventsource/dist/index.js
6170
- var ErrorEvent = class extends Event {
6171
- /**
6172
- * Constructs a new `ErrorEvent` instance. This is typically not called directly,
6173
- * but rather emitted by the `EventSource` object when an error occurs.
6174
- *
6175
- * @param type - The type of the event (should be "error")
6176
- * @param errorEventInitDict - Optional properties to include in the error event
6177
- */
6178
- constructor(type, errorEventInitDict) {
6179
- var _a, _b;
6180
- super(type), this.code = (_a = errorEventInitDict == null ? void 0 : errorEventInitDict.code) != null ? _a : void 0, this.message = (_b = errorEventInitDict == null ? void 0 : errorEventInitDict.message) != null ? _b : void 0;
6037
+ // src/commands/init.ts
6038
+ import { existsSync as existsSync2 } from "node:fs";
6039
+ import * as readline from "node:readline/promises";
6040
+ var promptPick = async (label, items) => {
6041
+ if (items.length === 0) throw new Error(`no ${label}s found on server`);
6042
+ console.log(`${label}s:`);
6043
+ items.forEach((it, i) => {
6044
+ console.log(` [${i + 1}] #${it.id} ${it.name}`);
6045
+ });
6046
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6047
+ try {
6048
+ const ans = (await rl.question(`pick a ${label} (1-${items.length}): `)).trim();
6049
+ const idx = Number(ans) - 1;
6050
+ const picked = items[idx];
6051
+ if (!picked) throw new Error(`invalid choice: ${ans}`);
6052
+ return picked;
6053
+ } finally {
6054
+ rl.close();
6181
6055
  }
6182
- /**
6183
- * Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance,
6184
- * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
6185
- * we explicitly include the properties in the `inspect` method.
6186
- *
6187
- * This is automatically called by Node.js when you `console.log` an instance of this class.
6188
- *
6189
- * @param _depth - The current depth
6190
- * @param options - The options passed to `util.inspect`
6191
- * @param inspect - The inspect function to use (prevents having to import it from `util`)
6192
- * @returns A string representation of the error
6193
- */
6194
- [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")](_depth, options, inspect) {
6195
- return inspect(inspectableError(this), options);
6196
- }
6197
- /**
6198
- * Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance,
6199
- * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
6200
- * we explicitly include the properties in the `inspect` method.
6201
- *
6202
- * This is automatically called by Deno when you `console.log` an instance of this class.
6203
- *
6204
- * @param inspect - The inspect function to use (prevents having to import it from `util`)
6205
- * @param options - The options passed to `Deno.inspect`
6206
- * @returns A string representation of the error
6207
- */
6208
- [/* @__PURE__ */ Symbol.for("Deno.customInspect")](inspect, options) {
6209
- return inspect(inspectableError(this), options);
6210
- }
6211
- };
6212
- function syntaxError(message) {
6213
- const DomException = globalThis.DOMException;
6214
- return typeof DomException == "function" ? new DomException(message, "SyntaxError") : new SyntaxError(message);
6215
- }
6216
- function flattenError(err) {
6217
- return err instanceof Error ? "errors" in err && Array.isArray(err.errors) ? err.errors.map(flattenError).join(", ") : "cause" in err && err.cause instanceof Error ? `${err}: ${flattenError(err.cause)}` : err.message : `${err}`;
6218
- }
6219
- function inspectableError(err) {
6220
- return {
6221
- type: err.type,
6222
- message: err.message,
6223
- code: err.code,
6224
- defaultPrevented: err.defaultPrevented,
6225
- cancelable: err.cancelable,
6226
- timeStamp: err.timeStamp
6227
- };
6228
- }
6229
- var __typeError = (msg) => {
6230
- throw TypeError(msg);
6231
- };
6232
- var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
6233
- var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
6234
- var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
6235
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
6236
- var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
6237
- var _readyState;
6238
- var _url;
6239
- var _redirectUrl;
6240
- var _withCredentials;
6241
- var _fetch;
6242
- var _reconnectInterval;
6243
- var _reconnectTimer;
6244
- var _lastEventId;
6245
- var _controller;
6246
- var _parser;
6247
- var _onError;
6248
- var _onMessage;
6249
- var _onOpen;
6250
- var _EventSource_instances;
6251
- var connect_fn;
6252
- var _onFetchResponse;
6253
- var _onFetchError;
6254
- var getRequestOptions_fn;
6255
- var _onEvent;
6256
- var _onRetryChange;
6257
- var failConnection_fn;
6258
- var scheduleReconnect_fn;
6259
- var _reconnect;
6260
- var EventSource = class extends EventTarget {
6261
- constructor(url, eventSourceInitDict) {
6262
- var _a, _b;
6263
- super(), __privateAdd(this, _EventSource_instances), this.CONNECTING = 0, this.OPEN = 1, this.CLOSED = 2, __privateAdd(this, _readyState), __privateAdd(this, _url), __privateAdd(this, _redirectUrl), __privateAdd(this, _withCredentials), __privateAdd(this, _fetch), __privateAdd(this, _reconnectInterval), __privateAdd(this, _reconnectTimer), __privateAdd(this, _lastEventId, null), __privateAdd(this, _controller), __privateAdd(this, _parser), __privateAdd(this, _onError, null), __privateAdd(this, _onMessage, null), __privateAdd(this, _onOpen, null), __privateAdd(this, _onFetchResponse, async (response) => {
6264
- var _a2;
6265
- __privateGet(this, _parser).reset();
6266
- const { body, redirected, status, headers } = response;
6267
- if (status === 204) {
6268
- __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Server sent HTTP 204, not reconnecting", 204), this.close();
6269
- return;
6270
- }
6271
- if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) {
6272
- __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status);
6273
- return;
6274
- }
6275
- if (!(headers.get("content-type") || "").startsWith("text/event-stream")) {
6276
- __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, 'Invalid content type, expected "text/event-stream"', status);
6277
- return;
6278
- }
6279
- if (__privateGet(this, _readyState) === this.CLOSED)
6280
- return;
6281
- __privateSet(this, _readyState, this.OPEN);
6282
- const openEvent = new Event("open");
6283
- if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != "object" || !body || !("getReader" in body)) {
6284
- __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid response body, expected a web ReadableStream", status), this.close();
6285
- return;
6286
- }
6287
- const decoder = new TextDecoder(), reader = body.getReader();
6288
- let open3 = true;
6289
- do {
6290
- const { done, value } = await reader.read();
6291
- value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open3 = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
6292
- } while (open3);
6293
- }), __privateAdd(this, _onFetchError, (err) => {
6294
- __privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
6295
- }), __privateAdd(this, _onEvent, (event) => {
6296
- typeof event.id == "string" && __privateSet(this, _lastEventId, event.id);
6297
- const messageEvent = new MessageEvent(event.event || "message", {
6298
- data: event.data,
6299
- origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin,
6300
- lastEventId: event.id || ""
6301
- });
6302
- __privateGet(this, _onMessage) && (!event.event || event.event === "message") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent);
6303
- }), __privateAdd(this, _onRetryChange, (value) => {
6304
- __privateSet(this, _reconnectInterval, value);
6305
- }), __privateAdd(this, _reconnect, () => {
6306
- __privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this);
6307
- });
6308
- try {
6309
- if (url instanceof URL)
6310
- __privateSet(this, _url, url);
6311
- else if (typeof url == "string")
6312
- __privateSet(this, _url, new URL(url, getBaseURL()));
6313
- else
6314
- throw new Error("Invalid URL");
6315
- } catch {
6316
- throw syntaxError("An invalid or illegal string was specified");
6317
- }
6318
- __privateSet(this, _parser, createParser({
6319
- onEvent: __privateGet(this, _onEvent),
6320
- onRetry: __privateGet(this, _onRetryChange)
6321
- })), __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _reconnectInterval, 3e3), __privateSet(this, _fetch, (_a = eventSourceInitDict == null ? void 0 : eventSourceInitDict.fetch) != null ? _a : globalThis.fetch), __privateSet(this, _withCredentials, (_b = eventSourceInitDict == null ? void 0 : eventSourceInitDict.withCredentials) != null ? _b : false), __privateMethod(this, _EventSource_instances, connect_fn).call(this);
6322
- }
6323
- /**
6324
- * Returns the state of this EventSource object's connection. It can have the values described below.
6325
- *
6326
- * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)
6327
- *
6328
- * Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface,
6329
- * defined in the TypeScript `dom` library.
6330
- *
6331
- * @public
6332
- */
6333
- get readyState() {
6334
- return __privateGet(this, _readyState);
6335
- }
6336
- /**
6337
- * Returns the URL providing the event stream.
6338
- *
6339
- * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)
6340
- *
6341
- * @public
6342
- */
6343
- get url() {
6344
- return __privateGet(this, _url).href;
6345
- }
6346
- /**
6347
- * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise.
6348
- *
6349
- * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)
6350
- */
6351
- get withCredentials() {
6352
- return __privateGet(this, _withCredentials);
6353
- }
6354
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */
6355
- get onerror() {
6356
- return __privateGet(this, _onError);
6357
- }
6358
- set onerror(value) {
6359
- __privateSet(this, _onError, value);
6360
- }
6361
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */
6362
- get onmessage() {
6363
- return __privateGet(this, _onMessage);
6364
- }
6365
- set onmessage(value) {
6366
- __privateSet(this, _onMessage, value);
6367
- }
6368
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
6369
- get onopen() {
6370
- return __privateGet(this, _onOpen);
6371
- }
6372
- set onopen(value) {
6373
- __privateSet(this, _onOpen, value);
6374
- }
6375
- addEventListener(type, listener, options) {
6376
- const listen = listener;
6377
- super.addEventListener(type, listen, options);
6378
- }
6379
- removeEventListener(type, listener, options) {
6380
- const listen = listener;
6381
- super.removeEventListener(type, listen, options);
6382
- }
6383
- /**
6384
- * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
6385
- *
6386
- * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
6387
- *
6388
- * @public
6389
- */
6390
- close() {
6391
- __privateGet(this, _reconnectTimer) && clearTimeout(__privateGet(this, _reconnectTimer)), __privateGet(this, _readyState) !== this.CLOSED && (__privateGet(this, _controller) && __privateGet(this, _controller).abort(), __privateSet(this, _readyState, this.CLOSED), __privateSet(this, _controller, void 0));
6392
- }
6393
- };
6394
- _readyState = /* @__PURE__ */ new WeakMap(), _url = /* @__PURE__ */ new WeakMap(), _redirectUrl = /* @__PURE__ */ new WeakMap(), _withCredentials = /* @__PURE__ */ new WeakMap(), _fetch = /* @__PURE__ */ new WeakMap(), _reconnectInterval = /* @__PURE__ */ new WeakMap(), _reconnectTimer = /* @__PURE__ */ new WeakMap(), _lastEventId = /* @__PURE__ */ new WeakMap(), _controller = /* @__PURE__ */ new WeakMap(), _parser = /* @__PURE__ */ new WeakMap(), _onError = /* @__PURE__ */ new WeakMap(), _onMessage = /* @__PURE__ */ new WeakMap(), _onOpen = /* @__PURE__ */ new WeakMap(), _EventSource_instances = /* @__PURE__ */ new WeakSet(), /**
6395
- * Connect to the given URL and start receiving events
6396
- *
6397
- * @internal
6398
- */
6399
- connect_fn = function() {
6400
- __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _controller, new AbortController()), __privateGet(this, _fetch)(__privateGet(this, _url), __privateMethod(this, _EventSource_instances, getRequestOptions_fn).call(this)).then(__privateGet(this, _onFetchResponse)).catch(__privateGet(this, _onFetchError));
6401
- }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
6402
- * Get request options for the `fetch()` request
6403
- *
6404
- * @returns The request options
6405
- * @internal
6406
- */
6407
- getRequestOptions_fn = function() {
6408
- var _a;
6409
- const init2 = {
6410
- // [spec] Let `corsAttributeState` be `Anonymous`…
6411
- // [spec] …will have their mode set to "cors"…
6412
- mode: "cors",
6413
- redirect: "follow",
6414
- headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
6415
- cache: "no-store",
6416
- signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
6417
- };
6418
- return "window" in globalThis && (init2.credentials = this.withCredentials ? "include" : "same-origin"), init2;
6419
- }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
6420
- * Handles the process referred to in the EventSource specification as "failing a connection".
6421
- *
6422
- * @param error - The error causing the connection to fail
6423
- * @param code - The HTTP status code, if available
6424
- * @internal
6425
- */
6426
- failConnection_fn = function(message, code) {
6427
- var _a;
6428
- __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
6429
- const errorEvent = new ErrorEvent("error", { code, message });
6430
- (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
6431
- }, /**
6432
- * Schedules a reconnection attempt against the EventSource endpoint.
6433
- *
6434
- * @param message - The error causing the connection to fail
6435
- * @param code - The HTTP status code, if available
6436
- * @internal
6437
- */
6438
- scheduleReconnect_fn = function(message, code) {
6439
- var _a;
6440
- if (__privateGet(this, _readyState) === this.CLOSED)
6441
- return;
6442
- __privateSet(this, _readyState, this.CONNECTING);
6443
- const errorEvent = new ErrorEvent("error", { code, message });
6444
- (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
6445
- }, _reconnect = /* @__PURE__ */ new WeakMap(), /**
6446
- * ReadyState representing an EventSource currently trying to connect
6447
- *
6448
- * @public
6449
- */
6450
- EventSource.CONNECTING = 0, /**
6451
- * ReadyState representing an EventSource connection that is open (eg connected)
6452
- *
6453
- * @public
6454
- */
6455
- EventSource.OPEN = 1, /**
6456
- * ReadyState representing an EventSource connection that is closed (eg disconnected)
6457
- *
6458
- * @public
6459
- */
6460
- EventSource.CLOSED = 2;
6461
- Object.defineProperty(EventSource, /* @__PURE__ */ Symbol.for("eventsource.supports-fetch-override"), {
6462
- value: true,
6463
- writable: false,
6464
- configurable: false,
6465
- enumerable: false
6466
- });
6467
- function getBaseURL() {
6468
- const doc = "document" in globalThis ? globalThis.document : void 0;
6469
- return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
6470
- }
6471
-
6472
- // src/client/channel.ts
6473
- var channelClient = (baseUrl) => {
6474
- const u = (p) => `${baseUrl}${p}`;
6475
- const JSON_CT = { "content-type": "application/json" };
6476
- const member = (channelId, name) => u(`/channels/${channelId}/members/${encodeURIComponent(name)}`);
6477
- return {
6478
- create: async (workspaceId, title, description, token) => {
6479
- const res = await fetch(u(`/workspaces/${workspaceId}/channels`), {
6480
- method: "POST",
6481
- headers: { ...JSON_CT, ...token ? { authorization: `Bearer ${token}` } : {} },
6482
- body: JSON.stringify({
6483
- ...title ? { title } : {},
6484
- ...description ? { description } : {}
6485
- })
6486
- });
6487
- if (!res.ok)
6488
- throw new Error(
6489
- `POST /workspaces/${workspaceId}/channels \u2192 ${res.status}: ${await res.text()}`
6490
- );
6491
- return await res.json();
6492
- },
6493
- manifest: async (channelId) => {
6494
- const res = await fetch(u(`/channels/${channelId}`));
6495
- if (!res.ok) throw new Error(`GET channel \u2192 ${res.status}: ${await res.text()}`);
6496
- return await res.json();
6497
- },
6498
- update: async (channelId, patch) => {
6499
- const res = await fetch(u(`/channels/${channelId}`), {
6500
- method: "PATCH",
6501
- headers: JSON_CT,
6502
- body: JSON.stringify(patch)
6503
- });
6504
- if (!res.ok) throw new Error(`PATCH channel \u2192 ${res.status}: ${await res.text()}`);
6505
- return await res.json();
6506
- },
6507
- help: async () => {
6508
- const res = await fetch(u("/channels/help"));
6509
- if (!res.ok) throw new Error(`GET /channels/help \u2192 ${res.status}: ${await res.text()}`);
6510
- return await res.text();
6511
- },
6512
- destroy: async (channelId) => {
6513
- const res = await fetch(u(`/channels/${channelId}`), { method: "DELETE" });
6514
- if (!res.ok) throw new Error(`DELETE channel \u2192 ${res.status}: ${await res.text()}`);
6515
- },
6516
- join: async (channelId, name, kind) => {
6517
- const res = await fetch(member(channelId, name), {
6518
- method: "PUT",
6519
- headers: JSON_CT,
6520
- body: JSON.stringify({ kind: kind ?? "agent" })
6521
- });
6522
- if (!res.ok) throw new Error(`join channel \u2192 ${res.status}: ${await res.text()}`);
6523
- return (await res.json()).members;
6524
- },
6525
- leave: async (channelId, name) => {
6526
- const res = await fetch(member(channelId, name), { method: "DELETE" });
6527
- if (!res.ok) throw new Error(`leave channel \u2192 ${res.status}: ${await res.text()}`);
6528
- },
6529
- members: async (channelId) => {
6530
- const res = await fetch(u(`/channels/${channelId}/members`));
6531
- if (!res.ok) throw new Error(`GET members \u2192 ${res.status}: ${await res.text()}`);
6532
- return (await res.json()).members;
6533
- },
6534
- send: async (channelId, msg) => {
6535
- const res = await fetch(u(`/channels/${channelId}/messages`), {
6536
- method: "POST",
6537
- headers: JSON_CT,
6538
- body: JSON.stringify(msg)
6539
- });
6540
- if (!res.ok) throw new Error(`POST channel message \u2192 ${res.status}: ${await res.text()}`);
6541
- return await res.json();
6542
- },
6543
- uploadAttachment: async (channelId, input) => {
6544
- const res = await fetch(
6545
- u(`/channels/${channelId}/attachments?filename=${encodeURIComponent(input.filename)}`),
6546
- { method: "POST", headers: { "content-type": input.contentType }, body: input.body }
6547
- );
6548
- if (!res.ok) throw new Error(`upload attachment \u2192 ${res.status}: ${await res.text()}`);
6549
- return await res.json();
6550
- },
6551
- downloadAttachment: async (channelId, attId) => {
6552
- const res = await fetch(u(`/channels/${channelId}/attachments/${attId}`));
6553
- if (!res.ok) throw new Error(`download attachment \u2192 ${res.status}: ${await res.text()}`);
6554
- return res;
6555
- },
6556
- read: async (channelId, opts) => {
6557
- const params = new URLSearchParams({ since: String(opts?.since ?? 0) });
6558
- if (opts?.for) params.set("for", opts.for);
6559
- const res = await fetch(u(`/channels/${channelId}/messages?${params}`));
6560
- if (!res.ok) throw new Error(`GET messages \u2192 ${res.status}: ${await res.text()}`);
6561
- return (await res.json()).messages;
6562
- },
6563
- listen: (channelId, opts) => {
6564
- let cursor = opts.since ?? 0;
6565
- const params = new URLSearchParams({ since: String(cursor) });
6566
- if (opts.for) params.set("for", opts.for);
6567
- if (opts.as) params.set("as", opts.as);
6568
- const es = new EventSource(u(`/channels/${channelId}/stream?${params}`));
6569
- es.onmessage = (e) => {
6570
- try {
6571
- const m = JSON.parse(e.data);
6572
- if (m.seq <= cursor) return;
6573
- cursor = m.seq;
6574
- opts.onMessage(m);
6575
- } catch (err) {
6576
- opts.onError?.(err);
6577
- }
6578
- };
6579
- es.onerror = (err) => opts.onError?.(err);
6580
- return () => es.close();
6581
- }
6582
- };
6583
6056
  };
6584
-
6585
- // src/commands/channel/close.ts
6586
- var closeCommand = defineCommand({
6587
- meta: { name: "close", description: "delete a channel (removes the room and all its messages)" },
6057
+ var init = defineCommand({
6058
+ meta: {
6059
+ name: "init",
6060
+ description: `write ${PROJECT_CONFIG_NAME} in the current directory (local state, gitignored)`
6061
+ },
6588
6062
  args: {
6589
- channel: { type: "positional", required: true, description: "channel id" },
6063
+ workspace: { type: "string", description: "workspace id (skip prompt)" },
6064
+ project: { type: "string", description: "project id (skip prompt)" },
6065
+ force: { type: "boolean", description: `overwrite existing ${PROJECT_CONFIG_NAME}` },
6590
6066
  ...common
6591
6067
  },
6592
6068
  run: async ({ args }) => {
6593
- await channelClient(resolveBaseUrl(args.url)).destroy(args.channel);
6594
- console.log(args.json ? toJson({ ok: true, deleted: args.channel }) : `closed ${args.channel}`);
6069
+ const server = resolveBaseUrl(args.url);
6070
+ const cfgPath = projectConfigPath();
6071
+ if (existsSync2(cfgPath) && !args.force)
6072
+ throw new Error(`${PROJECT_CONFIG_NAME} already exists. use --force to overwrite.`);
6073
+ const c = createClient(server);
6074
+ let project2;
6075
+ let workspace2;
6076
+ if (args.project) {
6077
+ project2 = await c.projects.get(Number(args.project));
6078
+ workspace2 = await c.workspaces.get(project2.workspaceId);
6079
+ } else {
6080
+ const wsList = await c.workspaces.list();
6081
+ workspace2 = args.workspace ? wsList.find((w) => w.id === Number(args.workspace)) ?? (() => {
6082
+ throw new Error(`workspace ${args.workspace} not found`);
6083
+ })() : await promptPick("workspace", wsList);
6084
+ const projects = await c.projects.listByWorkspace(workspace2.id);
6085
+ project2 = await promptPick("project", projects);
6086
+ }
6087
+ const cfg = {
6088
+ server,
6089
+ workspace: workspace2.id,
6090
+ project: project2.id,
6091
+ name: project2.name
6092
+ };
6093
+ saveProjectConfig(cfgPath, cfg);
6094
+ console.log(`wrote ${cfgPath}`);
6095
+ console.log(` server: ${server}`);
6096
+ console.log(` workspace: #${workspace2.id} ${workspace2.name}`);
6097
+ console.log(` project: #${project2.id} ${project2.name}`);
6595
6098
  }
6596
6099
  });
6597
6100
 
6598
- // src/commands/channel/shared.ts
6599
- var readStdin = async () => {
6600
- const chunks = [];
6601
- for await (const c of process.stdin) chunks.push(c);
6602
- return Buffer.concat(chunks).toString("utf8");
6603
- };
6604
- var renderRoster = (members) => members.length ? members.map((m) => ` ${m.name} (${m.kind})`).join("\n") : " (nobody online)";
6605
- var webLink = (url, channelId) => {
6606
- const origin = url.replace(/\/api\/?$/, "");
6607
- return origin === url ? null : `${origin}/channel/${channelId}`;
6608
- };
6609
- var inviteBlock = (url, channelId) => {
6610
- const web = webLink(url, channelId);
6611
- return [
6612
- "\u2500\u2500 Share this to invite anyone into the room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
6613
- ...web ? [`Humans \u2014 open in a browser: ${web}`, ""] : [],
6614
- "Agents \u2014 two curl steps:",
6615
- ` 1) see the room: curl -sS "${url}/channels/${channelId}"`,
6616
- ` 2) read protocol: curl -sS "${url}/channels/help" (then join / listen / send, pick your own NAME)`,
6617
- "",
6618
- `connection: url=${url} channel=${channelId}`,
6619
- "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
6620
- ].join("\n");
6101
+ // src/commands/session/shared.ts
6102
+ var resolveSession = async (client, projectId, handle) => {
6103
+ const asInt = Number(handle);
6104
+ if (Number.isInteger(asInt) && asInt > 0) {
6105
+ const byId = await client.sessions.get(asInt).catch(() => null);
6106
+ if (byId) return { id: byId.id, name: byId.name };
6107
+ }
6108
+ const byName = await client.sessions.findByName(projectId, handle);
6109
+ if (byName) return { id: byName.id, name: byName.name };
6110
+ throw new Error(`session "${handle}" not found in project ${projectId}`);
6621
6111
  };
6622
6112
 
6623
- // src/commands/channel/create.ts
6624
- var createCommand = defineCommand({
6625
- meta: {
6626
- name: "create",
6627
- description: "open a channel (chat room) in a workspace and print an invite"
6628
- },
6113
+ // src/commands/loop/create.ts
6114
+ var loopCreateCommand = defineCommand({
6115
+ meta: { name: "create", description: "create a recurring scheduled message on a session" },
6629
6116
  args: {
6630
- workspace: {
6117
+ session: { type: "positional", required: true, description: "session int id or name" },
6118
+ message: {
6631
6119
  type: "string",
6632
- description: "owning workspace id (or the cwd .baton.json workspace)"
6120
+ required: true,
6121
+ description: "message sent to the session each beat"
6633
6122
  },
6634
- name: { type: "string", description: "room title (optional)" },
6635
- desc: {
6123
+ every: {
6636
6124
  type: "string",
6637
- description: "room self-description: purpose / topic / how to participate"
6125
+ required: true,
6126
+ description: "interval: 90s / 30m / 2h / 1d (min 30s)"
6638
6127
  },
6128
+ name: { type: "string", description: "optional label" },
6129
+ off: { type: "boolean", description: "create paused (disabled)" },
6130
+ project: { type: "string", description: "project id (overrides .baton.json)" },
6639
6131
  ...common
6640
6132
  },
6641
6133
  run: async ({ args }) => {
6642
- const url = resolveBaseUrl(args.url);
6643
- const workspaceId = resolveWorkspaceId(args);
6644
- const { channelId } = await channelClient(url).create(
6645
- workspaceId,
6646
- args.name,
6647
- args.desc,
6648
- resolveBearer()
6649
- );
6650
- if (args.json) {
6651
- console.log(toJson({ channelId, url, workspaceId, name: args.name, description: args.desc }));
6652
- return;
6653
- }
6654
- console.log(`channel ${channelId} opened in workspace ${workspaceId}
6655
- `);
6656
- console.log(inviteBlock(url, channelId));
6134
+ const c = clientFor(args);
6135
+ const session2 = await resolveSession(c, resolveProjectId(args), args.session);
6136
+ const loop2 = await c.loops.create(session2.id, {
6137
+ message: args.message,
6138
+ intervalSec: parseDuration(args.every),
6139
+ name: args.name,
6140
+ enabled: !args.off
6141
+ });
6142
+ console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
6657
6143
  }
6658
6144
  });
6659
6145
 
6660
- // src/commands/channel/download.ts
6661
- import { writeFileSync as writeFileSync2 } from "node:fs";
6662
- var downloadCommand = defineCommand({
6663
- meta: { name: "download", description: "download a channel attachment to a file" },
6146
+ // src/commands/loop/disable.ts
6147
+ var loopDisableCommand = defineCommand({
6148
+ meta: { name: "disable", description: "disable (pause) a loop" },
6664
6149
  args: {
6665
- channel: { type: "positional", required: true, description: "channel id" },
6666
- att: { type: "positional", required: true, description: "attachment id (from a message ref)" },
6667
- out: { type: "string", alias: "o", description: "output file path (default: ./<attId>)" },
6150
+ loop: { type: "positional", required: true, description: "loop int id" },
6668
6151
  ...common
6669
6152
  },
6670
6153
  run: async ({ args }) => {
6671
- const url = resolveBaseUrl(args.url);
6672
- const res = await channelClient(url).downloadAttachment(args.channel, args.att);
6673
- const out = args.out ?? args.att;
6674
- writeFileSync2(out, Buffer.from(await res.arrayBuffer()));
6675
- console.log(`saved \u2192 ${out}`);
6154
+ const c = clientFor(args);
6155
+ const loop2 = await c.loops.update(Number(args.loop), { enabled: false });
6156
+ console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
6676
6157
  }
6677
6158
  });
6678
6159
 
6679
- // src/commands/channel/info.ts
6680
- var aboutCommand = defineCommand({
6681
- meta: { name: "about", description: "show a channel's manifest (description + online roster)" },
6160
+ // src/commands/loop/enable.ts
6161
+ var loopEnableCommand = defineCommand({
6162
+ meta: { name: "enable", description: "enable (resume) a loop" },
6682
6163
  args: {
6683
- channel: { type: "positional", required: true, description: "channel id" },
6164
+ loop: { type: "positional", required: true, description: "loop int id" },
6684
6165
  ...common
6685
6166
  },
6686
6167
  run: async ({ args }) => {
6687
- const url = resolveBaseUrl(args.url);
6688
- const m = await channelClient(url).manifest(args.channel);
6689
- if (args.json) {
6690
- console.log(toJson(m));
6691
- return;
6692
- }
6693
- console.log(`${m.title ?? "(untitled)"} [${m.id}]`);
6694
- if (m.description) console.log(m.description);
6695
- console.log(`
6696
- online (${m.members.length}):
6697
- ${renderRoster(m.members)}`);
6698
- console.log(`
6699
- protocol: curl -sS "${url}${m.help}"`);
6168
+ const c = clientFor(args);
6169
+ const loop2 = await c.loops.update(Number(args.loop), { enabled: true });
6170
+ console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
6700
6171
  }
6701
6172
  });
6702
- var helpCommand = defineCommand({
6703
- meta: { name: "help", description: "print the channel protocol doc" },
6704
- args: { ...common },
6173
+
6174
+ // src/commands/loop/get.ts
6175
+ var loopGetCommand = defineCommand({
6176
+ meta: { name: "get", description: "show one loop" },
6177
+ args: {
6178
+ loop: { type: "positional", required: true, description: "loop int id" },
6179
+ ...common
6180
+ },
6705
6181
  run: async ({ args }) => {
6706
- console.log(await channelClient(resolveBaseUrl(args.url)).help());
6182
+ const c = clientFor(args);
6183
+ const loop2 = await c.loops.get(Number(args.loop));
6184
+ console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
6707
6185
  }
6708
6186
  });
6709
- var membersCommand = defineCommand({
6710
- meta: { name: "members", description: "list the channel's online roster" },
6187
+
6188
+ // src/commands/loop/ls.ts
6189
+ var loopLsCommand = defineCommand({
6190
+ meta: { name: "ls", description: "list a session's loops" },
6711
6191
  args: {
6712
- channel: { type: "positional", required: true, description: "channel id" },
6192
+ session: { type: "positional", required: true, description: "session int id or name" },
6193
+ project: { type: "string", description: "project id (overrides .baton.json)" },
6713
6194
  ...common
6714
6195
  },
6715
6196
  run: async ({ args }) => {
6716
- const url = resolveBaseUrl(args.url);
6717
- const members = await channelClient(url).members(args.channel);
6718
- console.log(args.json ? toJson(members) : renderRoster(members));
6197
+ const c = clientFor(args);
6198
+ const session2 = await resolveSession(c, resolveProjectId(args), args.session);
6199
+ const loops = await c.loops.listBySession(session2.id);
6200
+ console.log(renderList(loops, fmtLoop, Boolean(args.json)));
6719
6201
  }
6720
6202
  });
6721
- var updateCommand = defineCommand({
6722
- meta: { name: "update", description: "update a channel's title / description (topic + rules)" },
6203
+
6204
+ // src/commands/loop/rm.ts
6205
+ var loopRmCommand = defineCommand({
6206
+ meta: { name: "rm", description: "delete a loop" },
6723
6207
  args: {
6724
- channel: { type: "positional", required: true, description: "channel id" },
6725
- title: { type: "string", description: "new title" },
6726
- desc: { type: "string", description: "new description (purpose / topic / rules)" },
6208
+ loop: { type: "positional", required: true, description: "loop int id" },
6727
6209
  ...common
6728
6210
  },
6729
6211
  run: async ({ args }) => {
6730
- if (args.title === void 0 && args.desc === void 0)
6731
- throw new Error("nothing to update: pass --title and/or --desc");
6732
- const url = resolveBaseUrl(args.url);
6733
- const ch = await channelClient(url).update(args.channel, {
6734
- title: args.title,
6735
- description: args.desc
6736
- });
6737
- console.log(args.json ? toJson(ch) : `updated ${ch.id}${ch.title ? ` \u2014 ${ch.title}` : ""}`);
6212
+ const c = clientFor(args);
6213
+ const id = Number(args.loop);
6214
+ await c.loops.remove(id);
6215
+ console.log(removed("loop", id, Boolean(args.json)));
6216
+ }
6217
+ });
6218
+
6219
+ // src/commands/loop/set.ts
6220
+ var loopSetCommand = defineCommand({
6221
+ meta: { name: "set", description: "update a loop (message / interval / name)" },
6222
+ args: {
6223
+ loop: { type: "positional", required: true, description: "loop int id" },
6224
+ message: { type: "string", description: "new message" },
6225
+ every: { type: "string", description: "new interval: 90s / 30m / 2h / 1d" },
6226
+ name: { type: "string", description: "new label" },
6227
+ ...common
6228
+ },
6229
+ run: async ({ args }) => {
6230
+ const c = clientFor(args);
6231
+ const patch = {};
6232
+ if (args.message !== void 0) patch.message = args.message;
6233
+ if (args.every !== void 0 && args.every !== "") patch.intervalSec = parseDuration(args.every);
6234
+ if (args.name !== void 0) patch.name = args.name;
6235
+ const loop2 = await c.loops.update(Number(args.loop), patch);
6236
+ console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
6237
+ }
6238
+ });
6239
+
6240
+ // src/commands/loop.ts
6241
+ var loop = defineCommand({
6242
+ meta: { name: "loop", description: "recurring scheduled messages that wake a session" },
6243
+ subCommands: {
6244
+ create: loopCreateCommand,
6245
+ ls: loopLsCommand,
6246
+ get: loopGetCommand,
6247
+ set: loopSetCommand,
6248
+ enable: loopEnableCommand,
6249
+ disable: loopDisableCommand,
6250
+ rm: loopRmCommand
6251
+ }
6252
+ });
6253
+
6254
+ // src/commands/notify.ts
6255
+ var describe = (result) => {
6256
+ if (result.status === "sent") return `notified ${result.devices} device(s)`;
6257
+ if (result.status === "throttled")
6258
+ return `throttled: this session can notify again in ${result.retryAfterSec}s`;
6259
+ return "nobody to notify: no human has messaged this session, or they have not enabled notifications";
6260
+ };
6261
+ var notify = defineCommand({
6262
+ meta: {
6263
+ name: "notify",
6264
+ description: "Push a phone notification to the person working with this session. Use it only when a human needs to act now: something is wrong, a decision is needed, or the work is blocked. Do not use it for routine progress, and not to announce that a turn finished (that is already pushed). At most one notification per session every 10 minutes; text is cut at 200 characters."
6265
+ },
6266
+ args: {
6267
+ text: { type: "positional", required: true, description: "one short line for the lock screen" },
6268
+ ...common
6269
+ },
6270
+ run: async ({ args }) => {
6271
+ const raw = process.env.BATON_SESSION_ID;
6272
+ if (raw === void 0)
6273
+ throw new Error("baton notify only works inside a baton session (BATON_SESSION_ID is unset)");
6274
+ const sessionId = Number(raw);
6275
+ if (!Number.isSafeInteger(sessionId) || sessionId <= 0)
6276
+ throw new Error("invalid BATON_SESSION_ID");
6277
+ const result = await clientFor(args).sessions.notify(sessionId, args.text);
6278
+ console.log(args.json ? toJson(result) : describe(result));
6279
+ }
6280
+ });
6281
+
6282
+ // src/commands/project.ts
6283
+ var createProject = (c, input, json) => c.projects.create(input).then((p) => renderOne(p, fmtProject, json));
6284
+ var listProjects = (c, workspaceId, json) => c.projects.listByWorkspace(workspaceId).then((ps) => renderList(ps, fmtProject, json));
6285
+ var getProject = (c, id, json) => c.projects.get(id).then((p) => renderOne(p, fmtProject, json));
6286
+ var updateProject = (c, id, patch, json) => c.projects.update(id, patch).then((p) => renderOne(p, fmtProject, json));
6287
+ var removeProject = (c, id, json) => c.projects.remove(id).then(() => removed("project", id, json));
6288
+ var project = defineCommand({
6289
+ meta: { name: "project", description: "manage projects" },
6290
+ subCommands: {
6291
+ create: defineCommand({
6292
+ meta: { name: "create", description: "create a project" },
6293
+ args: {
6294
+ name: { type: "positional", required: true },
6295
+ workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
6296
+ desc: { type: "string", description: "description" },
6297
+ ...common
6298
+ },
6299
+ run: async ({ args }) => {
6300
+ const out = await createProject(
6301
+ clientFor(args),
6302
+ { workspaceId: resolveWorkspaceId(args), name: args.name, description: args.desc },
6303
+ Boolean(args.json)
6304
+ );
6305
+ console.log(out);
6306
+ }
6307
+ }),
6308
+ ls: defineCommand({
6309
+ meta: { name: "ls", description: "list projects in a workspace" },
6310
+ args: {
6311
+ workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
6312
+ ...common
6313
+ },
6314
+ run: async ({ args }) => {
6315
+ console.log(
6316
+ await listProjects(clientFor(args), resolveWorkspaceId(args), Boolean(args.json))
6317
+ );
6318
+ }
6319
+ }),
6320
+ get: defineCommand({
6321
+ meta: { name: "get", description: "get a project" },
6322
+ args: { id: { type: "positional", required: true }, ...common },
6323
+ run: async ({ args }) => {
6324
+ console.log(await getProject(clientFor(args), Number(args.id), Boolean(args.json)));
6325
+ }
6326
+ }),
6327
+ update: defineCommand({
6328
+ meta: { name: "update", description: "rename a project / set its description" },
6329
+ args: {
6330
+ id: { type: "positional", required: true },
6331
+ name: { type: "string", description: "new name" },
6332
+ desc: { type: "string", description: "new description" },
6333
+ ...common
6334
+ },
6335
+ run: async ({ args }) => {
6336
+ const patch = {
6337
+ ...args.name !== void 0 ? { name: args.name } : {},
6338
+ ...args.desc !== void 0 ? { description: args.desc } : {}
6339
+ };
6340
+ if (Object.keys(patch).length === 0) throw new Error("pass --name and/or --desc");
6341
+ console.log(
6342
+ await updateProject(clientFor(args), Number(args.id), patch, Boolean(args.json))
6343
+ );
6344
+ }
6345
+ }),
6346
+ rm: defineCommand({
6347
+ meta: { name: "rm", description: "delete a project" },
6348
+ args: { id: { type: "positional", required: true }, ...common },
6349
+ run: async ({ args }) => {
6350
+ console.log(await removeProject(clientFor(args), Number(args.id), Boolean(args.json)));
6351
+ }
6352
+ })
6738
6353
  }
6739
6354
  });
6740
6355
 
6741
- // src/commands/channel/listen.ts
6742
- import { mkdirSync, writeFileSync as writeFileSync3 } from "node:fs";
6743
- import { tmpdir } from "node:os";
6744
- import { join as join2 } from "node:path";
6356
+ // src/provisioner/config.ts
6357
+ import { execFile } from "node:child_process";
6358
+ import { constants } from "node:fs";
6359
+ import { access, readFile, stat as stat2 } from "node:fs/promises";
6360
+ import { isAbsolute } from "node:path";
6361
+ import { promisify } from "node:util";
6745
6362
 
6746
6363
  // ../shared/src/domain/attachment.ts
6747
6364
  var labelAttachments = (attachments) => {
@@ -6753,9 +6370,6 @@ var labelAttachments = (attachments) => {
6753
6370
  });
6754
6371
  };
6755
6372
 
6756
- // ../shared/src/domain/channel.ts
6757
- var isMessageFor = (msg, name) => !msg.to || msg.to.length === 0 || msg.to.includes(name);
6758
-
6759
6373
  // ../shared/src/domain/file.ts
6760
6374
  var FILE_TEXT_MAX_BYTES = 5 * 1024 * 1024;
6761
6375
  var FILE_DIRECTORY_MAX_ENTRIES = 2e3;
@@ -6799,738 +6413,650 @@ var normalizeFilePath = (value, allowRoot = false) => {
6799
6413
  var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([".git", ".ssh", ".aws", ".kube", ".gnupg"]);
6800
6414
  var SENSITIVE_NAMES = /* @__PURE__ */ new Set([
6801
6415
  ".baton.json",
6802
- ".envrc",
6803
- ".npmrc",
6804
- ".netrc",
6805
- ".pypirc",
6806
- ".git-credentials",
6807
- ".pgpass",
6808
- ".htpasswd",
6809
- ".credentials.json"
6810
- ]);
6811
- var SENSITIVE_EXTENSIONS = [".pem", ".key", ".p12", ".pfx", ".jks", ".keystore"];
6812
- var isSensitiveName = (name) => SENSITIVE_NAMES.has(name) || /^\.env(\..+)?$/.test(name) && !/\.(example|sample|template)$/.test(name) || /^id_(rsa|dsa|ecdsa|ed25519)$/.test(name) || SENSITIVE_EXTENSIONS.some((extension2) => name.endsWith(extension2));
6813
- var isSensitiveFilePath = (path) => {
6814
- const segments = path.toLowerCase().split("/");
6815
- return segments.some((segment) => SENSITIVE_SEGMENTS.has(segment)) || isSensitiveName(segments.at(-1) ?? "");
6816
- };
6817
- var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set([
6818
- "node_modules",
6819
- ".pnpm-store",
6820
- "dist",
6821
- "build",
6822
- "coverage",
6823
- ".next",
6824
- ".turbo",
6825
- ".cache",
6826
- ".venv",
6827
- "__pycache__",
6828
- "target"
6829
- ]);
6830
- var isExcludedDirectoryName = (name) => EXCLUDED_DIRECTORIES.has(name);
6831
-
6832
- // ../shared/src/domain/service.ts
6833
- var SERVICE_PUBLIC_URL_MAX_LENGTH = 2048;
6834
- var SERVICE_NOTE_MAX_LENGTH = 500;
6835
- var isServicePublicUrl = (value) => {
6836
- if (value.length === 0 || value.length > SERVICE_PUBLIC_URL_MAX_LENGTH) return false;
6837
- try {
6838
- const url = new URL(value);
6839
- return url.protocol === "http:" || url.protocol === "https:";
6840
- } catch {
6841
- return false;
6842
- }
6843
- };
6844
- var isServiceNote = (value) => value.length > 0 && value.length <= SERVICE_NOTE_MAX_LENGTH;
6845
-
6846
- // ../shared/src/domain/session.ts
6847
- var AGENT_KINDS = ["claude-code", "codex", "pi"];
6848
- var isAgentKind = (v) => typeof v === "string" && AGENT_KINDS.includes(v);
6849
- var EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"];
6850
- var isAgentEffort = (v) => typeof v === "string" && EFFORTS.includes(v);
6851
-
6852
- // ../shared/src/domain/session-event.ts
6853
- var agentMessageText = (payload) => {
6854
- if (typeof payload !== "object" || payload === null) return null;
6855
- const event = payload;
6856
- if (!["item.started", "item.updated", "item.completed"].includes(String(event.type))) return null;
6857
- const item = event.item;
6858
- if (typeof item !== "object" || item === null) return null;
6859
- const { type, id, text } = item;
6860
- if (type !== "agent_message" || typeof id !== "string" || typeof text !== "string") return null;
6861
- const trimmed = text.trim();
6862
- return trimmed ? { id, text: trimmed } : null;
6863
- };
6864
-
6865
- // ../shared/src/domain/worker-creation.ts
6866
- var WORKER_TEMPLATES = [
6867
- { id: "mini-glm-codeplan", name: "GLM Code Plan", agentKinds: ["claude-code", "pi"] },
6868
- { id: "mini-dashscope-codeplan", name: "DashScope Code Plan", agentKinds: ["claude-code", "pi"] },
6869
- { id: "mini-codex", name: "\u672C\u673A Codex", agentKinds: ["codex"] }
6870
- ];
6871
- var workerTemplateAgentKinds = (id) => id === "mini-local" ? AGENT_KINDS : WORKER_TEMPLATES.find((template) => template.id === id)?.agentKinds ?? [];
6872
- var isWorkerTemplateId = (value) => typeof value === "string" && workerTemplateAgentKinds(value).length > 0;
6873
-
6874
- // src/commands/channel/listen.ts
6875
- var PREVIEW = 280;
6876
- var describe = (e) => {
6877
- const ev = e;
6878
- return ev?.code ? `${ev.code}${ev.message ? ` ${ev.message}` : ""}` : String(e);
6879
- };
6880
- var runListen = async (o) => {
6881
- const emit = (x) => {
6882
- process.stdout.write(`${JSON.stringify(x)}
6883
- `);
6884
- };
6885
- const spillDir = join2(tmpdir(), "channel", o.channel);
6886
- mkdirSync(spillDir, { recursive: true });
6887
- const surface = (m) => {
6888
- const text = typeof m.text === "string" ? m.text : "";
6889
- const file = join2(spillDir, `${m.seq}.txt`);
6890
- writeFileSync3(file, text);
6891
- const truncated = text.length > PREVIEW;
6892
- emit({
6893
- type: "channel.message",
6894
- seq: m.seq,
6895
- from: m.from,
6896
- senderKind: m.senderKind,
6897
- to: m.to,
6898
- mine: o.from ? isMessageFor(m, o.from) : void 0,
6899
- ts: m.ts,
6900
- chars: text.length,
6901
- truncated,
6902
- preview: truncated ? `${text.slice(0, PREVIEW)} \u2026` : text,
6903
- // BASE-prefixed urls so the subscribing agent can fetch directly.
6904
- attachments: m.attachments?.length ? m.attachments.map((a) => ({
6905
- filename: a.filename,
6906
- contentType: a.contentType,
6907
- size: a.size,
6908
- url: `${o.url}${a.url}`
6909
- })) : void 0,
6910
- file
6911
- });
6912
- };
6913
- const close = channelClient(o.url).listen(o.channel, {
6914
- since: o.since,
6915
- for: o.for,
6916
- as: o.from,
6917
- // Skip our own messages (they echo back over the same channel).
6918
- onMessage: (m) => {
6919
- if (m.from !== o.from) surface(m);
6920
- },
6921
- onError: (e) => emit({ type: "channel.error", error: describe(e) })
6922
- });
6923
- emit({ type: "channel.listening", channel: o.channel, from: o.from, url: o.url });
6924
- await new Promise((resolve4) => {
6925
- const stop = () => {
6926
- close();
6927
- resolve4();
6928
- };
6929
- process.on("SIGINT", stop);
6930
- process.on("SIGTERM", stop);
6931
- });
6932
- };
6933
- var listenCommand = defineCommand({
6934
- meta: {
6935
- name: "listen",
6936
- description: "stream a channel: one JSON line per message (for a background subscriber)"
6937
- },
6938
- args: {
6939
- channel: { type: "positional", required: true, description: "channel id" },
6940
- from: {
6941
- type: "string",
6942
- description: "your name; your own messages skipped, presence kept fresh"
6943
- },
6944
- since: { type: "string", description: "replay messages after this seq (default 0 = full)" },
6945
- for: { type: "string", description: "only broadcasts + messages addressed to this name" },
6946
- ...common
6947
- },
6948
- run: async ({ args }) => {
6949
- await runListen({
6950
- url: resolveBaseUrl(args.url),
6951
- channel: args.channel,
6952
- from: args.from ?? "",
6953
- since: args.since ? Number(args.since) : 0,
6954
- for: args.for
6955
- });
6956
- }
6957
- });
6958
-
6959
- // src/commands/channel/join.ts
6960
- var joinCommand = defineCommand({
6961
- meta: {
6962
- name: "join",
6963
- description: "join a channel (register presence); --listen to also stream"
6964
- },
6965
- args: {
6966
- channel: { type: "positional", required: true, description: "channel id" },
6967
- from: { type: "string", required: true, description: "your participant name" },
6968
- kind: { type: "string", description: "your kind: agent (default) | human" },
6969
- listen: {
6970
- type: "boolean",
6971
- description: "after joining, stream messages (background subscriber)"
6972
- },
6973
- since: { type: "string", description: "with --listen: replay after this seq (default 0)" },
6974
- ...common
6975
- },
6976
- run: async ({ args }) => {
6977
- const url = resolveBaseUrl(args.url);
6978
- const kind = args.kind === "human" ? "human" : "agent";
6979
- const members = await channelClient(url).join(args.channel, args.from, kind);
6980
- if (!args.listen) {
6981
- if (args.json) console.log(toJson({ joined: args.from, members }));
6982
- else
6983
- console.log(`joined as ${args.from} (${members.length} online)
6984
- ${renderRoster(members)}`);
6985
- return;
6986
- }
6987
- await runListen({
6988
- url,
6989
- channel: args.channel,
6990
- from: args.from,
6991
- since: args.since ? Number(args.since) : 0
6992
- });
6993
- }
6994
- });
6995
-
6996
- // src/commands/channel/read.ts
6997
- var fmtMessage = (m, base) => {
6998
- const head = `${m.seq} ${m.from} ${m.text}`;
6999
- const atts = (m.attachments ?? []).map((a) => `
7000
- \u{1F4CE} ${a.filename} \u2192 ${base}${a.url}`).join("");
7001
- return head + atts;
7002
- };
7003
- var readCommand = defineCommand({
7004
- meta: { name: "read", description: "poll channel messages once (curl-style, since a cursor)" },
7005
- args: {
7006
- channel: { type: "positional", required: true, description: "channel id" },
7007
- since: { type: "string", description: "only messages after this seq (default 0)" },
7008
- from: { type: "string", description: "skip messages from this name (no echo)" },
7009
- for: { type: "string", description: "only broadcasts + messages addressed to this name" },
7010
- ...common
7011
- },
7012
- run: async ({ args }) => {
7013
- const url = resolveBaseUrl(args.url);
7014
- const msgs = await channelClient(url).read(args.channel, {
7015
- since: args.since ? Number(args.since) : 0,
7016
- for: args.for
7017
- });
7018
- const out = args.from ? msgs.filter((m) => m.from !== args.from) : msgs;
7019
- if (args.json) console.log(toJson(out));
7020
- else console.log(out.length ? out.map((m) => fmtMessage(m, url)).join("\n") : "(none)");
7021
- }
7022
- });
7023
-
7024
- // src/commands/channel/send.ts
7025
- import { openAsBlob as openAsBlob3, readFileSync as readFileSync2 } from "node:fs";
7026
- import { basename as basename3 } from "node:path";
7027
-
7028
- // src/commands/attach.ts
7029
- import { openAsBlob as openAsBlob2 } from "node:fs";
7030
- import { basename as basename2 } from "node:path";
7031
- var attachPaths = (raw) => (raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw]).flatMap((p) => p.split(",")).map((p) => p.trim()).filter(Boolean);
7032
- var uploadAttachments = async (client, sessionId, paths) => {
7033
- const out = [];
7034
- for (const path of paths) {
7035
- const body = await openAsBlob2(path, { type: contentTypeForPath(path) });
7036
- out.push(
7037
- await client.sessions.uploadAttachment(sessionId, {
7038
- filename: basename2(path),
7039
- contentType: contentTypeForPath(path),
7040
- body
7041
- })
7042
- );
7043
- }
7044
- return out;
7045
- };
7046
-
7047
- // src/commands/channel/send.ts
7048
- var sendCommand = defineCommand({
7049
- meta: { name: "send", description: "post one message to a channel" },
7050
- args: {
7051
- channel: { type: "positional", required: true, description: "channel id" },
7052
- text: { type: "positional", required: false, description: "message text (short one-liner)" },
7053
- from: { type: "string", description: "your participant name (default: peer)" },
7054
- to: {
7055
- type: "string",
7056
- description: "direct it at these names (comma-separated); omit = broadcast"
7057
- },
7058
- file: { type: "string", description: "read message body from this file (for large content)" },
7059
- attach: {
7060
- type: "string",
7061
- description: "attach file(s) (comma-separated paths); uploaded + carried on the message"
7062
- },
7063
- ...common
7064
- },
7065
- run: async ({ args }) => {
7066
- const url = resolveBaseUrl(args.url);
7067
- const client = channelClient(url);
7068
- let text = args.text;
7069
- if (text === void 0 && args.file) text = readFileSync2(args.file, "utf8");
7070
- if (text === void 0 && !process.stdin.isTTY) text = await readStdin();
7071
- const attachments = [];
7072
- for (const path of attachPaths(args.attach)) {
7073
- attachments.push(
7074
- await client.uploadAttachment(args.channel, {
7075
- filename: basename3(path),
7076
- contentType: contentTypeForPath(path),
7077
- body: await openAsBlob3(path, { type: contentTypeForPath(path) })
7078
- })
7079
- );
7080
- }
7081
- if (!text && attachments.length === 0)
7082
- throw new Error(
7083
- "nothing to send: provide text, --file <path>, --attach <file>, or pipe stdin"
7084
- );
7085
- const msg = await client.send(args.channel, {
7086
- from: args.from ?? "peer",
7087
- text: text ?? "",
7088
- to: splitCsv(args.to),
7089
- attachments: attachments.length ? attachments : void 0
7090
- });
7091
- if (args.json) console.log(toJson(msg));
7092
- else console.log(`sent (seq ${msg.seq})`);
7093
- }
7094
- });
7095
-
7096
- // src/commands/channel.ts
7097
- var channel = defineCommand({
7098
- meta: {
7099
- name: "channel",
7100
- description: "multi-agent chat room: create / about / update / help / join / members / send / read / listen / download / close"
7101
- },
7102
- subCommands: {
7103
- create: createCommand,
7104
- about: aboutCommand,
7105
- update: updateCommand,
7106
- help: helpCommand,
7107
- join: joinCommand,
7108
- members: membersCommand,
7109
- send: sendCommand,
7110
- read: readCommand,
7111
- listen: listenCommand,
7112
- download: downloadCommand,
7113
- close: closeCommand
7114
- }
7115
- });
7116
-
7117
- // src/commands/init.ts
7118
- import { existsSync as existsSync2 } from "node:fs";
7119
- import * as readline from "node:readline/promises";
7120
- var promptPick = async (label, items) => {
7121
- if (items.length === 0) throw new Error(`no ${label}s found on server`);
7122
- console.log(`${label}s:`);
7123
- items.forEach((it, i) => {
7124
- console.log(` [${i + 1}] #${it.id} ${it.name}`);
7125
- });
7126
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
7127
- try {
7128
- const ans = (await rl.question(`pick a ${label} (1-${items.length}): `)).trim();
7129
- const idx = Number(ans) - 1;
7130
- const picked = items[idx];
7131
- if (!picked) throw new Error(`invalid choice: ${ans}`);
7132
- return picked;
7133
- } finally {
7134
- rl.close();
7135
- }
7136
- };
7137
- var init = defineCommand({
7138
- meta: {
7139
- name: "init",
7140
- description: `write ${PROJECT_CONFIG_NAME} in the current directory (local state, gitignored)`
7141
- },
7142
- args: {
7143
- workspace: { type: "string", description: "workspace id (skip prompt)" },
7144
- project: { type: "string", description: "project id (skip prompt)" },
7145
- force: { type: "boolean", description: `overwrite existing ${PROJECT_CONFIG_NAME}` },
7146
- ...common
7147
- },
7148
- run: async ({ args }) => {
7149
- const server = resolveBaseUrl(args.url);
7150
- const cfgPath = projectConfigPath();
7151
- if (existsSync2(cfgPath) && !args.force)
7152
- throw new Error(`${PROJECT_CONFIG_NAME} already exists. use --force to overwrite.`);
7153
- const c = createClient(server);
7154
- let project2;
7155
- let workspace2;
7156
- if (args.project) {
7157
- project2 = await c.projects.get(Number(args.project));
7158
- workspace2 = await c.workspaces.get(project2.workspaceId);
7159
- } else {
7160
- const wsList = await c.workspaces.list();
7161
- workspace2 = args.workspace ? wsList.find((w) => w.id === Number(args.workspace)) ?? (() => {
7162
- throw new Error(`workspace ${args.workspace} not found`);
7163
- })() : await promptPick("workspace", wsList);
7164
- const projects = await c.projects.listByWorkspace(workspace2.id);
7165
- project2 = await promptPick("project", projects);
7166
- }
7167
- const cfg = {
7168
- server,
7169
- workspace: workspace2.id,
7170
- project: project2.id,
7171
- name: project2.name
7172
- };
7173
- saveProjectConfig(cfgPath, cfg);
7174
- console.log(`wrote ${cfgPath}`);
7175
- console.log(` server: ${server}`);
7176
- console.log(` workspace: #${workspace2.id} ${workspace2.name}`);
7177
- console.log(` project: #${project2.id} ${project2.name}`);
7178
- }
7179
- });
7180
-
7181
- // src/commands/session/shared.ts
7182
- var resolveSession = async (client, projectId, handle) => {
7183
- const asInt = Number(handle);
7184
- if (Number.isInteger(asInt) && asInt > 0) {
7185
- const byId = await client.sessions.get(asInt).catch(() => null);
7186
- if (byId) return { id: byId.id, name: byId.name };
7187
- }
7188
- const byName = await client.sessions.findByName(projectId, handle);
7189
- if (byName) return { id: byName.id, name: byName.name };
7190
- throw new Error(`session "${handle}" not found in project ${projectId}`);
6416
+ ".envrc",
6417
+ ".npmrc",
6418
+ ".netrc",
6419
+ ".pypirc",
6420
+ ".git-credentials",
6421
+ ".pgpass",
6422
+ ".htpasswd",
6423
+ ".credentials.json"
6424
+ ]);
6425
+ var SENSITIVE_EXTENSIONS = [".pem", ".key", ".p12", ".pfx", ".jks", ".keystore"];
6426
+ var isSensitiveName = (name) => SENSITIVE_NAMES.has(name) || /^\.env(\..+)?$/.test(name) && !/\.(example|sample|template)$/.test(name) || /^id_(rsa|dsa|ecdsa|ed25519)$/.test(name) || SENSITIVE_EXTENSIONS.some((extension2) => name.endsWith(extension2));
6427
+ var isSensitiveFilePath = (path) => {
6428
+ const segments = path.toLowerCase().split("/");
6429
+ return segments.some((segment) => SENSITIVE_SEGMENTS.has(segment)) || isSensitiveName(segments.at(-1) ?? "");
7191
6430
  };
6431
+ var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set([
6432
+ "node_modules",
6433
+ ".pnpm-store",
6434
+ "dist",
6435
+ "build",
6436
+ "coverage",
6437
+ ".next",
6438
+ ".turbo",
6439
+ ".cache",
6440
+ ".venv",
6441
+ "__pycache__",
6442
+ "target"
6443
+ ]);
6444
+ var isExcludedDirectoryName = (name) => EXCLUDED_DIRECTORIES.has(name);
7192
6445
 
7193
- // src/commands/loop/create.ts
7194
- var loopCreateCommand = defineCommand({
7195
- meta: { name: "create", description: "create a recurring scheduled message on a session" },
7196
- args: {
7197
- session: { type: "positional", required: true, description: "session int id or name" },
7198
- message: {
7199
- type: "string",
7200
- required: true,
7201
- description: "message sent to the session each beat"
7202
- },
7203
- every: {
7204
- type: "string",
7205
- required: true,
7206
- description: "interval: 90s / 30m / 2h / 1d (min 30s)"
7207
- },
7208
- name: { type: "string", description: "optional label" },
7209
- off: { type: "boolean", description: "create paused (disabled)" },
7210
- project: { type: "string", description: "project id (overrides .baton.json)" },
7211
- ...common
7212
- },
7213
- run: async ({ args }) => {
7214
- const c = clientFor(args);
7215
- const session2 = await resolveSession(c, resolveProjectId(args), args.session);
7216
- const loop2 = await c.loops.create(session2.id, {
7217
- message: args.message,
7218
- intervalSec: parseDuration(args.every),
7219
- name: args.name,
7220
- enabled: !args.off
7221
- });
7222
- console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
7223
- }
7224
- });
7225
-
7226
- // src/commands/loop/disable.ts
7227
- var loopDisableCommand = defineCommand({
7228
- meta: { name: "disable", description: "disable (pause) a loop" },
7229
- args: {
7230
- loop: { type: "positional", required: true, description: "loop int id" },
7231
- ...common
7232
- },
7233
- run: async ({ args }) => {
7234
- const c = clientFor(args);
7235
- const loop2 = await c.loops.update(Number(args.loop), { enabled: false });
7236
- console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
7237
- }
7238
- });
7239
-
7240
- // src/commands/loop/enable.ts
7241
- var loopEnableCommand = defineCommand({
7242
- meta: { name: "enable", description: "enable (resume) a loop" },
7243
- args: {
7244
- loop: { type: "positional", required: true, description: "loop int id" },
7245
- ...common
7246
- },
7247
- run: async ({ args }) => {
7248
- const c = clientFor(args);
7249
- const loop2 = await c.loops.update(Number(args.loop), { enabled: true });
7250
- console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
6446
+ // ../shared/src/domain/service.ts
6447
+ var SERVICE_PUBLIC_URL_MAX_LENGTH = 2048;
6448
+ var SERVICE_NOTE_MAX_LENGTH = 500;
6449
+ var isServicePublicUrl = (value) => {
6450
+ if (value.length === 0 || value.length > SERVICE_PUBLIC_URL_MAX_LENGTH) return false;
6451
+ try {
6452
+ const url = new URL(value);
6453
+ return url.protocol === "http:" || url.protocol === "https:";
6454
+ } catch {
6455
+ return false;
7251
6456
  }
7252
- });
6457
+ };
6458
+ var isServiceNote = (value) => value.length > 0 && value.length <= SERVICE_NOTE_MAX_LENGTH;
7253
6459
 
7254
- // src/commands/loop/get.ts
7255
- var loopGetCommand = defineCommand({
7256
- meta: { name: "get", description: "show one loop" },
7257
- args: {
7258
- loop: { type: "positional", required: true, description: "loop int id" },
7259
- ...common
7260
- },
7261
- run: async ({ args }) => {
7262
- const c = clientFor(args);
7263
- const loop2 = await c.loops.get(Number(args.loop));
7264
- console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
7265
- }
7266
- });
6460
+ // ../shared/src/domain/session.ts
6461
+ var AGENT_KINDS = ["claude-code", "codex", "pi"];
6462
+ var isAgentKind = (v) => typeof v === "string" && AGENT_KINDS.includes(v);
6463
+ var EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"];
6464
+ var isAgentEffort = (v) => typeof v === "string" && EFFORTS.includes(v);
7267
6465
 
7268
- // src/commands/loop/ls.ts
7269
- var loopLsCommand = defineCommand({
7270
- meta: { name: "ls", description: "list a session's loops" },
7271
- args: {
7272
- session: { type: "positional", required: true, description: "session int id or name" },
7273
- project: { type: "string", description: "project id (overrides .baton.json)" },
7274
- ...common
7275
- },
7276
- run: async ({ args }) => {
7277
- const c = clientFor(args);
7278
- const session2 = await resolveSession(c, resolveProjectId(args), args.session);
7279
- const loops = await c.loops.listBySession(session2.id);
7280
- console.log(renderList(loops, fmtLoop, Boolean(args.json)));
7281
- }
7282
- });
6466
+ // ../shared/src/domain/session-event.ts
6467
+ var agentMessageText = (payload) => {
6468
+ if (typeof payload !== "object" || payload === null) return null;
6469
+ const event = payload;
6470
+ if (!["item.started", "item.updated", "item.completed"].includes(String(event.type))) return null;
6471
+ const item = event.item;
6472
+ if (typeof item !== "object" || item === null) return null;
6473
+ const { type, id, text } = item;
6474
+ if (type !== "agent_message" || typeof id !== "string" || typeof text !== "string") return null;
6475
+ const trimmed = text.trim();
6476
+ return trimmed ? { id, text: trimmed } : null;
6477
+ };
7283
6478
 
7284
- // src/commands/loop/rm.ts
7285
- var loopRmCommand = defineCommand({
7286
- meta: { name: "rm", description: "delete a loop" },
7287
- args: {
7288
- loop: { type: "positional", required: true, description: "loop int id" },
7289
- ...common
7290
- },
7291
- run: async ({ args }) => {
7292
- const c = clientFor(args);
7293
- const id = Number(args.loop);
7294
- await c.loops.remove(id);
7295
- console.log(removed("loop", id, Boolean(args.json)));
7296
- }
7297
- });
6479
+ // ../shared/src/domain/worker-creation.ts
6480
+ var WORKER_TEMPLATES = [
6481
+ { id: "mini-glm-codeplan", name: "GLM Code Plan", agentKinds: ["claude-code", "pi"] },
6482
+ { id: "mini-dashscope-codeplan", name: "DashScope Code Plan", agentKinds: ["claude-code", "pi"] },
6483
+ { id: "mini-codex", name: "\u672C\u673A Codex", agentKinds: ["codex"] }
6484
+ ];
6485
+ var workerTemplateAgentKinds = (id) => id === "mini-local" ? AGENT_KINDS : WORKER_TEMPLATES.find((template) => template.id === id)?.agentKinds ?? [];
6486
+ var isWorkerTemplateId = (value) => typeof value === "string" && workerTemplateAgentKinds(value).length > 0;
7298
6487
 
7299
- // src/commands/loop/set.ts
7300
- var loopSetCommand = defineCommand({
7301
- meta: { name: "set", description: "update a loop (message / interval / name)" },
7302
- args: {
7303
- loop: { type: "positional", required: true, description: "loop int id" },
7304
- message: { type: "string", description: "new message" },
7305
- every: { type: "string", description: "new interval: 90s / 30m / 2h / 1d" },
7306
- name: { type: "string", description: "new label" },
7307
- ...common
7308
- },
7309
- run: async ({ args }) => {
7310
- const c = clientFor(args);
7311
- const patch = {};
7312
- if (args.message !== void 0) patch.message = args.message;
7313
- if (args.every !== void 0 && args.every !== "") patch.intervalSec = parseDuration(args.every);
7314
- if (args.name !== void 0) patch.name = args.name;
7315
- const loop2 = await c.loops.update(Number(args.loop), patch);
7316
- console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
7317
- }
6488
+ // src/provisioner/config.ts
6489
+ var forbiddenEnv = /* @__PURE__ */ new Set([
6490
+ "BATON_PROVISIONER_TOKEN",
6491
+ "BATON_TOKEN",
6492
+ "BATON_WORKER_TOKEN",
6493
+ "BATON_USER",
6494
+ "BATON_PASS",
6495
+ "HOME",
6496
+ "XDG_DATA_HOME"
6497
+ ]);
6498
+ var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6499
+ var absolutePath = (value, field) => {
6500
+ if (typeof value !== "string" || !isAbsolute(value) || value.includes("\0"))
6501
+ throw new Error(`${field} must be an absolute path`);
6502
+ return value;
6503
+ };
6504
+ var environment = (value) => {
6505
+ if (!object(value)) throw new Error("workerEnv must be an object");
6506
+ return Object.fromEntries(
6507
+ Object.entries(value).map(([key, entry]) => {
6508
+ if (forbiddenEnv.has(key) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof entry !== "string" || entry.includes("\0"))
6509
+ throw new Error(`invalid worker environment key: ${key}`);
6510
+ return [key, entry];
6511
+ })
6512
+ );
6513
+ };
6514
+ var templates = (value) => {
6515
+ if (!object(value)) throw new Error("templates must be an object");
6516
+ return Object.fromEntries(
6517
+ WORKER_TEMPLATES.filter((template) => value[template.id] !== void 0).map((template) => {
6518
+ const raw = value[template.id];
6519
+ if (!object(raw)) throw new Error(`template ${template.id} must be an object`);
6520
+ const workerEnv2 = environment(raw.workerEnv);
6521
+ if (template.id === "mini-codex") {
6522
+ if (raw.pi !== void 0) throw new Error("mini-codex does not support pi");
6523
+ return [template.id, { workerEnv: workerEnv2 }];
6524
+ }
6525
+ const provider = template.id === "mini-glm-codeplan" ? "zai-coding-cn" : "qwen-token-plan-cn";
6526
+ const apiKey = template.id === "mini-glm-codeplan" ? "ZAI_CODING_CN_API_KEY" : "QWEN_TOKEN_PLAN_CN_API_KEY";
6527
+ if (!workerEnv2.ANTHROPIC_BASE_URL || !(workerEnv2.ANTHROPIC_AUTH_TOKEN || workerEnv2.ANTHROPIC_API_KEY) || !workerEnv2[apiKey])
6528
+ throw new Error(`template ${template.id} requires Claude and Pi credentials`);
6529
+ if (!object(raw.pi) || raw.pi.provider !== provider || typeof raw.pi.model !== "string" || !raw.pi.model.trim())
6530
+ throw new Error(`template ${template.id} requires its Pi provider and model`);
6531
+ return [template.id, { workerEnv: workerEnv2, pi: { provider, model: raw.pi.model.trim() } }];
6532
+ })
6533
+ );
6534
+ };
6535
+ var commandEnvironment = (config) => ({
6536
+ ...process.env,
6537
+ PATH: config.workerEnv.PATH ?? process.env.PATH ?? "/usr/bin:/bin"
7318
6538
  });
7319
-
7320
- // src/commands/loop.ts
7321
- var loop = defineCommand({
7322
- meta: { name: "loop", description: "recurring scheduled messages that wake a session" },
7323
- subCommands: {
7324
- create: loopCreateCommand,
7325
- ls: loopLsCommand,
7326
- get: loopGetCommand,
7327
- set: loopSetCommand,
7328
- enable: loopEnableCommand,
7329
- disable: loopDisableCommand,
7330
- rm: loopRmCommand
6539
+ var loadProvisionerConfig = async (path) => {
6540
+ if (process.platform !== "darwin") throw new Error("provisioner requires macOS");
6541
+ absolutePath(path, "config");
6542
+ if ((await stat2(path)).mode & 63) throw new Error("config must be private: chmod 600 <config>");
6543
+ const text = await readFile(path, "utf8");
6544
+ let raw;
6545
+ try {
6546
+ raw = JSON.parse(text);
6547
+ } catch (error) {
6548
+ if (!(error instanceof SyntaxError)) throw error;
6549
+ throw new Error("invalid provisioner JSON config");
7331
6550
  }
7332
- });
6551
+ if (!object(raw) || typeof raw.server !== "string" || typeof raw.token !== "string")
6552
+ throw new Error("server and token required");
6553
+ let server;
6554
+ try {
6555
+ server = new URL(raw.server);
6556
+ } catch (error) {
6557
+ if (!(error instanceof TypeError)) throw error;
6558
+ throw new Error("server must be a valid HTTP(S) URL");
6559
+ }
6560
+ if (!["http:", "https:"].includes(server.protocol) || server.username || server.password || server.search || server.hash)
6561
+ throw new Error("server must be an HTTP(S) URL without credentials, query, or fragment");
6562
+ if (!raw.token.trim() || /[\r\n]/.test(raw.token))
6563
+ throw new Error("valid provisioner token required");
6564
+ const config = {
6565
+ server: server.toString().replace(/\/$/, ""),
6566
+ token: raw.token.trim(),
6567
+ projectsRoot: absolutePath(raw.projectsRoot, "projectsRoot"),
6568
+ cliPath: absolutePath(raw.cliPath, "cliPath"),
6569
+ nodePath: absolutePath(raw.nodePath ?? process.execPath, "nodePath"),
6570
+ workerEnv: environment(raw.workerEnv),
6571
+ templates: templates(raw.templates)
6572
+ };
6573
+ await access(config.cliPath, constants.R_OK);
6574
+ await access(config.nodePath ?? process.execPath, constants.X_OK);
6575
+ const env = commandEnvironment(config);
6576
+ await promisify(execFile)("git", ["--version"], { env, timeout: 1e4 });
6577
+ await promisify(execFile)("launchctl", ["help"], { env, timeout: 1e4 });
6578
+ await promisify(execFile)("/usr/bin/plutil", ["-help"], { timeout: 1e4 });
6579
+ return config;
6580
+ };
7333
6581
 
7334
- // src/commands/notify.ts
7335
- var describe2 = (result) => {
7336
- if (result.status === "sent") return `notified ${result.devices} device(s)`;
7337
- if (result.status === "throttled")
7338
- return `throttled: this session can notify again in ${result.retryAfterSec}s`;
7339
- return "nobody to notify: no human has messaged this session, or they have not enabled notifications";
6582
+ // ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js
6583
+ var ParseError = class extends Error {
6584
+ constructor(message, options) {
6585
+ super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
6586
+ }
7340
6587
  };
7341
- var notify = defineCommand({
7342
- meta: {
7343
- name: "notify",
7344
- description: "Push a phone notification to the person working with this session. Use it only when a human needs to act now: something is wrong, a decision is needed, or the work is blocked. Do not use it for routine progress, and not to announce that a turn finished (that is already pushed). At most one notification per session every 10 minutes; text is cut at 200 characters."
7345
- },
7346
- args: {
7347
- text: { type: "positional", required: true, description: "one short line for the lock screen" },
7348
- ...common
7349
- },
7350
- run: async ({ args }) => {
7351
- const raw = process.env.BATON_SESSION_ID;
7352
- if (raw === void 0)
7353
- throw new Error("baton notify only works inside a baton session (BATON_SESSION_ID is unset)");
7354
- const sessionId = Number(raw);
7355
- if (!Number.isSafeInteger(sessionId) || sessionId <= 0)
7356
- throw new Error("invalid BATON_SESSION_ID");
7357
- const result = await clientFor(args).sessions.notify(sessionId, args.text);
7358
- console.log(args.json ? toJson(result) : describe2(result));
6588
+ var LF = 10;
6589
+ var CR = 13;
6590
+ var SPACE = 32;
6591
+ function noop(_arg) {
6592
+ }
6593
+ function createParser(config) {
6594
+ if (typeof config == "function")
6595
+ throw new TypeError(
6596
+ "`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
6597
+ );
6598
+ const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
6599
+ let pendingFragmentsLength = 0, isFirstChunk = true, id, data = "", dataLines = 0, eventType, terminated = false;
6600
+ function feed(chunk) {
6601
+ if (terminated)
6602
+ throw new Error(
6603
+ "Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
6604
+ );
6605
+ if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
6606
+ const trailing2 = processLines(chunk);
6607
+ trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
6608
+ return;
6609
+ }
6610
+ if (chunk.indexOf(`
6611
+ `) === -1 && chunk.indexOf("\r") === -1) {
6612
+ pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
6613
+ return;
6614
+ }
6615
+ pendingFragments.push(chunk);
6616
+ const input = pendingFragments.join("");
6617
+ pendingFragments.length = 0, pendingFragmentsLength = 0;
6618
+ const trailing = processLines(input);
6619
+ trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
7359
6620
  }
7360
- });
7361
-
7362
- // src/commands/project.ts
7363
- var createProject = (c, input, json) => c.projects.create(input).then((p) => renderOne(p, fmtProject, json));
7364
- var listProjects = (c, workspaceId, json) => c.projects.listByWorkspace(workspaceId).then((ps) => renderList(ps, fmtProject, json));
7365
- var getProject = (c, id, json) => c.projects.get(id).then((p) => renderOne(p, fmtProject, json));
7366
- var updateProject = (c, id, patch, json) => c.projects.update(id, patch).then((p) => renderOne(p, fmtProject, json));
7367
- var removeProject = (c, id, json) => c.projects.remove(id).then(() => removed("project", id, json));
7368
- var project = defineCommand({
7369
- meta: { name: "project", description: "manage projects" },
7370
- subCommands: {
7371
- create: defineCommand({
7372
- meta: { name: "create", description: "create a project" },
7373
- args: {
7374
- name: { type: "positional", required: true },
7375
- workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
7376
- desc: { type: "string", description: "description" },
7377
- ...common
7378
- },
7379
- run: async ({ args }) => {
7380
- const out = await createProject(
7381
- clientFor(args),
7382
- { workspaceId: resolveWorkspaceId(args), name: args.name, description: args.desc },
7383
- Boolean(args.json)
7384
- );
7385
- console.log(out);
7386
- }
7387
- }),
7388
- ls: defineCommand({
7389
- meta: { name: "ls", description: "list projects in a workspace" },
7390
- args: {
7391
- workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
7392
- ...common
7393
- },
7394
- run: async ({ args }) => {
7395
- console.log(
7396
- await listProjects(clientFor(args), resolveWorkspaceId(args), Boolean(args.json))
7397
- );
6621
+ function checkBufferSize() {
6622
+ maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
6623
+ new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
6624
+ type: "max-buffer-size-exceeded"
6625
+ })
6626
+ )));
6627
+ }
6628
+ function processLines(chunk) {
6629
+ let searchIndex = 0;
6630
+ if (chunk.indexOf("\r") === -1) {
6631
+ let lfIndex = chunk.indexOf(`
6632
+ `, searchIndex);
6633
+ for (; lfIndex !== -1; ) {
6634
+ if (searchIndex === lfIndex) {
6635
+ dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
6636
+ `, searchIndex);
6637
+ continue;
6638
+ }
6639
+ const firstCharCode = chunk.charCodeAt(searchIndex);
6640
+ if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
6641
+ const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);
6642
+ if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
6643
+ onEvent({ id, event: eventType, data: value }), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`
6644
+ `, searchIndex);
6645
+ continue;
6646
+ }
6647
+ data = dataLines === 0 ? value : `${data}
6648
+ ${value}`, dataLines++;
6649
+ } else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(
6650
+ chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,
6651
+ lfIndex
6652
+ ) || void 0 : parseLine(chunk, searchIndex, lfIndex);
6653
+ searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
6654
+ `, searchIndex);
7398
6655
  }
7399
- }),
7400
- get: defineCommand({
7401
- meta: { name: "get", description: "get a project" },
7402
- args: { id: { type: "positional", required: true }, ...common },
7403
- run: async ({ args }) => {
7404
- console.log(await getProject(clientFor(args), Number(args.id), Boolean(args.json)));
6656
+ return chunk.slice(searchIndex);
6657
+ }
6658
+ for (; searchIndex < chunk.length; ) {
6659
+ const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
6660
+ `, searchIndex);
6661
+ let lineEnd = -1;
6662
+ if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1)
6663
+ break;
6664
+ parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;
6665
+ }
6666
+ return chunk.slice(searchIndex);
6667
+ }
6668
+ function parseLine(chunk, start, end) {
6669
+ if (start === end) {
6670
+ dispatchEvent();
6671
+ return;
6672
+ }
6673
+ const firstCharCode = chunk.charCodeAt(start);
6674
+ if (isDataPrefix(chunk, start, firstCharCode)) {
6675
+ const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);
6676
+ data = dataLines === 0 ? value2 : `${data}
6677
+ ${value2}`, dataLines++;
6678
+ return;
6679
+ }
6680
+ if (isEventPrefix(chunk, start, firstCharCode)) {
6681
+ eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
6682
+ return;
6683
+ }
6684
+ if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
6685
+ const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
6686
+ id = value2.includes("\0") ? void 0 : value2;
6687
+ return;
6688
+ }
6689
+ if (firstCharCode === 58) {
6690
+ if (onComment) {
6691
+ const line2 = chunk.slice(start, end);
6692
+ onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
7405
6693
  }
7406
- }),
7407
- update: defineCommand({
7408
- meta: { name: "update", description: "rename a project / set its description" },
7409
- args: {
7410
- id: { type: "positional", required: true },
7411
- name: { type: "string", description: "new name" },
7412
- desc: { type: "string", description: "new description" },
7413
- ...common
7414
- },
7415
- run: async ({ args }) => {
7416
- const patch = {
7417
- ...args.name !== void 0 ? { name: args.name } : {},
7418
- ...args.desc !== void 0 ? { description: args.desc } : {}
7419
- };
7420
- if (Object.keys(patch).length === 0) throw new Error("pass --name and/or --desc");
7421
- console.log(
7422
- await updateProject(clientFor(args), Number(args.id), patch, Boolean(args.json))
6694
+ return;
6695
+ }
6696
+ const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":");
6697
+ if (fieldSeparatorIndex === -1) {
6698
+ processField(line, "", line);
6699
+ return;
6700
+ }
6701
+ const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
6702
+ processField(field, value, line);
6703
+ }
6704
+ function processField(field, value, line) {
6705
+ switch (field) {
6706
+ case "event":
6707
+ eventType = value || void 0;
6708
+ break;
6709
+ case "data":
6710
+ data = dataLines === 0 ? value : `${data}
6711
+ ${value}`, dataLines++;
6712
+ break;
6713
+ case "id":
6714
+ id = value.includes("\0") ? void 0 : value;
6715
+ break;
6716
+ case "retry":
6717
+ /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
6718
+ new ParseError(`Invalid \`retry\` value: "${value}"`, {
6719
+ type: "invalid-retry",
6720
+ value,
6721
+ line
6722
+ })
6723
+ );
6724
+ break;
6725
+ default:
6726
+ onError(
6727
+ new ParseError(
6728
+ `Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
6729
+ { type: "unknown-field", field, value, line }
6730
+ )
7423
6731
  );
7424
- }
7425
- }),
7426
- rm: defineCommand({
7427
- meta: { name: "rm", description: "delete a project" },
7428
- args: { id: { type: "positional", required: true }, ...common },
7429
- run: async ({ args }) => {
7430
- console.log(await removeProject(clientFor(args), Number(args.id), Boolean(args.json)));
7431
- }
7432
- })
6732
+ break;
6733
+ }
7433
6734
  }
7434
- });
6735
+ function dispatchEvent() {
6736
+ dataLines > 0 && onEvent({
6737
+ id,
6738
+ event: eventType,
6739
+ data
6740
+ }), id = void 0, data = "", dataLines = 0, eventType = void 0;
6741
+ }
6742
+ function reset(options = {}) {
6743
+ if (options.consume && pendingFragments.length > 0) {
6744
+ const incompleteLine = pendingFragments.join("");
6745
+ parseLine(incompleteLine, 0, incompleteLine.length);
6746
+ }
6747
+ isFirstChunk = true, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = false;
6748
+ }
6749
+ return { feed, reset };
6750
+ }
6751
+ function isDataPrefix(chunk, i, firstCharCode) {
6752
+ return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
6753
+ }
6754
+ function isEventPrefix(chunk, i, firstCharCode) {
6755
+ return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
6756
+ }
7435
6757
 
7436
- // src/provisioner/config.ts
7437
- import { execFile } from "node:child_process";
7438
- import { constants } from "node:fs";
7439
- import { access, readFile, stat as stat2 } from "node:fs/promises";
7440
- import { isAbsolute } from "node:path";
7441
- import { promisify } from "node:util";
7442
- var forbiddenEnv = /* @__PURE__ */ new Set([
7443
- "BATON_PROVISIONER_TOKEN",
7444
- "BATON_TOKEN",
7445
- "BATON_WORKER_TOKEN",
7446
- "BATON_USER",
7447
- "BATON_PASS",
7448
- "HOME",
7449
- "XDG_DATA_HOME"
7450
- ]);
7451
- var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7452
- var absolutePath = (value, field) => {
7453
- if (typeof value !== "string" || !isAbsolute(value) || value.includes("\0"))
7454
- throw new Error(`${field} must be an absolute path`);
7455
- return value;
6758
+ // ../../node_modules/.pnpm/eventsource@4.1.0/node_modules/eventsource/dist/index.js
6759
+ var ErrorEvent = class extends Event {
6760
+ /**
6761
+ * Constructs a new `ErrorEvent` instance. This is typically not called directly,
6762
+ * but rather emitted by the `EventSource` object when an error occurs.
6763
+ *
6764
+ * @param type - The type of the event (should be "error")
6765
+ * @param errorEventInitDict - Optional properties to include in the error event
6766
+ */
6767
+ constructor(type, errorEventInitDict) {
6768
+ var _a, _b;
6769
+ super(type), this.code = (_a = errorEventInitDict == null ? void 0 : errorEventInitDict.code) != null ? _a : void 0, this.message = (_b = errorEventInitDict == null ? void 0 : errorEventInitDict.message) != null ? _b : void 0;
6770
+ }
6771
+ /**
6772
+ * Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance,
6773
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
6774
+ * we explicitly include the properties in the `inspect` method.
6775
+ *
6776
+ * This is automatically called by Node.js when you `console.log` an instance of this class.
6777
+ *
6778
+ * @param _depth - The current depth
6779
+ * @param options - The options passed to `util.inspect`
6780
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
6781
+ * @returns A string representation of the error
6782
+ */
6783
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")](_depth, options, inspect) {
6784
+ return inspect(inspectableError(this), options);
6785
+ }
6786
+ /**
6787
+ * Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance,
6788
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
6789
+ * we explicitly include the properties in the `inspect` method.
6790
+ *
6791
+ * This is automatically called by Deno when you `console.log` an instance of this class.
6792
+ *
6793
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
6794
+ * @param options - The options passed to `Deno.inspect`
6795
+ * @returns A string representation of the error
6796
+ */
6797
+ [/* @__PURE__ */ Symbol.for("Deno.customInspect")](inspect, options) {
6798
+ return inspect(inspectableError(this), options);
6799
+ }
7456
6800
  };
7457
- var environment = (value) => {
7458
- if (!object(value)) throw new Error("workerEnv must be an object");
7459
- return Object.fromEntries(
7460
- Object.entries(value).map(([key, entry]) => {
7461
- if (forbiddenEnv.has(key) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof entry !== "string" || entry.includes("\0"))
7462
- throw new Error(`invalid worker environment key: ${key}`);
7463
- return [key, entry];
7464
- })
7465
- );
6801
+ function syntaxError(message) {
6802
+ const DomException = globalThis.DOMException;
6803
+ return typeof DomException == "function" ? new DomException(message, "SyntaxError") : new SyntaxError(message);
6804
+ }
6805
+ function flattenError(err) {
6806
+ return err instanceof Error ? "errors" in err && Array.isArray(err.errors) ? err.errors.map(flattenError).join(", ") : "cause" in err && err.cause instanceof Error ? `${err}: ${flattenError(err.cause)}` : err.message : `${err}`;
6807
+ }
6808
+ function inspectableError(err) {
6809
+ return {
6810
+ type: err.type,
6811
+ message: err.message,
6812
+ code: err.code,
6813
+ defaultPrevented: err.defaultPrevented,
6814
+ cancelable: err.cancelable,
6815
+ timeStamp: err.timeStamp
6816
+ };
6817
+ }
6818
+ var __typeError = (msg) => {
6819
+ throw TypeError(msg);
7466
6820
  };
7467
- var templates = (value) => {
7468
- if (!object(value)) throw new Error("templates must be an object");
7469
- return Object.fromEntries(
7470
- WORKER_TEMPLATES.map((template) => {
7471
- const raw = value[template.id];
7472
- if (!object(raw)) throw new Error(`template ${template.id} must be configured`);
7473
- const workerEnv2 = environment(raw.workerEnv);
7474
- if (template.id === "mini-codex") {
7475
- if (raw.pi !== void 0) throw new Error("mini-codex does not support pi");
7476
- return [template.id, { workerEnv: workerEnv2 }];
6821
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
6822
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
6823
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
6824
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
6825
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
6826
+ var _readyState;
6827
+ var _url;
6828
+ var _redirectUrl;
6829
+ var _withCredentials;
6830
+ var _fetch;
6831
+ var _reconnectInterval;
6832
+ var _reconnectTimer;
6833
+ var _lastEventId;
6834
+ var _controller;
6835
+ var _parser;
6836
+ var _onError;
6837
+ var _onMessage;
6838
+ var _onOpen;
6839
+ var _EventSource_instances;
6840
+ var connect_fn;
6841
+ var _onFetchResponse;
6842
+ var _onFetchError;
6843
+ var getRequestOptions_fn;
6844
+ var _onEvent;
6845
+ var _onRetryChange;
6846
+ var failConnection_fn;
6847
+ var scheduleReconnect_fn;
6848
+ var _reconnect;
6849
+ var EventSource = class extends EventTarget {
6850
+ constructor(url, eventSourceInitDict) {
6851
+ var _a, _b;
6852
+ super(), __privateAdd(this, _EventSource_instances), this.CONNECTING = 0, this.OPEN = 1, this.CLOSED = 2, __privateAdd(this, _readyState), __privateAdd(this, _url), __privateAdd(this, _redirectUrl), __privateAdd(this, _withCredentials), __privateAdd(this, _fetch), __privateAdd(this, _reconnectInterval), __privateAdd(this, _reconnectTimer), __privateAdd(this, _lastEventId, null), __privateAdd(this, _controller), __privateAdd(this, _parser), __privateAdd(this, _onError, null), __privateAdd(this, _onMessage, null), __privateAdd(this, _onOpen, null), __privateAdd(this, _onFetchResponse, async (response) => {
6853
+ var _a2;
6854
+ __privateGet(this, _parser).reset();
6855
+ const { body, redirected, status, headers } = response;
6856
+ if (status === 204) {
6857
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Server sent HTTP 204, not reconnecting", 204), this.close();
6858
+ return;
7477
6859
  }
7478
- const provider = template.id === "mini-glm-codeplan" ? "zai-coding-cn" : "qwen-token-plan-cn";
7479
- const apiKey = template.id === "mini-glm-codeplan" ? "ZAI_CODING_CN_API_KEY" : "QWEN_TOKEN_PLAN_CN_API_KEY";
7480
- if (!workerEnv2.ANTHROPIC_BASE_URL || !(workerEnv2.ANTHROPIC_AUTH_TOKEN || workerEnv2.ANTHROPIC_API_KEY) || !workerEnv2[apiKey])
7481
- throw new Error(`template ${template.id} requires Claude and Pi credentials`);
7482
- if (!object(raw.pi) || raw.pi.provider !== provider || typeof raw.pi.model !== "string" || !raw.pi.model.trim())
7483
- throw new Error(`template ${template.id} requires its Pi provider and model`);
7484
- return [template.id, { workerEnv: workerEnv2, pi: { provider, model: raw.pi.model.trim() } }];
7485
- })
7486
- );
7487
- };
7488
- var commandEnvironment = (config) => ({
7489
- ...process.env,
7490
- PATH: config.workerEnv.PATH ?? process.env.PATH ?? "/usr/bin:/bin"
7491
- });
7492
- var loadProvisionerConfig = async (path) => {
7493
- if (process.platform !== "darwin") throw new Error("provisioner requires macOS");
7494
- absolutePath(path, "config");
7495
- if ((await stat2(path)).mode & 63) throw new Error("config must be private: chmod 600 <config>");
7496
- const text = await readFile(path, "utf8");
7497
- let raw;
7498
- try {
7499
- raw = JSON.parse(text);
7500
- } catch (error) {
7501
- if (!(error instanceof SyntaxError)) throw error;
7502
- throw new Error("invalid provisioner JSON config");
6860
+ if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) {
6861
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status);
6862
+ return;
6863
+ }
6864
+ if (!(headers.get("content-type") || "").startsWith("text/event-stream")) {
6865
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, 'Invalid content type, expected "text/event-stream"', status);
6866
+ return;
6867
+ }
6868
+ if (__privateGet(this, _readyState) === this.CLOSED)
6869
+ return;
6870
+ __privateSet(this, _readyState, this.OPEN);
6871
+ const openEvent = new Event("open");
6872
+ if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != "object" || !body || !("getReader" in body)) {
6873
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid response body, expected a web ReadableStream", status), this.close();
6874
+ return;
6875
+ }
6876
+ const decoder = new TextDecoder(), reader = body.getReader();
6877
+ let open3 = true;
6878
+ do {
6879
+ const { done, value } = await reader.read();
6880
+ value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open3 = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
6881
+ } while (open3);
6882
+ }), __privateAdd(this, _onFetchError, (err) => {
6883
+ __privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
6884
+ }), __privateAdd(this, _onEvent, (event) => {
6885
+ typeof event.id == "string" && __privateSet(this, _lastEventId, event.id);
6886
+ const messageEvent = new MessageEvent(event.event || "message", {
6887
+ data: event.data,
6888
+ origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin,
6889
+ lastEventId: event.id || ""
6890
+ });
6891
+ __privateGet(this, _onMessage) && (!event.event || event.event === "message") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent);
6892
+ }), __privateAdd(this, _onRetryChange, (value) => {
6893
+ __privateSet(this, _reconnectInterval, value);
6894
+ }), __privateAdd(this, _reconnect, () => {
6895
+ __privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this);
6896
+ });
6897
+ try {
6898
+ if (url instanceof URL)
6899
+ __privateSet(this, _url, url);
6900
+ else if (typeof url == "string")
6901
+ __privateSet(this, _url, new URL(url, getBaseURL()));
6902
+ else
6903
+ throw new Error("Invalid URL");
6904
+ } catch {
6905
+ throw syntaxError("An invalid or illegal string was specified");
6906
+ }
6907
+ __privateSet(this, _parser, createParser({
6908
+ onEvent: __privateGet(this, _onEvent),
6909
+ onRetry: __privateGet(this, _onRetryChange)
6910
+ })), __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _reconnectInterval, 3e3), __privateSet(this, _fetch, (_a = eventSourceInitDict == null ? void 0 : eventSourceInitDict.fetch) != null ? _a : globalThis.fetch), __privateSet(this, _withCredentials, (_b = eventSourceInitDict == null ? void 0 : eventSourceInitDict.withCredentials) != null ? _b : false), __privateMethod(this, _EventSource_instances, connect_fn).call(this);
6911
+ }
6912
+ /**
6913
+ * Returns the state of this EventSource object's connection. It can have the values described below.
6914
+ *
6915
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)
6916
+ *
6917
+ * Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface,
6918
+ * defined in the TypeScript `dom` library.
6919
+ *
6920
+ * @public
6921
+ */
6922
+ get readyState() {
6923
+ return __privateGet(this, _readyState);
7503
6924
  }
7504
- if (!object(raw) || typeof raw.server !== "string" || typeof raw.token !== "string")
7505
- throw new Error("server and token required");
7506
- let server;
7507
- try {
7508
- server = new URL(raw.server);
7509
- } catch (error) {
7510
- if (!(error instanceof TypeError)) throw error;
7511
- throw new Error("server must be a valid HTTP(S) URL");
6925
+ /**
6926
+ * Returns the URL providing the event stream.
6927
+ *
6928
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)
6929
+ *
6930
+ * @public
6931
+ */
6932
+ get url() {
6933
+ return __privateGet(this, _url).href;
6934
+ }
6935
+ /**
6936
+ * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise.
6937
+ *
6938
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)
6939
+ */
6940
+ get withCredentials() {
6941
+ return __privateGet(this, _withCredentials);
6942
+ }
6943
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */
6944
+ get onerror() {
6945
+ return __privateGet(this, _onError);
6946
+ }
6947
+ set onerror(value) {
6948
+ __privateSet(this, _onError, value);
6949
+ }
6950
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */
6951
+ get onmessage() {
6952
+ return __privateGet(this, _onMessage);
6953
+ }
6954
+ set onmessage(value) {
6955
+ __privateSet(this, _onMessage, value);
6956
+ }
6957
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
6958
+ get onopen() {
6959
+ return __privateGet(this, _onOpen);
6960
+ }
6961
+ set onopen(value) {
6962
+ __privateSet(this, _onOpen, value);
6963
+ }
6964
+ addEventListener(type, listener, options) {
6965
+ const listen = listener;
6966
+ super.addEventListener(type, listen, options);
6967
+ }
6968
+ removeEventListener(type, listener, options) {
6969
+ const listen = listener;
6970
+ super.removeEventListener(type, listen, options);
6971
+ }
6972
+ /**
6973
+ * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
6974
+ *
6975
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
6976
+ *
6977
+ * @public
6978
+ */
6979
+ close() {
6980
+ __privateGet(this, _reconnectTimer) && clearTimeout(__privateGet(this, _reconnectTimer)), __privateGet(this, _readyState) !== this.CLOSED && (__privateGet(this, _controller) && __privateGet(this, _controller).abort(), __privateSet(this, _readyState, this.CLOSED), __privateSet(this, _controller, void 0));
7512
6981
  }
7513
- if (!["http:", "https:"].includes(server.protocol) || server.username || server.password || server.search || server.hash)
7514
- throw new Error("server must be an HTTP(S) URL without credentials, query, or fragment");
7515
- if (!raw.token.trim() || /[\r\n]/.test(raw.token))
7516
- throw new Error("valid provisioner token required");
7517
- const config = {
7518
- server: server.toString().replace(/\/$/, ""),
7519
- token: raw.token.trim(),
7520
- projectsRoot: absolutePath(raw.projectsRoot, "projectsRoot"),
7521
- cliPath: absolutePath(raw.cliPath, "cliPath"),
7522
- nodePath: absolutePath(raw.nodePath ?? process.execPath, "nodePath"),
7523
- workerEnv: environment(raw.workerEnv),
7524
- templates: templates(raw.templates)
7525
- };
7526
- await access(config.cliPath, constants.R_OK);
7527
- await access(config.nodePath ?? process.execPath, constants.X_OK);
7528
- const env = commandEnvironment(config);
7529
- await promisify(execFile)("git", ["--version"], { env, timeout: 1e4 });
7530
- await promisify(execFile)("launchctl", ["help"], { env, timeout: 1e4 });
7531
- await promisify(execFile)("/usr/bin/plutil", ["-help"], { timeout: 1e4 });
7532
- return config;
7533
6982
  };
6983
+ _readyState = /* @__PURE__ */ new WeakMap(), _url = /* @__PURE__ */ new WeakMap(), _redirectUrl = /* @__PURE__ */ new WeakMap(), _withCredentials = /* @__PURE__ */ new WeakMap(), _fetch = /* @__PURE__ */ new WeakMap(), _reconnectInterval = /* @__PURE__ */ new WeakMap(), _reconnectTimer = /* @__PURE__ */ new WeakMap(), _lastEventId = /* @__PURE__ */ new WeakMap(), _controller = /* @__PURE__ */ new WeakMap(), _parser = /* @__PURE__ */ new WeakMap(), _onError = /* @__PURE__ */ new WeakMap(), _onMessage = /* @__PURE__ */ new WeakMap(), _onOpen = /* @__PURE__ */ new WeakMap(), _EventSource_instances = /* @__PURE__ */ new WeakSet(), /**
6984
+ * Connect to the given URL and start receiving events
6985
+ *
6986
+ * @internal
6987
+ */
6988
+ connect_fn = function() {
6989
+ __privateSet(this, _readyState, this.CONNECTING), __privateSet(this, _controller, new AbortController()), __privateGet(this, _fetch)(__privateGet(this, _url), __privateMethod(this, _EventSource_instances, getRequestOptions_fn).call(this)).then(__privateGet(this, _onFetchResponse)).catch(__privateGet(this, _onFetchError));
6990
+ }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
6991
+ * Get request options for the `fetch()` request
6992
+ *
6993
+ * @returns The request options
6994
+ * @internal
6995
+ */
6996
+ getRequestOptions_fn = function() {
6997
+ var _a;
6998
+ const init2 = {
6999
+ // [spec] Let `corsAttributeState` be `Anonymous`…
7000
+ // [spec] …will have their mode set to "cors"…
7001
+ mode: "cors",
7002
+ redirect: "follow",
7003
+ headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
7004
+ cache: "no-store",
7005
+ signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
7006
+ };
7007
+ return "window" in globalThis && (init2.credentials = this.withCredentials ? "include" : "same-origin"), init2;
7008
+ }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
7009
+ * Handles the process referred to in the EventSource specification as "failing a connection".
7010
+ *
7011
+ * @param error - The error causing the connection to fail
7012
+ * @param code - The HTTP status code, if available
7013
+ * @internal
7014
+ */
7015
+ failConnection_fn = function(message, code) {
7016
+ var _a;
7017
+ __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
7018
+ const errorEvent = new ErrorEvent("error", { code, message });
7019
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
7020
+ }, /**
7021
+ * Schedules a reconnection attempt against the EventSource endpoint.
7022
+ *
7023
+ * @param message - The error causing the connection to fail
7024
+ * @param code - The HTTP status code, if available
7025
+ * @internal
7026
+ */
7027
+ scheduleReconnect_fn = function(message, code) {
7028
+ var _a;
7029
+ if (__privateGet(this, _readyState) === this.CLOSED)
7030
+ return;
7031
+ __privateSet(this, _readyState, this.CONNECTING);
7032
+ const errorEvent = new ErrorEvent("error", { code, message });
7033
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
7034
+ }, _reconnect = /* @__PURE__ */ new WeakMap(), /**
7035
+ * ReadyState representing an EventSource currently trying to connect
7036
+ *
7037
+ * @public
7038
+ */
7039
+ EventSource.CONNECTING = 0, /**
7040
+ * ReadyState representing an EventSource connection that is open (eg connected)
7041
+ *
7042
+ * @public
7043
+ */
7044
+ EventSource.OPEN = 1, /**
7045
+ * ReadyState representing an EventSource connection that is closed (eg disconnected)
7046
+ *
7047
+ * @public
7048
+ */
7049
+ EventSource.CLOSED = 2;
7050
+ Object.defineProperty(EventSource, /* @__PURE__ */ Symbol.for("eventsource.supports-fetch-override"), {
7051
+ value: true,
7052
+ writable: false,
7053
+ configurable: false,
7054
+ enumerable: false
7055
+ });
7056
+ function getBaseURL() {
7057
+ const doc = "document" in globalThis ? globalThis.document : void 0;
7058
+ return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
7059
+ }
7534
7060
 
7535
7061
  // src/provisioner/client.ts
7536
7062
  var createProvisionerClient = (config) => {
@@ -7563,7 +7089,7 @@ var createProvisionerClient = (config) => {
7563
7089
  import { execFile as execFile3 } from "node:child_process";
7564
7090
  import { lstat, mkdir as mkdir3, realpath, writeFile as writeFile2 } from "node:fs/promises";
7565
7091
  import { hostname } from "node:os";
7566
- import { dirname as dirname2, join as join5 } from "node:path";
7092
+ import { dirname as dirname2, join as join4 } from "node:path";
7567
7093
  import { setTimeout as setTimeout2 } from "node:timers/promises";
7568
7094
  import { promisify as promisify3 } from "node:util";
7569
7095
 
@@ -7571,18 +7097,18 @@ import { promisify as promisify3 } from "node:util";
7571
7097
  import { execFile as execFile2 } from "node:child_process";
7572
7098
  import { mkdir, open } from "node:fs/promises";
7573
7099
  import { homedir } from "node:os";
7574
- import { dirname, join as join3 } from "node:path";
7100
+ import { dirname, join as join2 } from "node:path";
7575
7101
  import { promisify as promisify2 } from "node:util";
7576
- var workerStatePath = (config, creation) => join3(
7102
+ var workerStatePath = (config, creation) => join2(
7577
7103
  config.projectsRoot,
7578
7104
  ".worker-state",
7579
7105
  `project-${creation.projectId}`,
7580
7106
  `creation-${creation.id}`
7581
7107
  );
7582
- var workerLogPath = (creation) => join3(homedir(), "Library/Logs", `baton-worker-creation-${creation.id}.log`);
7108
+ var workerLogPath = (creation) => join2(homedir(), "Library/Logs", `baton-worker-creation-${creation.id}.log`);
7583
7109
  var startAgent = async (config, agent, signal) => {
7584
7110
  signal?.throwIfAborted();
7585
- const plist = join3(homedir(), "Library/LaunchAgents", `${agent.label}.plist`);
7111
+ const plist = join2(homedir(), "Library/LaunchAgents", `${agent.label}.plist`);
7586
7112
  await mkdir(dirname(plist), { recursive: true });
7587
7113
  await mkdir(dirname(agent.log), { recursive: true });
7588
7114
  const domain = `gui/${process.getuid?.()}`;
@@ -7668,24 +7194,29 @@ var installProvisionerAgent = async (config, configPath) => startAgent(config, {
7668
7194
  ],
7669
7195
  cwd: dirname(configPath),
7670
7196
  env: { HOME: homedir(), PATH: config.workerEnv.PATH ?? process.env.PATH ?? "/usr/bin:/bin" },
7671
- log: join3(homedir(), "Library/Logs/baton-provisioner.log")
7197
+ log: join2(homedir(), "Library/Logs/baton-provisioner.log")
7672
7198
  });
7673
7199
 
7674
7200
  // src/provisioner/worker-template.ts
7675
7201
  import { mkdir as mkdir2, writeFile } from "node:fs/promises";
7676
- import { join as join4 } from "node:path";
7677
- var workerTemplateEnvironment = async (config, creation, state) => {
7202
+ import { join as join3 } from "node:path";
7203
+ var requireWorkerTemplate = (config, creation) => {
7678
7204
  if (!workerTemplateAgentKinds(creation.templateId).includes(creation.agentKind))
7679
7205
  throw new Error("agentKind is not supported by this template");
7680
- if (creation.templateId === "mini-local") return config.workerEnv;
7206
+ if (creation.templateId === "mini-local") return null;
7681
7207
  const profile = config.templates[creation.templateId];
7682
- if (!profile) throw new Error(`template ${creation.templateId} is not configured`);
7208
+ if (!profile) throw new Error(`template ${creation.templateId} is not configured on this host`);
7209
+ return profile;
7210
+ };
7211
+ var workerTemplateEnvironment = async (config, creation, state) => {
7212
+ const profile = requireWorkerTemplate(config, creation);
7213
+ if (!profile) return config.workerEnv;
7683
7214
  if (creation.agentKind !== "pi") return profile.workerEnv;
7684
7215
  if (!profile.pi) throw new Error("Pi provider and model are not configured");
7685
- const agentDir = join4(state, "pi");
7216
+ const agentDir = join3(state, "pi");
7686
7217
  await mkdir2(agentDir, { mode: 448 });
7687
7218
  await writeFile(
7688
- join4(agentDir, "settings.json"),
7219
+ join3(agentDir, "settings.json"),
7689
7220
  JSON.stringify({
7690
7221
  defaultProvider: profile.pi.provider,
7691
7222
  defaultModel: profile.pi.model
@@ -7723,13 +7254,13 @@ var initRepository = async (config, creation, repoPath, progress, signal) => {
7723
7254
  signal.throwIfAborted();
7724
7255
  await mkdir3(config.projectsRoot, { recursive: true, mode: 448 });
7725
7256
  const root = await realpath(config.projectsRoot);
7726
- await directory(join5(root, `project-${creation.projectId}`));
7727
- await mkdir3(join5(root, `project-${creation.projectId}`, `creation-${creation.id}`), {
7257
+ await directory(join4(root, `project-${creation.projectId}`));
7258
+ await mkdir3(join4(root, `project-${creation.projectId}`, `creation-${creation.id}`), {
7728
7259
  mode: 448
7729
7260
  });
7730
7261
  await progress("git");
7731
7262
  await gitIn(config, repoPath, ["init", "-b", "main"], signal);
7732
- await writeFile2(join5(repoPath, ".git/info/exclude"), "\n.baton.json\n", { flag: "a" });
7263
+ await writeFile2(join4(repoPath, ".git/info/exclude"), "\n.baton.json\n", { flag: "a" });
7733
7264
  await gitIn(
7734
7265
  config,
7735
7266
  repoPath,
@@ -7750,7 +7281,7 @@ var adoptRepository = async (config, creation, repoPath, progress, signal) => {
7750
7281
  await progress("directory");
7751
7282
  signal.throwIfAborted();
7752
7283
  const real = await realpath(repoPath);
7753
- const project2 = join5(await realpath(config.projectsRoot), `project-${creation.projectId}`);
7284
+ const project2 = join4(await realpath(config.projectsRoot), `project-${creation.projectId}`);
7754
7285
  if (dirname2(real) !== project2 || !(await lstat(real)).isDirectory())
7755
7286
  throw new Error(`unsafe directory: ${repoPath}`);
7756
7287
  await progress("git");
@@ -7765,19 +7296,20 @@ var adoptRepository = async (config, creation, repoPath, progress, signal) => {
7765
7296
  throw new Error(`not the root of a Git work tree: ${repoPath}`);
7766
7297
  };
7767
7298
  var provisionWorker = async (config, creation, client, signal) => {
7299
+ requireWorkerTemplate(config, creation);
7768
7300
  const shared = creation.repoPath !== null;
7769
- const repoPath = creation.repoPath ?? join5(config.projectsRoot, `project-${creation.projectId}`, `creation-${creation.id}`);
7301
+ const repoPath = creation.repoPath ?? join4(config.projectsRoot, `project-${creation.projectId}`, `creation-${creation.id}`);
7770
7302
  const progress = (step) => client.report(creation.id, { status: "running", step, repoPath }, signal);
7771
7303
  await (shared ? adoptRepository : initRepository)(config, creation, repoPath, progress, signal);
7772
7304
  await progress("register");
7773
7305
  const result = await client.register(creation.id, hostname(), signal);
7774
7306
  const state = workerStatePath(config, creation);
7775
7307
  const root = await realpath(config.projectsRoot);
7776
- await directory(join5(root, ".worker-state"));
7777
- await directory(join5(root, ".worker-state", `project-${creation.projectId}`));
7308
+ await directory(join4(root, ".worker-state"));
7309
+ await directory(join4(root, ".worker-state", `project-${creation.projectId}`));
7778
7310
  await mkdir3(state, { mode: 448 });
7779
- await mkdir3(join5(state, "baton"), { mode: 448 });
7780
- await writeFile2(join5(state, "baton/machine-id"), `${creation.machineId}
7311
+ await mkdir3(join4(state, "baton"), { mode: 448 });
7312
+ await writeFile2(join4(state, "baton/machine-id"), `${creation.machineId}
7781
7313
  `, {
7782
7314
  flag: "wx",
7783
7315
  mode: 384
@@ -7791,7 +7323,7 @@ var provisionWorker = async (config, creation, client, signal) => {
7791
7323
  machineId: result.worker.machineId,
7792
7324
  apiToken: result.apiToken
7793
7325
  });
7794
- const configPath = shared ? join5(state, "worker.json") : join5(repoPath, ".baton.json");
7326
+ const configPath = shared ? join4(state, "worker.json") : join4(repoPath, ".baton.json");
7795
7327
  await writeFile2(configPath, `${JSON.stringify(cfg, null, 2)}
7796
7328
  `, { flag: "wx", mode: 384 });
7797
7329
  await progress("start");
@@ -7916,346 +7448,57 @@ var runProvisioner = async (config, signal) => {
7916
7448
  console.error(`[provisioner] stream disconnected (${event.code ?? "network"}); reconnecting`);
7917
7449
  };
7918
7450
  signal.addEventListener("abort", stop, { once: true });
7919
- if (signal.aborted) stop();
7920
- console.log("[provisioner] waiting for worker creation requests");
7921
- try {
7922
- await ended;
7923
- await tail;
7924
- } finally {
7925
- stop();
7926
- signal.removeEventListener("abort", stop);
7927
- }
7928
- if (fatal !== void 0) throw new Error(taskError(fatal, config));
7929
- };
7930
-
7931
- // src/commands/provisioner.ts
7932
- var configArg = {
7933
- config: {
7934
- type: "string",
7935
- required: true,
7936
- description: "absolute path to private provisioner JSON config"
7937
- }
7938
- };
7939
- var provisioner = defineCommand({
7940
- meta: {
7941
- name: "provisioner",
7942
- description: "prepare local repositories and launch workers on macOS"
7943
- },
7944
- subCommands: {
7945
- run: defineCommand({
7946
- args: configArg,
7947
- run: async ({ args }) => {
7948
- const config = await loadProvisionerConfig(args.config);
7949
- const abort = new AbortController();
7950
- const stop = () => abort.abort();
7951
- process.on("SIGINT", stop);
7952
- process.on("SIGTERM", stop);
7953
- try {
7954
- await runProvisioner(config, abort.signal);
7955
- } finally {
7956
- process.off("SIGINT", stop);
7957
- process.off("SIGTERM", stop);
7958
- }
7959
- }
7960
- }),
7961
- install: defineCommand({
7962
- args: configArg,
7963
- run: async ({ args }) => {
7964
- const config = await loadProvisionerConfig(args.config);
7965
- await installProvisionerAgent(config, args.config);
7966
- console.log(
7967
- "installed dev.fmap.baton-provisioner; inspect ~/Library/Logs/baton-provisioner.log"
7968
- );
7969
- }
7970
- })
7971
- }
7972
- });
7973
-
7974
- // src/commands/relay.ts
7975
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
7976
- import { tmpdir as tmpdir2 } from "node:os";
7977
- import { join as join6 } from "node:path";
7978
-
7979
- // src/client/relay.ts
7980
- var relayClient = (baseUrl) => {
7981
- const u = (p) => `${baseUrl}${p}`;
7982
- const authed = (token) => ({ authorization: `Bearer ${token}` });
7983
- return {
7984
- create: async () => {
7985
- const res = await fetch(u("/relay/channels"), { method: "POST" });
7986
- if (!res.ok) throw new Error(`POST /relay/channels \u2192 ${res.status}: ${await res.text()}`);
7987
- return await res.json();
7988
- },
7989
- send: async (channelId, token, msg) => {
7990
- const res = await fetch(u(`/relay/channels/${channelId}/messages`), {
7991
- method: "POST",
7992
- headers: { "content-type": "application/json", ...authed(token) },
7993
- body: JSON.stringify(msg)
7994
- });
7995
- if (!res.ok) throw new Error(`POST relay message \u2192 ${res.status}: ${await res.text()}`);
7996
- return await res.json();
7997
- },
7998
- listen: (channelId, token, opts) => {
7999
- let cursor = opts.since ?? 0;
8000
- const es = new EventSource(u(`/relay/channels/${channelId}/stream?since=${cursor}`), {
8001
- fetch: (url, init2) => fetch(url, {
8002
- ...init2,
8003
- headers: { ...init2?.headers, ...authed(token) }
8004
- })
8005
- });
8006
- es.onmessage = (e) => {
8007
- try {
8008
- const m = JSON.parse(e.data);
8009
- if (m.seq <= cursor) return;
8010
- cursor = m.seq;
8011
- opts.onMessage(m);
8012
- } catch (err) {
8013
- opts.onError?.(err);
8014
- }
8015
- };
8016
- es.onerror = (err) => opts.onError?.(err);
8017
- return () => es.close();
8018
- }
8019
- };
8020
- };
8021
-
8022
- // src/commands/relay.ts
8023
- var PREVIEW2 = 280;
8024
- var readStdin2 = async () => {
8025
- const chunks = [];
8026
- for await (const c of process.stdin) chunks.push(c);
8027
- return Buffer.concat(chunks).toString("utf8");
8028
- };
8029
- var inviteBlock2 = (url, channelId, token) => [
8030
- "\u2500\u2500 Copy everything below to your peer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
8031
- "You're invited to a live Claude\u2194Claude channel. Use the `hotline` skill in",
8032
- "join mode, or run these two commands directly (pick your own NAME):",
8033
- "",
8034
- ` baton relay listen ${channelId} --token ${token} --from NAME --url ${url}`,
8035
- ` baton relay send ${channelId} --token ${token} --from NAME --text "..." --url ${url}`,
8036
- "",
8037
- `connection: url=${url} channel=${channelId} token=${token}`,
8038
- "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
8039
- ].join("\n");
8040
- var hostCommand = defineCommand({
8041
- meta: { name: "host", description: "open a relay channel and print an invite for a peer" },
8042
- args: {
8043
- name: { type: "string", description: "your participant name (default: host)" },
8044
- ...common
8045
- },
8046
- run: async ({ args }) => {
8047
- const url = resolveBaseUrl(args.url);
8048
- const { channelId, token } = await relayClient(url).create();
8049
- if (args.json) {
8050
- console.log(toJson({ channelId, token, url, host: args.name ?? "host" }));
8051
- return;
8052
- }
8053
- console.log(`channel ${channelId} opened
8054
- `);
8055
- console.log(inviteBlock2(url, channelId, token));
7451
+ if (signal.aborted) stop();
7452
+ console.log("[provisioner] waiting for worker creation requests");
7453
+ try {
7454
+ await ended;
7455
+ await tail;
7456
+ } finally {
7457
+ stop();
7458
+ signal.removeEventListener("abort", stop);
8056
7459
  }
8057
- });
8058
- var sendCommand2 = defineCommand({
8059
- meta: { name: "send", description: "post one message to a relay channel" },
8060
- args: {
8061
- channel: { type: "positional", required: true, description: "channel id" },
8062
- text: { type: "positional", required: false, description: "message text (short one-liner)" },
8063
- token: { type: "string", required: true, description: "channel token" },
8064
- from: { type: "string", description: "your participant name (default: peer)" },
8065
- file: { type: "string", description: "read message body from this file (for large content)" },
8066
- ...common
8067
- },
8068
- run: async ({ args }) => {
8069
- const url = resolveBaseUrl(args.url);
8070
- let text = args.text;
8071
- if (text === void 0 && args.file) text = readFileSync3(args.file, "utf8");
8072
- if (text === void 0 && !process.stdin.isTTY) text = await readStdin2();
8073
- if (text === void 0 || text === "")
8074
- throw new Error("nothing to send: provide text, --file <path>, or pipe stdin");
8075
- const msg = await relayClient(url).send(args.channel, args.token, {
8076
- from: args.from ?? "peer",
8077
- text
8078
- });
8079
- if (args.json) console.log(toJson(msg));
8080
- else console.log(`sent (seq ${msg.seq})`);
7460
+ if (fatal !== void 0) throw new Error(taskError(fatal, config));
7461
+ };
7462
+
7463
+ // src/commands/provisioner.ts
7464
+ var configArg = {
7465
+ config: {
7466
+ type: "string",
7467
+ required: true,
7468
+ description: "absolute path to private provisioner JSON config"
8081
7469
  }
8082
- });
8083
- var listenCommand2 = defineCommand({
7470
+ };
7471
+ var provisioner = defineCommand({
8084
7472
  meta: {
8085
- name: "listen",
8086
- description: "stream a channel: one JSON line per peer message (for a background subscriber)"
8087
- },
8088
- args: {
8089
- channel: { type: "positional", required: true, description: "channel id" },
8090
- token: { type: "string", required: true, description: "channel token" },
8091
- from: {
8092
- type: "string",
8093
- description: "your name; messages from this name are skipped (no echo)"
8094
- },
8095
- since: { type: "string", description: "replay messages after this seq (default 0 = full)" },
8096
- ...common
7473
+ name: "provisioner",
7474
+ description: "prepare local repositories and launch workers on macOS"
8097
7475
  },
8098
- run: async ({ args }) => {
8099
- const url = resolveBaseUrl(args.url);
8100
- const me = args.from ?? "";
8101
- const emit = (o) => {
8102
- process.stdout.write(`${JSON.stringify(o)}
8103
- `);
8104
- };
8105
- const spillDir = join6(tmpdir2(), "hotline", args.channel);
8106
- mkdirSync2(spillDir, { recursive: true });
8107
- const surface = (m) => {
8108
- const text = typeof m.text === "string" ? m.text : "";
8109
- const file = join6(spillDir, `${m.seq}.txt`);
8110
- writeFileSync4(file, text);
8111
- const truncated = text.length > PREVIEW2;
8112
- emit({
8113
- type: "relay.message",
8114
- seq: m.seq,
8115
- from: m.from,
8116
- ts: m.ts,
8117
- chars: text.length,
8118
- truncated,
8119
- preview: truncated ? `${text.slice(0, PREVIEW2)} \u2026` : text,
8120
- file
8121
- });
8122
- };
8123
- const describe3 = (e) => {
8124
- const ev = e;
8125
- return ev?.code ? `${ev.code}${ev.message ? ` ${ev.message}` : ""}` : String(e);
8126
- };
8127
- const close = relayClient(url).listen(args.channel, args.token, {
8128
- since: args.since ? Number(args.since) : 0,
8129
- // Skip our own messages (they come back over the same channel).
8130
- onMessage: (m) => {
8131
- if (m.from !== me) surface(m);
8132
- },
8133
- onError: (e) => emit({ type: "relay.error", error: describe3(e) })
8134
- });
8135
- emit({ type: "relay.listening", channel: args.channel, from: me, url });
8136
- await new Promise((resolve4) => {
8137
- const stop = () => {
8138
- close();
8139
- resolve4();
8140
- };
8141
- process.on("SIGINT", stop);
8142
- process.on("SIGTERM", stop);
8143
- });
8144
- }
8145
- });
8146
- var relay = defineCommand({
8147
- meta: { name: "relay", description: "Claude\u2194Claude live channel: host / listen / send" },
8148
- subCommands: { host: hostCommand, listen: listenCommand2, send: sendCommand2 }
8149
- });
8150
-
8151
- // src/commands/requirement.ts
8152
- var createRequirement = (c, input, json) => c.requirements.create(input).then((r) => renderOne(r, fmtRequirement, json));
8153
- var updateRequirement = (c, id, patch, json) => c.requirements.update(id, patch).then((r) => renderOne(r, fmtRequirement, json));
8154
- var listRequirements = (c, projectId, json) => c.requirements.listByProject(projectId).then((rs) => renderList(rs, fmtRequirement, json));
8155
- var getRequirement = (c, id, json) => c.requirements.get(id).then((r) => renderOne(r, fmtRequirement, json));
8156
- var setRequirementStatus = (c, id, status, json) => c.requirements.setStatus(id, status).then((r) => renderOne(r, fmtRequirement, json));
8157
- var removeRequirement = (c, id, label, json) => c.requirements.remove(id).then(() => removed("requirement", label, json));
8158
- var resolveByCode = async (c, projectId, code) => (await c.requirements.getByCode(projectId, code)).id;
8159
- var requirement = defineCommand({
8160
- meta: { name: "requirement", description: "manage requirements" },
8161
7476
  subCommands: {
8162
- create: defineCommand({
8163
- meta: { name: "create", description: "create a requirement" },
8164
- args: {
8165
- title: { type: "positional", required: true },
8166
- project: { type: "string", description: "project id (overrides .baton.json)" },
8167
- desc: { type: "string", description: "description" },
8168
- body: { type: "string", description: "requirement body (markdown)" },
8169
- ...common
8170
- },
8171
- run: async ({ args }) => {
8172
- console.log(
8173
- await createRequirement(
8174
- clientFor(args),
8175
- {
8176
- projectId: resolveProjectId(args),
8177
- title: args.title,
8178
- description: args.desc,
8179
- body: args.body
8180
- },
8181
- Boolean(args.json)
8182
- )
8183
- );
8184
- }
8185
- }),
8186
- update: defineCommand({
8187
- meta: { name: "update", description: "update a requirement title / body" },
8188
- args: {
8189
- code: { type: "positional", required: true, description: "requirement code (R-N)" },
8190
- title: { type: "string", description: "new title" },
8191
- body: { type: "string", description: "new body (markdown)" },
8192
- project: { type: "string", description: "project id (overrides .baton.json)" },
8193
- ...common
8194
- },
8195
- run: async ({ args }) => {
8196
- const patch = {
8197
- ...args.title !== void 0 ? { title: args.title } : {},
8198
- ...args.body !== void 0 ? { body: args.body } : {}
8199
- };
8200
- if (Object.keys(patch).length === 0) throw new Error("pass --title and/or --body");
8201
- const c = clientFor(args);
8202
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
8203
- console.log(await updateRequirement(c, id, patch, Boolean(args.json)));
8204
- }
8205
- }),
8206
- ls: defineCommand({
8207
- meta: { name: "ls", description: "list requirements in a project" },
8208
- args: {
8209
- project: { type: "string", description: "project id (overrides .baton.json)" },
8210
- ...common
8211
- },
8212
- run: async ({ args }) => {
8213
- console.log(
8214
- await listRequirements(clientFor(args), resolveProjectId(args), Boolean(args.json))
8215
- );
8216
- }
8217
- }),
8218
- get: defineCommand({
8219
- meta: { name: "get", description: "get a requirement by code (R-N)" },
8220
- args: {
8221
- code: { type: "positional", required: true, description: "requirement code, e.g. R-1" },
8222
- project: { type: "string", description: "project id (overrides .baton.json)" },
8223
- ...common
8224
- },
7477
+ run: defineCommand({
7478
+ args: configArg,
8225
7479
  run: async ({ args }) => {
8226
- const c = clientFor(args);
8227
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
8228
- console.log(await getRequirement(c, id, Boolean(args.json)));
7480
+ const config = await loadProvisionerConfig(args.config);
7481
+ const abort = new AbortController();
7482
+ const stop = () => abort.abort();
7483
+ process.on("SIGINT", stop);
7484
+ process.on("SIGTERM", stop);
7485
+ try {
7486
+ await runProvisioner(config, abort.signal);
7487
+ } finally {
7488
+ process.off("SIGINT", stop);
7489
+ process.off("SIGTERM", stop);
7490
+ }
8229
7491
  }
8230
7492
  }),
8231
- "set-status": defineCommand({
8232
- meta: { name: "set-status", description: "set requirement status (active|done|cancelled)" },
8233
- args: {
8234
- code: { type: "positional", required: true, description: "requirement code (R-N)" },
8235
- status: { type: "positional", required: true },
8236
- project: { type: "string", description: "project id (overrides .baton.json)" },
8237
- ...common
8238
- },
7493
+ install: defineCommand({
7494
+ args: configArg,
8239
7495
  run: async ({ args }) => {
8240
- const c = clientFor(args);
8241
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
7496
+ const config = await loadProvisionerConfig(args.config);
7497
+ await installProvisionerAgent(config, args.config);
8242
7498
  console.log(
8243
- await setRequirementStatus(c, id, args.status, Boolean(args.json))
7499
+ "installed dev.fmap.baton-provisioner; inspect ~/Library/Logs/baton-provisioner.log"
8244
7500
  );
8245
7501
  }
8246
- }),
8247
- rm: defineCommand({
8248
- meta: { name: "rm", description: "delete a requirement by code" },
8249
- args: {
8250
- code: { type: "positional", required: true },
8251
- project: { type: "string", description: "project id (overrides .baton.json)" },
8252
- ...common
8253
- },
8254
- run: async ({ args }) => {
8255
- const c = clientFor(args);
8256
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
8257
- console.log(await removeRequirement(c, id, args.code, Boolean(args.json)));
8258
- }
8259
7502
  })
8260
7503
  }
8261
7504
  });
@@ -8579,14 +7822,14 @@ import { randomUUID } from "node:crypto";
8579
7822
  import { query } from "@anthropic-ai/claude-agent-sdk";
8580
7823
 
8581
7824
  // src/session/runner/transcript.ts
8582
- import { readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
7825
+ import { readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
8583
7826
  import { homedir as homedir2 } from "node:os";
8584
- import { join as join7 } from "node:path";
7827
+ import { join as join5 } from "node:path";
8585
7828
  var findTranscriptPath = (agentSessionId) => {
8586
- const root = join7(homedir2(), ".claude", "projects");
7829
+ const root = join5(homedir2(), ".claude", "projects");
8587
7830
  try {
8588
7831
  for (const dir of readdirSync(root)) {
8589
- const candidate = join7(root, dir, `${agentSessionId}.jsonl`);
7832
+ const candidate = join5(root, dir, `${agentSessionId}.jsonl`);
8590
7833
  try {
8591
7834
  if (statSync(candidate).isFile()) return candidate;
8592
7835
  } catch {
@@ -8657,7 +7900,7 @@ var readFirstExchange = (agentSessionId) => {
8657
7900
  const path = findTranscriptPath(agentSessionId);
8658
7901
  if (!path) return null;
8659
7902
  try {
8660
- return parseFirstExchange(readFileSync4(path, "utf8"));
7903
+ return parseFirstExchange(readFileSync2(path, "utf8"));
8661
7904
  } catch {
8662
7905
  return null;
8663
7906
  }
@@ -8665,12 +7908,12 @@ var readFirstExchange = (agentSessionId) => {
8665
7908
 
8666
7909
  // src/session/runner/turn.ts
8667
7910
  import { mkdir as mkdir5, writeFile as writeFile3 } from "node:fs/promises";
8668
- import { join as join12 } from "node:path";
7911
+ import { join as join10 } from "node:path";
8669
7912
 
8670
7913
  // src/session/runner/attachments.ts
8671
7914
  import { createWriteStream } from "node:fs";
8672
7915
  import { mkdir as mkdir4 } from "node:fs/promises";
8673
- import { join as join8 } from "node:path";
7916
+ import { join as join6 } from "node:path";
8674
7917
  import { Readable } from "node:stream";
8675
7918
  import { pipeline } from "node:stream/promises";
8676
7919
  var dedupe = (name, taken) => {
@@ -8700,8 +7943,8 @@ var materializeAttachments = async (opts) => {
8700
7943
  const name = dedupe(att.filename, taken);
8701
7944
  const res = await fetchImpl(`${opts.serverBase}${att.url}`);
8702
7945
  if (!res.ok || !res.body) throw new Error(`attachment ${att.id} \u2192 ${res.status}`);
8703
- await pipeline(Readable.fromWeb(res.body), createWriteStream(join8(dir, name)));
8704
- paths.push(join8(dir, name));
7946
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(join6(dir, name)));
7947
+ paths.push(join6(dir, name));
8705
7948
  }
8706
7949
  return paths;
8707
7950
  };
@@ -8717,7 +7960,7 @@ ${text}` : header;
8717
7960
  };
8718
7961
 
8719
7962
  // src/session/runner/codex.ts
8720
- import { join as join9 } from "node:path";
7963
+ import { join as join7 } from "node:path";
8721
7964
  import { Codex } from "@openai/codex-sdk";
8722
7965
 
8723
7966
  // src/session/runner/sdk-env.ts
@@ -8879,7 +8122,7 @@ async function* startCodexEvents(config, input, worker2, opts) {
8879
8122
  const env = compactEnv(buildSdkEnv(opts.envOverlay));
8880
8123
  const codex = new Codex(env ? { env } : void 0);
8881
8124
  const resuming = !config.agentSessionId.startsWith("pending:");
8882
- const addDirs = additionalDirs([join9(config.runtimeDir, "attachments")]);
8125
+ const addDirs = additionalDirs([join7(config.runtimeDir, "attachments")]);
8883
8126
  const sandboxMode = codexSandboxMode(opts.planMode ?? false);
8884
8127
  const approvalPolicy = codexApprovalPolicy();
8885
8128
  const networkAccessEnabled = codexNetworkAccess();
@@ -8920,7 +8163,7 @@ async function* startCodexEvents(config, input, worker2, opts) {
8920
8163
  // src/session/runner/pi.ts
8921
8164
  import { readFile as readFile2 } from "node:fs/promises";
8922
8165
  import { homedir as homedir3 } from "node:os";
8923
- import { join as join10 } from "node:path";
8166
+ import { join as join8 } from "node:path";
8924
8167
  var newPiTurnState = (startedAt = Date.now()) => ({
8925
8168
  startedAt,
8926
8169
  usage: {},
@@ -9065,7 +8308,7 @@ async function* startPiEvents(config, input, opts) {
9065
8308
  agentDir,
9066
8309
  // pi discovers .pi/ and .agents/ skills natively; Baton workers deploy theirs
9067
8310
  // under .claude/skills (project and user level).
9068
- additionalSkillPaths: [join10(config.cwd, ".claude/skills"), join10(homedir3(), ".claude/skills")]
8311
+ additionalSkillPaths: [join8(config.cwd, ".claude/skills"), join8(homedir3(), ".claude/skills")]
9069
8312
  });
9070
8313
  await resourceLoader.reload();
9071
8314
  const existing = pi.SessionManager.findById(config.cwd, config.agentSessionId);
@@ -9140,11 +8383,11 @@ async function* startPiEvents(config, input, opts) {
9140
8383
  }
9141
8384
 
9142
8385
  // src/session/runner/query.ts
9143
- import { join as join11 } from "node:path";
8386
+ import { join as join9 } from "node:path";
9144
8387
  var startQuery = (config, text, resuming, queryFn, abortController, log, { envOverlay, planMode = false, model, effort } = {}) => {
9145
8388
  const env = buildSdkEnv(envOverlay);
9146
8389
  const exe = claudeExecutable();
9147
- const addDirs = additionalDirs([join11(config.runtimeDir, "attachments")]);
8390
+ const addDirs = additionalDirs([join9(config.runtimeDir, "attachments")]);
9148
8391
  const level = claudeEffort(effort);
9149
8392
  if (addDirs) log(`[add-dir] ${addDirs.join(", ")}`);
9150
8393
  if (planMode) log("[plan] read-only planning turn");
@@ -9395,7 +8638,7 @@ var UnsettledExecution = class extends Error {
9395
8638
  var materializeImages = async (config, execution) => {
9396
8639
  const images = execution.message.payload.images ?? [];
9397
8640
  if (!images.length) return [];
9398
- const directory2 = join12(
8641
+ const directory2 = join10(
9399
8642
  config.runtimeDir,
9400
8643
  "attachments",
9401
8644
  `message-${execution.message.id}`,
@@ -9406,7 +8649,7 @@ var materializeImages = async (config, execution) => {
9406
8649
  images.map(async (image, index) => {
9407
8650
  const match = /^data:image\/(png|jpeg|webp|gif);base64,([A-Za-z0-9+/=\r\n]+)$/.exec(image);
9408
8651
  if (!match) throw new Error("unsupported pasted image");
9409
- const path = join12(directory2, `pasted-${index + 1}.${match[1]}`);
8652
+ const path = join10(directory2, `pasted-${index + 1}.${match[1]}`);
9410
8653
  await writeFile3(path, Buffer.from(match[2] ?? "", "base64"));
9411
8654
  return path;
9412
8655
  })
@@ -9415,7 +8658,7 @@ var materializeImages = async (config, execution) => {
9415
8658
  var buildPrompt = async (config, execution, fetchImpl) => {
9416
8659
  const { text: rawText, attachments = [] } = execution.message.payload;
9417
8660
  const paths = attachments.length ? await materializeAttachments({
9418
- directory: join12(
8661
+ directory: join10(
9419
8662
  config.runtimeDir,
9420
8663
  "attachments",
9421
8664
  `message-${execution.message.id}`,
@@ -9672,15 +8915,15 @@ var runDaemon = async (config, deps, signal) => {
9672
8915
  // src/worker/runtime-paths.ts
9673
8916
  import { createHash } from "node:crypto";
9674
8917
  import { homedir as homedir4 } from "node:os";
9675
- import { join as join13 } from "node:path";
9676
- var workerRuntimeDir = (owner, env = process.env) => join13(
9677
- env.XDG_DATA_HOME ?? join13(env.HOME ?? homedir4(), ".local/share"),
8918
+ import { join as join11 } from "node:path";
8919
+ var workerRuntimeDir = (owner, env = process.env) => join11(
8920
+ env.XDG_DATA_HOME ?? join11(env.HOME ?? homedir4(), ".local/share"),
9678
8921
  "baton",
9679
8922
  "runtime",
9680
8923
  createHash("sha256").update(owner.server).digest("hex"),
9681
8924
  `worker-${owner.workerId}`
9682
8925
  );
9683
- var sessionRuntimeDir = (owner, sessionId, env = process.env) => join13(workerRuntimeDir(owner, env), "sessions", String(sessionId));
8926
+ var sessionRuntimeDir = (owner, sessionId, env = process.env) => join11(workerRuntimeDir(owner, env), "sessions", String(sessionId));
9684
8927
 
9685
8928
  // src/commands/session/run.ts
9686
8929
  var bearerFetch = (token) => (input, init2) => fetch(input, {
@@ -9775,6 +9018,25 @@ var sessionRunCommand = defineCommand({
9775
9018
  }
9776
9019
  });
9777
9020
 
9021
+ // src/commands/attach.ts
9022
+ import { openAsBlob as openAsBlob2 } from "node:fs";
9023
+ import { basename as basename2 } from "node:path";
9024
+ var attachPaths = (raw) => (raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw]).flatMap((p) => p.split(",")).map((p) => p.trim()).filter(Boolean);
9025
+ var uploadAttachments = async (client, sessionId, paths) => {
9026
+ const out = [];
9027
+ for (const path of paths) {
9028
+ const body = await openAsBlob2(path, { type: contentTypeForPath(path) });
9029
+ out.push(
9030
+ await client.sessions.uploadAttachment(sessionId, {
9031
+ filename: basename2(path),
9032
+ contentType: contentTypeForPath(path),
9033
+ body
9034
+ })
9035
+ );
9036
+ }
9037
+ return out;
9038
+ };
9039
+
9778
9040
  // src/commands/session/send.ts
9779
9041
  var sessionSendCommand = defineCommand({
9780
9042
  meta: { name: "send", description: "post a chat message into a session (no streaming reply)" },
@@ -9822,6 +9084,60 @@ var sessionStopCommand = defineCommand({
9822
9084
  }
9823
9085
  });
9824
9086
 
9087
+ // src/commands/session/tell.ts
9088
+ var TELL_LOOP_LIMIT = 10;
9089
+ var TELL_HEADER = /^\[Message from baton session #\d+ /gm;
9090
+ var renderTell = (from, body) => [
9091
+ `[Message from baton session #${from.id} ${JSON.stringify(from.name)}]`,
9092
+ `To reply, run: baton session tell ${from.id} "<your reply>"`,
9093
+ "Reply only if a response is actually needed.",
9094
+ "",
9095
+ body
9096
+ ].join("\n");
9097
+ var headerCount = (event) => {
9098
+ const text = event.payload?.text ?? "";
9099
+ return text.match(TELL_HEADER)?.length ?? 0;
9100
+ };
9101
+ var countConsecutiveTells = (events) => {
9102
+ const newestFirst = events.filter((event) => event.type === "user_message").map(headerCount).reverse();
9103
+ const end = newestFirst.indexOf(0);
9104
+ return (end < 0 ? newestFirst : newestFirst.slice(0, end)).reduce((sum, n) => sum + n, 0);
9105
+ };
9106
+ var sendTell = async (c, { fromId, toId, body }) => {
9107
+ if (body.trim() === "") throw new Error("nothing to send: message text is empty");
9108
+ if (toId === fromId) throw new Error("cannot tell your own session");
9109
+ const [from, events] = await Promise.all([c.sessions.get(fromId), c.sessions.listEvents(fromId)]);
9110
+ const received = countConsecutiveTells(events);
9111
+ if (received >= TELL_LOOP_LIMIT)
9112
+ throw new Error(
9113
+ `not sent: this session received ${received} agent messages in a row. Stop and ask a human; a message from them resets the count.`
9114
+ );
9115
+ return c.sessions.sendMessage(toId, renderTell(from, body));
9116
+ };
9117
+ var parseId = (raw) => raw !== void 0 && /^[1-9]\d*$/.test(raw) ? Number(raw) : null;
9118
+ var sessionTellCommand = defineCommand({
9119
+ meta: {
9120
+ name: "tell",
9121
+ description: "Send a message to another agent session in this workspace. It includes who sent it and how to reply, and wakes the target if needed. Delivery is fire-and-forget: end your turn, and any reply arrives as a new message. You cannot read another session; ask it instead."
9122
+ },
9123
+ args: {
9124
+ session: { type: "positional", required: true, description: "target session int id" },
9125
+ text: { type: "positional", required: true, description: "message text" },
9126
+ ...common
9127
+ },
9128
+ run: async ({ args }) => {
9129
+ const fromId = parseId(process.env.BATON_SESSION_ID);
9130
+ const bearer = process.env.BATON_WORKER_TOKEN;
9131
+ if (fromId === null || !bearer)
9132
+ throw new Error("baton session tell only works inside a baton session");
9133
+ const toId = parseId(args.session);
9134
+ if (toId === null) throw new Error(`invalid session "${args.session}": pass the session id`);
9135
+ const client = createClient(resolveBaseUrl(args.url), { bearer });
9136
+ const result = await sendTell(client, { fromId, toId, body: args.text });
9137
+ console.log(args.json ? toJson(result) : `told session #${toId} (input ${result.input.id})`);
9138
+ }
9139
+ });
9140
+
9825
9141
  // src/commands/session/terminal.ts
9826
9142
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
9827
9143
  var sessionTerminalCommand = defineCommand({
@@ -9870,6 +9186,7 @@ var session = defineCommand({
9870
9186
  rename: sessionRenameCommand,
9871
9187
  rm: sessionRmCommand,
9872
9188
  send: sessionSendCommand,
9189
+ tell: sessionTellCommand,
9873
9190
  ls: sessionLsCommand,
9874
9191
  get: sessionGetCommand,
9875
9192
  run: sessionRunCommand,
@@ -9877,177 +9194,18 @@ var session = defineCommand({
9877
9194
  }
9878
9195
  });
9879
9196
 
9880
- // src/commands/task.ts
9881
- var createTask = (c, input, json) => c.tasks.create(input).then((t) => renderOne(t, fmtTask, json));
9882
- var updateTask = (c, id, patch, json) => c.tasks.update(id, patch).then((t) => renderOne(t, fmtTask, json));
9883
- var listTasks = (c, requirementId, json) => c.tasks.listByRequirement(requirementId).then((ts) => renderList(ts, fmtTask, json));
9884
- var getTask = (c, id, json) => c.tasks.get(id).then((t) => renderOne(t, fmtTask, json));
9885
- var setTaskStatus = (c, id, status, json) => c.tasks.setStatus(id, status).then((t) => renderOne(t, fmtTask, json));
9886
- var removeTask = (c, id, label, json) => c.tasks.remove(id).then(() => removed("task", label, json));
9887
- var addTaskComment = (c, taskId, body, workerId, json) => c.tasks.addComment(taskId, body, workerId).then((x) => renderOne(x, fmtComment, json));
9888
- var listTaskComments = (c, taskId, json) => c.tasks.listComments(taskId).then((xs) => renderList(xs, fmtComment, json));
9889
- var resolveReqByCode = async (c, projectId, code) => (await c.requirements.getByCode(projectId, code)).id;
9890
- var resolveTaskByCode = async (c, projectId, code) => (await c.tasks.getByCode(projectId, code)).id;
9891
- var task = defineCommand({
9892
- meta: { name: "task", description: "manage tasks" },
9893
- subCommands: {
9894
- create: defineCommand({
9895
- meta: { name: "create", description: "create a task" },
9896
- args: {
9897
- title: { type: "positional", required: true },
9898
- requirement: { type: "string", required: true, description: "requirement code (R-N)" },
9899
- project: { type: "string", description: "project id (overrides .baton.json)" },
9900
- body: { type: "string", description: "task body (markdown)" },
9901
- deps: { type: "string", description: "comma-separated dependency task codes (T-N,T-N)" },
9902
- ...common
9903
- },
9904
- run: async ({ args }) => {
9905
- const c = clientFor(args);
9906
- const projectId = resolveProjectId(args);
9907
- const requirementId = await resolveReqByCode(c, projectId, args.requirement);
9908
- const depCodes = splitCsv(args.deps) ?? [];
9909
- const dependsOn = await Promise.all(
9910
- depCodes.map((code) => resolveTaskByCode(c, projectId, code))
9911
- );
9912
- console.log(
9913
- await createTask(
9914
- c,
9915
- {
9916
- requirementId,
9917
- title: args.title,
9918
- body: args.body,
9919
- dependsOn: dependsOn.length ? dependsOn : void 0
9920
- },
9921
- Boolean(args.json)
9922
- )
9923
- );
9924
- }
9925
- }),
9926
- update: defineCommand({
9927
- meta: { name: "update", description: "update a task title / body" },
9928
- args: {
9929
- code: { type: "positional", required: true, description: "task code (T-N)" },
9930
- title: { type: "string", description: "new title" },
9931
- body: { type: "string", description: "new body (markdown)" },
9932
- project: { type: "string", description: "project id (overrides .baton.json)" },
9933
- ...common
9934
- },
9935
- run: async ({ args }) => {
9936
- const patch = {
9937
- ...args.title !== void 0 ? { title: args.title } : {},
9938
- ...args.body !== void 0 ? { body: args.body } : {}
9939
- };
9940
- if (Object.keys(patch).length === 0) throw new Error("pass --title and/or --body");
9941
- const c = clientFor(args);
9942
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
9943
- console.log(await updateTask(c, id, patch, Boolean(args.json)));
9944
- }
9945
- }),
9946
- ls: defineCommand({
9947
- meta: { name: "ls", description: "list tasks in a requirement" },
9948
- args: {
9949
- requirement: { type: "string", required: true, description: "requirement code (R-N)" },
9950
- project: { type: "string", description: "project id (overrides .baton.json)" },
9951
- ...common
9952
- },
9953
- run: async ({ args }) => {
9954
- const c = clientFor(args);
9955
- const reqId = await resolveReqByCode(c, resolveProjectId(args), args.requirement);
9956
- console.log(await listTasks(c, reqId, Boolean(args.json)));
9957
- }
9958
- }),
9959
- get: defineCommand({
9960
- meta: { name: "get", description: "get a task by code (T-N)" },
9961
- args: {
9962
- code: { type: "positional", required: true },
9963
- project: { type: "string", description: "project id (overrides .baton.json)" },
9964
- ...common
9965
- },
9966
- run: async ({ args }) => {
9967
- const c = clientFor(args);
9968
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
9969
- console.log(await getTask(c, id, Boolean(args.json)));
9970
- }
9971
- }),
9972
- "set-status": defineCommand({
9973
- meta: {
9974
- name: "set-status",
9975
- description: "set task status (todo|in_progress|blocked|done|failed|cancelled)"
9976
- },
9977
- args: {
9978
- code: { type: "positional", required: true, description: "task code (T-N)" },
9979
- status: { type: "positional", required: true },
9980
- project: { type: "string", description: "project id (overrides .baton.json)" },
9981
- ...common
9982
- },
9983
- run: async ({ args }) => {
9984
- const c = clientFor(args);
9985
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
9986
- console.log(await setTaskStatus(c, id, args.status, Boolean(args.json)));
9987
- }
9988
- }),
9989
- rm: defineCommand({
9990
- meta: { name: "rm", description: "delete a task by code" },
9991
- args: {
9992
- code: { type: "positional", required: true },
9993
- project: { type: "string", description: "project id (overrides .baton.json)" },
9994
- ...common
9995
- },
9996
- run: async ({ args }) => {
9997
- const c = clientFor(args);
9998
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
9999
- console.log(await removeTask(c, id, args.code, Boolean(args.json)));
10000
- }
10001
- }),
10002
- comment: defineCommand({
10003
- meta: { name: "comment", description: "append-only task comments" },
10004
- subCommands: {
10005
- add: defineCommand({
10006
- meta: { name: "add", description: "add a comment to a task" },
10007
- args: {
10008
- code: { type: "positional", required: true, description: "task code (T-N)" },
10009
- body: { type: "positional", required: true, description: "comment text (markdown)" },
10010
- worker: { type: "string", description: "attribute to a worker id (omit = human)" },
10011
- project: { type: "string", description: "project id (overrides .baton.json)" },
10012
- ...common
10013
- },
10014
- run: async ({ args }) => {
10015
- const c = clientFor(args);
10016
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
10017
- const workerId = args.worker ? Number(args.worker) : void 0;
10018
- console.log(await addTaskComment(c, id, args.body, workerId, Boolean(args.json)));
10019
- }
10020
- }),
10021
- ls: defineCommand({
10022
- meta: { name: "ls", description: "list a task\u2019s comments in order" },
10023
- args: {
10024
- code: { type: "positional", required: true, description: "task code (T-N)" },
10025
- project: { type: "string", description: "project id (overrides .baton.json)" },
10026
- ...common
10027
- },
10028
- run: async ({ args }) => {
10029
- const c = clientFor(args);
10030
- const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
10031
- console.log(await listTaskComments(c, id, Boolean(args.json)));
10032
- }
10033
- })
10034
- }
10035
- })
10036
- }
10037
- });
10038
-
10039
9197
  // src/commands/worker/index.ts
10040
9198
  import { hostname as osHostname } from "node:os";
10041
9199
 
10042
9200
  // src/worker/bundled-skills.ts
10043
9201
  import { spawnSync as spawnSync2 } from "node:child_process";
10044
9202
  import { cpSync, existsSync as existsSync5 } from "node:fs";
10045
- import { dirname as dirname4, join as join14 } from "node:path";
9203
+ import { dirname as dirname4, join as join12 } from "node:path";
10046
9204
  import { fileURLToPath } from "node:url";
10047
9205
 
10048
9206
  // src/worker/git-exclude.ts
10049
9207
  import { spawnSync } from "node:child_process";
10050
- import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "node:fs";
9208
+ import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3 } from "node:fs";
10051
9209
  import { dirname as dirname3, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
10052
9210
  var ensureExcluded = (cwd, pattern) => {
10053
9211
  const result = spawnSync("git", ["-C", cwd, "rev-parse", "--git-path", "info/exclude"], {
@@ -10061,9 +9219,9 @@ var ensureExcluded = (cwd, pattern) => {
10061
9219
  }
10062
9220
  const location = result.stdout.trim();
10063
9221
  const path = isAbsolute3(location) ? location : resolve3(cwd, location);
10064
- const current = existsSync4(path) ? readFileSync5(path, "utf8") : "";
9222
+ const current = existsSync4(path) ? readFileSync3(path, "utf8") : "";
10065
9223
  if (current.split("\n").includes(pattern)) return;
10066
- mkdirSync3(dirname3(path), { recursive: true });
9224
+ mkdirSync(dirname3(path), { recursive: true });
10067
9225
  appendFileSync(path, `${current && !current.endsWith("\n") ? "\n" : ""}${pattern}
10068
9226
  `);
10069
9227
  };
@@ -10074,8 +9232,8 @@ var TARGETS = [`.claude/skills/${SKILL_NAME}`, `.agents/skills/${SKILL_NAME}`];
10074
9232
  var bundledSkillPath = () => {
10075
9233
  const here = dirname4(fileURLToPath(import.meta.url));
10076
9234
  const candidates = [
10077
- join14(here, "..", "skills", SKILL_NAME),
10078
- join14(here, "..", "..", "skills", SKILL_NAME)
9235
+ join12(here, "..", "skills", SKILL_NAME),
9236
+ join12(here, "..", "..", "skills", SKILL_NAME)
10079
9237
  ];
10080
9238
  const found = candidates.find(existsSync5);
10081
9239
  if (!found) throw new Error(`bundled skill missing: ${SKILL_NAME}`);
@@ -10092,7 +9250,7 @@ var syncBundledArtifactSkill = (cwd) => {
10092
9250
  const source = bundledSkillPath();
10093
9251
  TARGETS.filter((target) => !isTracked(cwd, target)).forEach((target) => {
10094
9252
  ensureExcluded(cwd, `${target}/`);
10095
- cpSync(source, join14(cwd, target), { recursive: true, force: true });
9253
+ cpSync(source, join12(cwd, target), { recursive: true, force: true });
10096
9254
  });
10097
9255
  };
10098
9256
 
@@ -10363,7 +9521,7 @@ var openCommitDownload = async (repo, commit, path, signal) => {
10363
9521
  // src/worker/files/disk.ts
10364
9522
  import { constants as constants2 } from "node:fs";
10365
9523
  import { open as open2, readdir, realpath as realpath3, stat as stat3 } from "node:fs/promises";
10366
- import { isAbsolute as isAbsolute4, join as join15, relative, sep } from "node:path";
9524
+ import { isAbsolute as isAbsolute4, join as join13, relative, sep } from "node:path";
10367
9525
  import { Readable as Readable3 } from "node:stream";
10368
9526
  var isMissing = (error) => ["ENOENT", "ENOTDIR", "ELOOP"].includes(error.code ?? "");
10369
9527
  var childPath = (parent, name) => parent ? `${parent}/${name}` : name;
@@ -10376,7 +9534,7 @@ var resolveInside = async (root, path, allowRoot) => {
10376
9534
  const rel = checkedPath(path, allowRoot);
10377
9535
  let real;
10378
9536
  try {
10379
- real = await realpath3(join15(root, rel));
9537
+ real = await realpath3(join13(root, rel));
10380
9538
  } catch (error) {
10381
9539
  if (isMissing(error)) throw notFound(rel);
10382
9540
  throw error;
@@ -10406,7 +9564,7 @@ var toEntry = async (root, dir, dirent) => {
10406
9564
  if (dirent.isDirectory())
10407
9565
  return isExcludedDirectoryName(dirent.name) ? null : { name: dirent.name, path, kind: "directory" };
10408
9566
  if (dirent.isFile()) return { name: dirent.name, path, kind: "file" };
10409
- return dirent.isSymbolicLink() ? symlinkEntry(root, join15(dir.real, dirent.name), dirent.name, path) : null;
9567
+ return dirent.isSymbolicLink() ? symlinkEntry(root, join13(dir.real, dirent.name), dirent.name, path) : null;
10410
9568
  };
10411
9569
  var listDirectory = async (root, path) => {
10412
9570
  const dir = await resolveInside(root, path, true);
@@ -10429,7 +9587,7 @@ var searchFiles = async (root, query3, now = Date.now) => {
10429
9587
  const dir = pending[index] ?? "";
10430
9588
  let dirents;
10431
9589
  try {
10432
- dirents = await readdir(join15(root, dir), { withFileTypes: true });
9590
+ dirents = await readdir(join13(root, dir), { withFileTypes: true });
10433
9591
  } catch (error) {
10434
9592
  console.warn(`[files] search skipped unreadable directory ${dir || "."}`, error);
10435
9593
  continue;
@@ -10673,14 +9831,14 @@ var createLiveness = (deps) => {
10673
9831
  };
10674
9832
 
10675
9833
  // src/worker/service-supervisor.ts
10676
- import { mkdirSync as mkdirSync4 } from "node:fs";
9834
+ import { mkdirSync as mkdirSync2 } from "node:fs";
10677
9835
  import { stat as stat4 } from "node:fs/promises";
10678
- import { join as join17 } from "node:path";
9836
+ import { join as join15 } from "node:path";
10679
9837
 
10680
9838
  // src/worker/service-process.ts
10681
9839
  import { spawn as spawn3 } from "node:child_process";
10682
9840
  import { closeSync, existsSync as existsSync6, openSync } from "node:fs";
10683
- import { dirname as dirname5, join as join16 } from "node:path";
9841
+ import { dirname as dirname5, join as join14 } from "node:path";
10684
9842
  import { fileURLToPath as fileURLToPath2 } from "node:url";
10685
9843
  var START_TIMEOUT_MS = 1e4;
10686
9844
  var isWrapperMessage = (message) => {
@@ -10691,7 +9849,7 @@ var isWrapperMessage = (message) => {
10691
9849
  };
10692
9850
  var binPath = () => {
10693
9851
  const here = dirname5(fileURLToPath2(import.meta.url));
10694
- const devShim = join16(here, "..", "..", "bin", "baton.mjs");
9852
+ const devShim = join14(here, "..", "..", "bin", "baton.mjs");
10695
9853
  return existsSync6(devShim) ? devShim : fileURLToPath2(import.meta.url);
10696
9854
  };
10697
9855
  var startServiceProcess = (input) => {
@@ -10785,9 +9943,9 @@ var createServiceSupervisor = (deps) => {
10785
9943
  if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
10786
9944
  return report({ requestId, ok: false, error: `directory not found: ${cwd}`, status: 400 });
10787
9945
  }
10788
- const logDir = join17(workerRuntimeDir(cfg), "services");
10789
- const logPath = join17(logDir, `${name}.log`);
10790
- mkdirSync4(logDir, { recursive: true, mode: 448 });
9946
+ const logDir = join15(workerRuntimeDir(cfg), "services");
9947
+ const logPath = join15(logDir, `${name}.log`);
9948
+ mkdirSync2(logDir, { recursive: true, mode: 448 });
10791
9949
  if (entries.has(name))
10792
9950
  return report({
10793
9951
  requestId,
@@ -10852,7 +10010,7 @@ import { spawn as spawn4 } from "node:child_process";
10852
10010
  import { randomUUID as randomUUID2 } from "node:crypto";
10853
10011
  import { existsSync as existsSync7 } from "node:fs";
10854
10012
  import { mkdir as mkdir6, rm as rm3, stat as stat5 } from "node:fs/promises";
10855
- import { dirname as dirname6, join as join21 } from "node:path";
10013
+ import { dirname as dirname6, join as join19 } from "node:path";
10856
10014
  import { fileURLToPath as fileURLToPath3 } from "node:url";
10857
10015
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
10858
10016
 
@@ -10945,8 +10103,8 @@ var generateTitle = async (input) => {
10945
10103
 
10946
10104
  // src/session/runner/title-codex.ts
10947
10105
  import { mkdtemp, rm } from "node:fs/promises";
10948
- import { tmpdir as tmpdir3 } from "node:os";
10949
- import { join as join18 } from "node:path";
10106
+ import { tmpdir } from "node:os";
10107
+ import { join as join16 } from "node:path";
10950
10108
  import { Codex as Codex2 } from "@openai/codex-sdk";
10951
10109
  var generateTitleWithCodex = async (input) => {
10952
10110
  const { userText, assistantText } = input;
@@ -10955,7 +10113,7 @@ var generateTitleWithCodex = async (input) => {
10955
10113
  const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
10956
10114
  let cwd = "";
10957
10115
  try {
10958
- cwd = await mkdtemp(join18(tmpdir3(), "baton-title-"));
10116
+ cwd = await mkdtemp(join16(tmpdir(), "baton-title-"));
10959
10117
  const client = input.client ?? new Codex2();
10960
10118
  const thread = client.startThread({
10961
10119
  workingDirectory: cwd,
@@ -10983,8 +10141,8 @@ var generateTitleWithCodex = async (input) => {
10983
10141
 
10984
10142
  // src/session/runner/title-pi.ts
10985
10143
  import { mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
10986
- import { tmpdir as tmpdir4 } from "node:os";
10987
- import { join as join19 } from "node:path";
10144
+ import { tmpdir as tmpdir2 } from "node:os";
10145
+ import { join as join17 } from "node:path";
10988
10146
  var generateTitleWithPi = async (input) => {
10989
10147
  const { userText, assistantText } = input;
10990
10148
  if (`${userText}${assistantText}`.trim().length < 4) return { kind: "declined" };
@@ -10992,7 +10150,7 @@ var generateTitleWithPi = async (input) => {
10992
10150
  let timedOut = false;
10993
10151
  let timer;
10994
10152
  try {
10995
- cwd = await mkdtemp2(join19(tmpdir4(), "baton-title-"));
10153
+ cwd = await mkdtemp2(join17(tmpdir2(), "baton-title-"));
10996
10154
  const pi = await import("@earendil-works/pi-coding-agent");
10997
10155
  const agentDir = pi.getAgentDir();
10998
10156
  const resourceLoader = new pi.DefaultResourceLoader({
@@ -11044,11 +10202,11 @@ var generateTitleWithPi = async (input) => {
11044
10202
  // src/worker/session-process.ts
11045
10203
  import { execFile as execFile5 } from "node:child_process";
11046
10204
  import { readFile as readFile3, rename, writeFile as writeFile4 } from "node:fs/promises";
11047
- import { join as join20 } from "node:path";
10205
+ import { join as join18 } from "node:path";
11048
10206
  import { promisify as promisify5 } from "node:util";
11049
10207
  var run3 = promisify5(execFile5);
11050
10208
  var RUNNER_RECORD = ".baton-runner.json";
11051
- var recordPath = (cwd) => join20(cwd, RUNNER_RECORD);
10209
+ var recordPath = (cwd) => join18(cwd, RUNNER_RECORD);
11052
10210
  var codeOf = (error) => typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
11053
10211
  var readRunnerRecord = async (cwd) => {
11054
10212
  let raw;
@@ -11116,7 +10274,7 @@ var stopRecordedRunner = async (record, expected, graceMs = 5e3) => {
11116
10274
  // src/worker/session-supervisor.ts
11117
10275
  var binPath2 = () => {
11118
10276
  const here = dirname6(fileURLToPath3(import.meta.url));
11119
- const devShim = join21(here, "..", "..", "bin", "baton.mjs");
10277
+ const devShim = join19(here, "..", "..", "bin", "baton.mjs");
11120
10278
  return existsSync7(devShim) ? devShim : fileURLToPath3(import.meta.url);
11121
10279
  };
11122
10280
  var createSessionSupervisor = (deps) => {
@@ -11361,7 +10519,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
11361
10519
  import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
11362
10520
  import { createRequire as createModuleRequire } from "node:module";
11363
10521
  import { homedir as homedir5 } from "node:os";
11364
- import { dirname as dirname7, join as join22 } from "node:path";
10522
+ import { dirname as dirname7, join as join20 } from "node:path";
11365
10523
  import { fileURLToPath as fileURLToPath4 } from "node:url";
11366
10524
  var MAX_TERMINALS = 10;
11367
10525
  var claudePtyCommand = (agentSessionId, hasJsonl, env = process.env) => ({
@@ -11382,13 +10540,13 @@ var piPtyCommand = (agentSessionId, env = process.env) => {
11382
10540
  const override = env.BATON_PI_BIN?.trim();
11383
10541
  if (override) return { file: override, args };
11384
10542
  const entry = fileURLToPath4(import.meta.resolve("@earendil-works/pi-coding-agent"));
11385
- return { file: process.execPath, args: [join22(dirname7(entry), "bundle", "cli.js"), ...args] };
10543
+ return { file: process.execPath, args: [join20(dirname7(entry), "bundle", "cli.js"), ...args] };
11386
10544
  };
11387
10545
  var hasSessionJsonl = (agentSessionId) => {
11388
- const root = join22(homedir5(), ".claude", "projects");
10546
+ const root = join20(homedir5(), ".claude", "projects");
11389
10547
  if (!existsSync8(root)) return false;
11390
10548
  for (const dir of readdirSync2(root))
11391
- if (existsSync8(join22(root, dir, `${agentSessionId}.jsonl`))) return true;
10549
+ if (existsSync8(join20(root, dir, `${agentSessionId}.jsonl`))) return true;
11392
10550
  return false;
11393
10551
  };
11394
10552
  var ptyCommand = (kind, agentSessionId) => ({
@@ -11585,18 +10743,18 @@ var runWorkerDaemon = async (cfg, client, signal) => {
11585
10743
 
11586
10744
  // src/worker/machine-id.ts
11587
10745
  import { randomUUID as randomUUID3 } from "node:crypto";
11588
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
10746
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
11589
10747
  import { homedir as homedir6 } from "node:os";
11590
- import { dirname as dirname8, join as join23 } from "node:path";
11591
- var machineIdPath = (env = process.env) => join23(env.XDG_DATA_HOME ?? join23(env.HOME ?? homedir6(), ".local/share"), "baton", "machine-id");
10748
+ import { dirname as dirname8, join as join21 } from "node:path";
10749
+ var machineIdPath = (env = process.env) => join21(env.XDG_DATA_HOME ?? join21(env.HOME ?? homedir6(), ".local/share"), "baton", "machine-id");
11592
10750
  var readOrCreateMachineId = (path = machineIdPath()) => {
11593
10751
  if (existsSync9(path)) {
11594
- const s = readFileSync6(path, "utf8").trim();
10752
+ const s = readFileSync4(path, "utf8").trim();
11595
10753
  if (s) return s;
11596
10754
  }
11597
10755
  const id = randomUUID3();
11598
- mkdirSync5(dirname8(path), { recursive: true });
11599
- writeFileSync5(path, `${id}
10756
+ mkdirSync3(dirname8(path), { recursive: true });
10757
+ writeFileSync2(path, `${id}
11600
10758
  `, "utf8");
11601
10759
  return id;
11602
10760
  };
@@ -11605,7 +10763,7 @@ var readOrCreateMachineId = (path = machineIdPath()) => {
11605
10763
  var projectArg = {
11606
10764
  project: { type: "string", description: "project id (overrides .baton.json)" }
11607
10765
  };
11608
- var positiveId = (raw) => {
10766
+ var positiveId2 = (raw) => {
11609
10767
  const id = Number(raw);
11610
10768
  if (!Number.isSafeInteger(id) || id <= 0) throw new Error("positive id required");
11611
10769
  return id;
@@ -11614,12 +10772,12 @@ var workerTemplatesCommand = defineCommand({
11614
10772
  meta: { name: "templates", description: "list worker creation templates for a project" },
11615
10773
  args: { ...projectArg, ...common },
11616
10774
  run: async ({ args }) => {
11617
- const list = await clientFor(args).workerCreations.templates(positiveId(resolveProjectId(args)));
10775
+ const list = await clientFor(args).workerCreations.templates(positiveId2(resolveProjectId(args)));
11618
10776
  console.log(renderList(list, fmtWorkerTemplate, Boolean(args.json)));
11619
10777
  }
11620
10778
  });
11621
10779
  var workerCreateCommand = defineCommand({
11622
- meta: { name: "create", description: "request a worker from the mini template (asynchronous)" },
10780
+ meta: { name: "create", description: "request a worker from a template (asynchronous)" },
11623
10781
  args: {
11624
10782
  ...projectArg,
11625
10783
  ...common,
@@ -11633,6 +10791,10 @@ var workerCreateCommand = defineCommand({
11633
10791
  repoPath: {
11634
10792
  type: "string",
11635
10793
  description: "share an existing worker's directory in this project (default: a new one)"
10794
+ },
10795
+ host: {
10796
+ type: "string",
10797
+ description: "host name or id (default: the workspace's only host)"
11636
10798
  }
11637
10799
  },
11638
10800
  run: async ({ args }) => {
@@ -11643,15 +10805,16 @@ var workerCreateCommand = defineCommand({
11643
10805
  throw new Error("agentKind is not supported by this template");
11644
10806
  const name = args.name.trim();
11645
10807
  if (!name) throw new Error("worker name required");
11646
- const creation = await clientFor(args).workerCreations.create(
11647
- positiveId(resolveProjectId(args)),
11648
- {
11649
- templateId: args.template,
11650
- name,
11651
- agentKind,
11652
- ...args.repoPath ? { repoPath: args.repoPath } : {}
11653
- }
11654
- );
10808
+ const client = clientFor(args);
10809
+ const projectId = positiveId2(resolveProjectId(args));
10810
+ const host2 = args.host ? findHost(await client.workerCreations.hosts(projectId), args.host) : null;
10811
+ const creation = await client.workerCreations.create(projectId, {
10812
+ templateId: args.template,
10813
+ name,
10814
+ agentKind,
10815
+ ...args.repoPath ? { repoPath: args.repoPath } : {},
10816
+ ...host2 ? { hostId: host2.id } : {}
10817
+ });
11655
10818
  console.log(renderOne(creation, fmtWorkerCreation, Boolean(args.json)));
11656
10819
  }
11657
10820
  });
@@ -11662,7 +10825,7 @@ var workerCreationCommand = defineCommand({
11662
10825
  args: { ...projectArg, ...common },
11663
10826
  run: async ({ args }) => {
11664
10827
  const list = await clientFor(args).workerCreations.listByProject(
11665
- positiveId(resolveProjectId(args))
10828
+ positiveId2(resolveProjectId(args))
11666
10829
  );
11667
10830
  console.log(renderList(list, fmtWorkerCreation, Boolean(args.json)));
11668
10831
  }
@@ -11670,7 +10833,7 @@ var workerCreationCommand = defineCommand({
11670
10833
  get: defineCommand({
11671
10834
  args: { ...common, id: { type: "positional", required: true } },
11672
10835
  run: async ({ args }) => {
11673
- const creation = await clientFor(args).workerCreations.get(positiveId(args.id));
10836
+ const creation = await clientFor(args).workerCreations.get(positiveId2(args.id));
11674
10837
  console.log(renderOne(creation, fmtWorkerCreation, Boolean(args.json)));
11675
10838
  }
11676
10839
  })
@@ -11962,24 +11125,21 @@ var workspace = defineCommand({
11962
11125
  var main = defineCommand({
11963
11126
  meta: {
11964
11127
  name: "baton",
11965
- description: "baton \u2014 run a worker daemon; manage projects / requirements / tasks / sessions"
11128
+ description: "baton \u2014 run a worker daemon; manage projects / sessions"
11966
11129
  },
11967
11130
  // Order matters: high-level verbs first so --help shows them on top.
11968
11131
  subCommands: {
11969
11132
  init,
11970
11133
  worker,
11971
11134
  provisioner,
11135
+ host,
11972
11136
  session,
11973
11137
  service,
11974
11138
  artifact,
11975
11139
  loop,
11976
11140
  notify,
11977
- relay,
11978
- channel,
11979
11141
  workspace,
11980
- project,
11981
- requirement,
11982
- task
11142
+ project
11983
11143
  }
11984
11144
  });
11985
11145
  runMain(main);