agenthud 0.11.4 → 0.12.2

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 existsSync8, 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 join8 } 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];
@@ -352,239 +682,91 @@ function parseArgs(args) {
352
682
  if (!val) {
353
683
  summaryError = "Invalid --model: missing value (e.g. --model sonnet).";
354
684
  } else {
355
- summaryModel = val;
356
- }
357
- }
358
- if (rest.includes("--force")) summaryForce = true;
359
- 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
- );
498
- }
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 = {};
507
- }
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
- );
518
- }
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 ?? []
525
- };
526
- writeState(merged);
527
- rewriteConfigWithoutHideFields(configRaw);
528
- config.hiddenSessions = merged.hiddenSessions;
529
- config.hiddenSubAgents = merged.hiddenSubAgents;
530
- config.hiddenProjects = merged.hiddenProjects;
531
- }
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
- );
685
+ summaryModel = val;
686
+ }
687
+ }
688
+ if (rest.includes("--force")) summaryForce = true;
689
+ if (rest.includes("-y") || rest.includes("--yes")) summaryAssumeYes = true;
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;
553
707
  }
554
708
  }
555
- } catch {
556
709
  }
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
+ }
721
+ }
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
+ }
732
+ }
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
753
+ };
557
754
  }
558
- for (const key of [
559
- "hiddenSessions",
560
- "hiddenSubAgents",
561
- "hiddenProjects"
562
- ]) {
563
- if (updates[key] !== void 0) {
564
- state[key] = updates[key];
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
+ };
760
+ }
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,435 @@ 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 existsSync6,
1489
1671
  mkdirSync as mkdirSync2,
1490
- readFileSync as readFileSync5,
1491
- unlinkSync
1672
+ readFileSync as readFileSync7,
1673
+ unlinkSync,
1674
+ writeFileSync as writeFileSync3
1492
1675
  } from "fs";
1493
1676
  import { homedir as homedir3 } from "os";
1494
- import { dirname as dirname2, join as join4 } from "path";
1677
+ import { dirname as dirname2, join as join6 } from "path";
1495
1678
  import { createInterface } from "readline";
1496
1679
  import { fileURLToPath as fileURLToPath2 } from "url";
