@lesscap/baton-cli 0.1.0

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 +50 -0
  2. package/dist/baton.mjs +2779 -0
  3. package/package.json +27 -0
package/dist/baton.mjs ADDED
@@ -0,0 +1,2779 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../../node_modules/.pnpm/citty@0.2.2/node_modules/citty/dist/_chunks/libs/scule.mjs
4
+ var NUMBER_CHAR_RE = /\d/;
5
+ var STR_SPLITTERS = [
6
+ "-",
7
+ "_",
8
+ "/",
9
+ "."
10
+ ];
11
+ function isUppercase(char = "") {
12
+ if (NUMBER_CHAR_RE.test(char)) return;
13
+ return char !== char.toLowerCase();
14
+ }
15
+ function splitByCase(str, separators) {
16
+ const splitters = separators ?? STR_SPLITTERS;
17
+ const parts = [];
18
+ if (!str || typeof str !== "string") return parts;
19
+ let buff = "";
20
+ let previousUpper;
21
+ let previousSplitter;
22
+ for (const char of str) {
23
+ const isSplitter = splitters.includes(char);
24
+ if (isSplitter === true) {
25
+ parts.push(buff);
26
+ buff = "";
27
+ previousUpper = void 0;
28
+ continue;
29
+ }
30
+ const isUpper = isUppercase(char);
31
+ if (previousSplitter === false) {
32
+ if (previousUpper === false && isUpper === true) {
33
+ parts.push(buff);
34
+ buff = char;
35
+ previousUpper = isUpper;
36
+ continue;
37
+ }
38
+ if (previousUpper === true && isUpper === false && buff.length > 1) {
39
+ const lastChar = buff.at(-1);
40
+ parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
41
+ buff = lastChar + char;
42
+ previousUpper = isUpper;
43
+ continue;
44
+ }
45
+ }
46
+ buff += char;
47
+ previousUpper = isUpper;
48
+ previousSplitter = isSplitter;
49
+ }
50
+ parts.push(buff);
51
+ return parts;
52
+ }
53
+ function upperFirst(str) {
54
+ return str ? str[0].toUpperCase() + str.slice(1) : "";
55
+ }
56
+ function lowerFirst(str) {
57
+ return str ? str[0].toLowerCase() + str.slice(1) : "";
58
+ }
59
+ function pascalCase(str, opts) {
60
+ return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
61
+ }
62
+ function camelCase(str, opts) {
63
+ return lowerFirst(pascalCase(str || "", opts));
64
+ }
65
+ function kebabCase(str, joiner) {
66
+ return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
67
+ }
68
+ function snakeCase(str) {
69
+ return kebabCase(str || "", "_");
70
+ }
71
+
72
+ // ../../node_modules/.pnpm/citty@0.2.2/node_modules/citty/dist/index.mjs
73
+ import { parseArgs as parseArgs$1 } from "node:util";
74
+ function toArray(val) {
75
+ if (Array.isArray(val)) return val;
76
+ return val === void 0 ? [] : [val];
77
+ }
78
+ function formatLineColumns(lines, linePrefix = "") {
79
+ const maxLength = [];
80
+ for (const line of lines) for (const [i, element] of line.entries()) maxLength[i] = Math.max(maxLength[i] || 0, element.length);
81
+ return lines.map((l) => l.map((c, i) => linePrefix + c[i === 0 ? "padStart" : "padEnd"](maxLength[i])).join(" ")).join("\n");
82
+ }
83
+ function resolveValue(input) {
84
+ return typeof input === "function" ? input() : input;
85
+ }
86
+ var CLIError = class extends Error {
87
+ code;
88
+ constructor(message, code) {
89
+ super(message);
90
+ this.name = "CLIError";
91
+ this.code = code;
92
+ }
93
+ };
94
+ function parseRawArgs(args = [], opts = {}) {
95
+ const booleans = new Set(opts.boolean || []);
96
+ const strings = new Set(opts.string || []);
97
+ const aliasMap = opts.alias || {};
98
+ const defaults = opts.default || {};
99
+ const aliasToMain = /* @__PURE__ */ new Map();
100
+ const mainToAliases = /* @__PURE__ */ new Map();
101
+ for (const [key, value] of Object.entries(aliasMap)) {
102
+ const targets = value;
103
+ for (const target of targets) {
104
+ aliasToMain.set(key, target);
105
+ if (!mainToAliases.has(target)) mainToAliases.set(target, []);
106
+ mainToAliases.get(target).push(key);
107
+ aliasToMain.set(target, key);
108
+ if (!mainToAliases.has(key)) mainToAliases.set(key, []);
109
+ mainToAliases.get(key).push(target);
110
+ }
111
+ }
112
+ const options = {};
113
+ function getType(name) {
114
+ if (booleans.has(name)) return "boolean";
115
+ const aliases = mainToAliases.get(name) || [];
116
+ for (const alias of aliases) if (booleans.has(alias)) return "boolean";
117
+ return "string";
118
+ }
119
+ function isStringType(name) {
120
+ if (strings.has(name)) return true;
121
+ const aliases = mainToAliases.get(name) || [];
122
+ for (const alias of aliases) if (strings.has(alias)) return true;
123
+ return false;
124
+ }
125
+ const allOptions = /* @__PURE__ */ new Set([
126
+ ...booleans,
127
+ ...strings,
128
+ ...Object.keys(aliasMap),
129
+ ...Object.values(aliasMap).flat(),
130
+ ...Object.keys(defaults)
131
+ ]);
132
+ for (const name of allOptions) if (!options[name]) options[name] = {
133
+ type: getType(name),
134
+ default: defaults[name]
135
+ };
136
+ for (const [alias, main2] of aliasToMain.entries()) if (alias.length === 1 && options[main2] && !options[main2].short) options[main2].short = alias;
137
+ const processedArgs = [];
138
+ const negatedFlags = {};
139
+ for (let i = 0; i < args.length; i++) {
140
+ const arg = args[i];
141
+ if (arg === "--") {
142
+ processedArgs.push(...args.slice(i));
143
+ break;
144
+ }
145
+ if (arg.startsWith("--no-")) {
146
+ const flagName = arg.slice(5);
147
+ negatedFlags[flagName] = true;
148
+ continue;
149
+ }
150
+ processedArgs.push(arg);
151
+ }
152
+ let parsed;
153
+ try {
154
+ parsed = parseArgs$1({
155
+ args: processedArgs,
156
+ options: Object.keys(options).length > 0 ? options : void 0,
157
+ allowPositionals: true,
158
+ strict: false
159
+ });
160
+ } catch {
161
+ parsed = {
162
+ values: {},
163
+ positionals: processedArgs
164
+ };
165
+ }
166
+ const out = { _: [] };
167
+ out._ = parsed.positionals;
168
+ for (const [key, value] of Object.entries(parsed.values)) {
169
+ let coerced = value;
170
+ if (getType(key) === "boolean" && typeof value === "string") coerced = value !== "false";
171
+ else if (isStringType(key) && typeof value === "boolean") coerced = "";
172
+ out[key] = coerced;
173
+ }
174
+ for (const [name] of Object.entries(negatedFlags)) {
175
+ out[name] = false;
176
+ const mainName = aliasToMain.get(name);
177
+ if (mainName) out[mainName] = false;
178
+ const aliases = mainToAliases.get(name);
179
+ if (aliases) for (const alias of aliases) out[alias] = false;
180
+ }
181
+ for (const [alias, main2] of aliasToMain.entries()) {
182
+ if (out[alias] !== void 0 && out[main2] === void 0) out[main2] = out[alias];
183
+ if (out[main2] !== void 0 && out[alias] === void 0) out[alias] = out[main2];
184
+ if (out[alias] !== out[main2] && defaults[main2] === out[main2]) out[main2] = out[alias];
185
+ }
186
+ return out;
187
+ }
188
+ var noColor = /* @__PURE__ */ (() => {
189
+ const env = globalThis.process?.env ?? {};
190
+ return env.NO_COLOR === "1" || env.TERM === "dumb" || env.TEST || env.CI;
191
+ })();
192
+ var _c = (c, r = 39) => (t) => noColor ? t : `\x1B[${c}m${t}\x1B[${r}m`;
193
+ var bold = /* @__PURE__ */ _c(1, 22);
194
+ var cyan = /* @__PURE__ */ _c(36);
195
+ var gray = /* @__PURE__ */ _c(90);
196
+ var underline = /* @__PURE__ */ _c(4, 24);
197
+ function parseArgs(rawArgs, argsDef) {
198
+ const parseOptions = {
199
+ boolean: [],
200
+ string: [],
201
+ alias: {},
202
+ default: {}
203
+ };
204
+ const args = resolveArgs(argsDef);
205
+ for (const arg of args) {
206
+ if (arg.type === "positional") continue;
207
+ if (arg.type === "string" || arg.type === "enum") parseOptions.string.push(arg.name);
208
+ else if (arg.type === "boolean") parseOptions.boolean.push(arg.name);
209
+ if (arg.default !== void 0) parseOptions.default[arg.name] = arg.default;
210
+ if (arg.alias) parseOptions.alias[arg.name] = arg.alias;
211
+ const camelName = camelCase(arg.name);
212
+ const kebabName = kebabCase(arg.name);
213
+ if (camelName !== arg.name || kebabName !== arg.name) {
214
+ const existingAliases = toArray(parseOptions.alias[arg.name] || []);
215
+ if (camelName !== arg.name && !existingAliases.includes(camelName)) existingAliases.push(camelName);
216
+ if (kebabName !== arg.name && !existingAliases.includes(kebabName)) existingAliases.push(kebabName);
217
+ if (existingAliases.length > 0) parseOptions.alias[arg.name] = existingAliases;
218
+ }
219
+ }
220
+ const parsed = parseRawArgs(rawArgs, parseOptions);
221
+ const [...positionalArguments] = parsed._;
222
+ const parsedArgsProxy = new Proxy(parsed, { get(target, prop) {
223
+ return target[prop] ?? target[camelCase(prop)] ?? target[kebabCase(prop)];
224
+ } });
225
+ for (const [, arg] of args.entries()) if (arg.type === "positional") {
226
+ const nextPositionalArgument = positionalArguments.shift();
227
+ if (nextPositionalArgument !== void 0) parsedArgsProxy[arg.name] = nextPositionalArgument;
228
+ else if (arg.default === void 0 && arg.required !== false) throw new CLIError(`Missing required positional argument: ${arg.name.toUpperCase()}`, "EARG");
229
+ else parsedArgsProxy[arg.name] = arg.default;
230
+ } else if (arg.type === "enum") {
231
+ const argument = parsedArgsProxy[arg.name];
232
+ const options = arg.options || [];
233
+ if (argument !== void 0 && options.length > 0 && !options.includes(argument)) throw new CLIError(`Invalid value for argument: ${cyan(`--${arg.name}`)} (${cyan(argument)}). Expected one of: ${options.map((o) => cyan(o)).join(", ")}.`, "EARG");
234
+ } else if (arg.required && parsedArgsProxy[arg.name] === void 0) throw new CLIError(`Missing required argument: --${arg.name}`, "EARG");
235
+ return parsedArgsProxy;
236
+ }
237
+ function resolveArgs(argsDef) {
238
+ const args = [];
239
+ for (const [name, argDef] of Object.entries(argsDef || {})) args.push({
240
+ ...argDef,
241
+ name,
242
+ alias: toArray(argDef.alias)
243
+ });
244
+ return args;
245
+ }
246
+ async function resolvePlugins(plugins) {
247
+ return Promise.all(plugins.map((p) => resolveValue(p)));
248
+ }
249
+ function defineCommand(def) {
250
+ return def;
251
+ }
252
+ async function runCommand(cmd, opts) {
253
+ const cmdArgs = await resolveValue(cmd.args || {});
254
+ const parsedArgs = parseArgs(opts.rawArgs, cmdArgs);
255
+ const context = {
256
+ rawArgs: opts.rawArgs,
257
+ args: parsedArgs,
258
+ data: opts.data,
259
+ cmd
260
+ };
261
+ const plugins = await resolvePlugins(cmd.plugins ?? []);
262
+ let result;
263
+ let runError;
264
+ try {
265
+ for (const plugin of plugins) await plugin.setup?.(context);
266
+ if (typeof cmd.setup === "function") await cmd.setup(context);
267
+ const subCommands = await resolveValue(cmd.subCommands);
268
+ if (subCommands && Object.keys(subCommands).length > 0) {
269
+ const subCommandArgIndex = findSubCommandIndex(opts.rawArgs, cmdArgs);
270
+ const explicitName = opts.rawArgs[subCommandArgIndex];
271
+ if (explicitName) {
272
+ const subCommand = await _findSubCommand(subCommands, explicitName);
273
+ if (!subCommand) throw new CLIError(`Unknown command ${cyan(explicitName)}`, "E_UNKNOWN_COMMAND");
274
+ await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
275
+ } else {
276
+ const defaultSubCommand = await resolveValue(cmd.default);
277
+ if (defaultSubCommand) {
278
+ if (cmd.run) throw new CLIError(`Cannot specify both 'run' and 'default' on the same command.`, "E_DEFAULT_CONFLICT");
279
+ const subCommand = await _findSubCommand(subCommands, defaultSubCommand);
280
+ if (!subCommand) throw new CLIError(`Default sub command ${cyan(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND");
281
+ await runCommand(subCommand, { rawArgs: opts.rawArgs });
282
+ } else if (!cmd.run) throw new CLIError(`No command specified.`, "E_NO_COMMAND");
283
+ }
284
+ }
285
+ if (typeof cmd.run === "function") result = await cmd.run(context);
286
+ } catch (error) {
287
+ runError = error;
288
+ }
289
+ const cleanupErrors = [];
290
+ if (typeof cmd.cleanup === "function") try {
291
+ await cmd.cleanup(context);
292
+ } catch (error) {
293
+ cleanupErrors.push(error);
294
+ }
295
+ for (const plugin of [...plugins].reverse()) try {
296
+ await plugin.cleanup?.(context);
297
+ } catch (error) {
298
+ cleanupErrors.push(error);
299
+ }
300
+ if (runError) throw runError;
301
+ if (cleanupErrors.length === 1) throw cleanupErrors[0];
302
+ if (cleanupErrors.length > 1) throw new Error("Multiple cleanup errors", { cause: cleanupErrors });
303
+ return { result };
304
+ }
305
+ async function resolveSubCommand(cmd, rawArgs, parent) {
306
+ const subCommands = await resolveValue(cmd.subCommands);
307
+ if (subCommands && Object.keys(subCommands).length > 0) {
308
+ const subCommandArgIndex = findSubCommandIndex(rawArgs, await resolveValue(cmd.args || {}));
309
+ const subCommandName = rawArgs[subCommandArgIndex];
310
+ const subCommand = await _findSubCommand(subCommands, subCommandName);
311
+ if (subCommand) return resolveSubCommand(subCommand, rawArgs.slice(subCommandArgIndex + 1), cmd);
312
+ }
313
+ return [cmd, parent];
314
+ }
315
+ async function _findSubCommand(subCommands, name) {
316
+ if (name in subCommands) return resolveValue(subCommands[name]);
317
+ for (const sub of Object.values(subCommands)) {
318
+ const resolved = await resolveValue(sub);
319
+ const meta = await resolveValue(resolved?.meta);
320
+ if (meta?.alias) {
321
+ if (toArray(meta.alias).includes(name)) return resolved;
322
+ }
323
+ }
324
+ }
325
+ function findSubCommandIndex(rawArgs, argsDef) {
326
+ for (let i = 0; i < rawArgs.length; i++) {
327
+ const arg = rawArgs[i];
328
+ if (arg === "--") return -1;
329
+ if (arg.startsWith("-")) {
330
+ if (!arg.includes("=") && _isValueFlag(arg, argsDef)) i++;
331
+ continue;
332
+ }
333
+ return i;
334
+ }
335
+ return -1;
336
+ }
337
+ function _isValueFlag(flag, argsDef) {
338
+ const name = flag.replace(/^-{1,2}/, "");
339
+ const normalized = camelCase(name);
340
+ for (const [key, def] of Object.entries(argsDef)) {
341
+ if (def.type !== "string" && def.type !== "enum") continue;
342
+ if (normalized === camelCase(key)) return true;
343
+ if ((Array.isArray(def.alias) ? def.alias : def.alias ? [def.alias] : []).includes(name)) return true;
344
+ }
345
+ return false;
346
+ }
347
+ async function showUsage(cmd, parent) {
348
+ try {
349
+ console.log(await renderUsage(cmd, parent) + "\n");
350
+ } catch (error) {
351
+ console.error(error);
352
+ }
353
+ }
354
+ var negativePrefixRe = /^no[-A-Z]/;
355
+ async function renderUsage(cmd, parent) {
356
+ const cmdMeta = await resolveValue(cmd.meta || {});
357
+ const cmdArgs = resolveArgs(await resolveValue(cmd.args || {}));
358
+ const parentMeta = await resolveValue(parent?.meta || {});
359
+ const commandName = `${parentMeta.name ? `${parentMeta.name} ` : ""}` + (cmdMeta.name || process.argv[1]);
360
+ const argLines = [];
361
+ const posLines = [];
362
+ const commandsLines = [];
363
+ const usageLine = [];
364
+ for (const arg of cmdArgs) if (arg.type === "positional") {
365
+ const name = arg.name.toUpperCase();
366
+ const isRequired = arg.required !== false && arg.default === void 0;
367
+ posLines.push([cyan(name + renderValueHint(arg)), renderDescription(arg, isRequired)]);
368
+ usageLine.push(isRequired ? `<${name}>` : `[${name}]`);
369
+ } else {
370
+ const isRequired = arg.required === true && arg.default === void 0;
371
+ const argStr = [...(arg.alias || []).map((a) => `-${a}`), `--${arg.name}`].join(", ") + renderValueHint(arg);
372
+ argLines.push([cyan(argStr), renderDescription(arg, isRequired)]);
373
+ if (arg.type === "boolean" && (arg.default === true || arg.negativeDescription) && !negativePrefixRe.test(arg.name)) {
374
+ const negativeArgStr = [...(arg.alias || []).map((a) => `--no-${a}`), `--no-${arg.name}`].join(", ");
375
+ argLines.push([cyan(negativeArgStr), [arg.negativeDescription, isRequired ? gray("(Required)") : ""].filter(Boolean).join(" ")]);
376
+ }
377
+ if (isRequired) usageLine.push(`--${arg.name}` + renderValueHint(arg));
378
+ }
379
+ if (cmd.subCommands) {
380
+ const commandNames = [];
381
+ const subCommands = await resolveValue(cmd.subCommands);
382
+ for (const [name, sub] of Object.entries(subCommands)) {
383
+ const meta = await resolveValue((await resolveValue(sub))?.meta);
384
+ if (meta?.hidden) continue;
385
+ const aliases = toArray(meta?.alias);
386
+ const label = [name, ...aliases].join(", ");
387
+ commandsLines.push([cyan(label), meta?.description || ""]);
388
+ commandNames.push(name, ...aliases);
389
+ }
390
+ usageLine.push(commandNames.join("|"));
391
+ }
392
+ const usageLines = [];
393
+ const version = cmdMeta.version || parentMeta.version;
394
+ usageLines.push(gray(`${cmdMeta.description} (${commandName + (version ? ` v${version}` : "")})`), "");
395
+ const hasOptions = argLines.length > 0 || posLines.length > 0;
396
+ usageLines.push(`${underline(bold("USAGE"))} ${cyan(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, "");
397
+ if (posLines.length > 0) {
398
+ usageLines.push(underline(bold("ARGUMENTS")), "");
399
+ usageLines.push(formatLineColumns(posLines, " "));
400
+ usageLines.push("");
401
+ }
402
+ if (argLines.length > 0) {
403
+ usageLines.push(underline(bold("OPTIONS")), "");
404
+ usageLines.push(formatLineColumns(argLines, " "));
405
+ usageLines.push("");
406
+ }
407
+ if (commandsLines.length > 0) {
408
+ usageLines.push(underline(bold("COMMANDS")), "");
409
+ usageLines.push(formatLineColumns(commandsLines, " "));
410
+ usageLines.push("", `Use ${cyan(`${commandName} <command> --help`)} for more information about a command.`);
411
+ }
412
+ return usageLines.filter((l) => typeof l === "string").join("\n");
413
+ }
414
+ function renderValueHint(arg) {
415
+ const valueHint = arg.valueHint ? `=<${arg.valueHint}>` : "";
416
+ const fallbackValueHint = valueHint || `=<${snakeCase(arg.name)}>`;
417
+ if (!arg.type || arg.type === "positional" || arg.type === "boolean") return valueHint;
418
+ if (arg.type === "enum" && arg.options?.length) return `=<${arg.options.join("|")}>`;
419
+ return fallbackValueHint;
420
+ }
421
+ function renderDescription(arg, required) {
422
+ const requiredHint = required ? gray("(Required)") : "";
423
+ const defaultHint = arg.default === void 0 ? "" : gray(`(Default: ${arg.default})`);
424
+ return [
425
+ arg.description,
426
+ requiredHint,
427
+ defaultHint
428
+ ].filter(Boolean).join(" ");
429
+ }
430
+ async function runMain(cmd, opts = {}) {
431
+ const rawArgs = opts.rawArgs || process.argv.slice(2);
432
+ const showUsage$1 = opts.showUsage || showUsage;
433
+ try {
434
+ const builtinFlags = await _resolveBuiltinFlags(cmd);
435
+ if (builtinFlags.help.length > 0 && rawArgs.some((arg) => builtinFlags.help.includes(arg))) {
436
+ await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
437
+ process.exit(0);
438
+ } else if (rawArgs.length === 1 && builtinFlags.version.includes(rawArgs[0])) {
439
+ const meta = typeof cmd.meta === "function" ? await cmd.meta() : await cmd.meta;
440
+ if (!meta?.version) throw new CLIError("No version specified", "E_NO_VERSION");
441
+ console.log(meta.version);
442
+ } else await runCommand(cmd, { rawArgs });
443
+ } catch (error) {
444
+ if (error instanceof CLIError) {
445
+ await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
446
+ console.error(error.message);
447
+ } else console.error(error, "\n");
448
+ process.exit(1);
449
+ }
450
+ }
451
+ async function _resolveBuiltinFlags(cmd) {
452
+ const argsDef = await resolveValue(cmd.args || {});
453
+ const userNames = /* @__PURE__ */ new Set();
454
+ const userAliases = /* @__PURE__ */ new Set();
455
+ for (const [name, def] of Object.entries(argsDef)) {
456
+ userNames.add(name);
457
+ for (const alias of toArray(def.alias)) userAliases.add(alias);
458
+ }
459
+ return {
460
+ help: _getBuiltinFlags("help", "h", userNames, userAliases),
461
+ version: _getBuiltinFlags("version", "v", userNames, userAliases)
462
+ };
463
+ }
464
+ function _getBuiltinFlags(long, short, userNames, userAliases) {
465
+ if (userNames.has(long) || userAliases.has(long)) return [];
466
+ if (userNames.has(short) || userAliases.has(short)) return [`--${long}`];
467
+ return [`--${long}`, `-${short}`];
468
+ }
469
+
470
+ // src/commands/init.ts
471
+ import { existsSync as existsSync2 } from "node:fs";
472
+ import * as readline from "node:readline/promises";
473
+
474
+ // src/client/request.ts
475
+ var authHeaders = {};
476
+ var loginGate = null;
477
+ var setAuthHeaders = (h) => {
478
+ authHeaders = h;
479
+ };
480
+ var setLoginGate = (p) => {
481
+ loginGate = p;
482
+ };
483
+ var request = async (url, init2) => {
484
+ if (loginGate) await loginGate;
485
+ const baseHeaders = init2.body !== void 0 ? { "content-type": "application/json" } : {};
486
+ const res = await fetch(url, {
487
+ method: init2.method,
488
+ headers: { ...baseHeaders, ...authHeaders, ...init2.headers ?? {} },
489
+ ...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
490
+ });
491
+ if (!res.ok) throw new Error(`${init2.method} ${url} \u2192 ${res.status}: ${await res.text()}`);
492
+ if (res.status === 204) return void 0;
493
+ return await res.json();
494
+ };
495
+
496
+ // src/client/auth.ts
497
+ var primed = false;
498
+ var primeLogin = (baseUrl) => {
499
+ if (primed) return;
500
+ primed = true;
501
+ const username = process.env.BATON_USER;
502
+ const password = process.env.BATON_PASS;
503
+ if (!username || !password) return;
504
+ setLoginGate(
505
+ (async () => {
506
+ const res = await fetch(`${baseUrl}/auth/login`, {
507
+ method: "POST",
508
+ headers: { "content-type": "application/json" },
509
+ body: JSON.stringify({ username, password })
510
+ });
511
+ const cookie = (res.headers.get("set-cookie") ?? "").split(";")[0];
512
+ if (res.ok && cookie) setAuthHeaders({ cookie });
513
+ })().catch(() => {
514
+ })
515
+ );
516
+ };
517
+
518
+ // src/client/projects.ts
519
+ var projectClient = (baseUrl) => {
520
+ const u = (p) => `${baseUrl}${p}`;
521
+ return {
522
+ create: (input) => request(u("/projects"), { method: "POST", body: input }),
523
+ listByWorkspace: (workspaceId) => request(u(`/workspaces/${workspaceId}/projects`), { method: "GET" }),
524
+ get: (id) => request(u(`/projects/${id}`), { method: "GET" }),
525
+ update: (id, patch2) => request(u(`/projects/${id}`), { method: "PATCH", body: patch2 }),
526
+ remove: (id) => request(u(`/projects/${id}`), { method: "DELETE" })
527
+ };
528
+ };
529
+
530
+ // src/client/items.ts
531
+ var fetchItemByCode = async (baseUrl, projectId, code, expectKind) => {
532
+ const r = await request(
533
+ `${baseUrl}/projects/${projectId}/items/${encodeURIComponent(code)}`,
534
+ { method: "GET" }
535
+ );
536
+ if (r.kind !== expectKind) throw new Error(`expected ${expectKind} but got ${r.kind} for ${code}`);
537
+ return r.item;
538
+ };
539
+
540
+ // src/client/requirements.ts
541
+ var requirementClient = (baseUrl) => {
542
+ const u = (p) => `${baseUrl}${p}`;
543
+ return {
544
+ create: (input) => request(u("/requirements"), { method: "POST", body: input }),
545
+ listByProject: (projectId) => request(u(`/projects/${projectId}/requirements`), { method: "GET" }),
546
+ get: (id) => request(u(`/requirements/${id}`), { method: "GET" }),
547
+ getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "requirement"),
548
+ setStatus: (id, status) => request(u(`/requirements/${id}`), { method: "PATCH", body: { status } }),
549
+ remove: (id) => request(u(`/requirements/${id}`), { method: "DELETE" })
550
+ };
551
+ };
552
+
553
+ // src/client/sessions.ts
554
+ var sessionsClient = (baseUrl) => {
555
+ const u = (p) => `${baseUrl}${p}`;
556
+ return {
557
+ create: (input) => request(u("/sessions"), { method: "POST", body: input }),
558
+ materialize: (id, input, workerToken) => request(u(`/sessions/${id}`), {
559
+ method: "PATCH",
560
+ body: input,
561
+ headers: { authorization: `Bearer ${workerToken}` }
562
+ }),
563
+ setStatus: (id, active, workerToken) => request(u(`/sessions/${id}/status`), {
564
+ method: "POST",
565
+ body: { active },
566
+ headers: { authorization: `Bearer ${workerToken}` }
567
+ }),
568
+ setName: (id, name, workerToken) => request(u(`/sessions/${id}`), {
569
+ method: "PATCH",
570
+ body: { name },
571
+ headers: { authorization: `Bearer ${workerToken}` }
572
+ }),
573
+ resume: (id) => request(u(`/sessions/${id}/resume`), { method: "POST" }),
574
+ stop: (id) => request(u(`/sessions/${id}/stop`), { method: "POST" }),
575
+ rename: (id, name) => request(u(`/sessions/${id}/rename`), { method: "POST", body: { name } }),
576
+ listByProject: (projectId) => request(u(`/projects/${projectId}/sessions`), { method: "GET" }),
577
+ get: (id) => request(u(`/sessions/${id}`), { method: "GET" }),
578
+ findByName: async (projectId, name) => {
579
+ const all = await request(u(`/projects/${projectId}/sessions`), { method: "GET" });
580
+ const matches = all.filter((s) => s.name === name);
581
+ return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
582
+ },
583
+ sendMessage: (id, text, attachments) => request(u(`/sessions/${id}/messages`), {
584
+ method: "POST",
585
+ body: attachments && attachments.length > 0 ? { text, attachments } : { text }
586
+ }),
587
+ // Raw-body upload: the file streams as the request body (no multipart), so
588
+ // a Blob backed by a file on disk uploads without buffering. filename rides
589
+ // a query param; content-type carries the media type.
590
+ uploadAttachment: async (id, input) => {
591
+ const url = u(`/sessions/${id}/attachments?filename=${encodeURIComponent(input.filename)}`);
592
+ const res = await fetch(url, {
593
+ method: "POST",
594
+ body: input.body,
595
+ headers: { "content-type": input.contentType }
596
+ });
597
+ if (!res.ok) throw new Error(`POST ${url} \u2192 ${res.status}: ${await res.text()}`);
598
+ return await res.json();
599
+ },
600
+ destroy: async (id) => {
601
+ await request(u(`/sessions/${id}`), { method: "DELETE" });
602
+ }
603
+ };
604
+ };
605
+
606
+ // src/client/tasks.ts
607
+ var taskClient = (baseUrl) => {
608
+ const u = (p) => `${baseUrl}${p}`;
609
+ return {
610
+ create: (input) => request(u("/tasks"), { method: "POST", body: input }),
611
+ listByRequirement: (requirementId) => request(u(`/requirements/${requirementId}/tasks`), { method: "GET" }),
612
+ get: (id) => request(u(`/tasks/${id}`), { method: "GET" }),
613
+ getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "task"),
614
+ setStatus: (id, status) => request(u(`/tasks/${id}`), { method: "PATCH", body: { status } }),
615
+ remove: (id) => request(u(`/tasks/${id}`), { method: "DELETE" }),
616
+ listComments: (id) => request(u(`/tasks/${id}/comments`), { method: "GET" }),
617
+ addComment: (id, body, workerId) => request(u(`/tasks/${id}/comments`), { method: "POST", body: { body, workerId } })
618
+ };
619
+ };
620
+
621
+ // src/client/workers.ts
622
+ var workersClient = (baseUrl) => {
623
+ const u = (p) => `${baseUrl}${p}`;
624
+ return {
625
+ register: (input) => request(u("/workers"), { method: "POST", body: input }),
626
+ listByProject: (projectId) => request(u(`/projects/${projectId}/workers`), { method: "GET" }),
627
+ get: (id) => request(u(`/workers/${id}`), { method: "GET" }),
628
+ findByName: async (projectId, name) => {
629
+ const all = await request(u(`/projects/${projectId}/workers`), {
630
+ method: "GET"
631
+ });
632
+ const matches = all.filter((w) => w.name === name);
633
+ return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
634
+ },
635
+ heartbeat: (machineId) => request(u("/workers/heartbeat"), { method: "POST", body: { machineId } }),
636
+ destroy: async (id) => {
637
+ await request(u(`/workers/${id}`), { method: "DELETE" });
638
+ }
639
+ };
640
+ };
641
+
642
+ // src/client/workspaces.ts
643
+ var workspaceClient = (baseUrl) => {
644
+ const u = (p) => `${baseUrl}${p}`;
645
+ return {
646
+ create: (input) => request(u("/workspaces"), { method: "POST", body: input }),
647
+ list: () => request(u("/workspaces"), { method: "GET" }),
648
+ get: (id) => request(u(`/workspaces/${id}`), { method: "GET" }),
649
+ update: (id, patch2) => request(u(`/workspaces/${id}`), { method: "PATCH", body: patch2 }),
650
+ remove: (id) => request(u(`/workspaces/${id}`), { method: "DELETE" })
651
+ };
652
+ };
653
+
654
+ // src/client.ts
655
+ var createClient = (baseUrl, opts) => {
656
+ if (opts?.bearer) setAuthHeaders({ authorization: `Bearer ${opts.bearer}` });
657
+ else primeLogin(baseUrl);
658
+ return clientFromBase(baseUrl);
659
+ };
660
+ var clientFromBase = (baseUrl) => ({
661
+ workspaces: workspaceClient(baseUrl),
662
+ projects: projectClient(baseUrl),
663
+ requirements: requirementClient(baseUrl),
664
+ tasks: taskClient(baseUrl),
665
+ sessions: sessionsClient(baseUrl),
666
+ workers: workersClient(baseUrl)
667
+ });
668
+ var createWorkerClient = (baseUrl, workerToken, sessionId) => {
669
+ const u = (p) => `${baseUrl}${p}`;
670
+ const auth = { authorization: `Bearer ${workerToken}` };
671
+ return {
672
+ emitEvent: (type, payload) => request(u(`/sessions/${sessionId}/events`), {
673
+ method: "POST",
674
+ body: { type, payload },
675
+ headers: auth
676
+ }),
677
+ setActive: (active) => request(u(`/sessions/${sessionId}/status`), {
678
+ method: "POST",
679
+ body: { active },
680
+ headers: auth
681
+ })
682
+ };
683
+ };
684
+
685
+ // src/project-config.ts
686
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
687
+ import { join } from "node:path";
688
+ var PROJECT_CONFIG_NAME = ".baton.json";
689
+ var projectConfigPath = (cwd = process.cwd()) => join(cwd, PROJECT_CONFIG_NAME);
690
+ var loadProjectConfig = (path) => {
691
+ try {
692
+ return JSON.parse(readFileSync(path, "utf8"));
693
+ } catch (err) {
694
+ const cause = err.code === "ENOENT" ? `not found at ${path}` : String(err);
695
+ throw new Error(`${PROJECT_CONFIG_NAME} ${cause} \u2014 run \`baton init\` first`);
696
+ }
697
+ };
698
+ var loadProjectConfigOrNull = (path) => {
699
+ if (!existsSync(path)) return null;
700
+ try {
701
+ return JSON.parse(readFileSync(path, "utf8"));
702
+ } catch {
703
+ return null;
704
+ }
705
+ };
706
+ var saveProjectConfig = (path, config) => {
707
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}
708
+ `, "utf8");
709
+ };
710
+ var patch = (path, fn) => {
711
+ saveProjectConfig(path, fn(loadProjectConfig(path)));
712
+ };
713
+ var setWorker = (path, w) => patch(path, (cfg) => ({ ...cfg, worker: w }));
714
+ var requireBase = (cfg) => {
715
+ if (!cfg.server || !cfg.project || !cfg.worker)
716
+ throw new Error("project-config missing server/project/worker \u2014 run `baton worker register`");
717
+ return { server: cfg.server, project: cfg.project, worker: cfg.worker };
718
+ };
719
+ var viewWorker = (cfg) => {
720
+ const { server, project: project2, worker: worker2 } = requireBase(cfg);
721
+ return {
722
+ server,
723
+ projectId: project2,
724
+ workerId: worker2.id,
725
+ name: worker2.name,
726
+ machineId: worker2.machineId,
727
+ apiToken: worker2.apiToken
728
+ };
729
+ };
730
+
731
+ // src/config.ts
732
+ var resolveBaseUrl = (urlArg, env = process.env) => {
733
+ if (urlArg) return urlArg;
734
+ const cfg = loadProjectConfigOrNull(projectConfigPath());
735
+ if (cfg?.server) return cfg.server;
736
+ return env.BATON_URL ?? "http://localhost:3280";
737
+ };
738
+
739
+ // src/util.ts
740
+ var common = {
741
+ url: {
742
+ type: "string",
743
+ description: "baton server url (--url > .baton.json > BATON_URL > http://localhost:3280)"
744
+ },
745
+ json: { type: "boolean", description: "output JSON" }
746
+ };
747
+ var clientFor = (args) => createClient(resolveBaseUrl(args.url));
748
+ var resolveProjectId = (args) => {
749
+ if (args.project !== void 0 && args.project !== "") return Number(args.project);
750
+ const cfg = loadProjectConfigOrNull(projectConfigPath());
751
+ if (cfg?.project !== void 0) return cfg.project;
752
+ throw new Error("no project in scope. pass --project <id> or run `baton init` in this directory.");
753
+ };
754
+ var resolveWorkspaceId = (args) => {
755
+ if (args.workspace !== void 0 && args.workspace !== "") return Number(args.workspace);
756
+ const cfg = loadProjectConfigOrNull(projectConfigPath());
757
+ if (cfg?.workspace !== void 0) return cfg.workspace;
758
+ throw new Error(
759
+ "no workspace in scope. pass --workspace <id> or run `baton init` in this directory."
760
+ );
761
+ };
762
+ var splitCsv = (s) => s ? s.split(",").map((x) => x.trim()).filter(Boolean) : void 0;
763
+
764
+ // src/commands/init.ts
765
+ var promptPick = async (label, items) => {
766
+ if (items.length === 0) throw new Error(`no ${label}s found on server`);
767
+ console.log(`${label}s:`);
768
+ items.forEach((it, i) => {
769
+ console.log(` [${i + 1}] #${it.id} ${it.name}`);
770
+ });
771
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
772
+ try {
773
+ const ans = (await rl.question(`pick a ${label} (1-${items.length}): `)).trim();
774
+ const idx = Number(ans) - 1;
775
+ const picked = items[idx];
776
+ if (!picked) throw new Error(`invalid choice: ${ans}`);
777
+ return picked;
778
+ } finally {
779
+ rl.close();
780
+ }
781
+ };
782
+ var init = defineCommand({
783
+ meta: {
784
+ name: "init",
785
+ description: `write ${PROJECT_CONFIG_NAME} in the current directory (local state, gitignored)`
786
+ },
787
+ args: {
788
+ workspace: { type: "string", description: "workspace id (skip prompt)" },
789
+ project: { type: "string", description: "project id (skip prompt)" },
790
+ force: { type: "boolean", description: `overwrite existing ${PROJECT_CONFIG_NAME}` },
791
+ ...common
792
+ },
793
+ run: async ({ args }) => {
794
+ const server = resolveBaseUrl(args.url);
795
+ const cfgPath = projectConfigPath();
796
+ if (existsSync2(cfgPath) && !args.force)
797
+ throw new Error(`${PROJECT_CONFIG_NAME} already exists. use --force to overwrite.`);
798
+ const c = createClient(server);
799
+ let project2;
800
+ let workspace2;
801
+ if (args.project) {
802
+ project2 = await c.projects.get(Number(args.project));
803
+ workspace2 = await c.workspaces.get(project2.workspaceId);
804
+ } else {
805
+ const wsList = await c.workspaces.list();
806
+ workspace2 = args.workspace ? wsList.find((w) => w.id === Number(args.workspace)) ?? (() => {
807
+ throw new Error(`workspace ${args.workspace} not found`);
808
+ })() : await promptPick("workspace", wsList);
809
+ const projects = await c.projects.listByWorkspace(workspace2.id);
810
+ project2 = await promptPick("project", projects);
811
+ }
812
+ const cfg = {
813
+ server,
814
+ workspace: workspace2.id,
815
+ project: project2.id,
816
+ name: project2.name
817
+ };
818
+ saveProjectConfig(cfgPath, cfg);
819
+ console.log(`wrote ${cfgPath}`);
820
+ console.log(` server: ${server}`);
821
+ console.log(` workspace: #${workspace2.id} ${workspace2.name}`);
822
+ console.log(` project: #${project2.id} ${project2.name}`);
823
+ }
824
+ });
825
+
826
+ // src/output.ts
827
+ var toJson = (data) => JSON.stringify(data, null, 2);
828
+ var fmtWorkspace = (w) => `${w.id} ${w.name}`;
829
+ var fmtProject = (p) => `${p.id} ${p.name}`;
830
+ var fmtRequirement = (r) => `${r.code} [${r.status}] ${r.title}`;
831
+ var fmtTask = (t) => `${t.code} [${t.status}] ${t.title}`;
832
+ var fmtComment = (c) => `${c.workerId !== void 0 ? `worker#${c.workerId}` : "you"} ${c.body}`;
833
+ var fmtSession = (s) => `${s.id} ${s.name} ${s.attached ? "[active]" : "[inactive]"} ${s.worktreePath ?? "(pending)"}`;
834
+ var fmtWorker = (w) => {
835
+ const offline = w.alive === false ? " [offline]" : "";
836
+ return `${w.id} ${w.name} ${w.hostname}${offline}`;
837
+ };
838
+ var renderOne = (item, fmt, json) => json ? toJson(item) : fmt(item);
839
+ var renderList = (items, fmt, json) => {
840
+ if (json) return toJson(items);
841
+ return items.length ? items.map(fmt).join("\n") : "(none)";
842
+ };
843
+ var removed = (kind, id, json) => json ? toJson({ ok: true, deleted: id }) : `deleted ${kind} ${id}`;
844
+
845
+ // src/commands/project.ts
846
+ var createProject = (c, input, json) => c.projects.create(input).then((p) => renderOne(p, fmtProject, json));
847
+ var listProjects = (c, workspaceId, json) => c.projects.listByWorkspace(workspaceId).then((ps) => renderList(ps, fmtProject, json));
848
+ var getProject = (c, id, json) => c.projects.get(id).then((p) => renderOne(p, fmtProject, json));
849
+ var updateProject = (c, id, patch2, json) => c.projects.update(id, patch2).then((p) => renderOne(p, fmtProject, json));
850
+ var removeProject = (c, id, json) => c.projects.remove(id).then(() => removed("project", id, json));
851
+ var project = defineCommand({
852
+ meta: { name: "project", description: "manage projects" },
853
+ subCommands: {
854
+ create: defineCommand({
855
+ meta: { name: "create", description: "create a project" },
856
+ args: {
857
+ name: { type: "positional", required: true },
858
+ workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
859
+ desc: { type: "string", description: "description" },
860
+ ...common
861
+ },
862
+ run: async ({ args }) => {
863
+ const out = await createProject(
864
+ clientFor(args),
865
+ { workspaceId: resolveWorkspaceId(args), name: args.name, description: args.desc },
866
+ Boolean(args.json)
867
+ );
868
+ console.log(out);
869
+ }
870
+ }),
871
+ ls: defineCommand({
872
+ meta: { name: "ls", description: "list projects in a workspace" },
873
+ args: {
874
+ workspace: { type: "string", description: "workspace id (overrides .baton.json)" },
875
+ ...common
876
+ },
877
+ run: async ({ args }) => {
878
+ console.log(
879
+ await listProjects(clientFor(args), resolveWorkspaceId(args), Boolean(args.json))
880
+ );
881
+ }
882
+ }),
883
+ get: defineCommand({
884
+ meta: { name: "get", description: "get a project" },
885
+ args: { id: { type: "positional", required: true }, ...common },
886
+ run: async ({ args }) => {
887
+ console.log(await getProject(clientFor(args), Number(args.id), Boolean(args.json)));
888
+ }
889
+ }),
890
+ update: defineCommand({
891
+ meta: { name: "update", description: "rename a project / set its description" },
892
+ args: {
893
+ id: { type: "positional", required: true },
894
+ name: { type: "string", description: "new name" },
895
+ desc: { type: "string", description: "new description" },
896
+ ...common
897
+ },
898
+ run: async ({ args }) => {
899
+ const patch2 = {
900
+ ...args.name !== void 0 ? { name: args.name } : {},
901
+ ...args.desc !== void 0 ? { description: args.desc } : {}
902
+ };
903
+ if (Object.keys(patch2).length === 0) throw new Error("pass --name and/or --desc");
904
+ console.log(
905
+ await updateProject(clientFor(args), Number(args.id), patch2, Boolean(args.json))
906
+ );
907
+ }
908
+ }),
909
+ rm: defineCommand({
910
+ meta: { name: "rm", description: "delete a project" },
911
+ args: { id: { type: "positional", required: true }, ...common },
912
+ run: async ({ args }) => {
913
+ console.log(await removeProject(clientFor(args), Number(args.id), Boolean(args.json)));
914
+ }
915
+ })
916
+ }
917
+ });
918
+
919
+ // src/commands/requirement.ts
920
+ var createRequirement = (c, input, json) => c.requirements.create(input).then((r) => renderOne(r, fmtRequirement, json));
921
+ var listRequirements = (c, projectId, json) => c.requirements.listByProject(projectId).then((rs) => renderList(rs, fmtRequirement, json));
922
+ var getRequirement = (c, id, json) => c.requirements.get(id).then((r) => renderOne(r, fmtRequirement, json));
923
+ var setRequirementStatus = (c, id, status, json) => c.requirements.setStatus(id, status).then((r) => renderOne(r, fmtRequirement, json));
924
+ var removeRequirement = (c, id, label, json) => c.requirements.remove(id).then(() => removed("requirement", label, json));
925
+ var resolveByCode = async (c, projectId, code) => (await c.requirements.getByCode(projectId, code)).id;
926
+ var requirement = defineCommand({
927
+ meta: { name: "requirement", description: "manage requirements" },
928
+ subCommands: {
929
+ create: defineCommand({
930
+ meta: { name: "create", description: "create a requirement" },
931
+ args: {
932
+ title: { type: "positional", required: true },
933
+ project: { type: "string", description: "project id (overrides .baton.json)" },
934
+ desc: { type: "string", description: "description" },
935
+ body: { type: "string", description: "requirement body (markdown)" },
936
+ ...common
937
+ },
938
+ run: async ({ args }) => {
939
+ console.log(
940
+ await createRequirement(
941
+ clientFor(args),
942
+ {
943
+ projectId: resolveProjectId(args),
944
+ title: args.title,
945
+ description: args.desc,
946
+ body: args.body
947
+ },
948
+ Boolean(args.json)
949
+ )
950
+ );
951
+ }
952
+ }),
953
+ ls: defineCommand({
954
+ meta: { name: "ls", description: "list requirements in a project" },
955
+ args: {
956
+ project: { type: "string", description: "project id (overrides .baton.json)" },
957
+ ...common
958
+ },
959
+ run: async ({ args }) => {
960
+ console.log(
961
+ await listRequirements(clientFor(args), resolveProjectId(args), Boolean(args.json))
962
+ );
963
+ }
964
+ }),
965
+ get: defineCommand({
966
+ meta: { name: "get", description: "get a requirement by code (R-N)" },
967
+ args: {
968
+ code: { type: "positional", required: true, description: "requirement code, e.g. R-1" },
969
+ project: { type: "string", description: "project id (overrides .baton.json)" },
970
+ ...common
971
+ },
972
+ run: async ({ args }) => {
973
+ const c = clientFor(args);
974
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
975
+ console.log(await getRequirement(c, id, Boolean(args.json)));
976
+ }
977
+ }),
978
+ "set-status": defineCommand({
979
+ meta: { name: "set-status", description: "set requirement status (active|done|cancelled)" },
980
+ args: {
981
+ code: { type: "positional", required: true, description: "requirement code (R-N)" },
982
+ status: { type: "positional", required: true },
983
+ project: { type: "string", description: "project id (overrides .baton.json)" },
984
+ ...common
985
+ },
986
+ run: async ({ args }) => {
987
+ const c = clientFor(args);
988
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
989
+ console.log(
990
+ await setRequirementStatus(c, id, args.status, Boolean(args.json))
991
+ );
992
+ }
993
+ }),
994
+ rm: defineCommand({
995
+ meta: { name: "rm", description: "delete a requirement by code" },
996
+ args: {
997
+ code: { type: "positional", required: true },
998
+ project: { type: "string", description: "project id (overrides .baton.json)" },
999
+ ...common
1000
+ },
1001
+ run: async ({ args }) => {
1002
+ const c = clientFor(args);
1003
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
1004
+ console.log(await removeRequirement(c, id, args.code, Boolean(args.json)));
1005
+ }
1006
+ })
1007
+ }
1008
+ });
1009
+
1010
+ // src/commands/session/create.ts
1011
+ var sessionCreateCommand = defineCommand({
1012
+ meta: { name: "create", description: "create + start a session on a worker" },
1013
+ args: {
1014
+ name: { type: "positional", required: true, description: "session name" },
1015
+ worker: { type: "string", description: "worker id (default: this machine's worker)" },
1016
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1017
+ ...common
1018
+ },
1019
+ run: async ({ args }) => {
1020
+ const c = clientFor(args);
1021
+ const projectId = resolveProjectId(args);
1022
+ const workerId = args.worker ? Number(args.worker) : loadProjectConfigOrNull(projectConfigPath())?.worker?.id;
1023
+ if (!workerId)
1024
+ throw new Error("no worker id \u2014 pass --worker or run `baton worker register` first");
1025
+ const s = await c.sessions.create({ projectId, workerId, name: args.name });
1026
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1027
+ }
1028
+ });
1029
+
1030
+ // src/commands/session/shared.ts
1031
+ import { homedir } from "node:os";
1032
+ import { join as join2 } from "node:path";
1033
+ var defaultWorktreeDir = (env = process.env) => env.BATON_WORKTREE_DIR ?? join2(env.XDG_DATA_HOME ?? join2(env.HOME ?? homedir(), ".local/share"), "baton", "worktrees");
1034
+ var slug = (s) => s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
1035
+ var resolveSession = async (client, projectId, handle) => {
1036
+ const asInt = Number(handle);
1037
+ if (Number.isInteger(asInt) && asInt > 0) {
1038
+ const byId = await client.sessions.get(asInt).catch(() => null);
1039
+ if (byId) return { id: byId.id, name: byId.name };
1040
+ }
1041
+ const byName = await client.sessions.findByName(projectId, handle);
1042
+ if (byName) return { id: byName.id, name: byName.name };
1043
+ throw new Error(`session "${handle}" not found in project ${projectId}`);
1044
+ };
1045
+
1046
+ // src/commands/session/get.ts
1047
+ var sessionGetCommand = defineCommand({
1048
+ meta: { name: "get", description: "get a session by int id or name" },
1049
+ args: {
1050
+ session: { type: "positional", required: true, description: "session int id or name" },
1051
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1052
+ ...common
1053
+ },
1054
+ run: async ({ args }) => {
1055
+ const c = clientFor(args);
1056
+ const projectId = resolveProjectId(args);
1057
+ const handle = await resolveSession(c, projectId, args.session);
1058
+ const s = await c.sessions.get(handle.id);
1059
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1060
+ }
1061
+ });
1062
+
1063
+ // src/commands/session/ls.ts
1064
+ var sessionLsCommand = defineCommand({
1065
+ meta: { name: "ls", description: "list sessions in a project" },
1066
+ args: {
1067
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1068
+ ...common
1069
+ },
1070
+ run: async ({ args }) => {
1071
+ const c = clientFor(args);
1072
+ const ss = await c.sessions.listByProject(resolveProjectId(args));
1073
+ console.log(renderList(ss, fmtSession, Boolean(args.json)));
1074
+ }
1075
+ });
1076
+
1077
+ // src/commands/session/rename.ts
1078
+ var sessionRenameCommand = defineCommand({
1079
+ meta: { name: "rename", description: "rename a session (locks the name against auto-title)" },
1080
+ args: {
1081
+ session: { type: "positional", required: true, description: "session int id or name" },
1082
+ name: { type: "positional", required: true, description: "new name" },
1083
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1084
+ ...common
1085
+ },
1086
+ run: async ({ args }) => {
1087
+ const c = clientFor(args);
1088
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
1089
+ const s = await c.sessions.rename(handle.id, args.name);
1090
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1091
+ }
1092
+ });
1093
+
1094
+ // src/commands/session/resume.ts
1095
+ var sessionResumeCommand = defineCommand({
1096
+ meta: { name: "resume", description: "resume (re-start) an existing session" },
1097
+ args: {
1098
+ session: { type: "positional", required: true, description: "session int id or name" },
1099
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1100
+ ...common
1101
+ },
1102
+ run: async ({ args }) => {
1103
+ const c = clientFor(args);
1104
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
1105
+ const s = await c.sessions.resume(handle.id);
1106
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1107
+ }
1108
+ });
1109
+
1110
+ // src/commands/session/rm.ts
1111
+ var sessionRmCommand = defineCommand({
1112
+ meta: { name: "rm", description: "delete a session (irreversible; removes its worktree)" },
1113
+ args: {
1114
+ session: { type: "positional", required: true, description: "session int id or name" },
1115
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1116
+ confirm: { type: "boolean", description: "actually perform the deletion" },
1117
+ ...common
1118
+ },
1119
+ run: async ({ args }) => {
1120
+ const c = clientFor(args);
1121
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
1122
+ if (!args.confirm) {
1123
+ console.log(`[dry-run] would delete session ${handle.name} (#${handle.id}) + its worktree`);
1124
+ console.log("re-run with --confirm to proceed.");
1125
+ return;
1126
+ }
1127
+ await c.sessions.destroy(handle.id);
1128
+ console.log(`deleted session ${handle.name} (#${handle.id})`);
1129
+ }
1130
+ });
1131
+
1132
+ // ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js
1133
+ var ParseError = class extends Error {
1134
+ constructor(message, options) {
1135
+ super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
1136
+ }
1137
+ };
1138
+ var LF = 10;
1139
+ var CR = 13;
1140
+ var SPACE = 32;
1141
+ function noop(_arg) {
1142
+ }
1143
+ function createParser(config) {
1144
+ if (typeof config == "function")
1145
+ throw new TypeError(
1146
+ "`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
1147
+ );
1148
+ const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
1149
+ let pendingFragmentsLength = 0, isFirstChunk = true, id, data = "", dataLines = 0, eventType, terminated = false;
1150
+ function feed(chunk) {
1151
+ if (terminated)
1152
+ throw new Error(
1153
+ "Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
1154
+ );
1155
+ if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
1156
+ const trailing2 = processLines(chunk);
1157
+ trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
1158
+ return;
1159
+ }
1160
+ if (chunk.indexOf(`
1161
+ `) === -1 && chunk.indexOf("\r") === -1) {
1162
+ pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
1163
+ return;
1164
+ }
1165
+ pendingFragments.push(chunk);
1166
+ const input = pendingFragments.join("");
1167
+ pendingFragments.length = 0, pendingFragmentsLength = 0;
1168
+ const trailing = processLines(input);
1169
+ trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
1170
+ }
1171
+ function checkBufferSize() {
1172
+ maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
1173
+ new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
1174
+ type: "max-buffer-size-exceeded"
1175
+ })
1176
+ )));
1177
+ }
1178
+ function processLines(chunk) {
1179
+ let searchIndex = 0;
1180
+ if (chunk.indexOf("\r") === -1) {
1181
+ let lfIndex = chunk.indexOf(`
1182
+ `, searchIndex);
1183
+ for (; lfIndex !== -1; ) {
1184
+ if (searchIndex === lfIndex) {
1185
+ dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
1186
+ `, searchIndex);
1187
+ continue;
1188
+ }
1189
+ const firstCharCode = chunk.charCodeAt(searchIndex);
1190
+ if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
1191
+ const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);
1192
+ if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
1193
+ onEvent({ id, event: eventType, data: value }), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`
1194
+ `, searchIndex);
1195
+ continue;
1196
+ }
1197
+ data = dataLines === 0 ? value : `${data}
1198
+ ${value}`, dataLines++;
1199
+ } else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(
1200
+ chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,
1201
+ lfIndex
1202
+ ) || void 0 : parseLine(chunk, searchIndex, lfIndex);
1203
+ searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
1204
+ `, searchIndex);
1205
+ }
1206
+ return chunk.slice(searchIndex);
1207
+ }
1208
+ for (; searchIndex < chunk.length; ) {
1209
+ const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
1210
+ `, searchIndex);
1211
+ let lineEnd = -1;
1212
+ 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)
1213
+ break;
1214
+ parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;
1215
+ }
1216
+ return chunk.slice(searchIndex);
1217
+ }
1218
+ function parseLine(chunk, start, end) {
1219
+ if (start === end) {
1220
+ dispatchEvent();
1221
+ return;
1222
+ }
1223
+ const firstCharCode = chunk.charCodeAt(start);
1224
+ if (isDataPrefix(chunk, start, firstCharCode)) {
1225
+ const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);
1226
+ data = dataLines === 0 ? value2 : `${data}
1227
+ ${value2}`, dataLines++;
1228
+ return;
1229
+ }
1230
+ if (isEventPrefix(chunk, start, firstCharCode)) {
1231
+ eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
1232
+ return;
1233
+ }
1234
+ if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
1235
+ const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
1236
+ id = value2.includes("\0") ? void 0 : value2;
1237
+ return;
1238
+ }
1239
+ if (firstCharCode === 58) {
1240
+ if (onComment) {
1241
+ const line2 = chunk.slice(start, end);
1242
+ onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
1243
+ }
1244
+ return;
1245
+ }
1246
+ const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":");
1247
+ if (fieldSeparatorIndex === -1) {
1248
+ processField(line, "", line);
1249
+ return;
1250
+ }
1251
+ const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
1252
+ processField(field, value, line);
1253
+ }
1254
+ function processField(field, value, line) {
1255
+ switch (field) {
1256
+ case "event":
1257
+ eventType = value || void 0;
1258
+ break;
1259
+ case "data":
1260
+ data = dataLines === 0 ? value : `${data}
1261
+ ${value}`, dataLines++;
1262
+ break;
1263
+ case "id":
1264
+ id = value.includes("\0") ? void 0 : value;
1265
+ break;
1266
+ case "retry":
1267
+ /^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
1268
+ new ParseError(`Invalid \`retry\` value: "${value}"`, {
1269
+ type: "invalid-retry",
1270
+ value,
1271
+ line
1272
+ })
1273
+ );
1274
+ break;
1275
+ default:
1276
+ onError(
1277
+ new ParseError(
1278
+ `Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
1279
+ { type: "unknown-field", field, value, line }
1280
+ )
1281
+ );
1282
+ break;
1283
+ }
1284
+ }
1285
+ function dispatchEvent() {
1286
+ dataLines > 0 && onEvent({
1287
+ id,
1288
+ event: eventType,
1289
+ data
1290
+ }), id = void 0, data = "", dataLines = 0, eventType = void 0;
1291
+ }
1292
+ function reset(options = {}) {
1293
+ if (options.consume && pendingFragments.length > 0) {
1294
+ const incompleteLine = pendingFragments.join("");
1295
+ parseLine(incompleteLine, 0, incompleteLine.length);
1296
+ }
1297
+ isFirstChunk = true, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = false;
1298
+ }
1299
+ return { feed, reset };
1300
+ }
1301
+ function isDataPrefix(chunk, i, firstCharCode) {
1302
+ return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
1303
+ }
1304
+ function isEventPrefix(chunk, i, firstCharCode) {
1305
+ 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;
1306
+ }
1307
+
1308
+ // ../../node_modules/.pnpm/eventsource@4.1.0/node_modules/eventsource/dist/index.js
1309
+ var ErrorEvent = class extends Event {
1310
+ /**
1311
+ * Constructs a new `ErrorEvent` instance. This is typically not called directly,
1312
+ * but rather emitted by the `EventSource` object when an error occurs.
1313
+ *
1314
+ * @param type - The type of the event (should be "error")
1315
+ * @param errorEventInitDict - Optional properties to include in the error event
1316
+ */
1317
+ constructor(type, errorEventInitDict) {
1318
+ var _a, _b;
1319
+ 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;
1320
+ }
1321
+ /**
1322
+ * Node.js "hides" the `message` and `code` properties of the `ErrorEvent` instance,
1323
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
1324
+ * we explicitly include the properties in the `inspect` method.
1325
+ *
1326
+ * This is automatically called by Node.js when you `console.log` an instance of this class.
1327
+ *
1328
+ * @param _depth - The current depth
1329
+ * @param options - The options passed to `util.inspect`
1330
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
1331
+ * @returns A string representation of the error
1332
+ */
1333
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")](_depth, options, inspect) {
1334
+ return inspect(inspectableError(this), options);
1335
+ }
1336
+ /**
1337
+ * Deno "hides" the `message` and `code` properties of the `ErrorEvent` instance,
1338
+ * when it is `console.log`'ed. This makes it harder to debug errors. To ease debugging,
1339
+ * we explicitly include the properties in the `inspect` method.
1340
+ *
1341
+ * This is automatically called by Deno when you `console.log` an instance of this class.
1342
+ *
1343
+ * @param inspect - The inspect function to use (prevents having to import it from `util`)
1344
+ * @param options - The options passed to `Deno.inspect`
1345
+ * @returns A string representation of the error
1346
+ */
1347
+ [/* @__PURE__ */ Symbol.for("Deno.customInspect")](inspect, options) {
1348
+ return inspect(inspectableError(this), options);
1349
+ }
1350
+ };
1351
+ function syntaxError(message) {
1352
+ const DomException = globalThis.DOMException;
1353
+ return typeof DomException == "function" ? new DomException(message, "SyntaxError") : new SyntaxError(message);
1354
+ }
1355
+ function flattenError(err) {
1356
+ 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}`;
1357
+ }
1358
+ function inspectableError(err) {
1359
+ return {
1360
+ type: err.type,
1361
+ message: err.message,
1362
+ code: err.code,
1363
+ defaultPrevented: err.defaultPrevented,
1364
+ cancelable: err.cancelable,
1365
+ timeStamp: err.timeStamp
1366
+ };
1367
+ }
1368
+ var __typeError = (msg) => {
1369
+ throw TypeError(msg);
1370
+ };
1371
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
1372
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
1373
+ 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);
1374
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
1375
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
1376
+ var _readyState;
1377
+ var _url;
1378
+ var _redirectUrl;
1379
+ var _withCredentials;
1380
+ var _fetch;
1381
+ var _reconnectInterval;
1382
+ var _reconnectTimer;
1383
+ var _lastEventId;
1384
+ var _controller;
1385
+ var _parser;
1386
+ var _onError;
1387
+ var _onMessage;
1388
+ var _onOpen;
1389
+ var _EventSource_instances;
1390
+ var connect_fn;
1391
+ var _onFetchResponse;
1392
+ var _onFetchError;
1393
+ var getRequestOptions_fn;
1394
+ var _onEvent;
1395
+ var _onRetryChange;
1396
+ var failConnection_fn;
1397
+ var scheduleReconnect_fn;
1398
+ var _reconnect;
1399
+ var EventSource = class extends EventTarget {
1400
+ constructor(url, eventSourceInitDict) {
1401
+ var _a, _b;
1402
+ 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) => {
1403
+ var _a2;
1404
+ __privateGet(this, _parser).reset();
1405
+ const { body, redirected, status, headers } = response;
1406
+ if (status === 204) {
1407
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Server sent HTTP 204, not reconnecting", 204), this.close();
1408
+ return;
1409
+ }
1410
+ if (redirected ? __privateSet(this, _redirectUrl, new URL(response.url)) : __privateSet(this, _redirectUrl, void 0), status !== 200) {
1411
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, `Non-200 status code (${status})`, status);
1412
+ return;
1413
+ }
1414
+ if (!(headers.get("content-type") || "").startsWith("text/event-stream")) {
1415
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, 'Invalid content type, expected "text/event-stream"', status);
1416
+ return;
1417
+ }
1418
+ if (__privateGet(this, _readyState) === this.CLOSED)
1419
+ return;
1420
+ __privateSet(this, _readyState, this.OPEN);
1421
+ const openEvent = new Event("open");
1422
+ if ((_a2 = __privateGet(this, _onOpen)) == null || _a2.call(this, openEvent), this.dispatchEvent(openEvent), typeof body != "object" || !body || !("getReader" in body)) {
1423
+ __privateMethod(this, _EventSource_instances, failConnection_fn).call(this, "Invalid response body, expected a web ReadableStream", status), this.close();
1424
+ return;
1425
+ }
1426
+ const decoder = new TextDecoder(), reader = body.getReader();
1427
+ let open = true;
1428
+ do {
1429
+ const { done, value } = await reader.read();
1430
+ value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
1431
+ } while (open);
1432
+ }), __privateAdd(this, _onFetchError, (err) => {
1433
+ __privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
1434
+ }), __privateAdd(this, _onEvent, (event) => {
1435
+ typeof event.id == "string" && __privateSet(this, _lastEventId, event.id);
1436
+ const messageEvent = new MessageEvent(event.event || "message", {
1437
+ data: event.data,
1438
+ origin: __privateGet(this, _redirectUrl) ? __privateGet(this, _redirectUrl).origin : __privateGet(this, _url).origin,
1439
+ lastEventId: event.id || ""
1440
+ });
1441
+ __privateGet(this, _onMessage) && (!event.event || event.event === "message") && __privateGet(this, _onMessage).call(this, messageEvent), this.dispatchEvent(messageEvent);
1442
+ }), __privateAdd(this, _onRetryChange, (value) => {
1443
+ __privateSet(this, _reconnectInterval, value);
1444
+ }), __privateAdd(this, _reconnect, () => {
1445
+ __privateSet(this, _reconnectTimer, void 0), __privateGet(this, _readyState) === this.CONNECTING && __privateMethod(this, _EventSource_instances, connect_fn).call(this);
1446
+ });
1447
+ try {
1448
+ if (url instanceof URL)
1449
+ __privateSet(this, _url, url);
1450
+ else if (typeof url == "string")
1451
+ __privateSet(this, _url, new URL(url, getBaseURL()));
1452
+ else
1453
+ throw new Error("Invalid URL");
1454
+ } catch {
1455
+ throw syntaxError("An invalid or illegal string was specified");
1456
+ }
1457
+ __privateSet(this, _parser, createParser({
1458
+ onEvent: __privateGet(this, _onEvent),
1459
+ onRetry: __privateGet(this, _onRetryChange)
1460
+ })), __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);
1461
+ }
1462
+ /**
1463
+ * Returns the state of this EventSource object's connection. It can have the values described below.
1464
+ *
1465
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState)
1466
+ *
1467
+ * Note: typed as `number` instead of `0 | 1 | 2` for compatibility with the `EventSource` interface,
1468
+ * defined in the TypeScript `dom` library.
1469
+ *
1470
+ * @public
1471
+ */
1472
+ get readyState() {
1473
+ return __privateGet(this, _readyState);
1474
+ }
1475
+ /**
1476
+ * Returns the URL providing the event stream.
1477
+ *
1478
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url)
1479
+ *
1480
+ * @public
1481
+ */
1482
+ get url() {
1483
+ return __privateGet(this, _url).href;
1484
+ }
1485
+ /**
1486
+ * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise.
1487
+ *
1488
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials)
1489
+ */
1490
+ get withCredentials() {
1491
+ return __privateGet(this, _withCredentials);
1492
+ }
1493
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */
1494
+ get onerror() {
1495
+ return __privateGet(this, _onError);
1496
+ }
1497
+ set onerror(value) {
1498
+ __privateSet(this, _onError, value);
1499
+ }
1500
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */
1501
+ get onmessage() {
1502
+ return __privateGet(this, _onMessage);
1503
+ }
1504
+ set onmessage(value) {
1505
+ __privateSet(this, _onMessage, value);
1506
+ }
1507
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
1508
+ get onopen() {
1509
+ return __privateGet(this, _onOpen);
1510
+ }
1511
+ set onopen(value) {
1512
+ __privateSet(this, _onOpen, value);
1513
+ }
1514
+ addEventListener(type, listener, options) {
1515
+ const listen = listener;
1516
+ super.addEventListener(type, listen, options);
1517
+ }
1518
+ removeEventListener(type, listener, options) {
1519
+ const listen = listener;
1520
+ super.removeEventListener(type, listen, options);
1521
+ }
1522
+ /**
1523
+ * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
1524
+ *
1525
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
1526
+ *
1527
+ * @public
1528
+ */
1529
+ close() {
1530
+ __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));
1531
+ }
1532
+ };
1533
+ _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(), /**
1534
+ * Connect to the given URL and start receiving events
1535
+ *
1536
+ * @internal
1537
+ */
1538
+ connect_fn = function() {
1539
+ __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));
1540
+ }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
1541
+ * Get request options for the `fetch()` request
1542
+ *
1543
+ * @returns The request options
1544
+ * @internal
1545
+ */
1546
+ getRequestOptions_fn = function() {
1547
+ var _a;
1548
+ const init2 = {
1549
+ // [spec] Let `corsAttributeState` be `Anonymous`…
1550
+ // [spec] …will have their mode set to "cors"…
1551
+ mode: "cors",
1552
+ redirect: "follow",
1553
+ headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
1554
+ cache: "no-store",
1555
+ signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
1556
+ };
1557
+ return "window" in globalThis && (init2.credentials = this.withCredentials ? "include" : "same-origin"), init2;
1558
+ }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
1559
+ * Handles the process referred to in the EventSource specification as "failing a connection".
1560
+ *
1561
+ * @param error - The error causing the connection to fail
1562
+ * @param code - The HTTP status code, if available
1563
+ * @internal
1564
+ */
1565
+ failConnection_fn = function(message, code) {
1566
+ var _a;
1567
+ __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
1568
+ const errorEvent = new ErrorEvent("error", { code, message });
1569
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
1570
+ }, /**
1571
+ * Schedules a reconnection attempt against the EventSource endpoint.
1572
+ *
1573
+ * @param message - The error causing the connection to fail
1574
+ * @param code - The HTTP status code, if available
1575
+ * @internal
1576
+ */
1577
+ scheduleReconnect_fn = function(message, code) {
1578
+ var _a;
1579
+ if (__privateGet(this, _readyState) === this.CLOSED)
1580
+ return;
1581
+ __privateSet(this, _readyState, this.CONNECTING);
1582
+ const errorEvent = new ErrorEvent("error", { code, message });
1583
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
1584
+ }, _reconnect = /* @__PURE__ */ new WeakMap(), /**
1585
+ * ReadyState representing an EventSource currently trying to connect
1586
+ *
1587
+ * @public
1588
+ */
1589
+ EventSource.CONNECTING = 0, /**
1590
+ * ReadyState representing an EventSource connection that is open (eg connected)
1591
+ *
1592
+ * @public
1593
+ */
1594
+ EventSource.OPEN = 1, /**
1595
+ * ReadyState representing an EventSource connection that is closed (eg disconnected)
1596
+ *
1597
+ * @public
1598
+ */
1599
+ EventSource.CLOSED = 2;
1600
+ Object.defineProperty(EventSource, /* @__PURE__ */ Symbol.for("eventsource.supports-fetch-override"), {
1601
+ value: true,
1602
+ writable: false,
1603
+ configurable: false,
1604
+ enumerable: false
1605
+ });
1606
+ function getBaseURL() {
1607
+ const doc = "document" in globalThis ? globalThis.document : void 0;
1608
+ return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
1609
+ }
1610
+
1611
+ // src/session/runner.ts
1612
+ import { spawn, spawnSync } from "node:child_process";
1613
+
1614
+ // src/session/runner/log.ts
1615
+ var claudeBin = () => process.env.BATON_CLAUDE_BIN ?? "claude";
1616
+ var buildClaudeArgs = (claudeSessionId, text, resuming) => [
1617
+ "--print",
1618
+ resuming ? "--resume" : "--session-id",
1619
+ claudeSessionId,
1620
+ "--output-format",
1621
+ "stream-json",
1622
+ "--verbose",
1623
+ "--dangerously-skip-permissions",
1624
+ text
1625
+ ];
1626
+ var STDERR_TAIL_BYTES = 2048;
1627
+ var tailBuffer = (cap = STDERR_TAIL_BYTES) => {
1628
+ let buf = "";
1629
+ return {
1630
+ append(chunk) {
1631
+ buf = (buf + chunk).slice(-cap);
1632
+ },
1633
+ toString() {
1634
+ return buf;
1635
+ }
1636
+ };
1637
+ };
1638
+ var maskedEnvKeys = (env) => env ? Object.keys(env).sort().join(", ") : "(none)";
1639
+ var previewText = (text, max = 80) => text.length > max ? `${text.slice(0, max)}\u2026` : text;
1640
+
1641
+ // src/session/runner/transcript.ts
1642
+ import { readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
1643
+ import { homedir as homedir2 } from "node:os";
1644
+ import { join as join3 } from "node:path";
1645
+ var findTranscriptPath = (agentSessionId) => {
1646
+ const root = join3(homedir2(), ".claude", "projects");
1647
+ try {
1648
+ for (const dir of readdirSync(root)) {
1649
+ const candidate = join3(root, dir, `${agentSessionId}.jsonl`);
1650
+ try {
1651
+ if (statSync(candidate).isFile()) return candidate;
1652
+ } catch {
1653
+ }
1654
+ }
1655
+ } catch {
1656
+ }
1657
+ return null;
1658
+ };
1659
+ var isRecord = (v) => typeof v === "object" && v !== null;
1660
+ var textOf = (rec) => {
1661
+ const message = rec.message;
1662
+ const content = isRecord(message) ? message.content : rec.content;
1663
+ if (typeof content === "string") return content.trim();
1664
+ if (Array.isArray(content))
1665
+ return content.filter(isRecord).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
1666
+ return "";
1667
+ };
1668
+ var roleOf = (rec) => rec.type === "user" || rec.type === "assistant" ? rec.type : null;
1669
+ var parseFirstExchange = (content) => {
1670
+ let userText = "";
1671
+ let assistantText = "";
1672
+ for (const line of content.split("\n")) {
1673
+ if (!line.trim()) continue;
1674
+ let parsed;
1675
+ try {
1676
+ parsed = JSON.parse(line);
1677
+ } catch {
1678
+ continue;
1679
+ }
1680
+ if (!isRecord(parsed)) continue;
1681
+ const role = roleOf(parsed);
1682
+ if (!role) continue;
1683
+ const text = textOf(parsed);
1684
+ if (!text) continue;
1685
+ if (role === "user" && !userText) userText = text;
1686
+ else if (role === "assistant" && !assistantText) assistantText = text;
1687
+ if (userText && assistantText) break;
1688
+ }
1689
+ return userText || assistantText ? { userText, assistantText } : null;
1690
+ };
1691
+ var readFirstExchange = (agentSessionId) => {
1692
+ const path = findTranscriptPath(agentSessionId);
1693
+ if (!path) return null;
1694
+ try {
1695
+ return parseFirstExchange(readFileSync2(path, "utf8"));
1696
+ } catch {
1697
+ return null;
1698
+ }
1699
+ };
1700
+
1701
+ // src/session/runner/attachments.ts
1702
+ import { createWriteStream } from "node:fs";
1703
+ import { mkdir, writeFile } from "node:fs/promises";
1704
+ import { join as join4 } from "node:path";
1705
+ import { Readable } from "node:stream";
1706
+ import { pipeline } from "node:stream/promises";
1707
+ var ATTACH_DIR = "attachments";
1708
+ var dedupe = (name, taken) => {
1709
+ if (!taken.has(name)) {
1710
+ taken.add(name);
1711
+ return name;
1712
+ }
1713
+ const dot = name.lastIndexOf(".");
1714
+ const stem = dot > 0 ? name.slice(0, dot) : name;
1715
+ const ext = dot > 0 ? name.slice(dot) : "";
1716
+ let n = 1;
1717
+ let candidate = `${stem}-${n}${ext}`;
1718
+ while (taken.has(candidate)) {
1719
+ n += 1;
1720
+ candidate = `${stem}-${n}${ext}`;
1721
+ }
1722
+ taken.add(candidate);
1723
+ return candidate;
1724
+ };
1725
+ var materializeAttachments = async (opts) => {
1726
+ const fetchImpl = opts.fetchImpl ?? fetch;
1727
+ const dir = join4(opts.worktreePath, ATTACH_DIR);
1728
+ await mkdir(dir, { recursive: true });
1729
+ await writeFile(join4(dir, ".gitignore"), "*\n", "utf8");
1730
+ const taken = /* @__PURE__ */ new Set([".gitignore"]);
1731
+ const relPaths = [];
1732
+ for (const att of opts.attachments) {
1733
+ const name = dedupe(att.filename, taken);
1734
+ const res = await fetchImpl(`${opts.serverBase}${att.url}`);
1735
+ if (!res.ok || !res.body) throw new Error(`attachment ${att.id} \u2192 ${res.status}`);
1736
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(join4(dir, name)));
1737
+ relPaths.push(`${ATTACH_DIR}/${name}`);
1738
+ }
1739
+ return relPaths;
1740
+ };
1741
+ var augmentPrompt = (text, relPaths) => {
1742
+ if (relPaths.length === 0) return text;
1743
+ const header = [
1744
+ "[Attached files \u2014 already saved in the working directory:]",
1745
+ ...relPaths.map((p) => `- ${p}`)
1746
+ ].join("\n");
1747
+ return text.length > 0 ? `${header}
1748
+
1749
+ ${text}` : header;
1750
+ };
1751
+
1752
+ // src/session/runner/spawn.ts
1753
+ import { createInterface as createInterface2 } from "node:readline";
1754
+ var spawnClaude = async (config, worker2, text, resuming, spawnImpl, log, envOverlay) => {
1755
+ const childEnv = envOverlay ? { ...process.env, ...envOverlay } : process.env;
1756
+ const bin = claudeBin();
1757
+ const args = buildClaudeArgs(config.agentSessionId, text, resuming);
1758
+ log(`[spawn] ${bin} ${args.slice(0, -1).join(" ")} -- "${previewText(text)}"`);
1759
+ log(`[spawn] cwd: ${config.worktreePath}`);
1760
+ log(`[spawn] runtime env keys: ${maskedEnvKeys(envOverlay)}`);
1761
+ let child;
1762
+ try {
1763
+ child = spawnImpl(bin, args, {
1764
+ cwd: config.worktreePath,
1765
+ stdio: ["ignore", "pipe", "pipe"],
1766
+ env: childEnv
1767
+ });
1768
+ } catch (err) {
1769
+ const message = err instanceof Error ? err.message : String(err);
1770
+ log(`[spawn] failed: ${message}`);
1771
+ await worker2.emitEvent("turn_error", { message: `spawn failed: ${message}` });
1772
+ return null;
1773
+ }
1774
+ if (!child.stdout) {
1775
+ log("[spawn] no stdout from claude \u2014 aborting turn");
1776
+ await worker2.emitEvent("turn_error", { message: "no stdout from claude" });
1777
+ return null;
1778
+ }
1779
+ const tail = tailBuffer();
1780
+ if (child.stderr) {
1781
+ const errLines = createInterface2({ input: child.stderr });
1782
+ void (async () => {
1783
+ for await (const line of errLines) {
1784
+ tail.append(`${line}
1785
+ `);
1786
+ log(`[claude] ${line}`);
1787
+ }
1788
+ })();
1789
+ }
1790
+ return { child, tail };
1791
+ };
1792
+
1793
+ // src/session/runner/stream.ts
1794
+ import { createInterface as createInterface3 } from "node:readline";
1795
+ var streamSdkEvents = async (stdout, worker2) => {
1796
+ const rl = createInterface3({ input: stdout });
1797
+ for await (const line of rl) {
1798
+ const trimmed = line.trim();
1799
+ if (!trimmed) continue;
1800
+ let payload;
1801
+ try {
1802
+ payload = JSON.parse(trimmed);
1803
+ } catch {
1804
+ payload = { raw: trimmed };
1805
+ }
1806
+ await worker2.emitEvent("sdk_event", payload);
1807
+ }
1808
+ };
1809
+
1810
+ // src/session/runner/turn.ts
1811
+ var finalizeTurn = async (child, tail, worker2, log) => {
1812
+ const exitCode = await new Promise(
1813
+ (resolve) => child.once("exit", (code) => resolve(code ?? -1))
1814
+ );
1815
+ log(`[exit] code=${exitCode}`);
1816
+ const stderrTail = tail.toString();
1817
+ if (exitCode !== 0) {
1818
+ await worker2.emitEvent("turn_error", {
1819
+ message: `claude exited with code ${exitCode}`,
1820
+ exitCode,
1821
+ stderrTail
1822
+ });
1823
+ }
1824
+ await worker2.emitEvent("turn_complete", {
1825
+ exitCode,
1826
+ ...stderrTail ? { stderrTail } : {}
1827
+ });
1828
+ return exitCode;
1829
+ };
1830
+ var runTurn = async (config, worker2, msg, resuming, spawnImpl, log = (m) => console.log(m), envOverlay, fetchImpl) => {
1831
+ await worker2.emitEvent("turn_start", { messageId: msg.id });
1832
+ const payload = msg.payload;
1833
+ const rawText = typeof payload?.text === "string" ? payload.text : "";
1834
+ const attachments = Array.isArray(payload?.attachments) ? payload.attachments : [];
1835
+ if (rawText.length === 0 && attachments.length === 0) {
1836
+ await worker2.emitEvent("turn_error", { message: "user_message missing text and attachments" });
1837
+ return -1;
1838
+ }
1839
+ let text = rawText;
1840
+ if (attachments.length > 0) {
1841
+ try {
1842
+ const relPaths = await materializeAttachments({
1843
+ worktreePath: config.worktreePath,
1844
+ serverBase: config.server,
1845
+ attachments,
1846
+ fetchImpl
1847
+ });
1848
+ log(`[attach] downloaded ${relPaths.length} file(s) \u2192 ${config.worktreePath}/attachments`);
1849
+ text = augmentPrompt(rawText, relPaths);
1850
+ } catch (err) {
1851
+ const message = err instanceof Error ? err.message : String(err);
1852
+ await worker2.emitEvent("turn_error", { message: `attachment download failed: ${message}` });
1853
+ return -1;
1854
+ }
1855
+ }
1856
+ const result = await spawnClaude(config, worker2, text, resuming, spawnImpl, log, envOverlay);
1857
+ if (!result) return -1;
1858
+ try {
1859
+ await streamSdkEvents(result.child.stdout, worker2);
1860
+ return await finalizeTurn(result.child, result.tail, worker2, log);
1861
+ } catch (err) {
1862
+ const message = err instanceof Error ? err.message : String(err);
1863
+ log(`[run] ${message}`);
1864
+ await worker2.emitEvent("turn_error", { message, stderrTail: result.tail.toString() });
1865
+ return -1;
1866
+ }
1867
+ };
1868
+
1869
+ // src/session/runner.ts
1870
+ var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onReady) => {
1871
+ const es = new ESCtor(url);
1872
+ es.onopen = onReady;
1873
+ es.onmessage = (e) => {
1874
+ try {
1875
+ const ev = JSON.parse(e.data);
1876
+ if (seen.has(ev.sequence)) return;
1877
+ seen.add(ev.sequence);
1878
+ if (ev.type === "user_message") onUserMessage(ev);
1879
+ } catch {
1880
+ }
1881
+ };
1882
+ es.onerror = () => log("sse error (eventsource will retry)");
1883
+ return es;
1884
+ };
1885
+ var waitForAbort = (signal) => new Promise((resolve) => {
1886
+ if (signal.aborted) return resolve();
1887
+ signal.addEventListener("abort", () => resolve(), { once: true });
1888
+ });
1889
+ var shouldReap = (lastActivity, now, busy, queueLen, idleMs) => !busy && queueLen === 0 && now - lastActivity >= idleMs;
1890
+ var idleMsFromEnv = () => Number(process.env.BATON_SESSION_IDLE_MS) || 30 * 6e4;
1891
+ var restoreTty = () => {
1892
+ if (!process.stdout.isTTY) return;
1893
+ try {
1894
+ spawnSync("reset", { stdio: "inherit" });
1895
+ } catch {
1896
+ }
1897
+ };
1898
+ var runDaemon = async (config, deps, signal) => {
1899
+ const log = deps.log ?? ((m) => console.log(`[#${config.sessionId} ${config.name}] ${m}`));
1900
+ const sp = deps.spawnImpl ?? spawn;
1901
+ const ESCtor = deps.eventSourceImpl ?? EventSource;
1902
+ log(`bin: ${claudeBin()} cwd: ${config.worktreePath}`);
1903
+ log(`runtime env keys: ${maskedEnvKeys(deps.env)}`);
1904
+ const state = {
1905
+ firstSpawnDone: findTranscriptPath(config.agentSessionId) !== null,
1906
+ seen: /* @__PURE__ */ new Set()
1907
+ };
1908
+ const pendingQueue = [];
1909
+ let busy = false;
1910
+ let lastActivity = Date.now();
1911
+ const drain = async () => {
1912
+ if (busy) return;
1913
+ busy = true;
1914
+ try {
1915
+ while (pendingQueue.length > 0 && !signal.aborted) {
1916
+ const msg = pendingQueue.shift();
1917
+ if (!msg) break;
1918
+ const resuming = state.firstSpawnDone;
1919
+ log(`\u25B6 msg #${msg.sequence} (${resuming ? "resume" : "first"})`);
1920
+ const code = await runTurn(
1921
+ config,
1922
+ deps.worker,
1923
+ msg,
1924
+ resuming,
1925
+ sp,
1926
+ log,
1927
+ deps.env,
1928
+ deps.fetchImpl
1929
+ );
1930
+ state.firstSpawnDone = true;
1931
+ lastActivity = Date.now();
1932
+ log(`\u2714 msg #${msg.sequence}${code === 0 ? "" : ` (exit ${code})`}`);
1933
+ }
1934
+ } finally {
1935
+ busy = false;
1936
+ }
1937
+ };
1938
+ const es = subscribeStream(
1939
+ // live-only: the server now replays history to viewers, but the child must
1940
+ // not re-receive (and re-run) past user_messages on connect/resume.
1941
+ `${config.server}/sessions/${config.sessionId}/stream?live=1`,
1942
+ ESCtor,
1943
+ state.seen,
1944
+ log,
1945
+ (ev) => {
1946
+ lastActivity = Date.now();
1947
+ pendingQueue.push(ev);
1948
+ void drain();
1949
+ },
1950
+ // Report active only now that we're subscribed — before this, a message
1951
+ // sent right after spawn would be missed (no replay on the live stream).
1952
+ () => {
1953
+ void deps.worker.setActive(true).then(() => log("\u25B2 active (subscribed)")).catch((e) => log(`status(active) failed: ${String(e)}`));
1954
+ }
1955
+ );
1956
+ const idleMs = idleMsFromEnv();
1957
+ let idleTimer;
1958
+ const reaped = new Promise((resolve) => {
1959
+ idleTimer = setInterval(
1960
+ () => {
1961
+ if (shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
1962
+ log("idle \u2014 shutting down (resumes on next message)");
1963
+ resolve();
1964
+ }
1965
+ },
1966
+ Math.min(idleMs, 3e4)
1967
+ );
1968
+ });
1969
+ await Promise.race([waitForAbort(signal), reaped]);
1970
+ if (idleTimer) clearInterval(idleTimer);
1971
+ es.close();
1972
+ restoreTty();
1973
+ };
1974
+
1975
+ // src/commands/session/run.ts
1976
+ var bearerFetch = (token) => (input, init2) => fetch(input, {
1977
+ ...init2,
1978
+ headers: {
1979
+ ...init2?.headers,
1980
+ authorization: `Bearer ${token}`
1981
+ }
1982
+ });
1983
+ var authedEventSource = (token) => class extends EventSource {
1984
+ constructor(url) {
1985
+ super(url, {
1986
+ fetch: (u, init2) => fetch(u, {
1987
+ ...init2,
1988
+ headers: {
1989
+ ...init2?.headers,
1990
+ authorization: `Bearer ${token}`
1991
+ }
1992
+ })
1993
+ });
1994
+ }
1995
+ };
1996
+ var sessionRunCommand = defineCommand({
1997
+ meta: { name: "run", description: "run a session child (spawned by the worker daemon)" },
1998
+ args: {
1999
+ session: { type: "positional", required: true, description: "session int id" }
2000
+ },
2001
+ run: async ({ args }) => {
2002
+ const server = process.env.BATON_SERVER;
2003
+ const workerToken = process.env.BATON_WORKER_TOKEN;
2004
+ if (!server || !workerToken)
2005
+ throw new Error("`session run` must be spawned by the worker daemon (missing BATON_* env)");
2006
+ const sessionId = Number(args.session);
2007
+ if (!Number.isInteger(sessionId) || sessionId <= 0)
2008
+ throw new Error(`invalid session id: ${args.session}`);
2009
+ const client = createClient(server, { bearer: workerToken });
2010
+ const s = await client.sessions.get(sessionId);
2011
+ if (!s.agentSessionId || !s.worktreePath)
2012
+ throw new Error(`session #${sessionId} is not materialized yet`);
2013
+ const config = {
2014
+ server,
2015
+ sessionId,
2016
+ name: s.name,
2017
+ agentSessionId: s.agentSessionId,
2018
+ worktreePath: s.worktreePath
2019
+ };
2020
+ const worker2 = createWorkerClient(server, workerToken, sessionId);
2021
+ const ac = new AbortController();
2022
+ const stop = () => ac.abort();
2023
+ process.on("SIGINT", stop);
2024
+ process.on("SIGTERM", stop);
2025
+ await runDaemon(
2026
+ config,
2027
+ {
2028
+ worker: worker2,
2029
+ eventSourceImpl: authedEventSource(workerToken),
2030
+ fetchImpl: bearerFetch(workerToken)
2031
+ },
2032
+ ac.signal
2033
+ );
2034
+ }
2035
+ });
2036
+
2037
+ // src/commands/attach.ts
2038
+ import { openAsBlob } from "node:fs";
2039
+ import { basename } from "node:path";
2040
+
2041
+ // src/mime.ts
2042
+ import { extname } from "node:path";
2043
+ var BY_EXT = {
2044
+ ".png": "image/png",
2045
+ ".jpg": "image/jpeg",
2046
+ ".jpeg": "image/jpeg",
2047
+ ".gif": "image/gif",
2048
+ ".webp": "image/webp",
2049
+ ".svg": "image/svg+xml",
2050
+ ".pdf": "application/pdf",
2051
+ ".txt": "text/plain",
2052
+ ".md": "text/markdown",
2053
+ ".json": "application/json",
2054
+ ".csv": "text/csv"
2055
+ };
2056
+ var contentTypeForPath = (path) => BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
2057
+
2058
+ // src/commands/attach.ts
2059
+ var attachPaths = (raw) => (raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw]).flatMap((p) => p.split(",")).map((p) => p.trim()).filter(Boolean);
2060
+ var uploadAttachments = async (client, sessionId, paths) => {
2061
+ const out = [];
2062
+ for (const path of paths) {
2063
+ const body = await openAsBlob(path, { type: contentTypeForPath(path) });
2064
+ out.push(
2065
+ await client.sessions.uploadAttachment(sessionId, {
2066
+ filename: basename(path),
2067
+ contentType: contentTypeForPath(path),
2068
+ body
2069
+ })
2070
+ );
2071
+ }
2072
+ return out;
2073
+ };
2074
+
2075
+ // src/commands/session/send.ts
2076
+ var sessionSendCommand = defineCommand({
2077
+ meta: { name: "send", description: "post a chat message into a session (no streaming reply)" },
2078
+ args: {
2079
+ session: { type: "positional", required: true, description: "session int id or name" },
2080
+ text: { type: "positional", required: false, description: "message text" },
2081
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2082
+ attach: {
2083
+ type: "string",
2084
+ description: "file(s) to attach; repeat or comma-separate (e.g. --attach a.png,b.pdf)"
2085
+ },
2086
+ ...common
2087
+ },
2088
+ run: async ({ args }) => {
2089
+ const c = clientFor(args);
2090
+ const projectId = resolveProjectId(args);
2091
+ const s = await resolveSession(c, projectId, args.session);
2092
+ const paths = attachPaths(args.attach);
2093
+ const text = args.text ?? "";
2094
+ if (text.length === 0 && paths.length === 0)
2095
+ throw new Error("nothing to send: provide message text and/or --attach <file>");
2096
+ const attachments = paths.length > 0 ? await uploadAttachments(c, s.id, paths) : void 0;
2097
+ const ev = await c.sessions.sendMessage(s.id, text, attachments);
2098
+ if (args.json) console.log(toJson(ev));
2099
+ else {
2100
+ const note = attachments ? ` +${attachments.length} attachment(s)` : "";
2101
+ console.log(`sent (seq ${ev.sequence}) \u2192 ${s.name} (#${s.id})${note}: ${text}`);
2102
+ }
2103
+ }
2104
+ });
2105
+
2106
+ // src/commands/session/stop.ts
2107
+ var sessionStopCommand = defineCommand({
2108
+ meta: { name: "stop", description: "stop a session (keeps it; resume to restart)" },
2109
+ args: {
2110
+ session: { type: "positional", required: true, description: "session int id or name" },
2111
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2112
+ ...common
2113
+ },
2114
+ run: async ({ args }) => {
2115
+ const c = clientFor(args);
2116
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
2117
+ const s = await c.sessions.stop(handle.id);
2118
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
2119
+ }
2120
+ });
2121
+
2122
+ // src/commands/session.ts
2123
+ var session = defineCommand({
2124
+ meta: { name: "session", description: "create / resume / stop / send to agent sessions" },
2125
+ subCommands: {
2126
+ create: sessionCreateCommand,
2127
+ resume: sessionResumeCommand,
2128
+ stop: sessionStopCommand,
2129
+ rename: sessionRenameCommand,
2130
+ rm: sessionRmCommand,
2131
+ send: sessionSendCommand,
2132
+ ls: sessionLsCommand,
2133
+ get: sessionGetCommand,
2134
+ run: sessionRunCommand
2135
+ }
2136
+ });
2137
+
2138
+ // src/commands/task.ts
2139
+ var createTask = (c, input, json) => c.tasks.create(input).then((t) => renderOne(t, fmtTask, json));
2140
+ var listTasks = (c, requirementId, json) => c.tasks.listByRequirement(requirementId).then((ts) => renderList(ts, fmtTask, json));
2141
+ var getTask = (c, id, json) => c.tasks.get(id).then((t) => renderOne(t, fmtTask, json));
2142
+ var setTaskStatus = (c, id, status, json) => c.tasks.setStatus(id, status).then((t) => renderOne(t, fmtTask, json));
2143
+ var removeTask = (c, id, label, json) => c.tasks.remove(id).then(() => removed("task", label, json));
2144
+ var addTaskComment = (c, taskId, body, workerId, json) => c.tasks.addComment(taskId, body, workerId).then((x) => renderOne(x, fmtComment, json));
2145
+ var listTaskComments = (c, taskId, json) => c.tasks.listComments(taskId).then((xs) => renderList(xs, fmtComment, json));
2146
+ var resolveReqByCode = async (c, projectId, code) => (await c.requirements.getByCode(projectId, code)).id;
2147
+ var resolveTaskByCode = async (c, projectId, code) => (await c.tasks.getByCode(projectId, code)).id;
2148
+ var task = defineCommand({
2149
+ meta: { name: "task", description: "manage tasks" },
2150
+ subCommands: {
2151
+ create: defineCommand({
2152
+ meta: { name: "create", description: "create a task" },
2153
+ args: {
2154
+ title: { type: "positional", required: true },
2155
+ requirement: { type: "string", required: true, description: "requirement code (R-N)" },
2156
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2157
+ body: { type: "string", description: "task body (markdown)" },
2158
+ deps: { type: "string", description: "comma-separated dependency task codes (T-N,T-N)" },
2159
+ ...common
2160
+ },
2161
+ run: async ({ args }) => {
2162
+ const c = clientFor(args);
2163
+ const projectId = resolveProjectId(args);
2164
+ const requirementId = await resolveReqByCode(c, projectId, args.requirement);
2165
+ const depCodes = splitCsv(args.deps) ?? [];
2166
+ const dependsOn = await Promise.all(
2167
+ depCodes.map((code) => resolveTaskByCode(c, projectId, code))
2168
+ );
2169
+ console.log(
2170
+ await createTask(
2171
+ c,
2172
+ {
2173
+ requirementId,
2174
+ title: args.title,
2175
+ body: args.body,
2176
+ dependsOn: dependsOn.length ? dependsOn : void 0
2177
+ },
2178
+ Boolean(args.json)
2179
+ )
2180
+ );
2181
+ }
2182
+ }),
2183
+ ls: defineCommand({
2184
+ meta: { name: "ls", description: "list tasks in a requirement" },
2185
+ args: {
2186
+ requirement: { type: "string", required: true, description: "requirement code (R-N)" },
2187
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2188
+ ...common
2189
+ },
2190
+ run: async ({ args }) => {
2191
+ const c = clientFor(args);
2192
+ const reqId = await resolveReqByCode(c, resolveProjectId(args), args.requirement);
2193
+ console.log(await listTasks(c, reqId, Boolean(args.json)));
2194
+ }
2195
+ }),
2196
+ get: defineCommand({
2197
+ meta: { name: "get", description: "get a task by code (T-N)" },
2198
+ args: {
2199
+ code: { type: "positional", required: true },
2200
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2201
+ ...common
2202
+ },
2203
+ run: async ({ args }) => {
2204
+ const c = clientFor(args);
2205
+ const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
2206
+ console.log(await getTask(c, id, Boolean(args.json)));
2207
+ }
2208
+ }),
2209
+ "set-status": defineCommand({
2210
+ meta: {
2211
+ name: "set-status",
2212
+ description: "set task status (todo|in_progress|done|failed|cancelled)"
2213
+ },
2214
+ args: {
2215
+ code: { type: "positional", required: true, description: "task code (T-N)" },
2216
+ status: { type: "positional", required: true },
2217
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2218
+ ...common
2219
+ },
2220
+ run: async ({ args }) => {
2221
+ const c = clientFor(args);
2222
+ const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
2223
+ console.log(await setTaskStatus(c, id, args.status, Boolean(args.json)));
2224
+ }
2225
+ }),
2226
+ rm: defineCommand({
2227
+ meta: { name: "rm", description: "delete a task by code" },
2228
+ args: {
2229
+ code: { type: "positional", required: true },
2230
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2231
+ ...common
2232
+ },
2233
+ run: async ({ args }) => {
2234
+ const c = clientFor(args);
2235
+ const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
2236
+ console.log(await removeTask(c, id, args.code, Boolean(args.json)));
2237
+ }
2238
+ }),
2239
+ comment: defineCommand({
2240
+ meta: { name: "comment", description: "append-only task comments" },
2241
+ subCommands: {
2242
+ add: defineCommand({
2243
+ meta: { name: "add", description: "add a comment to a task" },
2244
+ args: {
2245
+ code: { type: "positional", required: true, description: "task code (T-N)" },
2246
+ body: { type: "positional", required: true, description: "comment text (markdown)" },
2247
+ worker: { type: "string", description: "attribute to a worker id (omit = human)" },
2248
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2249
+ ...common
2250
+ },
2251
+ run: async ({ args }) => {
2252
+ const c = clientFor(args);
2253
+ const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
2254
+ const workerId = args.worker ? Number(args.worker) : void 0;
2255
+ console.log(await addTaskComment(c, id, args.body, workerId, Boolean(args.json)));
2256
+ }
2257
+ }),
2258
+ ls: defineCommand({
2259
+ meta: { name: "ls", description: "list a task\u2019s comments in order" },
2260
+ args: {
2261
+ code: { type: "positional", required: true, description: "task code (T-N)" },
2262
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2263
+ ...common
2264
+ },
2265
+ run: async ({ args }) => {
2266
+ const c = clientFor(args);
2267
+ const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
2268
+ console.log(await listTaskComments(c, id, Boolean(args.json)));
2269
+ }
2270
+ })
2271
+ }
2272
+ })
2273
+ }
2274
+ });
2275
+
2276
+ // src/commands/worker.ts
2277
+ import { hostname as osHostname } from "node:os";
2278
+
2279
+ // src/worker/daemon.ts
2280
+ import { spawn as spawn2 } from "node:child_process";
2281
+ import { randomUUID } from "node:crypto";
2282
+ import { existsSync as existsSync4 } from "node:fs";
2283
+ import { dirname as dirname2, join as join5 } from "node:path";
2284
+ import { fileURLToPath } from "node:url";
2285
+
2286
+ // src/session/runner/title.ts
2287
+ var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
2288
+ var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NONE";
2289
+ var sanitizeTitle = (raw) => raw.replace(/[\r\n]+/g, " ").replace(/["'`]/g, "").replace(/^\s*(title|session)\s*[:-]\s*/i, "").replace(/\s+/g, " ").trim().slice(0, 40).trim();
2290
+ var TITLE_TIMEOUT_MS = 3e4;
2291
+ var generateTitle = async (input) => {
2292
+ const { worktreePath, userText, assistantText, spawnImpl } = input;
2293
+ if (`${userText}${assistantText}`.trim().length < 4) return null;
2294
+ const exchange = [
2295
+ `User: ${clip(userText, 800)}`,
2296
+ assistantText ? `Assistant: ${clip(assistantText, 800)}` : ""
2297
+ ].filter(Boolean).join("\n");
2298
+ const prompt = `Reply with ONLY a 2-5 word title (no quotes, no punctuation) summarizing this coding session. If it is too short or generic to title meaningfully, reply with exactly NONE:
2299
+ ${exchange}`;
2300
+ const out = await new Promise((resolve) => {
2301
+ let buf = "";
2302
+ let done = false;
2303
+ const finish = (s) => {
2304
+ if (!done) {
2305
+ done = true;
2306
+ resolve(s);
2307
+ }
2308
+ };
2309
+ let child;
2310
+ try {
2311
+ child = spawnImpl(claudeBin(), ["--print", "--dangerously-skip-permissions", prompt], {
2312
+ cwd: worktreePath,
2313
+ stdio: ["ignore", "pipe", "pipe"],
2314
+ env: process.env
2315
+ });
2316
+ } catch {
2317
+ return finish("");
2318
+ }
2319
+ const timer = setTimeout(() => {
2320
+ try {
2321
+ child.kill();
2322
+ } catch {
2323
+ }
2324
+ finish("");
2325
+ }, TITLE_TIMEOUT_MS);
2326
+ child.stdout?.on("data", (d) => {
2327
+ buf += String(d);
2328
+ });
2329
+ child.on("exit", () => {
2330
+ clearTimeout(timer);
2331
+ finish(buf);
2332
+ });
2333
+ child.on("error", () => {
2334
+ clearTimeout(timer);
2335
+ finish("");
2336
+ });
2337
+ });
2338
+ const title = sanitizeTitle(out);
2339
+ return !title || isDeclined(title) ? null : title;
2340
+ };
2341
+
2342
+ // src/session/worktree.ts
2343
+ import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
2344
+ import { existsSync as existsSync3, mkdirSync } from "node:fs";
2345
+ import { dirname } from "node:path";
2346
+ var createWorktree = (input) => {
2347
+ if (existsSync3(input.worktreePath))
2348
+ throw new Error(`worktree path already exists: ${input.worktreePath}`);
2349
+ mkdirSync(dirname(input.worktreePath), { recursive: true });
2350
+ const branch = `baton/${input.sessionCode}`;
2351
+ const r = spawnSync2(
2352
+ "git",
2353
+ ["-C", input.repo, "worktree", "add", "-b", branch, input.worktreePath, input.base],
2354
+ { stdio: "pipe", encoding: "utf8" }
2355
+ );
2356
+ if (r.status !== 0) {
2357
+ throw new Error(`git worktree add failed: ${r.stderr || r.stdout || `exit ${r.status}`}`);
2358
+ }
2359
+ };
2360
+ var repoHeadBranch = (repo) => {
2361
+ const r = spawnSync2("git", ["-C", repo, "symbolic-ref", "--short", "HEAD"], {
2362
+ stdio: "pipe",
2363
+ encoding: "utf8"
2364
+ });
2365
+ const branch = r.status === 0 ? r.stdout.trim() : "";
2366
+ return branch || "main";
2367
+ };
2368
+ var removeWorktree = (repo, worktreePath) => {
2369
+ if (!existsSync3(worktreePath)) return;
2370
+ try {
2371
+ execFileSync("git", ["-C", repo, "worktree", "remove", "--force", worktreePath], {
2372
+ stdio: "pipe"
2373
+ });
2374
+ } catch {
2375
+ }
2376
+ };
2377
+
2378
+ // src/worker/daemon.ts
2379
+ var binPath = () => {
2380
+ const here = dirname2(fileURLToPath(import.meta.url));
2381
+ const devShim = join5(here, "..", "..", "bin", "baton.mjs");
2382
+ return existsSync4(devShim) ? devShim : fileURLToPath(import.meta.url);
2383
+ };
2384
+ var runWorkerDaemon = async (cfg, client, signal) => {
2385
+ const repo = process.cwd();
2386
+ const worktreeDir = defaultWorktreeDir();
2387
+ const children = /* @__PURE__ */ new Map();
2388
+ const log = (m) => console.log(`[worker #${cfg.workerId} ${cfg.name}] ${m}`);
2389
+ const ping = () => {
2390
+ void client.workers.heartbeat(cfg.machineId).catch((e) => log(`heartbeat failed: ${String(e)}`));
2391
+ };
2392
+ ping();
2393
+ const hb = setInterval(ping, 3e4);
2394
+ const killChild = (child) => {
2395
+ if (child.pid === void 0) return;
2396
+ try {
2397
+ process.kill(-child.pid, "SIGTERM");
2398
+ } catch {
2399
+ }
2400
+ };
2401
+ const spawnChild = (sessionId, worktreePath) => {
2402
+ if (children.has(sessionId)) return;
2403
+ const child = spawn2(process.execPath, [binPath(), "session", "run", String(sessionId)], {
2404
+ detached: true,
2405
+ stdio: "inherit",
2406
+ env: {
2407
+ ...process.env,
2408
+ BATON_SERVER: cfg.server,
2409
+ BATON_WORKER_TOKEN: cfg.apiToken
2410
+ }
2411
+ });
2412
+ children.set(sessionId, { child, worktreePath });
2413
+ log(`spawned session #${sessionId} (${worktreePath})`);
2414
+ child.on("exit", (code) => {
2415
+ children.delete(sessionId);
2416
+ log(`session #${sessionId} child exited (code=${code ?? -1})`);
2417
+ void client.sessions.setStatus(sessionId, false, cfg.apiToken).catch(() => {
2418
+ });
2419
+ });
2420
+ };
2421
+ const onStart = async (sessionId, name) => {
2422
+ if (children.has(sessionId)) return log(`session #${sessionId} already running`);
2423
+ const session2 = await client.sessions.get(sessionId);
2424
+ let worktreePath = session2.worktreePath;
2425
+ if (!session2.agentSessionId || !worktreePath) {
2426
+ const agentSessionId = randomUUID();
2427
+ worktreePath = join5(worktreeDir, slug(`${name}-${agentSessionId.slice(0, 8)}`));
2428
+ createWorktree({
2429
+ repo,
2430
+ worktreePath,
2431
+ sessionCode: agentSessionId.slice(0, 8),
2432
+ base: repoHeadBranch(repo)
2433
+ });
2434
+ await client.sessions.materialize(sessionId, { agentSessionId, worktreePath }, cfg.apiToken);
2435
+ log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
2436
+ }
2437
+ spawnChild(sessionId, worktreePath);
2438
+ };
2439
+ const onStop = (sessionId) => {
2440
+ const entry = children.get(sessionId);
2441
+ if (!entry) return;
2442
+ killChild(entry.child);
2443
+ children.delete(sessionId);
2444
+ log(`stopped session #${sessionId}`);
2445
+ };
2446
+ const onDelete = (sessionId, worktreePath) => {
2447
+ const entry = children.get(sessionId);
2448
+ if (entry) {
2449
+ killChild(entry.child);
2450
+ children.delete(sessionId);
2451
+ }
2452
+ const wt = entry?.worktreePath ?? worktreePath;
2453
+ if (wt) removeWorktree(repo, wt);
2454
+ log(`deleted session #${sessionId} (removed worktree)`);
2455
+ };
2456
+ const onTitle = async (sessionId, agentSessionId, worktreePath) => {
2457
+ const exchange = readFirstExchange(agentSessionId);
2458
+ if (!exchange) return log(`title #${sessionId}: no transcript yet, skipping`);
2459
+ const title = await generateTitle({
2460
+ worktreePath,
2461
+ userText: exchange.userText,
2462
+ assistantText: exchange.assistantText,
2463
+ spawnImpl: spawn2
2464
+ });
2465
+ if (!title) return log(`title #${sessionId}: not enough to title yet, skipping`);
2466
+ await client.sessions.setName(sessionId, title, cfg.apiToken);
2467
+ log(`\u270E titled session #${sessionId} \u2192 ${title}`);
2468
+ };
2469
+ const reconcile = async () => {
2470
+ const owned = (await client.sessions.listByProject(cfg.projectId)).filter(
2471
+ (s) => s.workerId === cfg.workerId
2472
+ );
2473
+ const live = new Set(owned.map((s) => s.id));
2474
+ for (const [sid, entry] of children) {
2475
+ if (!live.has(sid)) {
2476
+ killChild(entry.child);
2477
+ children.delete(sid);
2478
+ log(`reconcile: stopped orphan session #${sid}`);
2479
+ }
2480
+ }
2481
+ };
2482
+ const es = new EventSource(`${cfg.server}/workers/me/stream`, {
2483
+ fetch: (url, init2) => fetch(url, {
2484
+ ...init2,
2485
+ headers: {
2486
+ ...init2?.headers,
2487
+ authorization: `Bearer ${cfg.apiToken}`
2488
+ }
2489
+ })
2490
+ });
2491
+ es.onopen = () => void reconcile().catch((err) => log(`reconcile failed: ${String(err)}`));
2492
+ es.onmessage = (e) => {
2493
+ try {
2494
+ const cmd = JSON.parse(e.data);
2495
+ if (cmd.cmd === "session.start")
2496
+ void onStart(cmd.sessionId, cmd.name).catch((err) => log(`start failed: ${String(err)}`));
2497
+ else if (cmd.cmd === "session.stop") onStop(cmd.sessionId);
2498
+ else if (cmd.cmd === "session.delete") onDelete(cmd.sessionId, cmd.worktreePath);
2499
+ else if (cmd.cmd === "session.title")
2500
+ void onTitle(cmd.sessionId, cmd.agentSessionId, cmd.worktreePath).catch(
2501
+ (err) => log(`title failed: ${String(err)}`)
2502
+ );
2503
+ } catch {
2504
+ }
2505
+ };
2506
+ es.onerror = () => log("command stream error (eventsource will retry)");
2507
+ log(`listening for commands (server ${cfg.server}, repo ${repo})`);
2508
+ await new Promise((resolve) => {
2509
+ if (signal.aborted) return resolve();
2510
+ signal.addEventListener("abort", () => resolve(), { once: true });
2511
+ });
2512
+ es.close();
2513
+ clearInterval(hb);
2514
+ for (const { child } of children.values()) killChild(child);
2515
+ };
2516
+
2517
+ // src/worker/machine-id.ts
2518
+ import { randomUUID as randomUUID2 } from "node:crypto";
2519
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
2520
+ import { homedir as homedir3 } from "node:os";
2521
+ import { dirname as dirname3, join as join6 } from "node:path";
2522
+ var machineIdPath = (env = process.env) => join6(env.XDG_DATA_HOME ?? join6(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
2523
+ var readOrCreateMachineId = (path = machineIdPath()) => {
2524
+ if (existsSync5(path)) {
2525
+ const s = readFileSync3(path, "utf8").trim();
2526
+ if (s) return s;
2527
+ }
2528
+ const id = randomUUID2();
2529
+ mkdirSync2(dirname3(path), { recursive: true });
2530
+ writeFileSync2(path, `${id}
2531
+ `, "utf8");
2532
+ return id;
2533
+ };
2534
+
2535
+ // src/commands/worker.ts
2536
+ var resolveWorker = async (client, projectId, handle) => {
2537
+ const asInt = Number(handle);
2538
+ if (Number.isInteger(asInt) && asInt > 0) {
2539
+ const byId = await client.workers.get(asInt).catch(() => null);
2540
+ if (byId) return { id: byId.id, name: byId.name };
2541
+ }
2542
+ const byName = await client.workers.findByName(projectId, handle);
2543
+ if (byName) return { id: byName.id, name: byName.name };
2544
+ throw new Error(`worker "${handle}" not found in project ${projectId}`);
2545
+ };
2546
+ var registerWorker = async (client, input, cfgPath = projectConfigPath()) => {
2547
+ const out = await client.workers.register({
2548
+ projectId: input.projectId,
2549
+ machineId: input.machineId,
2550
+ name: input.name,
2551
+ hostname: input.hostname
2552
+ });
2553
+ try {
2554
+ loadProjectConfig(cfgPath);
2555
+ } catch {
2556
+ saveProjectConfig(cfgPath, { server: input.server, project: input.projectId });
2557
+ }
2558
+ setWorker(cfgPath, {
2559
+ id: out.worker.id,
2560
+ name: out.worker.name,
2561
+ machineId: out.worker.machineId,
2562
+ apiToken: out.apiToken
2563
+ });
2564
+ return { out, configPath: cfgPath };
2565
+ };
2566
+ var worker = defineCommand({
2567
+ meta: { name: "worker", description: "register / inspect / close workers for a project" },
2568
+ subCommands: {
2569
+ register: defineCommand({
2570
+ meta: {
2571
+ name: "register",
2572
+ description: "register this machine as a worker for a project (idempotent)"
2573
+ },
2574
+ args: {
2575
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2576
+ name: { type: "string", description: "worker display name (default: hostname)" },
2577
+ ...common
2578
+ },
2579
+ run: async ({ args }) => {
2580
+ const server = resolveBaseUrl(args.url);
2581
+ const c = clientFor(args);
2582
+ const hostname = osHostname();
2583
+ const machineId = readOrCreateMachineId();
2584
+ const name = args.name ?? hostname;
2585
+ const { out, configPath } = await registerWorker(c, {
2586
+ projectId: resolveProjectId(args),
2587
+ name,
2588
+ server,
2589
+ hostname,
2590
+ machineId
2591
+ });
2592
+ if (args.json) {
2593
+ console.log(toJson({ ...out, configPath, machineIdPath: machineIdPath() }));
2594
+ return;
2595
+ }
2596
+ console.log(`worker #${out.worker.id} (${out.worker.name}) \u2014 ${out.outcome}`);
2597
+ console.log(` hostname: ${out.worker.hostname}`);
2598
+ console.log(` machineId: ${out.worker.machineId}`);
2599
+ console.log(` machineId file: ${machineIdPath()}`);
2600
+ console.log(` config saved: ${configPath}`);
2601
+ }
2602
+ }),
2603
+ run: defineCommand({
2604
+ meta: {
2605
+ name: "run",
2606
+ description: "run the persistent worker daemon (listens for session create/delete commands)"
2607
+ },
2608
+ args: { ...common },
2609
+ run: async () => {
2610
+ const cfg = viewWorker(loadProjectConfig(projectConfigPath()));
2611
+ const client = createClient(cfg.server, { bearer: cfg.apiToken });
2612
+ const ac = new AbortController();
2613
+ const stop = () => ac.abort();
2614
+ process.on("SIGINT", stop);
2615
+ process.on("SIGTERM", stop);
2616
+ await runWorkerDaemon(cfg, client, ac.signal);
2617
+ }
2618
+ }),
2619
+ ls: defineCommand({
2620
+ meta: { name: "ls", description: "list workers in a project" },
2621
+ args: {
2622
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2623
+ ...common
2624
+ },
2625
+ run: async ({ args }) => {
2626
+ const c = clientFor(args);
2627
+ const ws = await c.workers.listByProject(resolveProjectId(args));
2628
+ console.log(renderList(ws, fmtWorker, Boolean(args.json)));
2629
+ }
2630
+ }),
2631
+ get: defineCommand({
2632
+ meta: { name: "get", description: "get a worker by int id or name" },
2633
+ args: {
2634
+ worker: { type: "positional", required: true, description: "worker int id or name" },
2635
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2636
+ ...common
2637
+ },
2638
+ run: async ({ args }) => {
2639
+ const c = clientFor(args);
2640
+ const projectId = resolveProjectId(args);
2641
+ const handle = await resolveWorker(c, projectId, args.worker);
2642
+ const w = await c.workers.get(handle.id);
2643
+ console.log(renderOne(w, fmtWorker, Boolean(args.json)));
2644
+ }
2645
+ }),
2646
+ destroy: defineCommand({
2647
+ meta: {
2648
+ name: "destroy",
2649
+ description: "permanently delete a worker and its sessions (irreversible)"
2650
+ },
2651
+ args: {
2652
+ worker: { type: "positional", required: true, description: "worker int id or name" },
2653
+ project: { type: "string", description: "project id (overrides .baton.json)" },
2654
+ confirm: { type: "boolean", description: "actually perform the deletion" },
2655
+ ...common
2656
+ },
2657
+ run: async ({ args }) => {
2658
+ const c = clientFor(args);
2659
+ const projectId = resolveProjectId(args);
2660
+ const handle = await resolveWorker(c, projectId, args.worker);
2661
+ const sessions = (await c.sessions.listByProject(projectId)).filter(
2662
+ (s) => s.workerId === handle.id
2663
+ );
2664
+ if (!args.confirm) {
2665
+ console.log(`[dry-run] would destroy worker ${handle.name} (#${handle.id}):`);
2666
+ console.log(` - ${sessions.length} session(s) cascade-deleted`);
2667
+ console.log(" - browser-local event history: NOT touched");
2668
+ console.log("re-run with --confirm to proceed.");
2669
+ return;
2670
+ }
2671
+ await c.workers.destroy(handle.id);
2672
+ console.log(
2673
+ `destroyed worker ${handle.name} (#${handle.id}); ${sessions.length} session(s) gone`
2674
+ );
2675
+ }
2676
+ }),
2677
+ whoami: defineCommand({
2678
+ meta: { name: "whoami", description: "show local worker config from .baton.json" },
2679
+ args: { ...common },
2680
+ run: ({ args }) => {
2681
+ const cfg = (() => {
2682
+ try {
2683
+ return loadProjectConfig(projectConfigPath());
2684
+ } catch {
2685
+ return null;
2686
+ }
2687
+ })();
2688
+ const worker2 = cfg?.worker;
2689
+ if (!worker2) {
2690
+ console.log("(no worker registered for this project \u2014 run `baton worker register`)");
2691
+ return;
2692
+ }
2693
+ if (args.json) {
2694
+ console.log(toJson({ ...worker2, server: cfg?.server, projectId: cfg?.project }));
2695
+ return;
2696
+ }
2697
+ console.log(`worker #${worker2.id} (${worker2.name})`);
2698
+ console.log(` machineId: ${worker2.machineId}`);
2699
+ console.log(` server: ${cfg?.server}`);
2700
+ }
2701
+ })
2702
+ }
2703
+ });
2704
+
2705
+ // src/commands/workspace.ts
2706
+ var createWorkspace = (c, name, json) => c.workspaces.create({ name }).then((w) => renderOne(w, fmtWorkspace, json));
2707
+ var listWorkspaces = (c, json) => c.workspaces.list().then((ws) => renderList(ws, fmtWorkspace, json));
2708
+ var getWorkspace = (c, id, json) => c.workspaces.get(id).then((w) => renderOne(w, fmtWorkspace, json));
2709
+ var updateWorkspace = (c, id, name, json) => c.workspaces.update(id, { name }).then((w) => renderOne(w, fmtWorkspace, json));
2710
+ var removeWorkspace = (c, id, json) => c.workspaces.remove(id).then(() => removed("workspace", id, json));
2711
+ var workspace = defineCommand({
2712
+ meta: { name: "workspace", description: "manage workspaces" },
2713
+ subCommands: {
2714
+ create: defineCommand({
2715
+ meta: { name: "create", description: "create a workspace" },
2716
+ args: {
2717
+ name: { type: "positional", required: true, description: "workspace name" },
2718
+ ...common
2719
+ },
2720
+ run: async ({ args }) => {
2721
+ console.log(await createWorkspace(clientFor(args), args.name, Boolean(args.json)));
2722
+ }
2723
+ }),
2724
+ ls: defineCommand({
2725
+ meta: { name: "ls", description: "list workspaces" },
2726
+ args: { ...common },
2727
+ run: async ({ args }) => {
2728
+ console.log(await listWorkspaces(clientFor(args), Boolean(args.json)));
2729
+ }
2730
+ }),
2731
+ get: defineCommand({
2732
+ meta: { name: "get", description: "get a workspace" },
2733
+ args: { id: { type: "positional", required: true }, ...common },
2734
+ run: async ({ args }) => {
2735
+ console.log(await getWorkspace(clientFor(args), Number(args.id), Boolean(args.json)));
2736
+ }
2737
+ }),
2738
+ update: defineCommand({
2739
+ meta: { name: "update", description: "rename a workspace" },
2740
+ args: {
2741
+ id: { type: "positional", required: true },
2742
+ name: { type: "string", description: "new name" },
2743
+ ...common
2744
+ },
2745
+ run: async ({ args }) => {
2746
+ if (!args.name) throw new Error("pass --name <new name>");
2747
+ console.log(
2748
+ await updateWorkspace(clientFor(args), Number(args.id), args.name, Boolean(args.json))
2749
+ );
2750
+ }
2751
+ }),
2752
+ rm: defineCommand({
2753
+ meta: { name: "rm", description: "delete a workspace" },
2754
+ args: { id: { type: "positional", required: true }, ...common },
2755
+ run: async ({ args }) => {
2756
+ console.log(await removeWorkspace(clientFor(args), Number(args.id), Boolean(args.json)));
2757
+ }
2758
+ })
2759
+ }
2760
+ });
2761
+
2762
+ // src/index.ts
2763
+ var main = defineCommand({
2764
+ meta: {
2765
+ name: "baton",
2766
+ description: "baton \u2014 run a worker daemon; manage projects / requirements / tasks / sessions"
2767
+ },
2768
+ // Order matters: high-level verbs first so --help shows them on top.
2769
+ subCommands: {
2770
+ init,
2771
+ worker,
2772
+ session,
2773
+ workspace,
2774
+ project,
2775
+ requirement,
2776
+ task
2777
+ }
2778
+ });
2779
+ runMain(main);