@simonfestl/husky-cli 1.36.0 → 1.37.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.
@@ -41,11 +41,19 @@ function validateApiUrl(url) {
41
41
  }
42
42
  export function getConfig() {
43
43
  try {
44
- if (!existsSync(CONFIG_FILE)) {
45
- return {};
44
+ let config = {};
45
+ if (existsSync(CONFIG_FILE)) {
46
+ const content = readFileSync(CONFIG_FILE, "utf-8");
47
+ config = JSON.parse(content);
46
48
  }
47
- const content = readFileSync(CONFIG_FILE, "utf-8");
48
- return JSON.parse(content);
49
+ // Environment variable overrides (useful for testing)
50
+ if (process.env.HUSKY_API_URL) {
51
+ config.apiUrl = process.env.HUSKY_API_URL;
52
+ }
53
+ if (process.env.HUSKY_API_KEY) {
54
+ config.apiKey = process.env.HUSKY_API_KEY;
55
+ }
56
+ return config;
49
57
  }
50
58
  catch {
51
59
  return {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Insights Command - Aggregated Session Analytics
3
+ *
4
+ * Part of the self-improving system. Provides commands for:
5
+ * - Viewing recent errors across sessions
6
+ * - Listing learnings
7
+ * - Showing suggested improvements
8
+ * - Aggregated statistics
9
+ */
10
+ import { Command } from "commander";
11
+ export declare const insightsCommand: Command;
12
+ export default insightsCommand;
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Insights Command - Aggregated Session Analytics
3
+ *
4
+ * Part of the self-improving system. Provides commands for:
5
+ * - Viewing recent errors across sessions
6
+ * - Listing learnings
7
+ * - Showing suggested improvements
8
+ * - Aggregated statistics
9
+ */
10
+ import { Command } from "commander";
11
+ import { getConfig } from "./config.js";
12
+ import { getApiClient } from "../lib/api-client.js";
13
+ // ============================================================================
14
+ // Helpers
15
+ // ============================================================================
16
+ function truncate(text, maxLen) {
17
+ if (text.length <= maxLen)
18
+ return text;
19
+ return text.slice(0, maxLen - 3) + "...";
20
+ }
21
+ function formatDate(date) {
22
+ const d = typeof date === "string" ? new Date(date) : date;
23
+ return d.toLocaleString("de-DE", {
24
+ day: "2-digit",
25
+ month: "2-digit",
26
+ hour: "2-digit",
27
+ minute: "2-digit",
28
+ });
29
+ }
30
+ function parseDuration(since) {
31
+ const match = since.match(/^(\d+)([dhwm])$/);
32
+ if (!match) {
33
+ throw new Error("Invalid duration format. Use: 7d, 24h, 2w, 1m");
34
+ }
35
+ const value = parseInt(match[1], 10);
36
+ const unit = match[2];
37
+ const now = new Date();
38
+ switch (unit) {
39
+ case "h":
40
+ return new Date(now.getTime() - value * 60 * 60 * 1000);
41
+ case "d":
42
+ return new Date(now.getTime() - value * 24 * 60 * 60 * 1000);
43
+ case "w":
44
+ return new Date(now.getTime() - value * 7 * 24 * 60 * 60 * 1000);
45
+ case "m":
46
+ return new Date(now.getTime() - value * 30 * 24 * 60 * 60 * 1000);
47
+ default:
48
+ throw new Error("Invalid duration unit. Use: h, d, w, m");
49
+ }
50
+ }
51
+ function ensureConfig() {
52
+ const config = getConfig();
53
+ if (!config.apiUrl) {
54
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
55
+ process.exit(1);
56
+ }
57
+ return config;
58
+ }
59
+ // ============================================================================
60
+ // Command Definition
61
+ // ============================================================================
62
+ export const insightsCommand = new Command("insights")
63
+ .description("View aggregated session insights and analytics");
64
+ // -------------------------------------------------------------------------
65
+ // Errors
66
+ // -------------------------------------------------------------------------
67
+ insightsCommand
68
+ .command("errors")
69
+ .description("List recent errors from analyzed sessions")
70
+ .option("--since <duration>", "Time window (e.g., 7d, 24h, 2w)", "7d")
71
+ .option("-l, --limit <num>", "Max errors to show", "20")
72
+ .option("--type <type>", "Filter by error type")
73
+ .option("--preventable", "Show only preventable errors")
74
+ .option("--json", "Output as JSON")
75
+ .action(async (options) => {
76
+ ensureConfig();
77
+ const api = getApiClient();
78
+ try {
79
+ // Get analyzed sessions
80
+ const result = await api.get("/api/sessions?status=analyzed&limit=50");
81
+ const sinceDate = parseDuration(options.since);
82
+ const limit = parseInt(options.limit, 10);
83
+ // Extract errors from summaries
84
+ const allErrors = [];
85
+ for (const session of result.sessions) {
86
+ if (!session.summary?.errors)
87
+ continue;
88
+ const analyzedAt = new Date(session.analyzedAt || session.createdAt);
89
+ if (analyzedAt < sinceDate)
90
+ continue;
91
+ for (const error of session.summary.errors) {
92
+ if (options.type && error.type !== options.type)
93
+ continue;
94
+ if (options.preventable && !error.preventable)
95
+ continue;
96
+ allErrors.push({
97
+ sessionId: session.id,
98
+ analyzedAt,
99
+ error,
100
+ });
101
+ }
102
+ }
103
+ // Sort by date, most recent first
104
+ allErrors.sort((a, b) => b.analyzedAt.getTime() - a.analyzedAt.getTime());
105
+ const errors = allErrors.slice(0, limit);
106
+ if (options.json) {
107
+ console.log(JSON.stringify({ success: true, errors, count: errors.length }));
108
+ return;
109
+ }
110
+ console.log(`\n Errors (last ${options.since}, ${errors.length} found)\n`);
111
+ if (errors.length === 0) {
112
+ console.log(" No errors found.");
113
+ console.log("");
114
+ return;
115
+ }
116
+ // Group by error type
117
+ const byType = new Map();
118
+ for (const e of errors) {
119
+ const list = byType.get(e.error.type) || [];
120
+ list.push(e);
121
+ byType.set(e.error.type, list);
122
+ }
123
+ for (const [type, typeErrors] of byType) {
124
+ console.log(` [${type}] (${typeErrors.length})`);
125
+ for (const e of typeErrors.slice(0, 5)) {
126
+ const preventable = e.error.preventable ? " (preventable)" : "";
127
+ console.log(` ${formatDate(e.analyzedAt)} | ${truncate(e.error.description, 50)}${preventable}`);
128
+ console.log(` ${truncate(e.error.solution, 55)}`);
129
+ }
130
+ if (typeErrors.length > 5) {
131
+ console.log(` ... and ${typeErrors.length - 5} more`);
132
+ }
133
+ console.log("");
134
+ }
135
+ }
136
+ catch (error) {
137
+ console.error("Error:", error.message);
138
+ process.exit(1);
139
+ }
140
+ });
141
+ // -------------------------------------------------------------------------
142
+ // Learnings
143
+ // -------------------------------------------------------------------------
144
+ insightsCommand
145
+ .command("learnings")
146
+ .description("List learnings from analyzed sessions")
147
+ .option("--since <duration>", "Time window (e.g., 7d, 24h, 2w)", "7d")
148
+ .option("-l, --limit <num>", "Max learnings to show", "20")
149
+ .option("-t, --tags <tags>", "Filter by tags (comma-separated)")
150
+ .option("--confidence <level>", "Filter by confidence (low, medium, high)")
151
+ .option("--saved-only", "Show only learnings saved to brain")
152
+ .option("--json", "Output as JSON")
153
+ .action(async (options) => {
154
+ ensureConfig();
155
+ const api = getApiClient();
156
+ try {
157
+ const result = await api.get("/api/sessions?status=analyzed&limit=50");
158
+ const sinceDate = parseDuration(options.since);
159
+ const limit = parseInt(options.limit, 10);
160
+ const filterTags = options.tags ? options.tags.split(",").map((t) => t.trim()) : [];
161
+ const allLearnings = [];
162
+ for (const session of result.sessions) {
163
+ if (!session.summary?.learnings)
164
+ continue;
165
+ const analyzedAt = new Date(session.analyzedAt || session.createdAt);
166
+ if (analyzedAt < sinceDate)
167
+ continue;
168
+ for (const learning of session.summary.learnings) {
169
+ if (options.confidence && learning.confidence !== options.confidence)
170
+ continue;
171
+ if (options.savedOnly && !learning.savedToBrain)
172
+ continue;
173
+ if (filterTags.length > 0) {
174
+ const hasTag = filterTags.some((t) => learning.tags.includes(t));
175
+ if (!hasTag)
176
+ continue;
177
+ }
178
+ allLearnings.push({
179
+ sessionId: session.id,
180
+ analyzedAt,
181
+ learning,
182
+ });
183
+ }
184
+ }
185
+ allLearnings.sort((a, b) => b.analyzedAt.getTime() - a.analyzedAt.getTime());
186
+ const learnings = allLearnings.slice(0, limit);
187
+ if (options.json) {
188
+ console.log(JSON.stringify({ success: true, learnings, count: learnings.length }));
189
+ return;
190
+ }
191
+ console.log(`\n Learnings (last ${options.since}, ${learnings.length} found)\n`);
192
+ if (learnings.length === 0) {
193
+ console.log(" No learnings found.");
194
+ console.log("");
195
+ return;
196
+ }
197
+ for (const l of learnings) {
198
+ const tags = l.learning.tags.length > 0 ? ` [${l.learning.tags.join(", ")}]` : "";
199
+ const confidence = l.learning.confidence;
200
+ const saved = l.learning.savedToBrain ? " " : "";
201
+ console.log(` ${formatDate(l.analyzedAt)} | [${confidence}]${saved} ${truncate(l.learning.content, 60)}${tags}`);
202
+ }
203
+ console.log("");
204
+ }
205
+ catch (error) {
206
+ console.error("Error:", error.message);
207
+ process.exit(1);
208
+ }
209
+ });
210
+ // -------------------------------------------------------------------------
211
+ // Improvements
212
+ // -------------------------------------------------------------------------
213
+ insightsCommand
214
+ .command("improvements")
215
+ .description("List suggested improvements from analyzed sessions")
216
+ .option("--since <duration>", "Time window (e.g., 7d, 24h, 2w)", "30d")
217
+ .option("-l, --limit <num>", "Max improvements to show", "20")
218
+ .option("--priority <priority>", "Filter by priority (low, medium, high)")
219
+ .option("--area <area>", "Filter by area")
220
+ .option("--json", "Output as JSON")
221
+ .action(async (options) => {
222
+ ensureConfig();
223
+ const api = getApiClient();
224
+ try {
225
+ const result = await api.get("/api/sessions?status=analyzed&limit=100");
226
+ const sinceDate = parseDuration(options.since);
227
+ const limit = parseInt(options.limit, 10);
228
+ const allImprovements = [];
229
+ for (const session of result.sessions) {
230
+ if (!session.summary?.improvements)
231
+ continue;
232
+ const analyzedAt = new Date(session.analyzedAt || session.createdAt);
233
+ if (analyzedAt < sinceDate)
234
+ continue;
235
+ for (const improvement of session.summary.improvements) {
236
+ if (options.priority && improvement.priority !== options.priority)
237
+ continue;
238
+ if (options.area && !improvement.area.toLowerCase().includes(options.area.toLowerCase()))
239
+ continue;
240
+ allImprovements.push({
241
+ sessionId: session.id,
242
+ analyzedAt,
243
+ improvement,
244
+ });
245
+ }
246
+ }
247
+ // Sort by priority (high first), then by date
248
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
249
+ allImprovements.sort((a, b) => {
250
+ const pA = priorityOrder[a.improvement.priority] ?? 3;
251
+ const pB = priorityOrder[b.improvement.priority] ?? 3;
252
+ if (pA !== pB)
253
+ return pA - pB;
254
+ return b.analyzedAt.getTime() - a.analyzedAt.getTime();
255
+ });
256
+ const improvements = allImprovements.slice(0, limit);
257
+ if (options.json) {
258
+ console.log(JSON.stringify({ success: true, improvements, count: improvements.length }));
259
+ return;
260
+ }
261
+ console.log(`\n Suggested Improvements (last ${options.since}, ${improvements.length} found)\n`);
262
+ if (improvements.length === 0) {
263
+ console.log(" No improvements found.");
264
+ console.log("");
265
+ return;
266
+ }
267
+ // Group by area
268
+ const byArea = new Map();
269
+ for (const i of improvements) {
270
+ const list = byArea.get(i.improvement.area) || [];
271
+ list.push(i);
272
+ byArea.set(i.improvement.area, list);
273
+ }
274
+ for (const [area, areaImprovements] of byArea) {
275
+ console.log(` ${area}`);
276
+ for (const i of areaImprovements) {
277
+ const priority = i.improvement.priority.toUpperCase().padEnd(6);
278
+ console.log(` [${priority}] ${truncate(i.improvement.suggestion, 55)}`);
279
+ }
280
+ console.log("");
281
+ }
282
+ }
283
+ catch (error) {
284
+ console.error("Error:", error.message);
285
+ process.exit(1);
286
+ }
287
+ });
288
+ // -------------------------------------------------------------------------
289
+ // Stats
290
+ // -------------------------------------------------------------------------
291
+ insightsCommand
292
+ .command("stats")
293
+ .description("Show aggregated statistics across sessions")
294
+ .option("--since <duration>", "Time window (e.g., 7d, 24h, 2w)", "7d")
295
+ .option("--json", "Output as JSON")
296
+ .action(async (options) => {
297
+ ensureConfig();
298
+ const api = getApiClient();
299
+ try {
300
+ // Get all sessions (not just analyzed)
301
+ const result = await api.get("/api/sessions?limit=100");
302
+ const sinceDate = parseDuration(options.since);
303
+ // Filter by date
304
+ const sessions = result.sessions.filter((s) => {
305
+ const createdAt = new Date(s.createdAt);
306
+ return createdAt >= sinceDate;
307
+ });
308
+ // Calculate stats
309
+ const totalSessions = sessions.length;
310
+ const analyzedSessions = sessions.filter((s) => s.status === "analyzed").length;
311
+ const activeSessions = sessions.filter((s) => s.status === "active").length;
312
+ let totalLogs = 0;
313
+ let totalErrors = 0;
314
+ let totalDurationMinutes = 0;
315
+ const toolUsage = {};
316
+ const errorTypes = {};
317
+ let totalLearnings = 0;
318
+ let totalImprovements = 0;
319
+ for (const session of sessions) {
320
+ totalLogs += session.logCount || 0;
321
+ totalErrors += session.errorCount || 0;
322
+ if (session.summary) {
323
+ totalDurationMinutes += session.summary.stats.durationMinutes || 0;
324
+ totalLearnings += session.summary.learnings.length;
325
+ totalImprovements += session.summary.improvements.length;
326
+ // Aggregate tool usage
327
+ for (const [tool, count] of Object.entries(session.summary.stats.toolUsage || {})) {
328
+ toolUsage[tool] = (toolUsage[tool] || 0) + count;
329
+ }
330
+ // Aggregate error types
331
+ for (const error of session.summary.errors) {
332
+ errorTypes[error.type] = (errorTypes[error.type] || 0) + 1;
333
+ }
334
+ }
335
+ }
336
+ const stats = {
337
+ period: options.since,
338
+ totalSessions,
339
+ analyzedSessions,
340
+ activeSessions,
341
+ totalLogs,
342
+ totalErrors,
343
+ totalDurationMinutes,
344
+ totalLearnings,
345
+ totalImprovements,
346
+ toolUsage,
347
+ errorTypes,
348
+ };
349
+ if (options.json) {
350
+ console.log(JSON.stringify({ success: true, stats }));
351
+ return;
352
+ }
353
+ console.log(`\n Session Stats (last ${options.since})`);
354
+ console.log(` ${"".repeat(40)}`);
355
+ console.log("");
356
+ console.log(` Sessions: ${totalSessions}`);
357
+ console.log(` Active: ${activeSessions}`);
358
+ console.log(` Analyzed: ${analyzedSessions}`);
359
+ console.log("");
360
+ console.log(` Activity:`);
361
+ console.log(` Total logs: ${totalLogs.toLocaleString()}`);
362
+ console.log(` Total errors: ${totalErrors.toLocaleString()}`);
363
+ console.log(` Duration: ${Math.round(totalDurationMinutes / 60)} hours`);
364
+ console.log("");
365
+ console.log(` Analysis:`);
366
+ console.log(` Learnings: ${totalLearnings}`);
367
+ console.log(` Improvements: ${totalImprovements}`);
368
+ console.log("");
369
+ // Top tools
370
+ const sortedTools = Object.entries(toolUsage)
371
+ .sort((a, b) => b[1] - a[1])
372
+ .slice(0, 5);
373
+ if (sortedTools.length > 0) {
374
+ console.log(` Top Tools:`);
375
+ for (const [tool, count] of sortedTools) {
376
+ console.log(` ${tool.padEnd(20)} ${count.toLocaleString()}`);
377
+ }
378
+ console.log("");
379
+ }
380
+ // Error types
381
+ const sortedErrors = Object.entries(errorTypes)
382
+ .sort((a, b) => b[1] - a[1])
383
+ .slice(0, 5);
384
+ if (sortedErrors.length > 0) {
385
+ console.log(` Error Types:`);
386
+ for (const [type, count] of sortedErrors) {
387
+ console.log(` ${type.padEnd(20)} ${count}`);
388
+ }
389
+ console.log("");
390
+ }
391
+ }
392
+ catch (error) {
393
+ console.error("Error:", error.message);
394
+ process.exit(1);
395
+ }
396
+ });
397
+ export default insightsCommand;
@@ -289,6 +289,42 @@ husky brain stats # Statistiken
289
289
  \`\`\`
290
290
  `;
291
291
  }
292
+ function getSessionSection() {
293
+ return `
294
+ ---
295
+
296
+ ## Session Logging (Self-Improving System)
297
+
298
+ > [!NOTE]
299
+ > Sessions werden automatisch geloggt und bei VM-Shutdown exportiert.
300
+ > Analysierte Sessions ermoeglichen semantische Suche nach aehnlichen Problemen/Loesungen.
301
+
302
+ \`\`\`bash
303
+ # Session Management
304
+ husky session current # Zeigt aktuelle Session
305
+ husky session list --limit 10 # Sessions auflisten
306
+ husky session log --tool "Bash" ... # Tool-Call loggen (via Hook)
307
+ husky session export --to-firestore # Logs exportieren
308
+
309
+ # Analyse & Embeddings
310
+ husky session analyze # Analyse triggern (async)
311
+ husky session embed # Embeddings generieren
312
+ husky session summary # Analyse-Summary anzeigen
313
+
314
+ # Semantische Suche (mit Vector Embeddings)
315
+ husky session search "trigger fehler" # Nach aehnlichen Problemen suchen
316
+ husky session search "RLS" --type errors # Nur Fehler
317
+ husky session search "fix" --type learnings # Nur Learnings
318
+ \`\`\`
319
+
320
+ ### Insights (Aggregierte Analytics)
321
+ \`\`\`bash
322
+ husky insights errors --since 7d # Fehler der letzten 7 Tage
323
+ husky insights learnings --since 7d # Learnings anzeigen
324
+ husky insights stats --since 7d # Aggregierte Statistiken
325
+ \`\`\`
326
+ `;
327
+ }
292
328
  function getVMSection() {
293
329
  return `
294
330
  ---
@@ -428,6 +464,8 @@ export function generateLLMContext(role, permissions) {
428
464
  }
429
465
  // Brain - always included
430
466
  context += getBrainSection(effectiveRole);
467
+ // Session Logging - always included
468
+ context += getSessionSection();
431
469
  // Tips - always included
432
470
  context += getTips();
433
471
  return context;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Session Command - Agent Session Logging & Analysis
3
+ *
4
+ * Part of the self-improving system. Provides commands for:
5
+ * - Logging tool calls during a session
6
+ * - Exporting session data before VM shutdown
7
+ * - Triggering analysis
8
+ * - Searching for similar problems/solutions
9
+ */
10
+ import { Command } from "commander";
11
+ export declare const sessionCommand: Command;
12
+ export default sessionCommand;
@@ -0,0 +1,665 @@
1
+ /**
2
+ * Session Command - Agent Session Logging & Analysis
3
+ *
4
+ * Part of the self-improving system. Provides commands for:
5
+ * - Logging tool calls during a session
6
+ * - Exporting session data before VM shutdown
7
+ * - Triggering analysis
8
+ * - Searching for similar problems/solutions
9
+ */
10
+ import { Command } from "commander";
11
+ import { getConfig } from "./config.js";
12
+ import { getApiClient } from "../lib/api-client.js";
13
+ import * as fs from "fs";
14
+ import * as path from "path";
15
+ // ============================================================================
16
+ // Helpers
17
+ // ============================================================================
18
+ const DEFAULT_AGENT_ID = process.env.HUSKY_AGENT_ID || "default";
19
+ const DEFAULT_VM_NAME = process.env.HUSKY_VM_NAME || process.env.HOSTNAME || "local";
20
+ // Local session state file for tracking current session
21
+ const SESSION_STATE_FILE = path.join(process.env.HOME || "/tmp", ".husky", "current-session.json");
22
+ function loadLocalState() {
23
+ try {
24
+ if (fs.existsSync(SESSION_STATE_FILE)) {
25
+ return JSON.parse(fs.readFileSync(SESSION_STATE_FILE, "utf-8"));
26
+ }
27
+ }
28
+ catch {
29
+ // Ignore errors
30
+ }
31
+ return null;
32
+ }
33
+ function saveLocalState(state) {
34
+ const dir = path.dirname(SESSION_STATE_FILE);
35
+ if (!fs.existsSync(dir)) {
36
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
37
+ }
38
+ fs.writeFileSync(SESSION_STATE_FILE, JSON.stringify(state, null, 2), { mode: 0o600 });
39
+ }
40
+ function clearLocalState() {
41
+ try {
42
+ if (fs.existsSync(SESSION_STATE_FILE)) {
43
+ fs.unlinkSync(SESSION_STATE_FILE);
44
+ }
45
+ }
46
+ catch {
47
+ // Ignore errors
48
+ }
49
+ }
50
+ function truncate(text, maxLen) {
51
+ if (text.length <= maxLen)
52
+ return text;
53
+ return text.slice(0, maxLen - 3) + "...";
54
+ }
55
+ function formatDate(date) {
56
+ const d = typeof date === "string" ? new Date(date) : date;
57
+ return d.toLocaleString("de-DE", {
58
+ day: "2-digit",
59
+ month: "2-digit",
60
+ hour: "2-digit",
61
+ minute: "2-digit",
62
+ });
63
+ }
64
+ function ensureConfig() {
65
+ const config = getConfig();
66
+ if (!config.apiUrl) {
67
+ console.error("Error: API URL not configured. Run: husky config set api-url <url>");
68
+ process.exit(1);
69
+ }
70
+ return config;
71
+ }
72
+ // ============================================================================
73
+ // Command Definition
74
+ // ============================================================================
75
+ export const sessionCommand = new Command("session")
76
+ .description("Agent session logging and analysis (self-improving system)");
77
+ // -------------------------------------------------------------------------
78
+ // Session Management
79
+ // -------------------------------------------------------------------------
80
+ sessionCommand
81
+ .command("init")
82
+ .description("Initialize a new session (typically called at VM startup)")
83
+ .option("--vm-name <name>", "VM name", DEFAULT_VM_NAME)
84
+ .option("--agent-id <id>", "Agent ID", DEFAULT_AGENT_ID)
85
+ .option("--agent-name <name>", "Agent display name")
86
+ .option("--task-id <id>", "Associated task ID")
87
+ .option("--json", "Output as JSON")
88
+ .action(async (options) => {
89
+ ensureConfig();
90
+ const api = getApiClient();
91
+ try {
92
+ const session = await api.post("/api/sessions", {
93
+ vmName: options.vmName,
94
+ agentId: options.agentId,
95
+ agentName: options.agentName,
96
+ taskId: options.taskId,
97
+ });
98
+ // Save local state
99
+ saveLocalState({
100
+ sessionId: session.id,
101
+ vmName: options.vmName,
102
+ agentId: options.agentId,
103
+ startedAt: new Date().toISOString(),
104
+ pendingLogs: [],
105
+ });
106
+ // Set environment variable for hooks
107
+ console.log(`export HUSKY_SESSION_ID="${session.id}"`);
108
+ if (options.json) {
109
+ console.log(JSON.stringify({ success: true, session }));
110
+ }
111
+ else {
112
+ console.log(` Session initialized: ${session.id}`);
113
+ console.log(` VM: ${options.vmName}`);
114
+ console.log(` Agent: ${options.agentId}`);
115
+ }
116
+ }
117
+ catch (error) {
118
+ console.error("Error:", error.message);
119
+ process.exit(1);
120
+ }
121
+ });
122
+ sessionCommand
123
+ .command("current")
124
+ .description("Show current session info")
125
+ .option("--vm-name <name>", "VM name filter", DEFAULT_VM_NAME)
126
+ .option("--agent-id <id>", "Agent ID filter")
127
+ .option("--json", "Output as JSON")
128
+ .action(async (options) => {
129
+ // First check local state
130
+ const localState = loadLocalState();
131
+ if (localState) {
132
+ if (options.json) {
133
+ console.log(JSON.stringify({
134
+ success: true,
135
+ source: "local",
136
+ sessionId: localState.sessionId,
137
+ vmName: localState.vmName,
138
+ agentId: localState.agentId,
139
+ startedAt: localState.startedAt,
140
+ pendingLogs: localState.pendingLogs.length,
141
+ }));
142
+ }
143
+ else {
144
+ console.log(`\n Current Session (local state)`);
145
+ console.log(` ID: ${localState.sessionId}`);
146
+ console.log(` VM: ${localState.vmName}`);
147
+ console.log(` Agent: ${localState.agentId}`);
148
+ console.log(` Started: ${formatDate(localState.startedAt)}`);
149
+ console.log(` Pending logs: ${localState.pendingLogs.length}`);
150
+ console.log("");
151
+ }
152
+ return;
153
+ }
154
+ // Fall back to API
155
+ ensureConfig();
156
+ const api = getApiClient();
157
+ try {
158
+ const params = new URLSearchParams();
159
+ if (options.vmName)
160
+ params.set("vmName", options.vmName);
161
+ if (options.agentId)
162
+ params.set("agentId", options.agentId);
163
+ const result = await api.get(`/api/sessions/current?${params.toString()}`);
164
+ if (options.json) {
165
+ console.log(JSON.stringify({ success: true, source: "api", session: result.session }));
166
+ }
167
+ else {
168
+ const s = result.session;
169
+ console.log(`\n Current Session`);
170
+ console.log(` ID: ${s.id}`);
171
+ console.log(` VM: ${s.vmName}`);
172
+ console.log(` Agent: ${s.agentId}`);
173
+ console.log(` Status: ${s.status}`);
174
+ console.log(` Logs: ${s.logCount} (${s.errorCount} errors)`);
175
+ console.log(` Started: ${formatDate(s.startedAt)}`);
176
+ console.log("");
177
+ }
178
+ }
179
+ catch (error) {
180
+ if (options.json) {
181
+ console.log(JSON.stringify({ success: false, error: error.message }));
182
+ }
183
+ else {
184
+ console.log(" No active session found");
185
+ }
186
+ }
187
+ });
188
+ sessionCommand
189
+ .command("list")
190
+ .description("List recent sessions")
191
+ .option("-s, --status <status>", "Filter by status (active, exported, analyzed)")
192
+ .option("--vm-name <name>", "Filter by VM name")
193
+ .option("--agent-id <id>", "Filter by agent ID")
194
+ .option("-l, --limit <num>", "Max results", "20")
195
+ .option("--json", "Output as JSON")
196
+ .action(async (options) => {
197
+ ensureConfig();
198
+ const api = getApiClient();
199
+ try {
200
+ const params = new URLSearchParams();
201
+ if (options.status)
202
+ params.set("status", options.status);
203
+ if (options.vmName)
204
+ params.set("vmName", options.vmName);
205
+ if (options.agentId)
206
+ params.set("agentId", options.agentId);
207
+ params.set("limit", options.limit);
208
+ const result = await api.get(`/api/sessions?${params.toString()}`);
209
+ if (options.json) {
210
+ console.log(JSON.stringify({ success: true, sessions: result.sessions }));
211
+ return;
212
+ }
213
+ console.log(`\n Sessions (${result.sessions.length})\n`);
214
+ if (result.sessions.length === 0) {
215
+ console.log(" No sessions found.");
216
+ return;
217
+ }
218
+ for (const s of result.sessions) {
219
+ const date = formatDate(s.createdAt);
220
+ const status = s.status.padEnd(10);
221
+ const logs = `${s.logCount} logs`.padEnd(12);
222
+ console.log(` ${date} | ${status} | ${logs} | ${s.vmName}/${s.agentId}`);
223
+ }
224
+ console.log("");
225
+ }
226
+ catch (error) {
227
+ console.error("Error:", error.message);
228
+ process.exit(1);
229
+ }
230
+ });
231
+ // -------------------------------------------------------------------------
232
+ // Logging
233
+ // -------------------------------------------------------------------------
234
+ sessionCommand
235
+ .command("log")
236
+ .description("Add a log entry to current session (for PostToolUse hook)")
237
+ .option("--session-id <id>", "Session ID (or uses current)")
238
+ .option("--tool <name>", "Tool name", "unknown")
239
+ .option("--input <text>", "Tool input (truncated to 1KB)")
240
+ .option("--output <text>", "Tool output (truncated to 5KB)")
241
+ .option("--status <status>", "Status: success, error, warning", "success")
242
+ .option("--error-type <type>", "Error type if status is error")
243
+ .option("--duration <ms>", "Duration in milliseconds")
244
+ .option("--buffer", "Buffer locally instead of sending immediately")
245
+ .option("--json", "Output as JSON")
246
+ .action(async (options) => {
247
+ const localState = loadLocalState();
248
+ const sessionId = options.sessionId || localState?.sessionId || process.env.HUSKY_SESSION_ID;
249
+ if (!sessionId) {
250
+ if (options.json) {
251
+ console.log(JSON.stringify({ success: false, error: "No session ID" }));
252
+ }
253
+ else {
254
+ console.error("Error: No session ID. Run 'husky session init' first.");
255
+ }
256
+ process.exit(1);
257
+ }
258
+ const logEntry = {
259
+ tool: options.tool,
260
+ toolInput: truncate(options.input || "", 1024),
261
+ toolOutput: truncate(options.output || "", 5120),
262
+ status: options.status,
263
+ errorType: options.errorType,
264
+ duration: options.duration ? parseInt(options.duration, 10) : undefined,
265
+ timestamp: new Date().toISOString(),
266
+ };
267
+ // Buffer mode: store locally
268
+ if (options.buffer && localState) {
269
+ localState.pendingLogs.push(logEntry);
270
+ saveLocalState(localState);
271
+ if (options.json) {
272
+ console.log(JSON.stringify({ success: true, buffered: true, pending: localState.pendingLogs.length }));
273
+ }
274
+ return;
275
+ }
276
+ // Send to API
277
+ ensureConfig();
278
+ const api = getApiClient();
279
+ try {
280
+ const vmName = localState?.vmName || DEFAULT_VM_NAME;
281
+ const agentId = localState?.agentId || DEFAULT_AGENT_ID;
282
+ await api.post(`/api/sessions/${sessionId}/logs`, {
283
+ vmName,
284
+ agentId,
285
+ ...logEntry,
286
+ });
287
+ if (options.json) {
288
+ console.log(JSON.stringify({ success: true, sessionId }));
289
+ }
290
+ }
291
+ catch (error) {
292
+ // Fail silently in non-json mode (don't disrupt main workflow)
293
+ if (options.json) {
294
+ console.log(JSON.stringify({ success: false, error: error.message }));
295
+ }
296
+ }
297
+ });
298
+ // -------------------------------------------------------------------------
299
+ // Export (for VM shutdown)
300
+ // -------------------------------------------------------------------------
301
+ sessionCommand
302
+ .command("export")
303
+ .description("Export buffered logs to Firestore (for VM shutdown)")
304
+ .option("--session-id <id>", "Session ID (or uses current)")
305
+ .option("--to-firestore", "Export to Firestore (default)")
306
+ .option("--force", "Export even if no pending logs")
307
+ .option("--json", "Output as JSON")
308
+ .action(async (options) => {
309
+ const localState = loadLocalState();
310
+ const sessionId = options.sessionId || localState?.sessionId || process.env.HUSKY_SESSION_ID;
311
+ if (!sessionId) {
312
+ if (options.json) {
313
+ console.log(JSON.stringify({ success: false, error: "No session ID" }));
314
+ }
315
+ else {
316
+ console.error("Error: No session ID. Run 'husky session init' first.");
317
+ }
318
+ process.exit(1);
319
+ }
320
+ if (!localState?.pendingLogs.length && !options.force) {
321
+ if (options.json) {
322
+ console.log(JSON.stringify({ success: true, logsExported: 0, message: "No pending logs" }));
323
+ }
324
+ else {
325
+ console.log(" No pending logs to export.");
326
+ }
327
+ return;
328
+ }
329
+ ensureConfig();
330
+ const api = getApiClient();
331
+ try {
332
+ const vmName = localState?.vmName || DEFAULT_VM_NAME;
333
+ const agentId = localState?.agentId || DEFAULT_AGENT_ID;
334
+ const logs = (localState?.pendingLogs || []).map((log) => ({
335
+ vmName,
336
+ agentId,
337
+ ...log,
338
+ }));
339
+ if (logs.length > 0) {
340
+ const result = await api.post(`/api/sessions/${sessionId}/export`, { logs });
341
+ // Clear local state after successful export
342
+ clearLocalState();
343
+ if (options.json) {
344
+ console.log(JSON.stringify(result));
345
+ }
346
+ else {
347
+ console.log(` Exported ${result.logsImported} logs to session ${sessionId}`);
348
+ }
349
+ }
350
+ else {
351
+ // Just update status
352
+ await api.patch(`/api/sessions/${sessionId}`, { status: "exported" });
353
+ if (options.json) {
354
+ console.log(JSON.stringify({ success: true, logsExported: 0 }));
355
+ }
356
+ else {
357
+ console.log(` Session ${sessionId} marked as exported`);
358
+ }
359
+ }
360
+ }
361
+ catch (error) {
362
+ console.error("Error:", error.message);
363
+ process.exit(1);
364
+ }
365
+ });
366
+ // -------------------------------------------------------------------------
367
+ // Analysis
368
+ // -------------------------------------------------------------------------
369
+ sessionCommand
370
+ .command("analyze")
371
+ .description("Trigger session analysis")
372
+ .option("--session-id <id>", "Session ID (or uses current)")
373
+ .option("--sync", "Wait for analysis to complete (not recommended)")
374
+ .option("--json", "Output as JSON")
375
+ .action(async (options) => {
376
+ const localState = loadLocalState();
377
+ const sessionId = options.sessionId || localState?.sessionId || process.env.HUSKY_SESSION_ID;
378
+ if (!sessionId) {
379
+ if (options.json) {
380
+ console.log(JSON.stringify({ success: false, error: "No session ID" }));
381
+ }
382
+ else {
383
+ console.error("Error: No session ID.");
384
+ }
385
+ process.exit(1);
386
+ }
387
+ ensureConfig();
388
+ const api = getApiClient();
389
+ try {
390
+ const result = await api.post(`/api/sessions/${sessionId}/analyze`, { async: !options.sync });
391
+ if (options.json) {
392
+ console.log(JSON.stringify(result));
393
+ }
394
+ else {
395
+ if (result.taskId) {
396
+ console.log(` Analysis task created: ${result.taskId}`);
397
+ }
398
+ else {
399
+ console.log(` ${result.message || "Analysis triggered"}`);
400
+ }
401
+ }
402
+ }
403
+ catch (error) {
404
+ console.error("Error:", error.message);
405
+ process.exit(1);
406
+ }
407
+ });
408
+ // -------------------------------------------------------------------------
409
+ // Get Logs (for analyzer worker)
410
+ // -------------------------------------------------------------------------
411
+ sessionCommand
412
+ .command("get-logs")
413
+ .description("Get logs for a session (for analyzer worker)")
414
+ .option("--session-id <id>", "Session ID", "")
415
+ .option("-l, --limit <num>", "Max results", "500")
416
+ .option("--offset <num>", "Offset", "0")
417
+ .option("--status <status>", "Filter by status")
418
+ .option("--json", "Output as JSON")
419
+ .option("--format <format>", "Output format (json, text)", "text")
420
+ .action(async (options) => {
421
+ if (!options.sessionId) {
422
+ console.error("Error: --session-id is required");
423
+ process.exit(1);
424
+ }
425
+ ensureConfig();
426
+ const api = getApiClient();
427
+ try {
428
+ const params = new URLSearchParams();
429
+ params.set("limit", options.limit);
430
+ params.set("offset", options.offset);
431
+ if (options.status)
432
+ params.set("status", options.status);
433
+ const result = await api.get(`/api/sessions/${options.sessionId}/logs?${params.toString()}`);
434
+ if (options.json || options.format === "json") {
435
+ console.log(JSON.stringify(result));
436
+ return;
437
+ }
438
+ console.log(`\n Logs for session ${options.sessionId} (${result.count})\n`);
439
+ for (const log of result.logs) {
440
+ const time = formatDate(log.timestamp);
441
+ const status = log.status === "error" ? "" : log.status === "warning" ? "" : "";
442
+ console.log(` ${time} | ${status} ${log.tool.padEnd(20)} | ${truncate(log.toolInput, 50)}`);
443
+ }
444
+ console.log("");
445
+ }
446
+ catch (error) {
447
+ console.error("Error:", error.message);
448
+ process.exit(1);
449
+ }
450
+ });
451
+ // -------------------------------------------------------------------------
452
+ // Save Summary (for analyzer worker)
453
+ // -------------------------------------------------------------------------
454
+ sessionCommand
455
+ .command("save-summary")
456
+ .description("Save analysis summary (for analyzer worker)")
457
+ .option("--session-id <id>", "Session ID", "")
458
+ .option("--file <path>", "JSON file with summary data")
459
+ .option("--json", "Output as JSON")
460
+ .action(async (options) => {
461
+ if (!options.sessionId) {
462
+ console.error("Error: --session-id is required");
463
+ process.exit(1);
464
+ }
465
+ if (!options.file) {
466
+ console.error("Error: --file is required");
467
+ process.exit(1);
468
+ }
469
+ let summaryData;
470
+ try {
471
+ const content = fs.readFileSync(options.file, "utf-8");
472
+ summaryData = JSON.parse(content);
473
+ }
474
+ catch (error) {
475
+ console.error("Error reading summary file:", error.message);
476
+ process.exit(1);
477
+ }
478
+ ensureConfig();
479
+ const api = getApiClient();
480
+ try {
481
+ const result = await api.post(`/api/sessions/${options.sessionId}/summary`, summaryData);
482
+ if (options.json) {
483
+ console.log(JSON.stringify(result));
484
+ }
485
+ else {
486
+ console.log(` Summary saved for session ${options.sessionId}`);
487
+ console.log(` Learnings: ${result.summary.learnings.length}`);
488
+ console.log(` Errors: ${result.summary.errors.length}`);
489
+ console.log(` Improvements: ${result.summary.improvements.length}`);
490
+ }
491
+ }
492
+ catch (error) {
493
+ console.error("Error:", error.message);
494
+ process.exit(1);
495
+ }
496
+ });
497
+ // -------------------------------------------------------------------------
498
+ // Search
499
+ // -------------------------------------------------------------------------
500
+ sessionCommand
501
+ .command("search <query>")
502
+ .description("Search for similar problems/solutions across sessions")
503
+ .option("--type <type>", "Search type: all, errors, learnings", "all")
504
+ .option("-l, --limit <num>", "Max results", "10")
505
+ .option("--json", "Output as JSON")
506
+ .action(async (query, options) => {
507
+ ensureConfig();
508
+ const api = getApiClient();
509
+ try {
510
+ const result = await api.post("/api/sessions/search", {
511
+ query,
512
+ type: options.type,
513
+ limit: parseInt(options.limit, 10),
514
+ });
515
+ if (options.json) {
516
+ console.log(JSON.stringify(result));
517
+ return;
518
+ }
519
+ console.log(`\n Search: "${query}" (${result.count} results)\n`);
520
+ if (result.results.length === 0) {
521
+ console.log(" No matching sessions found.");
522
+ console.log("");
523
+ return;
524
+ }
525
+ for (const r of result.results) {
526
+ const dateInfo = r.analyzedAt ? formatDate(r.analyzedAt) : "not analyzed";
527
+ const similarity = r.similarity ? ` ${r.similarity}% match` : "";
528
+ console.log(` Session ${r.sessionId.slice(0, 8)}... (${dateInfo})${similarity}`);
529
+ if (r.match.learnings.length > 0) {
530
+ for (const l of r.match.learnings.slice(0, 2)) {
531
+ console.log(` ${truncate(l.content, 70)}`);
532
+ }
533
+ }
534
+ if (r.match.errors.length > 0) {
535
+ for (const e of r.match.errors.slice(0, 2)) {
536
+ console.log(` ${truncate(e.description, 70)}`);
537
+ }
538
+ }
539
+ console.log("");
540
+ }
541
+ }
542
+ catch (error) {
543
+ console.error("Error:", error.message);
544
+ process.exit(1);
545
+ }
546
+ });
547
+ // -------------------------------------------------------------------------
548
+ // Summary
549
+ // -------------------------------------------------------------------------
550
+ sessionCommand
551
+ .command("summary")
552
+ .description("Get analysis summary for a session")
553
+ .option("--session-id <id>", "Session ID (or uses current)")
554
+ .option("--json", "Output as JSON")
555
+ .action(async (options) => {
556
+ const localState = loadLocalState();
557
+ const sessionId = options.sessionId || localState?.sessionId || process.env.HUSKY_SESSION_ID;
558
+ if (!sessionId) {
559
+ console.error("Error: No session ID.");
560
+ process.exit(1);
561
+ }
562
+ ensureConfig();
563
+ const api = getApiClient();
564
+ try {
565
+ const result = await api.get(`/api/sessions/${sessionId}/summary`);
566
+ if (options.json) {
567
+ console.log(JSON.stringify(result));
568
+ return;
569
+ }
570
+ const s = result.summary;
571
+ console.log(`\n Session Summary: ${sessionId.slice(0, 8)}...`);
572
+ console.log(` Analyzed: ${formatDate(s.analyzedAt)} by ${s.analyzedBy}`);
573
+ console.log("");
574
+ console.log(` Stats:`);
575
+ console.log(` Total logs: ${s.stats.totalLogs}`);
576
+ console.log(` Errors: ${s.stats.errorCount}`);
577
+ console.log(` Duration: ${s.stats.durationMinutes} min`);
578
+ console.log("");
579
+ if (s.learnings.length > 0) {
580
+ console.log(` Learnings (${s.learnings.length}):`);
581
+ for (const l of s.learnings) {
582
+ const saved = l.savedToBrain ? " [saved to brain]" : "";
583
+ console.log(` - ${truncate(l.content, 70)}${saved}`);
584
+ }
585
+ console.log("");
586
+ }
587
+ if (s.errors.length > 0) {
588
+ console.log(` Errors (${s.errors.length}):`);
589
+ for (const e of s.errors) {
590
+ console.log(` - [${e.type}] ${truncate(e.description, 60)}`);
591
+ console.log(` ${truncate(e.solution, 60)}`);
592
+ }
593
+ console.log("");
594
+ }
595
+ if (s.improvements.length > 0) {
596
+ console.log(` Improvements (${s.improvements.length}):`);
597
+ for (const i of s.improvements) {
598
+ console.log(` - [${i.priority}] ${i.area}: ${truncate(i.suggestion, 50)}`);
599
+ }
600
+ console.log("");
601
+ }
602
+ }
603
+ catch (error) {
604
+ if (error.message.includes("not found")) {
605
+ console.log(" Session has not been analyzed yet.");
606
+ console.log(" Run: husky session analyze --session-id " + sessionId);
607
+ }
608
+ else {
609
+ console.error("Error:", error.message);
610
+ process.exit(1);
611
+ }
612
+ }
613
+ });
614
+ // -------------------------------------------------------------------------
615
+ // Embed
616
+ // -------------------------------------------------------------------------
617
+ sessionCommand
618
+ .command("embed")
619
+ .description("Generate vector embeddings for a session (requires analysis)")
620
+ .option("--session-id <id>", "Session ID (or uses current)")
621
+ .option("--json", "Output as JSON")
622
+ .action(async (options) => {
623
+ const localState = loadLocalState();
624
+ const sessionId = options.sessionId || localState?.sessionId || process.env.HUSKY_SESSION_ID;
625
+ if (!sessionId) {
626
+ console.error("Error: No session ID. Use --session-id or set HUSKY_SESSION_ID.");
627
+ process.exit(1);
628
+ }
629
+ ensureConfig();
630
+ const api = getApiClient();
631
+ try {
632
+ if (!options.json) {
633
+ console.log(`Generating embeddings for session ${sessionId.slice(0, 8)}...`);
634
+ }
635
+ const result = await api.post(`/api/sessions/${sessionId}/embed`);
636
+ if (options.json) {
637
+ console.log(JSON.stringify(result));
638
+ return;
639
+ }
640
+ console.log(`✓ Generated ${result.embeddingsGenerated} embeddings`);
641
+ console.log("");
642
+ const learnings = result.embeddings.filter(e => e.type === "learning");
643
+ const errors = result.embeddings.filter(e => e.type === "error");
644
+ if (learnings.length > 0) {
645
+ console.log(` Learnings embedded: ${learnings.length}`);
646
+ }
647
+ if (errors.length > 0) {
648
+ console.log(` Errors embedded: ${errors.length}`);
649
+ }
650
+ console.log("");
651
+ console.log(" Session can now be found via semantic search:");
652
+ console.log(" husky session search \"your query\"");
653
+ }
654
+ catch (error) {
655
+ if (error.message.includes("must be analyzed")) {
656
+ console.error("Error: Session must be analyzed before embedding.");
657
+ console.error("Run: husky session analyze --session-id " + sessionId);
658
+ }
659
+ else {
660
+ console.error("Error:", error.message);
661
+ }
662
+ process.exit(1);
663
+ }
664
+ });
665
+ export default sessionCommand;
package/dist/index.js CHANGED
@@ -39,6 +39,8 @@ import { businessCommand } from "./commands/business.js";
39
39
  import { planCommand } from "./commands/plan.js";
40
40
  import { diagramsCommand } from "./commands/diagrams.js";
41
41
  import { supervisorCommand } from "./commands/supervisor.js";
42
+ import { sessionCommand } from "./commands/session.js";
43
+ import { insightsCommand } from "./commands/insights.js";
42
44
  import { checkVersion } from "./lib/version-check.js";
43
45
  // Read version from package.json
44
46
  const require = createRequire(import.meta.url);
@@ -86,6 +88,8 @@ program.addCommand(businessCommand);
86
88
  program.addCommand(planCommand);
87
89
  program.addCommand(diagramsCommand);
88
90
  program.addCommand(supervisorCommand);
91
+ program.addCommand(sessionCommand);
92
+ program.addCommand(insightsCommand);
89
93
  // Handle --llm flag specially
90
94
  if (process.argv.includes("--llm")) {
91
95
  printLLMContext();
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Session Types - Shared between session.ts and insights.ts
3
+ */
4
+ export interface SessionSummary {
5
+ sessionId: string;
6
+ analyzedAt: Date;
7
+ analyzedBy: string;
8
+ learnings: Array<{
9
+ content: string;
10
+ tags: string[];
11
+ confidence: string;
12
+ savedToBrain: boolean;
13
+ brainMemoryId?: string;
14
+ }>;
15
+ errors: Array<{
16
+ type: string;
17
+ description: string;
18
+ solution: string;
19
+ preventable: boolean;
20
+ }>;
21
+ improvements: Array<{
22
+ area: string;
23
+ suggestion: string;
24
+ priority: string;
25
+ }>;
26
+ stats: {
27
+ totalLogs: number;
28
+ errorCount: number;
29
+ durationMinutes: number;
30
+ toolUsage: Record<string, number>;
31
+ };
32
+ }
33
+ export interface AgentSession {
34
+ id: string;
35
+ vmName: string;
36
+ agentId: string;
37
+ agentName?: string;
38
+ taskId?: string;
39
+ status: string;
40
+ logCount: number;
41
+ errorCount: number;
42
+ startedAt: Date;
43
+ lastActivityAt: Date;
44
+ exportedAt?: Date;
45
+ analyzedAt?: Date;
46
+ embeddedAt?: Date;
47
+ embeddingCount?: number;
48
+ summary?: SessionSummary;
49
+ createdAt: Date;
50
+ }
51
+ export interface SessionLogEntry {
52
+ id: string;
53
+ sessionId: string;
54
+ vmName: string;
55
+ agentId: string;
56
+ timestamp: Date;
57
+ tool: string;
58
+ toolInput: string;
59
+ toolOutput: string;
60
+ status: "success" | "error" | "warning";
61
+ errorType?: string;
62
+ duration?: number;
63
+ }
64
+ export interface SearchResult {
65
+ sessionId: string;
66
+ similarity?: number;
67
+ analyzedAt?: Date;
68
+ match: {
69
+ learnings: Array<{
70
+ content: string;
71
+ tags: string[];
72
+ score?: number;
73
+ }>;
74
+ errors: Array<{
75
+ description: string;
76
+ solution: string;
77
+ score?: number;
78
+ }>;
79
+ };
80
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Session Types - Shared between session.ts and insights.ts
3
+ */
4
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonfestl/husky-cli",
3
- "version": "1.36.0",
3
+ "version": "1.37.0",
4
4
  "description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
5
5
  "type": "module",
6
6
  "bin": {