hypercore-cli 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/dist/api-XGC7D5AW.js +162 -0
  4. package/dist/auth-DNQWYQKT.js +21 -0
  5. package/dist/background-2EGCAAQH.js +14 -0
  6. package/dist/backlog-Q2NZCLNY.js +24 -0
  7. package/dist/chunk-2CMSCWQW.js +162 -0
  8. package/dist/chunk-2LJ2DVEB.js +167 -0
  9. package/dist/chunk-3RPFCQKJ.js +288 -0
  10. package/dist/chunk-43OLRXM5.js +263 -0
  11. package/dist/chunk-4DVYJAJL.js +57 -0
  12. package/dist/chunk-6OL3GA3P.js +173 -0
  13. package/dist/chunk-AUHU7ALH.js +2023 -0
  14. package/dist/chunk-B6A2AKLN.js +139 -0
  15. package/dist/chunk-BE46C7JW.js +46 -0
  16. package/dist/chunk-CUVAUOXL.js +58 -0
  17. package/dist/chunk-GH7E2OJE.js +223 -0
  18. package/dist/chunk-GOOTEPBK.js +271 -0
  19. package/dist/chunk-GPPMJYSM.js +133 -0
  20. package/dist/chunk-GU2FZQ6A.js +69 -0
  21. package/dist/chunk-IOPKN5GD.js +190 -0
  22. package/dist/chunk-IXOIOGR5.js +1505 -0
  23. package/dist/chunk-KRPOPWGA.js +251 -0
  24. package/dist/chunk-MGLJ53QN.js +219 -0
  25. package/dist/chunk-MV4TTRYX.js +533 -0
  26. package/dist/chunk-OPZYEVYR.js +150 -0
  27. package/dist/chunk-QTSLP47C.js +166 -0
  28. package/dist/chunk-R3GPQC7I.js +393 -0
  29. package/dist/chunk-RKB2JOV2.js +43 -0
  30. package/dist/chunk-RNG3K465.js +80 -0
  31. package/dist/chunk-TGTYKBGC.js +86 -0
  32. package/dist/chunk-U5SGAIMM.js +681 -0
  33. package/dist/chunk-V5UHPPSY.js +140 -0
  34. package/dist/chunk-WHLVZCQY.js +245 -0
  35. package/dist/chunk-XDRCBMZZ.js +66 -0
  36. package/dist/chunk-XOS6HPEF.js +134 -0
  37. package/dist/chunk-ZSBHUGWR.js +262 -0
  38. package/dist/claude-NSQ442XD.js +12 -0
  39. package/dist/commands-CK3WFAGI.js +128 -0
  40. package/dist/commands-U63OEO5J.js +1044 -0
  41. package/dist/commands-ZE6GD3WC.js +232 -0
  42. package/dist/config-4EW42BSF.js +8 -0
  43. package/dist/config-loader-SXO674TF.js +24 -0
  44. package/dist/diagnose-AFW3ZTZ4.js +12 -0
  45. package/dist/display-IIUBEYWN.js +58 -0
  46. package/dist/extractor-QV53W2YJ.js +129 -0
  47. package/dist/history-WMSCHERZ.js +180 -0
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.js +406 -0
  50. package/dist/instance-registry-YSIJXSO7.js +15 -0
  51. package/dist/keybindings-JAAMLH3G.js +15 -0
  52. package/dist/loader-WHNTZTLP.js +58 -0
  53. package/dist/network-MM6YWPGO.js +279 -0
  54. package/dist/notify-HPTALZDC.js +14 -0
  55. package/dist/openai-compat-UQWJXBEK.js +12 -0
  56. package/dist/permissions-JUKXMNDH.js +10 -0
  57. package/dist/prompt-QV45TXRL.js +166 -0
  58. package/dist/quality-ST7PPNFR.js +16 -0
  59. package/dist/repl-RT3AHL7M.js +3375 -0
  60. package/dist/roadmap-5OBEKROY.js +17 -0
  61. package/dist/server-PORT7OEG.js +57 -0
  62. package/dist/session-4VUNDWLH.js +21 -0
  63. package/dist/skills-V4A35XKG.js +175 -0
  64. package/dist/store-Y4LU5QTO.js +25 -0
  65. package/dist/team-HO7Z4SIM.js +385 -0
  66. package/dist/telemetry-6R4EIE6O.js +30 -0
  67. package/dist/test-runner-ZQH5Y6OJ.js +619 -0
  68. package/dist/theme-3SYJ3UQA.js +14 -0
  69. package/dist/upgrade-7TGI3SXO.js +83 -0
  70. package/dist/verify-JUDKTPKZ.js +14 -0
  71. package/dist/web/static/app.js +562 -0
  72. package/dist/web/static/index.html +132 -0
  73. package/dist/web/static/mirror.css +1001 -0
  74. package/dist/web/static/mirror.html +184 -0
  75. package/dist/web/static/mirror.js +1125 -0
  76. package/dist/web/static/onboard.css +302 -0
  77. package/dist/web/static/onboard.html +140 -0
  78. package/dist/web/static/onboard.js +260 -0
  79. package/dist/web/static/style.css +602 -0
  80. package/dist/web/static/workspace.css +1568 -0
  81. package/dist/web/static/workspace.html +408 -0
  82. package/dist/web/static/workspace.js +1683 -0
  83. package/dist/web-Z5HSCQHW.js +39 -0
  84. package/package.json +67 -0
