agenthud 0.11.4 → 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,7 +426,7 @@ 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
  }
@@ -138,11 +452,24 @@ function parseLocalMidnight(dateStr) {
138
452
  if (Number.isNaN(date.getTime())) return null;
139
453
  return date;
140
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
+ }
141
468
  function todayLocalMidnight() {
142
469
  const now = /* @__PURE__ */ new Date();
143
470
  return new Date(now.getFullYear(), now.getMonth(), now.getDate());
144
471
  }
145
- function parseArgs(args) {
472
+ function parseArgs(args, config) {
146
473
  if (args[0] === "watch") args = args.slice(1);
147
474
  if (args.includes("--help") || args.includes("-h")) {
148
475
  return { mode: "watch", command: "help" };
@@ -156,7 +483,7 @@ function parseArgs(args) {
156
483
  if (args[0] === "report") {
157
484
  const rest = args.slice(1);
158
485
  let reportDate = todayLocalMidnight();
159
- let reportInclude = DEFAULT_TYPES;
486
+ let reportInclude = config?.report.include ?? DEFAULT_INCLUDE_TYPES;
160
487
  let reportError;
161
488
  const FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
162
489
  "--date",
@@ -204,7 +531,7 @@ function parseArgs(args) {
204
531
  }
205
532
  }
206
533
  }
207
- let reportFormat = "markdown";
534
+ let reportFormat = config?.report.format ?? "markdown";
208
535
  const formatIdx = rest.indexOf("--format");
209
536
  if (formatIdx !== -1) {
210
537
  const fmt = rest[formatIdx + 1];
@@ -216,7 +543,7 @@ function parseArgs(args) {
216
543
  reportError = "Invalid format: missing value for --format.";
217
544
  }
218
545
  }
219
- let reportDetailLimit;
546
+ let reportDetailLimit = config?.report.detailLimit;
220
547
  const detailLimitIdx = rest.indexOf("--detail-limit");
221
548
  if (detailLimitIdx !== -1) {
222
549
  const val = rest[detailLimitIdx + 1];
@@ -227,7 +554,7 @@ function parseArgs(args) {
227
554
  reportDetailLimit = n;
228
555
  }
229
556
  }
230
- const reportWithGit = rest.includes("--with-git");
557
+ const reportWithGit = rest.includes("--with-git") || (config?.report.withGit ?? false);
231
558
  return {
232
559
  mode: "report",
233
560
  reportDate,
@@ -254,7 +581,10 @@ function parseArgs(args) {
254
581
  "--from",
255
582
  "--to",
256
583
  "--prompt",
257
- "--model"
584
+ "--model",
585
+ "--include",
586
+ "--format",
587
+ "--detail-limit"
258
588
  ]);
259
589
  for (let i = 0; i < rest.length; i++) {
260
590
  const arg = rest[i];
@@ -357,234 +687,86 @@ function parseArgs(args) {
357
687
  }
358
688
  if (rest.includes("--force")) summaryForce = true;
359
689
  if (rest.includes("-y") || rest.includes("--yes")) summaryAssumeYes = true;
360
- return {
361
- mode: "summary",
362
- summaryDate,
363
- summaryFrom,
364
- summaryTo,
365
- summaryPrompt,
366
- summaryForce,
367
- summaryAssumeYes,
368
- summaryModel,
369
- summaryError
370
- };
371
- }
372
- if (args[0] && !args[0].startsWith("-") && !KNOWN_SUBCOMMANDS.has(args[0])) {
373
- return {
374
- mode: "watch",
375
- error: `Unknown command: "${args[0]}". Run agenthud --help for usage.`
376
- };
377
- }
378
- for (const arg of args) {
379
- if (arg.startsWith("-") && !KNOWN_WATCH_FLAGS.has(arg)) {
380
- return {
381
- mode: "watch",
382
- error: `Unknown option: "${arg}". Run agenthud --help for usage.`
383
- };
384
- }
385
- }
386
- return args.includes("--cwd") ? { mode: "watch", scopeToCwd: true } : { mode: "watch" };
387
- }
388
-
389
- // src/config/globalConfig.ts
390
- import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
391
- import { homedir } from "os";
392
- import { join as join2 } from "path";
393
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
394
- var CONFIG_PATH = join2(homedir(), ".agenthud", "config.yaml");
395
- var STATE_PATH = join2(homedir(), ".agenthud", "state.yaml");
396
- var DEFAULT_GLOBAL_CONFIG = {
397
- refreshIntervalMs: 2e3,
398
- hiddenSessions: [],
399
- hiddenSubAgents: [],
400
- // [] means "show all"; conversation preset bundles assistant + user;
401
- // commits-only preset filters down to git activity.
402
- filterPresets: [[], ["response", "user"], ["commit"]],
403
- hiddenProjects: []
404
- };
405
- var ALL_PRESET_KEYWORDS = /* @__PURE__ */ new Set(["all", "*", "any"]);
406
- function normalizePreset(tokens) {
407
- if (tokens.some((t) => ALL_PRESET_KEYWORDS.has(t.toLowerCase()))) return [];
408
- return tokens;
409
- }
410
- function parseInterval(value) {
411
- const match = value.match(/^(\d+)(s|m)$/);
412
- if (!match) return null;
413
- const n = parseInt(match[1], 10);
414
- return match[2] === "m" ? n * 60 * 1e3 : n * 1e3;
415
- }
416
- function ensureAgenthudDir() {
417
- const dir = join2(homedir(), ".agenthud");
418
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
419
- }
420
- function writeDefaultConfig() {
421
- ensureAgenthudDir();
422
- const defaultYaml = `# AgentHUD user settings.
423
- # App-managed state (hidden sessions/projects) lives in state.yaml.
424
-
425
- # How often to poll for activity updates
426
- refreshInterval: 2s
427
-
428
- # Activity filter presets (cycle with 'f' key in viewer)
429
- # Each list is one preset. Use "all" (or "*") to show everything.
430
- # Types: response, user, bash, edit, thinking, read, glob, commit
431
- filterPresets:
432
- - ["all"]
433
- - ["response", "user"]
434
- - ["commit"]
435
- `;
436
- try {
437
- writeFileSync(CONFIG_PATH, defaultYaml, "utf-8");
438
- } catch {
439
- }
440
- }
441
- function writeState(state) {
442
- ensureAgenthudDir();
443
- try {
444
- writeFileSync(STATE_PATH, stringifyYaml(state), "utf-8");
445
- } catch {
446
- }
447
- }
448
- function rewriteConfigWithoutHideFields(raw) {
449
- const cleaned = {};
450
- for (const [k, v] of Object.entries(raw)) {
451
- if (k === "hiddenSessions" || k === "hiddenSubAgents" || k === "hiddenProjects")
452
- continue;
453
- cleaned[k] = v;
454
- }
455
- try {
456
- writeFileSync(CONFIG_PATH, stringifyYaml(cleaned), "utf-8");
457
- } catch {
458
- }
459
- }
460
- function loadGlobalConfig() {
461
- const config = { ...DEFAULT_GLOBAL_CONFIG };
462
- let configRaw = {};
463
- let configHadHideFields = false;
464
- if (existsSync(CONFIG_PATH)) {
465
- try {
466
- const text = readFileSync2(CONFIG_PATH, "utf-8");
467
- configRaw = parseYaml(text) ?? {};
468
- } catch {
469
- configRaw = {};
470
- }
471
- } else {
472
- writeDefaultConfig();
473
- }
474
- if (typeof configRaw.refreshInterval === "string") {
475
- const ms = parseInterval(configRaw.refreshInterval);
476
- if (ms !== null) config.refreshIntervalMs = ms;
477
- }
478
- if (Array.isArray(configRaw.filterPresets)) {
479
- const presets = configRaw.filterPresets.filter(Array.isArray).map((p) => {
480
- const tokens = p.filter(
481
- (t) => typeof t === "string"
482
- );
483
- return normalizePreset(tokens);
484
- });
485
- if (presets.length > 0) config.filterPresets = presets;
486
- }
487
- const legacyHidden = {};
488
- for (const key of [
489
- "hiddenSessions",
490
- "hiddenSubAgents",
491
- "hiddenProjects"
492
- ]) {
493
- if (Array.isArray(configRaw[key])) {
494
- configHadHideFields = true;
495
- legacyHidden[key] = configRaw[key].filter(
496
- (s) => typeof s === "string"
497
- );
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
+ }
498
709
  }
499
- }
500
- let stateRaw = {};
501
- if (existsSync(STATE_PATH)) {
502
- try {
503
- const text = readFileSync2(STATE_PATH, "utf-8");
504
- stateRaw = parseYaml(text) ?? {};
505
- } catch {
506
- 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
+ }
507
721
  }
508
- }
509
- for (const key of [
510
- "hiddenSessions",
511
- "hiddenSubAgents",
512
- "hiddenProjects"
513
- ]) {
514
- if (Array.isArray(stateRaw[key])) {
515
- config[key] = stateRaw[key].filter(
516
- (s) => typeof s === "string"
517
- );
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
+ }
518
732
  }
519
- }
520
- if (configHadHideFields) {
521
- const merged = {
522
- hiddenSessions: config.hiddenSessions.length > 0 ? config.hiddenSessions : legacyHidden.hiddenSessions ?? [],
523
- hiddenSubAgents: config.hiddenSubAgents.length > 0 ? config.hiddenSubAgents : legacyHidden.hiddenSubAgents ?? [],
524
- 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
525
753
  };
526
- writeState(merged);
527
- rewriteConfigWithoutHideFields(configRaw);
528
- config.hiddenSessions = merged.hiddenSessions;
529
- config.hiddenSubAgents = merged.hiddenSubAgents;
530
- config.hiddenProjects = merged.hiddenProjects;
531
754
  }
