@lesscap/baton-cli 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/baton.mjs +688 -457
  2. package/package.json +1 -1
package/dist/baton.mjs CHANGED
@@ -691,7 +691,8 @@ var createWorkerClient = (baseUrl, workerToken, sessionId) => {
691
691
  method: "POST",
692
692
  body: { active },
693
693
  headers: auth
694
- })
694
+ }),
695
+ listEvents: () => request(u(`/sessions/${sessionId}/events`), { method: "GET", headers: auth })
695
696
  };
696
697
  };
697
698
 
@@ -956,331 +957,56 @@ var project = defineCommand({
956
957
  }
957
958
  });
958
959
 
959
- // src/commands/requirement.ts
960
- var createRequirement = (c, input, json) => c.requirements.create(input).then((r) => renderOne(r, fmtRequirement, json));
961
- var updateRequirement = (c, id, patch2, json) => c.requirements.update(id, patch2).then((r) => renderOne(r, fmtRequirement, json));
962
- var listRequirements = (c, projectId, json) => c.requirements.listByProject(projectId).then((rs) => renderList(rs, fmtRequirement, json));
963
- var getRequirement = (c, id, json) => c.requirements.get(id).then((r) => renderOne(r, fmtRequirement, json));
964
- var setRequirementStatus = (c, id, status, json) => c.requirements.setStatus(id, status).then((r) => renderOne(r, fmtRequirement, json));
965
- var removeRequirement = (c, id, label, json) => c.requirements.remove(id).then(() => removed("requirement", label, json));
966
- var resolveByCode = async (c, projectId, code) => (await c.requirements.getByCode(projectId, code)).id;
967
- var requirement = defineCommand({
968
- meta: { name: "requirement", description: "manage requirements" },
969
- subCommands: {
970
- create: defineCommand({
971
- meta: { name: "create", description: "create a requirement" },
972
- args: {
973
- title: { type: "positional", required: true },
974
- project: { type: "string", description: "project id (overrides .baton.json)" },
975
- desc: { type: "string", description: "description" },
976
- body: { type: "string", description: "requirement body (markdown)" },
977
- ...common
978
- },
979
- run: async ({ args }) => {
980
- console.log(
981
- await createRequirement(
982
- clientFor(args),
983
- {
984
- projectId: resolveProjectId(args),
985
- title: args.title,
986
- description: args.desc,
987
- body: args.body
988
- },
989
- Boolean(args.json)
990
- )
991
- );
992
- }
993
- }),
994
- update: defineCommand({
995
- meta: { name: "update", description: "update a requirement title / body" },
996
- args: {
997
- code: { type: "positional", required: true, description: "requirement code (R-N)" },
998
- title: { type: "string", description: "new title" },
999
- body: { type: "string", description: "new body (markdown)" },
1000
- project: { type: "string", description: "project id (overrides .baton.json)" },
1001
- ...common
1002
- },
1003
- run: async ({ args }) => {
1004
- const patch2 = {
1005
- ...args.title !== void 0 ? { title: args.title } : {},
1006
- ...args.body !== void 0 ? { body: args.body } : {}
1007
- };
1008
- if (Object.keys(patch2).length === 0) throw new Error("pass --title and/or --body");
1009
- const c = clientFor(args);
1010
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
1011
- console.log(await updateRequirement(c, id, patch2, Boolean(args.json)));
1012
- }
1013
- }),
1014
- ls: defineCommand({
1015
- meta: { name: "ls", description: "list requirements in a project" },
1016
- args: {
1017
- project: { type: "string", description: "project id (overrides .baton.json)" },
1018
- ...common
1019
- },
1020
- run: async ({ args }) => {
1021
- console.log(
1022
- await listRequirements(clientFor(args), resolveProjectId(args), Boolean(args.json))
1023
- );
1024
- }
1025
- }),
1026
- get: defineCommand({
1027
- meta: { name: "get", description: "get a requirement by code (R-N)" },
1028
- args: {
1029
- code: { type: "positional", required: true, description: "requirement code, e.g. R-1" },
1030
- project: { type: "string", description: "project id (overrides .baton.json)" },
1031
- ...common
1032
- },
1033
- run: async ({ args }) => {
1034
- const c = clientFor(args);
1035
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
1036
- console.log(await getRequirement(c, id, Boolean(args.json)));
1037
- }
1038
- }),
1039
- "set-status": defineCommand({
1040
- meta: { name: "set-status", description: "set requirement status (active|done|cancelled)" },
1041
- args: {
1042
- code: { type: "positional", required: true, description: "requirement code (R-N)" },
1043
- status: { type: "positional", required: true },
1044
- project: { type: "string", description: "project id (overrides .baton.json)" },
1045
- ...common
1046
- },
1047
- run: async ({ args }) => {
1048
- const c = clientFor(args);
1049
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
1050
- console.log(
1051
- await setRequirementStatus(c, id, args.status, Boolean(args.json))
1052
- );
1053
- }
1054
- }),
1055
- rm: defineCommand({
1056
- meta: { name: "rm", description: "delete a requirement by code" },
1057
- args: {
1058
- code: { type: "positional", required: true },
1059
- project: { type: "string", description: "project id (overrides .baton.json)" },
1060
- ...common
1061
- },
1062
- run: async ({ args }) => {
1063
- const c = clientFor(args);
1064
- const id = await resolveByCode(c, resolveProjectId(args), args.code);
1065
- console.log(await removeRequirement(c, id, args.code, Boolean(args.json)));
1066
- }
1067
- })
1068
- }
1069
- });
1070
-
1071
- // src/commands/session/shared.ts
1072
- import { homedir } from "node:os";
960
+ // src/commands/relay.ts
961
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
962
+ import { tmpdir } from "node:os";
1073
963
  import { join as join2 } from "node:path";
1074
- var defaultWorktreeDir = (env = process.env) => env.BATON_WORKTREE_DIR ?? join2(env.XDG_DATA_HOME ?? join2(env.HOME ?? homedir(), ".local/share"), "baton", "worktrees");
1075
- var slug = (s) => s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
1076
- var resolveSession = async (client, projectId, handle) => {
1077
- const asInt = Number(handle);
1078
- if (Number.isInteger(asInt) && asInt > 0) {
1079
- const byId = await client.sessions.get(asInt).catch(() => null);
1080
- if (byId) return { id: byId.id, name: byId.name };
1081
- }
1082
- const byName = await client.sessions.findByName(projectId, handle);
1083
- if (byName) return { id: byName.id, name: byName.name };
1084
- throw new Error(`session "${handle}" not found in project ${projectId}`);
1085
- };
1086
-
1087
- // src/commands/session/abort.ts
1088
- var sessionAbortCommand = defineCommand({
1089
- meta: {
1090
- name: "abort",
1091
- description: "interrupt the in-flight turn (like Esc); keeps the session"
1092
- },
1093
- args: {
1094
- session: { type: "positional", required: true, description: "session int id or name" },
1095
- project: { type: "string", description: "project id (overrides .baton.json)" },
1096
- ...common
1097
- },
1098
- run: async ({ args }) => {
1099
- const c = clientFor(args);
1100
- const handle = await resolveSession(c, resolveProjectId(args), args.session);
1101
- const s = await c.sessions.abort(handle.id);
1102
- console.log(renderOne(s, fmtSession, Boolean(args.json)));
1103
- }
1104
- });
1105
964
 
