agenthud 0.11.3 → 0.12.1

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.
@@ -1,15 +1,306 @@
1
1
  // src/main.ts
2
- import { existsSync as existsSync6, readdirSync as readdirSync2, realpathSync, rmSync } from "fs";
2
+ import { existsSync as existsSync7, readdirSync as readdirSync3, realpathSync, rmSync } from "fs";
3
3
  import { homedir as homedir5 } from "os";
4
- import { join as join6 } from "path";
4
+ import { join as join7 } from "path";
5
5
  import { createInterface as createInterface2 } from "readline";
6
6
  import { render } from "ink";
7
7
  import React from "react";
8
8
 
9
9
  // src/cli.ts
10
- import { readFileSync } from "fs";
11
- import { dirname, join } from "path";
10
+ import { readFileSync as readFileSync2 } from "fs";
11
+ import { dirname, join as join2 } from "path";
12
12
  import { fileURLToPath } from "url";
13
+
14
+ // src/config/globalConfig.ts
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
16
+ import { homedir } from "os";
17
+ import { join } from "path";
18
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
19
+ var CONFIG_PATH = join(homedir(), ".agenthud", "config.yaml");
20
+ var STATE_PATH = join(homedir(), ".agenthud", "state.yaml");
21
+ var DEFAULT_INCLUDE_TYPES = [
22
+ "user",
23
+ "response",
24
+ "bash",
25
+ "edit",
26
+ "thinking"
27
+ ];
28
+ var ALLOWED_INCLUDE_TYPES = /* @__PURE__ */ new Set([
29
+ "user",
30
+ "response",
31
+ "bash",
32
+ "edit",
33
+ "thinking",
34
+ "read",
35
+ "glob",
36
+ "commit"
37
+ ]);
38
+ var DEFAULT_GLOBAL_CONFIG = {
39
+ refreshIntervalMs: 2e3,
40
+ hiddenSessions: [],
41
+ hiddenSubAgents: [],
42
+ // [] means "show all"; conversation preset bundles assistant + user;
43
+ // commits-only preset filters down to git activity.
44
+ filterPresets: [[], ["response", "user"], ["commit"]],
45
+ hiddenProjects: [],
46
+ report: {
47
+ include: [...DEFAULT_INCLUDE_TYPES],
48
+ detailLimit: 120,
49
+ withGit: false,
50
+ format: "markdown"
51
+ },
52
+ summary: {}
53
+ };
54
+ var ALL_PRESET_KEYWORDS = /* @__PURE__ */ new Set(["all", "*", "any"]);
55
+ function normalizePreset(tokens) {
56
+ if (tokens.some((t) => ALL_PRESET_KEYWORDS.has(t.toLowerCase()))) return [];
57
+ return tokens;
58
+ }
59
+ function parseInterval(value) {
60
+ const match = value.match(/^(\d+)(s|m)$/);
61
+ if (!match) return null;
62
+ const n = parseInt(match[1], 10);
63
+ return match[2] === "m" ? n * 60 * 1e3 : n * 1e3;
64
+ }
65
+ function ensureAgenthudDir() {
66
+ const dir = join(homedir(), ".agenthud");
67
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
68
+ }
69
+ function writeDefaultConfig() {
70
+ ensureAgenthudDir();
71
+ const defaultYaml = `# AgentHUD user settings.
72
+ # App-managed state (hidden sessions/projects) lives in state.yaml.
73
+
74
+ # How often to poll for activity updates
75
+ refreshInterval: 2s
76
+
77
+ # Activity filter presets (cycle with 'f' key in viewer)
78
+ # Each list is one preset. Use "all" (or "*") to show everything.
79
+ # Types: response, user, bash, edit, thinking, read, glob, commit
80
+ filterPresets:
81
+ - ["all"]
82
+ - ["response", "user"]
83
+ - ["commit"]
84
+
85
+ # Defaults for \`agenthud report\` (CLI flags still win per-invocation).
86
+ # include: activity types to keep. Types: user, response, bash, edit,
87
+ # thinking, read, glob.
88
+ # detailLimit: max chars per activity detail (0 = unlimited).
89
+ # withGit: merge git commits from each session's project.
90
+ # format: markdown | json.
91
+ report:
92
+ include: [user, response, bash, edit, thinking]
93
+ detailLimit: 120
94
+ withGit: false
95
+ format: markdown
96
+
97
+ # Defaults for \`agenthud summary\`. Any field omitted here is inherited
98
+ # from \`report\` above. \`model\` is summary-specific and passed to
99
+ # \`claude --model\` (e.g. sonnet, haiku, or a full model id).
100
+ summary:
101
+ withGit: true
102
+ detailLimit: 0
103
+ # model: sonnet
104
+ `;
105
+ try {
106
+ writeFileSync(CONFIG_PATH, defaultYaml, "utf-8");
107
+ } catch {
108
+ }
109
+ }
110
+ function writeState(state) {
111
+ ensureAgenthudDir();
112
+ try {
113
+ writeFileSync(STATE_PATH, stringifyYaml(state), "utf-8");
114
+ } catch {
115
+ }
116
+ }
117
+ function rewriteConfigWithoutHideFields(raw) {
118
+ const cleaned = {};
119
+ for (const [k, v] of Object.entries(raw)) {
120
+ if (k === "hiddenSessions" || k === "hiddenSubAgents" || k === "hiddenProjects")
121
+ continue;
122
+ cleaned[k] = v;
123
+ }
124
+ try {
125
+ writeFileSync(CONFIG_PATH, stringifyYaml(cleaned), "utf-8");
126
+ } catch {
127
+ }
128
+ }
129
+ function loadGlobalConfig() {
130
+ const config = {
131
+ ...DEFAULT_GLOBAL_CONFIG,
132
+ hiddenSessions: [...DEFAULT_GLOBAL_CONFIG.hiddenSessions],
133
+ hiddenSubAgents: [...DEFAULT_GLOBAL_CONFIG.hiddenSubAgents],
134
+ hiddenProjects: [...DEFAULT_GLOBAL_CONFIG.hiddenProjects],
135
+ filterPresets: DEFAULT_GLOBAL_CONFIG.filterPresets.map((p) => [...p]),
136
+ report: {
137
+ ...DEFAULT_GLOBAL_CONFIG.report,
138
+ include: [...DEFAULT_GLOBAL_CONFIG.report.include]
139
+ },
140
+ summary: { ...DEFAULT_GLOBAL_CONFIG.summary }
141
+ };
142
+ let configRaw = {};
143
+ let configHadHideFields = false;
144
+ if (existsSync(CONFIG_PATH)) {
145
+ try {
146
+ const text = readFileSync(CONFIG_PATH, "utf-8");
147
+ configRaw = parseYaml(text) ?? {};
148
+ } catch {
149
+ configRaw = {};
150
+ }
151
+ } else {
152
+ writeDefaultConfig();
153
+ }
154
+ if (typeof configRaw.refreshInterval === "string") {
155
+ const ms = parseInterval(configRaw.refreshInterval);
156
+ if (ms !== null) config.refreshIntervalMs = ms;
157
+ }
158
+ if (Array.isArray(configRaw.filterPresets)) {
159
+ const presets = configRaw.filterPresets.filter(Array.isArray).map((p) => {
160
+ const tokens = p.filter(
161
+ (t) => typeof t === "string"
162
+ );
163
+ return normalizePreset(tokens);
164
+ });
165
+ if (presets.length > 0) config.filterPresets = presets;
166
+ }
167
+ if (configRaw.report && typeof configRaw.report === "object") {
168
+ const r = configRaw.report;
169
+ if (Array.isArray(r.include)) {
170
+ const tokens = r.include.filter(
171
+ (t) => typeof t === "string"
172
+ );
173
+ const cleaned = tokens.filter((t) => ALLOWED_INCLUDE_TYPES.has(t));
174
+ if (cleaned.length > 0) config.report.include = cleaned;
175
+ }
176
+ if (typeof r.detailLimit === "number" && Number.isInteger(r.detailLimit) && r.detailLimit >= 0) {
177
+ config.report.detailLimit = r.detailLimit;
178
+ }
179
+ if (typeof r.withGit === "boolean") config.report.withGit = r.withGit;
180
+ if (r.format === "markdown" || r.format === "json") config.report.format = r.format;
181
+ }
182
+ if (configRaw.summary && typeof configRaw.summary === "object") {
183
+ const s = configRaw.summary;
184
+ if (Array.isArray(s.include)) {
185
+ const tokens = s.include.filter(
186
+ (t) => typeof t === "string"
187
+ );
188
+ const cleaned = tokens.filter((t) => ALLOWED_INCLUDE_TYPES.has(t));
189
+ if (cleaned.length > 0) config.summary.include = cleaned;
190
+ }
191
+ if (typeof s.detailLimit === "number" && Number.isInteger(s.detailLimit) && s.detailLimit >= 0) {
192
+ config.summary.detailLimit = s.detailLimit;
193
+ }
194
+ if (typeof s.withGit === "boolean") config.summary.withGit = s.withGit;
195
+ if (s.format === "markdown" || s.format === "json") config.summary.format = s.format;
196
+ if (typeof s.model === "string" && s.model.trim().length > 0) {
197
+ config.summary.model = s.model.trim();
198
+ }
199
+ }
200
+ const legacyHidden = {};
201
+ for (const key of [
202
+ "hiddenSessions",
203
+ "hiddenSubAgents",
204
+ "hiddenProjects"
205
+ ]) {
206
+ if (Array.isArray(configRaw[key])) {
207
+ configHadHideFields = true;
208
+ legacyHidden[key] = configRaw[key].filter(
209
+ (s) => typeof s === "string"
210
+ );
211
+ }
212
+ }
213
+ let stateRaw = {};
214
+ if (existsSync(STATE_PATH)) {
215
+ try {
216
+ const text = readFileSync(STATE_PATH, "utf-8");
217
+ stateRaw = parseYaml(text) ?? {};
218
+ } catch {
219
+ stateRaw = {};
220
+ }
221
+ }
222
+ for (const key of [
223
+ "hiddenSessions",
224
+ "hiddenSubAgents",
225
+ "hiddenProjects"
226
+ ]) {
227
+ if (Array.isArray(stateRaw[key])) {
228
+ config[key] = stateRaw[key].filter(
229
+ (s) => typeof s === "string"
230
+ );
231
+ }
232
+ }
233
+ if (configHadHideFields) {
234
+ const merged = {
235
+ hiddenSessions: config.hiddenSessions.length > 0 ? config.hiddenSessions : legacyHidden.hiddenSessions ?? [],
236
+ hiddenSubAgents: config.hiddenSubAgents.length > 0 ? config.hiddenSubAgents : legacyHidden.hiddenSubAgents ?? [],
237
+ hiddenProjects: config.hiddenProjects.length > 0 ? config.hiddenProjects : legacyHidden.hiddenProjects ?? []
238
+ };
239
+ writeState(merged);
240
+ rewriteConfigWithoutHideFields(configRaw);
241
+ config.hiddenSessions = merged.hiddenSessions;
242
+ config.hiddenSubAgents = merged.hiddenSubAgents;
243
+ config.hiddenProjects = merged.hiddenProjects;
244
+ }
245
+ return config;
246
+ }
247
+ function updateState(updates) {
248
+ let state = {
249
+ hiddenSessions: [],
250
+ hiddenSubAgents: [],
251
+ hiddenProjects: []
252
+ };
253
+ if (existsSync(STATE_PATH)) {
254
+ try {
255
+ const text = readFileSync(STATE_PATH, "utf-8");
256
+ const raw = parseYaml(text) ?? {};
257
+ for (const key of [
258
+ "hiddenSessions",
259
+ "hiddenSubAgents",
260
+ "hiddenProjects"
261
+ ]) {
262
+ if (Array.isArray(raw[key])) {
263
+ state[key] = raw[key].filter(
264
+ (s) => typeof s === "string"
265
+ );
266
+ }
267
+ }
268
+ } catch {
269
+ }
270
+ }
271
+ for (const key of [
272
+ "hiddenSessions",
273
+ "hiddenSubAgents",
274
+ "hiddenProjects"
275
+ ]) {
276
+ if (updates[key] !== void 0) {
277
+ state[key] = updates[key];
278
+ }
279
+ }
280
+ writeState(state);
281
+ }
282
+ function hideSession(id) {
283
+ const config = loadGlobalConfig();
284
+ if (config.hiddenSessions.includes(id)) return;
285
+ updateState({ hiddenSessions: [...config.hiddenSessions, id] });
286
+ }
287
+ function hideSubAgent(id) {
288
+ const config = loadGlobalConfig();
289
+ if (config.hiddenSubAgents.includes(id)) return;
290
+ updateState({ hiddenSubAgents: [...config.hiddenSubAgents, id] });
291
+ }
292
+ function hideProject(name) {
293
+ const config = loadGlobalConfig();
294
+ if (config.hiddenProjects.includes(name)) return;
295
+ updateState({ hiddenProjects: [...config.hiddenProjects, name] });
296
+ }
297
+ function hasProjectLevelConfig() {
298
+ const candidate = join(process.cwd(), ".agenthud", "config.yaml");
299
+ if (candidate === join(homedir(), ".agenthud", "config.yaml")) return false;
300
+ return existsSync(candidate);
301
+ }
302
+
303
+ // src/cli.ts
13
304
  var ALL_TYPES = [
14
305
  "response",
15
306
  "bash",
@@ -19,7 +310,6 @@ var ALL_TYPES = [
19
310
  "glob",
20
311
  "user"
21
312
  ];
22
- var DEFAULT_TYPES = ["user", "response", "bash", "edit", "thinking"];
23
313
  var KNOWN_WATCH_FLAGS = /* @__PURE__ */ new Set([
24
314
  "-w",
25
315
  "--watch",
@@ -46,7 +336,15 @@ var KNOWN_SUMMARY_FLAGS = /* @__PURE__ */ new Set([
46
336
  "--force",
47
337
  "--model",
48
338
  "-y",
49
- "--yes"
339
+ "--yes",
340
+ "--include",
341
+ "--format",
342
+ "--detail-limit",
343
+ "--with-git",
344
+ "-o",
345
+ "--open",
346
+ "-I",
347
+ "--open-index"
50
348
  ]);
51
349
  var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["watch", "report", "summary"]);
52
350
  function getHelp() {
@@ -81,7 +379,8 @@ Commands:
81
379
  project into the timeline
82
380
 
83
381
  summary [--date DATE | --last Nd | --from DATE --to DATE]
84
- [--prompt TEXT] [--force] [--model NAME] [-y]
382
+ [--include TYPES] [--detail-limit N] [--with-git]
383
+ [--prompt TEXT] [--force] [--model NAME] [-y] [-o]
85
384
  Generate an LLM summary via the claude
86
385
  CLI. A single day produces a daily
87
386
  summary; a date range produces a
@@ -91,12 +390,27 @@ Commands:
91
390
  (e.g. --last 7d)
92
391
  --from YYYY-MM-DD Range start (use with --to)
93
392
  --to YYYY-MM-DD Range end (use with --from)
393
+ --include TYPES Activity types fed to the LLM
394
+ (same shape as report's --include)
395
+ --detail-limit N Max chars per activity detail in the
396
+ LLM payload (0 = unlimited)
397
+ --with-git Merge git commits into the LLM payload
94
398
  --prompt TEXT Override prompt for this run (daily only)
95
399
  --force Regenerate even if cached
96
400
  --model NAME Pass --model to claude (e.g. "sonnet",
97
401
  "haiku", or a full model id)
98
402
  -y, --yes Skip confirmation prompts for new daily
99
403
  summaries
404
+ -o, --open Launch the resulting summary in your OS
405
+ default app once it's written (or read
406
+ back from cache).
407
+ -I, --open-index Launch ~/.agenthud/summaries/index.md
408
+ in your OS default app. Combinable with
409
+ -o (e.g. -oI opens both).
410
+
411
+ Defaults for report and summary live under \`report:\` and \`summary:\`
412
+ in ~/.agenthud/config.yaml. Flags override config values per-run; the
413
+ effective values are printed to stderr at the start of each run.
100
414
 
101
415
  Global options:
102
416
  -V, --version Show version number
@@ -112,13 +426,10 @@ Config: ~/.agenthud/config.yaml
112
426
  function getVersion() {
113
427
  const __dirname2 = dirname(fileURLToPath(import.meta.url));
114
428
  const packageJson = JSON.parse(
115
- readFileSync(join(__dirname2, "..", "package.json"), "utf-8")
429
+ readFileSync2(join2(__dirname2, "..", "package.json"), "utf-8")
116
430
  );
117
431
  return packageJson.version;
118
432
  }
119
- function clearScreen() {
120
- console.clear();
121
- }
122
433
  function parseLocalMidnight(dateStr) {
123
434
  if (dateStr === "today") {
124
435
  const now = /* @__PURE__ */ new Date();
@@ -141,11 +452,24 @@ function parseLocalMidnight(dateStr) {
141
452
  if (Number.isNaN(date.getTime())) return null;
142
453
  return date;
143
454
  }
455
+ function formatEffectiveOptionsLine(command, fields) {
456
+ const parts = [];
457
+ parts.push(`include=[${fields.include.join(",")}]`);
458
+ if (fields.detailLimit !== void 0) {
459
+ parts.push(
460
+ `detail-limit=${fields.detailLimit === 0 ? "\u221E" : fields.detailLimit}`
461
+ );
462
+ }
463
+ parts.push(`with-git=${fields.withGit ? "on" : "off"}`);
464
+ if (fields.format) parts.push(`format=${fields.format}`);
465
+ if (fields.model) parts.push(`model=${fields.model}`);
466
+ return `${command} \u2192 ${parts.join(" ")}`;
467
+ }
144
468
  function todayLocalMidnight() {
145
469
  const now = /* @__PURE__ */ new Date();
146
470
  return new Date(now.getFullYear(), now.getMonth(), now.getDate());
147
471
  }
148
- function parseArgs(args) {
472
+ function parseArgs(args, config) {
149
473
  if (args[0] === "watch") args = args.slice(1);
150
474
  if (args.includes("--help") || args.includes("-h")) {
151
475
  return { mode: "watch", command: "help" };
@@ -159,7 +483,7 @@ function parseArgs(args) {
159
483
  if (args[0] === "report") {
160
484
  const rest = args.slice(1);
161
485
  let reportDate = todayLocalMidnight();
162
- let reportInclude = DEFAULT_TYPES;
486
+ let reportInclude = config?.report.include ?? DEFAULT_INCLUDE_TYPES;
163
487
  let reportError;
164
488
  const FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
165
489
  "--date",
@@ -207,7 +531,7 @@ function parseArgs(args) {
207
531
  }
208
532
  }
209
533
  }
210
- let reportFormat = "markdown";
534
+ let reportFormat = config?.report.format ?? "markdown";
211
535
  const formatIdx = rest.indexOf("--format");
212
536
  if (formatIdx !== -1) {
213
537
  const fmt = rest[formatIdx + 1];
@@ -219,7 +543,7 @@ function parseArgs(args) {
219
543
  reportError = "Invalid format: missing value for --format.";
220
544
  }
221
545
  }
222
- let reportDetailLimit;
546
+ let reportDetailLimit = config?.report.detailLimit;
223
547
  const detailLimitIdx = rest.indexOf("--detail-limit");
224
548
  if (detailLimitIdx !== -1) {
225
549
  const val = rest[detailLimitIdx + 1];
@@ -230,7 +554,7 @@ function parseArgs(args) {
230
554
  reportDetailLimit = n;
231
555
  }
232
556
  }
233
- const reportWithGit = rest.includes("--with-git");
557
+ const reportWithGit = rest.includes("--with-git") || (config?.report.withGit ?? false);
234
558
  return {
235
559
  mode: "report",
236
560
  reportDate,
@@ -257,7 +581,10 @@ function parseArgs(args) {
257
581
  "--from",
258
582
  "--to",
259
583
  "--prompt",
260
- "--model"
584
+ "--model",
585
+ "--include",
586
+ "--format",
587
+ "--detail-limit"
261
588
  ]);
262
589
  for (let i = 0; i < rest.length; i++) {
263
590
  const arg = rest[i];
@@ -360,234 +687,86 @@ function parseArgs(args) {
360
687
  }
361
688
  if (rest.includes("--force")) summaryForce = true;
362
689
  if (rest.includes("-y") || rest.includes("--yes")) summaryAssumeYes = true;
363
- return {
364
- mode: "summary",
365
- summaryDate,
366
- summaryFrom,
367
- summaryTo,
368
- summaryPrompt,
369
- summaryForce,
370
- summaryAssumeYes,
371
- summaryModel,
372
- summaryError
373
- };
374
- }
375
- if (args[0] && !args[0].startsWith("-") && !KNOWN_SUBCOMMANDS.has(args[0])) {
376
- return {
377
- mode: "watch",
378
- error: `Unknown command: "${args[0]}". Run agenthud --help for usage.`
379
- };
380
- }
381
- for (const arg of args) {
382
- if (arg.startsWith("-") && !KNOWN_WATCH_FLAGS.has(arg)) {
383
- return {
384
- mode: "watch",
385
- error: `Unknown option: "${arg}". Run agenthud --help for usage.`
386
- };
387
- }
388
- }
389
- return args.includes("--cwd") ? { mode: "watch", scopeToCwd: true } : { mode: "watch" };
390
- }
391
-
392
- // src/config/globalConfig.ts
393
- import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
394
- import { homedir } from "os";
395
- import { join as join2 } from "path";
396
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
397
- var CONFIG_PATH = join2(homedir(), ".agenthud", "config.yaml");
398
- var STATE_PATH = join2(homedir(), ".agenthud", "state.yaml");
399
- var DEFAULT_GLOBAL_CONFIG = {
400
- refreshIntervalMs: 2e3,
401
- hiddenSessions: [],
402
- hiddenSubAgents: [],
403
- // [] means "show all"; conversation preset bundles assistant + user;
404
- // commits-only preset filters down to git activity.
405
- filterPresets: [[], ["response", "user"], ["commit"]],
406
- hiddenProjects: []
407
- };
408
- var ALL_PRESET_KEYWORDS = /* @__PURE__ */ new Set(["all", "*", "any"]);
409
- function normalizePreset(tokens) {
410
- if (tokens.some((t) => ALL_PRESET_KEYWORDS.has(t.toLowerCase()))) return [];
411
- return tokens;
412
- }
413
- function parseInterval(value) {
414
- const match = value.match(/^(\d+)(s|m)$/);
415
- if (!match) return null;
416
- const n = parseInt(match[1], 10);
417
- return match[2] === "m" ? n * 60 * 1e3 : n * 1e3;
418
- }
419
- function ensureAgenthudDir() {
420
- const dir = join2(homedir(), ".agenthud");
421
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
422
- }
423
- function writeDefaultConfig() {
424
- ensureAgenthudDir();
425
- const defaultYaml = `# AgentHUD user settings.
426
- # App-managed state (hidden sessions/projects) lives in state.yaml.
427
-
428
- # How often to poll for activity updates
429
- refreshInterval: 2s
430
-
431
- # Activity filter presets (cycle with 'f' key in viewer)
432
- # Each list is one preset. Use "all" (or "*") to show everything.
433
- # Types: response, user, bash, edit, thinking, read, glob, commit
434
- filterPresets:
435
- - ["all"]
436
- - ["response", "user"]
437
- - ["commit"]
438
- `;
439
- try {
440
- writeFileSync(CONFIG_PATH, defaultYaml, "utf-8");
441
- } catch {
442
- }
443
- }
444
- function writeState(state) {
445
- ensureAgenthudDir();
446
- try {
447
- writeFileSync(STATE_PATH, stringifyYaml(state), "utf-8");
448
- } catch {
449
- }
450
- }
451
- function rewriteConfigWithoutHideFields(raw) {
452
- const cleaned = {};
453
- for (const [k, v] of Object.entries(raw)) {
454
- if (k === "hiddenSessions" || k === "hiddenSubAgents" || k === "hiddenProjects")
455
- continue;
456
- cleaned[k] = v;
457
- }
458
- try {
459
- writeFileSync(CONFIG_PATH, stringifyYaml(cleaned), "utf-8");
460
- } catch {
461
- }
462
- }
463
- function loadGlobalConfig() {
464
- const config = { ...DEFAULT_GLOBAL_CONFIG };
465
- let configRaw = {};
466
- let configHadHideFields = false;
467
- if (existsSync(CONFIG_PATH)) {
468
- try {
469
- const text = readFileSync2(CONFIG_PATH, "utf-8");
470
- configRaw = parseYaml(text) ?? {};
471
- } catch {
472
- configRaw = {};
473
- }
474
- } else {
475
- writeDefaultConfig();
476
- }
477
- if (typeof configRaw.refreshInterval === "string") {
478
- const ms = parseInterval(configRaw.refreshInterval);
479
- if (ms !== null) config.refreshIntervalMs = ms;
480
- }
481
- if (Array.isArray(configRaw.filterPresets)) {
482
- const presets = configRaw.filterPresets.filter(Array.isArray).map((p) => {
483
- const tokens = p.filter(
484
- (t) => typeof t === "string"
485
- );
486
- return normalizePreset(tokens);
487
- });
488
- if (presets.length > 0) config.filterPresets = presets;
489
- }
490
- const legacyHidden = {};
491
- for (const key of [
492
- "hiddenSessions",
493
- "hiddenSubAgents",
494
- "hiddenProjects"
495
- ]) {
496
- if (Array.isArray(configRaw[key])) {
497
- configHadHideFields = true;
498
- legacyHidden[key] = configRaw[key].filter(
499
- (s) => typeof s === "string"
500
- );
690
+ const summaryOpen = rest.includes("--open") || rest.includes("-o") || void 0;
691
+ const summaryOpenIndex = rest.includes("--open-index") || rest.includes("-I") || void 0;
692
+ let summaryInclude = config?.summary.include ?? config?.report.include ?? DEFAULT_INCLUDE_TYPES;
693
+ const summaryIncludeIdx = rest.indexOf("--include");
694
+ if (summaryIncludeIdx !== -1) {
695
+ const includeStr = rest[summaryIncludeIdx + 1];
696
+ if (!includeStr) {
697
+ summaryError = summaryError ?? "Invalid --include: missing value.";
698
+ } else if (includeStr === "all") {
699
+ summaryInclude = ALL_TYPES;
700
+ } else {
701
+ const tokens = includeStr.split(",").map((s) => s.trim()).filter(Boolean);
702
+ const unknown = tokens.filter((t) => !ALL_TYPES.includes(t));
703
+ if (unknown.length > 0) {
704
+ summaryError = summaryError ?? `Unknown --include type${unknown.length > 1 ? "s" : ""}: ${unknown.map((u) => `"${u}"`).join(", ")}. Valid types: ${ALL_TYPES.join(", ")} (or "all").`;
705
+ } else {
706
+ summaryInclude = tokens;
707
+ }
708
+ }
501
709
  }
502
- }
503
- let stateRaw = {};
504
- if (existsSync(STATE_PATH)) {
505
- try {
506
- const text = readFileSync2(STATE_PATH, "utf-8");
507
- stateRaw = parseYaml(text) ?? {};
508
- } catch {
509
- stateRaw = {};
710
+ let summaryFormat = config?.summary.format ?? config?.report.format ?? "markdown";
711
+ const summaryFormatIdx = rest.indexOf("--format");
712
+ if (summaryFormatIdx !== -1) {
713
+ const fmt = rest[summaryFormatIdx + 1];
714
+ if (fmt === "json" || fmt === "markdown") {
715
+ summaryFormat = fmt;
716
+ } else if (fmt) {
717
+ summaryError = summaryError ?? `Invalid format: "${fmt}". Use "markdown" or "json".`;
718
+ } else {
719
+ summaryError = summaryError ?? "Invalid format: missing value for --format.";
720
+ }
510
721
  }
511
- }
512
- for (const key of [
513
- "hiddenSessions",
514
- "hiddenSubAgents",
515
- "hiddenProjects"
516
- ]) {
517
- if (Array.isArray(stateRaw[key])) {
518
- config[key] = stateRaw[key].filter(
519
- (s) => typeof s === "string"
520
- );
722
+ let summaryDetailLimit = config?.summary.detailLimit ?? config?.report.detailLimit;
723
+ const summaryDetailLimitIdx = rest.indexOf("--detail-limit");
724
+ if (summaryDetailLimitIdx !== -1) {
725
+ const val = rest[summaryDetailLimitIdx + 1];
726
+ const n = Number(val);
727
+ if (!val || Number.isNaN(n) || n < 0 || !Number.isInteger(n)) {
728
+ summaryError = summaryError ?? `Invalid --detail-limit: "${val}". Must be a non-negative integer.`;
729
+ } else {
730
+ summaryDetailLimit = n;
731
+ }
521
732
  }
522
- }
523
- if (configHadHideFields) {
524
- const merged = {
525
- hiddenSessions: config.hiddenSessions.length > 0 ? config.hiddenSessions : legacyHidden.hiddenSessions ?? [],
526
- hiddenSubAgents: config.hiddenSubAgents.length > 0 ? config.hiddenSubAgents : legacyHidden.hiddenSubAgents ?? [],
527
- hiddenProjects: config.hiddenProjects.length > 0 ? config.hiddenProjects : legacyHidden.hiddenProjects ?? []
733
+ const summaryWithGit = rest.includes("--with-git") || (config?.summary.withGit ?? config?.report.withGit ?? false);
734
+ if (summaryModel === void 0 && config?.summary.model) {
735
+ summaryModel = config.summary.model;
736
+ }
737
+ return {
738
+ mode: "summary",
739
+ summaryDate,
740
+ summaryFrom,
741
+ summaryTo,
742
+ summaryPrompt,
743
+ summaryForce,
744
+ summaryAssumeYes,
745
+ summaryModel,
746
+ summaryInclude,
747
+ summaryFormat,
748
+ summaryDetailLimit,
749
+ summaryWithGit,
750
+ summaryOpen,
751
+ summaryOpenIndex,
752
+ summaryError
528
753
  };
529
- writeState(merged);
530
- rewriteConfigWithoutHideFields(configRaw);
531
- config.hiddenSessions = merged.hiddenSessions;
532
- config.hiddenSubAgents = merged.hiddenSubAgents;
533
- config.hiddenProjects = merged.hiddenProjects;
534
754
  }
535
- return config;
536
- }
537
- function updateState(updates) {
538
- let state = {
539
- hiddenSessions: [],
540
- hiddenSubAgents: [],
541
- hiddenProjects: []
542
- };
543
- if (existsSync(STATE_PATH)) {
544
- try {
545
- const text = readFileSync2(STATE_PATH, "utf-8");
546
- const raw = parseYaml(text) ?? {};
547
- for (const key of [
548
- "hiddenSessions",
549
- "hiddenSubAgents",
550
- "hiddenProjects"
551
- ]) {
552
- if (Array.isArray(raw[key])) {
553
- state[key] = raw[key].filter(
554
- (s) => typeof s === "string"
555
- );
556
- }
557
- }
558
- } catch {
559
- }
755
+ if (args[0] && !args[0].startsWith("-") && !KNOWN_SUBCOMMANDS.has(args[0])) {
756
+ return {
757
+ mode: "watch",
758
+ error: `Unknown command: "${args[0]}". Run agenthud --help for usage.`
759
+ };
560
760
  }
561
- for (const key of [
562
- "hiddenSessions",
563
- "hiddenSubAgents",
564
- "hiddenProjects"
565
- ]) {
566
- if (updates[key] !== void 0) {
567
- state[key] = updates[key];
761
+ for (const arg of args) {
762
+ if (arg.startsWith("-") && !KNOWN_WATCH_FLAGS.has(arg)) {
763
+ return {
764
+ mode: "watch",
765
+ error: `Unknown option: "${arg}". Run agenthud --help for usage.`
766
+ };
568
767
  }
569
768
  }
570
- writeState(state);
571
- }
572
- function hideSession(id) {
573
- const config = loadGlobalConfig();
574
- if (config.hiddenSessions.includes(id)) return;
575
- updateState({ hiddenSessions: [...config.hiddenSessions, id] });
576
- }
577
- function hideSubAgent(id) {
578
- const config = loadGlobalConfig();
579
- if (config.hiddenSubAgents.includes(id)) return;
580
- updateState({ hiddenSubAgents: [...config.hiddenSubAgents, id] });
581
- }
582
- function hideProject(name) {
583
- const config = loadGlobalConfig();
584
- if (config.hiddenProjects.includes(name)) return;
585
- updateState({ hiddenProjects: [...config.hiddenProjects, name] });
586
- }
587
- function hasProjectLevelConfig() {
588
- const candidate = join2(process.cwd(), ".agenthud", "config.yaml");
589
- if (candidate === join2(homedir(), ".agenthud", "config.yaml")) return false;
590
- return existsSync(candidate);
769
+ return args.includes("--cwd") ? { mode: "watch", scopeToCwd: true } : { mode: "watch" };
591
770
  }
592
771
 
593
772
  // src/data/reportGenerator.ts
@@ -1484,56 +1663,365 @@ function discoverSessions(config, options2) {
1484
1663
  }
1485
1664
 
1486
1665
  // src/data/summaryRunner.ts
1487
- import { spawn } from "child_process";
1666
+ import { spawn as spawn2 } from "child_process";
1488
1667
  import {
1489
1668
  copyFileSync,
1490
1669
  createWriteStream,
1491
- existsSync as existsSync4,
1670
+ existsSync as existsSync5,
1492
1671
  mkdirSync as mkdirSync2,
1493
- readFileSync as readFileSync5,
1672
+ readFileSync as readFileSync6,
1494
1673
  unlinkSync
1495
1674
  } from "fs";
1496
1675
  import { homedir as homedir3 } from "os";
1497
- import { dirname as dirname2, join as join4 } from "path";
1676
+ import { dirname as dirname2, join as join5 } from "path";
1498
1677
  import { createInterface } from "readline";
1499
1678
  import { fileURLToPath as fileURLToPath2 } from "url";
1679
+
1680
+ // src/utils/openInDefaultApp.ts
1681
+ import { spawn } from "child_process";
1682
+ function buildOpenCommand(platform, path) {
1683
+ switch (platform) {
1684
+ case "darwin":
1685
+ return { command: "open", args: [path] };
1686
+ case "linux":
1687
+ return { command: "xdg-open", args: [path] };
1688
+ case "win32":
1689
+ return { command: "cmd", args: ["/c", "start", "", path] };
1690
+ default:
1691
+ return null;
1692
+ }
1693
+ }
1694
+ function openInDefaultApp(path) {
1695
+ const cmd = buildOpenCommand(process.platform, path);
1696
+ if (!cmd) {
1697
+ process.stderr.write(
1698
+ `agenthud: --open: no known opener for platform "${process.platform}"
1699
+ `
1700
+ );
1701
+ return;
1702
+ }
1703
+ try {
1704
+ const child = spawn(cmd.command, cmd.args, {
1705
+ detached: true,
1706
+ stdio: "ignore"
1707
+ });
1708
+ child.on("error", (err) => {
1709
+ process.stderr.write(`agenthud: --open failed: ${err.message}
1710
+ `);
1711
+ });
1712
+ child.unref();
1713
+ } catch (err) {
1714
+ const msg = err instanceof Error ? err.message : String(err);
1715
+ process.stderr.write(`agenthud: --open failed: ${msg}
1716
+ `);
1717
+ }
1718
+ }
1719
+
1720
+ // src/data/summariesIndex.ts
1721
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1722
+ import { join as join4 } from "path";
1723
+ var INDEX_HEADER_MARKER = "<!-- agenthud-summaries-index -->";
1724
+ var BACKLINK_START_MARKER = "<!-- agenthud-backlinks-start -->";
1725
+ var BACKLINK_END_MARKER = "<!-- agenthud-backlinks-end -->";
1726
+ var DAILY_RE = /^(\d{4})-(\d{2})-(\d{2})\.md$/;
1727
+ var RANGE_RE = /^range-(\d{4})-(\d{2})-(\d{2})_(\d{4})-(\d{2})-(\d{2})\.md$/;
1728
+ function makeLocalDate(y, m, d) {
1729
+ const date = new Date(y, m - 1, d);
1730
+ if (date.getFullYear() !== y || date.getMonth() !== m - 1 || date.getDate() !== d) {
1731
+ return null;
1732
+ }
1733
+ return date;
1734
+ }
1735
+ function parseSummaryFilename(name) {
1736
+ const range = RANGE_RE.exec(name);
1737
+ if (range) {
1738
+ const from = makeLocalDate(
1739
+ Number(range[1]),
1740
+ Number(range[2]),
1741
+ Number(range[3])
1742
+ );
1743
+ const to = makeLocalDate(
1744
+ Number(range[4]),
1745
+ Number(range[5]),
1746
+ Number(range[6])
1747
+ );
1748
+ if (!from || !to) return null;
1749
+ return { kind: "range", from, to, filename: name };
1750
+ }
1751
+ const daily = DAILY_RE.exec(name);
1752
+ if (daily) {
1753
+ const date = makeLocalDate(
1754
+ Number(daily[1]),
1755
+ Number(daily[2]),
1756
+ Number(daily[3])
1757
+ );
1758
+ if (!date) return null;
1759
+ return { kind: "daily", date, filename: name };
1760
+ }
1761
+ return null;
1762
+ }
1763
+ function listSummaries(dir) {
1764
+ if (!existsSync4(dir)) return [];
1765
+ let names;
1766
+ try {
1767
+ names = readdirSync2(dir);
1768
+ } catch {
1769
+ return [];
1770
+ }
1771
+ const entries = names.map((n) => parseSummaryFilename(n)).filter((e) => e !== null);
1772
+ const anchor = (e) => e.kind === "range" ? e.from : e.date;
1773
+ entries.sort((a, b) => {
1774
+ const da = anchor(a);
1775
+ const db = anchor(b);
1776
+ if (da.getFullYear() !== db.getFullYear()) {
1777
+ return db.getFullYear() - da.getFullYear();
1778
+ }
1779
+ if (da.getMonth() !== db.getMonth()) {
1780
+ return db.getMonth() - da.getMonth();
1781
+ }
1782
+ if (a.kind !== b.kind) return a.kind === "range" ? -1 : 1;
1783
+ return db.getTime() - da.getTime();
1784
+ });
1785
+ return entries;
1786
+ }
1787
+ var MONTHS = [
1788
+ "January",
1789
+ "February",
1790
+ "March",
1791
+ "April",
1792
+ "May",
1793
+ "June",
1794
+ "July",
1795
+ "August",
1796
+ "September",
1797
+ "October",
1798
+ "November",
1799
+ "December"
1800
+ ];
1801
+ var WEEKDAYS_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1802
+ var WEEKDAYS_LONG = [
1803
+ "Sunday",
1804
+ "Monday",
1805
+ "Tuesday",
1806
+ "Wednesday",
1807
+ "Thursday",
1808
+ "Friday",
1809
+ "Saturday"
1810
+ ];
1811
+ function formatDateKey(d) {
1812
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1813
+ }
1814
+ function formatDateLabel(d) {
1815
+ return `${formatDateKey(d)} (${WEEKDAYS_SHORT[d.getDay()]})`;
1816
+ }
1817
+ function entryAnchor(e) {
1818
+ return e.kind === "range" ? e.from : e.date;
1819
+ }
1820
+ function buildIndexMarkdown(entries, snippets) {
1821
+ const lines = [INDEX_HEADER_MARKER, "", "# AgentHUD summaries", ""];
1822
+ if (entries.length === 0) {
1823
+ lines.push("_No summaries yet. Run `agenthud summary` to create one._");
1824
+ return `${lines.join("\n")}
1825
+ `;
1826
+ }
1827
+ const byYear = /* @__PURE__ */ new Map();
1828
+ for (const entry of entries) {
1829
+ const a = entryAnchor(entry);
1830
+ const y = a.getFullYear();
1831
+ const m = a.getMonth();
1832
+ const months = byYear.get(y) ?? /* @__PURE__ */ new Map();
1833
+ const bucket = months.get(m) ?? [];
1834
+ bucket.push(entry);
1835
+ months.set(m, bucket);
1836
+ byYear.set(y, months);
1837
+ }
1838
+ for (const [year, months] of byYear) {
1839
+ lines.push(`## ${year}`, "");
1840
+ for (const [month, bucket] of months) {
1841
+ lines.push(`### ${MONTHS[month]}`);
1842
+ for (const entry of bucket) {
1843
+ const snippet = snippets?.get(entry.filename);
1844
+ const trail = snippet ? ` \u2014 ${snippet}` : "";
1845
+ if (entry.kind === "range") {
1846
+ const fromIso = formatDateKey(entry.from);
1847
+ const toIso = formatDateKey(entry.to);
1848
+ lines.push(
1849
+ `- [Range: ${fromIso} \u2192 ${toIso}](./${entry.filename}) \xB7 weekly${trail}`
1850
+ );
1851
+ } else {
1852
+ lines.push(
1853
+ `- [${formatDateLabel(entry.date)}](./${entry.filename})${trail}`
1854
+ );
1855
+ }
1856
+ }
1857
+ lines.push("");
1858
+ }
1859
+ }
1860
+ return `${lines.join("\n")}
1861
+ `;
1862
+ }
1863
+ function buildTitleLine(entry) {
1864
+ if (entry.kind === "range") {
1865
+ return `# Range: ${formatDateKey(entry.from)} \u2192 ${formatDateKey(entry.to)}`;
1866
+ }
1867
+ const iso = formatDateKey(entry.date);
1868
+ const weekday = WEEKDAYS_LONG[entry.date.getDay()];
1869
+ return `# ${iso} (${weekday})`;
1870
+ }
1871
+ function buildHeaderBlock(currentFilename, entries) {
1872
+ const parts = ["[\u2190 all summaries](./index.md)"];
1873
+ const me = entries.find((e) => e.filename === currentFilename);
1874
+ if (me && me.kind === "daily") {
1875
+ const dailies = entries.filter((e) => e.kind === "daily").sort((a, b) => a.date.getTime() - b.date.getTime());
1876
+ const idx = dailies.findIndex((e) => e.filename === currentFilename);
1877
+ if (idx > 0) {
1878
+ const prev = dailies[idx - 1];
1879
+ parts.push(
1880
+ `[\u2190 ${formatDateLabel(prev.date)}](./${prev.filename})`
1881
+ );
1882
+ }
1883
+ if (idx >= 0 && idx < dailies.length - 1) {
1884
+ const next = dailies[idx + 1];
1885
+ parts.push(
1886
+ `[${formatDateLabel(next.date)} \u2192](./${next.filename})`
1887
+ );
1888
+ }
1889
+ }
1890
+ const backlinkLine = parts.join(" \xB7 ");
1891
+ const title = me ? buildTitleLine(me) : "";
1892
+ return `${BACKLINK_START_MARKER}
1893
+ ${backlinkLine}
1894
+
1895
+ ${title}
1896
+ ${BACKLINK_END_MARKER}
1897
+
1898
+ `;
1899
+ }
1900
+ function stripExistingHeaderBlock(content) {
1901
+ if (!content.startsWith(BACKLINK_START_MARKER)) return content;
1902
+ const endIdx = content.indexOf(BACKLINK_END_MARKER);
1903
+ if (endIdx === -1) return content;
1904
+ let cut = endIdx + BACKLINK_END_MARKER.length;
1905
+ while (cut < content.length && (content[cut] === "\n" || content[cut] === "\r")) {
1906
+ cut++;
1907
+ }
1908
+ return content.slice(cut);
1909
+ }
1910
+ function prependHeaderBlock(content, block) {
1911
+ return block + stripExistingHeaderBlock(content);
1912
+ }
1913
+ function extractContextSnippet(content, maxChars = 200) {
1914
+ const body = stripExistingHeaderBlock(content);
1915
+ for (const raw of body.split("\n")) {
1916
+ const line = raw.trim();
1917
+ if (!line) continue;
1918
+ if (line.startsWith("#")) continue;
1919
+ if (line.startsWith("<!--")) continue;
1920
+ if (line.length <= maxChars) return line;
1921
+ return `${line.slice(0, maxChars - 1).trimEnd()}\u2026`;
1922
+ }
1923
+ return null;
1924
+ }
1925
+ function regenerateIndex(summariesDir2) {
1926
+ const entries = listSummaries(summariesDir2);
1927
+ const snippets = /* @__PURE__ */ new Map();
1928
+ for (const entry of entries) {
1929
+ const path = join4(summariesDir2, entry.filename);
1930
+ try {
1931
+ const content = readFileSync5(path, "utf-8");
1932
+ const snippet = extractContextSnippet(content);
1933
+ if (snippet) snippets.set(entry.filename, snippet);
1934
+ const block = buildHeaderBlock(entry.filename, entries);
1935
+ const updated = prependHeaderBlock(content, block);
1936
+ if (updated !== content) {
1937
+ writeFileSync2(path, updated, "utf-8");
1938
+ }
1939
+ } catch {
1940
+ }
1941
+ }
1942
+ const indexPath = join4(summariesDir2, "index.md");
1943
+ try {
1944
+ writeFileSync2(
1945
+ indexPath,
1946
+ buildIndexMarkdown(entries, snippets),
1947
+ "utf-8"
1948
+ );
1949
+ } catch (err) {
1950
+ process.stderr.write(
1951
+ `warning: cannot write summaries index (${err.message})
1952
+ `
1953
+ );
1954
+ }
1955
+ }
1956
+
1957
+ // src/utils/stderrTicker.ts
1958
+ function formatTickerLine(label, elapsedSeconds, dots) {
1959
+ const tail = ".".repeat(dots % 4).padEnd(3, " ");
1960
+ return `${label}${tail} ${elapsedSeconds}s`;
1961
+ }
1962
+ function startStderrTicker(label, options2 = {}) {
1963
+ const intervalMs = options2.intervalMs ?? 500;
1964
+ const stream = options2.stream ?? process.stderr;
1965
+ const start = Date.now();
1966
+ let ticks = 0;
1967
+ const id = setInterval(() => {
1968
+ ticks++;
1969
+ const elapsed = Math.floor((Date.now() - start) / 1e3);
1970
+ stream.write(`\r${formatTickerLine(label, elapsed, ticks)}`);
1971
+ }, intervalMs);
1972
+ return function stop() {
1973
+ clearInterval(id);
1974
+ if (ticks > 0) stream.write("\r\x1B[K");
1975
+ };
1976
+ }
1977
+
1978
+ // src/data/summaryRunner.ts
1500
1979
  function agenthudHomeDir() {
1501
- const dir = join4(homedir3(), ".agenthud");
1502
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
1980
+ const dir = join5(homedir3(), ".agenthud");
1981
+ if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
1503
1982
  return dir;
1504
1983
  }
1505
1984
  function summariesDir() {
1506
- const dir = join4(agenthudHomeDir(), "summaries");
1507
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
1985
+ const dir = join5(agenthudHomeDir(), "summaries");
1986
+ if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
1508
1987
  return dir;
1509
1988
  }
1510
1989
  function promptFilename(kind) {
1511
1990
  return kind === "daily" ? "summary-prompt.md" : "summary-range-prompt.md";
1512
1991
  }
1513
1992
  function userPromptPath(kind) {
1514
- return join4(homedir3(), ".agenthud", promptFilename(kind));
1993
+ return join5(homedir3(), ".agenthud", promptFilename(kind));
1994
+ }
1995
+ function formatPromptSource(kind, override) {
1996
+ if (kind === "daily" && override) {
1997
+ return "<inline> (from --prompt)";
1998
+ }
1999
+ const home = homedir3();
2000
+ const path = userPromptPath(kind);
2001
+ const abbreviated = path.startsWith(home) ? `~${path.slice(home.length)}` : path;
2002
+ return abbreviated.replace(/\\/g, "/");
1515
2003
  }
1516
2004
  function templatePath(kind) {
1517
2005
  const here = dirname2(fileURLToPath2(import.meta.url));
1518
- return join4(here, "templates", promptFilename(kind));
2006
+ return join5(here, "templates", promptFilename(kind));
1519
2007
  }
1520
2008
  function dateKey(d) {
1521
2009
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1522
2010
  }
1523
2011
  function dailyCachePath(date) {
1524
- return join4(summariesDir(), `${dateKey(date)}.md`);
2012
+ return join5(summariesDir(), `${dateKey(date)}.md`);
1525
2013
  }
1526
2014
  function rangeCachePath(from, to) {
1527
- return join4(summariesDir(), `range-${dateKey(from)}_${dateKey(to)}.md`);
2015
+ return join5(summariesDir(), `range-${dateKey(from)}_${dateKey(to)}.md`);
1528
2016
  }
1529
2017
  function isSameLocalDay2(a, b) {
1530
2018
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1531
2019
  }
1532
2020
  function ensureUserPromptFile(kind) {
1533
2021
  const p = userPromptPath(kind);
1534
- if (existsSync4(p)) return;
2022
+ if (existsSync5(p)) return;
1535
2023
  const dir = dirname2(p);
1536
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
2024
+ if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
1537
2025
  try {
1538
2026
  copyFileSync(templatePath(kind), p);
1539
2027
  } catch {
@@ -1542,14 +2030,14 @@ function ensureUserPromptFile(kind) {
1542
2030
  function resolvePrompt(kind, override) {
1543
2031
  if (override) return override;
1544
2032
  const p = userPromptPath(kind);
1545
- if (existsSync4(p)) {
2033
+ if (existsSync5(p)) {
1546
2034
  try {
1547
- return readFileSync5(p, "utf-8");
2035
+ return readFileSync6(p, "utf-8");
1548
2036
  } catch {
1549
2037
  }
1550
2038
  }
1551
2039
  try {
1552
- return readFileSync5(templatePath(kind), "utf-8");
2040
+ return readFileSync6(templatePath(kind), "utf-8");
1553
2041
  } catch {
1554
2042
  return "Summarize the input below.";
1555
2043
  }
@@ -1604,7 +2092,7 @@ function spawnClaude(opts) {
1604
2092
  ];
1605
2093
  if (opts.model) args.push("--model", opts.model);
1606
2094
  args.push(opts.prompt);
1607
- const proc = spawn(
2095
+ const proc = spawn2(
1608
2096
  "claude",
1609
2097
  args,
1610
2098
  {
@@ -1725,11 +2213,11 @@ async function generateDailySummary(opts) {
1725
2213
  const isToday = isSameLocalDay2(opts.date, opts.today);
1726
2214
  const cached = dailyCachePath(opts.date);
1727
2215
  const dateLabel = dateKey(opts.date);
1728
- if (!isToday && !opts.force && existsSync4(cached)) {
2216
+ if (!isToday && !opts.force && existsSync5(cached)) {
1729
2217
  try {
1730
- const content = readFileSync5(cached, "utf-8");
2218
+ const content = readFileSync6(cached, "utf-8");
1731
2219
  if (opts.announce) {
1732
- process.stderr.write(`agenthud: cached summary from ${cached}
2220
+ process.stderr.write(`cached summary from ${cached}
1733
2221
  `);
1734
2222
  }
1735
2223
  if (opts.streamToStdout) {
@@ -1747,7 +2235,7 @@ async function generateDailySummary(opts) {
1747
2235
  }
1748
2236
  }
1749
2237
  if (opts.announce) {
1750
- process.stderr.write(`agenthud: scanning sessions for ${dateLabel}...
2238
+ process.stderr.write(`scanning sessions for ${dateLabel}...
1751
2239
  `);
1752
2240
  }
1753
2241
  const config = loadGlobalConfig();
@@ -1758,10 +2246,13 @@ async function generateDailySummary(opts) {
1758
2246
  ];
1759
2247
  const reportMarkdown = generateReport(flatSessions, {
1760
2248
  date: opts.date,
1761
- include: ["response", "bash", "edit", "thinking"],
2249
+ include: opts.include,
2250
+ // The LLM only ingests markdown — the format option on summary is
2251
+ // for the report-extraction *export* surface (post-LLM), not for
2252
+ // the payload itself, so this stays fixed.
1762
2253
  format: "markdown",
1763
- detailLimit: 0,
1764
- withGit: true
2254
+ detailLimit: opts.detailLimit,
2255
+ withGit: opts.withGit
1765
2256
  });
1766
2257
  const reportBytes = Buffer.byteLength(reportMarkdown, "utf-8");
1767
2258
  const estimatedTokens = Math.ceil(reportBytes / 4);
@@ -1776,7 +2267,7 @@ async function generateDailySummary(opts) {
1776
2267
  ).length;
1777
2268
  const sizeKb = (reportBytes / 1024).toFixed(1);
1778
2269
  process.stderr.write(
1779
- `agenthud: input: ${sessionCount} sessions, ${activityCount} activities, ${commitCount} commits (${reportLines.length} lines, ${sizeKb}KB \u2248 ${estimatedTokens.toLocaleString()} tokens)
2270
+ `input: ${sessionCount} sessions, ${activityCount} activities, ${commitCount} commits (${reportLines.length} lines, ${sizeKb}KB \u2248 ${estimatedTokens.toLocaleString()} tokens)
1780
2271
  `
1781
2272
  );
1782
2273
  }
@@ -1815,13 +2306,15 @@ async function generateDailySummary(opts) {
1815
2306
  }
1816
2307
  }
1817
2308
  }
1818
- if (opts.announce) {
2309
+ const showTicker = opts.announce && !opts.streamToStdout;
2310
+ if (opts.announce && !showTicker) {
1819
2311
  process.stderr.write(
1820
- `agenthud: sending to claude (this may take a minute)...
2312
+ `sending to claude (this may take a minute)...
1821
2313
 
1822
2314
  `
1823
2315
  );
1824
2316
  }
2317
+ const stopTicker = showTicker ? startStderrTicker("sending to claude") : null;
1825
2318
  const prompt = resolvePrompt("daily", opts.promptOverride);
1826
2319
  const result = await spawnClaude({
1827
2320
  prompt,
@@ -1830,12 +2323,13 @@ async function generateDailySummary(opts) {
1830
2323
  streamToStdout: opts.streamToStdout,
1831
2324
  model: opts.model
1832
2325
  });
2326
+ if (stopTicker) stopTicker();
1833
2327
  if (opts.announce && result.code === 0) {
1834
2328
  process.stderr.write("\n");
1835
- process.stderr.write(`agenthud: saved to ${cached}
2329
+ process.stderr.write(`saved to ${cached}
1836
2330
  `);
1837
2331
  if (result.usage) {
1838
- process.stderr.write(`agenthud: ${formatUsage(result.usage)}
2332
+ process.stderr.write(`${formatUsage(result.usage)}
1839
2333
  `);
1840
2334
  }
1841
2335
  }
@@ -1853,10 +2347,25 @@ async function runSummary(options2) {
1853
2347
  today: options2.today,
1854
2348
  force: options2.force,
1855
2349
  promptOverride: options2.prompt,
1856
- streamToStdout: true,
2350
+ streamToStdout: !options2.open,
1857
2351
  announce: true,
1858
- model: options2.model
2352
+ model: options2.model,
2353
+ include: options2.include,
2354
+ detailLimit: options2.detailLimit,
2355
+ withGit: options2.withGit
1859
2356
  });
2357
+ if (res.code === 0 && !res.skipped) {
2358
+ try {
2359
+ regenerateIndex(summariesDir());
2360
+ } catch {
2361
+ }
2362
+ }
2363
+ if (options2.open && res.code === 0 && !res.skipped) {
2364
+ openInDefaultApp(dailyCachePath(options2.date));
2365
+ }
2366
+ if (options2.openIndex && res.code === 0 && !res.skipped) {
2367
+ openInDefaultApp(join5(summariesDir(), "index.md"));
2368
+ }
1860
2369
  return res.code;
1861
2370
  }
1862
2371
  async function runRangeSummary(options2) {
@@ -1870,16 +2379,26 @@ async function runRangeSummary(options2) {
1870
2379
  options2.force,
1871
2380
  dates,
1872
2381
  options2.today,
1873
- existsSync4(rangeCache)
2382
+ existsSync5(rangeCache)
1874
2383
  )) {
1875
2384
  try {
1876
- const content = readFileSync5(rangeCache, "utf-8");
2385
+ const content = readFileSync6(rangeCache, "utf-8");
1877
2386
  process.stderr.write(
1878
- `agenthud: cached range summary from ${rangeCache}
2387
+ `cached range summary from ${rangeCache}
1879
2388
  `
1880
2389
  );
1881
- process.stdout.write(content);
1882
- if (!content.endsWith("\n")) process.stdout.write("\n");
2390
+ if (!options2.open) {
2391
+ process.stdout.write(content);
2392
+ if (!content.endsWith("\n")) process.stdout.write("\n");
2393
+ }
2394
+ try {
2395
+ regenerateIndex(summariesDir());
2396
+ } catch {
2397
+ }
2398
+ if (options2.open) openInDefaultApp(rangeCache);
2399
+ if (options2.openIndex) {
2400
+ openInDefaultApp(join5(summariesDir(), "index.md"));
2401
+ }
1883
2402
  return 0;
1884
2403
  } catch {
1885
2404
  }
@@ -1888,15 +2407,15 @@ async function runRangeSummary(options2) {
1888
2407
  let missingCount = 0;
1889
2408
  for (const d of dates) {
1890
2409
  const isToday = isSameLocalDay2(d, options2.today);
1891
- if (!isToday && existsSync4(dailyCachePath(d))) cachedCount++;
2410
+ if (!isToday && existsSync5(dailyCachePath(d))) cachedCount++;
1892
2411
  else missingCount++;
1893
2412
  }
1894
2413
  process.stderr.write(
1895
- `agenthud: range ${fromLabel} \u2192 ${toLabel} (${dates.length} days)
2414
+ `range ${fromLabel} \u2192 ${toLabel} (${dates.length} days)
1896
2415
  `
1897
2416
  );
1898
2417
  process.stderr.write(
1899
- `agenthud: ${cachedCount} cached, ${missingCount} to generate
2418
+ `${cachedCount} cached, ${missingCount} to generate
1900
2419
  `
1901
2420
  );
1902
2421
  const dailyMarkdowns = [];
@@ -1905,9 +2424,9 @@ async function runRangeSummary(options2) {
1905
2424
  const label = dateKey(d);
1906
2425
  const isToday = isSameLocalDay2(d, options2.today);
1907
2426
  process.stderr.write(`
1908
- agenthud: --- ${label} ---
2427
+ --- ${label} ---
1909
2428
  `);
1910
- const willPrompt = !options2.assumeYes && (isToday || !existsSync4(dailyCachePath(d)));
2429
+ const willPrompt = !options2.assumeYes && (isToday || !existsSync5(dailyCachePath(d)));
1911
2430
  const confirmer = willPrompt ? async () => {
1912
2431
  const hint = isToday ? " (today \u2014 regenerated every time)" : "";
1913
2432
  return ask(`Generate this summary${hint}? [Y/n] `, true);
@@ -1920,7 +2439,10 @@ agenthud: --- ${label} ---
1920
2439
  announce: true,
1921
2440
  confirmBeforeSpawn: confirmer,
1922
2441
  assumeYes: options2.assumeYes,
1923
- model: options2.model
2442
+ model: options2.model,
2443
+ include: options2.include,
2444
+ detailLimit: options2.detailLimit,
2445
+ withGit: options2.withGit
1924
2446
  });
1925
2447
  if (res.skipped) {
1926
2448
  process.stderr.write(`agenthud: ${label} \u2014 skipped by user.
@@ -1937,7 +2459,7 @@ agenthud: --- ${label} ---
1937
2459
  }
1938
2460
  const text = res.markdown.trim();
1939
2461
  if (text.length === 0 || /^no activity found/i.test(text)) {
1940
- process.stderr.write(`agenthud: ${label} has no activity \u2014 skipping.
2462
+ process.stderr.write(`${label} has no activity \u2014 skipping.
1941
2463
  `);
1942
2464
  continue;
1943
2465
  }
@@ -1952,37 +2474,51 @@ agenthud: --- ${label} ---
1952
2474
  ${markdown}`).join("\n\n---\n\n");
1953
2475
  process.stderr.write(
1954
2476
  `
1955
- agenthud: combining ${dailyMarkdowns.length} daily summaries into range summary...
2477
+ combining ${dailyMarkdowns.length} daily summaries into range summary...
1956
2478
  `
1957
2479
  );
1958
- process.stderr.write(
1959
- `agenthud: sending to claude (this may take a minute)...
2480
+ const metaStreams = !options2.open;
2481
+ if (!metaStreams) {
2482
+ } else {
2483
+ process.stderr.write(
2484
+ `sending to claude (this may take a minute)...
1960
2485
 
1961
2486
  `
1962
- );
2487
+ );
2488
+ }
2489
+ const stopMetaTicker = metaStreams ? null : startStderrTicker("sending to claude");
1963
2490
  const metaPrompt = resolvePrompt("range");
1964
2491
  const metaResult = await spawnClaude({
1965
2492
  prompt: metaPrompt,
1966
2493
  stdin: metaInput,
1967
2494
  cachePath: rangeCache,
1968
- streamToStdout: true,
2495
+ streamToStdout: metaStreams,
1969
2496
  model: options2.model
1970
2497
  });
2498
+ if (stopMetaTicker) stopMetaTicker();
1971
2499
  if (metaResult.code !== 0) {
1972
2500
  return metaResult.code;
1973
2501
  }
1974
2502
  process.stderr.write("\n");
1975
- process.stderr.write(`agenthud: saved to ${rangeCache}
2503
+ process.stderr.write(`saved to ${rangeCache}
1976
2504
  `);
1977
2505
  if (metaResult.usage) {
1978
- process.stderr.write(`agenthud: ${formatUsage(metaResult.usage)}
2506
+ process.stderr.write(`${formatUsage(metaResult.usage)}
1979
2507
  `);
1980
2508
  }
2509
+ try {
2510
+ regenerateIndex(summariesDir());
2511
+ } catch {
2512
+ }
2513
+ if (options2.open) openInDefaultApp(rangeCache);
2514
+ if (options2.openIndex) {
2515
+ openInDefaultApp(join5(summariesDir(), "index.md"));
2516
+ }
1981
2517
  return 0;
1982
2518
  }
1983
2519
 
1984
2520
  // src/ui/App.tsx
1985
- import { existsSync as existsSync5, watch } from "fs";
2521
+ import { existsSync as existsSync6, watch } from "fs";
1986
2522
  import { basename as basename3 } from "path";
1987
2523
  import { Box as Box5, Text as Text5, useApp, useInput, useStdout } from "ink";
1988
2524
  import { useCallback, useEffect as useEffect3, useMemo, useRef, useState as useState3 } from "react";
@@ -2419,6 +2955,7 @@ var SECTIONS = [
2419
2955
  title: "Project tree",
2420
2956
  rows: [
2421
2957
  ["\u2191 \u2193 / k j", "Move selection"],
2958
+ ["\u2190", "Jump to parent (sub-agent \u2192 session, session \u2192 project)"],
2422
2959
  ["PgUp / Ctrl+B", "Page up"],
2423
2960
  ["PgDn / Ctrl+F", "Page down"],
2424
2961
  ["\u21B5", "Expand/collapse project, session, or summary"],
@@ -2568,6 +3105,7 @@ function useHotkeys({
2568
3105
  onHelpScroll,
2569
3106
  onHelpScrollToTop,
2570
3107
  onToggleTracking,
3108
+ onJumpToParent,
2571
3109
  filterLabel,
2572
3110
  trackingOn = false
2573
3111
  }) {
@@ -2687,6 +3225,10 @@ function useHotkeys({
2687
3225
  onHide();
2688
3226
  return;
2689
3227
  }
3228
+ if (key.leftArrow && onJumpToParent) {
3229
+ onJumpToParent();
3230
+ return;
3231
+ }
2690
3232
  if (key.upArrow || input === "k") {
2691
3233
  onScrollUp();
2692
3234
  return;
@@ -2720,6 +3262,7 @@ function useHotkeys({
2720
3262
  ...trackingItems,
2721
3263
  "Tab: viewer",
2722
3264
  "\u2191\u2193/jk: select",
3265
+ "\u2190: parent",
2723
3266
  "PgUp/Dn: page",
2724
3267
  "\u21B5: expand",
2725
3268
  "h: hide",
@@ -3197,7 +3740,8 @@ function appendSubAgentRows(result, session, expandedIds) {
3197
3740
  const sessionExpandedKey = `__expanded-session-${session.id}`;
3198
3741
  const sessionHidden = isCold ? !expandedIds.has(sessionExpandedKey) : expandedIds.has(sessionCollapsedKey);
3199
3742
  if (sessionHidden) return;
3200
- if (expandedIds.has(session.id)) {
3743
+ const subAgentsFullyExpanded = expandedIds.has(session.id) || isCold && expandedIds.has(sessionExpandedKey);
3744
+ if (subAgentsFullyExpanded) {
3201
3745
  result.push(...session.subAgents);
3202
3746
  } else {
3203
3747
  result.push(
@@ -3325,6 +3869,26 @@ function collectAllIds(tree) {
3325
3869
  }
3326
3870
  return ids;
3327
3871
  }
3872
+ function findParentTarget(currentId, tree, flat) {
3873
+ if (currentId.startsWith("__sub-") && currentId.endsWith("__")) {
3874
+ return currentId.slice("__sub-".length, -"__".length);
3875
+ }
3876
+ const allProjects = [...tree.projects, ...tree.coldProjects];
3877
+ const allSessions = allProjects.flatMap((p) => p.sessions);
3878
+ for (const session of allSessions) {
3879
+ if (session.subAgents.some((sa) => sa.id === currentId)) {
3880
+ return session.id;
3881
+ }
3882
+ }
3883
+ for (const project of allProjects) {
3884
+ if (project.sessions.some((s) => s.id === currentId)) {
3885
+ return `__proj-${project.name}__`;
3886
+ }
3887
+ }
3888
+ const idx = flat.findIndex((row) => row.id === currentId);
3889
+ if (idx <= 0) return currentId;
3890
+ return flat[idx - 1]?.id ?? currentId;
3891
+ }
3328
3892
  function initialSelectedId(tree) {
3329
3893
  const firstProject = tree.projects[0];
3330
3894
  if (firstProject) return `__proj-${firstProject.name}__`;
@@ -3499,7 +4063,7 @@ function App({
3499
4063
  useEffect3(() => {
3500
4064
  if (!isWatchMode) return;
3501
4065
  const projectsDir = getProjectsDir();
3502
- const usePolling = process.platform === "linux" || !existsSync5(projectsDir);
4066
+ const usePolling = process.platform === "linux" || !existsSync6(projectsDir);
3503
4067
  if (usePolling) {
3504
4068
  const timer = setInterval(
3505
4069
  () => refreshRef.current(),
@@ -3593,6 +4157,13 @@ function App({
3593
4157
  });
3594
4158
  },
3595
4159
  trackingOn: tracking,
4160
+ onJumpToParent: () => {
4161
+ if (focus !== "tree") return;
4162
+ stopTracking();
4163
+ if (!selectedId) return;
4164
+ const target = findParentTarget(selectedId, sessionTree, allFlat);
4165
+ if (target && target !== selectedId) setSelectedId(target);
4166
+ },
3596
4167
  onSwitchFocus: () => setFocus((f) => f === "tree" ? "viewer" : "tree"),
3597
4168
  // cursorLine = "entries back from the newest" (0 = newest = bottom row).
3598
4169
  // Up arrow moves visually upward = older direction = cursorLine++.
@@ -4034,15 +4605,16 @@ function installAltScreenCleanup() {
4034
4605
  }
4035
4606
 
4036
4607
  // src/utils/legacyConfig.ts
4037
- import { join as join5, resolve } from "path";
4608
+ import { join as join6, resolve } from "path";
4038
4609
  function isLegacyProjectConfig(cwd, home) {
4039
- const legacy = resolve(join5(cwd, ".agenthud", "config.yaml"));
4040
- const global = resolve(join5(home, ".agenthud", "config.yaml"));
4610
+ const legacy = resolve(join6(cwd, ".agenthud", "config.yaml"));
4611
+ const global = resolve(join6(home, ".agenthud", "config.yaml"));
4041
4612
  return legacy !== global;
4042
4613
  }
4043
4614
 
4044
4615
  // src/main.ts
4045
- var options = parseArgs(process.argv.slice(2));
4616
+ var globalConfig = loadGlobalConfig();
4617
+ var options = parseArgs(process.argv.slice(2), globalConfig);
4046
4618
  if (options.error) {
4047
4619
  process.stderr.write(`agenthud: ${options.error}
4048
4620
  `);
@@ -4056,8 +4628,8 @@ if (options.command === "version") {
4056
4628
  console.log(getVersion());
4057
4629
  process.exit(0);
4058
4630
  }
4059
- var legacyConfig = join6(process.cwd(), ".agenthud", "config.yaml");
4060
- if (isLegacyProjectConfig(process.cwd(), homedir5()) && existsSync6(legacyConfig)) {
4631
+ var legacyConfig = join7(process.cwd(), ".agenthud", "config.yaml");
4632
+ if (isLegacyProjectConfig(process.cwd(), homedir5()) && existsSync7(legacyConfig)) {
4061
4633
  console.log(
4062
4634
  "The project-level config file (.agenthud/config.yaml) is no longer supported."
4063
4635
  );
@@ -4083,8 +4655,16 @@ if (options.mode === "report") {
4083
4655
  `);
4084
4656
  process.exit(1);
4085
4657
  }
4086
- const config = loadGlobalConfig();
4087
- const tree = discoverSessions(config);
4658
+ process.stderr.write(
4659
+ `${formatEffectiveOptionsLine("report", {
4660
+ include: options.reportInclude,
4661
+ detailLimit: options.reportDetailLimit,
4662
+ withGit: options.reportWithGit ?? false,
4663
+ format: options.reportFormat
4664
+ })}
4665
+ `
4666
+ );
4667
+ const tree = discoverSessions(globalConfig);
4088
4668
  const flatSessions = [
4089
4669
  ...tree.projects.flatMap((p) => p.sessions),
4090
4670
  ...tree.coldProjects.flatMap((p) => p.sessions)
@@ -4106,6 +4686,23 @@ if (options.mode === "summary") {
4106
4686
  `);
4107
4687
  process.exit(1);
4108
4688
  }
4689
+ process.stderr.write(
4690
+ `${formatEffectiveOptionsLine("summary", {
4691
+ include: options.summaryInclude,
4692
+ detailLimit: options.summaryDetailLimit,
4693
+ withGit: options.summaryWithGit ?? false,
4694
+ model: options.summaryModel
4695
+ })}
4696
+ `
4697
+ );
4698
+ const isRangeMode = !!(options.summaryFrom && options.summaryTo);
4699
+ process.stderr.write(
4700
+ `prompt = ${formatPromptSource(
4701
+ isRangeMode ? "range" : "daily",
4702
+ options.summaryPrompt
4703
+ )}
4704
+ `
4705
+ );
4109
4706
  const today = /* @__PURE__ */ new Date();
4110
4707
  if (options.summaryFrom && options.summaryTo) {
4111
4708
  const exitCode2 = await runRangeSummary({
@@ -4114,7 +4711,12 @@ if (options.mode === "summary") {
4114
4711
  today,
4115
4712
  force: options.summaryForce ?? false,
4116
4713
  assumeYes: options.summaryAssumeYes ?? false,
4117
- model: options.summaryModel
4714
+ model: options.summaryModel,
4715
+ include: options.summaryInclude,
4716
+ detailLimit: options.summaryDetailLimit,
4717
+ withGit: options.summaryWithGit,
4718
+ open: options.summaryOpen,
4719
+ openIndex: options.summaryOpenIndex
4118
4720
  });
4119
4721
  process.exit(exitCode2);
4120
4722
  }
@@ -4123,7 +4725,12 @@ if (options.mode === "summary") {
4123
4725
  prompt: options.summaryPrompt,
4124
4726
  force: options.summaryForce ?? false,
4125
4727
  today,
4126
- model: options.summaryModel
4728
+ model: options.summaryModel,
4729
+ include: options.summaryInclude,
4730
+ detailLimit: options.summaryDetailLimit,
4731
+ withGit: options.summaryWithGit,
4732
+ open: options.summaryOpen,
4733
+ openIndex: options.summaryOpenIndex
4127
4734
  });
4128
4735
  process.exit(exitCode);
4129
4736
  }
@@ -4132,7 +4739,7 @@ if (options.scopeToCwd) {
4132
4739
  const projectsDir = getProjectsDir();
4133
4740
  let registered = [];
4134
4741
  try {
4135
- registered = readdirSync2(projectsDir).map(decodeProjectPath);
4742
+ registered = readdirSync3(projectsDir).map(decodeProjectPath);
4136
4743
  } catch {
4137
4744
  }
4138
4745
  const safeReal = (p) => {
@@ -4153,13 +4760,11 @@ if (options.scopeToCwd) {
4153
4760
  process.exit(1);
4154
4761
  }
4155
4762
  scopeToProject = match;
4156
- process.stderr.write(`agenthud: scope = ${match}
4763
+ process.stderr.write(`scope = ${match}
4157
4764
  `);
4158
4765
  }
4159
4766
  if (options.mode === "watch") {
4160
4767
  installAltScreenCleanup();
4161
4768
  enterAltScreen();
4162
- } else {
4163
- if (options.mode === "once") clearScreen();
4164
4769
  }
4165
4770
  render(React.createElement(App, { mode: options.mode, scopeToProject }));