clauder-hub 0.1.4 → 0.1.6
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/package.json +1 -1
- package/server/dist/index.js +1019 -798
- package/web/dist/assets/{MonacoEditor-7a1aZ6Pf.js → MonacoEditor-CBiXYCP7.js} +3 -3
- package/web/dist/assets/{cssMode-D42SG8q2.js → cssMode-CQchaYnQ.js} +1 -1
- package/web/dist/assets/{freemarker2-HuLVqAyk.js → freemarker2-BN6MtnD2.js} +1 -1
- package/web/dist/assets/{handlebars-CQbtHmDj.js → handlebars-Ciy4VHE8.js} +1 -1
- package/web/dist/assets/{html-DCQkaSoP.js → html-BqmI8abz.js} +1 -1
- package/web/dist/assets/{htmlMode-Occ0oux7.js → htmlMode-hqhxUX1t.js} +1 -1
- package/web/dist/assets/index-CRmMCp9l.css +32 -0
- package/web/dist/assets/index-Ch_46Kom.js +594 -0
- package/web/dist/assets/{index-DB6lAoRD.js → index-sOL-0RCg.js} +1 -1
- package/web/dist/assets/{javascript-DkkoffJP.js → javascript-tSmmqqDu.js} +1 -1
- package/web/dist/assets/{jsonMode-CvTmblXx.js → jsonMode-BWRnRKuQ.js} +1 -1
- package/web/dist/assets/{liquid-DhsiR38F.js → liquid-UASeAh73.js} +1 -1
- package/web/dist/assets/{lspLanguageFeatures-lfpHn74b.js → lspLanguageFeatures-k7agW304.js} +1 -1
- package/web/dist/assets/{mdx-LuMf9AxQ.js → mdx-4PNlcmYK.js} +1 -1
- package/web/dist/assets/{python-BkVjahJB.js → python-B3oyLMGp.js} +1 -1
- package/web/dist/assets/{razor-CoVA4mXW.js → razor-DertI82h.js} +1 -1
- package/web/dist/assets/{tsMode-C3jqF6o4.js → tsMode-BJcxrzuV.js} +1 -1
- package/web/dist/assets/{typescript-DzEhu0NR.js → typescript-B2dfrWYs.js} +1 -1
- package/web/dist/assets/{workers-DO15KvMQ.js → workers-C3liFWwz.js} +1 -1
- package/web/dist/assets/{xml-Dnv-hWWk.js → xml-D6-qhmMC.js} +1 -1
- package/web/dist/assets/{yaml-hUd7D5AP.js → yaml-CnI-_wqS.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-382ZKQ6T.js +0 -527
- package/web/dist/assets/index-YQ0zZelJ.css +0 -32
package/server/dist/index.js
CHANGED
|
@@ -1665,8 +1665,8 @@ var require_node = __commonJS({
|
|
|
1665
1665
|
break;
|
|
1666
1666
|
case "PIPE":
|
|
1667
1667
|
case "TCP":
|
|
1668
|
-
var
|
|
1669
|
-
stream2 = new
|
|
1668
|
+
var net2 = __require("net");
|
|
1669
|
+
stream2 = new net2.Socket({
|
|
1670
1670
|
fd: fd2,
|
|
1671
1671
|
readable: false,
|
|
1672
1672
|
writable: true
|
|
@@ -25418,7 +25418,7 @@ var require_websocket = __commonJS({
|
|
|
25418
25418
|
var EventEmitter = __require("events");
|
|
25419
25419
|
var https = __require("https");
|
|
25420
25420
|
var http = __require("http");
|
|
25421
|
-
var
|
|
25421
|
+
var net2 = __require("net");
|
|
25422
25422
|
var tls = __require("tls");
|
|
25423
25423
|
var { randomBytes, createHash } = __require("crypto");
|
|
25424
25424
|
var { Duplex, Readable } = __require("stream");
|
|
@@ -26175,12 +26175,12 @@ var require_websocket = __commonJS({
|
|
|
26175
26175
|
}
|
|
26176
26176
|
function netConnect(options) {
|
|
26177
26177
|
options.path = options.socketPath;
|
|
26178
|
-
return
|
|
26178
|
+
return net2.connect(options);
|
|
26179
26179
|
}
|
|
26180
26180
|
function tlsConnect(options) {
|
|
26181
26181
|
options.path = void 0;
|
|
26182
26182
|
if (!options.servername && options.servername !== "") {
|
|
26183
|
-
options.servername =
|
|
26183
|
+
options.servername = net2.isIP(options.host) ? "" : options.host;
|
|
26184
26184
|
}
|
|
26185
26185
|
return tls.connect(options);
|
|
26186
26186
|
}
|
|
@@ -28560,14 +28560,14 @@ function claudeSettings() {
|
|
|
28560
28560
|
}
|
|
28561
28561
|
function saveClaudeSettings(input) {
|
|
28562
28562
|
const next = { ...claudeSettings(), ...input };
|
|
28563
|
-
const
|
|
28563
|
+
const check3 = (ok, message) => {
|
|
28564
28564
|
if (!ok) throw new Error(message);
|
|
28565
28565
|
};
|
|
28566
|
-
|
|
28567
|
-
|
|
28568
|
-
|
|
28569
|
-
|
|
28570
|
-
|
|
28566
|
+
check3(!next.permissionMode || PERMISSION_MODES.includes(next.permissionMode), `Unknown permission mode '${next.permissionMode}'.`);
|
|
28567
|
+
check3(!next.runPermissionMode || PERMISSION_MODES.includes(next.runPermissionMode), `Unknown permission mode '${next.runPermissionMode}'.`);
|
|
28568
|
+
check3(!next.effort || EFFORTS.includes(next.effort), `Effort is one of ${EFFORTS.join(", ")}.`);
|
|
28569
|
+
check3(word.test(next.model) && word.test(next.fallbackModel), "A model is one word, like opus or claude-sonnet-5-5.");
|
|
28570
|
+
check3(["", "on", "off"].includes(next.chrome), "Claude in Chrome is on, off or your own setting.");
|
|
28571
28571
|
next.addDirs = (Array.isArray(next.addDirs) ? next.addDirs : []).map((dir) => String(dir).trim()).filter(Boolean);
|
|
28572
28572
|
db.prepare("INSERT INTO settings (key, value, updated_at) VALUES ('claude', ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at").run(
|
|
28573
28573
|
JSON.stringify(next),
|
|
@@ -29070,6 +29070,294 @@ var init_categories = __esm({
|
|
|
29070
29070
|
}
|
|
29071
29071
|
});
|
|
29072
29072
|
|
|
29073
|
+
// server/src/channels/markdown.ts
|
|
29074
|
+
function pieces(markdown) {
|
|
29075
|
+
const out = [];
|
|
29076
|
+
let prose = [];
|
|
29077
|
+
let code = null;
|
|
29078
|
+
for (const line of markdown.split("\n")) {
|
|
29079
|
+
if (code) {
|
|
29080
|
+
if (/^\s*```/.test(line)) {
|
|
29081
|
+
out.push({ code: true, text: code.join("\n") });
|
|
29082
|
+
code = null;
|
|
29083
|
+
} else code.push(line);
|
|
29084
|
+
} else if (/^\s*```[^`]*$/.test(line)) {
|
|
29085
|
+
if (prose.length) out.push({ code: false, text: prose.join("\n") });
|
|
29086
|
+
prose = [];
|
|
29087
|
+
code = [];
|
|
29088
|
+
} else prose.push(line);
|
|
29089
|
+
}
|
|
29090
|
+
if (code) out.push({ code: true, text: code.join("\n") });
|
|
29091
|
+
if (prose.length) out.push({ code: false, text: prose.join("\n") });
|
|
29092
|
+
return out;
|
|
29093
|
+
}
|
|
29094
|
+
function holdLinks(text, render2, rewrite) {
|
|
29095
|
+
const links = [];
|
|
29096
|
+
const held = text.replace(/\[([^\]\n]+)\]\((https?:\/\/[^)\s]+)\)/g, (_whole, label, url) => `${links.push(render2(label, url)) - 1}`);
|
|
29097
|
+
return rewrite(held).replace(/\u0002(\d+)\u0002/g, (_whole, index) => links[Number(index)]);
|
|
29098
|
+
}
|
|
29099
|
+
function lines(text, bold) {
|
|
29100
|
+
return text.split("\n").map((line) => {
|
|
29101
|
+
if (/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/.test(line)) return null;
|
|
29102
|
+
if (/^\s*\|.*\|\s*$/.test(line))
|
|
29103
|
+
return line.trim().replace(/^\||\|$/g, "").split("|").map((cell) => cell.trim()).filter(Boolean).join(" \xB7 ");
|
|
29104
|
+
if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) return "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
|
|
29105
|
+
const heading = /^\s*#{1,6}\s+(.*)$/.exec(line);
|
|
29106
|
+
if (heading) return bold(heading[1].replace(/\*\*/g, ""));
|
|
29107
|
+
const bullet = /^(\s*)[-*+]\s+(.*)$/.exec(line);
|
|
29108
|
+
if (bullet) return `${bullet[1].length >= 2 ? " \u25E6 " : "\u2022 "}${bullet[2]}`;
|
|
29109
|
+
return line.replace(/^\s*>\s?/, "");
|
|
29110
|
+
}).filter((line) => line !== null).join("\n").replace(/\n{3,}/g, "\n\n");
|
|
29111
|
+
}
|
|
29112
|
+
function inline(text, codeOf, rest) {
|
|
29113
|
+
const codes = [];
|
|
29114
|
+
const held = text.replace(/`{1,3}([^`\n]+)`{1,3}/g, (_whole, code) => `\0${codes.push(code) - 1}\0`);
|
|
29115
|
+
return rest(held).replace(/\u0000(\d+)\u0000/g, (_whole, index) => codeOf(codes[Number(index)]));
|
|
29116
|
+
}
|
|
29117
|
+
function toTelegramHtml(markdown, { inQuote = false } = {}) {
|
|
29118
|
+
return pieces(markdown).map((piece) => {
|
|
29119
|
+
if (piece.code) return inQuote ? `<code>${escapeHtml(piece.text)}</code>` : `<pre>${escapeHtml(piece.text)}</pre>`;
|
|
29120
|
+
return inline(
|
|
29121
|
+
lines(piece.text, (heading) => `**${heading}**`),
|
|
29122
|
+
(code) => `<code>${escapeHtml(code)}</code>`,
|
|
29123
|
+
(text) => holdLinks(
|
|
29124
|
+
escapeHtml(text),
|
|
29125
|
+
(label, url) => `<a href="${url.replace(/"/g, """)}">${label}</a>`,
|
|
29126
|
+
(prose) => prose.replace(/\*\*([^*\n]+)\*\*/g, "<b>$1</b>").replace(/(^|[\s(])__([^_\n]+)__(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1<b>$2</b>").replace(/~~([^~\n]+)~~/g, "<s>$1</s>").replace(/(^|[\s(])\*([^*\s][^*\n]*?)\*(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1<i>$2</i>").replace(/(^|[\s(])_([^_\s][^_\n]*?)_(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1<i>$2</i>")
|
|
29127
|
+
)
|
|
29128
|
+
);
|
|
29129
|
+
}).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
29130
|
+
}
|
|
29131
|
+
function toSlackMrkdwn(markdown) {
|
|
29132
|
+
return pieces(markdown).map((piece) => {
|
|
29133
|
+
if (piece.code) return `\`\`\`
|
|
29134
|
+
${escapeHtml(piece.text)}
|
|
29135
|
+
\`\`\``;
|
|
29136
|
+
return inline(
|
|
29137
|
+
lines(piece.text, (heading) => `**${heading}**`),
|
|
29138
|
+
// Escaped like the rest: "<https://x>" or "<!here>" in code would otherwise become a link or a mention.
|
|
29139
|
+
(code) => `\`${escapeHtml(code)}\``,
|
|
29140
|
+
(text) => holdLinks(
|
|
29141
|
+
escapeHtml(text),
|
|
29142
|
+
(label, url) => `<${url}|${label}>`,
|
|
29143
|
+
(prose) => prose.replace(/(^|[\s(])\*([^*\s][^*\n]*?)\*(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1$2").replace(/\*\*([^*\n]+)\*\*/g, "*$1*").replace(/(^|[\s(])__([^_\n]+)__(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1*$2*").replace(/~~([^~\n]+)~~/g, "~$1~").replace(/\u0001/g, "_")
|
|
29144
|
+
)
|
|
29145
|
+
);
|
|
29146
|
+
}).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
29147
|
+
}
|
|
29148
|
+
function gistOf(markdown, skip = "") {
|
|
29149
|
+
const prose = pieces(markdown).filter((piece) => !piece.code).map((piece) => piece.text).join("\n");
|
|
29150
|
+
const strip = (text) => text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^\s*#+\s+/, "").replace(/`+|\*\*|~~/g, "").replace(/(^|[\s(])(__|_|\*)([^_*\n]+?)\2(?=[\s),:;!?]|\.(?!\w)|$)/g, "$1$3").replace(/\s+/g, " ").trim();
|
|
29151
|
+
const short2 = (text, max) => text.length > max ? `${text.slice(0, max - 1).trimEnd()}\u2026` : text;
|
|
29152
|
+
const items = prose.split("\n").map((line) => /^\s{0,1}(?:[-*+]|\d+[.)])\s+(.*)$/.exec(line)?.[1]).filter((item) => Boolean(item)).map(strip).filter(Boolean);
|
|
29153
|
+
if (items.length >= 2) {
|
|
29154
|
+
const shown = items.slice(0, 4).map((item) => `\u2022 ${short2(item, 110)}`);
|
|
29155
|
+
return items.length > 4 ? [...shown, `\u2026and ${items.length - 4} more`] : shown;
|
|
29156
|
+
}
|
|
29157
|
+
const opening = strip(
|
|
29158
|
+
prose.split(/\n\s*\n/).map((part) => part.trim()).find((part) => part && !/^\s*(#|\|)/.test(part)) ?? ""
|
|
29159
|
+
);
|
|
29160
|
+
const sentences = opening.split(/(?<=[.?!])\s+/).slice(0, 2).join(" ");
|
|
29161
|
+
const gist = short2(sentences, 260);
|
|
29162
|
+
return gist && strip(skip) !== gist ? [gist] : [];
|
|
29163
|
+
}
|
|
29164
|
+
var escapeHtml;
|
|
29165
|
+
var init_markdown = __esm({
|
|
29166
|
+
"server/src/channels/markdown.ts"() {
|
|
29167
|
+
"use strict";
|
|
29168
|
+
escapeHtml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
29169
|
+
}
|
|
29170
|
+
});
|
|
29171
|
+
|
|
29172
|
+
// server/src/presentation.ts
|
|
29173
|
+
function mainQuestion(message) {
|
|
29174
|
+
const paragraphs = plain(message).split(/\n\s*\n/).map((part) => part.trim()).filter(Boolean);
|
|
29175
|
+
const sentencesOf = (part) => part.split(/(?<=[.?!])\s+|\n+/).map((sentence) => sentence.trim()).filter(Boolean);
|
|
29176
|
+
const last = paragraphs[paragraphs.length - 1] ?? "";
|
|
29177
|
+
const sentences = sentencesOf(last);
|
|
29178
|
+
const first = sentences.findIndex((sentence) => sentence.endsWith("?"));
|
|
29179
|
+
if (first !== -1) {
|
|
29180
|
+
const run4 = sentences.slice(first).join(" ");
|
|
29181
|
+
const lastQuestion = [...sentences].reverse().find((sentence) => sentence.endsWith("?"));
|
|
29182
|
+
return { headline: oneLine(run4.length <= 160 ? run4 : lastQuestion), isQuestion: true };
|
|
29183
|
+
}
|
|
29184
|
+
const anyQuestion = [...paragraphs].reverse().flatMap((part) => sentencesOf(part).filter((sentence) => sentence.endsWith("?")).reverse())[0];
|
|
29185
|
+
if (anyQuestion) return { headline: oneLine(anyQuestion), isQuestion: true };
|
|
29186
|
+
return { headline: oneLine(last || "Claude finished its turn."), isQuestion: false };
|
|
29187
|
+
}
|
|
29188
|
+
function target(toolName2, input) {
|
|
29189
|
+
if (!input) return "";
|
|
29190
|
+
const pick = (key) => typeof input[key] === "string" ? input[key] : "";
|
|
29191
|
+
const file = pick("file_path") || pick("notebook_path") || pick("path");
|
|
29192
|
+
if (file) return file.split(/[\\/]/).slice(-2).join("/");
|
|
29193
|
+
const command = pick("command");
|
|
29194
|
+
if (command) return command.split("\n")[0];
|
|
29195
|
+
return pick("url") || pick("pattern") || pick("query") || pick("description") || (toolName2.startsWith("mcp__") ? toolName2.split("__").pop() ?? "" : "");
|
|
29196
|
+
}
|
|
29197
|
+
function present(interaction, sessionTitle2) {
|
|
29198
|
+
const data = interaction.data ?? {};
|
|
29199
|
+
const answerable = interaction.status === "open";
|
|
29200
|
+
const base = { kind: interaction.kind, sessionTitle: sessionTitle2, answerable, quickReplies: [] };
|
|
29201
|
+
switch (interaction.kind) {
|
|
29202
|
+
case "permission": {
|
|
29203
|
+
const tool = String(data.toolName ?? "a tool");
|
|
29204
|
+
const what = target(tool, data.toolInput);
|
|
29205
|
+
const shortTool = tool.startsWith("mcp__") ? tool.replace(/^mcp__/, "").split("__")[0] : tool;
|
|
29206
|
+
return {
|
|
29207
|
+
...base,
|
|
29208
|
+
headline: oneLine(`${shortTool}${what ? ` \u2192 ${what}` : ""}`, 120),
|
|
29209
|
+
question: data.agent ? `The ${data.agent} agent asks to use ${tool}.` : `Claude asks to use ${tool}.`,
|
|
29210
|
+
choices: [
|
|
29211
|
+
{ ref: "o:0", label: "Allow", tone: "primary" },
|
|
29212
|
+
{ ref: "o:1", label: "Allow for this session" },
|
|
29213
|
+
{ ref: "o:2", label: "Deny", tone: "danger" }
|
|
29214
|
+
],
|
|
29215
|
+
freeText: "Deny with a reason for Claude...",
|
|
29216
|
+
details: { label: "Full request", text: interaction.body, format: "code" }
|
|
29217
|
+
};
|
|
29218
|
+
}
|
|
29219
|
+
case "question": {
|
|
29220
|
+
const questions = (Array.isArray(data.questions) ? data.questions : []).map((item) => ({
|
|
29221
|
+
header: item?.header ? String(item.header) : void 0,
|
|
29222
|
+
question: String(item?.question ?? ""),
|
|
29223
|
+
options: (Array.isArray(item?.options) ? item.options : []).map((option) => ({
|
|
29224
|
+
label: String(option?.label ?? option),
|
|
29225
|
+
description: option?.description ? String(option.description) : void 0
|
|
29226
|
+
})),
|
|
29227
|
+
multiSelect: Boolean(item?.multiSelect)
|
|
29228
|
+
}));
|
|
29229
|
+
const first = questions[0];
|
|
29230
|
+
const single = questions.length === 1;
|
|
29231
|
+
return {
|
|
29232
|
+
...base,
|
|
29233
|
+
headline: oneLine(first?.question || interaction.body || interaction.title),
|
|
29234
|
+
question: first?.header,
|
|
29235
|
+
questions,
|
|
29236
|
+
choices: single ? first.options.map((option, index) => ({ ref: `o:${index}`, label: option.label, description: option.description, tone: index === 0 ? "primary" : void 0 })) : [],
|
|
29237
|
+
freeText: "Or answer in your own words..."
|
|
29238
|
+
};
|
|
29239
|
+
}
|
|
29240
|
+
case "turn": {
|
|
29241
|
+
const { headline, isQuestion } = mainQuestion(interaction.body);
|
|
29242
|
+
const replies = isQuestion ? ["Yes", "No", "Continue"] : ["Continue"];
|
|
29243
|
+
return {
|
|
29244
|
+
...base,
|
|
29245
|
+
headline,
|
|
29246
|
+
choices: [],
|
|
29247
|
+
quickReplies: replies.map((label, index) => ({ ref: `q:${index}`, label })),
|
|
29248
|
+
freeText: "Reply to the session...",
|
|
29249
|
+
details: interaction.body && plain(interaction.body) !== headline ? { label: "Full message", text: interaction.body, format: "markdown" } : void 0,
|
|
29250
|
+
gist: interaction.body ? gistOf(interaction.body, headline) : void 0
|
|
29251
|
+
};
|
|
29252
|
+
}
|
|
29253
|
+
case "mcp":
|
|
29254
|
+
return {
|
|
29255
|
+
...base,
|
|
29256
|
+
headline: oneLine(`Needs the MCP server '${data.name ?? ""}'`),
|
|
29257
|
+
question: data.reason ? oneLine(String(data.reason), 240) : void 0,
|
|
29258
|
+
choices: [
|
|
29259
|
+
{ ref: "o:0", label: "Install & resume", tone: "primary" },
|
|
29260
|
+
{ ref: "o:1", label: "Skip" }
|
|
29261
|
+
],
|
|
29262
|
+
freeText: "Or tell Claude what to do instead...",
|
|
29263
|
+
details: data.command ? { label: "Install command", text: String(data.command), format: "code" } : void 0
|
|
29264
|
+
};
|
|
29265
|
+
case "done": {
|
|
29266
|
+
const deliverables = Object.entries(data.deliverables ?? {}).map(([key, value]) => `- **${key}**: ${typeof value === "string" ? value : JSON.stringify(value)}`).join("\n");
|
|
29267
|
+
return {
|
|
29268
|
+
...base,
|
|
29269
|
+
answerable: false,
|
|
29270
|
+
headline: oneLine(String(data.summary ?? interaction.body ?? "Task done."), 200),
|
|
29271
|
+
question: data.category ? `Category: ${data.category}` : void 0,
|
|
29272
|
+
choices: [],
|
|
29273
|
+
freeText: null,
|
|
29274
|
+
details: deliverables ? { label: "Deliverables", text: deliverables, format: "markdown" } : void 0
|
|
29275
|
+
};
|
|
29276
|
+
}
|
|
29277
|
+
}
|
|
29278
|
+
}
|
|
29279
|
+
function resolveChoice(interaction, ref) {
|
|
29280
|
+
const [type, raw] = ref.split(":");
|
|
29281
|
+
const index = Number(raw);
|
|
29282
|
+
if (!Number.isInteger(index)) return null;
|
|
29283
|
+
if (type === "q") return present(interaction, "").quickReplies[index]?.label ?? null;
|
|
29284
|
+
if (type !== "o") return null;
|
|
29285
|
+
switch (interaction.kind) {
|
|
29286
|
+
case "permission":
|
|
29287
|
+
return ["1", "2", "3"][index] ?? null;
|
|
29288
|
+
case "question":
|
|
29289
|
+
return String(index + 1);
|
|
29290
|
+
case "mcp":
|
|
29291
|
+
return ["install", "skip"][index] ?? null;
|
|
29292
|
+
default:
|
|
29293
|
+
return null;
|
|
29294
|
+
}
|
|
29295
|
+
}
|
|
29296
|
+
var oneLine, plain;
|
|
29297
|
+
var init_presentation = __esm({
|
|
29298
|
+
"server/src/presentation.ts"() {
|
|
29299
|
+
"use strict";
|
|
29300
|
+
init_markdown();
|
|
29301
|
+
oneLine = (text, max = 160) => {
|
|
29302
|
+
const flat2 = text.replace(/\s+/g, " ").trim();
|
|
29303
|
+
return flat2.length > max ? `${flat2.slice(0, max - 1).trimEnd()}...` : flat2;
|
|
29304
|
+
};
|
|
29305
|
+
plain = (text) => text.replace(/`{3}[\s\S]*?`{3}/g, " ").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^\s*(#+|>)\s?/gm, "").replace(/`+|\*\*|~~/g, "").replace(/(^|[\s(])(__|_|\*)([^_*\n]+?)\2(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1$3").trim();
|
|
29306
|
+
}
|
|
29307
|
+
});
|
|
29308
|
+
|
|
29309
|
+
// server/src/smallModel.ts
|
|
29310
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
29311
|
+
import fs5 from "node:fs";
|
|
29312
|
+
import path6 from "node:path";
|
|
29313
|
+
function askSmallModel(prompt, timeoutMs = 45e3) {
|
|
29314
|
+
const claude2 = findClaudeCode();
|
|
29315
|
+
if (!claude2 || claudeStatus()?.problem) return Promise.resolve(null);
|
|
29316
|
+
return new Promise((resolve) => {
|
|
29317
|
+
const cwd = path6.join(hubHome, "global");
|
|
29318
|
+
fs5.mkdirSync(cwd, { recursive: true });
|
|
29319
|
+
const child = spawn2(claude2, ["-p", "--model", "haiku", "--tools", "", "--no-session-persistence", "--strict-mcp-config", "--output-format", "text"], {
|
|
29320
|
+
cwd,
|
|
29321
|
+
windowsHide: true,
|
|
29322
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
29323
|
+
});
|
|
29324
|
+
let out = "";
|
|
29325
|
+
const timer = setTimeout(() => child.kill(), timeoutMs);
|
|
29326
|
+
child.stdout.on("data", (chunk) => out += chunk);
|
|
29327
|
+
child.on("error", () => {
|
|
29328
|
+
clearTimeout(timer);
|
|
29329
|
+
resolve(null);
|
|
29330
|
+
});
|
|
29331
|
+
child.on("close", (code) => {
|
|
29332
|
+
clearTimeout(timer);
|
|
29333
|
+
resolve(code === 0 && out.trim() ? out : null);
|
|
29334
|
+
});
|
|
29335
|
+
child.stdin.end(prompt);
|
|
29336
|
+
});
|
|
29337
|
+
}
|
|
29338
|
+
function jsonIn(answer, open) {
|
|
29339
|
+
if (!answer) return null;
|
|
29340
|
+
const start = answer.indexOf(open);
|
|
29341
|
+
const end = answer.lastIndexOf(open === "[" ? "]" : "}");
|
|
29342
|
+
if (start === -1 || end <= start) return null;
|
|
29343
|
+
try {
|
|
29344
|
+
return JSON.parse(answer.slice(start, end + 1));
|
|
29345
|
+
} catch {
|
|
29346
|
+
return null;
|
|
29347
|
+
}
|
|
29348
|
+
}
|
|
29349
|
+
async function pickNumbers(prompt) {
|
|
29350
|
+
const picked = jsonIn(await askSmallModel(prompt), "[");
|
|
29351
|
+
return picked ? picked.map(Number).filter((number) => Number.isInteger(number) && number > 0) : null;
|
|
29352
|
+
}
|
|
29353
|
+
var init_smallModel = __esm({
|
|
29354
|
+
"server/src/smallModel.ts"() {
|
|
29355
|
+
"use strict";
|
|
29356
|
+
init_claudeCode();
|
|
29357
|
+
init_paths();
|
|
29358
|
+
}
|
|
29359
|
+
});
|
|
29360
|
+
|
|
29073
29361
|
// server/src/terminals.ts
|
|
29074
29362
|
import * as pty from "@lydell/node-pty";
|
|
29075
29363
|
function onTerminalExit(listener) {
|
|
@@ -29102,6 +29390,7 @@ function spawnTerminal(input) {
|
|
|
29102
29390
|
clients: /* @__PURE__ */ new Set(),
|
|
29103
29391
|
backlog: [],
|
|
29104
29392
|
backlogSize: 0,
|
|
29393
|
+
lastOutputAt: Date.now(),
|
|
29105
29394
|
cols,
|
|
29106
29395
|
rows
|
|
29107
29396
|
};
|
|
@@ -29109,6 +29398,7 @@ function spawnTerminal(input) {
|
|
|
29109
29398
|
child.onData(bindProject((data) => {
|
|
29110
29399
|
terminal.backlog.push(data);
|
|
29111
29400
|
terminal.backlogSize += data.length;
|
|
29401
|
+
terminal.lastOutputAt = Date.now();
|
|
29112
29402
|
while (terminal.backlogSize > BACKLOG_LIMIT && terminal.backlog.length > 1) {
|
|
29113
29403
|
terminal.backlogSize -= terminal.backlog.shift().length;
|
|
29114
29404
|
}
|
|
@@ -29195,11 +29485,11 @@ function killTerminal(terminal) {
|
|
|
29195
29485
|
function waitForExit(terminal, timeoutMs = 1e4) {
|
|
29196
29486
|
return new Promise((resolve) => {
|
|
29197
29487
|
const started = Date.now();
|
|
29198
|
-
const
|
|
29488
|
+
const check3 = () => {
|
|
29199
29489
|
if (!terminals.has(terminal.id) || Date.now() - started > timeoutMs) return resolve();
|
|
29200
|
-
setTimeout(
|
|
29490
|
+
setTimeout(check3, 100);
|
|
29201
29491
|
};
|
|
29202
|
-
|
|
29492
|
+
check3();
|
|
29203
29493
|
});
|
|
29204
29494
|
}
|
|
29205
29495
|
function killAll() {
|
|
@@ -29238,232 +29528,6 @@ var init_terminals = __esm({
|
|
|
29238
29528
|
}
|
|
29239
29529
|
});
|
|
29240
29530
|
|
|
29241
|
-
// server/src/inventory.ts
|
|
29242
|
-
import fs5 from "node:fs";
|
|
29243
|
-
import os2 from "node:os";
|
|
29244
|
-
import path6 from "node:path";
|
|
29245
|
-
function readJson(file) {
|
|
29246
|
-
try {
|
|
29247
|
-
return JSON.parse(fs5.readFileSync(file, "utf8"));
|
|
29248
|
-
} catch {
|
|
29249
|
-
return null;
|
|
29250
|
-
}
|
|
29251
|
-
}
|
|
29252
|
-
function frontMatter(text) {
|
|
29253
|
-
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
|
|
29254
|
-
if (!match) return {};
|
|
29255
|
-
const fields = {};
|
|
29256
|
-
let lastKey = "";
|
|
29257
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
29258
|
-
const pair = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
29259
|
-
if (pair) {
|
|
29260
|
-
lastKey = pair[1];
|
|
29261
|
-
fields[lastKey] = pair[2].replace(/^["']|["']$/g, "").replace(/^[>|]-?$/, "");
|
|
29262
|
-
} else if (lastKey && /^\s+\S/.test(line)) {
|
|
29263
|
-
fields[lastKey] = `${fields[lastKey]} ${line.trim()}`.trim();
|
|
29264
|
-
}
|
|
29265
|
-
}
|
|
29266
|
-
return fields;
|
|
29267
|
-
}
|
|
29268
|
-
function definition(file, fallbackName, scope, plugin) {
|
|
29269
|
-
let text;
|
|
29270
|
-
try {
|
|
29271
|
-
text = fs5.readFileSync(file, "utf8");
|
|
29272
|
-
} catch {
|
|
29273
|
-
return null;
|
|
29274
|
-
}
|
|
29275
|
-
const meta = frontMatter(text);
|
|
29276
|
-
const name = meta.name || fallbackName;
|
|
29277
|
-
return {
|
|
29278
|
-
name: plugin ? `${plugin.split("@")[0]}:${name}` : name,
|
|
29279
|
-
description: meta.description ?? "",
|
|
29280
|
-
scope,
|
|
29281
|
-
plugin,
|
|
29282
|
-
path: file,
|
|
29283
|
-
model: meta.model || void 0
|
|
29284
|
-
};
|
|
29285
|
-
}
|
|
29286
|
-
function markdownFiles(dir, depth = 3) {
|
|
29287
|
-
const found = [];
|
|
29288
|
-
const walk = (current, left) => {
|
|
29289
|
-
let entries;
|
|
29290
|
-
try {
|
|
29291
|
-
entries = fs5.readdirSync(current, { withFileTypes: true });
|
|
29292
|
-
} catch {
|
|
29293
|
-
return;
|
|
29294
|
-
}
|
|
29295
|
-
for (const entry of entries) {
|
|
29296
|
-
const full = path6.join(current, entry.name);
|
|
29297
|
-
if (entry.isDirectory() && left > 0) walk(full, left - 1);
|
|
29298
|
-
else if (entry.isFile() && entry.name.endsWith(".md")) found.push(full);
|
|
29299
|
-
}
|
|
29300
|
-
};
|
|
29301
|
-
walk(dir, depth);
|
|
29302
|
-
return found;
|
|
29303
|
-
}
|
|
29304
|
-
function agentsIn(dir, scope, plugin) {
|
|
29305
|
-
return markdownFiles(dir, 1).map((file) => definition(file, path6.basename(file, ".md"), scope, plugin)).filter((item) => item !== null);
|
|
29306
|
-
}
|
|
29307
|
-
function skillsIn(dir, scope, plugin) {
|
|
29308
|
-
let entries;
|
|
29309
|
-
try {
|
|
29310
|
-
entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
29311
|
-
} catch {
|
|
29312
|
-
return [];
|
|
29313
|
-
}
|
|
29314
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) => definition(path6.join(dir, entry.name, "SKILL.md"), entry.name, scope, plugin)).filter((item) => item !== null);
|
|
29315
|
-
}
|
|
29316
|
-
function commandsIn(dir, scope, plugin) {
|
|
29317
|
-
return markdownFiles(dir, 2).map((file) => definition(file, path6.relative(dir, file).replace(/\.md$/, "").replace(/[\\/]/g, ":"), scope, plugin)).filter((item) => item !== null);
|
|
29318
|
-
}
|
|
29319
|
-
function mcpFrom(servers, scope, source, plugin) {
|
|
29320
|
-
if (!servers || typeof servers !== "object") return [];
|
|
29321
|
-
return Object.entries(servers).map(([name, config]) => ({
|
|
29322
|
-
name: plugin ? `plugin:${plugin.split("@")[0]}:${name}` : name,
|
|
29323
|
-
scope,
|
|
29324
|
-
plugin,
|
|
29325
|
-
transport: String(config?.type ?? (config?.url ? "http" : "stdio")),
|
|
29326
|
-
target: config?.url ? String(config.url) : [config?.command, ...Array.isArray(config?.args) ? config.args : []].filter(Boolean).join(" "),
|
|
29327
|
-
source
|
|
29328
|
-
}));
|
|
29329
|
-
}
|
|
29330
|
-
function hooksFrom(settings, scope, source, plugin) {
|
|
29331
|
-
const hooks = settings?.hooks;
|
|
29332
|
-
if (!hooks || typeof hooks !== "object") return [];
|
|
29333
|
-
const items = [];
|
|
29334
|
-
for (const [event, groups] of Object.entries(hooks)) {
|
|
29335
|
-
if (!Array.isArray(groups)) continue;
|
|
29336
|
-
for (const group of groups) {
|
|
29337
|
-
for (const hook of Array.isArray(group?.hooks) ? group.hooks : []) {
|
|
29338
|
-
items.push({
|
|
29339
|
-
event,
|
|
29340
|
-
matcher: String(group?.matcher ?? ""),
|
|
29341
|
-
type: String(hook?.type ?? "command"),
|
|
29342
|
-
scope,
|
|
29343
|
-
plugin,
|
|
29344
|
-
source,
|
|
29345
|
-
summary: String(hook?.command ?? hook?.url ?? hook?.prompt ?? hook?.tool ?? "").slice(0, 200)
|
|
29346
|
-
});
|
|
29347
|
-
}
|
|
29348
|
-
}
|
|
29349
|
-
}
|
|
29350
|
-
return items;
|
|
29351
|
-
}
|
|
29352
|
-
function localProjectConfig(global3) {
|
|
29353
|
-
const projects = global3?.projects ?? {};
|
|
29354
|
-
const wanted = [projectRoot(), projectRoot().replace(/\\/g, "/")];
|
|
29355
|
-
for (const [key, value] of Object.entries(projects)) {
|
|
29356
|
-
if (wanted.some((candidate) => candidate.toLowerCase() === key.toLowerCase())) return value;
|
|
29357
|
-
}
|
|
29358
|
-
return null;
|
|
29359
|
-
}
|
|
29360
|
-
function readInventory() {
|
|
29361
|
-
const userSettingsFile = path6.join(claudeHome, "settings.json");
|
|
29362
|
-
const projectSettingsFile = path6.join(projectRoot(), ".claude", "settings.json");
|
|
29363
|
-
const localSettingsFile = path6.join(projectRoot(), ".claude", "settings.local.json");
|
|
29364
|
-
const userSettings = readJson(userSettingsFile);
|
|
29365
|
-
const projectSettings = readJson(projectSettingsFile);
|
|
29366
|
-
const localSettings = readJson(localSettingsFile);
|
|
29367
|
-
const globalConfigFile = path6.join(os2.homedir(), ".claude.json");
|
|
29368
|
-
const globalConfig = readJson(globalConfigFile);
|
|
29369
|
-
const local = localProjectConfig(globalConfig);
|
|
29370
|
-
const enabled = {
|
|
29371
|
-
...userSettings?.enabledPlugins ?? {},
|
|
29372
|
-
...projectSettings?.enabledPlugins ?? {},
|
|
29373
|
-
...localSettings?.enabledPlugins ?? {}
|
|
29374
|
-
};
|
|
29375
|
-
const plugins = [];
|
|
29376
|
-
const installed = readJson(path6.join(claudeHome, "plugins", "installed_plugins.json"));
|
|
29377
|
-
for (const [id, installs] of Object.entries(installed?.plugins ?? {})) {
|
|
29378
|
-
const install = Array.isArray(installs) ? installs[installs.length - 1] : null;
|
|
29379
|
-
if (!install?.installPath) continue;
|
|
29380
|
-
plugins.push({
|
|
29381
|
-
id,
|
|
29382
|
-
version: String(install.version ?? ""),
|
|
29383
|
-
scope: String(install.scope ?? "user"),
|
|
29384
|
-
// Installed plugins are on unless a settings file turns them off.
|
|
29385
|
-
enabled: enabled[id] !== false,
|
|
29386
|
-
path: install.installPath
|
|
29387
|
-
});
|
|
29388
|
-
}
|
|
29389
|
-
const agents = [
|
|
29390
|
-
...agentsIn(path6.join(claudeHome, "agents"), "user"),
|
|
29391
|
-
...agentsIn(path6.join(projectRoot(), ".claude", "agents"), "project"),
|
|
29392
|
-
// No file behind these, so no path: they can be turned off, not edited.
|
|
29393
|
-
...BUILT_IN_AGENTS.map(([name, description]) => ({ name, description, scope: "built-in", path: "" }))
|
|
29394
|
-
];
|
|
29395
|
-
const skills = [...skillsIn(path6.join(claudeHome, "skills"), "user"), ...skillsIn(path6.join(projectRoot(), ".claude", "skills"), "project")];
|
|
29396
|
-
const commands = [
|
|
29397
|
-
...commandsIn(path6.join(claudeHome, "commands"), "user"),
|
|
29398
|
-
...commandsIn(path6.join(projectRoot(), ".claude", "commands"), "project")
|
|
29399
|
-
];
|
|
29400
|
-
const disabledJson = new Set(local?.disabledMcpjsonServers ?? []);
|
|
29401
|
-
const mcpServers = [
|
|
29402
|
-
...mcpFrom(globalConfig?.mcpServers, "user", globalConfigFile),
|
|
29403
|
-
...mcpFrom(local?.mcpServers, "local", globalConfigFile),
|
|
29404
|
-
...mcpFrom(readJson(path6.join(projectRoot(), ".mcp.json"))?.mcpServers, "project", path6.join(projectRoot(), ".mcp.json")).map(
|
|
29405
|
-
(server2) => ({ ...server2, disabled: disabledJson.has(server2.name) })
|
|
29406
|
-
)
|
|
29407
|
-
];
|
|
29408
|
-
const hooks = [
|
|
29409
|
-
...hooksFrom(userSettings, "user", userSettingsFile),
|
|
29410
|
-
...hooksFrom(projectSettings, "project", projectSettingsFile),
|
|
29411
|
-
...hooksFrom(localSettings, "local", localSettingsFile)
|
|
29412
|
-
];
|
|
29413
|
-
for (const plugin of plugins.filter((item) => item.enabled)) {
|
|
29414
|
-
agents.push(...agentsIn(path6.join(plugin.path, "agents"), "plugin", plugin.id));
|
|
29415
|
-
skills.push(...skillsIn(path6.join(plugin.path, "skills"), "plugin", plugin.id));
|
|
29416
|
-
commands.push(...commandsIn(path6.join(plugin.path, "commands"), "plugin", plugin.id));
|
|
29417
|
-
const pluginMcp = readJson(path6.join(plugin.path, ".mcp.json"));
|
|
29418
|
-
mcpServers.push(...mcpFrom(pluginMcp?.mcpServers ?? pluginMcp, "plugin", path6.join(plugin.path, ".mcp.json"), plugin.id));
|
|
29419
|
-
hooks.push(...hooksFrom(readJson(path6.join(plugin.path, "hooks", "hooks.json")), "plugin", path6.join(plugin.path, "hooks", "hooks.json"), plugin.id));
|
|
29420
|
-
}
|
|
29421
|
-
return { agents, skills, commands, mcpServers, hooks, plugins };
|
|
29422
|
-
}
|
|
29423
|
-
function memoryFiles() {
|
|
29424
|
-
const files = [];
|
|
29425
|
-
const add = (file, label, group, scope, always = false) => {
|
|
29426
|
-
const exists = fs5.existsSync(file);
|
|
29427
|
-
if (exists || always) files.push({ path: file, label, group, scope, exists });
|
|
29428
|
-
};
|
|
29429
|
-
add(path6.join(claudeHome, "CLAUDE.md"), "~/.claude/CLAUDE.md", "memory", "user", true);
|
|
29430
|
-
add(path6.join(projectRoot(), "CLAUDE.md"), "CLAUDE.md", "memory", "project", true);
|
|
29431
|
-
add(path6.join(projectRoot(), ".claude", "CLAUDE.md"), ".claude/CLAUDE.md", "memory", "project");
|
|
29432
|
-
add(path6.join(projectRoot(), "CLAUDE.local.md"), "CLAUDE.local.md", "memory", "local");
|
|
29433
|
-
for (const file of markdownFiles(path6.join(claudeHome, "rules"))) add(file, `~/.claude/rules/${path6.relative(path6.join(claudeHome, "rules"), file)}`, "rules", "user");
|
|
29434
|
-
for (const file of markdownFiles(path6.join(projectRoot(), ".claude", "rules"))) add(file, `.claude/rules/${path6.relative(path6.join(projectRoot(), ".claude", "rules"), file)}`, "rules", "project");
|
|
29435
|
-
for (const file of markdownFiles(path6.join(transcriptsDir(), "memory"), 1)) add(file, `memory/${path6.basename(file)}`, "auto-memory", "local");
|
|
29436
|
-
const inventory = readInventory();
|
|
29437
|
-
for (const agent of inventory.agents.filter((item) => item.scope !== "plugin" && item.path)) add(agent.path, `agent: ${agent.name}`, "agent", agent.scope);
|
|
29438
|
-
for (const skill of inventory.skills.filter((item) => item.scope !== "plugin")) add(skill.path, `skill: ${skill.name}`, "skill", skill.scope);
|
|
29439
|
-
for (const command of inventory.commands.filter((item) => item.scope !== "plugin")) add(command.path, `command: /${command.name}`, "command", command.scope);
|
|
29440
|
-
return files.map((file) => ({ ...file, label: file.label.replace(/\\/g, "/") }));
|
|
29441
|
-
}
|
|
29442
|
-
function isMemoryFile(file) {
|
|
29443
|
-
const wanted = path6.resolve(file).toLowerCase();
|
|
29444
|
-
return memoryFiles().some((item) => path6.resolve(item.path).toLowerCase() === wanted);
|
|
29445
|
-
}
|
|
29446
|
-
function isDefinitionFile(file) {
|
|
29447
|
-
const wanted = path6.resolve(file).toLowerCase();
|
|
29448
|
-
const inventory = readInventory();
|
|
29449
|
-
return [...inventory.agents, ...inventory.skills, ...inventory.commands].some((item) => item.path && path6.resolve(item.path).toLowerCase() === wanted);
|
|
29450
|
-
}
|
|
29451
|
-
var BUILT_IN_AGENTS;
|
|
29452
|
-
var init_inventory = __esm({
|
|
29453
|
-
"server/src/inventory.ts"() {
|
|
29454
|
-
"use strict";
|
|
29455
|
-
init_paths();
|
|
29456
|
-
BUILT_IN_AGENTS = [
|
|
29457
|
-
["Explore", "Fast read-only search across the codebase."],
|
|
29458
|
-
["Plan", "Designs an implementation plan."],
|
|
29459
|
-
["general-purpose", "Multi-step research, code search and tasks."],
|
|
29460
|
-
["claude-code-guide", "Answers questions about Claude Code, the Agent SDK and the API."],
|
|
29461
|
-
["statusline-setup", "Configures the status line."],
|
|
29462
|
-
["web-fetch", "Fetches and reads web pages."]
|
|
29463
|
-
];
|
|
29464
|
-
}
|
|
29465
|
-
});
|
|
29466
|
-
|
|
29467
29531
|
// server/src/transcripts.ts
|
|
29468
29532
|
import fs6 from "node:fs";
|
|
29469
29533
|
import path7 from "node:path";
|
|
@@ -29847,414 +29911,6 @@ var init_transcripts = __esm({
|
|
|
29847
29911
|
}
|
|
29848
29912
|
});
|
|
29849
29913
|
|
|
29850
|
-
// server/src/toolset.ts
|
|
29851
|
-
function normaliseToolset(input) {
|
|
29852
|
-
const value = input ?? {};
|
|
29853
|
-
const list = (items) => [...new Set((Array.isArray(items) ? items : []).map(String).filter(Boolean))].sort();
|
|
29854
|
-
return { agents: list(value.agents), skills: list(value.skills), mcp: list(value.mcp), plugins: list(value.plugins) };
|
|
29855
|
-
}
|
|
29856
|
-
function isEmptyToolset(toolset) {
|
|
29857
|
-
return !toolset.agents.length && !toolset.skills.length && !toolset.mcp.length && !toolset.plugins.length;
|
|
29858
|
-
}
|
|
29859
|
-
function mcpPrefix(server2) {
|
|
29860
|
-
if (server2.scope !== "plugin") return `mcp__${toolName(server2.name)}`;
|
|
29861
|
-
const [, plugin = "", name = ""] = server2.name.split(":");
|
|
29862
|
-
return `mcp__plugin_${toolName(plugin)}_${toolName(name)}`;
|
|
29863
|
-
}
|
|
29864
|
-
function connectorsSeen() {
|
|
29865
|
-
const prefixes = /* @__PURE__ */ new Set();
|
|
29866
|
-
for (const summary of listTranscripts()) {
|
|
29867
|
-
for (const name of Object.keys(transcriptDetail(summary.id)?.totals.tools ?? {})) {
|
|
29868
|
-
const match = /^(mcp__claude_ai_[A-Za-z0-9_-]+?)__/.exec(name);
|
|
29869
|
-
if (match) prefixes.add(match[1]);
|
|
29870
|
-
}
|
|
29871
|
-
}
|
|
29872
|
-
return [...prefixes].sort();
|
|
29873
|
-
}
|
|
29874
|
-
function toolsetCatalog() {
|
|
29875
|
-
const inventory = readInventory();
|
|
29876
|
-
const items = [];
|
|
29877
|
-
for (const plugin of inventory.plugins.filter((item) => item.enabled)) {
|
|
29878
|
-
items.push({ kind: "plugins", id: plugin.id, label: plugin.id, description: `v${plugin.version}`, scope: plugin.scope === "user" ? "user" : "project", leavesContext: true });
|
|
29879
|
-
}
|
|
29880
|
-
for (const agent of inventory.agents) {
|
|
29881
|
-
items.push({ kind: "agents", id: agent.name, label: agent.name, description: agent.description, scope: agent.scope, plugin: agent.plugin, leavesContext: true });
|
|
29882
|
-
}
|
|
29883
|
-
for (const skill of inventory.skills) {
|
|
29884
|
-
items.push({ kind: "skills", id: skill.name, label: skill.name, description: skill.description, scope: skill.scope, plugin: skill.plugin, leavesContext: skill.scope !== "plugin" });
|
|
29885
|
-
}
|
|
29886
|
-
for (const server2 of inventory.mcpServers) {
|
|
29887
|
-
items.push({ kind: "mcp", id: mcpPrefix(server2), label: server2.name, description: server2.target, scope: server2.scope, plugin: server2.plugin, leavesContext: false });
|
|
29888
|
-
}
|
|
29889
|
-
for (const prefix of connectorsSeen()) {
|
|
29890
|
-
items.push({ kind: "mcp", id: prefix, label: prefix.replace(/^mcp__claude_ai_/, "claude.ai ").replace(/_/g, " "), description: "claude.ai connector", scope: "connector", leavesContext: false });
|
|
29891
|
-
}
|
|
29892
|
-
const seen = /* @__PURE__ */ new Set();
|
|
29893
|
-
return items.filter((item) => {
|
|
29894
|
-
const key = `${item.kind}:${item.id}`;
|
|
29895
|
-
if (seen.has(key)) return false;
|
|
29896
|
-
seen.add(key);
|
|
29897
|
-
return true;
|
|
29898
|
-
});
|
|
29899
|
-
}
|
|
29900
|
-
function toolsetSettings(toolset) {
|
|
29901
|
-
if (isEmptyToolset(toolset)) return {};
|
|
29902
|
-
const pluginSkills = new Set(readInventory().skills.filter((skill) => skill.scope === "plugin").map((skill) => skill.name));
|
|
29903
|
-
const deny = [
|
|
29904
|
-
...toolset.agents.map((name) => `Agent(${name})`),
|
|
29905
|
-
...toolset.skills.filter((name) => pluginSkills.has(name)).map((name) => `Skill(${name})`),
|
|
29906
|
-
...toolset.mcp
|
|
29907
|
-
];
|
|
29908
|
-
const overrides = Object.fromEntries(toolset.skills.filter((name) => !pluginSkills.has(name)).map((name) => [name, "off"]));
|
|
29909
|
-
const settings = {};
|
|
29910
|
-
if (deny.length) settings.permissions = { deny };
|
|
29911
|
-
if (Object.keys(overrides).length) settings.skillOverrides = overrides;
|
|
29912
|
-
if (toolset.plugins.length) settings.enabledPlugins = Object.fromEntries(toolset.plugins.map((id) => [id, false]));
|
|
29913
|
-
return settings;
|
|
29914
|
-
}
|
|
29915
|
-
function listPresets() {
|
|
29916
|
-
return db.prepare("SELECT * FROM presets ORDER BY name COLLATE NOCASE").all().map(toPreset);
|
|
29917
|
-
}
|
|
29918
|
-
function getPreset(id) {
|
|
29919
|
-
const row = db.prepare("SELECT * FROM presets WHERE id = ?").get(id);
|
|
29920
|
-
return row ? toPreset(row) : null;
|
|
29921
|
-
}
|
|
29922
|
-
function defaultToolset() {
|
|
29923
|
-
const row = db.prepare("SELECT * FROM presets WHERE is_default = 1 LIMIT 1").get();
|
|
29924
|
-
return row ? toPreset(row).toolset : EMPTY_TOOLSET;
|
|
29925
|
-
}
|
|
29926
|
-
function savePreset(input) {
|
|
29927
|
-
const name = String(input.name ?? "").trim();
|
|
29928
|
-
if (!name) throw new Error("A preset needs a name.");
|
|
29929
|
-
const id = input.id ?? nanoid(10);
|
|
29930
|
-
db.transaction(() => {
|
|
29931
|
-
if (input.isDefault) db.prepare("UPDATE presets SET is_default = 0").run();
|
|
29932
|
-
db.prepare(
|
|
29933
|
-
`INSERT INTO presets (id, name, toolset, is_default, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
29934
|
-
ON CONFLICT(id) DO UPDATE SET name = excluded.name, toolset = excluded.toolset, is_default = excluded.is_default, updated_at = excluded.updated_at`
|
|
29935
|
-
).run(id, name, JSON.stringify(normaliseToolset(input.toolset)), input.isDefault ? 1 : 0, now(), now());
|
|
29936
|
-
})();
|
|
29937
|
-
broadcast("jobs", {});
|
|
29938
|
-
return getPreset(id);
|
|
29939
|
-
}
|
|
29940
|
-
function setDefaultItem(input) {
|
|
29941
|
-
const kind = String(input.kind ?? "");
|
|
29942
|
-
const id = String(input.id ?? "").trim();
|
|
29943
|
-
if (!(kind in EMPTY_TOOLSET) || !id) throw new Error("Send { kind, id, enabled }.");
|
|
29944
|
-
const row = db.prepare("SELECT * FROM presets WHERE is_default = 1 LIMIT 1").get();
|
|
29945
|
-
const preset = row ? toPreset(row) : { id: void 0, name: "Default", toolset: EMPTY_TOOLSET };
|
|
29946
|
-
const off = preset.toolset[kind].filter((item) => item !== id);
|
|
29947
|
-
if (input.enabled !== true) off.push(id);
|
|
29948
|
-
return savePreset({ id: preset.id, name: preset.name, toolset: { ...preset.toolset, [kind]: off }, isDefault: true });
|
|
29949
|
-
}
|
|
29950
|
-
function deletePreset(id) {
|
|
29951
|
-
db.prepare("DELETE FROM presets WHERE id = ?").run(id);
|
|
29952
|
-
db.prepare("UPDATE jobs SET preset_id = NULL WHERE preset_id = ?").run(id);
|
|
29953
|
-
broadcast("jobs", {});
|
|
29954
|
-
}
|
|
29955
|
-
var EMPTY_TOOLSET, toolName, toPreset;
|
|
29956
|
-
var init_toolset = __esm({
|
|
29957
|
-
"server/src/toolset.ts"() {
|
|
29958
|
-
"use strict";
|
|
29959
|
-
init_nanoid();
|
|
29960
|
-
init_db();
|
|
29961
|
-
init_events();
|
|
29962
|
-
init_inventory();
|
|
29963
|
-
init_transcripts();
|
|
29964
|
-
EMPTY_TOOLSET = { agents: [], skills: [], mcp: [], plugins: [] };
|
|
29965
|
-
toolName = (value) => value.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
29966
|
-
toPreset = (row) => ({ id: row.id, name: row.name, toolset: normaliseToolset(parseJson(row.toolset, {})), isDefault: Boolean(row.is_default) });
|
|
29967
|
-
}
|
|
29968
|
-
});
|
|
29969
|
-
|
|
29970
|
-
// server/src/channels/markdown.ts
|
|
29971
|
-
function pieces(markdown) {
|
|
29972
|
-
const out = [];
|
|
29973
|
-
let prose = [];
|
|
29974
|
-
let code = null;
|
|
29975
|
-
for (const line of markdown.split("\n")) {
|
|
29976
|
-
if (code) {
|
|
29977
|
-
if (/^\s*```/.test(line)) {
|
|
29978
|
-
out.push({ code: true, text: code.join("\n") });
|
|
29979
|
-
code = null;
|
|
29980
|
-
} else code.push(line);
|
|
29981
|
-
} else if (/^\s*```[^`]*$/.test(line)) {
|
|
29982
|
-
if (prose.length) out.push({ code: false, text: prose.join("\n") });
|
|
29983
|
-
prose = [];
|
|
29984
|
-
code = [];
|
|
29985
|
-
} else prose.push(line);
|
|
29986
|
-
}
|
|
29987
|
-
if (code) out.push({ code: true, text: code.join("\n") });
|
|
29988
|
-
if (prose.length) out.push({ code: false, text: prose.join("\n") });
|
|
29989
|
-
return out;
|
|
29990
|
-
}
|
|
29991
|
-
function holdLinks(text, render2, rewrite) {
|
|
29992
|
-
const links = [];
|
|
29993
|
-
const held = text.replace(/\[([^\]\n]+)\]\((https?:\/\/[^)\s]+)\)/g, (_whole, label, url) => `${links.push(render2(label, url)) - 1}`);
|
|
29994
|
-
return rewrite(held).replace(/\u0002(\d+)\u0002/g, (_whole, index) => links[Number(index)]);
|
|
29995
|
-
}
|
|
29996
|
-
function lines(text, bold) {
|
|
29997
|
-
return text.split("\n").map((line) => {
|
|
29998
|
-
if (/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/.test(line)) return null;
|
|
29999
|
-
if (/^\s*\|.*\|\s*$/.test(line))
|
|
30000
|
-
return line.trim().replace(/^\||\|$/g, "").split("|").map((cell) => cell.trim()).filter(Boolean).join(" \xB7 ");
|
|
30001
|
-
if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) return "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
|
|
30002
|
-
const heading = /^\s*#{1,6}\s+(.*)$/.exec(line);
|
|
30003
|
-
if (heading) return bold(heading[1].replace(/\*\*/g, ""));
|
|
30004
|
-
const bullet = /^(\s*)[-*+]\s+(.*)$/.exec(line);
|
|
30005
|
-
if (bullet) return `${bullet[1].length >= 2 ? " \u25E6 " : "\u2022 "}${bullet[2]}`;
|
|
30006
|
-
return line.replace(/^\s*>\s?/, "");
|
|
30007
|
-
}).filter((line) => line !== null).join("\n").replace(/\n{3,}/g, "\n\n");
|
|
30008
|
-
}
|
|
30009
|
-
function inline(text, codeOf, rest) {
|
|
30010
|
-
const codes = [];
|
|
30011
|
-
const held = text.replace(/`{1,3}([^`\n]+)`{1,3}/g, (_whole, code) => `\0${codes.push(code) - 1}\0`);
|
|
30012
|
-
return rest(held).replace(/\u0000(\d+)\u0000/g, (_whole, index) => codeOf(codes[Number(index)]));
|
|
30013
|
-
}
|
|
30014
|
-
function toTelegramHtml(markdown, { inQuote = false } = {}) {
|
|
30015
|
-
return pieces(markdown).map((piece) => {
|
|
30016
|
-
if (piece.code) return inQuote ? `<code>${escapeHtml(piece.text)}</code>` : `<pre>${escapeHtml(piece.text)}</pre>`;
|
|
30017
|
-
return inline(
|
|
30018
|
-
lines(piece.text, (heading) => `**${heading}**`),
|
|
30019
|
-
(code) => `<code>${escapeHtml(code)}</code>`,
|
|
30020
|
-
(text) => holdLinks(
|
|
30021
|
-
escapeHtml(text),
|
|
30022
|
-
(label, url) => `<a href="${url.replace(/"/g, """)}">${label}</a>`,
|
|
30023
|
-
(prose) => prose.replace(/\*\*([^*\n]+)\*\*/g, "<b>$1</b>").replace(/(^|[\s(])__([^_\n]+)__(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1<b>$2</b>").replace(/~~([^~\n]+)~~/g, "<s>$1</s>").replace(/(^|[\s(])\*([^*\s][^*\n]*?)\*(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1<i>$2</i>").replace(/(^|[\s(])_([^_\s][^_\n]*?)_(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1<i>$2</i>")
|
|
30024
|
-
)
|
|
30025
|
-
);
|
|
30026
|
-
}).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
30027
|
-
}
|
|
30028
|
-
function toSlackMrkdwn(markdown) {
|
|
30029
|
-
return pieces(markdown).map((piece) => {
|
|
30030
|
-
if (piece.code) return `\`\`\`
|
|
30031
|
-
${escapeHtml(piece.text)}
|
|
30032
|
-
\`\`\``;
|
|
30033
|
-
return inline(
|
|
30034
|
-
lines(piece.text, (heading) => `**${heading}**`),
|
|
30035
|
-
// Escaped like the rest: "<https://x>" or "<!here>" in code would otherwise become a link or a mention.
|
|
30036
|
-
(code) => `\`${escapeHtml(code)}\``,
|
|
30037
|
-
(text) => holdLinks(
|
|
30038
|
-
escapeHtml(text),
|
|
30039
|
-
(label, url) => `<${url}|${label}>`,
|
|
30040
|
-
(prose) => prose.replace(/(^|[\s(])\*([^*\s][^*\n]*?)\*(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1$2").replace(/\*\*([^*\n]+)\*\*/g, "*$1*").replace(/(^|[\s(])__([^_\n]+)__(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1*$2*").replace(/~~([^~\n]+)~~/g, "~$1~").replace(/\u0001/g, "_")
|
|
30041
|
-
)
|
|
30042
|
-
);
|
|
30043
|
-
}).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
30044
|
-
}
|
|
30045
|
-
function gistOf(markdown, skip = "") {
|
|
30046
|
-
const prose = pieces(markdown).filter((piece) => !piece.code).map((piece) => piece.text).join("\n");
|
|
30047
|
-
const strip = (text) => text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^\s*#+\s+/, "").replace(/`+|\*\*|~~/g, "").replace(/(^|[\s(])(__|_|\*)([^_*\n]+?)\2(?=[\s),:;!?]|\.(?!\w)|$)/g, "$1$3").replace(/\s+/g, " ").trim();
|
|
30048
|
-
const short2 = (text, max) => text.length > max ? `${text.slice(0, max - 1).trimEnd()}\u2026` : text;
|
|
30049
|
-
const items = prose.split("\n").map((line) => /^\s{0,1}(?:[-*+]|\d+[.)])\s+(.*)$/.exec(line)?.[1]).filter((item) => Boolean(item)).map(strip).filter(Boolean);
|
|
30050
|
-
if (items.length >= 2) {
|
|
30051
|
-
const shown = items.slice(0, 4).map((item) => `\u2022 ${short2(item, 110)}`);
|
|
30052
|
-
return items.length > 4 ? [...shown, `\u2026and ${items.length - 4} more`] : shown;
|
|
30053
|
-
}
|
|
30054
|
-
const opening = strip(
|
|
30055
|
-
prose.split(/\n\s*\n/).map((part) => part.trim()).find((part) => part && !/^\s*(#|\|)/.test(part)) ?? ""
|
|
30056
|
-
);
|
|
30057
|
-
const sentences = opening.split(/(?<=[.?!])\s+/).slice(0, 2).join(" ");
|
|
30058
|
-
const gist = short2(sentences, 260);
|
|
30059
|
-
return gist && strip(skip) !== gist ? [gist] : [];
|
|
30060
|
-
}
|
|
30061
|
-
var escapeHtml;
|
|
30062
|
-
var init_markdown = __esm({
|
|
30063
|
-
"server/src/channels/markdown.ts"() {
|
|
30064
|
-
"use strict";
|
|
30065
|
-
escapeHtml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
30066
|
-
}
|
|
30067
|
-
});
|
|
30068
|
-
|
|
30069
|
-
// server/src/presentation.ts
|
|
30070
|
-
function mainQuestion(message) {
|
|
30071
|
-
const paragraphs = plain(message).split(/\n\s*\n/).map((part) => part.trim()).filter(Boolean);
|
|
30072
|
-
const sentencesOf = (part) => part.split(/(?<=[.?!])\s+|\n+/).map((sentence) => sentence.trim()).filter(Boolean);
|
|
30073
|
-
const last = paragraphs[paragraphs.length - 1] ?? "";
|
|
30074
|
-
const sentences = sentencesOf(last);
|
|
30075
|
-
const first = sentences.findIndex((sentence) => sentence.endsWith("?"));
|
|
30076
|
-
if (first !== -1) {
|
|
30077
|
-
const run4 = sentences.slice(first).join(" ");
|
|
30078
|
-
const lastQuestion = [...sentences].reverse().find((sentence) => sentence.endsWith("?"));
|
|
30079
|
-
return { headline: oneLine(run4.length <= 160 ? run4 : lastQuestion), isQuestion: true };
|
|
30080
|
-
}
|
|
30081
|
-
const anyQuestion = [...paragraphs].reverse().flatMap((part) => sentencesOf(part).filter((sentence) => sentence.endsWith("?")).reverse())[0];
|
|
30082
|
-
if (anyQuestion) return { headline: oneLine(anyQuestion), isQuestion: true };
|
|
30083
|
-
return { headline: oneLine(last || "Claude finished its turn."), isQuestion: false };
|
|
30084
|
-
}
|
|
30085
|
-
function target(toolName2, input) {
|
|
30086
|
-
if (!input) return "";
|
|
30087
|
-
const pick = (key) => typeof input[key] === "string" ? input[key] : "";
|
|
30088
|
-
const file = pick("file_path") || pick("notebook_path") || pick("path");
|
|
30089
|
-
if (file) return file.split(/[\\/]/).slice(-2).join("/");
|
|
30090
|
-
const command = pick("command");
|
|
30091
|
-
if (command) return command.split("\n")[0];
|
|
30092
|
-
return pick("url") || pick("pattern") || pick("query") || pick("description") || (toolName2.startsWith("mcp__") ? toolName2.split("__").pop() ?? "" : "");
|
|
30093
|
-
}
|
|
30094
|
-
function present(interaction, sessionTitle2) {
|
|
30095
|
-
const data = interaction.data ?? {};
|
|
30096
|
-
const answerable = interaction.status === "open";
|
|
30097
|
-
const base = { kind: interaction.kind, sessionTitle: sessionTitle2, answerable, quickReplies: [] };
|
|
30098
|
-
switch (interaction.kind) {
|
|
30099
|
-
case "permission": {
|
|
30100
|
-
const tool = String(data.toolName ?? "a tool");
|
|
30101
|
-
const what = target(tool, data.toolInput);
|
|
30102
|
-
const shortTool = tool.startsWith("mcp__") ? tool.replace(/^mcp__/, "").split("__")[0] : tool;
|
|
30103
|
-
return {
|
|
30104
|
-
...base,
|
|
30105
|
-
headline: oneLine(`${shortTool}${what ? ` \u2192 ${what}` : ""}`, 120),
|
|
30106
|
-
question: data.agent ? `The ${data.agent} agent asks to use ${tool}.` : `Claude asks to use ${tool}.`,
|
|
30107
|
-
choices: [
|
|
30108
|
-
{ ref: "o:0", label: "Allow", tone: "primary" },
|
|
30109
|
-
{ ref: "o:1", label: "Allow for this session" },
|
|
30110
|
-
{ ref: "o:2", label: "Deny", tone: "danger" }
|
|
30111
|
-
],
|
|
30112
|
-
freeText: "Deny with a reason for Claude...",
|
|
30113
|
-
details: { label: "Full request", text: interaction.body, format: "code" }
|
|
30114
|
-
};
|
|
30115
|
-
}
|
|
30116
|
-
case "question": {
|
|
30117
|
-
const questions = (Array.isArray(data.questions) ? data.questions : []).map((item) => ({
|
|
30118
|
-
header: item?.header ? String(item.header) : void 0,
|
|
30119
|
-
question: String(item?.question ?? ""),
|
|
30120
|
-
options: (Array.isArray(item?.options) ? item.options : []).map((option) => ({
|
|
30121
|
-
label: String(option?.label ?? option),
|
|
30122
|
-
description: option?.description ? String(option.description) : void 0
|
|
30123
|
-
})),
|
|
30124
|
-
multiSelect: Boolean(item?.multiSelect)
|
|
30125
|
-
}));
|
|
30126
|
-
const first = questions[0];
|
|
30127
|
-
const single = questions.length === 1;
|
|
30128
|
-
return {
|
|
30129
|
-
...base,
|
|
30130
|
-
headline: oneLine(first?.question || interaction.body || interaction.title),
|
|
30131
|
-
question: first?.header,
|
|
30132
|
-
questions,
|
|
30133
|
-
choices: single ? first.options.map((option, index) => ({ ref: `o:${index}`, label: option.label, description: option.description, tone: index === 0 ? "primary" : void 0 })) : [],
|
|
30134
|
-
freeText: "Or answer in your own words..."
|
|
30135
|
-
};
|
|
30136
|
-
}
|
|
30137
|
-
case "turn": {
|
|
30138
|
-
const { headline, isQuestion } = mainQuestion(interaction.body);
|
|
30139
|
-
const replies = isQuestion ? ["Yes", "No", "Continue"] : ["Continue"];
|
|
30140
|
-
return {
|
|
30141
|
-
...base,
|
|
30142
|
-
headline,
|
|
30143
|
-
choices: [],
|
|
30144
|
-
quickReplies: replies.map((label, index) => ({ ref: `q:${index}`, label })),
|
|
30145
|
-
freeText: "Reply to the session...",
|
|
30146
|
-
details: interaction.body && plain(interaction.body) !== headline ? { label: "Full message", text: interaction.body, format: "markdown" } : void 0,
|
|
30147
|
-
gist: interaction.body ? gistOf(interaction.body, headline) : void 0
|
|
30148
|
-
};
|
|
30149
|
-
}
|
|
30150
|
-
case "mcp":
|
|
30151
|
-
return {
|
|
30152
|
-
...base,
|
|
30153
|
-
headline: oneLine(`Needs the MCP server '${data.name ?? ""}'`),
|
|
30154
|
-
question: data.reason ? oneLine(String(data.reason), 240) : void 0,
|
|
30155
|
-
choices: [
|
|
30156
|
-
{ ref: "o:0", label: "Install & resume", tone: "primary" },
|
|
30157
|
-
{ ref: "o:1", label: "Skip" }
|
|
30158
|
-
],
|
|
30159
|
-
freeText: "Or tell Claude what to do instead...",
|
|
30160
|
-
details: data.command ? { label: "Install command", text: String(data.command), format: "code" } : void 0
|
|
30161
|
-
};
|
|
30162
|
-
case "done": {
|
|
30163
|
-
const deliverables = Object.entries(data.deliverables ?? {}).map(([key, value]) => `- **${key}**: ${typeof value === "string" ? value : JSON.stringify(value)}`).join("\n");
|
|
30164
|
-
return {
|
|
30165
|
-
...base,
|
|
30166
|
-
answerable: false,
|
|
30167
|
-
headline: oneLine(String(data.summary ?? interaction.body ?? "Task done."), 200),
|
|
30168
|
-
question: data.category ? `Category: ${data.category}` : void 0,
|
|
30169
|
-
choices: [],
|
|
30170
|
-
freeText: null,
|
|
30171
|
-
details: deliverables ? { label: "Deliverables", text: deliverables, format: "markdown" } : void 0
|
|
30172
|
-
};
|
|
30173
|
-
}
|
|
30174
|
-
}
|
|
30175
|
-
}
|
|
30176
|
-
function resolveChoice(interaction, ref) {
|
|
30177
|
-
const [type, raw] = ref.split(":");
|
|
30178
|
-
const index = Number(raw);
|
|
30179
|
-
if (!Number.isInteger(index)) return null;
|
|
30180
|
-
if (type === "q") return present(interaction, "").quickReplies[index]?.label ?? null;
|
|
30181
|
-
if (type !== "o") return null;
|
|
30182
|
-
switch (interaction.kind) {
|
|
30183
|
-
case "permission":
|
|
30184
|
-
return ["1", "2", "3"][index] ?? null;
|
|
30185
|
-
case "question":
|
|
30186
|
-
return String(index + 1);
|
|
30187
|
-
case "mcp":
|
|
30188
|
-
return ["install", "skip"][index] ?? null;
|
|
30189
|
-
default:
|
|
30190
|
-
return null;
|
|
30191
|
-
}
|
|
30192
|
-
}
|
|
30193
|
-
var oneLine, plain;
|
|
30194
|
-
var init_presentation = __esm({
|
|
30195
|
-
"server/src/presentation.ts"() {
|
|
30196
|
-
"use strict";
|
|
30197
|
-
init_markdown();
|
|
30198
|
-
oneLine = (text, max = 160) => {
|
|
30199
|
-
const flat2 = text.replace(/\s+/g, " ").trim();
|
|
30200
|
-
return flat2.length > max ? `${flat2.slice(0, max - 1).trimEnd()}...` : flat2;
|
|
30201
|
-
};
|
|
30202
|
-
plain = (text) => text.replace(/`{3}[\s\S]*?`{3}/g, " ").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^\s*(#+|>)\s?/gm, "").replace(/`+|\*\*|~~/g, "").replace(/(^|[\s(])(__|_|\*)([^_*\n]+?)\2(?=[\s),:;!?]|\.(?!\w)|$)/gm, "$1$3").trim();
|
|
30203
|
-
}
|
|
30204
|
-
});
|
|
30205
|
-
|
|
30206
|
-
// server/src/smallModel.ts
|
|
30207
|
-
import { spawn as spawn3 } from "node:child_process";
|
|
30208
|
-
import fs7 from "node:fs";
|
|
30209
|
-
import path8 from "node:path";
|
|
30210
|
-
function askSmallModel(prompt, timeoutMs = 45e3) {
|
|
30211
|
-
const claude2 = findClaudeCode();
|
|
30212
|
-
if (!claude2 || claudeStatus()?.problem) return Promise.resolve(null);
|
|
30213
|
-
return new Promise((resolve) => {
|
|
30214
|
-
const cwd = path8.join(hubHome, "global");
|
|
30215
|
-
fs7.mkdirSync(cwd, { recursive: true });
|
|
30216
|
-
const child = spawn3(claude2, ["-p", "--model", "haiku", "--tools", "", "--no-session-persistence", "--strict-mcp-config", "--output-format", "text"], {
|
|
30217
|
-
cwd,
|
|
30218
|
-
windowsHide: true,
|
|
30219
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
30220
|
-
});
|
|
30221
|
-
let out = "";
|
|
30222
|
-
const timer = setTimeout(() => child.kill(), timeoutMs);
|
|
30223
|
-
child.stdout.on("data", (chunk) => out += chunk);
|
|
30224
|
-
child.on("error", () => {
|
|
30225
|
-
clearTimeout(timer);
|
|
30226
|
-
resolve(null);
|
|
30227
|
-
});
|
|
30228
|
-
child.on("close", (code) => {
|
|
30229
|
-
clearTimeout(timer);
|
|
30230
|
-
resolve(code === 0 && out.trim() ? out : null);
|
|
30231
|
-
});
|
|
30232
|
-
child.stdin.end(prompt);
|
|
30233
|
-
});
|
|
30234
|
-
}
|
|
30235
|
-
function jsonIn(answer, open) {
|
|
30236
|
-
if (!answer) return null;
|
|
30237
|
-
const start = answer.indexOf(open);
|
|
30238
|
-
const end = answer.lastIndexOf(open === "[" ? "]" : "}");
|
|
30239
|
-
if (start === -1 || end <= start) return null;
|
|
30240
|
-
try {
|
|
30241
|
-
return JSON.parse(answer.slice(start, end + 1));
|
|
30242
|
-
} catch {
|
|
30243
|
-
return null;
|
|
30244
|
-
}
|
|
30245
|
-
}
|
|
30246
|
-
async function pickNumbers(prompt) {
|
|
30247
|
-
const picked = jsonIn(await askSmallModel(prompt), "[");
|
|
30248
|
-
return picked ? picked.map(Number).filter((number) => Number.isInteger(number) && number > 0) : null;
|
|
30249
|
-
}
|
|
30250
|
-
var init_smallModel = __esm({
|
|
30251
|
-
"server/src/smallModel.ts"() {
|
|
30252
|
-
"use strict";
|
|
30253
|
-
init_claudeCode();
|
|
30254
|
-
init_paths();
|
|
30255
|
-
}
|
|
30256
|
-
});
|
|
30257
|
-
|
|
30258
29914
|
// server/src/channels/commands.ts
|
|
30259
29915
|
var commands_exports = {};
|
|
30260
29916
|
__export(commands_exports, {
|
|
@@ -31276,6 +30932,113 @@ var init_telegram = __esm({
|
|
|
31276
30932
|
}
|
|
31277
30933
|
});
|
|
31278
30934
|
|
|
30935
|
+
// server/src/watchdog.ts
|
|
30936
|
+
var watchdog_exports = {};
|
|
30937
|
+
__export(watchdog_exports, {
|
|
30938
|
+
answerTrust: () => answerTrust,
|
|
30939
|
+
startWatchdog: () => startWatchdog
|
|
30940
|
+
});
|
|
30941
|
+
function screenTail(terminal) {
|
|
30942
|
+
let text = "";
|
|
30943
|
+
for (let index = terminal.backlog.length - 1; index >= 0 && text.length < SCREEN_TAIL * 2; index -= 1) text = terminal.backlog[index] + text;
|
|
30944
|
+
return text.slice(-SCREEN_TAIL * 2).replace(ANSI, "").slice(-SCREEN_TAIL);
|
|
30945
|
+
}
|
|
30946
|
+
async function answerTrust(terminal, trust) {
|
|
30947
|
+
if (!trust) return pressKeys(terminal, "\x1B");
|
|
30948
|
+
for (let step = 0; step < 4; step += 1) {
|
|
30949
|
+
const flat2 = squeeze(screenTail(terminal).slice(-1500));
|
|
30950
|
+
const cursor = flat2.lastIndexOf("\u276F");
|
|
30951
|
+
const after = cursor >= 0 ? flat2.slice(cursor + 1) : "";
|
|
30952
|
+
if (after.startsWith("yes")) return pressKeys(terminal, "\r");
|
|
30953
|
+
const yesAt = flat2.lastIndexOf("yes,itrust");
|
|
30954
|
+
await pressKeys(terminal, yesAt > cursor ? "\x1B[B" : "\x1B[A");
|
|
30955
|
+
await sleep2(300);
|
|
30956
|
+
}
|
|
30957
|
+
throw new Error("Could not find the trust option on the screen.");
|
|
30958
|
+
}
|
|
30959
|
+
function lastLines(screen) {
|
|
30960
|
+
const lines2 = screen.split(/\r?\n/).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
30961
|
+
return lines2.slice(-14).join("\n").slice(-1200);
|
|
30962
|
+
}
|
|
30963
|
+
function check2() {
|
|
30964
|
+
for (const terminal of listTerminals()) {
|
|
30965
|
+
if (terminal.finished || terminal.restarting) continue;
|
|
30966
|
+
const open = listInteractions({ sessionId: terminal.sessionId, status: "open" });
|
|
30967
|
+
const screen = screenTail(terminal);
|
|
30968
|
+
const trustOpen = open.find((item) => item.data?.trust);
|
|
30969
|
+
if (showsTrustDialog(screen)) {
|
|
30970
|
+
if (!trustOpen) {
|
|
30971
|
+
createInteraction({
|
|
30972
|
+
sessionId: terminal.sessionId,
|
|
30973
|
+
kind: "question",
|
|
30974
|
+
title: "Trust this folder?",
|
|
30975
|
+
body: "Claude Code asks whether to trust the files in this project's folder before it starts. Nothing runs until it is answered.",
|
|
30976
|
+
options: [
|
|
30977
|
+
{ key: "1", label: "Yes, trust this folder" },
|
|
30978
|
+
{ key: "2", label: "No, exit" }
|
|
30979
|
+
],
|
|
30980
|
+
data: { trust: true, questions: [{ question: "Trust this folder?", options: ["Yes, trust this folder", "No, exit"] }] }
|
|
30981
|
+
});
|
|
30982
|
+
}
|
|
30983
|
+
continue;
|
|
30984
|
+
}
|
|
30985
|
+
if (trustOpen) closeOpen(terminal.sessionId, "terminal", ["question"]);
|
|
30986
|
+
const stalled = open.find((item) => item.data?.stalled);
|
|
30987
|
+
if (stalled && terminal.lastOutputAt > Date.parse(stalled.createdAt) + 5e3) {
|
|
30988
|
+
closeOpen(terminal.sessionId, "terminal", ["turn"]);
|
|
30989
|
+
setSessionStatus(terminal.sessionId, "running");
|
|
30990
|
+
continue;
|
|
30991
|
+
}
|
|
30992
|
+
const quietFor = Date.now() - terminal.lastOutputAt;
|
|
30993
|
+
if (quietFor < STALL_MS || open.length || reported.get(terminal.id) === terminal.lastOutputAt) continue;
|
|
30994
|
+
if (!getSessionRow(terminal.sessionId)?.run_id) continue;
|
|
30995
|
+
reported.set(terminal.id, terminal.lastOutputAt);
|
|
30996
|
+
createInteraction({
|
|
30997
|
+
sessionId: terminal.sessionId,
|
|
30998
|
+
kind: "turn",
|
|
30999
|
+
title: "This run looks stuck",
|
|
31000
|
+
body: `Nothing has changed on its screen for ${Math.round(quietFor / 6e4)} minutes. It may be waiting on a dialog, or it stopped without reporting done. Reply to type into it, or stop it.
|
|
31001
|
+
|
|
31002
|
+
${lastLines(screen)}`,
|
|
31003
|
+
data: { stalled: true }
|
|
31004
|
+
});
|
|
31005
|
+
}
|
|
31006
|
+
for (const id of reported.keys()) if (!allTerminals().some((terminal) => terminal.id === id)) reported.delete(id);
|
|
31007
|
+
}
|
|
31008
|
+
function startWatchdog(intervalMs = 3e3) {
|
|
31009
|
+
const timer = setInterval(bindProject(() => {
|
|
31010
|
+
try {
|
|
31011
|
+
check2();
|
|
31012
|
+
} catch (error) {
|
|
31013
|
+
console.error("[watchdog]", error.message);
|
|
31014
|
+
}
|
|
31015
|
+
}), intervalMs);
|
|
31016
|
+
return () => clearInterval(timer);
|
|
31017
|
+
}
|
|
31018
|
+
var TRUST_MARKERS, STALL_MS, SCREEN_TAIL, ANSI, squeeze, showsTrustDialog, sleep2, reported;
|
|
31019
|
+
var init_watchdog = __esm({
|
|
31020
|
+
"server/src/watchdog.ts"() {
|
|
31021
|
+
"use strict";
|
|
31022
|
+
init_context();
|
|
31023
|
+
init_interactions();
|
|
31024
|
+
init_sessions();
|
|
31025
|
+
init_terminals();
|
|
31026
|
+
TRUST_MARKERS = ["doyoutrustthefilesinthisfolder", "isthisaprojectyoucreatedoroneyoutrust", "yes,itrustthisfolder"];
|
|
31027
|
+
STALL_MS = Number(process.env.CLAUDER_HUB_STALL_MS) || 8 * 6e4;
|
|
31028
|
+
SCREEN_TAIL = 8e3;
|
|
31029
|
+
ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[()][A-Za-z0-9]|\x1b[=>78]/g;
|
|
31030
|
+
squeeze = (text) => text.toLowerCase().replace(/\s+/g, "");
|
|
31031
|
+
showsTrustDialog = (screen) => {
|
|
31032
|
+
const flat2 = squeeze(screen.slice(-3e3));
|
|
31033
|
+
if (!TRUST_MARKERS.some((marker) => flat2.includes(marker))) return false;
|
|
31034
|
+
const end = Math.max(flat2.lastIndexOf("entertoconfirm"), flat2.lastIndexOf("itrustthisfolder"), flat2.lastIndexOf("no,exit"));
|
|
31035
|
+
return end >= 0 && flat2.length - end < 120;
|
|
31036
|
+
};
|
|
31037
|
+
sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
31038
|
+
reported = /* @__PURE__ */ new Map();
|
|
31039
|
+
}
|
|
31040
|
+
});
|
|
31041
|
+
|
|
31279
31042
|
// server/src/mcpSuggestions.ts
|
|
31280
31043
|
var mcpSuggestions_exports = {};
|
|
31281
31044
|
__export(mcpSuggestions_exports, {
|
|
@@ -31430,6 +31193,7 @@ __export(interactions_exports, {
|
|
|
31430
31193
|
chooseInteraction: () => chooseInteraction,
|
|
31431
31194
|
closeOpen: () => closeOpen,
|
|
31432
31195
|
createInteraction: () => createInteraction,
|
|
31196
|
+
dismissInteraction: () => dismissInteraction,
|
|
31433
31197
|
getInteraction: () => getInteraction,
|
|
31434
31198
|
listInteractions: () => listInteractions,
|
|
31435
31199
|
routeReply: () => routeReply,
|
|
@@ -31498,8 +31262,22 @@ function closeOpen(sessionId, by, kinds) {
|
|
|
31498
31262
|
}
|
|
31499
31263
|
broadcast("interactions", { sessionId });
|
|
31500
31264
|
}
|
|
31265
|
+
function dismissInteraction(id) {
|
|
31266
|
+
const interaction = getInteraction(id);
|
|
31267
|
+
if (!interaction) throw new AnswerError("No such interaction.");
|
|
31268
|
+
if (interaction.status !== "open") return interaction;
|
|
31269
|
+
db.prepare("UPDATE interactions SET status = 'closed', answered_by = 'dismissed', updated_at = ? WHERE id = ?").run(now(), id);
|
|
31270
|
+
void acknowledgeOnChannels(interaction, "Dismissed on the dashboard.");
|
|
31271
|
+
broadcast("interactions", { sessionId: interaction.sessionId });
|
|
31272
|
+
return getInteraction(id);
|
|
31273
|
+
}
|
|
31501
31274
|
async function answerQuestion(interaction, answer) {
|
|
31502
31275
|
const terminal = terminalForSession(interaction.sessionId);
|
|
31276
|
+
if (interaction.data.trust) {
|
|
31277
|
+
const { answerTrust: answerTrust2 } = await Promise.resolve().then(() => (init_watchdog(), watchdog_exports));
|
|
31278
|
+
await answerTrust2(terminal, answer.trim() !== "2");
|
|
31279
|
+
return;
|
|
31280
|
+
}
|
|
31503
31281
|
const questions = interaction.data.questions ?? [];
|
|
31504
31282
|
const split = answer.includes("\n") ? answer.split("\n") : answer.split(/[;,]/);
|
|
31505
31283
|
const parts = questions.length > 1 ? split.map((part) => part.trim()) : [answer.trim()];
|
|
@@ -31671,8 +31449,8 @@ __export(projects_exports, {
|
|
|
31671
31449
|
touchProject: () => touchProject
|
|
31672
31450
|
});
|
|
31673
31451
|
import Database2 from "better-sqlite3";
|
|
31674
|
-
import
|
|
31675
|
-
import
|
|
31452
|
+
import fs7 from "node:fs";
|
|
31453
|
+
import path8 from "node:path";
|
|
31676
31454
|
function listProjects() {
|
|
31677
31455
|
return globalDb.prepare("SELECT * FROM projects ORDER BY opened_at DESC").all().map(toInfo);
|
|
31678
31456
|
}
|
|
@@ -31689,19 +31467,19 @@ function globalProject() {
|
|
|
31689
31467
|
}
|
|
31690
31468
|
function bootGlobal() {
|
|
31691
31469
|
if (global2) return global2.project;
|
|
31692
|
-
const root2 =
|
|
31693
|
-
|
|
31470
|
+
const root2 = path8.join(hubHome, GLOBAL_ID);
|
|
31471
|
+
fs7.mkdirSync(root2, { recursive: true });
|
|
31694
31472
|
const project = {
|
|
31695
31473
|
id: GLOBAL_ID,
|
|
31696
31474
|
name: "Global",
|
|
31697
31475
|
root: root2,
|
|
31698
|
-
transcriptsDir:
|
|
31476
|
+
transcriptsDir: path8.join(claudeHome, "projects", projectSlug(root2)),
|
|
31699
31477
|
dataDir: root2,
|
|
31700
|
-
db: openProjectDb(
|
|
31478
|
+
db: openProjectDb(path8.join(root2, "hub.db"))
|
|
31701
31479
|
};
|
|
31702
31480
|
const stops = withProject(project, () => {
|
|
31703
31481
|
recoverRuns();
|
|
31704
|
-
const list = [startTranscriptWatcher(), startInteractionSweeper(), stopChannels];
|
|
31482
|
+
const list = [startTranscriptWatcher(), startInteractionSweeper(), startWatchdog(), stopChannels];
|
|
31705
31483
|
startChannels({ device: false });
|
|
31706
31484
|
return list;
|
|
31707
31485
|
});
|
|
@@ -31714,7 +31492,7 @@ function freeId(name, root2) {
|
|
|
31714
31492
|
const taken = new Set(globalDb.prepare("SELECT id FROM projects").all().map((row) => row.id));
|
|
31715
31493
|
const free = (id) => {
|
|
31716
31494
|
if (taken.has(id) || RESERVED.has(id)) return false;
|
|
31717
|
-
if (!
|
|
31495
|
+
if (!fs7.existsSync(path8.join(projectsDir, id))) return true;
|
|
31718
31496
|
const owner = ownerOf(id);
|
|
31719
31497
|
return owner !== null && samePath(owner, root2);
|
|
31720
31498
|
};
|
|
@@ -31722,8 +31500,8 @@ function freeId(name, root2) {
|
|
|
31722
31500
|
for (let n2 = 2; ; n2++) if (free(`${base}-${n2}`)) return `${base}-${n2}`;
|
|
31723
31501
|
}
|
|
31724
31502
|
function importOldDatabase(root2, file) {
|
|
31725
|
-
const old =
|
|
31726
|
-
if (!
|
|
31503
|
+
const old = path8.join(root2, ".clauder-hub", "data", "hub.db");
|
|
31504
|
+
if (!fs7.existsSync(old) || fs7.existsSync(file)) return;
|
|
31727
31505
|
const source = new Database2(old, { readonly: true, fileMustExist: true });
|
|
31728
31506
|
try {
|
|
31729
31507
|
source.prepare("VACUUM INTO ?").run(file);
|
|
@@ -31732,7 +31510,7 @@ function importOldDatabase(root2, file) {
|
|
|
31732
31510
|
for (const row of overrides) insert.run(row.key, row.text, row.updated_at);
|
|
31733
31511
|
console.log(` imported : ${old}`);
|
|
31734
31512
|
} catch (error) {
|
|
31735
|
-
|
|
31513
|
+
fs7.rmSync(file, { force: true });
|
|
31736
31514
|
console.error(`[projects] could not import ${old}:`, error.message);
|
|
31737
31515
|
} finally {
|
|
31738
31516
|
source.close();
|
|
@@ -31741,16 +31519,16 @@ function importOldDatabase(root2, file) {
|
|
|
31741
31519
|
function boot(row) {
|
|
31742
31520
|
const running = live.get(row.id);
|
|
31743
31521
|
if (running) return running.project;
|
|
31744
|
-
const dataDir =
|
|
31745
|
-
|
|
31746
|
-
if (ownerOf(row.id) === null)
|
|
31747
|
-
const file =
|
|
31522
|
+
const dataDir = path8.join(projectsDir, row.id);
|
|
31523
|
+
fs7.mkdirSync(dataDir, { recursive: true });
|
|
31524
|
+
if (ownerOf(row.id) === null) fs7.writeFileSync(path8.join(dataDir, OWNER_FILE), row.root, "utf8");
|
|
31525
|
+
const file = path8.join(dataDir, "hub.db");
|
|
31748
31526
|
importOldDatabase(row.root, file);
|
|
31749
31527
|
const project = {
|
|
31750
31528
|
id: row.id,
|
|
31751
31529
|
name: row.name,
|
|
31752
31530
|
root: row.root,
|
|
31753
|
-
transcriptsDir:
|
|
31531
|
+
transcriptsDir: path8.join(claudeHome, "projects", projectSlug(row.root)),
|
|
31754
31532
|
dataDir,
|
|
31755
31533
|
db: openProjectDb(file)
|
|
31756
31534
|
};
|
|
@@ -31759,7 +31537,7 @@ function boot(row) {
|
|
|
31759
31537
|
withProject(project, () => {
|
|
31760
31538
|
const stranded = recoverRuns();
|
|
31761
31539
|
if (stranded) console.log(` note : ${row.name}: ${stranded} queued run(s) were running when the hub last stopped`);
|
|
31762
|
-
stops.push(startTranscriptWatcher(), startInteractionSweeper());
|
|
31540
|
+
stops.push(startTranscriptWatcher(), startInteractionSweeper(), startWatchdog());
|
|
31763
31541
|
stops.push(stopChannels);
|
|
31764
31542
|
startChannels();
|
|
31765
31543
|
stops.push(stopScheduler);
|
|
@@ -31778,7 +31556,7 @@ function boot(row) {
|
|
|
31778
31556
|
}
|
|
31779
31557
|
function bootProjects() {
|
|
31780
31558
|
for (const row of globalDb.prepare("SELECT * FROM projects").all()) {
|
|
31781
|
-
if (!
|
|
31559
|
+
if (!fs7.existsSync(row.root)) continue;
|
|
31782
31560
|
try {
|
|
31783
31561
|
boot(row);
|
|
31784
31562
|
} catch (error) {
|
|
@@ -31789,13 +31567,13 @@ function bootProjects() {
|
|
|
31789
31567
|
}
|
|
31790
31568
|
function addProject(input) {
|
|
31791
31569
|
if (typeof input !== "string" || !input.trim()) throw new Error("Send the folder's full path.");
|
|
31792
|
-
const typed =
|
|
31793
|
-
if (!
|
|
31794
|
-
const root2 =
|
|
31570
|
+
const typed = path8.resolve(input.trim());
|
|
31571
|
+
if (!fs7.existsSync(typed) || !fs7.statSync(typed).isDirectory()) throw new Error(`${typed} is not a folder.`);
|
|
31572
|
+
const root2 = fs7.realpathSync.native(typed);
|
|
31795
31573
|
const rows = globalDb.prepare("SELECT * FROM projects").all();
|
|
31796
31574
|
let row = rows.find((item) => samePath(item.root, root2));
|
|
31797
31575
|
if (!row) {
|
|
31798
|
-
const name =
|
|
31576
|
+
const name = path8.basename(root2) || root2;
|
|
31799
31577
|
row = { id: freeId(name, root2), root: root2, name, added_at: now(), opened_at: now() };
|
|
31800
31578
|
boot(row);
|
|
31801
31579
|
globalDb.prepare("INSERT INTO projects (id, root, name, added_at, opened_at) VALUES (?, ?, ?, ?, ?)").run(row.id, row.root, row.name, row.added_at, row.opened_at);
|
|
@@ -31840,18 +31618,19 @@ var init_projects = __esm({
|
|
|
31840
31618
|
init_db();
|
|
31841
31619
|
init_events();
|
|
31842
31620
|
init_interactions();
|
|
31621
|
+
init_watchdog();
|
|
31843
31622
|
init_jobs();
|
|
31844
31623
|
init_paths();
|
|
31845
31624
|
init_terminals();
|
|
31846
31625
|
init_transcripts();
|
|
31847
31626
|
live = /* @__PURE__ */ new Map();
|
|
31848
|
-
toInfo = (row) => ({ id: row.id, name: row.name, root: row.root, addedAt: row.added_at, openedAt: row.opened_at, exists:
|
|
31627
|
+
toInfo = (row) => ({ id: row.id, name: row.name, root: row.root, addedAt: row.added_at, openedAt: row.opened_at, exists: fs7.existsSync(row.root) });
|
|
31849
31628
|
global2 = null;
|
|
31850
31629
|
samePath = (a, b2) => process.platform === "linux" ? a === b2 : a.toLowerCase() === b2.toLowerCase();
|
|
31851
31630
|
OWNER_FILE = "root.txt";
|
|
31852
31631
|
ownerOf = (id) => {
|
|
31853
31632
|
try {
|
|
31854
|
-
return
|
|
31633
|
+
return fs7.readFileSync(path8.join(projectsDir, id, OWNER_FILE), "utf8").trim();
|
|
31855
31634
|
} catch {
|
|
31856
31635
|
return null;
|
|
31857
31636
|
}
|
|
@@ -31865,29 +31644,29 @@ var folderFinder_exports = {};
|
|
|
31865
31644
|
__export(folderFinder_exports, {
|
|
31866
31645
|
findFolders: () => findFolders
|
|
31867
31646
|
});
|
|
31868
|
-
import
|
|
31869
|
-
import
|
|
31870
|
-
import
|
|
31647
|
+
import fs8 from "node:fs";
|
|
31648
|
+
import os2 from "node:os";
|
|
31649
|
+
import path9 from "node:path";
|
|
31871
31650
|
function isProject(dir, entries) {
|
|
31872
31651
|
return entries.some((entry) => MARKERS.includes(entry.name) || entry.isFile() && PROJECT_FILE.test(entry.name));
|
|
31873
31652
|
}
|
|
31874
31653
|
function roots() {
|
|
31875
|
-
const home =
|
|
31654
|
+
const home = os2.homedir();
|
|
31876
31655
|
const found = /* @__PURE__ */ new Set();
|
|
31877
31656
|
for (const project of allContexts()) {
|
|
31878
31657
|
if (isGlobal(project)) continue;
|
|
31879
|
-
found.add(
|
|
31880
|
-
found.add(
|
|
31658
|
+
found.add(path9.dirname(project.root));
|
|
31659
|
+
found.add(path9.dirname(path9.dirname(project.root)));
|
|
31881
31660
|
}
|
|
31882
|
-
for (const dir of CODE_DIRS) found.add(
|
|
31661
|
+
for (const dir of CODE_DIRS) found.add(path9.join(home, dir));
|
|
31883
31662
|
if (process.platform === "win32") {
|
|
31884
31663
|
for (const letter of "CDEFG") {
|
|
31885
31664
|
const drive = `${letter}:\\`;
|
|
31886
|
-
if (!
|
|
31887
|
-
for (const dir of CODE_DIRS) found.add(
|
|
31665
|
+
if (!fs8.existsSync(drive)) continue;
|
|
31666
|
+
for (const dir of CODE_DIRS) found.add(path9.join(drive, dir));
|
|
31888
31667
|
}
|
|
31889
31668
|
}
|
|
31890
|
-
const real = [...found].filter((dir) => dir !==
|
|
31669
|
+
const real = [...found].filter((dir) => dir !== path9.parse(dir).root && fs8.existsSync(dir)).map((dir) => fs8.realpathSync.native(dir));
|
|
31891
31670
|
return [...new Set(real)];
|
|
31892
31671
|
}
|
|
31893
31672
|
function scan() {
|
|
@@ -31899,22 +31678,22 @@ function scan() {
|
|
|
31899
31678
|
visited += 1;
|
|
31900
31679
|
let entries;
|
|
31901
31680
|
try {
|
|
31902
|
-
entries =
|
|
31681
|
+
entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
31903
31682
|
} catch {
|
|
31904
31683
|
return;
|
|
31905
31684
|
}
|
|
31906
31685
|
if (depth > 0 && isProject(dir, entries)) {
|
|
31907
31686
|
let mtimeMs = 0;
|
|
31908
31687
|
try {
|
|
31909
|
-
mtimeMs =
|
|
31688
|
+
mtimeMs = fs8.statSync(dir).mtimeMs;
|
|
31910
31689
|
} catch {
|
|
31911
31690
|
}
|
|
31912
|
-
folders.set(dir.toLowerCase(), { path: dir, name:
|
|
31691
|
+
folders.set(dir.toLowerCase(), { path: dir, name: path9.basename(dir), mtimeMs });
|
|
31913
31692
|
return;
|
|
31914
31693
|
}
|
|
31915
31694
|
for (const entry of entries) {
|
|
31916
31695
|
if (!entry.isDirectory() || entry.name.startsWith(".") || SKIP.has(entry.name.toLowerCase())) continue;
|
|
31917
|
-
walk(
|
|
31696
|
+
walk(path9.join(dir, entry.name), depth + 1);
|
|
31918
31697
|
}
|
|
31919
31698
|
};
|
|
31920
31699
|
for (const root2 of roots()) walk(root2, 0);
|
|
@@ -31964,7 +31743,7 @@ var init_folderFinder = __esm({
|
|
|
31964
31743
|
MARKERS = [".git", "package.json", "pyproject.toml", "requirements.txt", "go.mod", "Cargo.toml", "pom.xml", "build.gradle", "composer.json", "Gemfile", "CLAUDE.md", ".claude"];
|
|
31965
31744
|
PROJECT_FILE = /\.(sln|csproj)$/i;
|
|
31966
31745
|
SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "bin", "obj", "target", "vendor", "venv", ".venv", "__pycache__", "appdata", "library", "application data", "$recycle.bin", "program files", "program files (x86)", "windows", "programdata"]);
|
|
31967
|
-
CODE_DIRS = ["source", "src", "repos", "projects", "code", "dev", "work", "git", "github", "workspace", "documents", "desktop",
|
|
31746
|
+
CODE_DIRS = ["source", "src", "repos", "projects", "code", "dev", "work", "git", "github", "workspace", "documents", "desktop", path9.join("source", "repos"), "source code"];
|
|
31968
31747
|
cache2 = null;
|
|
31969
31748
|
CACHE_MS = 5 * 6e4;
|
|
31970
31749
|
}
|
|
@@ -32055,6 +31834,47 @@ function stopChannels() {
|
|
|
32055
31834
|
adapters.delete(key);
|
|
32056
31835
|
}
|
|
32057
31836
|
}
|
|
31837
|
+
function readSetting(key, fallback) {
|
|
31838
|
+
const row = db.prepare("SELECT value FROM settings WHERE key = ?").get(key);
|
|
31839
|
+
return parseJson(row?.value, fallback);
|
|
31840
|
+
}
|
|
31841
|
+
function writeSetting(key, value) {
|
|
31842
|
+
db.prepare("INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at").run(
|
|
31843
|
+
key,
|
|
31844
|
+
JSON.stringify(value),
|
|
31845
|
+
now()
|
|
31846
|
+
);
|
|
31847
|
+
}
|
|
31848
|
+
function notificationsPaused() {
|
|
31849
|
+
return readSetting("notificationsPaused", false) === true;
|
|
31850
|
+
}
|
|
31851
|
+
function setNotificationsPaused(paused) {
|
|
31852
|
+
writeSetting("notificationsPaused", paused);
|
|
31853
|
+
broadcast("channels", {});
|
|
31854
|
+
return { paused };
|
|
31855
|
+
}
|
|
31856
|
+
function notifyKinds() {
|
|
31857
|
+
const saved = readSetting("notifyKinds", null);
|
|
31858
|
+
if (saved) return saved.filter((kind) => INTERACTION_KINDS.includes(kind));
|
|
31859
|
+
const channels = allChannels();
|
|
31860
|
+
return channels.length ? INTERACTION_KINDS.filter((kind) => channels.some((channel) => channel.kinds.includes(kind))) : [...INTERACTION_KINDS];
|
|
31861
|
+
}
|
|
31862
|
+
function setNotifyKinds(kinds) {
|
|
31863
|
+
writeSetting("notifyKinds", kinds.filter((kind) => INTERACTION_KINDS.includes(kind)));
|
|
31864
|
+
broadcast("channels", {});
|
|
31865
|
+
return notifyKinds();
|
|
31866
|
+
}
|
|
31867
|
+
function quietSessions() {
|
|
31868
|
+
return readSetting("quietSessions", []);
|
|
31869
|
+
}
|
|
31870
|
+
function setSessionQuiet(sessionId, quiet) {
|
|
31871
|
+
const next = new Set(quietSessions());
|
|
31872
|
+
if (quiet) next.add(sessionId);
|
|
31873
|
+
else next.delete(sessionId);
|
|
31874
|
+
writeSetting("quietSessions", [...next]);
|
|
31875
|
+
broadcast("sessions", { sessionId });
|
|
31876
|
+
return { quiet };
|
|
31877
|
+
}
|
|
32058
31878
|
function mutedProjects() {
|
|
32059
31879
|
const global3 = globalContext();
|
|
32060
31880
|
if (!global3) return [];
|
|
@@ -32107,8 +31927,9 @@ function messageText(interaction, session) {
|
|
|
32107
31927
|
async function deliver(interaction, sessionTitle2, projectId) {
|
|
32108
31928
|
const text = messageText(interaction, sessionTitle2);
|
|
32109
31929
|
const presented = present(interaction, sessionTitle2);
|
|
31930
|
+
if (!notifyKinds().includes(interaction.kind)) return;
|
|
32110
31931
|
for (const channel of allChannels()) {
|
|
32111
|
-
if (!channel.enabled
|
|
31932
|
+
if (!channel.enabled) continue;
|
|
32112
31933
|
const adapter = adapters.get(adapterKey(channel.id));
|
|
32113
31934
|
if (!adapter) continue;
|
|
32114
31935
|
const thread = db.prepare("SELECT thread FROM channel_threads WHERE channel_id = ? AND session_id = ?").get(channel.id, interaction.sessionId)?.thread ?? null;
|
|
@@ -32149,12 +31970,14 @@ async function deliver(interaction, sessionTitle2, projectId) {
|
|
|
32149
31970
|
}
|
|
32150
31971
|
}
|
|
32151
31972
|
async function notifyChannels(interaction, sessionTitle2) {
|
|
31973
|
+
if (notificationsPaused() || quietSessions().includes(interaction.sessionId)) return;
|
|
32152
31974
|
await deliver(interaction, sessionTitle2, null);
|
|
32153
31975
|
broadcast("channels", {});
|
|
32154
31976
|
const project = currentProject();
|
|
32155
31977
|
const global3 = globalContext();
|
|
32156
31978
|
if (!global3 || isGlobal(project) || mutedProjects().includes(project.id)) return;
|
|
32157
31979
|
await withProject(global3, async () => {
|
|
31980
|
+
if (notificationsPaused()) return;
|
|
32158
31981
|
await deliver(interaction, `${project.name} \xB7 ${sessionTitle2}`, project.id);
|
|
32159
31982
|
broadcast("channels", {});
|
|
32160
31983
|
});
|
|
@@ -32464,6 +32287,461 @@ var init_channels = __esm({
|
|
|
32464
32287
|
}
|
|
32465
32288
|
});
|
|
32466
32289
|
|
|
32290
|
+
// server/src/sessionFiles.ts
|
|
32291
|
+
import fs9 from "node:fs";
|
|
32292
|
+
import os3 from "node:os";
|
|
32293
|
+
import path10 from "node:path";
|
|
32294
|
+
function checkId(id) {
|
|
32295
|
+
if (!UUID.test(id)) throw new Error("Not a session id.");
|
|
32296
|
+
}
|
|
32297
|
+
function transcriptFile(id) {
|
|
32298
|
+
return path10.join(transcriptsDir(), `${id}.jsonl`);
|
|
32299
|
+
}
|
|
32300
|
+
function setSessionTitle(id, title) {
|
|
32301
|
+
checkId(id);
|
|
32302
|
+
const name = title.replace(/\s+/g, " ").trim();
|
|
32303
|
+
if (!name) throw new Error("The name is empty.");
|
|
32304
|
+
if (name.length > 200) throw new Error("Keep the name under 200 characters.");
|
|
32305
|
+
const file = transcriptFile(id);
|
|
32306
|
+
if (!fs9.existsSync(file)) throw new Error("This session has no transcript yet; give it a moment to start.");
|
|
32307
|
+
const size = fs9.statSync(file).size;
|
|
32308
|
+
let lead = "";
|
|
32309
|
+
if (size > 0) {
|
|
32310
|
+
const fd = fs9.openSync(file, "r");
|
|
32311
|
+
const last = Buffer.alloc(1);
|
|
32312
|
+
fs9.readSync(fd, last, 0, 1, size - 1);
|
|
32313
|
+
fs9.closeSync(fd);
|
|
32314
|
+
if (last.toString() !== "\n") lead = "\n";
|
|
32315
|
+
}
|
|
32316
|
+
fs9.appendFileSync(file, `${lead}${JSON.stringify({ type: "custom-title", customTitle: name, sessionId: id })}
|
|
32317
|
+
`, "utf8");
|
|
32318
|
+
broadcast("sessions", { sessionId: id });
|
|
32319
|
+
}
|
|
32320
|
+
function sizeOf(target2) {
|
|
32321
|
+
const stat = fs9.statSync(target2);
|
|
32322
|
+
if (!stat.isDirectory()) return stat.size;
|
|
32323
|
+
return fs9.readdirSync(target2).reduce((total, name) => total + sizeOf(path10.join(target2, name)), 0);
|
|
32324
|
+
}
|
|
32325
|
+
function tempDirOf(id) {
|
|
32326
|
+
const base = process.env.CLAUDE_CODE_TMPDIR ?? os3.tmpdir();
|
|
32327
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
32328
|
+
const roots2 = [path10.join(base, "claude"), ...uid !== null ? [path10.join(base, `claude-${uid}`)] : []];
|
|
32329
|
+
const candidates2 = roots2.map((root2) => path10.join(root2, projectSlug(projectRoot()), id));
|
|
32330
|
+
return candidates2.find((candidate) => fs9.existsSync(candidate)) ?? candidates2[0];
|
|
32331
|
+
}
|
|
32332
|
+
function sessionDiskBytes(id) {
|
|
32333
|
+
if (!UUID.test(id)) return null;
|
|
32334
|
+
const key = `${projectRoot()}\0${id}`;
|
|
32335
|
+
const hit = diskCache.get(key);
|
|
32336
|
+
if (hit && Date.now() - hit.at < 6e4) return hit.bytes;
|
|
32337
|
+
let bytes = null;
|
|
32338
|
+
try {
|
|
32339
|
+
const plan = deletionPlan(id);
|
|
32340
|
+
bytes = plan.totalBytes + (plan.temp?.bytes ?? 0);
|
|
32341
|
+
} catch {
|
|
32342
|
+
}
|
|
32343
|
+
if (bytes !== null) diskCache.set(key, { bytes, at: Date.now() });
|
|
32344
|
+
return bytes ?? hit?.bytes ?? null;
|
|
32345
|
+
}
|
|
32346
|
+
function deletionPlan(id) {
|
|
32347
|
+
checkId(id);
|
|
32348
|
+
const candidates2 = [
|
|
32349
|
+
[transcriptFile(id), "transcript"],
|
|
32350
|
+
[path10.join(transcriptsDir(), id), "subagent transcripts and tool output"],
|
|
32351
|
+
[path10.join(claudeHome, "file-history", id), "file snapshots used to rewind"],
|
|
32352
|
+
[path10.join(claudeHome, "session-env", id), "session environment"]
|
|
32353
|
+
];
|
|
32354
|
+
const files = candidates2.filter(([target2]) => fs9.existsSync(target2)).map(([target2, what]) => ({ path: target2, bytes: sizeOf(target2), what }));
|
|
32355
|
+
const temp = tempDirOf(id);
|
|
32356
|
+
return {
|
|
32357
|
+
sessionId: id,
|
|
32358
|
+
files,
|
|
32359
|
+
totalBytes: files.reduce((total, item) => total + item.bytes, 0),
|
|
32360
|
+
temp: fs9.existsSync(temp) ? { path: temp, bytes: sizeOf(temp) } : null
|
|
32361
|
+
};
|
|
32362
|
+
}
|
|
32363
|
+
async function deleteSession(id, options = {}) {
|
|
32364
|
+
checkId(id);
|
|
32365
|
+
const terminal = terminalForSession(id);
|
|
32366
|
+
if (terminal) {
|
|
32367
|
+
killTerminal(terminal);
|
|
32368
|
+
await waitForExit(terminal);
|
|
32369
|
+
if (terminalForSession(id)) throw new Error("The session did not stop in time; nothing was deleted. Try again.");
|
|
32370
|
+
}
|
|
32371
|
+
const plan = deletionPlan(id);
|
|
32372
|
+
for (const item of plan.files) fs9.rmSync(item.path, { recursive: true, force: true });
|
|
32373
|
+
if (options.clearTemp !== false && plan.temp) fs9.rmSync(plan.temp.path, { recursive: true, force: true });
|
|
32374
|
+
db.transaction(() => {
|
|
32375
|
+
for (const table of ["interactions", "mcp_suggestions", "channel_refs", "channel_threads", "sessions"]) {
|
|
32376
|
+
db.prepare(`DELETE FROM ${table} WHERE ${table === "sessions" ? "id" : "session_id"} = ?`).run(id);
|
|
32377
|
+
}
|
|
32378
|
+
})();
|
|
32379
|
+
forgetTranscript(id);
|
|
32380
|
+
broadcast("sessions", {});
|
|
32381
|
+
broadcast("interactions", {});
|
|
32382
|
+
broadcast("mcp", {});
|
|
32383
|
+
return plan;
|
|
32384
|
+
}
|
|
32385
|
+
var UUID, diskCache;
|
|
32386
|
+
var init_sessionFiles = __esm({
|
|
32387
|
+
"server/src/sessionFiles.ts"() {
|
|
32388
|
+
"use strict";
|
|
32389
|
+
init_db();
|
|
32390
|
+
init_events();
|
|
32391
|
+
init_paths();
|
|
32392
|
+
init_terminals();
|
|
32393
|
+
init_transcripts();
|
|
32394
|
+
UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
32395
|
+
diskCache = /* @__PURE__ */ new Map();
|
|
32396
|
+
}
|
|
32397
|
+
});
|
|
32398
|
+
|
|
32399
|
+
// server/src/inventory.ts
|
|
32400
|
+
import fs10 from "node:fs";
|
|
32401
|
+
import os4 from "node:os";
|
|
32402
|
+
import path11 from "node:path";
|
|
32403
|
+
function readJson(file) {
|
|
32404
|
+
try {
|
|
32405
|
+
return JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
32406
|
+
} catch {
|
|
32407
|
+
return null;
|
|
32408
|
+
}
|
|
32409
|
+
}
|
|
32410
|
+
function frontMatter(text) {
|
|
32411
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
|
|
32412
|
+
if (!match) return {};
|
|
32413
|
+
const fields = {};
|
|
32414
|
+
let lastKey = "";
|
|
32415
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
32416
|
+
const pair = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
32417
|
+
if (pair) {
|
|
32418
|
+
lastKey = pair[1];
|
|
32419
|
+
fields[lastKey] = pair[2].replace(/^["']|["']$/g, "").replace(/^[>|]-?$/, "");
|
|
32420
|
+
} else if (lastKey && /^\s+\S/.test(line)) {
|
|
32421
|
+
fields[lastKey] = `${fields[lastKey]} ${line.trim()}`.trim();
|
|
32422
|
+
}
|
|
32423
|
+
}
|
|
32424
|
+
return fields;
|
|
32425
|
+
}
|
|
32426
|
+
function definition(file, fallbackName, scope, plugin) {
|
|
32427
|
+
let text;
|
|
32428
|
+
try {
|
|
32429
|
+
text = fs10.readFileSync(file, "utf8");
|
|
32430
|
+
} catch {
|
|
32431
|
+
return null;
|
|
32432
|
+
}
|
|
32433
|
+
const meta = frontMatter(text);
|
|
32434
|
+
const name = meta.name || fallbackName;
|
|
32435
|
+
return {
|
|
32436
|
+
name: plugin ? `${plugin.split("@")[0]}:${name}` : name,
|
|
32437
|
+
description: meta.description ?? "",
|
|
32438
|
+
scope,
|
|
32439
|
+
plugin,
|
|
32440
|
+
path: file,
|
|
32441
|
+
model: meta.model || void 0
|
|
32442
|
+
};
|
|
32443
|
+
}
|
|
32444
|
+
function markdownFiles(dir, depth = 3) {
|
|
32445
|
+
const found = [];
|
|
32446
|
+
const walk = (current, left) => {
|
|
32447
|
+
let entries;
|
|
32448
|
+
try {
|
|
32449
|
+
entries = fs10.readdirSync(current, { withFileTypes: true });
|
|
32450
|
+
} catch {
|
|
32451
|
+
return;
|
|
32452
|
+
}
|
|
32453
|
+
for (const entry of entries) {
|
|
32454
|
+
const full = path11.join(current, entry.name);
|
|
32455
|
+
if (entry.isDirectory() && left > 0) walk(full, left - 1);
|
|
32456
|
+
else if (entry.isFile() && entry.name.endsWith(".md")) found.push(full);
|
|
32457
|
+
}
|
|
32458
|
+
};
|
|
32459
|
+
walk(dir, depth);
|
|
32460
|
+
return found;
|
|
32461
|
+
}
|
|
32462
|
+
function agentsIn(dir, scope, plugin) {
|
|
32463
|
+
return markdownFiles(dir, 1).map((file) => definition(file, path11.basename(file, ".md"), scope, plugin)).filter((item) => item !== null);
|
|
32464
|
+
}
|
|
32465
|
+
function skillsIn(dir, scope, plugin) {
|
|
32466
|
+
let entries;
|
|
32467
|
+
try {
|
|
32468
|
+
entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
32469
|
+
} catch {
|
|
32470
|
+
return [];
|
|
32471
|
+
}
|
|
32472
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => definition(path11.join(dir, entry.name, "SKILL.md"), entry.name, scope, plugin)).filter((item) => item !== null);
|
|
32473
|
+
}
|
|
32474
|
+
function commandsIn(dir, scope, plugin) {
|
|
32475
|
+
return markdownFiles(dir, 2).map((file) => definition(file, path11.relative(dir, file).replace(/\.md$/, "").replace(/[\\/]/g, ":"), scope, plugin)).filter((item) => item !== null);
|
|
32476
|
+
}
|
|
32477
|
+
function mcpFrom(servers, scope, source, plugin) {
|
|
32478
|
+
if (!servers || typeof servers !== "object") return [];
|
|
32479
|
+
return Object.entries(servers).map(([name, config]) => ({
|
|
32480
|
+
name: plugin ? `plugin:${plugin.split("@")[0]}:${name}` : name,
|
|
32481
|
+
scope,
|
|
32482
|
+
plugin,
|
|
32483
|
+
transport: String(config?.type ?? (config?.url ? "http" : "stdio")),
|
|
32484
|
+
target: config?.url ? String(config.url) : [config?.command, ...Array.isArray(config?.args) ? config.args : []].filter(Boolean).join(" "),
|
|
32485
|
+
source
|
|
32486
|
+
}));
|
|
32487
|
+
}
|
|
32488
|
+
function hooksFrom(settings, scope, source, plugin) {
|
|
32489
|
+
const hooks = settings?.hooks;
|
|
32490
|
+
if (!hooks || typeof hooks !== "object") return [];
|
|
32491
|
+
const items = [];
|
|
32492
|
+
for (const [event, groups] of Object.entries(hooks)) {
|
|
32493
|
+
if (!Array.isArray(groups)) continue;
|
|
32494
|
+
for (const group of groups) {
|
|
32495
|
+
for (const hook of Array.isArray(group?.hooks) ? group.hooks : []) {
|
|
32496
|
+
items.push({
|
|
32497
|
+
event,
|
|
32498
|
+
matcher: String(group?.matcher ?? ""),
|
|
32499
|
+
type: String(hook?.type ?? "command"),
|
|
32500
|
+
scope,
|
|
32501
|
+
plugin,
|
|
32502
|
+
source,
|
|
32503
|
+
summary: String(hook?.command ?? hook?.url ?? hook?.prompt ?? hook?.tool ?? "").slice(0, 200)
|
|
32504
|
+
});
|
|
32505
|
+
}
|
|
32506
|
+
}
|
|
32507
|
+
}
|
|
32508
|
+
return items;
|
|
32509
|
+
}
|
|
32510
|
+
function localProjectConfig(global3) {
|
|
32511
|
+
const projects = global3?.projects ?? {};
|
|
32512
|
+
const wanted = [projectRoot(), projectRoot().replace(/\\/g, "/")];
|
|
32513
|
+
for (const [key, value] of Object.entries(projects)) {
|
|
32514
|
+
if (wanted.some((candidate) => candidate.toLowerCase() === key.toLowerCase())) return value;
|
|
32515
|
+
}
|
|
32516
|
+
return null;
|
|
32517
|
+
}
|
|
32518
|
+
function readInventory() {
|
|
32519
|
+
const userSettingsFile = path11.join(claudeHome, "settings.json");
|
|
32520
|
+
const projectSettingsFile = path11.join(projectRoot(), ".claude", "settings.json");
|
|
32521
|
+
const localSettingsFile = path11.join(projectRoot(), ".claude", "settings.local.json");
|
|
32522
|
+
const userSettings = readJson(userSettingsFile);
|
|
32523
|
+
const projectSettings = readJson(projectSettingsFile);
|
|
32524
|
+
const localSettings = readJson(localSettingsFile);
|
|
32525
|
+
const globalConfigFile = path11.join(os4.homedir(), ".claude.json");
|
|
32526
|
+
const globalConfig = readJson(globalConfigFile);
|
|
32527
|
+
const local = localProjectConfig(globalConfig);
|
|
32528
|
+
const enabled = {
|
|
32529
|
+
...userSettings?.enabledPlugins ?? {},
|
|
32530
|
+
...projectSettings?.enabledPlugins ?? {},
|
|
32531
|
+
...localSettings?.enabledPlugins ?? {}
|
|
32532
|
+
};
|
|
32533
|
+
const plugins = [];
|
|
32534
|
+
const installed = readJson(path11.join(claudeHome, "plugins", "installed_plugins.json"));
|
|
32535
|
+
for (const [id, installs] of Object.entries(installed?.plugins ?? {})) {
|
|
32536
|
+
const install = Array.isArray(installs) ? installs[installs.length - 1] : null;
|
|
32537
|
+
if (!install?.installPath) continue;
|
|
32538
|
+
plugins.push({
|
|
32539
|
+
id,
|
|
32540
|
+
version: String(install.version ?? ""),
|
|
32541
|
+
scope: String(install.scope ?? "user"),
|
|
32542
|
+
// Installed plugins are on unless a settings file turns them off.
|
|
32543
|
+
enabled: enabled[id] !== false,
|
|
32544
|
+
path: install.installPath
|
|
32545
|
+
});
|
|
32546
|
+
}
|
|
32547
|
+
const agents = [
|
|
32548
|
+
...agentsIn(path11.join(claudeHome, "agents"), "user"),
|
|
32549
|
+
...agentsIn(path11.join(projectRoot(), ".claude", "agents"), "project"),
|
|
32550
|
+
// No file behind these, so no path: they can be turned off, not edited.
|
|
32551
|
+
...BUILT_IN_AGENTS.map(([name, description]) => ({ name, description, scope: "built-in", path: "" }))
|
|
32552
|
+
];
|
|
32553
|
+
const skills = [...skillsIn(path11.join(claudeHome, "skills"), "user"), ...skillsIn(path11.join(projectRoot(), ".claude", "skills"), "project")];
|
|
32554
|
+
const commands = [
|
|
32555
|
+
...commandsIn(path11.join(claudeHome, "commands"), "user"),
|
|
32556
|
+
...commandsIn(path11.join(projectRoot(), ".claude", "commands"), "project")
|
|
32557
|
+
];
|
|
32558
|
+
const disabledJson = new Set(local?.disabledMcpjsonServers ?? []);
|
|
32559
|
+
const mcpServers = [
|
|
32560
|
+
...mcpFrom(globalConfig?.mcpServers, "user", globalConfigFile),
|
|
32561
|
+
...mcpFrom(local?.mcpServers, "local", globalConfigFile),
|
|
32562
|
+
...mcpFrom(readJson(path11.join(projectRoot(), ".mcp.json"))?.mcpServers, "project", path11.join(projectRoot(), ".mcp.json")).map(
|
|
32563
|
+
(server2) => ({ ...server2, disabled: disabledJson.has(server2.name) })
|
|
32564
|
+
)
|
|
32565
|
+
];
|
|
32566
|
+
const hooks = [
|
|
32567
|
+
...hooksFrom(userSettings, "user", userSettingsFile),
|
|
32568
|
+
...hooksFrom(projectSettings, "project", projectSettingsFile),
|
|
32569
|
+
...hooksFrom(localSettings, "local", localSettingsFile)
|
|
32570
|
+
];
|
|
32571
|
+
for (const plugin of plugins.filter((item) => item.enabled)) {
|
|
32572
|
+
agents.push(...agentsIn(path11.join(plugin.path, "agents"), "plugin", plugin.id));
|
|
32573
|
+
skills.push(...skillsIn(path11.join(plugin.path, "skills"), "plugin", plugin.id));
|
|
32574
|
+
commands.push(...commandsIn(path11.join(plugin.path, "commands"), "plugin", plugin.id));
|
|
32575
|
+
const pluginMcp = readJson(path11.join(plugin.path, ".mcp.json"));
|
|
32576
|
+
mcpServers.push(...mcpFrom(pluginMcp?.mcpServers ?? pluginMcp, "plugin", path11.join(plugin.path, ".mcp.json"), plugin.id));
|
|
32577
|
+
hooks.push(...hooksFrom(readJson(path11.join(plugin.path, "hooks", "hooks.json")), "plugin", path11.join(plugin.path, "hooks", "hooks.json"), plugin.id));
|
|
32578
|
+
}
|
|
32579
|
+
return { agents, skills, commands, mcpServers, hooks, plugins };
|
|
32580
|
+
}
|
|
32581
|
+
function memoryFiles() {
|
|
32582
|
+
const files = [];
|
|
32583
|
+
const add = (file, label, group, scope, always = false) => {
|
|
32584
|
+
const exists = fs10.existsSync(file);
|
|
32585
|
+
if (exists || always) files.push({ path: file, label, group, scope, exists });
|
|
32586
|
+
};
|
|
32587
|
+
add(path11.join(claudeHome, "CLAUDE.md"), "~/.claude/CLAUDE.md", "memory", "user", true);
|
|
32588
|
+
add(path11.join(projectRoot(), "CLAUDE.md"), "CLAUDE.md", "memory", "project", true);
|
|
32589
|
+
add(path11.join(projectRoot(), ".claude", "CLAUDE.md"), ".claude/CLAUDE.md", "memory", "project");
|
|
32590
|
+
add(path11.join(projectRoot(), "CLAUDE.local.md"), "CLAUDE.local.md", "memory", "local");
|
|
32591
|
+
for (const file of markdownFiles(path11.join(claudeHome, "rules"))) add(file, `~/.claude/rules/${path11.relative(path11.join(claudeHome, "rules"), file)}`, "rules", "user");
|
|
32592
|
+
for (const file of markdownFiles(path11.join(projectRoot(), ".claude", "rules"))) add(file, `.claude/rules/${path11.relative(path11.join(projectRoot(), ".claude", "rules"), file)}`, "rules", "project");
|
|
32593
|
+
for (const file of markdownFiles(path11.join(transcriptsDir(), "memory"), 1)) add(file, `memory/${path11.basename(file)}`, "auto-memory", "local");
|
|
32594
|
+
const inventory = readInventory();
|
|
32595
|
+
for (const agent of inventory.agents.filter((item) => item.scope !== "plugin" && item.path)) add(agent.path, `agent: ${agent.name}`, "agent", agent.scope);
|
|
32596
|
+
for (const skill of inventory.skills.filter((item) => item.scope !== "plugin")) add(skill.path, `skill: ${skill.name}`, "skill", skill.scope);
|
|
32597
|
+
for (const command of inventory.commands.filter((item) => item.scope !== "plugin")) add(command.path, `command: /${command.name}`, "command", command.scope);
|
|
32598
|
+
return files.map((file) => ({ ...file, label: file.label.replace(/\\/g, "/") }));
|
|
32599
|
+
}
|
|
32600
|
+
function isMemoryFile(file) {
|
|
32601
|
+
const wanted = path11.resolve(file).toLowerCase();
|
|
32602
|
+
return memoryFiles().some((item) => path11.resolve(item.path).toLowerCase() === wanted);
|
|
32603
|
+
}
|
|
32604
|
+
function isDefinitionFile(file) {
|
|
32605
|
+
const wanted = path11.resolve(file).toLowerCase();
|
|
32606
|
+
const inventory = readInventory();
|
|
32607
|
+
return [...inventory.agents, ...inventory.skills, ...inventory.commands].some((item) => item.path && path11.resolve(item.path).toLowerCase() === wanted);
|
|
32608
|
+
}
|
|
32609
|
+
var BUILT_IN_AGENTS;
|
|
32610
|
+
var init_inventory = __esm({
|
|
32611
|
+
"server/src/inventory.ts"() {
|
|
32612
|
+
"use strict";
|
|
32613
|
+
init_paths();
|
|
32614
|
+
BUILT_IN_AGENTS = [
|
|
32615
|
+
["Explore", "Fast read-only search across the codebase."],
|
|
32616
|
+
["Plan", "Designs an implementation plan."],
|
|
32617
|
+
["general-purpose", "Multi-step research, code search and tasks."],
|
|
32618
|
+
["claude-code-guide", "Answers questions about Claude Code, the Agent SDK and the API."],
|
|
32619
|
+
["statusline-setup", "Configures the status line."],
|
|
32620
|
+
["web-fetch", "Fetches and reads web pages."]
|
|
32621
|
+
];
|
|
32622
|
+
}
|
|
32623
|
+
});
|
|
32624
|
+
|
|
32625
|
+
// server/src/toolset.ts
|
|
32626
|
+
function normaliseToolset(input) {
|
|
32627
|
+
const value = input ?? {};
|
|
32628
|
+
const list = (items) => [...new Set((Array.isArray(items) ? items : []).map(String).filter(Boolean))].sort();
|
|
32629
|
+
return { agents: list(value.agents), skills: list(value.skills), mcp: list(value.mcp), plugins: list(value.plugins) };
|
|
32630
|
+
}
|
|
32631
|
+
function isEmptyToolset(toolset) {
|
|
32632
|
+
return !toolset.agents.length && !toolset.skills.length && !toolset.mcp.length && !toolset.plugins.length;
|
|
32633
|
+
}
|
|
32634
|
+
function mcpPrefix(server2) {
|
|
32635
|
+
if (server2.scope !== "plugin") return `mcp__${toolName(server2.name)}`;
|
|
32636
|
+
const [, plugin = "", name = ""] = server2.name.split(":");
|
|
32637
|
+
return `mcp__plugin_${toolName(plugin)}_${toolName(name)}`;
|
|
32638
|
+
}
|
|
32639
|
+
function connectorsSeen() {
|
|
32640
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
32641
|
+
for (const summary of listTranscripts()) {
|
|
32642
|
+
for (const name of Object.keys(transcriptDetail(summary.id)?.totals.tools ?? {})) {
|
|
32643
|
+
const match = /^(mcp__claude_ai_[A-Za-z0-9_-]+?)__/.exec(name);
|
|
32644
|
+
if (match) prefixes.add(match[1]);
|
|
32645
|
+
}
|
|
32646
|
+
}
|
|
32647
|
+
return [...prefixes].sort();
|
|
32648
|
+
}
|
|
32649
|
+
function toolsetCatalog() {
|
|
32650
|
+
const inventory = readInventory();
|
|
32651
|
+
const items = [];
|
|
32652
|
+
for (const plugin of inventory.plugins.filter((item) => item.enabled)) {
|
|
32653
|
+
items.push({ kind: "plugins", id: plugin.id, label: plugin.id, description: `v${plugin.version}`, scope: plugin.scope === "user" ? "user" : "project", leavesContext: true });
|
|
32654
|
+
}
|
|
32655
|
+
for (const agent of inventory.agents) {
|
|
32656
|
+
items.push({ kind: "agents", id: agent.name, label: agent.name, description: agent.description, scope: agent.scope, plugin: agent.plugin, leavesContext: true });
|
|
32657
|
+
}
|
|
32658
|
+
for (const skill of inventory.skills) {
|
|
32659
|
+
items.push({ kind: "skills", id: skill.name, label: skill.name, description: skill.description, scope: skill.scope, plugin: skill.plugin, leavesContext: skill.scope !== "plugin" });
|
|
32660
|
+
}
|
|
32661
|
+
for (const server2 of inventory.mcpServers) {
|
|
32662
|
+
items.push({ kind: "mcp", id: mcpPrefix(server2), label: server2.name, description: server2.target, scope: server2.scope, plugin: server2.plugin, leavesContext: false });
|
|
32663
|
+
}
|
|
32664
|
+
for (const prefix of connectorsSeen()) {
|
|
32665
|
+
items.push({ kind: "mcp", id: prefix, label: prefix.replace(/^mcp__claude_ai_/, "claude.ai ").replace(/_/g, " "), description: "claude.ai connector", scope: "connector", leavesContext: false });
|
|
32666
|
+
}
|
|
32667
|
+
const seen = /* @__PURE__ */ new Set();
|
|
32668
|
+
return items.filter((item) => {
|
|
32669
|
+
const key = `${item.kind}:${item.id}`;
|
|
32670
|
+
if (seen.has(key)) return false;
|
|
32671
|
+
seen.add(key);
|
|
32672
|
+
return true;
|
|
32673
|
+
});
|
|
32674
|
+
}
|
|
32675
|
+
function toolsetSettings(toolset) {
|
|
32676
|
+
if (isEmptyToolset(toolset)) return {};
|
|
32677
|
+
const pluginSkills = new Set(readInventory().skills.filter((skill) => skill.scope === "plugin").map((skill) => skill.name));
|
|
32678
|
+
const deny = [
|
|
32679
|
+
...toolset.agents.map((name) => `Agent(${name})`),
|
|
32680
|
+
...toolset.skills.filter((name) => pluginSkills.has(name)).map((name) => `Skill(${name})`),
|
|
32681
|
+
...toolset.mcp
|
|
32682
|
+
];
|
|
32683
|
+
const overrides = Object.fromEntries(toolset.skills.filter((name) => !pluginSkills.has(name)).map((name) => [name, "off"]));
|
|
32684
|
+
const settings = {};
|
|
32685
|
+
if (deny.length) settings.permissions = { deny };
|
|
32686
|
+
if (Object.keys(overrides).length) settings.skillOverrides = overrides;
|
|
32687
|
+
if (toolset.plugins.length) settings.enabledPlugins = Object.fromEntries(toolset.plugins.map((id) => [id, false]));
|
|
32688
|
+
return settings;
|
|
32689
|
+
}
|
|
32690
|
+
function listPresets() {
|
|
32691
|
+
return db.prepare("SELECT * FROM presets ORDER BY name COLLATE NOCASE").all().map(toPreset);
|
|
32692
|
+
}
|
|
32693
|
+
function getPreset(id) {
|
|
32694
|
+
const row = db.prepare("SELECT * FROM presets WHERE id = ?").get(id);
|
|
32695
|
+
return row ? toPreset(row) : null;
|
|
32696
|
+
}
|
|
32697
|
+
function defaultToolset() {
|
|
32698
|
+
const row = db.prepare("SELECT * FROM presets WHERE is_default = 1 LIMIT 1").get();
|
|
32699
|
+
return row ? toPreset(row).toolset : EMPTY_TOOLSET;
|
|
32700
|
+
}
|
|
32701
|
+
function savePreset(input) {
|
|
32702
|
+
const name = String(input.name ?? "").trim();
|
|
32703
|
+
if (!name) throw new Error("A preset needs a name.");
|
|
32704
|
+
const id = input.id ?? nanoid(10);
|
|
32705
|
+
db.transaction(() => {
|
|
32706
|
+
if (input.isDefault) db.prepare("UPDATE presets SET is_default = 0").run();
|
|
32707
|
+
db.prepare(
|
|
32708
|
+
`INSERT INTO presets (id, name, toolset, is_default, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
32709
|
+
ON CONFLICT(id) DO UPDATE SET name = excluded.name, toolset = excluded.toolset, is_default = excluded.is_default, updated_at = excluded.updated_at`
|
|
32710
|
+
).run(id, name, JSON.stringify(normaliseToolset(input.toolset)), input.isDefault ? 1 : 0, now(), now());
|
|
32711
|
+
})();
|
|
32712
|
+
broadcast("jobs", {});
|
|
32713
|
+
return getPreset(id);
|
|
32714
|
+
}
|
|
32715
|
+
function setDefaultItem(input) {
|
|
32716
|
+
const kind = String(input.kind ?? "");
|
|
32717
|
+
const id = String(input.id ?? "").trim();
|
|
32718
|
+
if (!(kind in EMPTY_TOOLSET) || !id) throw new Error("Send { kind, id, enabled }.");
|
|
32719
|
+
const row = db.prepare("SELECT * FROM presets WHERE is_default = 1 LIMIT 1").get();
|
|
32720
|
+
const preset = row ? toPreset(row) : { id: void 0, name: "Default", toolset: EMPTY_TOOLSET };
|
|
32721
|
+
const off = preset.toolset[kind].filter((item) => item !== id);
|
|
32722
|
+
if (input.enabled !== true) off.push(id);
|
|
32723
|
+
return savePreset({ id: preset.id, name: preset.name, toolset: { ...preset.toolset, [kind]: off }, isDefault: true });
|
|
32724
|
+
}
|
|
32725
|
+
function deletePreset(id) {
|
|
32726
|
+
db.prepare("DELETE FROM presets WHERE id = ?").run(id);
|
|
32727
|
+
db.prepare("UPDATE jobs SET preset_id = NULL WHERE preset_id = ?").run(id);
|
|
32728
|
+
broadcast("jobs", {});
|
|
32729
|
+
}
|
|
32730
|
+
var EMPTY_TOOLSET, toolName, toPreset;
|
|
32731
|
+
var init_toolset = __esm({
|
|
32732
|
+
"server/src/toolset.ts"() {
|
|
32733
|
+
"use strict";
|
|
32734
|
+
init_nanoid();
|
|
32735
|
+
init_db();
|
|
32736
|
+
init_events();
|
|
32737
|
+
init_inventory();
|
|
32738
|
+
init_transcripts();
|
|
32739
|
+
EMPTY_TOOLSET = { agents: [], skills: [], mcp: [], plugins: [] };
|
|
32740
|
+
toolName = (value) => value.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
32741
|
+
toPreset = (row) => ({ id: row.id, name: row.name, toolset: normaliseToolset(parseJson(row.toolset, {})), isDefault: Boolean(row.is_default) });
|
|
32742
|
+
}
|
|
32743
|
+
});
|
|
32744
|
+
|
|
32467
32745
|
// server/src/mcpCatalog.ts
|
|
32468
32746
|
function catalogEntry(id) {
|
|
32469
32747
|
return MCP_CATALOG.find((entry) => entry.id === id) ?? null;
|
|
@@ -32694,7 +32972,6 @@ function channelParams() {
|
|
|
32694
32972
|
{ name: "kind", type: `"${CHANNEL_KINDS.map((kind) => kind.kind).join('" | "')}"`, required: true, description: "The platform. Cannot change after creation.", example: "slack" },
|
|
32695
32973
|
{ name: "name", type: "string", required: true, description: "Shown in the dashboard.", example: "Team Slack" },
|
|
32696
32974
|
{ name: "enabled", type: "boolean", required: false, description: "Default true.", example: "true" },
|
|
32697
|
-
{ name: "kinds", type: "string[]", required: false, description: `What it is sent; any of ${INTERACTION_KINDS.join(", ")}. Default all.`, example: '["permission", "question", "done"]' },
|
|
32698
32975
|
{ name: "config", type: "object", required: true, description: `Per platform. ${fields}`, example: '{ "botToken": "xoxb-...", "appToken": "xapp-...", "channel": "C0123456789" }' }
|
|
32699
32976
|
];
|
|
32700
32977
|
}
|
|
@@ -32778,8 +33055,8 @@ function definitionOf(purpose) {
|
|
|
32778
33055
|
summary: isGlobal(currentProject()) ? "A global channel serves every project in the hub: their permission requests, questions, ends of turns, MCP suggestions and finished tasks all arrive on it, each named after its project, and replies go back into the right session. A bot used here cannot also be a project's own channel." : "A channel is where sessions reach the person: permission requests, questions, ends of turns, MCP suggestions and finished tasks. Replies there go back into the session.",
|
|
32779
33056
|
params: channelParams(),
|
|
32780
33057
|
samples: [
|
|
32781
|
-
{ kind: "slack", name: "Team Slack", enabled: true,
|
|
32782
|
-
{ kind: "telegram", name: "My phone", enabled: true,
|
|
33058
|
+
{ kind: "slack", name: "Team Slack", enabled: true, config: { botToken: "xoxb-...", appToken: "xapp-...", channel: "C0123456789" } },
|
|
33059
|
+
{ kind: "telegram", name: "My phone", enabled: true, config: { botToken: "123456:ABC-...", chatId: "123456789" } }
|
|
32783
33060
|
],
|
|
32784
33061
|
api: [
|
|
32785
33062
|
"GET /api/channels (kinds and fields, existing channels with secrets masked as ********)",
|
|
@@ -32807,7 +33084,7 @@ function currentValue(purpose) {
|
|
|
32807
33084
|
}
|
|
32808
33085
|
case "edit-channel": {
|
|
32809
33086
|
const channel = listChannels().find((item) => item.id === purpose.targetId);
|
|
32810
|
-
return channel && { kind: channel.kind, name: channel.name, enabled: channel.enabled,
|
|
33087
|
+
return channel && { kind: channel.kind, name: channel.name, enabled: channel.enabled, config: channel.config };
|
|
32811
33088
|
}
|
|
32812
33089
|
default:
|
|
32813
33090
|
return void 0;
|
|
@@ -32937,8 +33214,8 @@ JSON`;
|
|
|
32937
33214
|
|
|
32938
33215
|
// server/src/sessions.ts
|
|
32939
33216
|
import { randomUUID } from "node:crypto";
|
|
32940
|
-
import
|
|
32941
|
-
import
|
|
33217
|
+
import fs11 from "node:fs";
|
|
33218
|
+
import path12 from "node:path";
|
|
32942
33219
|
function getSessionRow(id) {
|
|
32943
33220
|
return db.prepare("SELECT * FROM sessions WHERE id = ?").get(id) ?? null;
|
|
32944
33221
|
}
|
|
@@ -33034,11 +33311,11 @@ function startSession(options) {
|
|
|
33034
33311
|
const category = options.category !== void 0 ? options.category || null : existing?.category ?? null;
|
|
33035
33312
|
const token = nanoid(24);
|
|
33036
33313
|
const toolset = options.toolset ? normaliseToolset(options.toolset) : existing?.toolset ? normaliseToolset(JSON.parse(existing.toolset)) : defaultToolset();
|
|
33037
|
-
const settingsFile =
|
|
33038
|
-
const promptFile =
|
|
33039
|
-
|
|
33314
|
+
const settingsFile = path12.join(runDir, `${token}.settings.json`);
|
|
33315
|
+
const promptFile = path12.join(runDir, `${token}.prompt.md`);
|
|
33316
|
+
fs11.writeFileSync(settingsFile, JSON.stringify({ ...toolsetSettings(toolset), ...hookSettings(token) }, null, 2), "utf8");
|
|
33040
33317
|
const purpose = options.purpose ?? (existing?.purpose ? JSON.parse(existing.purpose) : null);
|
|
33041
|
-
|
|
33318
|
+
fs11.writeFileSync(promptFile, systemPromptFor(category, purpose, existing ? sessionSetup(sessionId) : null), "utf8");
|
|
33042
33319
|
const args = ["--settings", settingsFile, "--append-system-prompt-file", promptFile];
|
|
33043
33320
|
args.push(...options.resumeId ? ["--resume", options.resumeId] : ["--session-id", sessionId]);
|
|
33044
33321
|
if (options.name) args.push("--name", options.name);
|
|
@@ -33087,7 +33364,7 @@ function terminalByToken(token) {
|
|
|
33087
33364
|
return allTerminals().find((terminal) => terminal.token === token);
|
|
33088
33365
|
}
|
|
33089
33366
|
function cleanRunFiles(token) {
|
|
33090
|
-
for (const suffix of [".settings.json", ".prompt.md"])
|
|
33367
|
+
for (const suffix of [".settings.json", ".prompt.md"]) fs11.rmSync(path12.join(runDir, `${token}${suffix}`), { force: true });
|
|
33091
33368
|
}
|
|
33092
33369
|
function purposeOf(row) {
|
|
33093
33370
|
const purpose = parseJson(row?.purpose, null);
|
|
@@ -33101,6 +33378,7 @@ function listSessions() {
|
|
|
33101
33378
|
)
|
|
33102
33379
|
);
|
|
33103
33380
|
const terminals2 = new Map(listTerminals().map((terminal) => [terminal.sessionId, terminal]));
|
|
33381
|
+
const quiet = new Set(quietSessions());
|
|
33104
33382
|
const views = [];
|
|
33105
33383
|
const seen = /* @__PURE__ */ new Set();
|
|
33106
33384
|
const view = (id, summary) => {
|
|
@@ -33121,7 +33399,9 @@ function listSessions() {
|
|
|
33121
33399
|
runId: row?.run_id ?? null,
|
|
33122
33400
|
openInteractions: open.get(id) ?? 0,
|
|
33123
33401
|
done: parseJson(row?.done_json, null),
|
|
33124
|
-
purpose
|
|
33402
|
+
purpose,
|
|
33403
|
+
diskBytes: sessionDiskBytes(id),
|
|
33404
|
+
quiet: quiet.has(id)
|
|
33125
33405
|
};
|
|
33126
33406
|
};
|
|
33127
33407
|
for (const summary of listTranscripts()) {
|
|
@@ -33144,7 +33424,9 @@ var init_sessions = __esm({
|
|
|
33144
33424
|
init_db();
|
|
33145
33425
|
init_events();
|
|
33146
33426
|
init_paths();
|
|
33427
|
+
init_channels();
|
|
33147
33428
|
init_prompts();
|
|
33429
|
+
init_sessionFiles();
|
|
33148
33430
|
init_terminals();
|
|
33149
33431
|
init_toolset();
|
|
33150
33432
|
init_purposes();
|
|
@@ -33421,6 +33703,7 @@ init_claudeCode();
|
|
|
33421
33703
|
init_context();
|
|
33422
33704
|
init_db();
|
|
33423
33705
|
import fs14 from "node:fs";
|
|
33706
|
+
import net from "node:net";
|
|
33424
33707
|
import path15 from "node:path";
|
|
33425
33708
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
33426
33709
|
|
|
@@ -33865,16 +34148,16 @@ import os5 from "node:os";
|
|
|
33865
34148
|
import path14 from "node:path";
|
|
33866
34149
|
|
|
33867
34150
|
// server/src/fileIcons.ts
|
|
33868
|
-
import
|
|
34151
|
+
import fs12 from "node:fs";
|
|
33869
34152
|
import nodeModule from "node:module";
|
|
33870
|
-
import
|
|
34153
|
+
import path13 from "node:path";
|
|
33871
34154
|
var resolveFrom = nodeModule.createRequire(import.meta.url);
|
|
33872
|
-
var root =
|
|
33873
|
-
var iconsDir =
|
|
34155
|
+
var root = path13.dirname(resolveFrom.resolve("material-icon-theme/package.json"));
|
|
34156
|
+
var iconsDir = path13.join(root, "icons");
|
|
33874
34157
|
var manifest = null;
|
|
33875
34158
|
function iconManifest() {
|
|
33876
34159
|
if (!manifest) {
|
|
33877
|
-
const full = JSON.parse(
|
|
34160
|
+
const full = JSON.parse(fs12.readFileSync(path13.join(root, "dist", "material-icons.json"), "utf8"));
|
|
33878
34161
|
manifest = {
|
|
33879
34162
|
file: full.file,
|
|
33880
34163
|
folder: full.folder,
|
|
@@ -33889,8 +34172,8 @@ function iconManifest() {
|
|
|
33889
34172
|
}
|
|
33890
34173
|
function iconFile(name) {
|
|
33891
34174
|
if (!/^[\w.-]+$/.test(name)) return null;
|
|
33892
|
-
const file =
|
|
33893
|
-
return
|
|
34175
|
+
const file = path13.join(iconsDir, `${name}.svg`);
|
|
34176
|
+
return fs12.existsSync(file) ? file : null;
|
|
33894
34177
|
}
|
|
33895
34178
|
|
|
33896
34179
|
// server/src/routes.ts
|
|
@@ -34039,96 +34322,7 @@ init_prompts();
|
|
|
34039
34322
|
init_sessions();
|
|
34040
34323
|
init_terminals();
|
|
34041
34324
|
init_purposes();
|
|
34042
|
-
|
|
34043
|
-
// server/src/sessionFiles.ts
|
|
34044
|
-
init_db();
|
|
34045
|
-
init_events();
|
|
34046
|
-
init_paths();
|
|
34047
|
-
init_terminals();
|
|
34048
|
-
init_transcripts();
|
|
34049
|
-
import fs12 from "node:fs";
|
|
34050
|
-
import os4 from "node:os";
|
|
34051
|
-
import path13 from "node:path";
|
|
34052
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
34053
|
-
function checkId(id) {
|
|
34054
|
-
if (!UUID.test(id)) throw new Error("Not a session id.");
|
|
34055
|
-
}
|
|
34056
|
-
function transcriptFile(id) {
|
|
34057
|
-
return path13.join(transcriptsDir(), `${id}.jsonl`);
|
|
34058
|
-
}
|
|
34059
|
-
function setSessionTitle(id, title) {
|
|
34060
|
-
checkId(id);
|
|
34061
|
-
const name = title.replace(/\s+/g, " ").trim();
|
|
34062
|
-
if (!name) throw new Error("The name is empty.");
|
|
34063
|
-
if (name.length > 200) throw new Error("Keep the name under 200 characters.");
|
|
34064
|
-
const file = transcriptFile(id);
|
|
34065
|
-
if (!fs12.existsSync(file)) throw new Error("This session has no transcript yet; give it a moment to start.");
|
|
34066
|
-
const size = fs12.statSync(file).size;
|
|
34067
|
-
let lead = "";
|
|
34068
|
-
if (size > 0) {
|
|
34069
|
-
const fd = fs12.openSync(file, "r");
|
|
34070
|
-
const last = Buffer.alloc(1);
|
|
34071
|
-
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
34072
|
-
fs12.closeSync(fd);
|
|
34073
|
-
if (last.toString() !== "\n") lead = "\n";
|
|
34074
|
-
}
|
|
34075
|
-
fs12.appendFileSync(file, `${lead}${JSON.stringify({ type: "custom-title", customTitle: name, sessionId: id })}
|
|
34076
|
-
`, "utf8");
|
|
34077
|
-
broadcast("sessions", { sessionId: id });
|
|
34078
|
-
}
|
|
34079
|
-
function sizeOf(target2) {
|
|
34080
|
-
const stat = fs12.statSync(target2);
|
|
34081
|
-
if (!stat.isDirectory()) return stat.size;
|
|
34082
|
-
return fs12.readdirSync(target2).reduce((total, name) => total + sizeOf(path13.join(target2, name)), 0);
|
|
34083
|
-
}
|
|
34084
|
-
function tempDirOf(id) {
|
|
34085
|
-
const base = process.env.CLAUDE_CODE_TMPDIR ?? os4.tmpdir();
|
|
34086
|
-
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
34087
|
-
const roots2 = [path13.join(base, "claude"), ...uid !== null ? [path13.join(base, `claude-${uid}`)] : []];
|
|
34088
|
-
const candidates2 = roots2.map((root2) => path13.join(root2, projectSlug(projectRoot()), id));
|
|
34089
|
-
return candidates2.find((candidate) => fs12.existsSync(candidate)) ?? candidates2[0];
|
|
34090
|
-
}
|
|
34091
|
-
function deletionPlan(id) {
|
|
34092
|
-
checkId(id);
|
|
34093
|
-
const candidates2 = [
|
|
34094
|
-
[transcriptFile(id), "transcript"],
|
|
34095
|
-
[path13.join(transcriptsDir(), id), "subagent transcripts and tool output"],
|
|
34096
|
-
[path13.join(claudeHome, "file-history", id), "file snapshots used to rewind"],
|
|
34097
|
-
[path13.join(claudeHome, "session-env", id), "session environment"]
|
|
34098
|
-
];
|
|
34099
|
-
const files = candidates2.filter(([target2]) => fs12.existsSync(target2)).map(([target2, what]) => ({ path: target2, bytes: sizeOf(target2), what }));
|
|
34100
|
-
const temp = tempDirOf(id);
|
|
34101
|
-
return {
|
|
34102
|
-
sessionId: id,
|
|
34103
|
-
files,
|
|
34104
|
-
totalBytes: files.reduce((total, item) => total + item.bytes, 0),
|
|
34105
|
-
temp: fs12.existsSync(temp) ? { path: temp, bytes: sizeOf(temp) } : null
|
|
34106
|
-
};
|
|
34107
|
-
}
|
|
34108
|
-
async function deleteSession(id, options = {}) {
|
|
34109
|
-
checkId(id);
|
|
34110
|
-
const terminal = terminalForSession(id);
|
|
34111
|
-
if (terminal) {
|
|
34112
|
-
killTerminal(terminal);
|
|
34113
|
-
await waitForExit(terminal);
|
|
34114
|
-
if (terminalForSession(id)) throw new Error("The session did not stop in time; nothing was deleted. Try again.");
|
|
34115
|
-
}
|
|
34116
|
-
const plan = deletionPlan(id);
|
|
34117
|
-
for (const item of plan.files) fs12.rmSync(item.path, { recursive: true, force: true });
|
|
34118
|
-
if (options.clearTemp !== false && plan.temp) fs12.rmSync(plan.temp.path, { recursive: true, force: true });
|
|
34119
|
-
db.transaction(() => {
|
|
34120
|
-
for (const table of ["interactions", "mcp_suggestions", "channel_refs", "channel_threads", "sessions"]) {
|
|
34121
|
-
db.prepare(`DELETE FROM ${table} WHERE ${table === "sessions" ? "id" : "session_id"} = ?`).run(id);
|
|
34122
|
-
}
|
|
34123
|
-
})();
|
|
34124
|
-
forgetTranscript(id);
|
|
34125
|
-
broadcast("sessions", {});
|
|
34126
|
-
broadcast("interactions", {});
|
|
34127
|
-
broadcast("mcp", {});
|
|
34128
|
-
return plan;
|
|
34129
|
-
}
|
|
34130
|
-
|
|
34131
|
-
// server/src/routes.ts
|
|
34325
|
+
init_sessionFiles();
|
|
34132
34326
|
init_toolset();
|
|
34133
34327
|
init_transcripts();
|
|
34134
34328
|
var api = import_express2.default.Router();
|
|
@@ -34364,6 +34558,16 @@ api.post(
|
|
|
34364
34558
|
"/interactions/:id/answer",
|
|
34365
34559
|
wrap2((req) => answerInteraction(req.params.id, String(req.body?.answer ?? ""), "dashboard"))
|
|
34366
34560
|
);
|
|
34561
|
+
api.post("/interactions/:id/dismiss", wrap2((req) => dismissInteraction(req.params.id)));
|
|
34562
|
+
api.post(
|
|
34563
|
+
"/sessions/:id/dismiss-waiting",
|
|
34564
|
+
wrap2((req) => {
|
|
34565
|
+
const open = listInteractions({ sessionId: req.params.id, status: "open" });
|
|
34566
|
+
for (const item of open) dismissInteraction(item.id);
|
|
34567
|
+
return { dismissed: open.length };
|
|
34568
|
+
})
|
|
34569
|
+
);
|
|
34570
|
+
api.put("/sessions/:id/quiet", wrap2((req) => setSessionQuiet(req.params.id, Boolean(req.body?.quiet))));
|
|
34367
34571
|
api.get("/inventory", wrap2(() => readInventory()));
|
|
34368
34572
|
api.get(
|
|
34369
34573
|
"/stats",
|
|
@@ -34437,7 +34641,9 @@ api.put(
|
|
|
34437
34641
|
return { path: file, content };
|
|
34438
34642
|
})
|
|
34439
34643
|
);
|
|
34440
|
-
api.get("/channels", wrap2(() => ({ kinds: CHANNEL_KINDS, interactionKinds: INTERACTION_KINDS, channels: listChannels() })));
|
|
34644
|
+
api.get("/channels", wrap2(() => ({ kinds: CHANNEL_KINDS, interactionKinds: INTERACTION_KINDS, channels: listChannels(), paused: notificationsPaused(), notifyKinds: notifyKinds() })));
|
|
34645
|
+
api.put("/notifications/kinds", wrap2((req) => ({ notifyKinds: setNotifyKinds(Array.isArray(req.body?.kinds) ? req.body.kinds.map(String) : []) })));
|
|
34646
|
+
api.put("/notifications/paused", wrap2((req) => setNotificationsPaused(Boolean(req.body?.paused))));
|
|
34441
34647
|
api.post("/channels", wrap2((req) => saveChannel(req.body ?? {})));
|
|
34442
34648
|
api.put("/channels/:id", wrap2((req) => saveChannel({ ...req.body ?? {}, id: req.params.id })));
|
|
34443
34649
|
api.delete("/channels/:id", wrap2((req) => deleteChannel(req.params.id)));
|
|
@@ -34566,6 +34772,21 @@ async function otherHubPort() {
|
|
|
34566
34772
|
}
|
|
34567
34773
|
return null;
|
|
34568
34774
|
}
|
|
34775
|
+
var listening = (port2) => new Promise((resolve) => {
|
|
34776
|
+
const socket = net.connect(port2, "127.0.0.1");
|
|
34777
|
+
const done = (answer) => {
|
|
34778
|
+
socket.destroy();
|
|
34779
|
+
resolve(answer);
|
|
34780
|
+
};
|
|
34781
|
+
socket.setTimeout(2e3);
|
|
34782
|
+
socket.once("connect", () => done(true));
|
|
34783
|
+
socket.once("timeout", () => done(true));
|
|
34784
|
+
socket.once("error", () => done(false));
|
|
34785
|
+
});
|
|
34786
|
+
var leftover = readLock();
|
|
34787
|
+
if (leftover?.port && alive(leftover.pid) && !await listening(leftover.port) && readLock()?.pid === leftover.pid) {
|
|
34788
|
+
fs14.rmSync(lockFile, { force: true });
|
|
34789
|
+
}
|
|
34569
34790
|
if (!takeLock() && !await otherHubPort().then((found) => found === null && takeLock())) {
|
|
34570
34791
|
const otherPort = await otherHubPort();
|
|
34571
34792
|
if (!otherPort) {
|