1680
+
1681
+ // src/utils/openInDefaultApp.ts
1682
+ import { spawn } from "child_process";
1683
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
1684
+ import { join as join4 } from "path";
1685
+ function buildOpenCommand(platform, path, opts = {}) {
1686
+ switch (platform) {
1687
+ case "darwin":
1688
+ return { command: "open", args: [path] };
1689
+ case "linux":
1690
+ if (opts.wslView) return { command: "wslview", args: [path] };
1691
+ return { command: "xdg-open", args: [path] };
1692
+ case "win32":
1693
+ return { command: "cmd", args: ["/c", "start", "", path] };
1694
+ default:
1695
+ return null;
1696
+ }
1697
+ }
1698
+ function isWSL() {
1699
+ if (process.env.WSL_DISTRO_NAME) return true;
1700
+ try {
1701
+ const ver = readFileSync5("/proc/version", "utf-8");
1702
+ return /microsoft|wsl/i.test(ver);
1703
+ } catch {
1704
+ return false;
1705
+ }
1706
+ }
1707
+ function commandExists(command) {
1708
+ const PATH = process.env.PATH ?? "";
1709
+ const sep = process.platform === "win32" ? ";" : ":";
1710
+ const exts = process.platform === "win32" ? (process.env.PATHEXT?.split(";") ?? [".EXE", ".CMD", ".BAT"]).map(
1711
+ (e) => e.toLowerCase()
1712
+ ) : [""];
1713
+ for (const dir of PATH.split(sep)) {
1714
+ if (!dir) continue;
1715
+ const full = join4(dir, command);
1716
+ for (const ext of exts) {
1717
+ try {
1718
+ if (existsSync4(full + ext)) return true;
1719
+ } catch {
1720
+ }
1721
+ }
1722
+ }
1723
+ return false;
1724
+ }
1725
+ async function openInDefaultApp(path) {
1726
+ const wslView = isWSL() && commandExists("wslview");
1727
+ const cmd = buildOpenCommand(process.platform, path, { wslView });
1728
+ if (!cmd) {
1729
+ process.stderr.write(
1730
+ `agenthud: --open: no known opener for platform "${process.platform}"
1731
+ `
1732
+ );
1733
+ return;
1734
+ }
1735
+ if (!commandExists(cmd.command)) {
1736
+ process.stderr.write(
1737
+ `agenthud: --open: '${cmd.command}' is not on PATH; cannot open ${path}
1738
+ `
1739
+ );
1740
+ if (isWSL()) {
1741
+ process.stderr.write(
1742
+ "agenthud: hint: on WSL install `wslu` (provides wslview) so files open with the Windows host's default app \u2014 e.g. `sudo apt install wslu`\n"
1743
+ );
1744
+ }
1745
+ return;
1746
+ }
1747
+ await new Promise((resolve2) => {
1748
+ let settled = false;
1749
+ const finish = () => {
1750
+ if (settled) return;
1751
+ settled = true;
1752
+ resolve2();
1753
+ };
1754
+ let child;
1755
+ try {
1756
+ child = spawn(cmd.command, cmd.args, {
1757
+ detached: true,
1758
+ stdio: "ignore"
1759
+ });
1760
+ } catch (err) {
1761
+ const msg = err instanceof Error ? err.message : String(err);
1762
+ process.stderr.write(`agenthud: --open failed: ${msg}
1763
+ `);
1764
+ finish();
1765
+ return;
1766
+ }
1767
+ child.on("error", (err) => {
1768
+ process.stderr.write(`agenthud: --open failed: ${err.message}
1769
+ `);
1770
+ finish();
1771
+ });
1772
+ child.on("exit", (code) => {
1773
+ if (code !== null && code !== 0) {
1774
+ process.stderr.write(
1775
+ `agenthud: --open: '${cmd.command}' exited with code ${code}
1776
+ `
1777
+ );
1778
+ }
1779
+ finish();
1780
+ });
1781
+ setTimeout(() => {
1782
+ if (!settled) {
1783
+ child.unref();
1784
+ finish();
1785
+ }
1786
+ }, 200).unref();
1787
+ });
1788
+ }
1789
+
1790
+ // src/data/summariesIndex.ts
1791
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
1792
+ import { join as join5 } from "path";
1793
+ var INDEX_HEADER_MARKER = "<!-- agenthud-summaries-index -->";
1794
+ var BACKLINK_START_MARKER = "<!-- agenthud-backlinks-start -->";
1795
+ var BACKLINK_END_MARKER = "<!-- agenthud-backlinks-end -->";
1796
+ var DAILY_RE = /^(\d{4})-(\d{2})-(\d{2})\.md$/;
1797
+ var RANGE_RE = /^range-(\d{4})-(\d{2})-(\d{2})_(\d{4})-(\d{2})-(\d{2})\.md$/;
1798
+ function makeLocalDate(y, m, d) {
1799
+ const date = new Date(y, m - 1, d);
1800
+ if (date.getFullYear() !== y || date.getMonth() !== m - 1 || date.getDate() !== d) {
1801
+ return null;
1802
+ }
1803
+ return date;
1804
+ }
1805
+ function parseSummaryFilename(name) {
1806
+ const range = RANGE_RE.exec(name);
1807
+ if (range) {
1808
+ const from = makeLocalDate(
1809
+ Number(range[1]),
1810
+ Number(range[2]),
1811
+ Number(range[3])
1812
+ );
1813
+ const to = makeLocalDate(
1814
+ Number(range[4]),
1815
+ Number(range[5]),
1816
+ Number(range[6])
1817
+ );
1818
+ if (!from || !to) return null;
1819
+ return { kind: "range", from, to, filename: name };
1820
+ }
1821
+ const daily = DAILY_RE.exec(name);
1822
+ if (daily) {
1823
+ const date = makeLocalDate(
1824
+ Number(daily[1]),
1825
+ Number(daily[2]),
1826
+ Number(daily[3])
1827
+ );
1828
+ if (!date) return null;
1829
+ return { kind: "daily", date, filename: name };
1830
+ }
1831
+ return null;
1832
+ }
1833
+ function listSummaries(dir) {
1834
+ if (!existsSync5(dir)) return [];
1835
+ let names;
1836
+ try {
1837
+ names = readdirSync2(dir);
1838
+ } catch {
1839
+ return [];
1840
+ }
1841
+ const entries = names.map((n) => parseSummaryFilename(n)).filter((e) => e !== null);
1842
+ const anchor = (e) => e.kind === "range" ? e.from : e.date;
1843
+ entries.sort((a, b) => {
1844
+ const da = anchor(a);
1845
+ const db = anchor(b);
1846
+ if (da.getFullYear() !== db.getFullYear()) {
1847
+ return db.getFullYear() - da.getFullYear();
1848
+ }
1849
+ if (da.getMonth() !== db.getMonth()) {
1850
+ return db.getMonth() - da.getMonth();
1851
+ }
1852
+ if (a.kind !== b.kind) return a.kind === "range" ? -1 : 1;
1853
+ return db.getTime() - da.getTime();
1854
+ });
1855
+ return entries;
1856
+ }
1857
+ var MONTHS = [
1858
+ "January",
1859
+ "February",
1860
+ "March",
1861
+ "April",
1862
+ "May",
1863
+ "June",
1864
+ "July",
1865
+ "August",
1866
+ "September",
1867
+ "October",
1868
+ "November",
1869
+ "December"
1870
+ ];
1871
+ var WEEKDAYS_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1872
+ var WEEKDAYS_LONG = [
1873
+ "Sunday",
1874
+ "Monday",
1875
+ "Tuesday",
1876
+ "Wednesday",
1877
+ "Thursday",
1878
+ "Friday",
1879
+ "Saturday"
1880
+ ];
1881
+ function formatDateKey(d) {
1882
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1883
+ }
1884
+ function formatDateLabel(d) {
1885
+ return `${formatDateKey(d)} (${WEEKDAYS_SHORT[d.getDay()]})`;
1886
+ }
1887
+ function entryAnchor(e) {
1888
+ return e.kind === "range" ? e.from : e.date;
1889
+ }
1890
+ function buildIndexMarkdown(entries, snippets) {
1891
+ const lines = [INDEX_HEADER_MARKER, "", "# AgentHUD summaries", ""];
1892
+ if (entries.length === 0) {
1893
+ lines.push("_No summaries yet. Run `agenthud summary` to create one._");
1894
+ return `${lines.join("\n")}
1895
+ `;
1896
+ }
1897
+ const byYear = /* @__PURE__ */ new Map();
1898
+ for (const entry of entries) {
1899
+ const a = entryAnchor(entry);
1900
+ const y = a.getFullYear();
1901
+ const m = a.getMonth();
1902
+ const months = byYear.get(y) ?? /* @__PURE__ */ new Map();
1903
+ const bucket = months.get(m) ?? [];
1904
+ bucket.push(entry);
1905
+ months.set(m, bucket);
1906
+ byYear.set(y, months);
1907
+ }
1908
+ for (const [year, months] of byYear) {
1909
+ lines.push(`## ${year}`, "");
1910
+ for (const [month, bucket] of months) {
1911
+ lines.push(`### ${MONTHS[month]}`);
1912
+ for (const entry of bucket) {
1913
+ const snippet = snippets?.get(entry.filename);
1914
+ const trail = snippet ? ` \u2014 ${snippet}` : "";
1915
+ if (entry.kind === "range") {
1916
+ const fromIso = formatDateKey(entry.from);
1917
+ const toIso = formatDateKey(entry.to);
1918
+ lines.push(
1919
+ `- [Range: ${fromIso} \u2192 ${toIso}](./${entry.filename}) \xB7 weekly${trail}`
1920
+ );
1921
+ } else {
1922
+ lines.push(
1923
+ `- [${formatDateLabel(entry.date)}](./${entry.filename})${trail}`
1924
+ );
1925
+ }
1926
+ }
1927
+ lines.push("");
1928
+ }
1929
+ }
1930
+ return `${lines.join("\n")}
1931
+ `;
1932
+ }
1933
+ function buildTitleLine(entry) {
1934
+ if (entry.kind === "range") {
1935
+ return `# Range: ${formatDateKey(entry.from)} \u2192 ${formatDateKey(entry.to)}`;
1936
+ }
1937
+ const iso = formatDateKey(entry.date);
1938
+ const weekday = WEEKDAYS_LONG[entry.date.getDay()];
1939
+ return `# ${iso} (${weekday})`;
1940
+ }
1941
+ function buildHeaderBlock(currentFilename, entries) {
1942
+ const parts = ["[\u2190 all summaries](./index.md)"];
1943
+ const me = entries.find((e) => e.filename === currentFilename);
1944
+ if (me && me.kind === "daily") {
1945
+ const dailies = entries.filter((e) => e.kind === "daily").sort((a, b) => a.date.getTime() - b.date.getTime());
1946
+ const idx = dailies.findIndex((e) => e.filename === currentFilename);
1947
+ if (idx > 0) {
1948
+ const prev = dailies[idx - 1];
1949
+ parts.push(
1950
+ `[\u2190 ${formatDateLabel(prev.date)}](./${prev.filename})`
1951
+ );
1952
+ }
1953
+ if (idx >= 0 && idx < dailies.length - 1) {
1954
+ const next = dailies[idx + 1];
1955
+ parts.push(
1956
+ `[${formatDateLabel(next.date)} \u2192](./${next.filename})`
1957
+ );
1958
+ }
1959
+ }
1960
+ const backlinkLine = parts.join(" \xB7 ");
1961
+ const title = me ? buildTitleLine(me) : "";
1962
+ return `${BACKLINK_START_MARKER}
1963
+ ${backlinkLine}
1964
+
1965
+ ${title}
1966
+ ${BACKLINK_END_MARKER}
1967
+
1968
+ `;
1969
+ }
1970
+ function stripExistingHeaderBlock(content) {
1971
+ if (!content.startsWith(BACKLINK_START_MARKER)) return content;
1972
+ const endIdx = content.indexOf(BACKLINK_END_MARKER);
1973
+ if (endIdx === -1) return content;
1974
+ let cut = endIdx + BACKLINK_END_MARKER.length;
1975
+ while (cut < content.length && (content[cut] === "\n" || content[cut] === "\r")) {
1976
+ cut++;
1977
+ }
1978
+ return content.slice(cut);
1979
+ }
1980
+ function prependHeaderBlock(content, block) {
1981
+ return block + stripExistingHeaderBlock(content);
1982
+ }
1983
+ function extractContextSnippet(content, maxChars = 200) {
1984
+ const body = stripExistingHeaderBlock(content);
1985
+ for (const raw of body.split("\n")) {
1986
+ const line = raw.trim();
1987
+ if (!line) continue;
1988
+ if (line.startsWith("#")) continue;
1989
+ if (line.startsWith("<!--")) continue;
1990
+ if (line.length <= maxChars) return line;
1991
+ return `${line.slice(0, maxChars - 1).trimEnd()}\u2026`;
1992
+ }
1993
+ return null;
1994
+ }
1995
+ function regenerateIndex(summariesDir2) {
1996
+ const entries = listSummaries(summariesDir2);
1997
+ const snippets = /* @__PURE__ */ new Map();
1998
+ for (const entry of entries) {
1999
+ const path = join5(summariesDir2, entry.filename);
2000
+ try {
2001
+ const content = readFileSync6(path, "utf-8");
2002
+ const snippet = extractContextSnippet(content);
2003
+ if (snippet) snippets.set(entry.filename, snippet);
2004
+ const block = buildHeaderBlock(entry.filename, entries);
2005
+ const updated = prependHeaderBlock(content, block);
2006
+ if (updated !== content) {
2007
+ writeFileSync2(path, updated, "utf-8");
2008
+ }
2009
+ } catch {
2010
+ }
2011
+ }
2012
+ const indexPath = join5(summariesDir2, "index.md");
2013
+ try {
2014
+ writeFileSync2(
2015
+ indexPath,
2016
+ buildIndexMarkdown(entries, snippets),
2017
+ "utf-8"
2018
+ );
2019
+ } catch (err) {
2020
+ process.stderr.write(
2021
+ `warning: cannot write summaries index (${err.message})
2022
+ `
2023
+ );
2024
+ }
2025
+ }
2026
+
2027
+ // src/utils/stderrTicker.ts
2028
+ function formatTickerLine(label, elapsedSeconds, dots) {
2029
+ const tail = ".".repeat(dots % 4).padEnd(3, " ");
2030
+ return `${label}${tail} ${elapsedSeconds}s`;
2031
+ }
2032
+ function startStderrTicker(label, options2 = {}) {
2033
+ const intervalMs = options2.intervalMs ?? 500;
2034
+ const stream = options2.stream ?? process.stderr;
2035
+ const start = Date.now();
2036
+ let ticks = 0;
2037
+ const id = setInterval(() => {
2038
+ ticks++;
2039
+ const elapsed = Math.floor((Date.now() - start) / 1e3);
2040
+ stream.write(`\r${formatTickerLine(label, elapsed, ticks)}`);
2041
+ }, intervalMs);
2042
+ return function stop() {
2043
+ clearInterval(id);
2044
+ if (ticks > 0) stream.write("\r\x1B[K");
2045
+ };
2046
+ }
2047
+
2048
+ // src/data/summaryRunner.ts
1497
2049
  function agenthudHomeDir() {
1498
- const dir = join4(homedir3(), ".agenthud");
1499
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
2050
+ const dir = join6(homedir3(), ".agenthud");
2051
+ if (!existsSync6(dir)) mkdirSync2(dir, { recursive: true });
1500
2052
  return dir;
1501
2053
  }