532
- return config;
533
- }
534
- function updateState(updates) {
535
- let state = {
536
- hiddenSessions: [],
537
- hiddenSubAgents: [],
538
- hiddenProjects: []
539
- };
540
- if (existsSync(STATE_PATH)) {
541
- try {
542
- const text = readFileSync2(STATE_PATH, "utf-8");
543
- const raw = parseYaml(text) ?? {};
544
- for (const key of [
545
- "hiddenSessions",
546
- "hiddenSubAgents",
547
- "hiddenProjects"
548
- ]) {
549
- if (Array.isArray(raw[key])) {
550
- state[key] = raw[key].filter(
551
- (s) => typeof s === "string"
552
- );
553
- }
554
- }
555
- } catch {
556
- }
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
+ };
557
760
  }
558
- for (const key of [
559
- "hiddenSessions",
560
- "hiddenSubAgents",
561
- "hiddenProjects"
562
- ]) {
563
- if (updates[key] !== void 0) {
564
- 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
+ };
565
767
  }
566
768
  }
567
- writeState(state);
568
- }
569
- function hideSession(id) {
570
- const config = loadGlobalConfig();
571
- if (config.hiddenSessions.includes(id)) return;
572
- updateState({ hiddenSessions: [...config.hiddenSessions, id] });
573
- }
574
- function hideSubAgent(id) {
575
- const config = loadGlobalConfig();
576
- if (config.hiddenSubAgents.includes(id)) return;
577
- updateState({ hiddenSubAgents: [...config.hiddenSubAgents, id] });
578
- }
579
- function hideProject(name) {
580
- const config = loadGlobalConfig();
581
- if (config.hiddenProjects.includes(name)) return;
582
- updateState({ hiddenProjects: [...config.hiddenProjects, name] });
583
- }
584
- function hasProjectLevelConfig() {
585
- const candidate = join2(process.cwd(), ".agenthud", "config.yaml");
586
- if (candidate === join2(homedir(), ".agenthud", "config.yaml")) return false;
587
- return existsSync(candidate);
769
+ return args.includes("--cwd") ? { mode: "watch", scopeToCwd: true } : { mode: "watch" };
588
770
  }