1106
- // src/commands/session/clear.ts
1107
- var sessionClearCommand = defineCommand({
1108
- meta: {
1109
- name: "clear",
1110
- description: "reset the conversation context (keeps the session + worktree)"
1111
- },
1112
- args: {
1113
- session: { type: "positional", required: true, description: "session int id or name" },
1114
- project: { type: "string", description: "project id (overrides .baton.json)" },
1115
- ...common
1116
- },
1117
- run: async ({ args }) => {
1118
- const c = clientFor(args);
1119
- const handle = await resolveSession(c, resolveProjectId(args), args.session);
1120
- const s = await c.sessions.clear(handle.id);
1121
- console.log(renderOne(s, fmtSession, Boolean(args.json)));
965
+ // ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js
966
+ var ParseError = class extends Error {
967
+ constructor(message, options) {
968
+ super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
1122
969
  }
1123
- });
1124
-
1125
- // src/commands/session/create.ts
1126
- var sessionCreateCommand = defineCommand({
1127
- meta: { name: "create", description: "create + start a session on a worker" },
1128
- args: {
1129
- name: { type: "positional", required: true, description: "session name" },
1130
- worker: {
1131
- type: "string",
1132
- description: "worker handle: W-N or int id (default: this machine's worker)"
1133
- },
1134
- project: {
1135
- type: "string",
1136
- description: "project id (default: the worker's own project, else .baton.json)"
1137
- },
1138
- ...common
1139
- },
1140
- run: async ({ args }) => {
1141
- const c = clientFor(args);
1142
- const workerId = args.worker ? parseWorkerHandle(args.worker) : loadProjectConfigOrNull(projectConfigPath())?.worker?.id ?? null;
1143
- if (workerId === null)
970
+ };
971
+ var LF = 10;
972
+ var CR = 13;
973
+ var SPACE = 32;
974
+ function noop(_arg) {
975
+ }
976
+ function createParser(config) {
977
+ if (typeof config == "function")
978
+ throw new TypeError(
979
+ "`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
980
+ );
981
+ const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
982
+ let pendingFragmentsLength = 0, isFirstChunk = true, id, data = "", dataLines = 0, eventType, terminated = false;
983
+ function feed(chunk) {
984
+ if (terminated)
1144
985
  throw new Error(
1145
- "no worker \u2014 pass --worker <W-N|id> or run `baton worker register` first (names need --project)"
986
+ "Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
1146
987
  );
1147
- const projectId = args.project !== void 0 && args.project !== "" ? Number(args.project) : args.worker ? (await c.workers.get(workerId)).projectId : resolveProjectId(args);
1148
- const s = await c.sessions.create({ projectId, workerId, name: args.name });
1149
- console.log(renderOne(s, fmtSession, Boolean(args.json)));
988
+ if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
989
+ const trailing2 = processLines(chunk);
990
+ trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
991
+ return;
992
+ }
993
+ if (chunk.indexOf(`
994
+ `) === -1 && chunk.indexOf("\r") === -1) {
995
+ pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
996
+ return;
997
+ }
998
+ pendingFragments.push(chunk);
999
+ const input = pendingFragments.join("");
1000
+ pendingFragments.length = 0, pendingFragmentsLength = 0;
1001
+ const trailing = processLines(input);
1002
+ trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
1150
1003
  }
1151
- });
1152
-
1153
- // src/commands/session/get.ts
1154
- var sessionGetCommand = defineCommand({
1155
- meta: { name: "get", description: "get a session by int id or name" },
1156
- args: {
1157
- session: { type: "positional", required: true, description: "session int id or name" },
1158
- project: { type: "string", description: "project id (overrides .baton.json)" },
1159
- ...common
1160
- },
1161
- run: async ({ args }) => {
1162
- const c = clientFor(args);
1163
- const projectId = resolveProjectId(args);
1164
- const handle = await resolveSession(c, projectId, args.session);
1165
- const s = await c.sessions.get(handle.id);
1166
- console.log(renderOne(s, fmtSession, Boolean(args.json)));
1167
- }
1168
- });
1169
-
1170
- // src/commands/session/ls.ts
1171
- var sessionLsCommand = defineCommand({
1172
- meta: { name: "ls", description: "list sessions in a project" },
1173
- args: {
1174
- project: { type: "string", description: "project id (overrides .baton.json)" },
1175
- ...common
1176
- },
1177
- run: async ({ args }) => {
1178
- const c = clientFor(args);
1179
- const ss = await c.sessions.listByProject(resolveProjectId(args));
1180
- console.log(renderList(ss, fmtSession, Boolean(args.json)));
1181
- }
1182
- });
1183
-
1184
- // src/commands/session/rename.ts
1185
- var sessionRenameCommand = defineCommand({
1186
- meta: { name: "rename", description: "rename a session (locks the name against auto-title)" },
1187
- args: {
1188
- session: { type: "positional", required: true, description: "session int id or name" },
1189
- name: { type: "positional", required: true, description: "new name" },
1190
- project: { type: "string", description: "project id (overrides .baton.json)" },
1191
- ...common
1192
- },
1193
- run: async ({ args }) => {
1194
- const c = clientFor(args);
1195
- const handle = await resolveSession(c, resolveProjectId(args), args.session);
1196
- const s = await c.sessions.rename(handle.id, args.name);
1197
- console.log(renderOne(s, fmtSession, Boolean(args.json)));
1198
- }
1199
- });
1200
-
1201
- // src/commands/session/resume.ts
1202
- var sessionResumeCommand = defineCommand({
1203
- meta: { name: "resume", description: "resume (re-start) an existing session" },
1204
- args: {
1205
- session: { type: "positional", required: true, description: "session int id or name" },
1206
- project: { type: "string", description: "project id (overrides .baton.json)" },
1207
- ...common
1208
- },
1209
- run: async ({ args }) => {
1210
- const c = clientFor(args);
1211
- const handle = await resolveSession(c, resolveProjectId(args), args.session);
1212
- const s = await c.sessions.resume(handle.id);
1213
- console.log(renderOne(s, fmtSession, Boolean(args.json)));
1214
- }
1215
- });
1216
-
1217
- // src/commands/session/rm.ts
1218
- var sessionRmCommand = defineCommand({
1219
- meta: { name: "rm", description: "delete a session (irreversible; removes its worktree)" },
1220
- args: {
1221
- session: { type: "positional", required: true, description: "session int id or name" },
1222
- project: { type: "string", description: "project id (overrides .baton.json)" },
1223
- confirm: { type: "boolean", description: "actually perform the deletion" },
1224
- ...common
1225
- },
1226
- run: async ({ args }) => {
1227
- const c = clientFor(args);
1228
- const handle = await resolveSession(c, resolveProjectId(args), args.session);
1229
- if (!args.confirm) {
1230
- console.log(`[dry-run] would delete session ${handle.name} (#${handle.id}) + its worktree`);
1231
- console.log("re-run with --confirm to proceed.");
1232
- return;
1233
- }
1234
- await c.sessions.destroy(handle.id);
1235
- console.log(`deleted session ${handle.name} (#${handle.id})`);
1236
- }
1237
- });
1238
-
1239
- // ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js
1240
- var ParseError = class extends Error {
1241
- constructor(message, options) {
1242
- super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
1243
- }
1244
- };
1245
- var LF = 10;
1246
- var CR = 13;
1247
- var SPACE = 32;
1248
- function noop(_arg) {
1249
- }
1250
- function createParser(config) {
1251
- if (typeof config == "function")
1252
- throw new TypeError(
1253
- "`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
1254
- );
1255
- const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
1256
- let pendingFragmentsLength = 0, isFirstChunk = true, id, data = "", dataLines = 0, eventType, terminated = false;
1257
- function feed(chunk) {
1258
- if (terminated)
1259
- throw new Error(
1260
- "Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
1261
- );
1262
- if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
1263
- const trailing2 = processLines(chunk);
1264
- trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
1265
- return;
1266
- }
1267
- if (chunk.indexOf(`
1268
- `) === -1 && chunk.indexOf("\r") === -1) {
1269
- pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
1270
- return;
1271
- }
1272
- pendingFragments.push(chunk);
1273
- const input = pendingFragments.join("");
1274
- pendingFragments.length = 0, pendingFragmentsLength = 0;
1275
- const trailing = processLines(input);
1276
- trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
1277
- }
1278
- function checkBufferSize() {
1279
- maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
1280
- new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
1281
- type: "max-buffer-size-exceeded"
1282
- })
1283
- )));
1004
+ function checkBufferSize() {
1005
+ maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
1006
+ new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
1007
+ type: "max-buffer-size-exceeded"
1008
+ })
1009
+ )));
1284
1010
  }