1502
2054
  function summariesDir() {
1503
- const dir = join4(agenthudHomeDir(), "summaries");
1504
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
2055
+ const dir = join6(agenthudHomeDir(), "summaries");
2056
+ if (!existsSync6(dir)) mkdirSync2(dir, { recursive: true });
1505
2057
  return dir;
1506
2058
  }
1507
2059
  function promptFilename(kind) {
1508
2060
  return kind === "daily" ? "summary-prompt.md" : "summary-range-prompt.md";
1509
2061
  }
1510
2062
  function userPromptPath(kind) {
1511
- return join4(homedir3(), ".agenthud", promptFilename(kind));
2063
+ return join6(homedir3(), ".agenthud", promptFilename(kind));
2064
+ }
2065
+ function formatPromptSource(kind, override) {
2066
+ if (kind === "daily" && override) {
2067
+ return "<inline> (from --prompt)";
2068
+ }
2069
+ const home = homedir3();
2070
+ const path = userPromptPath(kind);
2071
+ const abbreviated = path.startsWith(home) ? `~${path.slice(home.length)}` : path;
2072
+ return abbreviated.replace(/\\/g, "/");
1512
2073
  }
1513
2074
  function templatePath(kind) {
1514
2075
  const here = dirname2(fileURLToPath2(import.meta.url));
1515
- return join4(here, "templates", promptFilename(kind));
2076
+ return join6(here, "templates", promptFilename(kind));
1516
2077
  }
