@lesscap/baton-cli 0.2.12 → 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.
- package/dist/baton.mjs +678 -454
- 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/
|
|
960
|
-
|
|
961
|
-
|
|
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
|
-
//
|
|
1107
|
-
var
|
|
1108
|
-
|
|
1109
|
-
name
|
|
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
|
-
|
|
1126
|
-
var
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
"
|
|
986
|
+
"Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
|
|
1146
987
|
);
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
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
|
-
|
|
1619
|
-
|
|
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
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
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
|
|
1942
|
+
import { readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
1739
1943
|
import { homedir as homedir2 } from "node:os";
|
|
1740
|
-
import { join as
|
|
1944
|
+
import { join as join4 } from "node:path";
|
|
1741
1945
|
var findTranscriptPath = (agentSessionId) => {
|
|
1742
|
-
const root =
|
|
1946
|
+
const root = join4(homedir2(), ".claude", "projects");
|
|
1743
1947
|
try {
|
|
1744
1948
|
for (const dir of readdirSync(root)) {
|
|
1745
|
-
const candidate =
|
|
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(
|
|
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
|
|
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 =
|
|
2027
|
+
const dir = join5(opts.worktreePath, ATTACH_DIR);
|
|
1835
2028
|
await mkdir(dir, { recursive: true });
|
|
1836
|
-
await writeFile(
|
|
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(
|
|
2036
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(join5(dir, name)));
|
|
1844
2037
|
relPaths.push(`${ATTACH_DIR}/${name}`);
|
|
1845
2038
|
}
|
|
1846
2039
|
return relPaths;
|
|
@@ -1867,6 +2060,10 @@ var startQuery = (config, text, resuming, queryFn, abortController, log, { envOv
|
|
|
1867
2060
|
const options = {
|
|
1868
2061
|
cwd: config.worktreePath,
|
|
1869
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"],
|
|
1870
2067
|
includePartialMessages: false,
|
|
1871
2068
|
abortController,
|
|
1872
2069
|
stderr: (line) => log(`[claude] ${line.trimEnd()}`),
|
|
@@ -2038,6 +2235,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2038
2235
|
};
|
|
2039
2236
|
const pendingQueue = [];
|
|
2040
2237
|
let busy = false;
|
|
2238
|
+
let reconciling = false;
|
|
2041
2239
|
let lastActivity = Date.now();
|
|
2042
2240
|
let currentTurn;
|
|
2043
2241
|
const drain = async () => {
|
|
@@ -2071,6 +2269,28 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2071
2269
|
busy = false;
|
|
2072
2270
|
}
|
|
2073
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
|
+
};
|
|
2074
2294
|
const es = subscribeStream(
|
|
2075
2295
|
// live-only: the server now replays history to viewers, but the child must
|
|
2076
2296
|
// not re-receive (and re-run) past user_messages on connect/resume.
|
|
@@ -2092,8 +2312,11 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2092
2312
|
},
|
|
2093
2313
|
// Report active only now that we're subscribed — before this, a message
|
|
2094
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).
|
|
2095
2317
|
() => {
|
|
2096
2318
|
void deps.worker.setActive(true).then(() => log("\u25B2 active (subscribed)")).catch((e) => log(`status(active) failed: ${String(e)}`));
|
|
2319
|
+
void reconcile();
|
|
2097
2320
|
}
|
|
2098
2321
|
);
|
|
2099
2322
|
const idleMs = idleMsFromEnv();
|
|
@@ -2101,7 +2324,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2101
2324
|
const reaped = new Promise((resolve2) => {
|
|
2102
2325
|
idleTimer = setInterval(
|
|
2103
2326
|
() => {
|
|
2104
|
-
if (shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
|
|
2327
|
+
if (!reconciling && shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
|
|
2105
2328
|
log("idle \u2014 shutting down (resumes on next message)");
|
|
2106
2329
|
resolve2();
|
|
2107
2330
|
}
|
|
@@ -2446,7 +2669,7 @@ import { hostname as osHostname } from "node:os";
|
|
|
2446
2669
|
import { spawn } from "node:child_process";
|
|
2447
2670
|
import { randomUUID } from "node:crypto";
|
|
2448
2671
|
import { existsSync as existsSync5 } from "node:fs";
|
|
2449
|
-
import { dirname as dirname2, join as
|
|
2672
|
+
import { dirname as dirname2, join as join6 } from "node:path";
|
|
2450
2673
|
import { fileURLToPath } from "node:url";
|
|
2451
2674
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
2452
2675
|
|
|
@@ -2536,7 +2759,7 @@ var generateTitle = async (input) => {
|
|
|
2536
2759
|
|
|
2537
2760
|
// src/session/worktree.ts
|
|
2538
2761
|
import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
2539
|
-
import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as
|
|
2762
|
+
import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
2540
2763
|
import { dirname, isAbsolute as isAbsolute2, resolve } from "node:path";
|
|
2541
2764
|
var createWorktree = (input) => {
|
|
2542
2765
|
if (existsSync4(input.worktreePath))
|
|
@@ -2549,7 +2772,7 @@ var createWorktree = (input) => {
|
|
|
2549
2772
|
throw new Error(
|
|
2550
2773
|
`repo has no commits (unborn HEAD) \u2014 create an initial commit before starting sessions`
|
|
2551
2774
|
);
|
|
2552
|
-
|
|
2775
|
+
mkdirSync2(dirname(input.worktreePath), { recursive: true });
|
|
2553
2776
|
const branch = `baton/${input.sessionCode}`;
|
|
2554
2777
|
const r = spawnSync2(
|
|
2555
2778
|
"git",
|
|
@@ -2577,9 +2800,9 @@ var ensureExcluded = (repo, pattern) => {
|
|
|
2577
2800
|
if (r.status !== 0) return;
|
|
2578
2801
|
const rel = r.stdout.trim();
|
|
2579
2802
|
const path = isAbsolute2(rel) ? rel : resolve(repo, rel);
|
|
2580
|
-
const current = existsSync4(path) ?
|
|
2803
|
+
const current = existsSync4(path) ? readFileSync4(path, "utf8") : "";
|
|
2581
2804
|
if (current.split("\n").includes(pattern)) return;
|
|
2582
|
-
|
|
2805
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
2583
2806
|
const sep = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
2584
2807
|
appendFileSync(path, `${sep}${pattern}
|
|
2585
2808
|
`, "utf8");
|
|
@@ -2603,7 +2826,7 @@ var streamWedged = (isOpen, okAt, now, thresholdMs) => !isOpen && stale(okAt, no
|
|
|
2603
2826
|
// src/worker/daemon.ts
|
|
2604
2827
|
var binPath = () => {
|
|
2605
2828
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
2606
|
-
const devShim =
|
|
2829
|
+
const devShim = join6(here, "..", "..", "bin", "baton.mjs");
|
|
2607
2830
|
return existsSync5(devShim) ? devShim : fileURLToPath(import.meta.url);
|
|
2608
2831
|
};
|
|
2609
2832
|
var runWorkerDaemon = async (cfg, client, signal) => {
|
|
@@ -2673,7 +2896,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2673
2896
|
let worktreePath = session2.worktreePath;
|
|
2674
2897
|
if (!session2.agentSessionId || !worktreePath) {
|
|
2675
2898
|
const agentSessionId = randomUUID();
|
|
2676
|
-
worktreePath =
|
|
2899
|
+
worktreePath = join6(worktreeDir, slug(`${name}-${agentSessionId.slice(0, 8)}`));
|
|
2677
2900
|
createWorktree({
|
|
2678
2901
|
repo,
|
|
2679
2902
|
worktreePath,
|
|
@@ -2684,7 +2907,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2684
2907
|
log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
|
|
2685
2908
|
}
|
|
2686
2909
|
ensureExcluded(repo, PROJECT_CONFIG_NAME);
|
|
2687
|
-
saveProjectConfig(
|
|
2910
|
+
saveProjectConfig(join6(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
|
|
2688
2911
|
spawnChild(sessionId, worktreePath);
|
|
2689
2912
|
};
|
|
2690
2913
|
const onStop = (sessionId) => {
|
|
@@ -2787,18 +3010,18 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2787
3010
|
|
|
2788
3011
|
// src/worker/machine-id.ts
|
|
2789
3012
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2790
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
3013
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2791
3014
|
import { homedir as homedir3 } from "node:os";
|
|
2792
|
-
import { dirname as dirname3, join as
|
|
2793
|
-
var machineIdPath = (env = process.env) =>
|
|
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");
|
|
2794
3017
|
var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
2795
3018
|
if (existsSync6(path)) {
|
|
2796
|
-
const s =
|
|
3019
|
+
const s = readFileSync5(path, "utf8").trim();
|
|
2797
3020
|
if (s) return s;
|
|
2798
3021
|
}
|
|
2799
3022
|
const id = randomUUID2();
|
|
2800
|
-
|
|
2801
|
-
|
|
3023
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
3024
|
+
writeFileSync3(path, `${id}
|
|
2802
3025
|
`, "utf8");
|
|
2803
3026
|
return id;
|
|
2804
3027
|
};
|
|
@@ -3066,6 +3289,7 @@ var main = defineCommand({
|
|
|
3066
3289
|
init,
|
|
3067
3290
|
worker,
|
|
3068
3291
|
session,
|
|
3292
|
+
relay,
|
|
3069
3293
|
workspace,
|
|
3070
3294
|
project,
|
|
3071
3295
|
requirement,
|