@@ -0,0 +1,288 @@
1
+ import {
2
+ getEventsSummary,
3
+ readEvents
4
+ } from "./chunk-2CMSCWQW.js";
5
+ import {
6
+ addItem,
7
+ listItems
8
+ } from "./chunk-MGLJ53QN.js";
9
+
10
+ // src/admin/diagnose.ts
11
+ async function runRuleDiagnosis(days = 7) {
12
+ const findings = [];
13
+ const events = await readEvents(days);
14
+ const summary = await getEventsSummary(days);
15
+ if (events.length === 0) {
16
+ return [{
17
+ severity: "warning",
18
+ title: "\u65E0\u4E8B\u4EF6\u6570\u636E",
19
+ detail: `\u8FD1 ${days} \u5929\u6CA1\u6709\u91C7\u96C6\u5230\u4EFB\u4F55\u4E8B\u4EF6`,
20
+ evidence: "\u4E8B\u4EF6\u6587\u4EF6\u4E3A\u7A7A\u6216\u4E0D\u5B58\u5728",
21
+ suggestion: "\u786E\u8BA4 telemetry \u5DF2\u542F\u7528\uFF0C\u6B63\u5E38\u4F7F\u7528 Hypercore \u540E\u4F1A\u81EA\u52A8\u91C7\u96C6",
22
+ suggestedType: "bugfix",
23
+ suggestedPriority: "A",
24
+ suggestedWuxing: "\u6728"
25
+ }];
26
+ }
27
+ for (const [cmd, stats] of Object.entries(summary.cmdCounts)) {
28
+ const failRate = stats.total >= 3 ? stats.fail / stats.total : 0;
29
+ if (failRate > 0.1) {
30
+ const failEvents = events.filter(
31
+ (e) => e.event === "cmd_exec" && e.cmd === cmd && !e.ok
32
+ );
33
+ const errorMessages = failEvents.map((e) => String(e.err || "")).filter(Boolean);
34
+ const topError = mostCommon(errorMessages) || "\u672A\u77E5";
35
+ findings.push({
36
+ severity: "high",
37
+ title: `${cmd} \u5931\u8D25\u7387 ${(failRate * 100).toFixed(0)}%`,
38
+ detail: `\u8FD1 ${days} \u5929\u5185 ${cmd} \u6267\u884C ${stats.total} \u6B21\uFF0C\u5931\u8D25 ${stats.fail} \u6B21`,
39
+ evidence: `\u5E38\u89C1\u9519\u8BEF: ${topError} (${stats.fail}/${stats.total})`,
40
+ suggestion: `\u6392\u67E5 ${cmd} \u547D\u4EE4\u7684\u9519\u8BEF\u5904\u7406\u903B\u8F91`,
41
+ suggestedType: "bugfix",
42
+ suggestedPriority: failRate > 0.2 ? "S" : "A",
43
+ suggestedWuxing: "\u6728"
44
+ });
45
+ }
46
+ }
47
+ for (const err of summary.errors) {
48
+ if (err.count >= 3) {
49
+ findings.push({
50
+ severity: "high",
51
+ title: `${err.type} \u9519\u8BEF\u9891\u53D1 (${err.count}\u6B21)`,
52
+ detail: `\u8FD1 ${days} \u5929\u5185 ${err.type} \u7C7B\u578B\u9519\u8BEF\u51FA\u73B0 ${err.count} \u6B21`,
53
+ evidence: `\u9519\u8BEF\u7C7B\u578B\u7EDF\u8BA1: ${err.type} = ${err.count}`,
54
+ suggestion: "\u68C0\u67E5\u9519\u8BEF\u6839\u56E0\u5E76\u4FEE\u590D",
55
+ suggestedType: "bugfix",
56
+ suggestedPriority: err.count >= 5 ? "S" : "A",
57
+ suggestedWuxing: "\u6728"
58
+ });
59
+ }
60
+ }
61
+ const lineEvents = events.filter((e) => e.event === "line_run");
62
+ const lineStats = {};
63
+ for (const e of lineEvents) {
64
+ const name = String(e.line || "unknown");
65
+ if (!lineStats[name]) lineStats[name] = { durations: [], fails: 0, total: 0 };
66
+ lineStats[name].total++;
67
+ if (e.duration_ms) lineStats[name].durations.push(e.duration_ms);
68
+ if (!e.ok) lineStats[name].fails++;
69
+ }
70
+ for (const [name, stats] of Object.entries(lineStats)) {
71
+ if (stats.durations.length >= 3) {
72
+ const avg = stats.durations.reduce((a, b) => a + b, 0) / stats.durations.length;
73
+ const p95 = stats.durations.sort((a, b) => a - b)[Math.floor(stats.durations.length * 0.95)];
74
+ if (p95 > 3e4) {
75
+ findings.push({
76
+ severity: "warning",
77
+ title: `\u751F\u4EA7\u7EBF "${name}" \u8017\u65F6\u504F\u9AD8`,
78
+ detail: `\u5E73\u5747 ${(avg / 1e3).toFixed(1)}s, p95 ${(p95 / 1e3).toFixed(1)}s (${stats.total}\u6B21\u8FD0\u884C)`,
79
+ evidence: `\u8017\u65F6\u6570\u636E: avg=${(avg / 1e3).toFixed(1)}s, p95=${(p95 / 1e3).toFixed(1)}s`,
80
+ suggestion: "\u68C0\u67E5 prompt \u957F\u5EA6\u6216\u8003\u8651\u5207\u6362\u66F4\u5FEB\u7684\u6A21\u578B",
81
+ suggestedType: "improvement",
82
+ suggestedPriority: "A",
83
+ suggestedWuxing: "\u6728"
84
+ });
85
+ }
86
+ }
87
+ }
88
+ const allCmds = events.filter((e) => e.event === "cmd_exec").map((e) => String(e.cmd));
89
+ const knownCmds = ["/model", "/team", "/memory", "/network", "/admin", "/run", "/git", "/session"];
90
+ for (const cmd of knownCmds) {
91
+ if (!allCmds.includes(cmd) && events.length > 20) {
92
+ findings.push({
93
+ severity: "opportunity",
94
+ title: `${cmd} \u8FD1 ${days} \u5929\u672A\u4F7F\u7528`,
95
+ detail: `\u5728 ${events.length} \u6761\u4E8B\u4EF6\u4E2D\u6CA1\u6709 ${cmd} \u7684\u4F7F\u7528\u8BB0\u5F55`,
96
+ evidence: `${cmd} \u8C03\u7528\u6B21\u6570: 0`,
97
+ suggestion: "\u8BC4\u4F30\u8BE5\u529F\u80FD\u662F\u5426\u9700\u8981\u6539\u8FDB\u5165\u53E3\u6216\u8003\u8651\u4E0B\u7EBF",
98
+ suggestedType: "idea",
99
+ suggestedPriority: "C",
100
+ suggestedWuxing: "\u91D1"
101
+ });
102
+ }
103
+ }
104
+ const sessionEnds = events.filter((e) => e.event === "session_end");
105
+ if (sessionEnds.length >= 4) {
106
+ const mid = Math.floor(sessionEnds.length / 2);
107
+ const firstHalf = sessionEnds.slice(0, mid);
108
+ const secondHalf = sessionEnds.slice(mid);
109
+ const avgFirst = firstHalf.reduce((s, e) => s + (e.rounds || 0), 0) / firstHalf.length;
110
+ const avgSecond = secondHalf.reduce((s, e) => s + (e.rounds || 0), 0) / secondHalf.length;
111
+ if (avgFirst > 0 && avgSecond / avgFirst < 0.6) {
112
+ findings.push({
113
+ severity: "warning",
114
+ title: `\u4F1A\u8BDD\u6DF1\u5EA6\u4E0B\u964D (${avgFirst.toFixed(1)} \u2192 ${avgSecond.toFixed(1)} \u8F6E)`,
115
+ detail: `\u524D\u534A\u671F\u5E73\u5747 ${avgFirst.toFixed(1)} \u8F6E\uFF0C\u540E\u534A\u671F ${avgSecond.toFixed(1)} \u8F6E`,
116
+ evidence: `\u4E0B\u964D\u5E45\u5EA6: ${((1 - avgSecond / avgFirst) * 100).toFixed(0)}%`,
117
+ suggestion: "\u5206\u6790\u7528\u6237\u662F\u5426\u63D0\u524D\u653E\u5F03\u8FD8\u662F\u9700\u6C42\u53D8\u7B80\u77ED",
118
+ suggestedType: "idea",
119
+ suggestedPriority: "B",
120
+ suggestedWuxing: "\u6C34"
121
+ });
122
+ }
123
+ }
124
+ return findings;
125
+ }
126
+ function buildDiagnosePrompt(summary, ruleFindings) {
127
+ const parts = [];
128
+ parts.push(`\u4F60\u662F\u8D85\u534F\u4F53 (Hypercore) \u9879\u76EE\u7684\u5185\u89C2\u8BCA\u65AD\u5F15\u64CE\u3002\u4EE5\u4E0B\u662F\u8FD1 ${summary.period.days} \u5929\u7684\u4F7F\u7528\u6570\u636E\u6458\u8981\u3002`);
129
+ parts.push(`\u8BF7\u5206\u6790\u5E76\u8865\u5145\u89C4\u5219\u5F15\u64CE\u53EF\u80FD\u9057\u6F0F\u7684\u6D1E\u5BDF\u3002
130
+ `);
131
+ parts.push(`## \u57FA\u7840\u7EDF\u8BA1`);
132
+ parts.push(`- \u4E8B\u4EF6\u603B\u6570: ${summary.totalEvents}`);
133
+ parts.push(`- \u4F1A\u8BDD\u6570: ${summary.totalSessions}`);
134
+ parts.push(`- \u5E73\u5747\u4F1A\u8BDD\u8F6E\u6B21: ${summary.avgSessionRounds}`);
135
+ parts.push(`- \u7EDF\u8BA1\u5468\u671F: ${summary.period.from} \u2192 ${summary.period.to}
136
+ `);
137
+ if (Object.keys(summary.cmdCounts).length > 0) {
138
+ parts.push(`## \u547D\u4EE4\u4F7F\u7528\u7EDF\u8BA1`);
139
+ for (const [cmd, stats] of Object.entries(summary.cmdCounts)) {
140
+ const failPart = stats.fail > 0 ? ` (\u5931\u8D25${stats.fail}\u6B21, \u7387${(stats.fail / stats.total * 100).toFixed(0)}%)` : "";
141
+ parts.push(`- ${cmd}: ${stats.total}\u6B21${failPart}`);
142
+ }
143
+ parts.push("");
144
+ }
145
+ if (summary.errors.length > 0) {
146
+ parts.push(`## \u9519\u8BEF\u7EDF\u8BA1`);
147
+ for (const e of summary.errors) {
148
+ parts.push(`- ${e.type}: ${e.count}\u6B21`);
149
+ }
150
+ parts.push("");
151
+ }
152
+ if (ruleFindings.length > 0) {
153
+ parts.push(`## \u89C4\u5219\u5F15\u64CE\u5DF2\u53D1\u73B0`);
154
+ for (const f of ruleFindings) {
155
+ parts.push(`- [${f.severity}] ${f.title}: ${f.detail}`);
156
+ }
157
+ parts.push("");
158
+ }
159
+ parts.push(`## \u4F60\u7684\u4EFB\u52A1`);
160
+ parts.push(`\u57FA\u4E8E\u4EE5\u4E0A\u6570\u636E\uFF0C\u8F93\u51FA\uFF1A`);
161
+ parts.push(`1. \u89C4\u5219\u5F15\u64CE\u53EF\u80FD\u9057\u6F0F\u7684\u95EE\u9898\u6216\u6A21\u5F0F`);
162
+ parts.push(`2. \u4F7F\u7528\u8D8B\u52BF\u7684\u6DF1\u5C42\u89E3\u8BFB`);
163
+ parts.push(`3. \u4EA7\u54C1\u6539\u8FDB\u673A\u4F1A`);
164
+ parts.push(`
165
+ \u6BCF\u6761\u53D1\u73B0\u8BF7\u7528\u4EE5\u4E0B\u683C\u5F0F\uFF1A`);
166
+ parts.push(`### [severity: high/warning/opportunity] \u6807\u9898`);
167
+ parts.push(`- \u8BE6\u60C5: ...`);
168
+ parts.push(`- \u8BC1\u636E: ...`);
169
+ parts.push(`- \u5EFA\u8BAE: ...`);
170
+ parts.push(`- \u7C7B\u578B: bugfix/feature/improvement/idea`);
171
+ parts.push(`- \u4F18\u5148\u7EA7: S/A/B/C`);
172
+ parts.push(`- \u4E94\u884C: \u6728/\u706B/\u6C34/\u91D1/\u571F`);
173
+ parts.push(`
174
+ \u5982\u679C\u6CA1\u6709\u989D\u5916\u53D1\u73B0\uFF0C\u76F4\u63A5\u8BF4"\u89C4\u5219\u5F15\u64CE\u5DF2\u8986\u76D6\u4E3B\u8981\u95EE\u9898"\u3002`);
175
+ return parts.join("\n");
176
+ }
177
+ function parseAIFindings(text) {
178
+ const findings = [];
179
+ const blocks = text.split(/###\s+\[/).filter(Boolean);
180
+ for (const block of blocks) {
181
+ const severityMatch = block.match(/^(high|warning|opportunity)\]\s*(.+)/);
182
+ if (!severityMatch) continue;
183
+ const severity = severityMatch[1];
184
+ const title = severityMatch[2].trim();
185
+ const detail = extractField(block, "\u8BE6\u60C5") || "";
186
+ const evidence = extractField(block, "\u8BC1\u636E") || "";
187
+ const suggestion = extractField(block, "\u5EFA\u8BAE") || "";
188
+ const typeStr = extractField(block, "\u7C7B\u578B") || "idea";
189
+ const priorityStr = extractField(block, "\u4F18\u5148\u7EA7") || "B";
190
+ const wuxingStr = extractField(block, "\u4E94\u884C") || "\u6728";
191
+ findings.push({
192
+ severity,
193
+ title,
194
+ detail,
195
+ evidence,
196
+ suggestion,
197
+ suggestedType: ["bugfix", "feature", "improvement", "idea"].includes(typeStr) ? typeStr : "idea",
198
+ suggestedPriority: ["S", "A", "B", "C"].includes(priorityStr) ? priorityStr : "B",
199
+ suggestedWuxing: ["\u6728", "\u706B", "\u6C34", "\u91D1", "\u571F"].includes(wuxingStr) ? wuxingStr : "\u6728"
200
+ });
201
+ }
202
+ return findings;
203
+ }
204
+ function extractField(block, fieldName) {
205
+ const regex = new RegExp(`-\\s*${fieldName}[\uFF1A:]\\s*(.+)`, "i");
206
+ const match = block.match(regex);
207
+ return match ? match[1].trim() : "";
208
+ }
209
+ async function runAIDiagnosis(ctx, days = 7) {
210
+ const summary = await getEventsSummary(days);
211
+ const ruleFindings = await runRuleDiagnosis(days);
212
+ const prompt = buildDiagnosePrompt(summary, ruleFindings);
213
+ let aiRawText = "";
214
+ const aiFindings = [];
215
+ try {
216
+ if (ctx.config.modelConfig.sdkType === "openai") {
217
+ const { streamOpenAIChat } = await import("./openai-compat-UQWJXBEK.js");
218
+ const OpenAI = (await import("openai")).default;
219
+ const client = ctx.getClient();
220
+ const result = await streamOpenAIChat(client, [
221
+ { role: "system", content: "\u4F60\u662F\u8D85\u534F\u4F53\u9879\u76EE\u7684\u5185\u89C2\u8BCA\u65AD\u5F15\u64CE\u3002\u5206\u6790\u6570\u636E\uFF0C\u8F93\u51FA\u7CBE\u51C6\u8BCA\u65AD\u3002\u7528\u4E2D\u6587\u56DE\u590D\u3002" },
222
+ { role: "user", content: prompt }
223
+ ], {
224
+ model: ctx.config.modelConfig.model,
225
+ tools: [],
226
+ onChunk: (text) => {
227
+ process.stdout.write(text);
228
+ },
229
+ onToolCall: () => {
230
+ }
231
+ });
232
+ aiRawText = result.content.trim();
233
+ } else {
234
+ const { streamCallLLM } = await import("./claude-NSQ442XD.js");
235
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
236
+ const client = ctx.getClient();
237
+ const result = await streamCallLLM(client, {
238
+ systemPrompt: "\u4F60\u662F\u8D85\u534F\u4F53\u9879\u76EE\u7684\u5185\u89C2\u8BCA\u65AD\u5F15\u64CE\u3002\u5206\u6790\u6570\u636E\uFF0C\u8F93\u51FA\u7CBE\u51C6\u8BCA\u65AD\u3002\u7528\u4E2D\u6587\u56DE\u590D\u3002",
239
+ userPrompt: prompt,
240
+ history: [],
241
+ tools: [],
242
+ model: ctx.config.modelConfig.model,
243
+ onText: (text) => {
244
+ process.stdout.write(text);
245
+ },
246
+ onToolCall: () => {
247
+ }
248
+ });
249
+ aiRawText = result.output.trim();
250
+ }
251
+ aiFindings.push(...parseAIFindings(aiRawText));
252
+ } catch (err) {
253
+ aiRawText = `AI \u8BCA\u65AD\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`;
254
+ }
255
+ return { ruleFindings, aiFindings, aiRawText };
256
+ }
257
+ async function findingsToBacklog(findings, targetVersion) {
258
+ const existing = await listItems();
259
+ const existingTitles = new Set(existing.map((i) => i.title.toLowerCase()));
260
+ const createdIds = [];
261
+ for (const f of findings) {
262
+ if (existingTitles.has(f.title.toLowerCase())) continue;
263
+ const item = await addItem({
264
+ title: f.title,
265
+ description: f.detail,
266
+ type: f.suggestedType,
267
+ wuxing: f.suggestedWuxing,
268
+ priority: f.suggestedPriority,
269
+ targetVersion,
270
+ source: "auto-diagnose",
271
+ evidence: f.evidence
272
+ });
273
+ createdIds.push(item.id);
274
+ }
275
+ return createdIds;
276
+ }
277
+ function mostCommon(arr) {
278
+ if (arr.length === 0) return void 0;
279
+ const counts = {};
280
+ for (const s of arr) counts[s] = (counts[s] || 0) + 1;
281
+ return Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0];
282
+ }
283
+
284
+ export {
285
+ runRuleDiagnosis,
286
+ runAIDiagnosis,
287
+ findingsToBacklog
288
+ };
@@ -0,0 +1,263 @@
1
+ import {
2
+ hookManager
3
+ } from "./chunk-B6A2AKLN.js";
4
+
5
+ // src/llm/claude.ts
6
+ import Anthropic from "@anthropic-ai/sdk";
7
+ function createLLMClient(config) {
8
+ const options = {
9
+ apiKey: config.apiKey
10
+ };
11
+ if (config.baseURL) {
12
+ options.baseURL = config.baseURL;
13
+ }
14
+ return new Anthropic(options);
15
+ }
16
+ function buildMessages(history, userPrompt) {
17
+ const messages = [];
18
+ if (history && history.length > 0) {
19
+ for (const msg of history) {
20
+ messages.push({
21
+ role: msg.role,
22
+ content: msg.content
23
+ });
24
+ }
25
+ }
26
+ messages.push({ role: "user", content: userPrompt });
27
+ return messages;
28
+ }
29
+ async function triggerToolHook(event, context) {
30
+ try {
31
+ return await hookManager.trigger(event, context);
32
+ } catch {
33
+ return { intercepted: false };
34
+ }
35
+ }
36
+ async function callLLM(client, options) {
37
+ const {
38
+ systemPrompt,
39
+ userPrompt,
40
+ tools,
41
+ model = "claude-sonnet-4-20250514",
42
+ maxTokens = 8192,
43
+ history,
44
+ onText,
45
+ onToolCall,
46
+ onThinking,
47
+ onToolCallDone
48
+ } = options;
49
+ const toolDefinitions = tools.map((t) => ({
50
+ name: t.definition.name,
51
+ description: t.definition.description,
52
+ input_schema: t.definition.input_schema
53
+ }));
54
+ const messages = buildMessages(history, userPrompt);
55
+ let totalOutput = "";
56
+ let currentRoundText = "";
57
+ const allToolCalls = [];
58
+ let totalInputTokens = 0;
59
+ let totalOutputTokens = 0;
60
+ let maxIterations = 10;
61
+ while (maxIterations-- > 0) {
62
+ currentRoundText = "";
63
+ const response = await client.messages.create({
64
+ model,
65
+ max_tokens: maxTokens,
66
+ system: systemPrompt,
67
+ messages,
68
+ tools: toolDefinitions.length > 0 ? toolDefinitions : void 0
69
+ });
70
+ totalInputTokens += response.usage.input_tokens;
71
+ totalOutputTokens += response.usage.output_tokens;
72
+ let hasToolUse = false;
73
+ let thinkingEmitted = false;
74
+ const toolResults = [];
75
+ for (const block of response.content) {
76
+ if (block.type === "text") {
77
+ totalOutput += block.text;
78
+ currentRoundText += block.text;
79
+ onText?.(block.text);
80
+ } else if (block.type === "tool_use") {
81
+ hasToolUse = true;
82
+ if (!thinkingEmitted && currentRoundText.trim()) {
83
+ onThinking?.(currentRoundText.trim());
84
+ thinkingEmitted = true;
85
+ }
86
+ const tool = tools.find((t) => t.definition.name === block.name);
87
+ const toolInput = block.input;
88
+ const toolInputText = JSON.stringify(toolInput);
89
+ onToolCall?.(block.name, toolInput);
90
+ let result;
91
+ const toolStart = Date.now();
92
+ const hookResult = await triggerToolHook("onToolCall", {
93
+ model,
94
+ toolName: block.name,
95
+ toolInput: toolInputText
96
+ });
97
+ if (hookResult.intercepted) {
98
+ result = `Error: Hook intercepted tool call - ${hookResult.reason || "blocked"}`;
99
+ } else if (tool) {
100
+ try {
101
+ result = await tool.handler(toolInput);
102
+ } catch (err) {
103
+ result = `Error: ${err instanceof Error ? err.message : String(err)}`;
104
+ }
105
+ } else {
106
+ result = `Error: Tool "${block.name}" not found`;
107
+ }
108
+ const toolDuration = Date.now() - toolStart;
109
+ onToolCallDone?.(block.name, toolDuration);
110
+ await triggerToolHook("onToolResult", {
111
+ model,
112
+ toolName: block.name,
113
+ toolInput: toolInputText,
114
+ toolResult: result
115
+ });
116
+ allToolCalls.push({
117
+ toolName: block.name,
118
+ input: toolInput,
119
+ output: result
120
+ });
121
+ toolResults.push({
122
+ type: "tool_result",
123
+ tool_use_id: block.id,
124
+ content: result
125
+ });
126
+ }
127
+ }
128
+ if (hasToolUse) {
129
+ messages.push({ role: "assistant", content: response.content });
130
+ messages.push({ role: "user", content: toolResults });
131
+ } else {
132
+ break;
133
+ }
134
+ if (response.stop_reason === "end_turn") break;
135
+ }
136
+ return {
137
+ output: totalOutput,
138
+ responseText: allToolCalls.length > 0 ? currentRoundText : totalOutput,
139
+ toolCalls: allToolCalls,
140
+ tokenUsage: {
141
+ inputTokens: totalInputTokens,
142
+ outputTokens: totalOutputTokens
143
+ }
144
+ };
145
+ }
146
+ async function streamCallLLM(client, options) {
147
+ const {
148
+ systemPrompt,
149
+ userPrompt,
150
+ tools,
151
+ model = "claude-sonnet-4-20250514",
152
+ maxTokens = 8192,
153
+ history,
154
+ onText,
155
+ onToolCall,
156
+ onThinking,
157
+ onToolCallDone
158
+ } = options;
159
+ const toolDefinitions = tools.map((t) => ({
160
+ name: t.definition.name,
161
+ description: t.definition.description,
162
+ input_schema: t.definition.input_schema
163
+ }));
164
+ const messages = buildMessages(history, userPrompt);
165
+ let totalOutput = "";
166
+ let currentRoundText = "";
167
+ const allToolCalls = [];
168
+ let totalInputTokens = 0;
169
+ let totalOutputTokens = 0;
170
+ let maxIterations = 10;
171
+ while (maxIterations-- > 0) {
172
+ currentRoundText = "";
173
+ const stream = client.messages.stream({
174
+ model,
175
+ max_tokens: maxTokens,
176
+ system: systemPrompt,
177
+ messages,
178
+ tools: toolDefinitions.length > 0 ? toolDefinitions : void 0
179
+ });
180
+ stream.on("text", (text) => {
181
+ totalOutput += text;
182
+ currentRoundText += text;
183
+ onText?.(text);
184
+ });
185
+ const response = await stream.finalMessage();
186
+ totalInputTokens += response.usage.input_tokens;
187
+ totalOutputTokens += response.usage.output_tokens;
188
+ let hasToolUse = false;
189
+ let thinkingEmitted = false;
190
+ const toolResults = [];
191
+ for (const block of response.content) {
192
+ if (block.type === "tool_use") {
193
+ hasToolUse = true;
194
+ if (!thinkingEmitted && currentRoundText.trim()) {
195
+ onThinking?.(currentRoundText.trim());
196
+ thinkingEmitted = true;
197
+ }
198
+ const toolInput = block.input;
199
+ const toolInputText = JSON.stringify(toolInput);
200
+ onToolCall?.(block.name, toolInput);
201
+ const tool = tools.find((t) => t.definition.name === block.name);
202
+ let result;
203
+ const toolStart = Date.now();
204
+ const hookResult = await triggerToolHook("onToolCall", {
205
+ model,
206
+ toolName: block.name,
207
+ toolInput: toolInputText
208
+ });
209
+ if (hookResult.intercepted) {
210
+ result = `Error: Hook intercepted tool call - ${hookResult.reason || "blocked"}`;
211
+ } else if (tool) {
212
+ try {
213
+ result = await tool.handler(toolInput);
214
+ } catch (err) {
215
+ result = `Error: ${err instanceof Error ? err.message : String(err)}`;
216
+ }
217
+ } else {
218
+ result = `Error: Tool "${block.name}" not found`;
219
+ }
220
+ const toolDuration = Date.now() - toolStart;
221
+ onToolCallDone?.(block.name, toolDuration);
222
+ await triggerToolHook("onToolResult", {
223
+ model,
224
+ toolName: block.name,
225
+ toolInput: toolInputText,
226
+ toolResult: result
227
+ });
228
+ allToolCalls.push({
229
+ toolName: block.name,
230
+ input: toolInput,
231
+ output: result
232
+ });
233
+ toolResults.push({
234
+ type: "tool_result",
235
+ tool_use_id: block.id,
236
+ content: result
237
+ });
238
+ }
239
+ }
240
+ if (hasToolUse) {
241
+ messages.push({ role: "assistant", content: response.content });
242
+ messages.push({ role: "user", content: toolResults });
243
+ } else {
244
+ break;
245
+ }
246
+ if (response.stop_reason === "end_turn") break;
247
+ }
248
+ return {
249
+ output: totalOutput,
250
+ responseText: allToolCalls.length > 0 ? currentRoundText : totalOutput,
251
+ toolCalls: allToolCalls,
252
+ tokenUsage: {
253
+ inputTokens: totalInputTokens,
254
+ outputTokens: totalOutputTokens
255
+ }
256
+ };
257
+ }
258
+
259
+ export {
260
+ createLLMClient,
261
+ callLLM,
262
+ streamCallLLM
263
+ };
@@ -0,0 +1,57 @@
1
+ // src/ui/notify.ts
2
+ import { exec } from "child_process";
3
+ import os from "os";
4
+ var notificationsEnabled = true;
5
+ function setNotificationsEnabled(enabled) {
6
+ notificationsEnabled = enabled;
7
+ }
8
+ function isNotificationsEnabled() {
9
+ return notificationsEnabled;
10
+ }
11
+ function sendNotification(title, body, subtitle) {
12
+ if (!notificationsEnabled) return;
13
+ const platform = os.platform();
14
+ try {
15
+ if (platform === "darwin") {
16
+ const subtitlePart = subtitle ? ` subtitle "${escapeOsascript(subtitle)}"` : "";
17
+ const script = `display notification "${escapeOsascript(body)}" with title "${escapeOsascript(title)}"${subtitlePart} sound name "default"`;
18
+ exec(`osascript -e '${script}'`, () => {
19
+ });
20
+ } else if (platform === "linux") {
21
+ const escapedTitle = escapeShell(title);
22
+ const escapedBody = escapeShell(body);
23
+ exec(`notify-send "${escapedTitle}" "${escapedBody}" --app-name=Hypercore`, () => {
24
+ });
25
+ } else if (platform === "win32") {
26
+ const escapedTitle = title.replace(/'/g, "''");
27
+ const escapedBody = body.replace(/'/g, "''");
28
+ const ps = `[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null; $template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02); $textNodes = $template.GetElementsByTagName('text'); $textNodes.Item(0).AppendChild($template.CreateTextNode('${escapedTitle}')); $textNodes.Item(1).AppendChild($template.CreateTextNode('${escapedBody}')); $toast = [Windows.UI.Notifications.ToastNotification]::new($template); [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Hypercore').Show($toast)`;
29
+ exec(`powershell -Command "${ps}"`, () => {
30
+ });
31
+ }
32
+ } catch {
33
+ }
34
+ }
35
+ function bellNotify() {
36
+ process.stdout.write("\x07");
37
+ }
38
+ function notifyIfLong(title, body, durationMs, thresholdMs = 1e4) {
39
+ if (durationMs >= thresholdMs) {
40
+ sendNotification(title, body);
41
+ bellNotify();
42
+ }
43
+ }
44
+ function escapeOsascript(str) {
45
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
46
+ }
47
+ function escapeShell(str) {
48
+ return str.replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
49
+ }
50
+
51
+ export {
52
+ setNotificationsEnabled,
53
+ isNotificationsEnabled,
54
+ sendNotification,
55
+ bellNotify,
56
+ notifyIfLong
57
+ };