1517
2078
  function dateKey(d) {
1518
2079
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1519
2080
  }
1520
2081
  function dailyCachePath(date) {
1521
- return join4(summariesDir(), `${dateKey(date)}.md`);
2082
+ return join6(summariesDir(), `${dateKey(date)}.md`);
1522
2083
  }
1523
2084
  function rangeCachePath(from, to) {
1524
- return join4(summariesDir(), `range-${dateKey(from)}_${dateKey(to)}.md`);
2085
+ return join6(summariesDir(), `range-${dateKey(from)}_${dateKey(to)}.md`);
1525
2086
  }
1526
2087
  function isSameLocalDay2(a, b) {
1527
2088
  return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1528
2089
  }
1529
2090
  function ensureUserPromptFile(kind) {
1530
2091
  const p = userPromptPath(kind);
1531
- if (existsSync4(p)) return;
2092
+ if (existsSync6(p)) return;
1532
2093
  const dir = dirname2(p);
1533
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
2094
+ if (!existsSync6(dir)) mkdirSync2(dir, { recursive: true });
1534
2095
  try {
1535
2096
  copyFileSync(templatePath(kind), p);
1536
2097
  } catch {
@@ -1539,14 +2100,14 @@ function ensureUserPromptFile(kind) {
1539
2100
  function resolvePrompt(kind, override) {
1540
2101
  if (override) return override;
1541
2102
  const p = userPromptPath(kind);
1542
- if (existsSync4(p)) {
2103
+ if (existsSync6(p)) {
1543
2104
  try {
1544
- return readFileSync5(p, "utf-8");
2105
+ return readFileSync7(p, "utf-8");
1545
2106
  } catch {
1546
2107
  }
1547
2108
  }
1548
2109
  try {
1549
- return readFileSync5(templatePath(kind), "utf-8");
2110
+ return readFileSync7(templatePath(kind), "utf-8");
1550
2111
  } catch {
1551
2112
  return "Summarize the input below.";
1552
2113
  }
@@ -1601,7 +2162,7 @@ function spawnClaude(opts) {
1601
2162
  ];
1602
2163
  if (opts.model) args.push("--model", opts.model);
1603
2164
  args.push(opts.prompt);
1604
- const proc = spawn(
2165
+ const proc = spawn2(
1605
2166
  "claude",
1606
2167
  args,
1607
2168
  {
@@ -1722,11 +2283,11 @@ async function generateDailySummary(opts) {
1722
2283
  const isToday = isSameLocalDay2(opts.date, opts.today);
1723
2284
  const cached = dailyCachePath(opts.date);
1724
2285
  const dateLabel = dateKey(opts.date);
1725
- if (!isToday && !opts.force && existsSync4(cached)) {
2286
+ if (!isToday && !opts.force && existsSync6(cached)) {
1726
2287
  try {
1727
- const content = readFileSync5(cached, "utf-8");
2288
+ const content = readFileSync7(cached, "utf-8");
1728
2289
  if (opts.announce) {
1729
- process.stderr.write(`agenthud: cached summary from ${cached}
2290
+ process.stderr.write(`cached summary from ${cached}
1730
2291
  `);
1731
2292
  }
1732
2293
  if (opts.streamToStdout) {
@@ -1744,7 +2305,7 @@ async function generateDailySummary(opts) {
1744
2305
  }
1745
2306
  }
1746
2307
  if (opts.announce) {
1747
- process.stderr.write(`agenthud: scanning sessions for ${dateLabel}...
2308
+ process.stderr.write(`scanning sessions for ${dateLabel}...
1748
2309
  `);
1749
2310
  }
1750
2311
  const config = loadGlobalConfig();
@@ -1755,28 +2316,58 @@ async function generateDailySummary(opts) {
1755
2316
  ];
1756
2317
  const reportMarkdown = generateReport(flatSessions, {
1757
2318
  date: opts.date,
1758
- include: ["response", "bash", "edit", "thinking"],
2319
+ include: opts.include,
2320
+ // The LLM only ingests markdown — the format option on summary is
2321
+ // for the report-extraction *export* surface (post-LLM), not for
2322
+ // the payload itself, so this stays fixed.
1759
2323
  format: "markdown",
1760
- detailLimit: 0,
1761
- withGit: true
2324
+ detailLimit: opts.detailLimit,
2325
+ withGit: opts.withGit
1762
2326
  });
1763
2327
  const reportBytes = Buffer.byteLength(reportMarkdown, "utf-8");
1764
2328
  const estimatedTokens = Math.ceil(reportBytes / 4);
2329
+ const reportLines = reportMarkdown.split("\n");
2330
+ const sessionCount = reportLines.filter((l) => l.startsWith("## ")).length;
2331
+ const activityCount = reportLines.filter(
2332
+ (l) => /^\[\d{2}:\d{2}\]/.test(l)
2333
+ ).length;
2334
+ const commitCount = reportLines.filter(
2335
+ (l) => /^\[\d{2}:\d{2}\] ◆/.test(l)
2336
+ ).length;
1765
2337
  if (opts.announce) {
1766
- const reportLines = reportMarkdown.split("\n");
1767
- const sessionCount = reportLines.filter((l) => l.startsWith("## ")).length;
1768
- const activityCount = reportLines.filter(
1769
- (l) => /^\[\d{2}:\d{2}\]/.test(l)
1770
- ).length;
1771
- const commitCount = reportLines.filter(
1772
- (l) => /^\[\d{2}:\d{2}\] ◆/.test(l)
1773
- ).length;
1774
2338
  const sizeKb = (reportBytes / 1024).toFixed(1);
1775
2339
  process.stderr.write(
1776
- `agenthud: input: ${sessionCount} sessions, ${activityCount} activities, ${commitCount} commits (${reportLines.length} lines, ${sizeKb}KB \u2248 ${estimatedTokens.toLocaleString()} tokens)
2340
+ `input: ${sessionCount} sessions, ${activityCount} activities, ${commitCount} commits (${reportLines.length} lines, ${sizeKb}KB \u2248 ${estimatedTokens.toLocaleString()} tokens)
1777
2341
  `
1778
2342
  );
1779
2343
  }
2344
+ if (sessionCount === 0 && activityCount === 0 && commitCount === 0) {
2345
+ const stub = `## Context
2346
+
2347
+ No activity recorded for ${dateLabel}.
2348
+ No claude call was issued \u2014 the report was empty.
2349
+ `;
2350
+ try {
2351
+ writeFileSync3(cached, stub, "utf-8");
2352
+ } catch {
2353
+ }
2354
+ if (opts.announce) {
2355
+ process.stderr.write(
2356
+ `${dateLabel} has no activity \u2014 wrote stub to ${cached}, skipped claude
2357
+ `
2358
+ );
2359
+ }
2360
+ if (opts.streamToStdout) {
2361
+ process.stdout.write(stub);
2362
+ }
2363
+ return {
2364
+ code: 0,
2365
+ markdown: stub,
2366
+ fromCache: false,
2367
+ skipped: false,
2368
+ usage: null
2369
+ };
2370
+ }
1780
2371
  if (opts.confirmBeforeSpawn) {
1781
2372
  const proceed = await opts.confirmBeforeSpawn();
1782
2373
  if (!proceed) {
@@ -1812,13 +2403,15 @@ async function generateDailySummary(opts) {
1812
2403
  }
1813
2404
  }
1814
2405
  }
1815
- if (opts.announce) {
2406
+ const showTicker = opts.announce && !opts.streamToStdout;
2407
+ if (opts.announce && !showTicker) {
1816
2408
  process.stderr.write(
1817
- `agenthud: sending to claude (this may take a minute)...
2409
+ `sending to claude (this may take a minute)...
1818
2410
 
1819
2411
  `
1820
2412
  );
1821
2413
  }
2414
+ const stopTicker = showTicker ? startStderrTicker("sending to claude") : null;
1822
2415
  const prompt = resolvePrompt("daily", opts.promptOverride);
1823
2416
  const result = await spawnClaude({
1824
2417
  prompt,
@@ -1827,12 +2420,13 @@ async function generateDailySummary(opts) {
1827
2420
  streamToStdout: opts.streamToStdout,
1828
2421
  model: opts.model
1829
2422
  });
2423
+ if (stopTicker) stopTicker();
1830
2424
  if (opts.announce && result.code === 0) {
1831
2425
  process.stderr.write("\n");
1832
- process.stderr.write(`agenthud: saved to ${cached}
2426
+ process.stderr.write(`saved to ${cached}
1833
2427
  `);
1834
2428
  if (result.usage) {
1835
- process.stderr.write(`agenthud: ${formatUsage(result.usage)}
2429
+ process.stderr.write(`${formatUsage(result.usage)}
1836
2430
  `);
1837
2431
  }
1838
2432
  }
@@ -1850,10 +2444,25 @@ async function runSummary(options2) {
1850
2444
  today: options2.today,
1851
2445
  force: options2.force,
1852
2446
  promptOverride: options2.prompt,
1853
- streamToStdout: true,
2447
+ streamToStdout: !options2.open,
1854
2448
  announce: true,
1855
- model: options2.model
2449
+ model: options2.model,
2450
+ include: options2.include,
2451
+ detailLimit: options2.detailLimit,
2452
+ withGit: options2.withGit
1856
2453
  });
2454
+ if (res.code === 0 && !res.skipped) {
2455
+ try {
2456
+ regenerateIndex(summariesDir());
2457
+ } catch {
2458
+ }
2459
+ }
2460
+ if (options2.open && res.code === 0 && !res.skipped) {
2461
+ await openInDefaultApp(dailyCachePath(options2.date));
2462
+ }
2463
+ if (options2.openIndex && res.code === 0 && !res.skipped) {
2464
+ await openInDefaultApp(join6(summariesDir(), "index.md"));
2465
+ }
1857
2466
  return res.code;
1858
2467
  }
1859
2468
  async function runRangeSummary(options2) {
@@ -1867,16 +2476,26 @@ async function runRangeSummary(options2) {
1867
2476
  options2.force,
1868
2477
  dates,
1869
2478
  options2.today,
1870
- existsSync4(rangeCache)
2479
+ existsSync6(rangeCache)
1871
2480
  )) {
1872
2481
  try {
1873
- const content = readFileSync5(rangeCache, "utf-8");
2482
+ const content = readFileSync7(rangeCache, "utf-8");
1874
2483
  process.stderr.write(
1875
- `agenthud: cached range summary from ${rangeCache}
2484
+ `cached range summary from ${rangeCache}
1876
2485
  `
1877
2486
  );
1878
- process.stdout.write(content);
1879
- if (!content.endsWith("\n")) process.stdout.write("\n");
2487
+ if (!options2.open) {
2488
+ process.stdout.write(content);
2489
+ if (!content.endsWith("\n")) process.stdout.write("\n");
2490
+ }
2491
+ try {
2492
+ regenerateIndex(summariesDir());
2493
+ } catch {
2494
+ }
2495
+ if (options2.open) await openInDefaultApp(rangeCache);
2496
+ if (options2.openIndex) {
2497
+ await openInDefaultApp(join6(summariesDir(), "index.md"));
2498
+ }
1880
2499
  return 0;
1881
2500
  } catch {
1882
2501
  }
@@ -1885,15 +2504,15 @@ async function runRangeSummary(options2) {
1885
2504
  let missingCount = 0;
1886
2505
  for (const d of dates) {
1887
2506
  const isToday = isSameLocalDay2(d, options2.today);
1888
- if (!isToday && existsSync4(dailyCachePath(d))) cachedCount++;
2507
+ if (!isToday && existsSync6(dailyCachePath(d))) cachedCount++;
1889
2508
  else missingCount++;
1890
2509
  }
1891
2510
  process.stderr.write(
1892
- `agenthud: range ${fromLabel} \u2192 ${toLabel} (${dates.length} days)
2511
+ `range ${fromLabel} \u2192 ${toLabel} (${dates.length} days)
1893
2512
  `
1894
2513
  );
1895
2514
  process.stderr.write(
1896
- `agenthud: ${cachedCount} cached, ${missingCount} to generate
2515
+ `${cachedCount} cached, ${missingCount} to generate
1897
2516
  `
1898
2517
  );
1899
2518
  const dailyMarkdowns = [];
@@ -1902,9 +2521,9 @@ async function runRangeSummary(options2) {
1902
2521
  const label = dateKey(d);
1903
2522
  const isToday = isSameLocalDay2(d, options2.today);
1904
2523
  process.stderr.write(`
1905
- agenthud: --- ${label} ---
2524
+ --- ${label} ---
1906
2525
  `);
1907
- const willPrompt = !options2.assumeYes && (isToday || !existsSync4(dailyCachePath(d)));
2526
+ const willPrompt = !options2.assumeYes && (isToday || !existsSync6(dailyCachePath(d)));
1908
2527
  const confirmer = willPrompt ? async () => {
1909
2528
  const hint = isToday ? " (today \u2014 regenerated every time)" : "";
1910
2529
  return ask(`Generate this summary${hint}? [Y/n] `, true);
@@ -1917,7 +2536,10 @@ agenthud: --- ${label} ---
1917
2536
  announce: true,
1918
2537
  confirmBeforeSpawn: confirmer,
1919
2538
  assumeYes: options2.assumeYes,
1920
- model: options2.model
2539
+ model: options2.model,
2540
+ include: options2.include,
2541
+ detailLimit: options2.detailLimit,
2542
+ withGit: options2.withGit
1921
2543
  });
1922
2544
  if (res.skipped) {
1923
2545
  process.stderr.write(`agenthud: ${label} \u2014 skipped by user.
@@ -1934,7 +2556,7 @@ agenthud: --- ${label} ---
1934
2556
  }
1935
2557
  const text = res.markdown.trim();
1936
2558
  if (text.length === 0 || /^no activity found/i.test(text)) {
1937
- process.stderr.write(`agenthud: ${label} has no activity \u2014 skipping.
2559
+ process.stderr.write(`${label} has no activity \u2014 skipping.
1938
2560
  `);
1939
2561
  continue;
1940
2562
  }
@@ -1949,37 +2571,51 @@ agenthud: --- ${label} ---
1949
2571
  ${markdown}`).join("\n\n---\n\n");
1950
2572
  process.stderr.write(
1951
2573
  `
1952
- agenthud: combining ${dailyMarkdowns.length} daily summaries into range summary...
2574
+ combining ${dailyMarkdowns.length} daily summaries into range summary...
1953
2575
  `
1954
2576
  );
1955
- process.stderr.write(
1956
- `agenthud: sending to claude (this may take a minute)...
2577
+ const metaStreams = !options2.open;
2578
+ if (!metaStreams) {
2579
+ } else {
2580
+ process.stderr.write(
2581
+ `sending to claude (this may take a minute)...
1957
2582
 
1958
2583
  `
1959
- );
2584
+ );
2585
+ }
2586
+ const stopMetaTicker = metaStreams ? null : startStderrTicker("sending to claude");
1960
2587
  const metaPrompt = resolvePrompt("range");
1961
2588
  const metaResult = await spawnClaude({
1962
2589
  prompt: metaPrompt,
1963
2590
  stdin: metaInput,
1964
2591
  cachePath: rangeCache,
1965
- streamToStdout: true,
2592
+ streamToStdout: metaStreams,
1966
2593
  model: options2.model
1967
2594
  });
2595
+ if (stopMetaTicker) stopMetaTicker();
1968
2596
  if (metaResult.code !== 0) {
1969
2597
  return metaResult.code;
1970
2598
  }
1971
2599
  process.stderr.write("\n");
1972
- process.stderr.write(`agenthud: saved to ${rangeCache}
2600
+ process.stderr.write(`saved to ${rangeCache}
1973
2601
  `);
1974
2602
  if (metaResult.usage) {
1975
- process.stderr.write(`agenthud: ${formatUsage(metaResult.usage)}
2603
+ process.stderr.write(`${formatUsage(metaResult.usage)}
1976
2604
  `);
1977
2605
  }
2606
+ try {
2607
+ regenerateIndex(summariesDir());
2608
+ } catch {
2609
+ }
2610
+ if (options2.open) await openInDefaultApp(rangeCache);
2611
+ if (options2.openIndex) {
2612
+ await openInDefaultApp(join6(summariesDir(), "index.md"));
2613
+ }
1978
2614
  return 0;
1979
2615
  }
1980
2616
 
1981
2617
  // src/ui/App.tsx
1982
- import { existsSync as existsSync5, watch } from "fs";
2618
+ import { existsSync as existsSync7, watch } from "fs";
1983
2619
  import { basename as basename3 } from "path";
1984
2620
  import { Box as Box5, Text as Text5, useApp, useInput, useStdout } from "ink";
1985
2621
  import { useCallback, useEffect as useEffect3, useMemo, useRef, useState as useState3 } from "react";
@@ -2416,6 +3052,7 @@ var SECTIONS = [
2416
3052
  title: "Project tree",
2417
3053
  rows: [
2418
3054
  ["\u2191 \u2193 / k j", "Move selection"],
3055
+ ["\u2190", "Jump to parent (sub-agent \u2192 session, session \u2192 project)"],
2419
3056
  ["PgUp / Ctrl+B", "Page up"],
2420
3057
  ["PgDn / Ctrl+F", "Page down"],
2421
3058
  ["\u21B5", "Expand/collapse project, session, or summary"],
@@ -2565,6 +3202,7 @@ function useHotkeys({
2565
3202
  onHelpScroll,
2566
3203
  onHelpScrollToTop,
2567
3204
  onToggleTracking,
3205
+ onJumpToParent,
2568
3206
  filterLabel,
2569
3207
  trackingOn = false
2570
3208
  }) {
@@ -2684,6 +3322,10 @@ function useHotkeys({
2684
3322
  onHide();
2685
3323
  return;
2686
3324
  }
3325
+ if (key.leftArrow && onJumpToParent) {
3326
+ onJumpToParent();
3327
+ return;
3328
+ }
2687
3329
  if (key.upArrow || input === "k") {
2688
3330
  onScrollUp();
2689
3331
  return;
@@ -2717,6 +3359,7 @@ function useHotkeys({
2717
3359
  ...trackingItems,
2718
3360
  "Tab: viewer",
2719
3361
  "\u2191\u2193/jk: select",
3362
+ "\u2190: parent",
2720
3363
  "PgUp/Dn: page",
2721
3364
  "\u21B5: expand",
2722
3365
  "h: hide",
@@ -3194,7 +3837,8 @@ function appendSubAgentRows(result, session, expandedIds) {
3194
3837
  const sessionExpandedKey = `__expanded-session-${session.id}`;
3195
3838
  const sessionHidden = isCold ? !expandedIds.has(sessionExpandedKey) : expandedIds.has(sessionCollapsedKey);
3196
3839
  if (sessionHidden) return;
3197
- if (expandedIds.has(session.id)) {
3840
+ const subAgentsFullyExpanded = expandedIds.has(session.id) || isCold && expandedIds.has(sessionExpandedKey);
3841
+ if (subAgentsFullyExpanded) {
3198
3842
  result.push(...session.subAgents);
3199
3843
  } else {
3200
3844
  result.push(
@@ -3322,6 +3966,26 @@ function collectAllIds(tree) {
3322
3966
  }
3323
3967
  return ids;
3324
3968
  }
3969
+ function findParentTarget(currentId, tree, flat) {
3970
+ if (currentId.startsWith("__sub-") && currentId.endsWith("__")) {
3971
+ return currentId.slice("__sub-".length, -"__".length);
3972
+ }
3973
+ const allProjects = [...tree.projects, ...tree.coldProjects];
3974
+ const allSessions = allProjects.flatMap((p) => p.sessions);
3975
+ for (const session of allSessions) {
3976
+ if (session.subAgents.some((sa) => sa.id === currentId)) {
3977
+ return session.id;
3978
+ }
3979
+ }
3980
+ for (const project of allProjects) {
3981
+ if (project.sessions.some((s) => s.id === currentId)) {
3982
+ return `__proj-${project.name}__`;
3983
+ }
3984
+ }
3985
+ const idx = flat.findIndex((row) => row.id === currentId);
3986
+ if (idx <= 0) return currentId;
3987
+ return flat[idx - 1]?.id ?? currentId;
3988
+ }
3325
3989
  function initialSelectedId(tree) {
3326
3990
  const firstProject = tree.projects[0];
3327
3991
  if (firstProject) return `__proj-${firstProject.name}__`;
@@ -3496,7 +4160,7 @@ function App({
3496
4160
  useEffect3(() => {
3497
4161
  if (!isWatchMode) return;
3498
4162
  const projectsDir = getProjectsDir();
3499
- const usePolling = process.platform === "linux" || !existsSync5(projectsDir);
4163
+ const usePolling = process.platform === "linux" || !existsSync7(projectsDir);
3500
4164
  if (usePolling) {
3501
4165
  const timer = setInterval(
3502
4166
  () => refreshRef.current(),
@@ -3590,6 +4254,13 @@ function App({
3590
4254
  });
3591
4255
  },
3592
4256
  trackingOn: tracking,
4257
+ onJumpToParent: () => {
4258
+ if (focus !== "tree") return;
4259
+ stopTracking();
4260
+ if (!selectedId) return;
4261
+ const target = findParentTarget(selectedId, sessionTree, allFlat);
4262
+ if (target && target !== selectedId) setSelectedId(target);
4263
+ },
3593
4264
  onSwitchFocus: () => setFocus((f) => f === "tree" ? "viewer" : "tree"),
3594
4265
  // cursorLine = "entries back from the newest" (0 = newest = bottom row).
3595
4266
  // Up arrow moves visually upward = older direction = cursorLine++.
@@ -4031,15 +4702,16 @@ function installAltScreenCleanup() {
4031
4702
  }
4032
4703
 
4033
4704
  // src/utils/legacyConfig.ts
4034
- import { join as join5, resolve } from "path";
4705
+ import { join as join7, resolve } from "path";
4035
4706
  function isLegacyProjectConfig(cwd, home) {
4036
- const legacy = resolve(join5(cwd, ".agenthud", "config.yaml"));
4037
- const global = resolve(join5(home, ".agenthud", "config.yaml"));
4707
+ const legacy = resolve(join7(cwd, ".agenthud", "config.yaml"));
4708
+ const global = resolve(join7(home, ".agenthud", "config.yaml"));
4038
4709
  return legacy !== global;
4039
4710
  }
4040
4711
 
4041
4712
  // src/main.ts
4042
- var options = parseArgs(process.argv.slice(2));
4713
+ var globalConfig = loadGlobalConfig();
4714
+ var options = parseArgs(process.argv.slice(2), globalConfig);
4043
4715
  if (options.error) {
4044
4716
  process.stderr.write(`agenthud: ${options.error}
4045
4717
  `);
@@ -4053,8 +4725,8 @@ if (options.command === "version") {
4053
4725
  console.log(getVersion());
4054
4726
  process.exit(0);
4055
4727
  }
4056
- var legacyConfig = join6(process.cwd(), ".agenthud", "config.yaml");
4057
- if (isLegacyProjectConfig(process.cwd(), homedir5()) && existsSync6(legacyConfig)) {
4728
+ var legacyConfig = join8(process.cwd(), ".agenthud", "config.yaml");
4729
+ if (isLegacyProjectConfig(process.cwd(), homedir5()) && existsSync8(legacyConfig)) {
4058
4730
  console.log(
4059
4731
  "The project-level config file (.agenthud/config.yaml) is no longer supported."
4060
4732
  );
@@ -4080,8 +4752,16 @@ if (options.mode === "report") {
4080
4752
  `);
4081
4753
  process.exit(1);
4082
4754
  }
4083
- const config = loadGlobalConfig();
4084
- const tree = discoverSessions(config);
4755
+ process.stderr.write(
4756
+ `${formatEffectiveOptionsLine("report", {
4757
+ include: options.reportInclude,
4758
+ detailLimit: options.reportDetailLimit,
4759
+ withGit: options.reportWithGit ?? false,
4760
+ format: options.reportFormat
4761
+ })}
4762
+ `
4763
+ );
4764
+ const tree = discoverSessions(globalConfig);
4085
4765
  const flatSessions = [
4086
4766
  ...tree.projects.flatMap((p) => p.sessions),
4087
4767
  ...tree.coldProjects.flatMap((p) => p.sessions)
@@ -4103,6 +4783,23 @@ if (options.mode === "summary") {
4103
4783
  `);
4104
4784
  process.exit(1);
4105
4785
  }
4786
+ process.stderr.write(
4787
+ `${formatEffectiveOptionsLine("summary", {
4788
+ include: options.summaryInclude,
4789
+ detailLimit: options.summaryDetailLimit,
4790
+ withGit: options.summaryWithGit ?? false,
4791
+ model: options.summaryModel
4792
+ })}
4793
+ `
4794
+ );
4795
+ const isRangeMode = !!(options.summaryFrom && options.summaryTo);
4796
+ process.stderr.write(
4797
+ `prompt = ${formatPromptSource(
4798
+ isRangeMode ? "range" : "daily",
4799
+ options.summaryPrompt
4800
+ )}
4801
+ `
4802
+ );
4106
4803
  const today = /* @__PURE__ */ new Date();
4107
4804
  if (options.summaryFrom && options.summaryTo) {
4108
4805
  const exitCode2 = await runRangeSummary({
@@ -4111,7 +4808,12 @@ if (options.mode === "summary") {
4111
4808
  today,
4112
4809
  force: options.summaryForce ?? false,
4113
4810
  assumeYes: options.summaryAssumeYes ?? false,
4114
- model: options.summaryModel
4811
+ model: options.summaryModel,
4812
+ include: options.summaryInclude,
4813
+ detailLimit: options.summaryDetailLimit,
4814
+ withGit: options.summaryWithGit,
4815
+ open: options.summaryOpen,
4816
+ openIndex: options.summaryOpenIndex
4115
4817
  });
4116
4818
  process.exit(exitCode2);
4117
4819
  }
@@ -4120,7 +4822,12 @@ if (options.mode === "summary") {
4120
4822
  prompt: options.summaryPrompt,
4121
4823
  force: options.summaryForce ?? false,
4122
4824
  today,
4123
- model: options.summaryModel
4825
+ model: options.summaryModel,
4826
+ include: options.summaryInclude,
4827
+ detailLimit: options.summaryDetailLimit,
4828
+ withGit: options.summaryWithGit,
4829
+ open: options.summaryOpen,
4830
+ openIndex: options.summaryOpenIndex
4124
4831
  });
4125
4832
  process.exit(exitCode);
4126
4833
  }
@@ -4129,7 +4836,7 @@ if (options.scopeToCwd) {
4129
4836
  const projectsDir = getProjectsDir();
4130
4837
  let registered = [];
4131
4838
  try {
4132
- registered = readdirSync2(projectsDir).map(decodeProjectPath);
4839
+ registered = readdirSync3(projectsDir).map(decodeProjectPath);
4133
4840
  } catch {
4134
4841
  }
4135
4842
  const safeReal = (p) => {
@@ -4150,7 +4857,7 @@ if (options.scopeToCwd) {
4150
4857
  process.exit(1);
4151
4858
  }
4152
4859
  scopeToProject = match;
4153
- process.stderr.write(`agenthud: scope = ${match}
4860
+ process.stderr.write(`scope = ${match}
4154
4861
  `);
4155
4862
  }
4156
4863
  if (options.mode === "watch") {