589
771
 
590
772
  // src/data/reportGenerator.ts
@@ -1481,56 +1663,365 @@ function discoverSessions(config, options2) {
1481
1663
  }
1482
1664
 
1483
1665
  // src/data/summaryRunner.ts
1484
- import { spawn } from "child_process";
1666
+ import { spawn as spawn2 } from "child_process";
1485
1667
  import {
1486
1668
  copyFileSync,
1487
1669
  createWriteStream,
1488
- existsSync as existsSync4,
1670
+ existsSync as existsSync5,
1489
1671
  mkdirSync as mkdirSync2,
1490
- readFileSync as readFileSync5,
1672
+ readFileSync as readFileSync6,
1491
1673
  unlinkSync
1492
1674
  } from "fs";
1493
1675
  import { homedir as homedir3 } from "os";
1494
- import { dirname as dirname2, join as join4 } from "path";
1676
+ import { dirname as dirname2, join as join5 } from "path";
1495
1677
  import { createInterface } from "readline";
1496
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
1497
1979
  function agenthudHomeDir() {
1498
- const dir = join4(homedir3(), ".agenthud");
1499
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
1980
+ const dir = join5(homedir3(), ".agenthud");
1981
+ if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
1500
1982
  return dir;
1501
1983
  }
1502
1984
  function summariesDir() {
1503
- const dir = join4(agenthudHomeDir(), "summaries");
1504
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
1985
+ const dir = join5(agenthudHomeDir(), "summaries");
1986
+ if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
1505
1987
  return dir;
1506
1988
  }