1285
1011
  function processLines(chunk) {
1286
1012
  let searchIndex = 0;
@@ -1608,117 +1334,595 @@ var EventSource = class extends EventTarget {
1608
1334
  get onmessage() {
1609
1335
  return __privateGet(this, _onMessage);
1610
1336
  }
1611
- set onmessage(value) {
1612
- __privateSet(this, _onMessage, value);
1337
+ set onmessage(value) {
1338
+ __privateSet(this, _onMessage, value);
1339
+ }
1340
+ /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
1341
+ get onopen() {
1342
+ return __privateGet(this, _onOpen);
1343
+ }
1344
+ set onopen(value) {
1345
+ __privateSet(this, _onOpen, value);
1346
+ }
1347
+ addEventListener(type, listener, options) {
1348
+ const listen = listener;
1349
+ super.addEventListener(type, listen, options);
1350
+ }
1351
+ removeEventListener(type, listener, options) {
1352
+ const listen = listener;
1353
+ super.removeEventListener(type, listen, options);
1354
+ }
1355
+ /**
1356
+ * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
1357
+ *
1358
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
1359
+ *
1360
+ * @public
1361
+ */
1362
+ close() {
1363
+ __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));
1364
+ }
1365
+ };
1366
+ _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(), /**
1367
+ * Connect to the given URL and start receiving events
1368
+ *
1369
+ * @internal
1370
+ */
1371
+ connect_fn = function() {
1372
+ __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));
1373
+ }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
1374
+ * Get request options for the `fetch()` request
1375
+ *
1376
+ * @returns The request options
1377
+ * @internal
1378
+ */
1379
+ getRequestOptions_fn = function() {
1380
+ var _a;
1381
+ const init2 = {
1382
+ // [spec] Let `corsAttributeState` be `Anonymous`…
1383
+ // [spec] …will have their mode set to "cors"…
1384
+ mode: "cors",
1385
+ redirect: "follow",
1386
+ headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
1387
+ cache: "no-store",
1388
+ signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
1389
+ };
1390
+ return "window" in globalThis && (init2.credentials = this.withCredentials ? "include" : "same-origin"), init2;
1391
+ }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
1392
+ * Handles the process referred to in the EventSource specification as "failing a connection".
1393
+ *
1394
+ * @param error - The error causing the connection to fail
1395
+ * @param code - The HTTP status code, if available
1396
+ * @internal
1397
+ */
1398
+ failConnection_fn = function(message, code) {
1399
+ var _a;
1400
+ __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
1401
+ const errorEvent = new ErrorEvent("error", { code, message });
1402
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
1403
+ }, /**
1404
+ * Schedules a reconnection attempt against the EventSource endpoint.
1405
+ *
1406
+ * @param message - The error causing the connection to fail
1407
+ * @param code - The HTTP status code, if available
1408
+ * @internal
1409
+ */
1410
+ scheduleReconnect_fn = function(message, code) {
1411
+ var _a;
1412
+ if (__privateGet(this, _readyState) === this.CLOSED)
1413
+ return;
1414
+ __privateSet(this, _readyState, this.CONNECTING);
1415
+ const errorEvent = new ErrorEvent("error", { code, message });
1416
+ (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
1417
+ }, _reconnect = /* @__PURE__ */ new WeakMap(), /**
1418
+ * ReadyState representing an EventSource currently trying to connect
1419
+ *
1420
+ * @public
1421
+ */
1422
+ EventSource.CONNECTING = 0, /**
1423
+ * ReadyState representing an EventSource connection that is open (eg connected)
1424
+ *
1425
+ * @public
1426
+ */
1427
+ EventSource.OPEN = 1, /**
1428
+ * ReadyState representing an EventSource connection that is closed (eg disconnected)
1429
+ *
1430
+ * @public
1431
+ */
1432
+ EventSource.CLOSED = 2;
1433
+ Object.defineProperty(EventSource, /* @__PURE__ */ Symbol.for("eventsource.supports-fetch-override"), {
1434
+ value: true,
1435
+ writable: false,
1436
+ configurable: false,
1437
+ enumerable: false
1438
+ });
1439
+ function getBaseURL() {
1440
+ const doc = "document" in globalThis ? globalThis.document : void 0;
1441
+ return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
1442
+ }
1443
+
1444
+ // src/client/relay.ts
1445
+ var relayClient = (baseUrl) => {
1446
+ const u = (p) => `${baseUrl}${p}`;
1447
+ const authed = (token) => ({ authorization: `Bearer ${token}` });
1448
+ return {
1449
+ create: async () => {
1450
+ const res = await fetch(u("/relay/channels"), { method: "POST" });
1451
+ if (!res.ok) throw new Error(`POST /relay/channels \u2192 ${res.status}: ${await res.text()}`);
1452
+ return await res.json();
1453
+ },
1454
+ send: async (channelId, token, msg) => {
1455
+ const res = await fetch(u(`/relay/channels/${channelId}/messages`), {
1456
+ method: "POST",
1457
+ headers: { "content-type": "application/json", ...authed(token) },
1458
+ body: JSON.stringify(msg)
1459
+ });
1460
+ if (!res.ok) throw new Error(`POST relay message \u2192 ${res.status}: ${await res.text()}`);
1461
+ return await res.json();
1462
+ },
1463
+ listen: (channelId, token, opts) => {
1464
+ let cursor = opts.since ?? 0;
1465
+ const es = new EventSource(u(`/relay/channels/${channelId}/stream?since=${cursor}`), {
1466
+ fetch: (url, init2) => fetch(url, {
1467
+ ...init2,
1468
+ headers: { ...init2?.headers, ...authed(token) }
1469
+ })
1470
+ });
1471
+ es.onmessage = (e) => {
1472
+ try {
1473
+ const m = JSON.parse(e.data);
1474
+ if (m.seq <= cursor) return;
1475
+ cursor = m.seq;
1476
+ opts.onMessage(m);
1477
+ } catch (err) {
1478
+ opts.onError?.(err);
1479
+ }
1480
+ };
1481
+ es.onerror = (err) => opts.onError?.(err);
1482
+ return () => es.close();
1483
+ }
1484
+ };
1485
+ };
1486
+
1487
+ // src/commands/relay.ts
1488
+ var PREVIEW = 280;
1489
+ var readStdin = async () => {
1490
+ const chunks = [];
1491
+ for await (const c of process.stdin) chunks.push(c);
1492
+ return Buffer.concat(chunks).toString("utf8");
1493
+ };
1494
+ var inviteBlock = (url, channelId, token) => [
1495
+ "\u2500\u2500 Copy everything below to your peer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
1496
+ "You're invited to a live Claude\u2194Claude channel. Use the `hotline` skill in",
1497
+ "join mode, or run these two commands directly (pick your own NAME):",
1498
+ "",
1499
+ ` baton relay listen ${channelId} --token ${token} --from NAME --url ${url}`,
1500
+ ` baton relay send ${channelId} --token ${token} --from NAME --text "..." --url ${url}`,
1501
+ "",
1502
+ `connection: url=${url} channel=${channelId} token=${token}`,
1503
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
1504
+ ].join("\n");
1505
+ var hostCommand = defineCommand({
1506
+ meta: { name: "host", description: "open a relay channel and print an invite for a peer" },
1507
+ args: {
1508
+ name: { type: "string", description: "your participant name (default: host)" },
1509
+ ...common
1510
+ },
1511
+ run: async ({ args }) => {
1512
+ const url = resolveBaseUrl(args.url);
1513
+ const { channelId, token } = await relayClient(url).create();
1514
+ if (args.json) {
1515
+ console.log(toJson({ channelId, token, url, host: args.name ?? "host" }));
1516
+ return;
1517
+ }
1518
+ console.log(`channel ${channelId} opened
1519
+ `);
1520
+ console.log(inviteBlock(url, channelId, token));
1521
+ }
1522
+ });
1523
+ var sendCommand = defineCommand({
1524
+ meta: { name: "send", description: "post one message to a relay channel" },
1525
+ args: {
1526
+ channel: { type: "positional", required: true, description: "channel id" },
1527
+ text: { type: "positional", required: false, description: "message text (short one-liner)" },
1528
+ token: { type: "string", required: true, description: "channel token" },
1529
+ from: { type: "string", description: "your participant name (default: peer)" },
1530
+ file: { type: "string", description: "read message body from this file (for large content)" },
1531
+ ...common
1532
+ },
1533
+ run: async ({ args }) => {
1534
+ const url = resolveBaseUrl(args.url);
1535
+ let text = args.text;
1536
+ if (text === void 0 && args.file) text = readFileSync2(args.file, "utf8");
1537
+ if (text === void 0 && !process.stdin.isTTY) text = await readStdin();
1538
+ if (text === void 0 || text === "")
1539
+ throw new Error("nothing to send: provide text, --file <path>, or pipe stdin");
1540
+ const msg = await relayClient(url).send(args.channel, args.token, {
1541
+ from: args.from ?? "peer",
1542
+ text
1543
+ });
1544
+ if (args.json) console.log(toJson(msg));
1545
+ else console.log(`sent (seq ${msg.seq})`);
1546
+ }
1547
+ });
1548
+ var listenCommand = defineCommand({
1549
+ meta: {
1550
+ name: "listen",
1551
+ description: "stream a channel: one JSON line per peer message (for a background subscriber)"
1552
+ },
1553
+ args: {
1554
+ channel: { type: "positional", required: true, description: "channel id" },
1555
+ token: { type: "string", required: true, description: "channel token" },
1556
+ from: {
1557
+ type: "string",
1558
+ description: "your name; messages from this name are skipped (no echo)"
1559
+ },
1560
+ since: { type: "string", description: "replay messages after this seq (default 0 = full)" },
1561
+ ...common
1562
+ },
1563
+ run: async ({ args }) => {
1564
+ const url = resolveBaseUrl(args.url);
1565
+ const me = args.from ?? "";
1566
+ const emit = (o) => {
1567
+ process.stdout.write(`${JSON.stringify(o)}
1568
+ `);
1569
+ };
1570
+ const spillDir = join2(tmpdir(), "hotline", args.channel);
1571
+ mkdirSync(spillDir, { recursive: true });
1572
+ const surface = (m) => {
1573
+ const text = typeof m.text === "string" ? m.text : "";
1574
+ const file = join2(spillDir, `${m.seq}.txt`);
1575
+ writeFileSync2(file, text);
1576
+ const truncated = text.length > PREVIEW;
1577
+ emit({
1578
+ type: "relay.message",
1579
+ seq: m.seq,
1580
+ from: m.from,
1581
+ ts: m.ts,
1582
+ chars: text.length,
1583
+ truncated,
1584
+ preview: truncated ? `${text.slice(0, PREVIEW)} \u2026` : text,
1585
+ file
1586
+ });
1587
+ };
1588
+ const describe = (e) => {
1589
+ const ev = e;
1590
+ return ev?.code ? `${ev.code}${ev.message ? ` ${ev.message}` : ""}` : String(e);
1591
+ };
1592
+ const close = relayClient(url).listen(args.channel, args.token, {
1593
+ since: args.since ? Number(args.since) : 0,
1594
+ // Skip our own messages (they come back over the same channel).
1595
+ onMessage: (m) => {
1596
+ if (m.from !== me) surface(m);
1597
+ },
1598
+ onError: (e) => emit({ type: "relay.error", error: describe(e) })
1599
+ });
1600
+ emit({ type: "relay.listening", channel: args.channel, from: me, url });
1601
+ await new Promise((resolve2) => {
1602
+ const stop = () => {
1603
+ close();
1604
+ resolve2();
1605
+ };
1606
+ process.on("SIGINT", stop);
1607
+ process.on("SIGTERM", stop);
1608
+ });
1609
+ }
1610
+ });
1611
+ var relay = defineCommand({
1612
+ meta: { name: "relay", description: "Claude\u2194Claude live channel: host / listen / send" },
1613
+ subCommands: { host: hostCommand, listen: listenCommand, send: sendCommand }
1614
+ });
1615
+
1616
+ // src/commands/requirement.ts
1617
+ var createRequirement = (c, input, json) => c.requirements.create(input).then((r) => renderOne(r, fmtRequirement, json));
1618
+ var updateRequirement = (c, id, patch2, json) => c.requirements.update(id, patch2).then((r) => renderOne(r, fmtRequirement, json));
1619
+ var listRequirements = (c, projectId, json) => c.requirements.listByProject(projectId).then((rs) => renderList(rs, fmtRequirement, json));
1620
+ var getRequirement = (c, id, json) => c.requirements.get(id).then((r) => renderOne(r, fmtRequirement, json));
1621
+ var setRequirementStatus = (c, id, status, json) => c.requirements.setStatus(id, status).then((r) => renderOne(r, fmtRequirement, json));
1622
+ var removeRequirement = (c, id, label, json) => c.requirements.remove(id).then(() => removed("requirement", label, json));
1623
+ var resolveByCode = async (c, projectId, code) => (await c.requirements.getByCode(projectId, code)).id;
1624
+ var requirement = defineCommand({
1625
+ meta: { name: "requirement", description: "manage requirements" },
1626
+ subCommands: {
1627
+ create: defineCommand({
1628
+ meta: { name: "create", description: "create a requirement" },
1629
+ args: {
1630
+ title: { type: "positional", required: true },
1631
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1632
+ desc: { type: "string", description: "description" },
1633
+ body: { type: "string", description: "requirement body (markdown)" },
1634
+ ...common
1635
+ },
1636
+ run: async ({ args }) => {
1637
+ console.log(
1638
+ await createRequirement(
1639
+ clientFor(args),
1640
+ {
1641
+ projectId: resolveProjectId(args),
1642
+ title: args.title,
1643
+ description: args.desc,
1644
+ body: args.body
1645
+ },
1646
+ Boolean(args.json)
1647
+ )
1648
+ );
1649
+ }
1650
+ }),
1651
+ update: defineCommand({
1652
+ meta: { name: "update", description: "update a requirement title / body" },
1653
+ args: {
1654
+ code: { type: "positional", required: true, description: "requirement code (R-N)" },
1655
+ title: { type: "string", description: "new title" },
1656
+ body: { type: "string", description: "new body (markdown)" },
1657
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1658
+ ...common
1659
+ },
1660
+ run: async ({ args }) => {
1661
+ const patch2 = {
1662
+ ...args.title !== void 0 ? { title: args.title } : {},
1663
+ ...args.body !== void 0 ? { body: args.body } : {}
1664
+ };
1665
+ if (Object.keys(patch2).length === 0) throw new Error("pass --title and/or --body");
1666
+ const c = clientFor(args);
1667
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
1668
+ console.log(await updateRequirement(c, id, patch2, Boolean(args.json)));
1669
+ }
1670
+ }),
1671
+ ls: defineCommand({
1672
+ meta: { name: "ls", description: "list requirements in a project" },
1673
+ args: {
1674
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1675
+ ...common
1676
+ },
1677
+ run: async ({ args }) => {
1678
+ console.log(
1679
+ await listRequirements(clientFor(args), resolveProjectId(args), Boolean(args.json))
1680
+ );
1681
+ }
1682
+ }),
1683
+ get: defineCommand({
1684
+ meta: { name: "get", description: "get a requirement by code (R-N)" },
1685
+ args: {
1686
+ code: { type: "positional", required: true, description: "requirement code, e.g. R-1" },
1687
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1688
+ ...common
1689
+ },
1690
+ run: async ({ args }) => {
1691
+ const c = clientFor(args);
1692
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
1693
+ console.log(await getRequirement(c, id, Boolean(args.json)));
1694
+ }
1695
+ }),
1696
+ "set-status": defineCommand({
1697
+ meta: { name: "set-status", description: "set requirement status (active|done|cancelled)" },
1698
+ args: {
1699
+ code: { type: "positional", required: true, description: "requirement code (R-N)" },
1700
+ status: { type: "positional", required: true },
1701
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1702
+ ...common
1703
+ },
1704
+ run: async ({ args }) => {
1705
+ const c = clientFor(args);
1706
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
1707
+ console.log(
1708
+ await setRequirementStatus(c, id, args.status, Boolean(args.json))
1709
+ );
1710
+ }
1711
+ }),
1712
+ rm: defineCommand({
1713
+ meta: { name: "rm", description: "delete a requirement by code" },
1714
+ args: {
1715
+ code: { type: "positional", required: true },
1716
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1717
+ ...common
1718
+ },
1719
+ run: async ({ args }) => {
1720
+ const c = clientFor(args);
1721
+ const id = await resolveByCode(c, resolveProjectId(args), args.code);
1722
+ console.log(await removeRequirement(c, id, args.code, Boolean(args.json)));
1723
+ }
1724
+ })
1725
+ }
1726
+ });
1727
+
1728
+ // src/commands/session/shared.ts
1729
+ import { homedir } from "node:os";
1730
+ import { join as join3 } from "node:path";
1731
+ var defaultWorktreeDir = (env = process.env) => env.BATON_WORKTREE_DIR ?? join3(env.XDG_DATA_HOME ?? join3(env.HOME ?? homedir(), ".local/share"), "baton", "worktrees");
1732
+ var slug = (s) => s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
1733
+ var resolveSession = async (client, projectId, handle) => {
1734
+ const asInt = Number(handle);
1735
+ if (Number.isInteger(asInt) && asInt > 0) {
1736
+ const byId = await client.sessions.get(asInt).catch(() => null);
1737
+ if (byId) return { id: byId.id, name: byId.name };
1738
+ }
1739
+ const byName = await client.sessions.findByName(projectId, handle);
1740
+ if (byName) return { id: byName.id, name: byName.name };
1741
+ throw new Error(`session "${handle}" not found in project ${projectId}`);
1742
+ };
1743
+
1744
+ // src/commands/session/abort.ts
1745
+ var sessionAbortCommand = defineCommand({
1746
+ meta: {
1747
+ name: "abort",
1748
+ description: "interrupt the in-flight turn (like Esc); keeps the session"
1749
+ },
1750
+ args: {
1751
+ session: { type: "positional", required: true, description: "session int id or name" },
1752
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1753
+ ...common
1754
+ },
1755
+ run: async ({ args }) => {
1756
+ const c = clientFor(args);
1757
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
1758
+ const s = await c.sessions.abort(handle.id);
1759
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1760
+ }
1761
+ });
1762
+
1763
+ // src/commands/session/clear.ts
1764
+ var sessionClearCommand = defineCommand({
1765
+ meta: {
1766
+ name: "clear",
1767
+ description: "reset the conversation context (keeps the session + worktree)"
1768
+ },
1769
+ args: {
1770
+ session: { type: "positional", required: true, description: "session int id or name" },
1771
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1772
+ ...common
1773
+ },
1774
+ run: async ({ args }) => {
1775
+ const c = clientFor(args);
1776
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
1777
+ const s = await c.sessions.clear(handle.id);
1778
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1779
+ }
1780
+ });
1781
+
1782
+ // src/commands/session/create.ts
1783
+ var sessionCreateCommand = defineCommand({
1784
+ meta: { name: "create", description: "create + start a session on a worker" },
1785
+ args: {
1786
+ name: { type: "positional", required: true, description: "session name" },
1787
+ worker: {
1788
+ type: "string",
1789
+ description: "worker handle: W-N or int id (default: this machine's worker)"
1790
+ },
1791
+ project: {
1792
+ type: "string",
1793
+ description: "project id (default: the worker's own project, else .baton.json)"
1794
+ },
1795
+ ...common
1796
+ },
1797
+ run: async ({ args }) => {
1798
+ const c = clientFor(args);
1799
+ const workerId = args.worker ? parseWorkerHandle(args.worker) : loadProjectConfigOrNull(projectConfigPath())?.worker?.id ?? null;
1800
+ if (workerId === null)
1801
+ throw new Error(
1802
+ "no worker \u2014 pass --worker <W-N|id> or run `baton worker register` first (names need --project)"
1803
+ );
1804
+ const projectId = args.project !== void 0 && args.project !== "" ? Number(args.project) : args.worker ? (await c.workers.get(workerId)).projectId : resolveProjectId(args);
1805
+ const s = await c.sessions.create({ projectId, workerId, name: args.name });
1806
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1613
1807
  }
1614
- /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */
1615
- get onopen() {
1616
- return __privateGet(this, _onOpen);
1808
+ });
1809
+
1810
+ // src/commands/session/get.ts
1811
+ var sessionGetCommand = defineCommand({
1812
+ meta: { name: "get", description: "get a session by int id or name" },
1813
+ args: {
1814
+ session: { type: "positional", required: true, description: "session int id or name" },
1815
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1816
+ ...common
1817
+ },
1818
+ run: async ({ args }) => {
1819
+ const c = clientFor(args);
1820
+ const projectId = resolveProjectId(args);
1821
+ const handle = await resolveSession(c, projectId, args.session);
1822
+ const s = await c.sessions.get(handle.id);
1823
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1617
1824
  }
1618
- set onopen(value) {
1619
- __privateSet(this, _onOpen, value);
1825
+ });
1826
+
1827
+ // src/commands/session/ls.ts
1828
+ var sessionLsCommand = defineCommand({
1829
+ meta: { name: "ls", description: "list sessions in a project" },
1830
+ args: {
1831
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1832
+ ...common
1833
+ },
1834
+ run: async ({ args }) => {
1835
+ const c = clientFor(args);
1836
+ const ss = await c.sessions.listByProject(resolveProjectId(args));
1837
+ console.log(renderList(ss, fmtSession, Boolean(args.json)));
1620
1838
  }
1621
- addEventListener(type, listener, options) {
1622
- const listen = listener;
1623
- super.addEventListener(type, listen, options);
1839
+ });
1840
+
1841
+ // src/commands/session/rename.ts
1842
+ var sessionRenameCommand = defineCommand({
1843
+ meta: { name: "rename", description: "rename a session (locks the name against auto-title)" },
1844
+ args: {
1845
+ session: { type: "positional", required: true, description: "session int id or name" },
1846
+ name: { type: "positional", required: true, description: "new name" },
1847
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1848
+ ...common
1849
+ },
1850
+ run: async ({ args }) => {
1851
+ const c = clientFor(args);
1852
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
1853
+ const s = await c.sessions.rename(handle.id, args.name);
1854
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1624
1855
  }
1625
- removeEventListener(type, listener, options) {
1626
- const listen = listener;
1627
- super.removeEventListener(type, listen, options);
1856
+ });
1857
+
1858
+ // src/commands/session/resume.ts
1859
+ var sessionResumeCommand = defineCommand({
1860
+ meta: { name: "resume", description: "resume (re-start) an existing session" },
1861
+ args: {
1862
+ session: { type: "positional", required: true, description: "session int id or name" },
1863
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1864
+ ...common
1865
+ },
1866
+ run: async ({ args }) => {
1867
+ const c = clientFor(args);
1868
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
1869
+ const s = await c.sessions.resume(handle.id);
1870
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1628
1871
  }
1629
- /**
1630
- * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED.
1631
- *
1632
- * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close)
1633
- *
1634
- * @public
1635
- */
1636
- close() {
1637
- __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));
1872
+ });
1873
+
1874
+ // src/commands/session/rm.ts
1875
+ var sessionRmCommand = defineCommand({
1876
+ meta: { name: "rm", description: "delete a session (irreversible; removes its worktree)" },
1877
+ args: {
1878
+ session: { type: "positional", required: true, description: "session int id or name" },
1879
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1880
+ confirm: { type: "boolean", description: "actually perform the deletion" },
1881
+ ...common
1882
+ },
1883
+ run: async ({ args }) => {
1884
+ const c = clientFor(args);
1885
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
1886
+ if (!args.confirm) {
1887
+ console.log(`[dry-run] would delete session ${handle.name} (#${handle.id}) + its worktree`);
1888
+ console.log("re-run with --confirm to proceed.");
1889
+ return;
1890
+ }
1891
+ await c.sessions.destroy(handle.id);
1892
+ console.log(`deleted session ${handle.name} (#${handle.id})`);
1638
1893
  }
1639
- };
1640
- _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(), /**
1641
- * Connect to the given URL and start receiving events
1642
- *
1643
- * @internal
1644
- */
1645
- connect_fn = function() {
1646
- __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));
1647
- }, _onFetchResponse = /* @__PURE__ */ new WeakMap(), _onFetchError = /* @__PURE__ */ new WeakMap(), /**
1648
- * Get request options for the `fetch()` request
1649
- *
1650
- * @returns The request options
1651
- * @internal
1652
- */
1653
- getRequestOptions_fn = function() {
1654
- var _a;
1655
- const init2 = {
1656
- // [spec] Let `corsAttributeState` be `Anonymous`…
1657
- // [spec] …will have their mode set to "cors"…
1658
- mode: "cors",
1659
- redirect: "follow",
1660
- headers: { Accept: "text/event-stream", ...__privateGet(this, _lastEventId) ? { "Last-Event-ID": __privateGet(this, _lastEventId) } : void 0 },
1661
- cache: "no-store",
1662
- signal: (_a = __privateGet(this, _controller)) == null ? void 0 : _a.signal
1663
- };
1664
- return "window" in globalThis && (init2.credentials = this.withCredentials ? "include" : "same-origin"), init2;
1665
- }, _onEvent = /* @__PURE__ */ new WeakMap(), _onRetryChange = /* @__PURE__ */ new WeakMap(), /**
1666
- * Handles the process referred to in the EventSource specification as "failing a connection".
1667
- *
1668
- * @param error - The error causing the connection to fail
1669
- * @param code - The HTTP status code, if available
1670
- * @internal
1671
- */
1672
- failConnection_fn = function(message, code) {
1673
- var _a;
1674
- __privateGet(this, _readyState) !== this.CLOSED && __privateSet(this, _readyState, this.CLOSED);
1675
- const errorEvent = new ErrorEvent("error", { code, message });
1676
- (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent);
1677
- }, /**
1678
- * Schedules a reconnection attempt against the EventSource endpoint.
1679
- *
1680
- * @param message - The error causing the connection to fail
1681
- * @param code - The HTTP status code, if available
1682
- * @internal
1683
- */
1684
- scheduleReconnect_fn = function(message, code) {
1685
- var _a;
1686
- if (__privateGet(this, _readyState) === this.CLOSED)
1687
- return;
1688
- __privateSet(this, _readyState, this.CONNECTING);
1689
- const errorEvent = new ErrorEvent("error", { code, message });
1690
- (_a = __privateGet(this, _onError)) == null || _a.call(this, errorEvent), this.dispatchEvent(errorEvent), __privateSet(this, _reconnectTimer, setTimeout(__privateGet(this, _reconnect), __privateGet(this, _reconnectInterval)));
1691
- }, _reconnect = /* @__PURE__ */ new WeakMap(), /**
1692
- * ReadyState representing an EventSource currently trying to connect
1693
- *
1694
- * @public
1695
- */
1696
- EventSource.CONNECTING = 0, /**
1697
- * ReadyState representing an EventSource connection that is open (eg connected)
1698
- *
1699
- * @public
1700
- */
1701
- EventSource.OPEN = 1, /**
1702
- * ReadyState representing an EventSource connection that is closed (eg disconnected)
1703
- *
1704
- * @public
1705
- */
1706
- EventSource.CLOSED = 2;
1707
- Object.defineProperty(EventSource, /* @__PURE__ */ Symbol.for("eventsource.supports-fetch-override"), {
1708
- value: true,
1709
- writable: false,
1710
- configurable: false,
1711
- enumerable: false
1712
1894
  });
1713
- function getBaseURL() {
1714
- const doc = "document" in globalThis ? globalThis.document : void 0;
1715
- return doc && typeof doc == "object" && "baseURI" in doc && typeof doc.baseURI == "string" ? doc.baseURI : void 0;
1716
- }
1717
1895
 
1718
1896
  // src/session/runner.ts
1719
1897
  import { spawnSync } from "node:child_process";
1720
1898
  import { query } from "@anthropic-ai/claude-agent-sdk";
1721
1899
 
1900
+ // ../shared/src/domain/attachment.ts
1901
+ var isImageAttachment = (att) => att.contentType.startsWith("image/");
1902
+ var labelAttachments = (attachments) => {
1903
+ const counters = { image: 0, file: 0 };
1904
+ return attachments.map((att) => {
1905
+ const kind = isImageAttachment(att) ? "image" : "file";
1906
+ counters[kind] += 1;
1907
+ return `${kind}-${counters[kind]}`;
1908
+ });
1909
+ };
1910
+
1911
+ // ../shared/src/domain/session-event.ts
1912
+ var startedMessageIds = (events) => {
1913
+ const ids = /* @__PURE__ */ new Set();
1914
+ for (const e of events) {
1915
+ if (e.type !== "turn_start") continue;
1916
+ const id = e.payload?.messageId;
1917
+ if (typeof id === "number") ids.add(id);
1918
+ }
1919
+ return ids;
1920
+ };
1921
+ var unstartedUserMessages = (events) => {
1922
+ const started = startedMessageIds(events);
1923
+ return events.filter((e) => e.type === "user_message" && !started.has(e.id));
1924
+ };
1925
+
1722
1926
  // src/session/runner/log.ts
1723
1927
  var maskedEnvKeys = (env) => env ? Object.keys(env).sort().join(", ") : "(none)";
1724
1928
 
@@ -1735,14 +1939,14 @@ var additionalDirs = () => {
1735
1939
  };
1736
1940
 
1737
1941
  // src/session/runner/transcript.ts
1738
- import { readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
1942
+ import { readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
1739
1943
  import { homedir as homedir2 } from "node:os";
1740
- import { join as join3 } from "node:path";
1944
+ import { join as join4 } from "node:path";
1741
1945
  var findTranscriptPath = (agentSessionId) => {
1742
- const root = join3(homedir2(), ".claude", "projects");
1946
+ const root = join4(homedir2(), ".claude", "projects");
1743
1947
  try {
1744
1948
  for (const dir of readdirSync(root)) {
1745
- const candidate = join3(root, dir, `${agentSessionId}.jsonl`);
1949
+ const candidate = join4(root, dir, `${agentSessionId}.jsonl`);
1746
1950
  try {
1747
1951
  if (statSync(candidate).isFile()) return candidate;
1748
1952
  } catch {
@@ -1788,27 +1992,16 @@ var readFirstExchange = (agentSessionId) => {
1788
1992
  const path = findTranscriptPath(agentSessionId);
1789
1993
  if (!path) return null;
1790
1994
  try {
1791
- return parseFirstExchange(readFileSync2(path, "utf8"));
1995
+ return parseFirstExchange(readFileSync3(path, "utf8"));
1792
1996
  } catch {
1793
1997
  return null;
1794
1998
  }
1795
1999
  };
1796
2000
 
1797
- // ../shared/src/domain/attachment.ts
1798
- var isImageAttachment = (att) => att.contentType.startsWith("image/");
1799
- var labelAttachments = (attachments) => {
1800
- const counters = { image: 0, file: 0 };
1801
- return attachments.map((att) => {
1802
- const kind = isImageAttachment(att) ? "image" : "file";
1803
- counters[kind] += 1;
1804
- return `${kind}-${counters[kind]}`;
1805
- });
1806
- };
1807
-
1808
2001
  // src/session/runner/attachments.ts
1809
2002
  import { createWriteStream } from "node:fs";
1810
2003
  import { mkdir, writeFile } from "node:fs/promises";
1811
- import { join as join4 } from "node:path";
2004
+ import { join as join5 } from "node:path";
1812
2005
  import { Readable } from "node:stream";
1813
2006
  import { pipeline } from "node:stream/promises";
1814
2007
  var ATTACH_DIR = "attachments";
@@ -1831,16 +2024,16 @@ var dedupe = (name, taken) => {
1831
2024
  };
1832
2025
  var materializeAttachments = async (opts) => {
1833
2026
  const fetchImpl = opts.fetchImpl ?? fetch;
1834
- const dir = join4(opts.worktreePath, ATTACH_DIR);
2027
+ const dir = join5(opts.worktreePath, ATTACH_DIR);
1835
2028
  await mkdir(dir, { recursive: true });
1836
- await writeFile(join4(dir, ".gitignore"), "*\n", "utf8");
2029
+ await writeFile(join5(dir, ".gitignore"), "*\n", "utf8");
1837
2030
  const taken = /* @__PURE__ */ new Set([".gitignore"]);
1838
2031
  const relPaths = [];
1839
2032
  for (const att of opts.attachments) {
1840
2033
  const name = dedupe(att.filename, taken);
1841
2034
  const res = await fetchImpl(`${opts.serverBase}${att.url}`);
1842
2035
  if (!res.ok || !res.body) throw new Error(`attachment ${att.id} \u2192 ${res.status}`);
1843
- await pipeline(Readable.fromWeb(res.body), createWriteStream(join4(dir, name)));
2036
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(join5(dir, name)));
1844
2037
  relPaths.push(`${ATTACH_DIR}/${name}`);
1845
2038
  }
1846
2039
  return relPaths;
@@ -1857,22 +2050,28 @@ ${text}` : header;
1857
2050
  };
1858
2051
 
1859
2052
  // src/session/runner/query.ts
1860
- var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay, planMode = false) => {
2053
+ var startQuery = (config, text, resuming, queryFn, abortController, log, { envOverlay, planMode = false, model } = {}) => {
1861
2054
  const env = buildSdkEnv(envOverlay);
1862
2055
  const exe = claudeExecutable();
1863
2056
  const addDirs = additionalDirs();
1864
2057
  if (addDirs) log(`[add-dir] ${addDirs.join(", ")}`);
1865
2058
  if (planMode) log("[plan] read-only planning turn");
2059
+ if (model) log(`[model] ${model}`);
1866
2060
  const options = {
1867
2061
  cwd: config.worktreePath,
1868
2062
  permissionMode: planMode ? "plan" : "bypassPermissions",
2063
+ // Headless relay: no TTY/TUI can answer an interactive ask, so AskUserQuestion
2064
+ // would fail the whole turn. Block it — the model asks in plain text instead,
2065
+ // which the human reads in web/Feishu and replies to as a new turn.
2066
+ disallowedTools: ["AskUserQuestion"],
1869
2067
  includePartialMessages: false,
1870
2068
  abortController,
1871
2069
  stderr: (line) => log(`[claude] ${line.trimEnd()}`),
1872
2070
  ...resuming ? { resume: config.agentSessionId } : { sessionId: config.agentSessionId },
1873
2071
  ...env ? { env } : {},
1874
2072
  ...exe ? { pathToClaudeCodeExecutable: exe } : {},
1875
- ...addDirs ? { additionalDirectories: addDirs } : {}
2073
+ ...addDirs ? { additionalDirectories: addDirs } : {},
2074
+ ...model ? { model } : {}
1876
2075
  };
1877
2076
  return queryFn({ prompt: text, options });
1878
2077
  };
@@ -1932,6 +2131,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
1932
2131
  const rawText = typeof payload?.text === "string" ? payload.text : "";
1933
2132
  const attachments = Array.isArray(payload?.attachments) ? payload.attachments : [];
1934
2133
  const planMode = payload?.planMode === true;
2134
+ const model = typeof payload?.model === "string" ? payload.model : void 0;
1935
2135
  if (rawText.length === 0 && attachments.length === 0) {
1936
2136
  await worker2.emitEvent("turn_error", { message: "user_message missing text and attachments" });
1937
2137
  return -1;
@@ -1955,7 +2155,11 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
1955
2155
  else externalSignal?.addEventListener("abort", () => resolve2(ABORTED), { once: true });
1956
2156
  });
1957
2157
  try {
1958
- const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay, planMode);
2158
+ const messages = startQuery(config, text, resuming, queryFn, abort, log, {
2159
+ envOverlay,
2160
+ planMode,
2161
+ model
2162
+ });
1959
2163
  const consume = streamSdkEvents(messages, worker2);
1960
2164
  void consume.catch(() => {
1961
2165
  });
@@ -2031,6 +2235,7 @@ var runDaemon = async (config, deps, signal) => {
2031
2235
  };
2032
2236
  const pendingQueue = [];
2033
2237
  let busy = false;
2238
+ let reconciling = false;
2034
2239
  let lastActivity = Date.now();
2035
2240
  let currentTurn;
2036
2241
  const drain = async () => {
@@ -2064,6 +2269,28 @@ var runDaemon = async (config, deps, signal) => {
2064
2269
  busy = false;
2065
2270
  }
2066
2271
  };
2272
+ const reconcile = async () => {
2273
+ reconciling = true;
2274
+ try {
2275
+ const pending = unstartedUserMessages(await deps.worker.listEvents());
2276
+ let added = 0;
2277
+ for (const ev of pending) {
2278
+ if (state.seen.has(ev.sequence)) continue;
2279
+ state.seen.add(ev.sequence);
2280
+ pendingQueue.push(ev);
2281
+ added++;
2282
+ }
2283
+ if (added > 0) {
2284
+ log(`reconciled ${added} queued message(s) from transcript`);
2285
+ lastActivity = Date.now();
2286
+ void drain();
2287
+ }
2288
+ } catch (e) {
2289
+ log(`reconcile failed: ${String(e)}`);
2290
+ } finally {
2291
+ reconciling = false;
2292
+ }
2293
+ };
2067
2294
  const es = subscribeStream(
2068
2295
  // live-only: the server now replays history to viewers, but the child must
2069
2296
  // not re-receive (and re-run) past user_messages on connect/resume.
@@ -2085,8 +2312,11 @@ var runDaemon = async (config, deps, signal) => {
2085
2312
  },
2086
2313
  // Report active only now that we're subscribed — before this, a message
2087
2314
  // sent right after spawn would be missed (no replay on the live stream).
2315
+ // Then reconcile against the durable transcript to pick up anything that
2316
+ // landed while we weren't subscribed (initial spawn or a reconnect gap).
2088
2317
  () => {
2089
2318
  void deps.worker.setActive(true).then(() => log("\u25B2 active (subscribed)")).catch((e) => log(`status(active) failed: ${String(e)}`));
2319
+ void reconcile();
2090
2320
  }
2091
2321
  );
2092
2322
  const idleMs = idleMsFromEnv();
@@ -2094,7 +2324,7 @@ var runDaemon = async (config, deps, signal) => {
2094
2324
  const reaped = new Promise((resolve2) => {
2095
2325
  idleTimer = setInterval(
2096
2326
  () => {
2097
- if (shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
2327
+ if (!reconciling && shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
2098
2328
  log("idle \u2014 shutting down (resumes on next message)");
2099
2329
  resolve2();
2100
2330
  }
@@ -2439,7 +2669,7 @@ import { hostname as osHostname } from "node:os";
2439
2669
  import { spawn } from "node:child_process";
2440
2670
  import { randomUUID } from "node:crypto";
2441
2671
  import { existsSync as existsSync5 } from "node:fs";
2442
- import { dirname as dirname2, join as join5 } from "node:path";
2672
+ import { dirname as dirname2, join as join6 } from "node:path";
2443
2673
  import { fileURLToPath } from "node:url";
2444
2674
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
2445
2675
 
@@ -2529,7 +2759,7 @@ var generateTitle = async (input) => {
2529
2759
 
2530
2760
  // src/session/worktree.ts
2531
2761
  import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
2532
- import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3 } from "node:fs";
2762
+ import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "node:fs";
2533
2763
  import { dirname, isAbsolute as isAbsolute2, resolve } from "node:path";
2534
2764
  var createWorktree = (input) => {
2535
2765
  if (existsSync4(input.worktreePath))
@@ -2542,7 +2772,7 @@ var createWorktree = (input) => {
2542
2772
  throw new Error(
2543
2773
  `repo has no commits (unborn HEAD) \u2014 create an initial commit before starting sessions`
2544
2774
  );
2545
- mkdirSync(dirname(input.worktreePath), { recursive: true });
2775
+ mkdirSync2(dirname(input.worktreePath), { recursive: true });
2546
2776
  const branch = `baton/${input.sessionCode}`;
2547
2777
  const r = spawnSync2(
2548
2778
  "git",
@@ -2570,9 +2800,9 @@ var ensureExcluded = (repo, pattern) => {
2570
2800
  if (r.status !== 0) return;
2571
2801
  const rel = r.stdout.trim();
2572
2802
  const path = isAbsolute2(rel) ? rel : resolve(repo, rel);
2573
- const current = existsSync4(path) ? readFileSync3(path, "utf8") : "";
2803
+ const current = existsSync4(path) ? readFileSync4(path, "utf8") : "";
2574
2804
  if (current.split("\n").includes(pattern)) return;
2575
- mkdirSync(dirname(path), { recursive: true });
2805
+ mkdirSync2(dirname(path), { recursive: true });
2576
2806
  const sep = current === "" || current.endsWith("\n") ? "" : "\n";
2577
2807
  appendFileSync(path, `${sep}${pattern}
2578
2808
  `, "utf8");
@@ -2596,7 +2826,7 @@ var streamWedged = (isOpen, okAt, now, thresholdMs) => !isOpen && stale(okAt, no
2596
2826
  // src/worker/daemon.ts
2597
2827
  var binPath = () => {
2598
2828
  const here = dirname2(fileURLToPath(import.meta.url));
2599
- const devShim = join5(here, "..", "..", "bin", "baton.mjs");
2829
+ const devShim = join6(here, "..", "..", "bin", "baton.mjs");
2600
2830
  return existsSync5(devShim) ? devShim : fileURLToPath(import.meta.url);
2601
2831
  };
2602
2832
  var runWorkerDaemon = async (cfg, client, signal) => {
@@ -2666,7 +2896,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2666
2896
  let worktreePath = session2.worktreePath;
2667
2897
  if (!session2.agentSessionId || !worktreePath) {
2668
2898
  const agentSessionId = randomUUID();
2669
- worktreePath = join5(worktreeDir, slug(`${name}-${agentSessionId.slice(0, 8)}`));
2899
+ worktreePath = join6(worktreeDir, slug(`${name}-${agentSessionId.slice(0, 8)}`));
2670
2900
  createWorktree({
2671
2901
  repo,
2672
2902
  worktreePath,
@@ -2677,7 +2907,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2677
2907
  log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
2678
2908
  }
2679
2909
  ensureExcluded(repo, PROJECT_CONFIG_NAME);
2680
- saveProjectConfig(join5(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
2910
+ saveProjectConfig(join6(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
2681
2911
  spawnChild(sessionId, worktreePath);
2682
2912
  };
2683
2913
  const onStop = (sessionId) => {
@@ -2780,18 +3010,18 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2780
3010
 
2781
3011
  // src/worker/machine-id.ts
2782
3012
  import { randomUUID as randomUUID2 } from "node:crypto";
2783
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
3013
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
2784
3014
  import { homedir as homedir3 } from "node:os";
2785
- import { dirname as dirname3, join as join6 } from "node:path";
2786
- var machineIdPath = (env = process.env) => join6(env.XDG_DATA_HOME ?? join6(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
3015
+ import { dirname as dirname3, join as join7 } from "node:path";
3016
+ var machineIdPath = (env = process.env) => join7(env.XDG_DATA_HOME ?? join7(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
2787
3017
  var readOrCreateMachineId = (path = machineIdPath()) => {
2788
3018
  if (existsSync6(path)) {
2789
- const s = readFileSync4(path, "utf8").trim();
3019
+ const s = readFileSync5(path, "utf8").trim();
2790
3020
  if (s) return s;
2791
3021
  }
2792
3022
  const id = randomUUID2();
2793
- mkdirSync2(dirname3(path), { recursive: true });
2794
- writeFileSync2(path, `${id}
3023
+ mkdirSync3(dirname3(path), { recursive: true });
3024
+ writeFileSync3(path, `${id}
2795
3025
  `, "utf8");
2796
3026
  return id;
2797
3027
  };
@@ -3059,6 +3289,7 @@ var main = defineCommand({
3059
3289
  init,
3060
3290
  worker,
3061
3291
  session,
3292
+ relay,
3062
3293
  workspace,
3063
3294
  project,
3064
3295
  requirement,