@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.
- package/dist/baton.mjs +688 -457
- 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;
|
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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) ?
|
|
2803
|
+
const current = existsSync4(path) ? readFileSync4(path, "utf8") : "";
|
|
2574
2804
|
if (current.split("\n").includes(pattern)) return;
|
|
2575
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
2786
|
-
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");
|
|
2787
3017
|
var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
2788
3018
|
if (existsSync6(path)) {
|
|
2789
|
-
const s =
|
|
3019
|
+
const s = readFileSync5(path, "utf8").trim();
|
|
2790
3020
|
if (s) return s;
|
|
2791
3021
|
}
|
|
2792
3022
|
const id = randomUUID2();
|
|
2793
|
-
|
|
2794
|
-
|
|
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,
|