1507
1989
  function promptFilename(kind) {
1508
1990
  return kind === "daily" ? "summary-prompt.md" : "summary-range-prompt.md";
1509
1991
  }
1510
1992
  function userPromptPath(kind) {
1511
- 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, "/");
1512
2003
  }
1513
2004
  function templatePath(kind) {
1514
2005
  const here = dirname2(fileURLToPath2(import.meta.url));
1515
- return join4(here, "templates", promptFilename(kind));
2006
+ return join5(here, "templates", promptFilename(kind));
1516
2007
  }
1517
2008
  function dateKey(d) {
1518
2009
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1519
2010
  }
1520
2011
  function dailyCachePath(date) {
1521
- return join4(summariesDir(), `${dateKey(date)}.md`);
2012
+ return join5(summariesDir(), `${dateKey(date)}.md`);
1522
2013
  }
1523
2014
  function rangeCachePath(from, to) {
1524
- return join4(summariesDir(), `range-${dateKey(from)}_${dateKey(to)}.md`);
2015
+ return join5(summariesDir(), `range-${dateKey(from)}_${dateKey(to)}.md`);
1525
2016
  }
1526
2017
  function isSameLocalDay2(a, b) {
1527
2018
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1528
2019
  }
1529
2020
  function ensureUserPromptFile(kind) {
1530
2021
  const p = userPromptPath(kind);
1531
- if (existsSync4(p)) return;
2022
+ if (existsSync5(p)) return;
1532
2023
  const dir = dirname2(p);
1533
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
2024
+ if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
1534
2025
  try {
1535
2026
  copyFileSync(templatePath(kind), p);
1536
2027
  } catch {
@@ -1539,14 +2030,14 @@ function ensureUserPromptFile(kind) {
1539
2030
  function resolvePrompt(kind, override) {
1540
2031
  if (override) return override;
1541
2032
  const p = userPromptPath(kind);
1542
- if (existsSync4(p)) {
2033
+ if (existsSync5(p)) {
1543
2034
  try {
1544
- return readFileSync5(p, "utf-8");
2035
+ return readFileSync6(p, "utf-8");
1545
2036
  } catch {
1546
2037
  }
1547
2038
  }
1548
2039
  try {
1549
- return readFileSync5(templatePath(kind), "utf-8");
2040
+ return readFileSync6(templatePath(kind), "utf-8");
1550
2041
  } catch {
1551
2042
  return "Summarize the input below.";
1552
2043
  }
@@ -1601,7 +2092,7 @@ function spawnClaude(opts) {
1601
2092
  ];
1602
2093
  if (opts.model) args.push("--model", opts.model);
1603
2094
  args.push(opts.prompt);
1604
- const proc = spawn(
2095
+ const proc = spawn2(
1605
2096
  "claude",
1606
2097
  args,
1607
2098
  {
@@ -1722,11 +2213,11 @@ async function generateDailySummary(opts) {
1722
2213
  const isToday = isSameLocalDay2(opts.date, opts.today);
1723
2214
  const cached = dailyCachePath(opts.date);
1724
2215
  const dateLabel = dateKey(opts.date);
1725
- if (!isToday && !opts.force && existsSync4(cached)) {
2216
+ if (!isToday && !opts.force && existsSync5(cached)) {
1726
2217
  try {
1727
- const content = readFileSync5(cached, "utf-8");
2218
+ const content = readFileSync6(cached, "utf-8");
1728
2219
  if (opts.announce) {
1729
- process.stderr.write(`agenthud: cached summary from ${cached}
2220
+ process.stderr.write(`cached summary from ${cached}
1730
2221
  `);
1731
2222
  }
1732
2223
  if (opts.streamToStdout) {
@@ -1744,7 +2235,7 @@ async function generateDailySummary(opts) {
1744
2235
  }
1745
2236
  }
1746
2237
  if (opts.announce) {
1747
- process.stderr.write(`agenthud: scanning sessions for ${dateLabel}...
2238
+ process.stderr.write(`scanning sessions for ${dateLabel}...
1748
2239
  `);
1749
2240
  }
1750
2241
  const config = loadGlobalConfig();
@@ -1755,10 +2246,13 @@ async function generateDailySummary(opts) {
1755
2246
  ];
1756
2247
  const reportMarkdown = generateReport(flatSessions, {
1757
2248
  date: opts.date,
1758
- 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.
1759
2253
  format: "markdown",
1760
- detailLimit: 0,
1761
- withGit: true
2254
+ detailLimit: opts.detailLimit,
2255
+ withGit: opts.withGit
1762
2256
  });
1763
2257
  const reportBytes = Buffer.byteLength(reportMarkdown, "utf-8");
1764
2258
  const estimatedTokens = Math.ceil(reportBytes / 4);
@@ -1773,7 +2267,7 @@ async function generateDailySummary(opts) {
1773
2267
  ).length;
1774
2268
  const sizeKb = (reportBytes / 1024).toFixed(1);
1775
2269
  process.stderr.write(
1776
- `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)
1777
2271
  `
1778
2272
  );
1779
2273
  }
@@ -1812,13 +2306,15 @@ async function generateDailySummary(opts) {
1812
2306
  }
1813
2307
  }
1814
2308
  }
1815
- if (opts.announce) {
2309
+ const showTicker = opts.announce && !opts.streamToStdout;
2310
+ if (opts.announce && !showTicker) {
1816
2311
  process.stderr.write(
1817
- `agenthud: sending to claude (this may take a minute)...
2312
+ `sending to claude (this may take a minute)...
1818
2313
 
1819
2314
  `
1820
2315
  );
1821
2316
  }
2317
+ const stopTicker = showTicker ? startStderrTicker("sending to claude") : null;
1822
2318
  const prompt = resolvePrompt("daily", opts.promptOverride);
1823
2319
  const result = await spawnClaude({
1824
2320
  prompt,
@@ -1827,12 +2323,13 @@ async function generateDailySummary(opts) {
1827
2323
  streamToStdout: opts.streamToStdout,
1828
2324
  model: opts.model
1829
2325
  });
2326
+ if (stopTicker) stopTicker();
1830
2327
  if (opts.announce && result.code === 0) {
1831
2328
  process.stderr.write("\n");
1832
- process.stderr.write(`agenthud: saved to ${cached}
2329
+ process.stderr.write(`saved to ${cached}
1833
2330
  `);
1834
2331
  if (result.usage) {
1835
- process.stderr.write(`agenthud: ${formatUsage(result.usage)}
2332
+ process.stderr.write(`${formatUsage(result.usage)}
1836
2333
  `);
1837
2334
  }
1838
2335
  }
@@ -1850,10 +2347,25 @@ async function runSummary(options2) {
1850
2347
  today: options2.today,
1851
2348
  force: options2.force,
1852
2349
  promptOverride: options2.prompt,
1853
- streamToStdout: true,
2350
+ streamToStdout: !options2.open,
1854
2351
  announce: true,
1855
- model: options2.model
2352
+ model: options2.model,
2353
+ include: options2.include,
2354
+ detailLimit: options2.detailLimit,
2355
+ withGit: options2.withGit
1856
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
+ }
1857
2369
  return res.code;
1858
2370
  }
1859
2371
  async function runRangeSummary(options2) {
@@ -1867,16 +2379,26 @@ async function runRangeSummary(options2) {
1867
2379
  options2.force,
1868
2380
  dates,
1869
2381
  options2.today,
1870
- existsSync4(rangeCache)
2382
+ existsSync5(rangeCache)
1871
2383
  )) {
1872
2384
  try {
1873
- const content = readFileSync5(rangeCache, "utf-8");
2385
+ const content = readFileSync6(rangeCache, "utf-8");
1874
2386
  process.stderr.write(
1875
- `agenthud: cached range summary from ${rangeCache}
2387
+ `cached range summary from ${rangeCache}
1876
2388
  `
1877
2389
  );
1878
- process.stdout.write(content);
1879
- 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
+ }
1880
2402
  return 0;
1881
2403
  } catch {
1882
2404
  }
@@ -1885,15 +2407,15 @@ async function runRangeSummary(options2) {
1885
2407
  let missingCount = 0;
1886
2408
  for (const d of dates) {
1887
2409
  const isToday = isSameLocalDay2(d, options2.today);
1888
- if (!isToday && existsSync4(dailyCachePath(d))) cachedCount++;
2410
+ if (!isToday && existsSync5(dailyCachePath(d))) cachedCount++;
1889
2411
  else missingCount++;
1890
2412
  }
1891
2413
  process.stderr.write(
1892
- `agenthud: range ${fromLabel} \u2192 ${toLabel} (${dates.length} days)
2414
+ `range ${fromLabel} \u2192 ${toLabel} (${dates.length} days)
1893
2415
  `
1894
2416
  );
1895
2417
  process.stderr.write(
1896
- `agenthud: ${cachedCount} cached, ${missingCount} to generate
2418
+ `${cachedCount} cached, ${missingCount} to generate
1897
2419
  `
1898
2420
  );
1899
2421
  const dailyMarkdowns = [];
@@ -1902,9 +2424,9 @@ async function runRangeSummary(options2) {
1902
2424
  const label = dateKey(d);
1903
2425
  const isToday = isSameLocalDay2(d, options2.today);
1904
2426
  process.stderr.write(`
1905
- agenthud: --- ${label} ---
2427
+ --- ${label} ---
1906
2428
  `);
1907
- const willPrompt = !options2.assumeYes && (isToday || !existsSync4(dailyCachePath(d)));
2429
+ const willPrompt = !options2.assumeYes && (isToday || !existsSync5(dailyCachePath(d)));
1908
2430
  const confirmer = willPrompt ? async () => {
1909
2431
  const hint = isToday ? " (today \u2014 regenerated every time)" : "";
1910
2432
  return ask(`Generate this summary${hint}? [Y/n] `, true);
@@ -1917,7 +2439,10 @@ agenthud: --- ${label} ---
1917
2439
  announce: true,
1918
2440
  confirmBeforeSpawn: confirmer,
1919
2441
  assumeYes: options2.assumeYes,
1920
- model: options2.model
2442
+ model: options2.model,
2443
+ include: options2.include,
2444
+ detailLimit: options2.detailLimit,
2445
+ withGit: options2.withGit
1921
2446
  });
1922
2447
  if (res.skipped) {
1923
2448
  process.stderr.write(`agenthud: ${label} \u2014 skipped by user.
@@ -1934,7 +2459,7 @@ agenthud: --- ${label} ---
1934
2459
  }
1935
2460
  const text = res.markdown.trim();
1936
2461
  if (text.length === 0 || /^no activity found/i.test(text)) {
1937
- process.stderr.write(`agenthud: ${label} has no activity \u2014 skipping.
2462
+ process.stderr.write(`${label} has no activity \u2014 skipping.
1938
2463
  `);
1939
2464
  continue;
1940
2465
  }
@@ -1949,37 +2474,51 @@ agenthud: --- ${label} ---
1949
2474
  ${markdown}`).join("\n\n---\n\n");
1950
2475
  process.stderr.write(
1951
2476
  `
1952
- agenthud: combining ${dailyMarkdowns.length} daily summaries into range summary...
2477
+ combining ${dailyMarkdowns.length} daily summaries into range summary...
1953
2478
  `
1954
2479
  );
1955
- process.stderr.write(
1956
- `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)...
1957
2485
 
1958
2486
  `
1959
- );
2487
+ );
2488
+ }
2489
+ const stopMetaTicker = metaStreams ? null : startStderrTicker("sending to claude");
1960
2490
  const metaPrompt = resolvePrompt("range");
1961
2491
  const metaResult = await spawnClaude({
1962
2492
  prompt: metaPrompt,
1963
2493
  stdin: metaInput,
1964
2494
  cachePath: rangeCache,
1965
- streamToStdout: true,
2495
+ streamToStdout: metaStreams,
1966
2496
  model: options2.model
1967
2497
  });
2498
+ if (stopMetaTicker) stopMetaTicker();
1968
2499
  if (metaResult.code !== 0) {
1969
2500
  return metaResult.code;
1970
2501
  }
1971
2502
  process.stderr.write("\n");
1972
- process.stderr.write(`agenthud: saved to ${rangeCache}
2503
+ process.stderr.write(`saved to ${rangeCache}
1973
2504
  `);
1974
2505
  if (metaResult.usage) {
1975
- process.stderr.write(`agenthud: ${formatUsage(metaResult.usage)}
2506
+ process.stderr.write(`${formatUsage(metaResult.usage)}
1976
2507
  `);
1977
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
+ }
1978
2517
  return 0;
1979
2518
  }
1980
2519
 
1981
2520
  // src/ui/App.tsx
1982
- import { existsSync as existsSync5, watch } from "fs";
2521
+ import { existsSync as existsSync6, watch } from "fs";
1983
2522
  import { basename as basename3 } from "path";
1984
2523
  import { Box as Box5, Text as Text5, useApp, useInput, useStdout } from "ink";
1985
2524
  import { useCallback, useEffect as useEffect3, useMemo, useRef, useState as useState3 } from "react";
@@ -2416,6 +2955,7 @@ var SECTIONS = [
2416
2955
  title: "Project tree",
2417
2956
  rows: [
2418
2957
  ["\u2191 \u2193 / k j", "Move selection"],
2958
+ ["\u2190", "Jump to parent (sub-agent \u2192 session, session \u2192 project)"],
2419
2959
  ["PgUp / Ctrl+B", "Page up"],
2420
2960
  ["PgDn / Ctrl+F", "Page down"],
2421
2961
  ["\u21B5", "Expand/collapse project, session, or summary"],
@@ -2565,6 +3105,7 @@ function useHotkeys({
2565
3105
  onHelpScroll,
2566
3106
  onHelpScrollToTop,
2567
3107
  onToggleTracking,
3108
+ onJumpToParent,
2568
3109
  filterLabel,
2569
3110
  trackingOn = false
2570
3111
  }) {
@@ -2684,6 +3225,10 @@ function useHotkeys({
2684
3225
  onHide();
2685
3226
  return;
2686
3227
  }
3228
+ if (key.leftArrow && onJumpToParent) {
3229
+ onJumpToParent();
3230
+ return;
3231
+ }
2687
3232
  if (key.upArrow || input === "k") {
2688
3233
  onScrollUp();
2689
3234
  return;
@@ -2717,6 +3262,7 @@ function useHotkeys({
2717
3262
  ...trackingItems,
2718
3263
  "Tab: viewer",
2719
3264
  "\u2191\u2193/jk: select",
3265
+ "\u2190: parent",
2720
3266
  "PgUp/Dn: page",
2721
3267
  "\u21B5: expand",
2722
3268
  "h: hide",
@@ -3194,7 +3740,8 @@ function appendSubAgentRows(result, session, expandedIds) {
3194
3740
  const sessionExpandedKey = `__expanded-session-${session.id}`;
3195
3741
  const sessionHidden = isCold ? !expandedIds.has(sessionExpandedKey) : expandedIds.has(sessionCollapsedKey);
3196
3742
  if (sessionHidden) return;
3197
- if (expandedIds.has(session.id)) {
3743
+ const subAgentsFullyExpanded = expandedIds.has(session.id) || isCold && expandedIds.has(sessionExpandedKey);
3744
+ if (subAgentsFullyExpanded) {
3198
3745
  result.push(...session.subAgents);
3199
3746
  } else {
3200
3747
  result.push(
@@ -3322,6 +3869,26 @@ function collectAllIds(tree) {
3322
3869
  }
3323
3870
  return ids;
3324
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
+ }
3325
3892
  function initialSelectedId(tree) {
3326
3893
  const firstProject = tree.projects[0];
3327
3894
  if (firstProject) return `__proj-${firstProject.name}__`;
@@ -3496,7 +4063,7 @@ function App({
3496
4063
  useEffect3(() => {
3497
4064
  if (!isWatchMode) return;
3498
4065
  const projectsDir = getProjectsDir();
3499
- const usePolling = process.platform === "linux" || !existsSync5(projectsDir);
4066
+ const usePolling = process.platform === "linux" || !existsSync6(projectsDir);
3500
4067
  if (usePolling) {
3501
4068
  const timer = setInterval(
3502
4069
  () => refreshRef.current(),
@@ -3590,6 +4157,13 @@ function App({
3590
4157
  });
3591
4158
  },
3592
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
+ },
3593
4167
  onSwitchFocus: () => setFocus((f) => f === "tree" ? "viewer" : "tree"),
3594
4168
  // cursorLine = "entries back from the newest" (0 = newest = bottom row).
3595
4169
  // Up arrow moves visually upward = older direction = cursorLine++.
@@ -4031,15 +4605,16 @@ function installAltScreenCleanup() {
4031
4605
  }
4032
4606
 
4033
4607
  // src/utils/legacyConfig.ts
4034
- import { join as join5, resolve } from "path";
4608
+ import { join as join6, resolve } from "path";
4035
4609
  function isLegacyProjectConfig(cwd, home) {
4036
- const legacy = resolve(join5(cwd, ".agenthud", "config.yaml"));
4037
- 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"));
4038
4612
  return legacy !== global;
4039
4613
  }
4040
4614
 
4041
4615
  // src/main.ts
4042
- var options = parseArgs(process.argv.slice(2));
4616
+ var globalConfig = loadGlobalConfig();
4617
+ var options = parseArgs(process.argv.slice(2), globalConfig);
4043
4618
  if (options.error) {
4044
4619
  process.stderr.write(`agenthud: ${options.error}
4045
4620
  `);
@@ -4053,8 +4628,8 @@ if (options.command === "version") {
4053
4628
  console.log(getVersion());
4054
4629
  process.exit(0);
4055
4630
  }
4056
- var legacyConfig = join6(process.cwd(), ".agenthud", "config.yaml");
4057
- if (isLegacyProjectConfig(process.cwd(), homedir5()) && existsSync6(legacyConfig)) {
4631
+ var legacyConfig = join7(process.cwd(), ".agenthud", "config.yaml");
4632
+ if (isLegacyProjectConfig(process.cwd(), homedir5()) && existsSync7(legacyConfig)) {
4058
4633
  console.log(
4059
4634
  "The project-level config file (.agenthud/config.yaml) is no longer supported."
4060
4635
  );
@@ -4080,8 +4655,16 @@ if (options.mode === "report") {
4080
4655
  `);
4081
4656
  process.exit(1);
4082
4657
  }
4083
- const config = loadGlobalConfig();
4084
- 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);
4085
4668
  const flatSessions = [
4086
4669
  ...tree.projects.flatMap((p) => p.sessions),
4087
4670
  ...tree.coldProjects.flatMap((p) => p.sessions)
@@ -4103,6 +4686,23 @@ if (options.mode === "summary") {
4103
4686
  `);
4104
4687
  process.exit(1);
4105
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
+ );
4106
4706
  const today = /* @__PURE__ */ new Date();
4107
4707
  if (options.summaryFrom && options.summaryTo) {
4108
4708
  const exitCode2 = await runRangeSummary({
@@ -4111,7 +4711,12 @@ if (options.mode === "summary") {
4111
4711
  today,
4112
4712
  force: options.summaryForce ?? false,
4113
4713
  assumeYes: options.summaryAssumeYes ?? false,
4114
- 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
4115
4720
  });
4116
4721
  process.exit(exitCode2);
4117
4722
  }
@@ -4120,7 +4725,12 @@ if (options.mode === "summary") {
4120
4725
  prompt: options.summaryPrompt,
4121
4726
  force: options.summaryForce ?? false,
4122
4727
  today,
4123
- 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
4124
4734
  });
4125
4735
  process.exit(exitCode);
4126
4736
  }
@@ -4129,7 +4739,7 @@ if (options.scopeToCwd) {
4129
4739
  const projectsDir = getProjectsDir();
4130
4740
  let registered = [];
4131
4741
  try {
4132
- registered = readdirSync2(projectsDir).map(decodeProjectPath);
4742
+ registered = readdirSync3(projectsDir).map(decodeProjectPath);
4133
4743
  } catch {
4134
4744
  }
4135
4745
  const safeReal = (p) => {
@@ -4150,7 +4760,7 @@ if (options.scopeToCwd) {
4150
4760
  process.exit(1);
4151
4761
  }
4152
4762
  scopeToProject = match;
4153
- process.stderr.write(`agenthud: scope = ${match}
4763
+ process.stderr.write(`scope = ${match}
4154
4764
  `);
4155
4765
  }
4156
4766
  if (options.mode === "watch") {