agenthud 0.8.4 → 0.9.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.
- package/README.md +106 -52
- package/dist/index.js +2 -1
- package/dist/{main-WBZ2KBF2.js → main-NHQJ23YJ.js} +969 -251
- package/dist/templates/summary-prompt.md +12 -0
- package/package.json +1 -1
- package/scripts/diff-heap.ts +74 -0
- package/scripts/memcheck-minimal.tsx +41 -0
- package/scripts/memcheck-ui.tsx +47 -0
- package/scripts/memcheck.ts +51 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/main.ts
|
|
2
|
-
import { existsSync as
|
|
3
|
-
import { join as
|
|
2
|
+
import { existsSync as existsSync6, rmSync } from "fs";
|
|
3
|
+
import { join as join6 } from "path";
|
|
4
4
|
import { createInterface } from "readline";
|
|
5
5
|
import { render } from "ink";
|
|
6
6
|
import React from "react";
|
|
@@ -35,7 +35,8 @@ var KNOWN_REPORT_FLAGS = /* @__PURE__ */ new Set([
|
|
|
35
35
|
"--detail-limit",
|
|
36
36
|
"--with-git"
|
|
37
37
|
]);
|
|
38
|
-
var
|
|
38
|
+
var KNOWN_SUMMARY_FLAGS = /* @__PURE__ */ new Set(["--date", "--prompt", "--force"]);
|
|
39
|
+
var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["report", "summary"]);
|
|
39
40
|
function getHelp() {
|
|
40
41
|
return `Usage: agenthud [options]
|
|
41
42
|
|
|
@@ -48,7 +49,7 @@ Options:
|
|
|
48
49
|
-h, --help Show this help message
|
|
49
50
|
|
|
50
51
|
Commands:
|
|
51
|
-
report [--date DATE] [--include TYPES]
|
|
52
|
+
report [--date DATE] [--include TYPES] [--format FORMAT] [--detail-limit N] [--with-git]
|
|
52
53
|
Print activity report for a date (default: today)
|
|
53
54
|
--date YYYY-MM-DD|today Date to report on
|
|
54
55
|
--include TYPES Comma-separated types or "all"
|
|
@@ -58,6 +59,12 @@ Commands:
|
|
|
58
59
|
--detail-limit N Max chars per activity detail (default: 120, 0 = unlimited)
|
|
59
60
|
--with-git Append today's git commits from cwd to report
|
|
60
61
|
|
|
62
|
+
summary [--date DATE] [--prompt TEXT] [--force]
|
|
63
|
+
Generate LLM summary of daily activity via claude CLI
|
|
64
|
+
--date YYYY-MM-DD|today Date to summarize (default: today)
|
|
65
|
+
--prompt TEXT Override prompt for this run
|
|
66
|
+
--force Regenerate even if cached (past dates)
|
|
67
|
+
|
|
61
68
|
Environment:
|
|
62
69
|
CLAUDE_PROJECTS_DIR Path to Claude projects directory
|
|
63
70
|
(default: ~/.claude/projects)
|
|
@@ -170,6 +177,53 @@ function parseArgs(args) {
|
|
|
170
177
|
reportError
|
|
171
178
|
};
|
|
172
179
|
}
|
|
180
|
+
if (args[0] === "summary") {
|
|
181
|
+
const rest = args.slice(1);
|
|
182
|
+
let summaryDate = todayLocalMidnight();
|
|
183
|
+
let summaryPrompt;
|
|
184
|
+
let summaryForce = false;
|
|
185
|
+
let summaryError;
|
|
186
|
+
for (let i = 0; i < rest.length; i++) {
|
|
187
|
+
const arg = rest[i];
|
|
188
|
+
if (!arg.startsWith("-")) continue;
|
|
189
|
+
if (!KNOWN_SUMMARY_FLAGS.has(arg)) {
|
|
190
|
+
summaryError = `Unknown option: "${arg}". Run agenthud --help for usage.`;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
if (arg === "--date" || arg === "--prompt") i++;
|
|
194
|
+
}
|
|
195
|
+
const dateIdx = rest.indexOf("--date");
|
|
196
|
+
if (dateIdx !== -1) {
|
|
197
|
+
const dateStr = rest[dateIdx + 1];
|
|
198
|
+
if (!dateStr) {
|
|
199
|
+
summaryError = "Invalid date: missing value for --date";
|
|
200
|
+
} else {
|
|
201
|
+
const parsed = parseLocalMidnight(dateStr);
|
|
202
|
+
if (!parsed) {
|
|
203
|
+
summaryError = `Invalid date: "${dateStr}". Use YYYY-MM-DD or "today".`;
|
|
204
|
+
} else {
|
|
205
|
+
summaryDate = parsed;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const promptIdx = rest.indexOf("--prompt");
|
|
210
|
+
if (promptIdx !== -1) {
|
|
211
|
+
const val = rest[promptIdx + 1];
|
|
212
|
+
if (!val) {
|
|
213
|
+
summaryError = "Invalid --prompt: missing value";
|
|
214
|
+
} else {
|
|
215
|
+
summaryPrompt = val;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (rest.includes("--force")) summaryForce = true;
|
|
219
|
+
return {
|
|
220
|
+
mode: "summary",
|
|
221
|
+
summaryDate,
|
|
222
|
+
summaryPrompt,
|
|
223
|
+
summaryForce,
|
|
224
|
+
summaryError
|
|
225
|
+
};
|
|
226
|
+
}
|
|
173
227
|
if (args[0] && !args[0].startsWith("-") && !KNOWN_SUBCOMMANDS.has(args[0])) {
|
|
174
228
|
return {
|
|
175
229
|
mode: "watch",
|
|
@@ -193,12 +247,14 @@ import { homedir } from "os";
|
|
|
193
247
|
import { join as join2 } from "path";
|
|
194
248
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
195
249
|
var CONFIG_PATH = join2(homedir(), ".agenthud", "config.yaml");
|
|
250
|
+
var STATE_PATH = join2(homedir(), ".agenthud", "state.yaml");
|
|
196
251
|
var DEFAULT_GLOBAL_CONFIG = {
|
|
197
252
|
refreshIntervalMs: 2e3,
|
|
198
253
|
logDir: join2(homedir(), ".agenthud", "logs"),
|
|
199
254
|
hiddenSessions: [],
|
|
200
255
|
hiddenSubAgents: [],
|
|
201
|
-
filterPresets: [[], ["response"], ["commit"]]
|
|
256
|
+
filterPresets: [[], ["response"], ["commit"]],
|
|
257
|
+
hiddenProjects: []
|
|
202
258
|
};
|
|
203
259
|
function parseInterval(value) {
|
|
204
260
|
const match = value.match(/^(\d+)(s|m)$/);
|
|
@@ -206,76 +262,175 @@ function parseInterval(value) {
|
|
|
206
262
|
const n = parseInt(match[1], 10);
|
|
207
263
|
return match[2] === "m" ? n * 60 * 1e3 : n * 1e3;
|
|
208
264
|
}
|
|
209
|
-
function
|
|
210
|
-
const
|
|
211
|
-
if (!existsSync(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
265
|
+
function ensureAgenthudDir() {
|
|
266
|
+
const dir = join2(homedir(), ".agenthud");
|
|
267
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
268
|
+
}
|
|
269
|
+
function writeDefaultConfig() {
|
|
270
|
+
ensureAgenthudDir();
|
|
271
|
+
const defaultYaml = `# AgentHUD user settings.
|
|
272
|
+
# App-managed state (hidden sessions/projects) lives in state.yaml.
|
|
273
|
+
|
|
274
|
+
# How often to poll for activity updates
|
|
275
|
+
refreshInterval: 2s
|
|
276
|
+
|
|
277
|
+
# Where 's' key saves activity logs
|
|
278
|
+
logDir: ~/.agenthud/logs
|
|
279
|
+
|
|
280
|
+
# Activity filter presets (cycle with 'f' key in viewer)
|
|
281
|
+
# Each list is one preset; [] means "all". First preset is the default.
|
|
282
|
+
filterPresets:
|
|
283
|
+
- []
|
|
284
|
+
- ["response"]
|
|
285
|
+
- ["commit"]
|
|
286
|
+
`;
|
|
215
287
|
try {
|
|
216
|
-
|
|
288
|
+
writeFileSync(CONFIG_PATH, defaultYaml, "utf-8");
|
|
217
289
|
} catch {
|
|
218
|
-
return config;
|
|
219
290
|
}
|
|
220
|
-
|
|
291
|
+
}
|
|
292
|
+
function writeState(state) {
|
|
293
|
+
ensureAgenthudDir();
|
|
221
294
|
try {
|
|
222
|
-
|
|
295
|
+
writeFileSync(STATE_PATH, stringifyYaml(state), "utf-8");
|
|
223
296
|
} catch {
|
|
224
|
-
return config;
|
|
225
297
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
298
|
+
}
|
|
299
|
+
function rewriteConfigWithoutHideFields(raw) {
|
|
300
|
+
const cleaned = {};
|
|
301
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
302
|
+
if (k === "hiddenSessions" || k === "hiddenSubAgents" || k === "hiddenProjects")
|
|
303
|
+
continue;
|
|
304
|
+
cleaned[k] = v;
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
writeFileSync(CONFIG_PATH, stringifyYaml(cleaned), "utf-8");
|
|
308
|
+
} catch {
|
|
229
309
|
}
|
|
230
|
-
|
|
231
|
-
|
|
310
|
+
}
|
|
311
|
+
function loadGlobalConfig() {
|
|
312
|
+
const config = { ...DEFAULT_GLOBAL_CONFIG };
|
|
313
|
+
let configRaw = {};
|
|
314
|
+
let configHadHideFields = false;
|
|
315
|
+
if (existsSync(CONFIG_PATH)) {
|
|
316
|
+
try {
|
|
317
|
+
const text = readFileSync2(CONFIG_PATH, "utf-8");
|
|
318
|
+
configRaw = parseYaml(text) ?? {};
|
|
319
|
+
} catch {
|
|
320
|
+
configRaw = {};
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
writeDefaultConfig();
|
|
232
324
|
}
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
);
|
|
325
|
+
if (typeof configRaw.refreshInterval === "string") {
|
|
326
|
+
const ms = parseInterval(configRaw.refreshInterval);
|
|
327
|
+
if (ms !== null) config.refreshIntervalMs = ms;
|
|
237
328
|
}
|
|
238
|
-
if (
|
|
239
|
-
config.
|
|
240
|
-
(s) => typeof s === "string"
|
|
241
|
-
);
|
|
329
|
+
if (typeof configRaw.logDir === "string") {
|
|
330
|
+
config.logDir = configRaw.logDir.replace(/^~/, homedir());
|
|
242
331
|
}
|
|
243
|
-
if (Array.isArray(
|
|
244
|
-
const presets =
|
|
332
|
+
if (Array.isArray(configRaw.filterPresets)) {
|
|
333
|
+
const presets = configRaw.filterPresets.filter(Array.isArray).map(
|
|
245
334
|
(p) => p.filter((t) => typeof t === "string")
|
|
246
335
|
);
|
|
247
336
|
if (presets.length > 0) config.filterPresets = presets;
|
|
248
337
|
}
|
|
338
|
+
const legacyHidden = {};
|
|
339
|
+
for (const key of [
|
|
340
|
+
"hiddenSessions",
|
|
341
|
+
"hiddenSubAgents",
|
|
342
|
+
"hiddenProjects"
|
|
343
|
+
]) {
|
|
344
|
+
if (Array.isArray(configRaw[key])) {
|
|
345
|
+
configHadHideFields = true;
|
|
346
|
+
legacyHidden[key] = configRaw[key].filter(
|
|
347
|
+
(s) => typeof s === "string"
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
let stateRaw = {};
|
|
352
|
+
if (existsSync(STATE_PATH)) {
|
|
353
|
+
try {
|
|
354
|
+
const text = readFileSync2(STATE_PATH, "utf-8");
|
|
355
|
+
stateRaw = parseYaml(text) ?? {};
|
|
356
|
+
} catch {
|
|
357
|
+
stateRaw = {};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const key of [
|
|
361
|
+
"hiddenSessions",
|
|
362
|
+
"hiddenSubAgents",
|
|
363
|
+
"hiddenProjects"
|
|
364
|
+
]) {
|
|
365
|
+
if (Array.isArray(stateRaw[key])) {
|
|
366
|
+
config[key] = stateRaw[key].filter(
|
|
367
|
+
(s) => typeof s === "string"
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (configHadHideFields) {
|
|
372
|
+
const merged = {
|
|
373
|
+
hiddenSessions: config.hiddenSessions.length > 0 ? config.hiddenSessions : legacyHidden.hiddenSessions ?? [],
|
|
374
|
+
hiddenSubAgents: config.hiddenSubAgents.length > 0 ? config.hiddenSubAgents : legacyHidden.hiddenSubAgents ?? [],
|
|
375
|
+
hiddenProjects: config.hiddenProjects.length > 0 ? config.hiddenProjects : legacyHidden.hiddenProjects ?? []
|
|
376
|
+
};
|
|
377
|
+
writeState(merged);
|
|
378
|
+
rewriteConfigWithoutHideFields(configRaw);
|
|
379
|
+
config.hiddenSessions = merged.hiddenSessions;
|
|
380
|
+
config.hiddenSubAgents = merged.hiddenSubAgents;
|
|
381
|
+
config.hiddenProjects = merged.hiddenProjects;
|
|
382
|
+
}
|
|
249
383
|
return config;
|
|
250
384
|
}
|
|
251
|
-
function
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (existsSync(
|
|
385
|
+
function updateState(updates) {
|
|
386
|
+
let state = {
|
|
387
|
+
hiddenSessions: [],
|
|
388
|
+
hiddenSubAgents: [],
|
|
389
|
+
hiddenProjects: []
|
|
390
|
+
};
|
|
391
|
+
if (existsSync(STATE_PATH)) {
|
|
258
392
|
try {
|
|
259
|
-
|
|
393
|
+
const text = readFileSync2(STATE_PATH, "utf-8");
|
|
394
|
+
const raw = parseYaml(text) ?? {};
|
|
395
|
+
for (const key of [
|
|
396
|
+
"hiddenSessions",
|
|
397
|
+
"hiddenSubAgents",
|
|
398
|
+
"hiddenProjects"
|
|
399
|
+
]) {
|
|
400
|
+
if (Array.isArray(raw[key])) {
|
|
401
|
+
state[key] = raw[key].filter(
|
|
402
|
+
(s) => typeof s === "string"
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
260
406
|
} catch {
|
|
261
|
-
raw = {};
|
|
262
407
|
}
|
|
263
408
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
409
|
+
for (const key of [
|
|
410
|
+
"hiddenSessions",
|
|
411
|
+
"hiddenSubAgents",
|
|
412
|
+
"hiddenProjects"
|
|
413
|
+
]) {
|
|
414
|
+
if (updates[key] !== void 0) {
|
|
415
|
+
state[key] = updates[key];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
writeState(state);
|
|
269
419
|
}
|
|
270
420
|
function hideSession(id) {
|
|
271
421
|
const config = loadGlobalConfig();
|
|
272
422
|
if (config.hiddenSessions.includes(id)) return;
|
|
273
|
-
|
|
423
|
+
updateState({ hiddenSessions: [...config.hiddenSessions, id] });
|
|
274
424
|
}
|
|
275
425
|
function hideSubAgent(id) {
|
|
276
426
|
const config = loadGlobalConfig();
|
|
277
427
|
if (config.hiddenSubAgents.includes(id)) return;
|
|
278
|
-
|
|
428
|
+
updateState({ hiddenSubAgents: [...config.hiddenSubAgents, id] });
|
|
429
|
+
}
|
|
430
|
+
function hideProject(name) {
|
|
431
|
+
const config = loadGlobalConfig();
|
|
432
|
+
if (config.hiddenProjects.includes(name)) return;
|
|
433
|
+
updateState({ hiddenProjects: [...config.hiddenProjects, name] });
|
|
279
434
|
}
|
|
280
435
|
function ensureLogDir(logDir) {
|
|
281
436
|
if (!existsSync(logDir)) {
|
|
@@ -317,11 +472,12 @@ function formatDateString(date) {
|
|
|
317
472
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
318
473
|
}
|
|
319
474
|
function getCommitDetail(projectPath, hash) {
|
|
475
|
+
if (!projectPath) return null;
|
|
320
476
|
try {
|
|
321
|
-
return execSync(
|
|
322
|
-
|
|
323
|
-
encoding: "utf-8"
|
|
324
|
-
|
|
477
|
+
return execSync(
|
|
478
|
+
`git --git-dir="${projectPath}/.git" show --stat --no-color ${hash}`,
|
|
479
|
+
{ encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
|
|
480
|
+
).trim();
|
|
325
481
|
} catch {
|
|
326
482
|
return null;
|
|
327
483
|
}
|
|
@@ -333,8 +489,8 @@ function parseGitCommits(projectPath, startDate, endDate) {
|
|
|
333
489
|
let raw;
|
|
334
490
|
try {
|
|
335
491
|
raw = execSync(
|
|
336
|
-
`git log --format="%ct|%h|%s" --after="${start} 00:00:00" --before="${end} 23:59:59"`,
|
|
337
|
-
{
|
|
492
|
+
`git --git-dir="${projectPath}/.git" log --format="%ct|%h|%s" --after="${start} 00:00:00" --before="${end} 23:59:59"`,
|
|
493
|
+
{ encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
|
|
338
494
|
).trim();
|
|
339
495
|
} catch {
|
|
340
496
|
return [];
|
|
@@ -377,7 +533,7 @@ function parseModelName(modelId) {
|
|
|
377
533
|
}
|
|
378
534
|
function getToolDetail(_toolName, input) {
|
|
379
535
|
if (!input) return "";
|
|
380
|
-
if (input.command) return stripAnsi(input.command
|
|
536
|
+
if (input.command) return stripAnsi(input.command);
|
|
381
537
|
if (input.file_path) return basename(input.file_path);
|
|
382
538
|
if (input.pattern) return stripAnsi(input.pattern);
|
|
383
539
|
if (input.query) return stripAnsi(input.query);
|
|
@@ -416,7 +572,7 @@ function parseActivitiesFromLines(lines) {
|
|
|
416
572
|
type: "user",
|
|
417
573
|
icon: ICONS.User,
|
|
418
574
|
label: "User",
|
|
419
|
-
detail: userText
|
|
575
|
+
detail: userText
|
|
420
576
|
});
|
|
421
577
|
}
|
|
422
578
|
}
|
|
@@ -438,7 +594,7 @@ function parseActivitiesFromLines(lines) {
|
|
|
438
594
|
type: "thinking",
|
|
439
595
|
icon: ICONS.Thinking,
|
|
440
596
|
label: "Thinking",
|
|
441
|
-
detail: block.thinking
|
|
597
|
+
detail: block.thinking
|
|
442
598
|
});
|
|
443
599
|
} else if (block.type === "tool_use" && block.name) {
|
|
444
600
|
if (block.name === "TodoWrite") continue;
|
|
@@ -463,7 +619,7 @@ function parseActivitiesFromLines(lines) {
|
|
|
463
619
|
type: "response",
|
|
464
620
|
icon: ICONS.Response,
|
|
465
621
|
label: "Response",
|
|
466
|
-
detail: block.text
|
|
622
|
+
detail: block.text
|
|
467
623
|
});
|
|
468
624
|
}
|
|
469
625
|
}
|
|
@@ -526,7 +682,15 @@ function sessionIsOnDate(session, date, activities) {
|
|
|
526
682
|
}
|
|
527
683
|
return activities.some((a) => isSameLocalDay(a.timestamp, date));
|
|
528
684
|
}
|
|
685
|
+
function flattenForOneLine(s) {
|
|
686
|
+
return s.replace(/[\r\n\t]+/g, " ").trim();
|
|
687
|
+
}
|
|
529
688
|
function truncateDetail(detail, limit) {
|
|
689
|
+
const flat = flattenForOneLine(detail);
|
|
690
|
+
if (limit === 0 || flat.length <= limit) return flat;
|
|
691
|
+
return flat.slice(0, limit);
|
|
692
|
+
}
|
|
693
|
+
function truncateRaw(detail, limit) {
|
|
530
694
|
if (limit === 0 || detail.length <= limit) return detail;
|
|
531
695
|
return detail.slice(0, limit);
|
|
532
696
|
}
|
|
@@ -573,7 +737,7 @@ function generateReport(sessions, options2) {
|
|
|
573
737
|
time: formatTime(a.timestamp),
|
|
574
738
|
icon: a.icon,
|
|
575
739
|
label: a.label,
|
|
576
|
-
detail:
|
|
740
|
+
detail: truncateRaw(a.detail, detailLimit)
|
|
577
741
|
}))
|
|
578
742
|
};
|
|
579
743
|
});
|
|
@@ -585,7 +749,7 @@ function generateReport(sessions, options2) {
|
|
|
585
749
|
time: formatTime(a.timestamp),
|
|
586
750
|
icon: a.icon,
|
|
587
751
|
label: a.label,
|
|
588
|
-
detail:
|
|
752
|
+
detail: truncateRaw(a.detail, detailLimit)
|
|
589
753
|
})),
|
|
590
754
|
subAgents: subAgentBlocks
|
|
591
755
|
};
|
|
@@ -615,6 +779,19 @@ function generateReport(sessions, options2) {
|
|
|
615
779
|
return lines.join("\n").trimEnd();
|
|
616
780
|
}
|
|
617
781
|
|
|
782
|
+
// src/data/summaryRunner.ts
|
|
783
|
+
import { spawn } from "child_process";
|
|
784
|
+
import {
|
|
785
|
+
copyFileSync,
|
|
786
|
+
createWriteStream,
|
|
787
|
+
existsSync as existsSync4,
|
|
788
|
+
mkdirSync as mkdirSync2,
|
|
789
|
+
readFileSync as readFileSync5
|
|
790
|
+
} from "fs";
|
|
791
|
+
import { homedir as homedir3 } from "os";
|
|
792
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
793
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
794
|
+
|
|
618
795
|
// src/data/sessions.ts
|
|
619
796
|
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
620
797
|
import { homedir as homedir2 } from "os";
|
|
@@ -655,7 +832,14 @@ function createBottomLine(panelWidth = DEFAULT_PANEL_WIDTH) {
|
|
|
655
832
|
return BOX.bl + BOX.h.repeat(getInnerWidth(panelWidth)) + BOX.br;
|
|
656
833
|
}
|
|
657
834
|
var SEPARATOR = "\u2500".repeat(CONTENT_WIDTH);
|
|
658
|
-
var
|
|
835
|
+
var widthCache = /* @__PURE__ */ new Map();
|
|
836
|
+
function getDisplayWidth(s) {
|
|
837
|
+
const cached = widthCache.get(s);
|
|
838
|
+
if (cached !== void 0) return cached;
|
|
839
|
+
const w = stringWidth(s);
|
|
840
|
+
widthCache.set(s, w);
|
|
841
|
+
return w;
|
|
842
|
+
}
|
|
659
843
|
|
|
660
844
|
// src/data/sessions.ts
|
|
661
845
|
function getProjectsDir() {
|
|
@@ -722,6 +906,71 @@ function readModelName(filePath) {
|
|
|
722
906
|
}
|
|
723
907
|
return null;
|
|
724
908
|
}
|
|
909
|
+
var SYSTEM_PREFIXES = [
|
|
910
|
+
"<command-name>",
|
|
911
|
+
"<command-message>",
|
|
912
|
+
"<command-args>",
|
|
913
|
+
"<local-command-stdout>",
|
|
914
|
+
"<local-command-caveat>",
|
|
915
|
+
"<system-reminder>",
|
|
916
|
+
"<bash-input>",
|
|
917
|
+
"<bash-stdout>",
|
|
918
|
+
"<bash-stderr>",
|
|
919
|
+
"<user-prompt-submit-hook>"
|
|
920
|
+
];
|
|
921
|
+
function isSystemNoise(text) {
|
|
922
|
+
const trimmed = text.trimStart();
|
|
923
|
+
return SYSTEM_PREFIXES.some((p) => trimmed.startsWith(p));
|
|
924
|
+
}
|
|
925
|
+
function readFirstUserPrompt(filePath) {
|
|
926
|
+
if (!existsSync3(filePath)) return null;
|
|
927
|
+
let content;
|
|
928
|
+
try {
|
|
929
|
+
content = readFileSync4(filePath, "utf-8");
|
|
930
|
+
} catch {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
for (const line of content.split("\n")) {
|
|
934
|
+
if (!line.trim()) continue;
|
|
935
|
+
let entry;
|
|
936
|
+
try {
|
|
937
|
+
entry = JSON.parse(line);
|
|
938
|
+
} catch {
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
if (entry.type !== "user") continue;
|
|
942
|
+
if (entry.toolUseResult !== void 0) continue;
|
|
943
|
+
const raw = entry.message?.content;
|
|
944
|
+
let text;
|
|
945
|
+
if (typeof raw === "string") {
|
|
946
|
+
text = raw;
|
|
947
|
+
} else if (Array.isArray(raw)) {
|
|
948
|
+
const textBlock = raw.find(
|
|
949
|
+
(b) => b && b.type === "text" && typeof b.text === "string"
|
|
950
|
+
);
|
|
951
|
+
text = textBlock?.text ?? "";
|
|
952
|
+
} else {
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (!text || isSystemNoise(text)) continue;
|
|
956
|
+
const firstLine = text.split("\n").find((l) => l.trim()) ?? "";
|
|
957
|
+
if (!firstLine || isSystemNoise(firstLine)) continue;
|
|
958
|
+
const trimmed = firstLine.trim();
|
|
959
|
+
return trimmed.length > 80 ? trimmed.slice(0, 80) : trimmed;
|
|
960
|
+
}
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
function readEntrypoint(filePath) {
|
|
964
|
+
if (!existsSync3(filePath)) return null;
|
|
965
|
+
try {
|
|
966
|
+
const firstLine = readFileSync4(filePath, "utf-8").split("\n")[0];
|
|
967
|
+
if (!firstLine) return null;
|
|
968
|
+
const entry = JSON.parse(firstLine);
|
|
969
|
+
return typeof entry.entrypoint === "string" ? entry.entrypoint : null;
|
|
970
|
+
} catch {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
725
974
|
function buildSubAgents(parentId, projectDir, config, projectName) {
|
|
726
975
|
const subagentsDir = join3(projectDir, parentId, "subagents");
|
|
727
976
|
if (!existsSync3(subagentsDir)) return [];
|
|
@@ -751,7 +1000,9 @@ function buildSubAgents(parentId, projectDir, config, projectName) {
|
|
|
751
1000
|
modelName: readModelName(filePath),
|
|
752
1001
|
subAgents: [],
|
|
753
1002
|
agentId: agentId ?? void 0,
|
|
754
|
-
taskDescription: taskDescription ?? void 0
|
|
1003
|
+
taskDescription: taskDescription ?? void 0,
|
|
1004
|
+
nonInteractive: false,
|
|
1005
|
+
firstUserPrompt: null
|
|
755
1006
|
};
|
|
756
1007
|
} catch {
|
|
757
1008
|
return null;
|
|
@@ -763,7 +1014,12 @@ function buildSubAgents(parentId, projectDir, config, projectName) {
|
|
|
763
1014
|
function discoverSessions(config) {
|
|
764
1015
|
const projectsDir = getProjectsDir();
|
|
765
1016
|
if (!existsSync3(projectsDir)) {
|
|
766
|
-
return {
|
|
1017
|
+
return {
|
|
1018
|
+
projects: [],
|
|
1019
|
+
coldProjects: [],
|
|
1020
|
+
totalCount: 0,
|
|
1021
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1022
|
+
};
|
|
767
1023
|
}
|
|
768
1024
|
let projectDirs;
|
|
769
1025
|
try {
|
|
@@ -775,7 +1031,12 @@ function discoverSessions(config) {
|
|
|
775
1031
|
}
|
|
776
1032
|
});
|
|
777
1033
|
} catch {
|
|
778
|
-
return {
|
|
1034
|
+
return {
|
|
1035
|
+
projects: [],
|
|
1036
|
+
coldProjects: [],
|
|
1037
|
+
totalCount: 0,
|
|
1038
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1039
|
+
};
|
|
779
1040
|
}
|
|
780
1041
|
const allSessions = [];
|
|
781
1042
|
for (const encodedDir of projectDirs) {
|
|
@@ -806,38 +1067,193 @@ function discoverSessions(config) {
|
|
|
806
1067
|
lastModifiedMs: stat.mtimeMs,
|
|
807
1068
|
status: getSessionStatus(stat.mtimeMs),
|
|
808
1069
|
modelName: readModelName(filePath),
|
|
809
|
-
subAgents
|
|
1070
|
+
subAgents,
|
|
1071
|
+
nonInteractive: readEntrypoint(filePath) === "sdk-cli",
|
|
1072
|
+
firstUserPrompt: readFirstUserPrompt(filePath)
|
|
810
1073
|
});
|
|
811
1074
|
} catch {
|
|
812
1075
|
}
|
|
813
1076
|
}
|
|
814
1077
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1078
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
1079
|
+
for (const s of allSessions) {
|
|
1080
|
+
if (config.hiddenSessions.includes(s.hideKey)) continue;
|
|
1081
|
+
const arr = byProject.get(s.projectPath) ?? [];
|
|
1082
|
+
arr.push(s);
|
|
1083
|
+
byProject.set(s.projectPath, arr);
|
|
1084
|
+
}
|
|
1085
|
+
const statusOrder = {
|
|
1086
|
+
hot: 0,
|
|
1087
|
+
warm: 1,
|
|
1088
|
+
cool: 2,
|
|
1089
|
+
cold: 3
|
|
1090
|
+
};
|
|
1091
|
+
const allProjects = [];
|
|
1092
|
+
for (const [projectPath, sessions] of byProject) {
|
|
1093
|
+
if (sessions.length === 0) continue;
|
|
1094
|
+
const projectName = sessions[0].projectName;
|
|
1095
|
+
if (config.hiddenProjects.includes(projectName)) continue;
|
|
1096
|
+
sessions.sort((a, b) => {
|
|
1097
|
+
if (a.nonInteractive !== b.nonInteractive) {
|
|
1098
|
+
return a.nonInteractive ? 1 : -1;
|
|
1099
|
+
}
|
|
1100
|
+
const statusDiff = statusOrder[a.status] - statusOrder[b.status];
|
|
1101
|
+
if (statusDiff !== 0) return statusDiff;
|
|
1102
|
+
return b.lastModifiedMs - a.lastModifiedMs;
|
|
1103
|
+
});
|
|
1104
|
+
const hotness = sessions[0].status;
|
|
1105
|
+
allProjects.push({ name: projectName, projectPath, sessions, hotness });
|
|
1106
|
+
}
|
|
1107
|
+
const activeProjects = allProjects.filter((p) => p.hotness !== "cold");
|
|
1108
|
+
const coldProjects = allProjects.filter((p) => p.hotness === "cold");
|
|
1109
|
+
activeProjects.sort((a, b) => {
|
|
1110
|
+
const statusDiff = statusOrder[a.hotness] - statusOrder[b.hotness];
|
|
823
1111
|
if (statusDiff !== 0) return statusDiff;
|
|
824
|
-
return b.lastModifiedMs - a.lastModifiedMs;
|
|
1112
|
+
return b.sessions[0].lastModifiedMs - a.sessions[0].lastModifiedMs;
|
|
825
1113
|
});
|
|
826
|
-
const
|
|
827
|
-
(
|
|
1114
|
+
const countSessions = (projects) => projects.reduce(
|
|
1115
|
+
(sum, p) => sum + p.sessions.length + p.sessions.reduce((s, sn) => s + sn.subAgents.length, 0),
|
|
1116
|
+
0
|
|
828
1117
|
);
|
|
829
|
-
const totalCount =
|
|
1118
|
+
const totalCount = countSessions(activeProjects) + countSessions(coldProjects);
|
|
830
1119
|
return {
|
|
831
|
-
|
|
1120
|
+
projects: activeProjects,
|
|
1121
|
+
coldProjects,
|
|
832
1122
|
totalCount,
|
|
833
1123
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
834
1124
|
};
|
|
835
1125
|
}
|
|
836
1126
|
|
|
1127
|
+
// src/data/summaryRunner.ts
|
|
1128
|
+
function agenthudHomeDir() {
|
|
1129
|
+
const dir = join4(homedir3(), ".agenthud");
|
|
1130
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
1131
|
+
return dir;
|
|
1132
|
+
}
|
|
1133
|
+
function summariesDir() {
|
|
1134
|
+
const dir = join4(agenthudHomeDir(), "summaries");
|
|
1135
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
1136
|
+
return dir;
|
|
1137
|
+
}
|
|
1138
|
+
function userPromptPath() {
|
|
1139
|
+
return join4(homedir3(), ".agenthud", "summary-prompt.md");
|
|
1140
|
+
}
|
|
1141
|
+
function templatePath() {
|
|
1142
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
1143
|
+
return join4(here, "templates", "summary-prompt.md");
|
|
1144
|
+
}
|
|
1145
|
+
function dateKey(d) {
|
|
1146
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
1147
|
+
}
|
|
1148
|
+
function cachePath(date) {
|
|
1149
|
+
return join4(summariesDir(), `${dateKey(date)}.md`);
|
|
1150
|
+
}
|
|
1151
|
+
function isSameLocalDay2(a, b) {
|
|
1152
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
1153
|
+
}
|
|
1154
|
+
function ensureUserPromptFile() {
|
|
1155
|
+
const p = userPromptPath();
|
|
1156
|
+
if (existsSync4(p)) return;
|
|
1157
|
+
const dir = dirname2(p);
|
|
1158
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
1159
|
+
try {
|
|
1160
|
+
copyFileSync(templatePath(), p);
|
|
1161
|
+
} catch {
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function resolvePrompt(override) {
|
|
1165
|
+
if (override) return override;
|
|
1166
|
+
const p = userPromptPath();
|
|
1167
|
+
if (existsSync4(p)) {
|
|
1168
|
+
try {
|
|
1169
|
+
return readFileSync5(p, "utf-8");
|
|
1170
|
+
} catch {
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
try {
|
|
1174
|
+
return readFileSync5(templatePath(), "utf-8");
|
|
1175
|
+
} catch {
|
|
1176
|
+
return "Summarize the activity log below.";
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
async function runSummary(options2) {
|
|
1180
|
+
ensureUserPromptFile();
|
|
1181
|
+
const isToday = isSameLocalDay2(options2.date, options2.today);
|
|
1182
|
+
const cached = cachePath(options2.date);
|
|
1183
|
+
if (!isToday && !options2.force && existsSync4(cached)) {
|
|
1184
|
+
try {
|
|
1185
|
+
const content = readFileSync5(cached, "utf-8");
|
|
1186
|
+
process.stdout.write(content);
|
|
1187
|
+
if (!content.endsWith("\n")) process.stdout.write("\n");
|
|
1188
|
+
return 0;
|
|
1189
|
+
} catch {
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
const config = loadGlobalConfig();
|
|
1193
|
+
const sessions = discoverSessions(config);
|
|
1194
|
+
const reportMarkdown = generateReport(sessions.sessions, {
|
|
1195
|
+
date: options2.date,
|
|
1196
|
+
include: ["response", "bash", "edit", "thinking"],
|
|
1197
|
+
format: "markdown",
|
|
1198
|
+
detailLimit: 0,
|
|
1199
|
+
withGit: true
|
|
1200
|
+
});
|
|
1201
|
+
const prompt = resolvePrompt(options2.prompt);
|
|
1202
|
+
return new Promise((resolve) => {
|
|
1203
|
+
const proc = spawn("claude", ["-p", prompt], {
|
|
1204
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1205
|
+
cwd: agenthudHomeDir()
|
|
1206
|
+
});
|
|
1207
|
+
let cacheStream = null;
|
|
1208
|
+
cacheStream = createWriteStream(cached, { encoding: "utf-8" });
|
|
1209
|
+
cacheStream.on("error", (err) => {
|
|
1210
|
+
process.stderr.write(
|
|
1211
|
+
`agenthud: warning: cannot write cache (${err.message})
|
|
1212
|
+
`
|
|
1213
|
+
);
|
|
1214
|
+
cacheStream = null;
|
|
1215
|
+
});
|
|
1216
|
+
let stderrBuf = "";
|
|
1217
|
+
proc.on("error", (err) => {
|
|
1218
|
+
if (err.code === "ENOENT") {
|
|
1219
|
+
process.stderr.write(
|
|
1220
|
+
"Error: claude CLI not found. Install: npm i -g @anthropic-ai/claude-code\n"
|
|
1221
|
+
);
|
|
1222
|
+
resolve(1);
|
|
1223
|
+
} else {
|
|
1224
|
+
process.stderr.write(`Error: ${err.message}
|
|
1225
|
+
`);
|
|
1226
|
+
resolve(1);
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
proc.stdout.on("data", (chunk) => {
|
|
1230
|
+
process.stdout.write(chunk);
|
|
1231
|
+
cacheStream?.write(chunk);
|
|
1232
|
+
});
|
|
1233
|
+
proc.stderr.on("data", (chunk) => {
|
|
1234
|
+
stderrBuf += chunk.toString();
|
|
1235
|
+
process.stderr.write(chunk);
|
|
1236
|
+
});
|
|
1237
|
+
proc.on("close", (code) => {
|
|
1238
|
+
cacheStream?.end();
|
|
1239
|
+
if (code !== 0) {
|
|
1240
|
+
const lower = stderrBuf.toLowerCase();
|
|
1241
|
+
if (lower.includes("not authenticated") || lower.includes("login") || lower.includes(" auth")) {
|
|
1242
|
+
process.stderr.write(
|
|
1243
|
+
"\nHint: claude appears to be unauthenticated. Run: claude\n"
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
resolve(code ?? 1);
|
|
1248
|
+
});
|
|
1249
|
+
proc.stdin.end(reportMarkdown);
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
|
|
837
1253
|
// src/ui/App.tsx
|
|
838
|
-
import { existsSync as
|
|
839
|
-
import { join as
|
|
840
|
-
import { Box as
|
|
1254
|
+
import { existsSync as existsSync5, watch, writeFileSync as writeFileSync2 } from "fs";
|
|
1255
|
+
import { join as join5 } from "path";
|
|
1256
|
+
import { Box as Box5, Text as Text5, useApp, useInput, useStdout } from "ink";
|
|
841
1257
|
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
842
1258
|
|
|
843
1259
|
// src/ui/ActivityViewerPanel.tsx
|
|
@@ -875,20 +1291,21 @@ function formatActivityTime(date, now) {
|
|
|
875
1291
|
const day = String(date.getDate()).padStart(2, "0");
|
|
876
1292
|
return `${month}/${day} ${time}`;
|
|
877
1293
|
}
|
|
1294
|
+
function flattenForOneLine2(detail) {
|
|
1295
|
+
return detail.replace(/[\r\n\t]+/g, " ").trim();
|
|
1296
|
+
}
|
|
878
1297
|
function truncateDetail2(detail, maxWidth) {
|
|
879
|
-
|
|
1298
|
+
const flat = flattenForOneLine2(detail);
|
|
1299
|
+
if (getDisplayWidth(flat) <= maxWidth) return flat;
|
|
880
1300
|
let truncated = "";
|
|
881
|
-
let
|
|
882
|
-
for (const char of
|
|
1301
|
+
let width = 0;
|
|
1302
|
+
for (const char of flat) {
|
|
883
1303
|
const charWidth = getDisplayWidth(char);
|
|
884
|
-
if (
|
|
885
|
-
truncated += "...";
|
|
886
|
-
break;
|
|
887
|
-
}
|
|
1304
|
+
if (width + charWidth > maxWidth - 1) break;
|
|
888
1305
|
truncated += char;
|
|
889
|
-
|
|
1306
|
+
width += charWidth;
|
|
890
1307
|
}
|
|
891
|
-
return truncated
|
|
1308
|
+
return `${truncated}\u2026`;
|
|
892
1309
|
}
|
|
893
1310
|
function ActivityViewerPanel({
|
|
894
1311
|
activities,
|
|
@@ -1091,10 +1508,114 @@ function DetailViewPanel({
|
|
|
1091
1508
|
] });
|
|
1092
1509
|
}
|
|
1093
1510
|
|
|
1511
|
+
// src/ui/HelpPanel.tsx
|
|
1512
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
1513
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1514
|
+
var SECTIONS = [
|
|
1515
|
+
{
|
|
1516
|
+
title: "Session tree",
|
|
1517
|
+
rows: [
|
|
1518
|
+
["\u2191 \u2193 / k j", "Move selection"],
|
|
1519
|
+
["PgUp / Ctrl+B", "Page up"],
|
|
1520
|
+
["PgDn / Ctrl+F", "Page down"],
|
|
1521
|
+
["\u21B5", "Expand/collapse project, session, or summary"],
|
|
1522
|
+
["h", "Hide selected (project/session/sub-agent)"],
|
|
1523
|
+
["Tab", "Switch focus to activity viewer"],
|
|
1524
|
+
["r", "Refresh now"]
|
|
1525
|
+
]
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
title: "Activity viewer",
|
|
1529
|
+
rows: [
|
|
1530
|
+
["\u2191 \u2193 / k j", "Scroll one line"],
|
|
1531
|
+
["PgUp/Dn, Ctrl+B/F", "Scroll one page"],
|
|
1532
|
+
["Ctrl+U / Ctrl+D", "Scroll half page"],
|
|
1533
|
+
["g", "Jump to live (newest)"],
|
|
1534
|
+
["G", "Jump to oldest"],
|
|
1535
|
+
["\u21B5", "Open detail view for selected activity"],
|
|
1536
|
+
["f", "Cycle filter preset (set in config.yaml)"],
|
|
1537
|
+
["s", "Save activity log to ~/.agenthud/logs/"],
|
|
1538
|
+
["Tab", "Switch focus to session tree"]
|
|
1539
|
+
]
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
title: "Detail view",
|
|
1543
|
+
rows: [
|
|
1544
|
+
["\u2191 \u2193 / k j", "Scroll"],
|
|
1545
|
+
["\u21B5 / Esc / q", "Close"]
|
|
1546
|
+
]
|
|
1547
|
+
},
|
|
1548
|
+
{
|
|
1549
|
+
title: "Always available",
|
|
1550
|
+
rows: [
|
|
1551
|
+
["?", "Toggle this help"],
|
|
1552
|
+
["q", "Quit (or close detail/help)"]
|
|
1553
|
+
]
|
|
1554
|
+
},
|
|
1555
|
+
{
|
|
1556
|
+
title: "CLI commands",
|
|
1557
|
+
rows: [
|
|
1558
|
+
["agenthud report", "Print activity for a date as Markdown/JSON"],
|
|
1559
|
+
["agenthud summary", "LLM-summarize a day via claude -p (cached)"],
|
|
1560
|
+
["agenthud --help", "Full CLI usage"]
|
|
1561
|
+
]
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
title: "Files",
|
|
1565
|
+
rows: [
|
|
1566
|
+
["~/.agenthud/config.yaml", "User settings (edit freely)"],
|
|
1567
|
+
["~/.agenthud/state.yaml", "Hidden items (app-managed)"],
|
|
1568
|
+
["~/.agenthud/summary-prompt.md", "LLM prompt template"],
|
|
1569
|
+
["~/.agenthud/summaries/", "Cached daily summaries"]
|
|
1570
|
+
]
|
|
1571
|
+
}
|
|
1572
|
+
];
|
|
1573
|
+
function HelpPanel({
|
|
1574
|
+
width,
|
|
1575
|
+
height
|
|
1576
|
+
}) {
|
|
1577
|
+
const allKeys = SECTIONS.flatMap((s) => s.rows.map((r) => r[0]));
|
|
1578
|
+
const keyColumn = Math.min(
|
|
1579
|
+
30,
|
|
1580
|
+
Math.max(...allKeys.map((k) => getDisplayWidth(k)))
|
|
1581
|
+
);
|
|
1582
|
+
const padTo = (s, w) => {
|
|
1583
|
+
const pad = Math.max(0, w - getDisplayWidth(s));
|
|
1584
|
+
return s + " ".repeat(pad);
|
|
1585
|
+
};
|
|
1586
|
+
const lines = [];
|
|
1587
|
+
lines.push(
|
|
1588
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "AgentHUD Help" }, "title")
|
|
1589
|
+
);
|
|
1590
|
+
lines.push(/* @__PURE__ */ jsx3(Text3, { children: " " }, "title-sp"));
|
|
1591
|
+
for (let s = 0; s < SECTIONS.length; s++) {
|
|
1592
|
+
if (s > 0) lines.push(/* @__PURE__ */ jsx3(Text3, { children: " " }, `sp-${s}`));
|
|
1593
|
+
lines.push(
|
|
1594
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: SECTIONS[s].title }, `title-${s}`)
|
|
1595
|
+
);
|
|
1596
|
+
for (let r = 0; r < SECTIONS[s].rows.length; r++) {
|
|
1597
|
+
const [key, desc] = SECTIONS[s].rows[r];
|
|
1598
|
+
const isCli = key.trim().startsWith("agenthud");
|
|
1599
|
+
const isFile = key.includes("~/.agenthud");
|
|
1600
|
+
lines.push(
|
|
1601
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1602
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " " }),
|
|
1603
|
+
/* @__PURE__ */ jsx3(Text3, { color: isCli ? "cyan" : isFile ? "green" : void 0, children: padTo(key, keyColumn) }),
|
|
1604
|
+
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
1605
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: desc })
|
|
1606
|
+
] }, `row-${s}-${r}`)
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
const visible = lines.slice(0, height);
|
|
1611
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", width, children: visible });
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1094
1614
|
// src/ui/hooks/useHotkeys.ts
|
|
1095
1615
|
function useHotkeys({
|
|
1096
1616
|
focus,
|
|
1097
1617
|
detailMode,
|
|
1618
|
+
helpMode,
|
|
1098
1619
|
onSwitchFocus,
|
|
1099
1620
|
onScrollUp,
|
|
1100
1621
|
onScrollDown,
|
|
@@ -1113,9 +1634,21 @@ function useHotkeys({
|
|
|
1113
1634
|
onDetailScrollUp,
|
|
1114
1635
|
onDetailScrollDown,
|
|
1115
1636
|
onFilter,
|
|
1637
|
+
onHelp,
|
|
1116
1638
|
filterLabel
|
|
1117
1639
|
}) {
|
|
1118
1640
|
const handleInput = (input, key) => {
|
|
1641
|
+
if (helpMode) {
|
|
1642
|
+
if (key.return || key.escape || input === "q" || input === "?") {
|
|
1643
|
+
onHelp();
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
if (input === "?") {
|
|
1649
|
+
onHelp();
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1119
1652
|
if (detailMode) {
|
|
1120
1653
|
if (key.upArrow || input === "k") {
|
|
1121
1654
|
onDetailScrollUp();
|
|
@@ -1214,13 +1747,14 @@ function useHotkeys({
|
|
|
1214
1747
|
}
|
|
1215
1748
|
}
|
|
1216
1749
|
};
|
|
1217
|
-
const statusBarItems = detailMode ? ["\u2191\u2193/jk: scroll", "\u21B5/Esc: close"] : focus === "tree" ? [
|
|
1750
|
+
const statusBarItems = helpMode ? ["\u21B5/Esc/q/?: close"] : detailMode ? ["\u2191\u2193/jk: scroll", "\u21B5/Esc: close", "?: help"] : focus === "tree" ? [
|
|
1218
1751
|
"Tab: viewer",
|
|
1219
1752
|
"\u2191\u2193/jk: select",
|
|
1220
1753
|
"PgUp/Dn: page",
|
|
1221
1754
|
"\u21B5: expand",
|
|
1222
1755
|
"h: hide",
|
|
1223
1756
|
"r: refresh",
|
|
1757
|
+
"?: help",
|
|
1224
1758
|
"q: quit"
|
|
1225
1759
|
] : [
|
|
1226
1760
|
"Tab: sessions",
|
|
@@ -1230,6 +1764,7 @@ function useHotkeys({
|
|
|
1230
1764
|
"G: oldest",
|
|
1231
1765
|
"\u21B5: detail",
|
|
1232
1766
|
`f: ${filterLabel}`,
|
|
1767
|
+
"?: help",
|
|
1233
1768
|
"q: quit"
|
|
1234
1769
|
];
|
|
1235
1770
|
return { handleInput, statusBarItems };
|
|
@@ -1251,9 +1786,9 @@ function useSpinner(active, intervalMs = 100) {
|
|
|
1251
1786
|
}
|
|
1252
1787
|
|
|
1253
1788
|
// src/ui/SessionTreePanel.tsx
|
|
1254
|
-
import { homedir as
|
|
1255
|
-
import { Box as
|
|
1256
|
-
import { jsx as
|
|
1789
|
+
import { homedir as homedir4 } from "os";
|
|
1790
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
1791
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1257
1792
|
function formatElapsed(lastModifiedMs) {
|
|
1258
1793
|
const elapsed = Date.now() - lastModifiedMs;
|
|
1259
1794
|
const seconds = Math.floor(elapsed / 1e3);
|
|
@@ -1277,7 +1812,7 @@ function getStatusColor(status) {
|
|
|
1277
1812
|
}
|
|
1278
1813
|
}
|
|
1279
1814
|
function formatProjectPath(projectPath) {
|
|
1280
|
-
const home =
|
|
1815
|
+
const home = homedir4();
|
|
1281
1816
|
const raw = projectPath.startsWith(home) ? `~${projectPath.slice(home.length)}` : projectPath;
|
|
1282
1817
|
return raw;
|
|
1283
1818
|
}
|
|
@@ -1293,23 +1828,24 @@ function SessionRow({
|
|
|
1293
1828
|
prefix,
|
|
1294
1829
|
contentWidth
|
|
1295
1830
|
}) {
|
|
1296
|
-
const isParent = prefix === "";
|
|
1831
|
+
const isParent = prefix === " ";
|
|
1297
1832
|
const statusColor = getStatusColor(session.status);
|
|
1298
1833
|
const badge = `[${session.status}]`;
|
|
1299
1834
|
const elapsed = formatElapsed(session.lastModifiedMs);
|
|
1300
1835
|
const model = session.modelName ?? "";
|
|
1301
|
-
const
|
|
1302
|
-
const
|
|
1836
|
+
const isNonInteractive = session.nonInteractive;
|
|
1837
|
+
const rawName = isParent ? isNonInteractive ? `(#${session.id.slice(0, 4)})` : `#${session.id.slice(0, 4)}` : session.agentId ?? session.id.slice(0, 8);
|
|
1838
|
+
const shortIdDisplay = "";
|
|
1303
1839
|
const rightParts = [elapsed];
|
|
1304
1840
|
if (model) rightParts.push(model);
|
|
1305
1841
|
const rightSide = rightParts.join(" ");
|
|
1306
|
-
const
|
|
1307
|
-
const leftCoreWidth = getDisplayWidth(
|
|
1842
|
+
const leftCoreBase = `${prefix}${rawName}${shortIdDisplay} ${badge}`;
|
|
1843
|
+
const leftCoreWidth = getDisplayWidth(leftCoreBase);
|
|
1308
1844
|
const rightWidth = getDisplayWidth(rightSide);
|
|
1309
1845
|
const middleAvailable = contentWidth - leftCoreWidth - 1 - rightWidth - 1;
|
|
1310
1846
|
let middleText = "";
|
|
1311
1847
|
if (middleAvailable > 3) {
|
|
1312
|
-
const raw = isParent ? session.
|
|
1848
|
+
const raw = isParent ? session.firstUserPrompt ?? "" : session.taskDescription ?? "";
|
|
1313
1849
|
if (raw) {
|
|
1314
1850
|
const truncated = truncatePath(raw, middleAvailable);
|
|
1315
1851
|
if (truncated) middleText = truncated;
|
|
@@ -1321,42 +1857,56 @@ function SessionRow({
|
|
|
1321
1857
|
contentWidth - leftCoreWidth - getDisplayWidth(middleSection) - rightWidth
|
|
1322
1858
|
);
|
|
1323
1859
|
const gap = " ".repeat(gapWidth);
|
|
1324
|
-
const fullLine =
|
|
1860
|
+
const fullLine = leftCoreBase + middleSection + gap + rightSide;
|
|
1325
1861
|
const linePadding = Math.max(0, contentWidth - getDisplayWidth(fullLine));
|
|
1326
1862
|
const highlight = isSelected && hasFocus;
|
|
1327
|
-
|
|
1863
|
+
const shouldDim = isNonInteractive;
|
|
1864
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1328
1865
|
BOX.v,
|
|
1329
1866
|
" ",
|
|
1330
|
-
/* @__PURE__ */
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1867
|
+
/* @__PURE__ */ jsxs4(
|
|
1868
|
+
Text4,
|
|
1869
|
+
{
|
|
1870
|
+
backgroundColor: highlight ? "blue" : void 0,
|
|
1871
|
+
bold: highlight,
|
|
1872
|
+
dimColor: shouldDim && !highlight,
|
|
1873
|
+
children: [
|
|
1874
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: shouldDim && !highlight, children: prefix }),
|
|
1875
|
+
/* @__PURE__ */ jsx4(Text4, { bold: !shouldDim, children: rawName }),
|
|
1876
|
+
shortIdDisplay ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: shortIdDisplay }) : null,
|
|
1877
|
+
/* @__PURE__ */ jsx4(Text4, { children: " " }),
|
|
1878
|
+
/* @__PURE__ */ jsx4(Text4, { color: statusColor, children: badge }),
|
|
1879
|
+
middleText ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: middleSection }) : null,
|
|
1880
|
+
/* @__PURE__ */ jsx4(Text4, { children: gap }),
|
|
1881
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: elapsed }),
|
|
1882
|
+
model ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: ` ${model}` }) : null
|
|
1883
|
+
]
|
|
1884
|
+
}
|
|
1885
|
+
),
|
|
1341
1886
|
" ".repeat(linePadding),
|
|
1342
1887
|
BOX.v
|
|
1343
1888
|
] });
|
|
1344
1889
|
}
|
|
1345
1890
|
function appendSessionRows(result, session, expandedIds) {
|
|
1346
|
-
const
|
|
1891
|
+
const isCold = session.status === "cold";
|
|
1892
|
+
const sessionCollapsedKey = `__collapsed-session-${session.id}`;
|
|
1893
|
+
const sessionExpandedKey = `__expanded-session-${session.id}`;
|
|
1894
|
+
const sessionHidden = isCold ? !expandedIds.has(sessionExpandedKey) : expandedIds.has(sessionCollapsedKey);
|
|
1895
|
+
if (sessionHidden) return;
|
|
1896
|
+
const subAgentsFullyExpanded = expandedIds.has(session.id) || isCold && expandedIds.has(sessionExpandedKey);
|
|
1347
1897
|
const hotWarm = session.subAgents.filter(
|
|
1348
1898
|
(s) => s.status === "hot" || s.status === "warm"
|
|
1349
1899
|
);
|
|
1350
1900
|
const cool = session.subAgents.filter((s) => s.status === "cool");
|
|
1351
1901
|
const cold = session.subAgents.filter((s) => s.status === "cold");
|
|
1352
|
-
if (
|
|
1902
|
+
if (subAgentsFullyExpanded) {
|
|
1353
1903
|
const all = [...hotWarm, ...cool, ...cold];
|
|
1354
1904
|
for (let i = 0; i < all.length; i++) {
|
|
1355
1905
|
const isLast = i === all.length - 1;
|
|
1356
1906
|
result.push({
|
|
1357
1907
|
kind: "session",
|
|
1358
1908
|
session: all[i],
|
|
1359
|
-
prefix:
|
|
1909
|
+
prefix: ` ${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}\xBB `
|
|
1360
1910
|
});
|
|
1361
1911
|
}
|
|
1362
1912
|
} else {
|
|
@@ -1366,7 +1916,7 @@ function appendSessionRows(result, session, expandedIds) {
|
|
|
1366
1916
|
result.push({
|
|
1367
1917
|
kind: "session",
|
|
1368
1918
|
session: hotWarm[i],
|
|
1369
|
-
prefix:
|
|
1919
|
+
prefix: ` ${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}\xBB `
|
|
1370
1920
|
});
|
|
1371
1921
|
}
|
|
1372
1922
|
if (hasSummary) {
|
|
@@ -1379,25 +1929,65 @@ function appendSessionRows(result, session, expandedIds) {
|
|
|
1379
1929
|
}
|
|
1380
1930
|
}
|
|
1381
1931
|
}
|
|
1382
|
-
function flattenSessions(
|
|
1932
|
+
function flattenSessions(projects, coldProjects, expandedIds) {
|
|
1383
1933
|
const result = [];
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
result.push({ kind: "cold-sessions-summary", count: coldSessions.length });
|
|
1392
|
-
if (expandedIds.has("__cold__")) {
|
|
1393
|
-
for (const session of coldSessions) {
|
|
1394
|
-
result.push({ kind: "session", session, prefix: "" });
|
|
1934
|
+
for (const project of projects) {
|
|
1935
|
+
const sentinelId = `__proj-${project.name}__`;
|
|
1936
|
+
result.push({ kind: "project", project, sentinelId });
|
|
1937
|
+
const collapsed = expandedIds.has(`__collapsed-${sentinelId}`);
|
|
1938
|
+
if (!collapsed) {
|
|
1939
|
+
for (const session of project.sessions) {
|
|
1940
|
+
result.push({ kind: "session", session, prefix: " " });
|
|
1395
1941
|
appendSessionRows(result, session, expandedIds);
|
|
1396
1942
|
}
|
|
1397
1943
|
}
|
|
1398
1944
|
}
|
|
1945
|
+
if (coldProjects.length > 0) {
|
|
1946
|
+
result.push({ kind: "cold-projects-summary", count: coldProjects.length });
|
|
1947
|
+
if (expandedIds.has("__cold__")) {
|
|
1948
|
+
for (const project of coldProjects) {
|
|
1949
|
+
const sentinelId = `__proj-${project.name}__`;
|
|
1950
|
+
result.push({ kind: "project", project, sentinelId });
|
|
1951
|
+
const expanded = expandedIds.has(`__expanded-${sentinelId}`);
|
|
1952
|
+
if (expanded) {
|
|
1953
|
+
for (const session of project.sessions) {
|
|
1954
|
+
result.push({ kind: "session", session, prefix: " " });
|
|
1955
|
+
appendSessionRows(result, session, expandedIds);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1399
1961
|
return result;
|
|
1400
1962
|
}
|
|
1963
|
+
function ProjectRow({
|
|
1964
|
+
project,
|
|
1965
|
+
isSelected,
|
|
1966
|
+
hasFocus,
|
|
1967
|
+
contentWidth
|
|
1968
|
+
}) {
|
|
1969
|
+
const nameText = `> ${project.name}`;
|
|
1970
|
+
const pathText = project.projectPath ? formatProjectPath(project.projectPath) : "";
|
|
1971
|
+
const nameWidth = getDisplayWidth(nameText);
|
|
1972
|
+
const pathWidth = pathText ? getDisplayWidth(pathText) : 0;
|
|
1973
|
+
const gapWidth = pathText ? 2 : 0;
|
|
1974
|
+
const totalWidth = nameWidth + gapWidth + pathWidth;
|
|
1975
|
+
const padding = Math.max(0, contentWidth - totalWidth);
|
|
1976
|
+
const highlight = isSelected && hasFocus;
|
|
1977
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1978
|
+
BOX.v,
|
|
1979
|
+
" ",
|
|
1980
|
+
/* @__PURE__ */ jsxs4(Text4, { backgroundColor: highlight ? "blue" : void 0, bold: !highlight, children: [
|
|
1981
|
+
nameText,
|
|
1982
|
+
pathText ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1983
|
+
" ",
|
|
1984
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: pathText })
|
|
1985
|
+
] }) : null,
|
|
1986
|
+
" ".repeat(padding)
|
|
1987
|
+
] }),
|
|
1988
|
+
BOX.v
|
|
1989
|
+
] });
|
|
1990
|
+
}
|
|
1401
1991
|
function SubagentSummaryRow({
|
|
1402
1992
|
coolCount,
|
|
1403
1993
|
coldCount,
|
|
@@ -1409,16 +1999,16 @@ function SubagentSummaryRow({
|
|
|
1409
1999
|
if (coolCount > 0) parts.push(`${coolCount} cool`);
|
|
1410
2000
|
if (coldCount > 0) parts.push(`${coldCount} cold`);
|
|
1411
2001
|
const hint = " +";
|
|
1412
|
-
const text =
|
|
2002
|
+
const text = ` \u2514\u2500 ... ${parts.join(" ")}`;
|
|
1413
2003
|
const padding = Math.max(
|
|
1414
2004
|
0,
|
|
1415
2005
|
contentWidth - getDisplayWidth(text) - getDisplayWidth(hint)
|
|
1416
2006
|
);
|
|
1417
2007
|
const active = isSelected && hasFocus;
|
|
1418
|
-
return /* @__PURE__ */
|
|
2008
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1419
2009
|
BOX.v,
|
|
1420
2010
|
" ",
|
|
1421
|
-
/* @__PURE__ */
|
|
2011
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: !active, inverse: active, children: [
|
|
1422
2012
|
text,
|
|
1423
2013
|
" ".repeat(padding),
|
|
1424
2014
|
hint
|
|
@@ -1426,7 +2016,7 @@ function SubagentSummaryRow({
|
|
|
1426
2016
|
BOX.v
|
|
1427
2017
|
] });
|
|
1428
2018
|
}
|
|
1429
|
-
function
|
|
2019
|
+
function ColdProjectsSummaryRow({
|
|
1430
2020
|
count,
|
|
1431
2021
|
isSelected,
|
|
1432
2022
|
hasFocus,
|
|
@@ -1441,8 +2031,8 @@ function ColdSessionsSummaryRow({
|
|
|
1441
2031
|
const dashes = BOX.h.repeat(dashCount);
|
|
1442
2032
|
const line = `${BOX.ml}${BOX.h}${label}${dashes}${hint}${BOX.mr}`;
|
|
1443
2033
|
const highlight = isSelected && hasFocus;
|
|
1444
|
-
return /* @__PURE__ */
|
|
1445
|
-
|
|
2034
|
+
return /* @__PURE__ */ jsx4(
|
|
2035
|
+
Text4,
|
|
1446
2036
|
{
|
|
1447
2037
|
backgroundColor: highlight ? "blue" : void 0,
|
|
1448
2038
|
bold: highlight,
|
|
@@ -1452,7 +2042,8 @@ function ColdSessionsSummaryRow({
|
|
|
1452
2042
|
);
|
|
1453
2043
|
}
|
|
1454
2044
|
function SessionTreePanel({
|
|
1455
|
-
|
|
2045
|
+
projects,
|
|
2046
|
+
coldProjects,
|
|
1456
2047
|
selectedId,
|
|
1457
2048
|
hasFocus,
|
|
1458
2049
|
width = DEFAULT_PANEL_WIDTH,
|
|
@@ -1463,28 +2054,30 @@ function SessionTreePanel({
|
|
|
1463
2054
|
const contentWidth = innerWidth - 1;
|
|
1464
2055
|
const titleLine = createTitleLine("Sessions", "", width);
|
|
1465
2056
|
const bottomLine = createBottomLine(width);
|
|
1466
|
-
|
|
2057
|
+
const totalProjectCount = projects.length + coldProjects.length;
|
|
2058
|
+
if (totalProjectCount === 0) {
|
|
1467
2059
|
const emptyText = "No Claude sessions";
|
|
1468
2060
|
const emptyPadding = Math.max(0, contentWidth - emptyText.length);
|
|
1469
|
-
return /* @__PURE__ */
|
|
1470
|
-
/* @__PURE__ */
|
|
1471
|
-
/* @__PURE__ */
|
|
2061
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
|
|
2062
|
+
/* @__PURE__ */ jsx4(Text4, { children: titleLine }),
|
|
2063
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1472
2064
|
BOX.v,
|
|
1473
2065
|
" ",
|
|
1474
|
-
/* @__PURE__ */
|
|
2066
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: emptyText }),
|
|
1475
2067
|
" ".repeat(emptyPadding),
|
|
1476
2068
|
BOX.v
|
|
1477
2069
|
] }),
|
|
1478
|
-
/* @__PURE__ */
|
|
2070
|
+
/* @__PURE__ */ jsx4(Text4, { children: bottomLine })
|
|
1479
2071
|
] });
|
|
1480
2072
|
}
|
|
1481
|
-
const flatRows = flattenSessions(
|
|
2073
|
+
const flatRows = flattenSessions(projects, coldProjects, expandedIds);
|
|
1482
2074
|
const totalRows = flatRows.length;
|
|
1483
2075
|
const selectedFlatIndex = flatRows.findIndex((row) => {
|
|
2076
|
+
if (row.kind === "project") return selectedId === row.sentinelId;
|
|
1484
2077
|
if (row.kind === "session") return row.session.id === selectedId;
|
|
1485
2078
|
if (row.kind === "subagent-summary")
|
|
1486
2079
|
return selectedId === `__sub-${row.parentId}__`;
|
|
1487
|
-
if (row.kind === "cold-
|
|
2080
|
+
if (row.kind === "cold-projects-summary") return selectedId === "__cold__";
|
|
1488
2081
|
return false;
|
|
1489
2082
|
});
|
|
1490
2083
|
const needsOverflow = maxRows !== void 0 && totalRows > maxRows;
|
|
@@ -1496,10 +2089,19 @@ function SessionTreePanel({
|
|
|
1496
2089
|
}
|
|
1497
2090
|
const displayRows = flatRows.slice(scrollTop, scrollTop + visibleCount);
|
|
1498
2091
|
const hiddenBelow = totalRows - (scrollTop + displayRows.length);
|
|
1499
|
-
return /* @__PURE__ */
|
|
1500
|
-
/* @__PURE__ */
|
|
2092
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
|
|
2093
|
+
/* @__PURE__ */ jsx4(Text4, { children: titleLine }),
|
|
1501
2094
|
displayRows.map(
|
|
1502
|
-
(row, idx) => row.kind === "
|
|
2095
|
+
(row, idx) => row.kind === "project" ? /* @__PURE__ */ jsx4(
|
|
2096
|
+
ProjectRow,
|
|
2097
|
+
{
|
|
2098
|
+
project: row.project,
|
|
2099
|
+
isSelected: selectedId === row.sentinelId,
|
|
2100
|
+
hasFocus,
|
|
2101
|
+
contentWidth
|
|
2102
|
+
},
|
|
2103
|
+
`project-${row.project.name}-${idx}`
|
|
2104
|
+
) : row.kind === "session" ? /* @__PURE__ */ jsx4(
|
|
1503
2105
|
SessionRow,
|
|
1504
2106
|
{
|
|
1505
2107
|
session: row.session,
|
|
@@ -1509,7 +2111,7 @@ function SessionTreePanel({
|
|
|
1509
2111
|
contentWidth
|
|
1510
2112
|
},
|
|
1511
2113
|
`${row.session.id}-${idx}`
|
|
1512
|
-
) : row.kind === "subagent-summary" ? /* @__PURE__ */
|
|
2114
|
+
) : row.kind === "subagent-summary" ? /* @__PURE__ */ jsx4(
|
|
1513
2115
|
SubagentSummaryRow,
|
|
1514
2116
|
{
|
|
1515
2117
|
coolCount: row.coolCount,
|
|
@@ -1519,8 +2121,8 @@ function SessionTreePanel({
|
|
|
1519
2121
|
hasFocus
|
|
1520
2122
|
},
|
|
1521
2123
|
`subagent-summary-${idx}`
|
|
1522
|
-
) : /* @__PURE__ */
|
|
1523
|
-
|
|
2124
|
+
) : /* @__PURE__ */ jsx4(
|
|
2125
|
+
ColdProjectsSummaryRow,
|
|
1524
2126
|
{
|
|
1525
2127
|
count: row.count,
|
|
1526
2128
|
isSelected: selectedId === "__cold__",
|
|
@@ -1530,35 +2132,42 @@ function SessionTreePanel({
|
|
|
1530
2132
|
"cold-summary"
|
|
1531
2133
|
)
|
|
1532
2134
|
),
|
|
1533
|
-
hiddenBelow > 0 && /* @__PURE__ */
|
|
2135
|
+
hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1534
2136
|
BOX.v,
|
|
1535
2137
|
" ",
|
|
1536
|
-
/* @__PURE__ */
|
|
2138
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: `... ${hiddenBelow} more` }),
|
|
1537
2139
|
" ".repeat(
|
|
1538
2140
|
Math.max(0, contentWidth - `... ${hiddenBelow} more`.length - 1)
|
|
1539
2141
|
),
|
|
1540
2142
|
BOX.v
|
|
1541
2143
|
] }),
|
|
1542
|
-
/* @__PURE__ */
|
|
2144
|
+
/* @__PURE__ */ jsx4(Text4, { children: bottomLine })
|
|
1543
2145
|
] });
|
|
1544
2146
|
}
|
|
1545
2147
|
|
|
1546
2148
|
// src/ui/App.tsx
|
|
1547
|
-
import { jsx as
|
|
2149
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1548
2150
|
var VIEWER_HEIGHT_FRACTION = 0.55;
|
|
1549
2151
|
function subSummarySentinel(parentId) {
|
|
1550
2152
|
return {
|
|
1551
2153
|
id: `__sub-${parentId}__`,
|
|
2154
|
+
hideKey: "",
|
|
1552
2155
|
filePath: "",
|
|
1553
2156
|
projectPath: "",
|
|
1554
2157
|
projectName: "",
|
|
1555
2158
|
lastModifiedMs: 0,
|
|
1556
2159
|
status: "cold",
|
|
1557
2160
|
modelName: null,
|
|
1558
|
-
subAgents: []
|
|
2161
|
+
subAgents: [],
|
|
2162
|
+
nonInteractive: false
|
|
1559
2163
|
};
|
|
1560
2164
|
}
|
|
1561
2165
|
function appendSubAgentRows(result, session, expandedIds) {
|
|
2166
|
+
const isCold = session.status === "cold";
|
|
2167
|
+
const sessionCollapsedKey = `__collapsed-session-${session.id}`;
|
|
2168
|
+
const sessionExpandedKey = `__expanded-session-${session.id}`;
|
|
2169
|
+
const sessionHidden = isCold ? !expandedIds.has(sessionExpandedKey) : expandedIds.has(sessionCollapsedKey);
|
|
2170
|
+
if (sessionHidden) return;
|
|
1562
2171
|
if (expandedIds.has(session.id)) {
|
|
1563
2172
|
result.push(...session.subAgents);
|
|
1564
2173
|
} else {
|
|
@@ -1576,27 +2185,47 @@ function appendSubAgentRows(result, session, expandedIds) {
|
|
|
1576
2185
|
}
|
|
1577
2186
|
function flattenSessions2(tree, expandedIds) {
|
|
1578
2187
|
const result = [];
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
2188
|
+
const projectToFlat = (project, isCold) => {
|
|
2189
|
+
const sentinelId = `__proj-${project.name}__`;
|
|
2190
|
+
result.push({
|
|
2191
|
+
id: sentinelId,
|
|
2192
|
+
hideKey: "",
|
|
2193
|
+
filePath: "",
|
|
2194
|
+
projectPath: project.projectPath,
|
|
2195
|
+
projectName: project.name,
|
|
2196
|
+
lastModifiedMs: 0,
|
|
2197
|
+
status: project.hotness,
|
|
2198
|
+
modelName: null,
|
|
2199
|
+
subAgents: [],
|
|
2200
|
+
nonInteractive: false
|
|
2201
|
+
});
|
|
2202
|
+
const shouldShowSessions = isCold ? expandedIds.has(`__expanded-${sentinelId}`) : !expandedIds.has(`__collapsed-${sentinelId}`);
|
|
2203
|
+
if (shouldShowSessions) {
|
|
2204
|
+
for (const session of project.sessions) {
|
|
2205
|
+
result.push(session);
|
|
2206
|
+
appendSubAgentRows(result, session, expandedIds);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
for (const project of tree.projects) {
|
|
2211
|
+
projectToFlat(project, false);
|
|
1584
2212
|
}
|
|
1585
|
-
if (
|
|
2213
|
+
if (tree.coldProjects.length > 0) {
|
|
1586
2214
|
result.push({
|
|
1587
2215
|
id: "__cold__",
|
|
2216
|
+
hideKey: "",
|
|
1588
2217
|
filePath: "",
|
|
1589
2218
|
projectPath: "",
|
|
1590
|
-
projectName: `${
|
|
2219
|
+
projectName: `${tree.coldProjects.length} cold`,
|
|
1591
2220
|
lastModifiedMs: 0,
|
|
1592
2221
|
status: "cold",
|
|
1593
2222
|
modelName: null,
|
|
1594
|
-
subAgents: []
|
|
2223
|
+
subAgents: [],
|
|
2224
|
+
nonInteractive: false
|
|
1595
2225
|
});
|
|
1596
2226
|
if (expandedIds.has("__cold__")) {
|
|
1597
|
-
for (const
|
|
1598
|
-
|
|
1599
|
-
appendSubAgentRows(result, s, expandedIds);
|
|
2227
|
+
for (const project of tree.coldProjects) {
|
|
2228
|
+
projectToFlat(project, true);
|
|
1600
2229
|
}
|
|
1601
2230
|
}
|
|
1602
2231
|
}
|
|
@@ -1625,8 +2254,9 @@ function App({ mode }) {
|
|
|
1625
2254
|
() => discoverSessions(config)
|
|
1626
2255
|
);
|
|
1627
2256
|
const [selectedId, setSelectedId] = useState2(() => {
|
|
1628
|
-
const
|
|
1629
|
-
|
|
2257
|
+
const firstProject = sessionTree.projects[0];
|
|
2258
|
+
if (firstProject) return `__proj-${firstProject.name}__`;
|
|
2259
|
+
return null;
|
|
1630
2260
|
});
|
|
1631
2261
|
const [focus, setFocus] = useState2("tree");
|
|
1632
2262
|
const [scrollOffset, setScrollOffset] = useState2(0);
|
|
@@ -1642,6 +2272,7 @@ function App({ mode }) {
|
|
|
1642
2272
|
);
|
|
1643
2273
|
const [detailScrollOffset, setDetailScrollOffset] = useState2(0);
|
|
1644
2274
|
const [filterIndex, setFilterIndex] = useState2(0);
|
|
2275
|
+
const [helpMode, setHelpMode] = useState2(false);
|
|
1645
2276
|
const allFlat = useMemo(
|
|
1646
2277
|
() => flattenSessions2(sessionTree, expandedIds),
|
|
1647
2278
|
[sessionTree, expandedIds]
|
|
@@ -1651,22 +2282,40 @@ function App({ mode }) {
|
|
|
1651
2282
|
allFlatRef.current = allFlat;
|
|
1652
2283
|
}, [allFlat]);
|
|
1653
2284
|
const activitiesLengthRef = useRef(0);
|
|
2285
|
+
const activitiesRef = useRef(activities);
|
|
1654
2286
|
useEffect2(() => {
|
|
1655
2287
|
activitiesLengthRef.current = activities.length;
|
|
1656
|
-
|
|
2288
|
+
activitiesRef.current = activities;
|
|
2289
|
+
}, [activities]);
|
|
2290
|
+
const lastLoadedFileRef = useRef(null);
|
|
1657
2291
|
useEffect2(() => {
|
|
1658
|
-
|
|
2292
|
+
let node = allFlatRef.current.find((s) => s.id === selectedId);
|
|
2293
|
+
if (node && selectedId?.startsWith("__proj-") && selectedId.endsWith("__")) {
|
|
2294
|
+
const projectName = selectedId.slice(7, -2);
|
|
2295
|
+
const project = sessionTree.projects.find((p) => p.name === projectName) ?? sessionTree.coldProjects.find((p) => p.name === projectName);
|
|
2296
|
+
if (project && project.sessions.length > 0) {
|
|
2297
|
+
node = project.sessions[0];
|
|
2298
|
+
} else {
|
|
2299
|
+
node = void 0;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
const newFile = node?.filePath ?? null;
|
|
2303
|
+
const fileChanged = lastLoadedFileRef.current !== newFile;
|
|
2304
|
+
lastLoadedFileRef.current = newFile;
|
|
1659
2305
|
if (node?.filePath) {
|
|
1660
2306
|
setActivities(parseSessionHistory(node.filePath));
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
2307
|
+
if (fileChanged) {
|
|
2308
|
+
setScrollOffset(0);
|
|
2309
|
+
setIsLive(true);
|
|
2310
|
+
setNewCount(0);
|
|
2311
|
+
setViewerCursorLine(0);
|
|
2312
|
+
setGitActivities([]);
|
|
2313
|
+
}
|
|
1665
2314
|
} else {
|
|
1666
2315
|
setActivities([]);
|
|
2316
|
+
if (fileChanged) setGitActivities([]);
|
|
1667
2317
|
}
|
|
1668
|
-
|
|
1669
|
-
}, [selectedId]);
|
|
2318
|
+
}, [selectedId, sessionTree]);
|
|
1670
2319
|
useEffect2(() => {
|
|
1671
2320
|
setScrollOffset(0);
|
|
1672
2321
|
setIsLive(true);
|
|
@@ -1677,7 +2326,7 @@ function App({ mode }) {
|
|
|
1677
2326
|
const node = allFlatRef.current.find((s) => s.id === selectedId);
|
|
1678
2327
|
if (!node?.projectPath) return;
|
|
1679
2328
|
const load = () => {
|
|
1680
|
-
const acts =
|
|
2329
|
+
const acts = activitiesRef.current;
|
|
1681
2330
|
const today = /* @__PURE__ */ new Date();
|
|
1682
2331
|
const todayMidnight = new Date(
|
|
1683
2332
|
today.getFullYear(),
|
|
@@ -1697,9 +2346,12 @@ function App({ mode }) {
|
|
|
1697
2346
|
const commits = parseGitCommits(node.projectPath, startDate, endDate);
|
|
1698
2347
|
setGitActivities(commits);
|
|
1699
2348
|
};
|
|
1700
|
-
load
|
|
2349
|
+
const initial = setTimeout(load, 100);
|
|
1701
2350
|
const timer = setInterval(load, 3e4);
|
|
1702
|
-
return () =>
|
|
2351
|
+
return () => {
|
|
2352
|
+
clearTimeout(initial);
|
|
2353
|
+
clearInterval(timer);
|
|
2354
|
+
};
|
|
1703
2355
|
}, [selectedId, isWatchMode]);
|
|
1704
2356
|
const refresh = useCallback(() => {
|
|
1705
2357
|
const freshConfig = loadGlobalConfig();
|
|
@@ -1707,7 +2359,8 @@ function App({ mode }) {
|
|
|
1707
2359
|
const updatedFlat = flattenSessions2(tree, expandedIds);
|
|
1708
2360
|
const node = updatedFlat.find((s) => s.id === selectedId);
|
|
1709
2361
|
if (!node) {
|
|
1710
|
-
const
|
|
2362
|
+
const allSessions = tree.projects?.flatMap((p) => p.sessions) ?? [];
|
|
2363
|
+
const parentSession = allSessions.find(
|
|
1711
2364
|
(s) => s.subAgents.some((sa) => sa.id === selectedId)
|
|
1712
2365
|
);
|
|
1713
2366
|
if (parentSession) setSelectedId(parentSession.id);
|
|
@@ -1729,7 +2382,7 @@ function App({ mode }) {
|
|
|
1729
2382
|
useEffect2(() => {
|
|
1730
2383
|
if (!isWatchMode) return;
|
|
1731
2384
|
const projectsDir = getProjectsDir();
|
|
1732
|
-
const usePolling = process.platform === "linux" || !
|
|
2385
|
+
const usePolling = process.platform === "linux" || !existsSync5(projectsDir);
|
|
1733
2386
|
if (usePolling) {
|
|
1734
2387
|
const timer = setInterval(
|
|
1735
2388
|
() => refreshRef.current(),
|
|
@@ -1757,8 +2410,14 @@ function App({ mode }) {
|
|
|
1757
2410
|
};
|
|
1758
2411
|
}, [isWatchMode, config.refreshIntervalMs]);
|
|
1759
2412
|
const filterPresets = config.filterPresets;
|
|
1760
|
-
const activePreset =
|
|
1761
|
-
|
|
2413
|
+
const activePreset = useMemo(
|
|
2414
|
+
() => filterPresets[filterIndex % filterPresets.length] ?? [],
|
|
2415
|
+
[filterPresets, filterIndex]
|
|
2416
|
+
);
|
|
2417
|
+
const filterLabel = useMemo(
|
|
2418
|
+
() => activePreset.length === 0 ? "all" : activePreset.join("+"),
|
|
2419
|
+
[activePreset]
|
|
2420
|
+
);
|
|
1762
2421
|
const mergedActivities = useMemo(() => {
|
|
1763
2422
|
const merged = [...activities, ...gitActivities].sort(
|
|
1764
2423
|
(a, b) => a.timestamp.getTime() - b.timestamp.getTime()
|
|
@@ -1779,7 +2438,7 @@ function App({ mode }) {
|
|
|
1779
2438
|
if (!activities.length || !selectedId) return;
|
|
1780
2439
|
ensureLogDir(config.logDir);
|
|
1781
2440
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1782
|
-
const filePath =
|
|
2441
|
+
const filePath = join5(
|
|
1783
2442
|
config.logDir,
|
|
1784
2443
|
`${date}-${selectedId.slice(0, 8)}.txt`
|
|
1785
2444
|
);
|
|
@@ -1796,6 +2455,8 @@ function App({ mode }) {
|
|
|
1796
2455
|
const { handleInput, statusBarItems } = useHotkeys({
|
|
1797
2456
|
focus,
|
|
1798
2457
|
detailMode,
|
|
2458
|
+
helpMode,
|
|
2459
|
+
onHelp: () => setHelpMode((m) => !m),
|
|
1799
2460
|
onSwitchFocus: () => setFocus((f) => f === "tree" ? "viewer" : "tree"),
|
|
1800
2461
|
onScrollUp: () => {
|
|
1801
2462
|
if (focus === "tree") {
|
|
@@ -1938,6 +2599,23 @@ function App({ mode }) {
|
|
|
1938
2599
|
return;
|
|
1939
2600
|
}
|
|
1940
2601
|
if (focus !== "tree" || !selectedId) return;
|
|
2602
|
+
if (selectedId.startsWith("__proj-") && selectedId.endsWith("__")) {
|
|
2603
|
+
const projectName = selectedId.slice(7, -2);
|
|
2604
|
+
const isCold = sessionTree.coldProjects.some(
|
|
2605
|
+
(p) => p.name === projectName
|
|
2606
|
+
);
|
|
2607
|
+
const toggleKey = isCold ? `__expanded-${selectedId}` : `__collapsed-${selectedId}`;
|
|
2608
|
+
setExpandedIds((prev) => {
|
|
2609
|
+
const next = new Set(prev);
|
|
2610
|
+
if (next.has(toggleKey)) {
|
|
2611
|
+
next.delete(toggleKey);
|
|
2612
|
+
} else {
|
|
2613
|
+
next.add(toggleKey);
|
|
2614
|
+
}
|
|
2615
|
+
return next;
|
|
2616
|
+
});
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
1941
2619
|
if (selectedId === "__cold__") {
|
|
1942
2620
|
setExpandedIds((prev) => {
|
|
1943
2621
|
const next = new Set(prev);
|
|
@@ -1959,7 +2637,8 @@ function App({ mode }) {
|
|
|
1959
2637
|
setSelectedId(parentId);
|
|
1960
2638
|
} else {
|
|
1961
2639
|
next.add(parentId);
|
|
1962
|
-
const
|
|
2640
|
+
const allSessions2 = sessionTree.projects?.flatMap((p) => p.sessions) ?? [];
|
|
2641
|
+
const parent = allSessions2.find((s) => s.id === parentId);
|
|
1963
2642
|
const firstNew = parent?.subAgents.find(
|
|
1964
2643
|
(sa) => sa.status === "cool" || sa.status === "cold"
|
|
1965
2644
|
);
|
|
@@ -1969,38 +2648,51 @@ function App({ mode }) {
|
|
|
1969
2648
|
});
|
|
1970
2649
|
return;
|
|
1971
2650
|
}
|
|
1972
|
-
const
|
|
1973
|
-
(
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
)
|
|
2651
|
+
const allSessions3 = [
|
|
2652
|
+
...sessionTree.projects.flatMap((p) => p.sessions),
|
|
2653
|
+
...sessionTree.coldProjects.flatMap((p) => p.sessions)
|
|
2654
|
+
];
|
|
2655
|
+
const selectedSessionObj = allSessions3.find((s) => s.id === selectedId);
|
|
2656
|
+
if (selectedSessionObj && selectedSessionObj.subAgents.length > 0) {
|
|
2657
|
+
const isCold = selectedSessionObj.status === "cold";
|
|
2658
|
+
const toggleKey = isCold ? `__expanded-session-${selectedId}` : `__collapsed-session-${selectedId}`;
|
|
2659
|
+
setExpandedIds((prev) => {
|
|
2660
|
+
const next = new Set(prev);
|
|
2661
|
+
if (next.has(toggleKey)) {
|
|
2662
|
+
next.delete(toggleKey);
|
|
2663
|
+
if (!isCold) setSelectedId(selectedId);
|
|
2664
|
+
} else {
|
|
2665
|
+
next.add(toggleKey);
|
|
2666
|
+
if (isCold) {
|
|
2667
|
+
const firstSub = selectedSessionObj.subAgents[0];
|
|
2668
|
+
if (firstSub) setSelectedId(firstSub.id);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
return next;
|
|
2672
|
+
});
|
|
1978
2673
|
return;
|
|
1979
|
-
|
|
1980
|
-
const next = new Set(prev);
|
|
1981
|
-
if (next.has(selectedId)) {
|
|
1982
|
-
next.delete(selectedId);
|
|
1983
|
-
} else {
|
|
1984
|
-
next.add(selectedId);
|
|
1985
|
-
}
|
|
1986
|
-
return next;
|
|
1987
|
-
});
|
|
2674
|
+
}
|
|
1988
2675
|
},
|
|
1989
2676
|
onHide: () => {
|
|
1990
2677
|
if (focus !== "tree" || !selectedId) return;
|
|
2678
|
+
if (selectedId.startsWith("__proj-") && selectedId.endsWith("__")) {
|
|
2679
|
+
const projectName = selectedId.slice(7, -2);
|
|
2680
|
+
hideProject(projectName);
|
|
2681
|
+
refresh();
|
|
2682
|
+
const nextId = allFlat[selectedIndex + 1]?.id ?? allFlat[selectedIndex - 1]?.id ?? null;
|
|
2683
|
+
setSelectedId(nextId);
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
1991
2686
|
if (selectedId === "__cold__") {
|
|
1992
|
-
const coldSessions = sessionTree.sessions
|
|
1993
|
-
(s) => s.status === "cold"
|
|
1994
|
-
);
|
|
2687
|
+
const coldSessions = sessionTree.coldProjects?.flatMap((p) => p.sessions) ?? [];
|
|
1995
2688
|
for (const s of coldSessions) hideSession(s.hideKey);
|
|
1996
2689
|
const nextId = allFlat[selectedIndex - 1]?.id ?? null;
|
|
1997
2690
|
refresh();
|
|
1998
2691
|
setSelectedId(nextId);
|
|
1999
2692
|
return;
|
|
2000
2693
|
}
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2003
|
-
);
|
|
2694
|
+
const allSessions4 = sessionTree.projects?.flatMap((p) => p.sessions) ?? [];
|
|
2695
|
+
const selectedSession2 = allSessions4.find((s) => s.id === selectedId);
|
|
2004
2696
|
if (selectedSession2) {
|
|
2005
2697
|
hideSession(selectedSession2.hideKey);
|
|
2006
2698
|
const nextId = allFlat[selectedIndex + 1]?.id ?? allFlat[selectedIndex - 1]?.id ?? null;
|
|
@@ -2008,7 +2700,7 @@ function App({ mode }) {
|
|
|
2008
2700
|
setSelectedId(nextId);
|
|
2009
2701
|
return;
|
|
2010
2702
|
}
|
|
2011
|
-
for (const s of
|
|
2703
|
+
for (const s of allSessions4) {
|
|
2012
2704
|
const selectedSubAgent = s.subAgents.find((sa) => sa.id === selectedId);
|
|
2013
2705
|
if (selectedSubAgent) {
|
|
2014
2706
|
hideSubAgent(selectedSubAgent.hideKey);
|
|
@@ -2026,55 +2718,67 @@ function App({ mode }) {
|
|
|
2026
2718
|
filterLabel
|
|
2027
2719
|
});
|
|
2028
2720
|
useInput((input, key) => handleInput(input, key), { isActive: isWatchMode });
|
|
2029
|
-
const
|
|
2721
|
+
const rawSelected = allFlat.find((s) => s.id === selectedId);
|
|
2722
|
+
const isProjectSentinel = !!selectedId && selectedId.startsWith("__proj-") && selectedId.endsWith("__");
|
|
2723
|
+
let selectedSession = rawSelected;
|
|
2724
|
+
if (isProjectSentinel && selectedId) {
|
|
2725
|
+
const projectName = selectedId.slice(7, -2);
|
|
2726
|
+
const project = sessionTree.projects.find((p) => p.name === projectName) ?? sessionTree.coldProjects.find((p) => p.name === projectName);
|
|
2727
|
+
if (project && project.sessions.length > 0) {
|
|
2728
|
+
selectedSession = project.sessions[0];
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2030
2731
|
const isPlaceholderSelected = !selectedSession || selectedId === "__cold__" || !!selectedId && selectedId.startsWith("__sub-") && selectedId.endsWith("__");
|
|
2031
2732
|
const sessionDisplayName = isPlaceholderSelected ? "No session selected" : selectedSession.projectPath ? selectedSession.projectName || selectedSession.id.slice(0, 8) : selectedSession.agentId ?? selectedSession.id.slice(0, 8);
|
|
2032
|
-
return /* @__PURE__ */
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
2733
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2734
|
+
isWatchMode && /* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, justifyContent: "space-between", width, children: [
|
|
2735
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
2036
2736
|
spinner,
|
|
2037
2737
|
" AgentHUD v",
|
|
2038
2738
|
getVersion()
|
|
2039
2739
|
] }),
|
|
2040
|
-
/* @__PURE__ */
|
|
2740
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: statusBarItems.join(" \xB7 ") })
|
|
2041
2741
|
] }),
|
|
2042
|
-
/* @__PURE__ */
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2742
|
+
helpMode ? /* @__PURE__ */ jsx5(HelpPanel, { width, height: height - 2 }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2743
|
+
migrationWarning && /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "Config moved to ~/.agenthud/config.yaml" }) }),
|
|
2744
|
+
/* @__PURE__ */ jsx5(
|
|
2745
|
+
SessionTreePanel,
|
|
2746
|
+
{
|
|
2747
|
+
projects: sessionTree.projects ?? [],
|
|
2748
|
+
coldProjects: sessionTree.coldProjects ?? [],
|
|
2749
|
+
selectedId,
|
|
2750
|
+
hasFocus: focus === "tree",
|
|
2751
|
+
width,
|
|
2752
|
+
maxRows: treeRows,
|
|
2753
|
+
expandedIds
|
|
2754
|
+
}
|
|
2755
|
+
),
|
|
2756
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: detailMode && detailActivity ? /* @__PURE__ */ jsx5(
|
|
2757
|
+
DetailViewPanel,
|
|
2758
|
+
{
|
|
2759
|
+
activity: detailActivity,
|
|
2760
|
+
sessionName: sessionDisplayName,
|
|
2761
|
+
scrollOffset: detailScrollOffset,
|
|
2762
|
+
visibleRows: viewerRows,
|
|
2763
|
+
width
|
|
2764
|
+
}
|
|
2765
|
+
) : /* @__PURE__ */ jsx5(
|
|
2766
|
+
ActivityViewerPanel,
|
|
2767
|
+
{
|
|
2768
|
+
activities: mergedActivities,
|
|
2769
|
+
sessionName: sessionDisplayName,
|
|
2770
|
+
scrollOffset,
|
|
2771
|
+
isLive,
|
|
2772
|
+
newCount,
|
|
2773
|
+
visibleRows: viewerRows,
|
|
2774
|
+
width,
|
|
2775
|
+
cursorLine: viewerCursorLine,
|
|
2776
|
+
hasFocus: focus === "viewer",
|
|
2777
|
+
spinner,
|
|
2778
|
+
filterLabel
|
|
2779
|
+
}
|
|
2780
|
+
) })
|
|
2781
|
+
] })
|
|
2078
2782
|
] });
|
|
2079
2783
|
}
|
|
2080
2784
|
|
|
@@ -2093,8 +2797,8 @@ if (options.command === "version") {
|
|
|
2093
2797
|
console.log(getVersion());
|
|
2094
2798
|
process.exit(0);
|
|
2095
2799
|
}
|
|
2096
|
-
var legacyConfig =
|
|
2097
|
-
if (
|
|
2800
|
+
var legacyConfig = join6(process.cwd(), ".agenthud", "config.yaml");
|
|
2801
|
+
if (existsSync6(legacyConfig)) {
|
|
2098
2802
|
console.log(
|
|
2099
2803
|
"The project-level config file (.agenthud/config.yaml) is no longer supported."
|
|
2100
2804
|
);
|
|
@@ -2133,6 +2837,20 @@ if (options.mode === "report") {
|
|
|
2133
2837
|
`);
|
|
2134
2838
|
process.exit(0);
|
|
2135
2839
|
}
|
|
2840
|
+
if (options.mode === "summary") {
|
|
2841
|
+
if (options.summaryError) {
|
|
2842
|
+
process.stderr.write(`agenthud: ${options.summaryError}
|
|
2843
|
+
`);
|
|
2844
|
+
process.exit(1);
|
|
2845
|
+
}
|
|
2846
|
+
const exitCode = await runSummary({
|
|
2847
|
+
date: options.summaryDate,
|
|
2848
|
+
prompt: options.summaryPrompt,
|
|
2849
|
+
force: options.summaryForce ?? false,
|
|
2850
|
+
today: /* @__PURE__ */ new Date()
|
|
2851
|
+
});
|
|
2852
|
+
process.exit(exitCode);
|
|
2853
|
+
}
|
|
2136
2854
|
if (options.mode === "watch") {
|
|
2137
2855
|
clearScreen();
|
|
2138
2856
|
}
|