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.
- package/README.md +60 -16
- package/dist/index.js +1 -1
- package/dist/{main-WAT4DY4T.js → main-YCUCY2QP.js} +1023 -316
- package/package.json +1 -1
|
@@ -1,15 +1,306 @@
|
|
|
1
1
|
// src/main.ts
|
|
2
|
-
import { existsSync as
|
|
2
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, realpathSync, rmSync } from "fs";
|
|
3
3
|
import { homedir as homedir5 } from "os";
|
|
4
|
-
import { join as
|
|
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
|
-
[--
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
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
|
|
1670
|
+
existsSync as existsSync6,
|
|
1489
1671
|
mkdirSync as mkdirSync2,
|
|
1490
|
-
readFileSync as
|
|
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
|
|
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 =
|
|
1499
|
-
if (!
|
|
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 =
|
|
1504
|
-
if (!
|
|
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
|
|
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
|
|
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
|
|
2082
|
+
return join6(summariesDir(), `${dateKey(date)}.md`);
|
|
1522
2083
|
}
|
|
1523
2084
|
function rangeCachePath(from, to) {
|
|
1524
|
-
return
|
|
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 (
|
|
2092
|
+
if (existsSync6(p)) return;
|
|
1532
2093
|
const dir = dirname2(p);
|
|
1533
|
-
if (!
|
|
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 (
|
|
2103
|
+
if (existsSync6(p)) {
|
|
1543
2104
|
try {
|
|
1544
|
-
return
|
|
2105
|
+
return readFileSync7(p, "utf-8");
|
|
1545
2106
|
} catch {
|
|
1546
2107
|
}
|
|
1547
2108
|
}
|
|
1548
2109
|
try {
|
|
1549
|
-
return
|
|
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 =
|
|
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 &&
|
|
2286
|
+
if (!isToday && !opts.force && existsSync6(cached)) {
|
|
1726
2287
|
try {
|
|
1727
|
-
const content =
|
|
2288
|
+
const content = readFileSync7(cached, "utf-8");
|
|
1728
2289
|
if (opts.announce) {
|
|
1729
|
-
process.stderr.write(`
|
|
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(`
|
|
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:
|
|
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:
|
|
1761
|
-
withGit:
|
|
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
|
-
`
|
|
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
|
-
|
|
2406
|
+
const showTicker = opts.announce && !opts.streamToStdout;
|
|
2407
|
+
if (opts.announce && !showTicker) {
|
|
1816
2408
|
process.stderr.write(
|
|
1817
|
-
`
|
|
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(`
|
|
2426
|
+
process.stderr.write(`saved to ${cached}
|
|
1833
2427
|
`);
|
|
1834
2428
|
if (result.usage) {
|
|
1835
|
-
process.stderr.write(
|
|
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:
|
|
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
|
-
|
|
2479
|
+
existsSync6(rangeCache)
|
|
1871
2480
|
)) {
|
|
1872
2481
|
try {
|
|
1873
|
-
const content =
|
|
2482
|
+
const content = readFileSync7(rangeCache, "utf-8");
|
|
1874
2483
|
process.stderr.write(
|
|
1875
|
-
`
|
|
2484
|
+
`cached range summary from ${rangeCache}
|
|
1876
2485
|
`
|
|
1877
2486
|
);
|
|
1878
|
-
|
|
1879
|
-
|
|
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 &&
|
|
2507
|
+
if (!isToday && existsSync6(dailyCachePath(d))) cachedCount++;
|
|
1889
2508
|
else missingCount++;
|
|
1890
2509
|
}
|
|
1891
2510
|
process.stderr.write(
|
|
1892
|
-
`
|
|
2511
|
+
`range ${fromLabel} \u2192 ${toLabel} (${dates.length} days)
|
|
1893
2512
|
`
|
|
1894
2513
|
);
|
|
1895
2514
|
process.stderr.write(
|
|
1896
|
-
|
|
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
|
-
|
|
2524
|
+
--- ${label} ---
|
|
1906
2525
|
`);
|
|
1907
|
-
const willPrompt = !options2.assumeYes && (isToday || !
|
|
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(
|
|
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
|
-
|
|
2574
|
+
combining ${dailyMarkdowns.length} daily summaries into range summary...
|
|
1953
2575
|
`
|
|
1954
2576
|
);
|
|
1955
|
-
|
|
1956
|
-
|
|
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:
|
|
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(`
|
|
2600
|
+
process.stderr.write(`saved to ${rangeCache}
|
|
1973
2601
|
`);
|
|
1974
2602
|
if (metaResult.usage) {
|
|
1975
|
-
process.stderr.write(
|
|
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
|
|
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
|
-
|
|
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" || !
|
|
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
|
|
4705
|
+
import { join as join7, resolve } from "path";
|
|
4035
4706
|
function isLegacyProjectConfig(cwd, home) {
|
|
4036
|
-
const legacy = resolve(
|
|
4037
|
-
const global = resolve(
|
|
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
|
|
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 =
|
|
4057
|
-
if (isLegacyProjectConfig(process.cwd(), homedir5()) &&
|
|
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
|
-
|
|
4084
|
-
|
|
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 =
|
|
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(`
|
|
4860
|
+
process.stderr.write(`scope = ${match}
|
|
4154
4861
|
`);
|
|
4155
4862
|
}
|
|
4156
4863
|
if (options.mode === "watch") {
|