agenthud 0.9.1 → 0.9.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 +37 -14
- package/dist/index.js +1 -1
- package/dist/{main-NHQJ23YJ.js → main-6SGKXL7E.js} +674 -157
- package/dist/templates/summary-prompt.md +32 -7
- package/dist/templates/summary-range-prompt.md +35 -0
- package/package.json +1 -1
- package/dist/templates/config.yaml +0 -35
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
// src/main.ts
|
|
2
2
|
import { existsSync as existsSync6, rmSync } from "fs";
|
|
3
|
+
import { homedir as homedir5 } from "os";
|
|
3
4
|
import { join as join6 } from "path";
|
|
4
|
-
import { createInterface } from "readline";
|
|
5
|
+
import { createInterface as createInterface2 } from "readline";
|
|
6
|
+
|
|
7
|
+
// src/utils/legacyConfig.ts
|
|
8
|
+
import { join, resolve } from "path";
|
|
9
|
+
function isLegacyProjectConfig(cwd, home) {
|
|
10
|
+
const legacy = resolve(join(cwd, ".agenthud", "config.yaml"));
|
|
11
|
+
const global = resolve(join(home, ".agenthud", "config.yaml"));
|
|
12
|
+
return legacy !== global;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/main.ts
|
|
5
16
|
import { render } from "ink";
|
|
6
17
|
import React from "react";
|
|
7
18
|
|
|
8
19
|
// src/cli.ts
|
|
9
20
|
import { readFileSync } from "fs";
|
|
10
|
-
import { dirname, join } from "path";
|
|
21
|
+
import { dirname, join as join2 } from "path";
|
|
11
22
|
import { fileURLToPath } from "url";
|
|
12
23
|
var ALL_TYPES = [
|
|
13
24
|
"response",
|
|
@@ -35,7 +46,16 @@ var KNOWN_REPORT_FLAGS = /* @__PURE__ */ new Set([
|
|
|
35
46
|
"--detail-limit",
|
|
36
47
|
"--with-git"
|
|
37
48
|
]);
|
|
38
|
-
var KNOWN_SUMMARY_FLAGS = /* @__PURE__ */ new Set([
|
|
49
|
+
var KNOWN_SUMMARY_FLAGS = /* @__PURE__ */ new Set([
|
|
50
|
+
"--date",
|
|
51
|
+
"--last",
|
|
52
|
+
"--from",
|
|
53
|
+
"--to",
|
|
54
|
+
"--prompt",
|
|
55
|
+
"--force",
|
|
56
|
+
"-y",
|
|
57
|
+
"--yes"
|
|
58
|
+
]);
|
|
39
59
|
var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["report", "summary"]);
|
|
40
60
|
function getHelp() {
|
|
41
61
|
return `Usage: agenthud [options]
|
|
@@ -51,32 +71,37 @@ Options:
|
|
|
51
71
|
Commands:
|
|
52
72
|
report [--date DATE] [--include TYPES] [--format FORMAT] [--detail-limit N] [--with-git]
|
|
53
73
|
Print activity report for a date (default: today)
|
|
54
|
-
--date YYYY-MM-DD|today Date to report on
|
|
74
|
+
--date YYYY-MM-DD|today|yesterday|-Nd Date to report on
|
|
55
75
|
--include TYPES Comma-separated types or "all"
|
|
56
76
|
Types: response,bash,edit,thinking,read,glob,user
|
|
57
77
|
Default: response,bash,edit,thinking
|
|
58
78
|
--format FORMAT Output format: markdown (default) or json
|
|
59
79
|
--detail-limit N Max chars per activity detail (default: 120, 0 = unlimited)
|
|
60
|
-
--with-git
|
|
80
|
+
--with-git Merge git commits from each session's project into the timeline
|
|
61
81
|
|
|
62
|
-
summary [--date DATE] [--prompt TEXT] [--force]
|
|
63
|
-
Generate LLM summary
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
--
|
|
82
|
+
summary [--date DATE | --last Nd | --from DATE --to DATE] [--prompt TEXT] [--force] [-y]
|
|
83
|
+
Generate LLM summary via claude CLI.
|
|
84
|
+
Single day produces a daily summary;
|
|
85
|
+
a date range produces a meta-summary built from daily summaries.
|
|
86
|
+
--date YYYY-MM-DD|today|yesterday|-Nd Date to summarize (default: today)
|
|
87
|
+
--last Nd Date range: last N days, ending today (e.g. --last 7d)
|
|
88
|
+
--from YYYY-MM-DD Date range: start date (use with --to)
|
|
89
|
+
--to YYYY-MM-DD Date range: end date (use with --from)
|
|
90
|
+
--prompt TEXT Override prompt for this run (daily only)
|
|
91
|
+
--force Regenerate even if cached
|
|
92
|
+
-y, --yes Skip confirmation prompts for new daily summaries
|
|
67
93
|
|
|
68
94
|
Environment:
|
|
69
95
|
CLAUDE_PROJECTS_DIR Path to Claude projects directory
|
|
70
96
|
(default: ~/.claude/projects)
|
|
71
97
|
|
|
72
98
|
Config: ~/.agenthud/config.yaml
|
|
73
|
-
Logs: ~/.agenthud/logs/
|
|
74
99
|
`;
|
|
75
100
|
}
|
|
76
101
|
function getVersion() {
|
|
77
102
|
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
78
103
|
const packageJson = JSON.parse(
|
|
79
|
-
readFileSync(
|
|
104
|
+
readFileSync(join2(__dirname2, "..", "package.json"), "utf-8")
|
|
80
105
|
);
|
|
81
106
|
return packageJson.version;
|
|
82
107
|
}
|
|
@@ -88,6 +113,16 @@ function parseLocalMidnight(dateStr) {
|
|
|
88
113
|
const now = /* @__PURE__ */ new Date();
|
|
89
114
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
90
115
|
}
|
|
116
|
+
if (dateStr === "yesterday") {
|
|
117
|
+
const now = /* @__PURE__ */ new Date();
|
|
118
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
|
|
119
|
+
}
|
|
120
|
+
const relMatch = dateStr.match(/^-(\d+)d$/);
|
|
121
|
+
if (relMatch) {
|
|
122
|
+
const days = Number(relMatch[1]);
|
|
123
|
+
const now = /* @__PURE__ */ new Date();
|
|
124
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - days);
|
|
125
|
+
}
|
|
91
126
|
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
92
127
|
if (!match) return null;
|
|
93
128
|
const [, y, m, d] = match.map(Number);
|
|
@@ -128,7 +163,7 @@ function parseArgs(args) {
|
|
|
128
163
|
} else {
|
|
129
164
|
const parsed = parseLocalMidnight(dateStr);
|
|
130
165
|
if (!parsed) {
|
|
131
|
-
reportError = `Invalid date: "${dateStr}". Use YYYY-MM-DD or "
|
|
166
|
+
reportError = `Invalid date: "${dateStr}". Use YYYY-MM-DD, "today", "yesterday", or "-Nd" (N days ago).`;
|
|
132
167
|
} else {
|
|
133
168
|
reportDate = parsed;
|
|
134
169
|
}
|
|
@@ -179,10 +214,20 @@ function parseArgs(args) {
|
|
|
179
214
|
}
|
|
180
215
|
if (args[0] === "summary") {
|
|
181
216
|
const rest = args.slice(1);
|
|
182
|
-
let summaryDate
|
|
217
|
+
let summaryDate;
|
|
218
|
+
let summaryFrom;
|
|
219
|
+
let summaryTo;
|
|
183
220
|
let summaryPrompt;
|
|
184
221
|
let summaryForce = false;
|
|
222
|
+
let summaryAssumeYes = false;
|
|
185
223
|
let summaryError;
|
|
224
|
+
const FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
225
|
+
"--date",
|
|
226
|
+
"--last",
|
|
227
|
+
"--from",
|
|
228
|
+
"--to",
|
|
229
|
+
"--prompt"
|
|
230
|
+
]);
|
|
186
231
|
for (let i = 0; i < rest.length; i++) {
|
|
187
232
|
const arg = rest[i];
|
|
188
233
|
if (!arg.startsWith("-")) continue;
|
|
@@ -190,7 +235,7 @@ function parseArgs(args) {
|
|
|
190
235
|
summaryError = `Unknown option: "${arg}". Run agenthud --help for usage.`;
|
|
191
236
|
break;
|
|
192
237
|
}
|
|
193
|
-
if (arg
|
|
238
|
+
if (FLAGS_WITH_VALUE.has(arg)) i++;
|
|
194
239
|
}
|
|
195
240
|
const dateIdx = rest.indexOf("--date");
|
|
196
241
|
if (dateIdx !== -1) {
|
|
@@ -200,12 +245,70 @@ function parseArgs(args) {
|
|
|
200
245
|
} else {
|
|
201
246
|
const parsed = parseLocalMidnight(dateStr);
|
|
202
247
|
if (!parsed) {
|
|
203
|
-
summaryError = `Invalid date: "${dateStr}". Use YYYY-MM-DD or "
|
|
248
|
+
summaryError = `Invalid date: "${dateStr}". Use YYYY-MM-DD, "today", "yesterday", or "-Nd" (N days ago).`;
|
|
204
249
|
} else {
|
|
205
250
|
summaryDate = parsed;
|
|
206
251
|
}
|
|
207
252
|
}
|
|
208
253
|
}
|
|
254
|
+
const lastIdx = rest.indexOf("--last");
|
|
255
|
+
if (lastIdx !== -1 && !summaryError) {
|
|
256
|
+
const val = rest[lastIdx + 1];
|
|
257
|
+
if (!val) {
|
|
258
|
+
summaryError = "Invalid --last: missing value (e.g. --last 7d).";
|
|
259
|
+
} else {
|
|
260
|
+
const m = val.match(/^(\d+)d$/);
|
|
261
|
+
if (!m) {
|
|
262
|
+
summaryError = `Invalid --last: "${val}". Use form like "7d".`;
|
|
263
|
+
} else {
|
|
264
|
+
const days = Number(m[1]);
|
|
265
|
+
if (days < 1) {
|
|
266
|
+
summaryError = `Invalid --last: "${val}". Must be at least 1 day.`;
|
|
267
|
+
} else {
|
|
268
|
+
const today = todayLocalMidnight();
|
|
269
|
+
const from = new Date(today);
|
|
270
|
+
from.setDate(today.getDate() - (days - 1));
|
|
271
|
+
summaryFrom = from;
|
|
272
|
+
summaryTo = today;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const fromIdx = rest.indexOf("--from");
|
|
278
|
+
const toIdx = rest.indexOf("--to");
|
|
279
|
+
if ((fromIdx !== -1 || toIdx !== -1) && !summaryError) {
|
|
280
|
+
if (fromIdx === -1 || toIdx === -1) {
|
|
281
|
+
summaryError = "--from and --to must be used together.";
|
|
282
|
+
} else {
|
|
283
|
+
const fromStr = rest[fromIdx + 1];
|
|
284
|
+
const toStr = rest[toIdx + 1];
|
|
285
|
+
const from = fromStr ? parseLocalMidnight(fromStr) : null;
|
|
286
|
+
const to = toStr ? parseLocalMidnight(toStr) : null;
|
|
287
|
+
if (!from) {
|
|
288
|
+
summaryError = `Invalid --from: "${fromStr}".`;
|
|
289
|
+
} else if (!to) {
|
|
290
|
+
summaryError = `Invalid --to: "${toStr}".`;
|
|
291
|
+
} else if (from.getTime() > to.getTime()) {
|
|
292
|
+
summaryError = `--from (${fromStr}) must be on or before --to (${toStr}).`;
|
|
293
|
+
} else {
|
|
294
|
+
summaryFrom = from;
|
|
295
|
+
summaryTo = to;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!summaryError) {
|
|
300
|
+
const modesUsed = [
|
|
301
|
+
summaryDate !== void 0,
|
|
302
|
+
lastIdx !== -1,
|
|
303
|
+
fromIdx !== -1 || toIdx !== -1
|
|
304
|
+
].filter(Boolean).length;
|
|
305
|
+
if (modesUsed > 1) {
|
|
306
|
+
summaryError = "--date, --last, and --from/--to are mutually exclusive.";
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (!summaryError && summaryDate === void 0 && summaryFrom === void 0) {
|
|
310
|
+
summaryDate = todayLocalMidnight();
|
|
311
|
+
}
|
|
209
312
|
const promptIdx = rest.indexOf("--prompt");
|
|
210
313
|
if (promptIdx !== -1) {
|
|
211
314
|
const val = rest[promptIdx + 1];
|
|
@@ -216,11 +319,15 @@ function parseArgs(args) {
|
|
|
216
319
|
}
|
|
217
320
|
}
|
|
218
321
|
if (rest.includes("--force")) summaryForce = true;
|
|
322
|
+
if (rest.includes("-y") || rest.includes("--yes")) summaryAssumeYes = true;
|
|
219
323
|
return {
|
|
220
324
|
mode: "summary",
|
|
221
325
|
summaryDate,
|
|
326
|
+
summaryFrom,
|
|
327
|
+
summaryTo,
|
|
222
328
|
summaryPrompt,
|
|
223
329
|
summaryForce,
|
|
330
|
+
summaryAssumeYes,
|
|
224
331
|
summaryError
|
|
225
332
|
};
|
|
226
333
|
}
|
|
@@ -244,13 +351,12 @@ function parseArgs(args) {
|
|
|
244
351
|
// src/config/globalConfig.ts
|
|
245
352
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
246
353
|
import { homedir } from "os";
|
|
247
|
-
import { join as
|
|
354
|
+
import { join as join3 } from "path";
|
|
248
355
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
249
|
-
var CONFIG_PATH =
|
|
250
|
-
var STATE_PATH =
|
|
356
|
+
var CONFIG_PATH = join3(homedir(), ".agenthud", "config.yaml");
|
|
357
|
+
var STATE_PATH = join3(homedir(), ".agenthud", "state.yaml");
|
|
251
358
|
var DEFAULT_GLOBAL_CONFIG = {
|
|
252
359
|
refreshIntervalMs: 2e3,
|
|
253
|
-
logDir: join2(homedir(), ".agenthud", "logs"),
|
|
254
360
|
hiddenSessions: [],
|
|
255
361
|
hiddenSubAgents: [],
|
|
256
362
|
filterPresets: [[], ["response"], ["commit"]],
|
|
@@ -263,7 +369,7 @@ function parseInterval(value) {
|
|
|
263
369
|
return match[2] === "m" ? n * 60 * 1e3 : n * 1e3;
|
|
264
370
|
}
|
|
265
371
|
function ensureAgenthudDir() {
|
|
266
|
-
const dir =
|
|
372
|
+
const dir = join3(homedir(), ".agenthud");
|
|
267
373
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
268
374
|
}
|
|
269
375
|
function writeDefaultConfig() {
|
|
@@ -274,9 +380,6 @@ function writeDefaultConfig() {
|
|
|
274
380
|
# How often to poll for activity updates
|
|
275
381
|
refreshInterval: 2s
|
|
276
382
|
|
|
277
|
-
# Where 's' key saves activity logs
|
|
278
|
-
logDir: ~/.agenthud/logs
|
|
279
|
-
|
|
280
383
|
# Activity filter presets (cycle with 'f' key in viewer)
|
|
281
384
|
# Each list is one preset; [] means "all". First preset is the default.
|
|
282
385
|
filterPresets:
|
|
@@ -326,9 +429,6 @@ function loadGlobalConfig() {
|
|
|
326
429
|
const ms = parseInterval(configRaw.refreshInterval);
|
|
327
430
|
if (ms !== null) config.refreshIntervalMs = ms;
|
|
328
431
|
}
|
|
329
|
-
if (typeof configRaw.logDir === "string") {
|
|
330
|
-
config.logDir = configRaw.logDir.replace(/^~/, homedir());
|
|
331
|
-
}
|
|
332
432
|
if (Array.isArray(configRaw.filterPresets)) {
|
|
333
433
|
const presets = configRaw.filterPresets.filter(Array.isArray).map(
|
|
334
434
|
(p) => p.filter((t) => typeof t === "string")
|
|
@@ -432,13 +532,10 @@ function hideProject(name) {
|
|
|
432
532
|
if (config.hiddenProjects.includes(name)) return;
|
|
433
533
|
updateState({ hiddenProjects: [...config.hiddenProjects, name] });
|
|
434
534
|
}
|
|
435
|
-
function ensureLogDir(logDir) {
|
|
436
|
-
if (!existsSync(logDir)) {
|
|
437
|
-
mkdirSync(logDir, { recursive: true });
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
535
|
function hasProjectLevelConfig() {
|
|
441
|
-
|
|
536
|
+
const candidate = join3(process.cwd(), ".agenthud", "config.yaml");
|
|
537
|
+
if (candidate === join3(homedir(), ".agenthud", "config.yaml")) return false;
|
|
538
|
+
return existsSync(candidate);
|
|
442
539
|
}
|
|
443
540
|
|
|
444
541
|
// src/data/reportGenerator.ts
|
|
@@ -786,16 +883,18 @@ import {
|
|
|
786
883
|
createWriteStream,
|
|
787
884
|
existsSync as existsSync4,
|
|
788
885
|
mkdirSync as mkdirSync2,
|
|
789
|
-
readFileSync as readFileSync5
|
|
886
|
+
readFileSync as readFileSync5,
|
|
887
|
+
unlinkSync
|
|
790
888
|
} from "fs";
|
|
791
889
|
import { homedir as homedir3 } from "os";
|
|
792
|
-
import { dirname as dirname2, join as
|
|
890
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
891
|
+
import { createInterface } from "readline";
|
|
793
892
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
794
893
|
|
|
795
894
|
// src/data/sessions.ts
|
|
796
895
|
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
797
896
|
import { homedir as homedir2 } from "os";
|
|
798
|
-
import { basename as basename2, join as
|
|
897
|
+
import { basename as basename2, join as join4 } from "path";
|
|
799
898
|
|
|
800
899
|
// src/ui/constants.ts
|
|
801
900
|
import stringWidth from "string-width";
|
|
@@ -843,7 +942,7 @@ function getDisplayWidth(s) {
|
|
|
843
942
|
|
|
844
943
|
// src/data/sessions.ts
|
|
845
944
|
function getProjectsDir() {
|
|
846
|
-
return process.env.CLAUDE_PROJECTS_DIR ??
|
|
945
|
+
return process.env.CLAUDE_PROJECTS_DIR ?? join4(homedir2(), ".claude", "projects");
|
|
847
946
|
}
|
|
848
947
|
function decodeProjectPath(encoded) {
|
|
849
948
|
const windowsDriveMatch = encoded.match(/^([A-Za-z])--(.*)$/);
|
|
@@ -972,7 +1071,7 @@ function readEntrypoint(filePath) {
|
|
|
972
1071
|
}
|
|
973
1072
|
}
|
|
974
1073
|
function buildSubAgents(parentId, projectDir, config, projectName) {
|
|
975
|
-
const subagentsDir =
|
|
1074
|
+
const subagentsDir = join4(projectDir, parentId, "subagents");
|
|
976
1075
|
if (!existsSync3(subagentsDir)) return [];
|
|
977
1076
|
let files;
|
|
978
1077
|
try {
|
|
@@ -985,7 +1084,7 @@ function buildSubAgents(parentId, projectDir, config, projectName) {
|
|
|
985
1084
|
return files.map((file) => {
|
|
986
1085
|
const id = file.replace(/\.jsonl$/, "");
|
|
987
1086
|
const hideKey = `${projectName}/${id}`;
|
|
988
|
-
const filePath =
|
|
1087
|
+
const filePath = join4(subagentsDir, file);
|
|
989
1088
|
try {
|
|
990
1089
|
const stat = statSync2(filePath);
|
|
991
1090
|
const { agentId, taskDescription } = readSubAgentInfo(filePath);
|
|
@@ -1025,7 +1124,7 @@ function discoverSessions(config) {
|
|
|
1025
1124
|
try {
|
|
1026
1125
|
projectDirs = readdirSync(projectsDir).filter((entry) => {
|
|
1027
1126
|
try {
|
|
1028
|
-
return statSync2(
|
|
1127
|
+
return statSync2(join4(projectsDir, entry)).isDirectory();
|
|
1029
1128
|
} catch {
|
|
1030
1129
|
return false;
|
|
1031
1130
|
}
|
|
@@ -1040,7 +1139,7 @@ function discoverSessions(config) {
|
|
|
1040
1139
|
}
|
|
1041
1140
|
const allSessions = [];
|
|
1042
1141
|
for (const encodedDir of projectDirs) {
|
|
1043
|
-
const projectDir =
|
|
1142
|
+
const projectDir = join4(projectsDir, encodedDir);
|
|
1044
1143
|
const decodedPath = decodeProjectPath(encodedDir);
|
|
1045
1144
|
const projectName = basename2(decodedPath);
|
|
1046
1145
|
let files;
|
|
@@ -1054,7 +1153,7 @@ function discoverSessions(config) {
|
|
|
1054
1153
|
for (const file of files) {
|
|
1055
1154
|
const id = file.replace(/\.jsonl$/, "");
|
|
1056
1155
|
const hideKey = `${projectName}/${id}`;
|
|
1057
|
-
const filePath =
|
|
1156
|
+
const filePath = join4(projectDir, file);
|
|
1058
1157
|
try {
|
|
1059
1158
|
const stat = statSync2(filePath);
|
|
1060
1159
|
const subAgents = buildSubAgents(id, projectDir, config, projectName);
|
|
@@ -1126,44 +1225,50 @@ function discoverSessions(config) {
|
|
|
1126
1225
|
|
|
1127
1226
|
// src/data/summaryRunner.ts
|
|
1128
1227
|
function agenthudHomeDir() {
|
|
1129
|
-
const dir =
|
|
1228
|
+
const dir = join5(homedir3(), ".agenthud");
|
|
1130
1229
|
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
1131
1230
|
return dir;
|
|
1132
1231
|
}
|
|
1133
1232
|
function summariesDir() {
|
|
1134
|
-
const dir =
|
|
1233
|
+
const dir = join5(agenthudHomeDir(), "summaries");
|
|
1135
1234
|
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
1136
1235
|
return dir;
|
|
1137
1236
|
}
|
|
1138
|
-
function
|
|
1139
|
-
return
|
|
1237
|
+
function promptFilename(kind) {
|
|
1238
|
+
return kind === "daily" ? "summary-prompt.md" : "summary-range-prompt.md";
|
|
1239
|
+
}
|
|
1240
|
+
function userPromptPath(kind) {
|
|
1241
|
+
return join5(homedir3(), ".agenthud", promptFilename(kind));
|
|
1140
1242
|
}
|
|
1141
|
-
function templatePath() {
|
|
1243
|
+
function templatePath(kind) {
|
|
1142
1244
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
1143
|
-
return
|
|
1245
|
+
return join5(here, "templates", promptFilename(kind));
|
|
1144
1246
|
}
|
|
1145
1247
|
function dateKey(d) {
|
|
1146
1248
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
1147
1249
|
}
|
|
1148
|
-
function
|
|
1149
|
-
return
|
|
1250
|
+
function dailyCachePath(date) {
|
|
1251
|
+
return join5(summariesDir(), `${dateKey(date)}.md`);
|
|
1252
|
+
}
|
|
1253
|
+
function rangeCachePath(from, to) {
|
|
1254
|
+
return join5(summariesDir(), `range-${dateKey(from)}_${dateKey(to)}.md`);
|
|
1150
1255
|
}
|
|
1151
1256
|
function isSameLocalDay2(a, b) {
|
|
1152
1257
|
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
1153
1258
|
}
|
|
1154
|
-
function ensureUserPromptFile() {
|
|
1155
|
-
const p = userPromptPath();
|
|
1259
|
+
function ensureUserPromptFile(kind) {
|
|
1260
|
+
const p = userPromptPath(kind);
|
|
1156
1261
|
if (existsSync4(p)) return;
|
|
1157
1262
|
const dir = dirname2(p);
|
|
1158
1263
|
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
1159
1264
|
try {
|
|
1160
|
-
copyFileSync(templatePath(), p);
|
|
1265
|
+
copyFileSync(templatePath(kind), p);
|
|
1161
1266
|
} catch {
|
|
1162
1267
|
}
|
|
1163
1268
|
}
|
|
1164
|
-
function resolvePrompt(override) {
|
|
1269
|
+
function resolvePrompt(kind, override) {
|
|
1165
1270
|
if (override) return override;
|
|
1166
|
-
const p = userPromptPath();
|
|
1271
|
+
const p = userPromptPath(kind);
|
|
1167
1272
|
if (existsSync4(p)) {
|
|
1168
1273
|
try {
|
|
1169
1274
|
return readFileSync5(p, "utf-8");
|
|
@@ -1171,88 +1276,409 @@ function resolvePrompt(override) {
|
|
|
1171
1276
|
}
|
|
1172
1277
|
}
|
|
1173
1278
|
try {
|
|
1174
|
-
return readFileSync5(templatePath(), "utf-8");
|
|
1279
|
+
return readFileSync5(templatePath(kind), "utf-8");
|
|
1175
1280
|
} catch {
|
|
1176
|
-
return "Summarize the
|
|
1281
|
+
return "Summarize the input below.";
|
|
1177
1282
|
}
|
|
1178
1283
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1284
|
+
function shouldUseRangeCache(force, dates, today, cacheExists) {
|
|
1285
|
+
if (force) return false;
|
|
1286
|
+
if (!cacheExists) return false;
|
|
1287
|
+
if (dates.some((d) => isSameLocalDay2(d, today))) return false;
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
function enumerateDates(from, to) {
|
|
1291
|
+
const dates = [];
|
|
1292
|
+
const cursor = new Date(from);
|
|
1293
|
+
while (cursor.getTime() <= to.getTime()) {
|
|
1294
|
+
dates.push(new Date(cursor));
|
|
1295
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
1191
1296
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1205
|
-
cwd: agenthudHomeDir()
|
|
1297
|
+
return dates;
|
|
1298
|
+
}
|
|
1299
|
+
async function ask(question, defaultYes = false) {
|
|
1300
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1301
|
+
return new Promise((resolve2) => {
|
|
1302
|
+
rl.question(question, (answer) => {
|
|
1303
|
+
rl.close();
|
|
1304
|
+
const trimmed = answer.trim();
|
|
1305
|
+
if (trimmed.length === 0) return resolve2(defaultYes);
|
|
1306
|
+
if (/^y(es)?$/i.test(trimmed)) return resolve2(true);
|
|
1307
|
+
if (/^n(o)?$/i.test(trimmed)) return resolve2(false);
|
|
1308
|
+
resolve2(defaultYes);
|
|
1206
1309
|
});
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
function formatUsage(u) {
|
|
1313
|
+
const fmt = (n) => n.toLocaleString("en-US");
|
|
1314
|
+
const parts = [`${fmt(u.inputTokens)} in / ${fmt(u.outputTokens)} out`];
|
|
1315
|
+
const cacheParts = [];
|
|
1316
|
+
if (u.cacheReadTokens > 0) cacheParts.push(`${fmt(u.cacheReadTokens)} read`);
|
|
1317
|
+
if (u.cacheCreationTokens > 0)
|
|
1318
|
+
cacheParts.push(`${fmt(u.cacheCreationTokens)} written`);
|
|
1319
|
+
if (cacheParts.length > 0) parts.push(`cache: ${cacheParts.join(", ")}`);
|
|
1320
|
+
if (u.costUsd != null) parts.push(`$${u.costUsd.toFixed(4)}`);
|
|
1321
|
+
return parts.join(" \xB7 ");
|
|
1322
|
+
}
|
|
1323
|
+
function spawnClaude(opts) {
|
|
1324
|
+
return new Promise((resolve2) => {
|
|
1325
|
+
const proc = spawn(
|
|
1326
|
+
"claude",
|
|
1327
|
+
[
|
|
1328
|
+
"-p",
|
|
1329
|
+
"--no-session-persistence",
|
|
1330
|
+
"--output-format",
|
|
1331
|
+
"stream-json",
|
|
1332
|
+
"--verbose",
|
|
1333
|
+
opts.prompt
|
|
1334
|
+
],
|
|
1335
|
+
{
|
|
1336
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1337
|
+
cwd: agenthudHomeDir()
|
|
1338
|
+
}
|
|
1339
|
+
);
|
|
1207
1340
|
let cacheStream = null;
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1341
|
+
if (opts.cachePath) {
|
|
1342
|
+
cacheStream = createWriteStream(opts.cachePath, { encoding: "utf-8" });
|
|
1343
|
+
cacheStream.on("error", (err) => {
|
|
1344
|
+
process.stderr.write(
|
|
1345
|
+
`agenthud: warning: cannot write cache (${err.message})
|
|
1212
1346
|
`
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1347
|
+
);
|
|
1348
|
+
cacheStream = null;
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1216
1351
|
let stderrBuf = "";
|
|
1352
|
+
let stdoutErrBuf = "";
|
|
1353
|
+
let lineBuf = "";
|
|
1354
|
+
let assembledText = "";
|
|
1355
|
+
let usage = null;
|
|
1217
1356
|
proc.on("error", (err) => {
|
|
1218
1357
|
if (err.code === "ENOENT") {
|
|
1219
1358
|
process.stderr.write(
|
|
1220
1359
|
"Error: claude CLI not found. Install: npm i -g @anthropic-ai/claude-code\n"
|
|
1221
1360
|
);
|
|
1222
|
-
|
|
1361
|
+
resolve2({ code: 1, text: "", usage: null });
|
|
1223
1362
|
} else {
|
|
1224
1363
|
process.stderr.write(`Error: ${err.message}
|
|
1225
1364
|
`);
|
|
1226
|
-
|
|
1365
|
+
resolve2({ code: 1, text: "", usage: null });
|
|
1227
1366
|
}
|
|
1228
1367
|
});
|
|
1368
|
+
const writeText = (text) => {
|
|
1369
|
+
assembledText += text;
|
|
1370
|
+
if (opts.streamToStdout) process.stdout.write(text);
|
|
1371
|
+
cacheStream?.write(text);
|
|
1372
|
+
};
|
|
1373
|
+
const handleEvent = (event) => {
|
|
1374
|
+
const type = event.type;
|
|
1375
|
+
if (type === "assistant") {
|
|
1376
|
+
const msg = event.message;
|
|
1377
|
+
for (const block of msg?.content ?? []) {
|
|
1378
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
1379
|
+
writeText(block.text);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
} else if (type === "result") {
|
|
1383
|
+
const u = event.usage;
|
|
1384
|
+
const cost = event.total_cost_usd;
|
|
1385
|
+
if (u) {
|
|
1386
|
+
usage = {
|
|
1387
|
+
inputTokens: u.input_tokens ?? 0,
|
|
1388
|
+
outputTokens: u.output_tokens ?? 0,
|
|
1389
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
1390
|
+
cacheCreationTokens: u.cache_creation_input_tokens ?? 0,
|
|
1391
|
+
costUsd: cost ?? null
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1229
1396
|
proc.stdout.on("data", (chunk) => {
|
|
1230
|
-
|
|
1231
|
-
|
|
1397
|
+
lineBuf += chunk.toString();
|
|
1398
|
+
let nl = lineBuf.indexOf("\n");
|
|
1399
|
+
while (nl !== -1) {
|
|
1400
|
+
const line = lineBuf.slice(0, nl).trim();
|
|
1401
|
+
lineBuf = lineBuf.slice(nl + 1);
|
|
1402
|
+
if (line.length > 0) {
|
|
1403
|
+
try {
|
|
1404
|
+
handleEvent(JSON.parse(line));
|
|
1405
|
+
} catch {
|
|
1406
|
+
if (stdoutErrBuf.length < 1024) stdoutErrBuf += `${line}
|
|
1407
|
+
`;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
nl = lineBuf.indexOf("\n");
|
|
1411
|
+
}
|
|
1232
1412
|
});
|
|
1233
1413
|
proc.stderr.on("data", (chunk) => {
|
|
1234
1414
|
stderrBuf += chunk.toString();
|
|
1235
1415
|
process.stderr.write(chunk);
|
|
1236
1416
|
});
|
|
1237
1417
|
proc.on("close", (code) => {
|
|
1418
|
+
if (lineBuf.trim().length > 0) {
|
|
1419
|
+
try {
|
|
1420
|
+
handleEvent(JSON.parse(lineBuf.trim()));
|
|
1421
|
+
} catch {
|
|
1422
|
+
if (stdoutErrBuf.length < 1024) stdoutErrBuf += lineBuf;
|
|
1423
|
+
}
|
|
1424
|
+
lineBuf = "";
|
|
1425
|
+
}
|
|
1426
|
+
if (opts.streamToStdout) process.stdout.write("\n");
|
|
1238
1427
|
cacheStream?.end();
|
|
1239
1428
|
if (code !== 0) {
|
|
1240
|
-
|
|
1241
|
-
|
|
1429
|
+
if (opts.cachePath) {
|
|
1430
|
+
try {
|
|
1431
|
+
unlinkSync(opts.cachePath);
|
|
1432
|
+
} catch {
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
const combined = (stderrBuf + stdoutErrBuf).toLowerCase();
|
|
1436
|
+
if (combined.includes("not logged in") || combined.includes("not authenticated") || combined.includes("please run /login") || combined.includes(" auth")) {
|
|
1242
1437
|
process.stderr.write(
|
|
1243
|
-
"\nHint: claude appears to be unauthenticated. Run: claude\n"
|
|
1438
|
+
"\nHint: claude appears to be unauthenticated. Run: claude /login\n"
|
|
1244
1439
|
);
|
|
1245
1440
|
}
|
|
1246
1441
|
}
|
|
1247
|
-
|
|
1442
|
+
resolve2({ code: code ?? 1, text: assembledText, usage });
|
|
1248
1443
|
});
|
|
1249
|
-
proc.stdin.end(
|
|
1444
|
+
proc.stdin.end(opts.stdin);
|
|
1250
1445
|
});
|
|
1251
1446
|
}
|
|
1447
|
+
async function generateDailySummary(opts) {
|
|
1448
|
+
ensureUserPromptFile("daily");
|
|
1449
|
+
const isToday = isSameLocalDay2(opts.date, opts.today);
|
|
1450
|
+
const cached = dailyCachePath(opts.date);
|
|
1451
|
+
const dateLabel = dateKey(opts.date);
|
|
1452
|
+
if (!isToday && !opts.force && existsSync4(cached)) {
|
|
1453
|
+
try {
|
|
1454
|
+
const content = readFileSync5(cached, "utf-8");
|
|
1455
|
+
if (opts.announce) {
|
|
1456
|
+
process.stderr.write(`agenthud: cached summary from ${cached}
|
|
1457
|
+
`);
|
|
1458
|
+
}
|
|
1459
|
+
if (opts.streamToStdout) {
|
|
1460
|
+
process.stdout.write(content);
|
|
1461
|
+
if (!content.endsWith("\n")) process.stdout.write("\n");
|
|
1462
|
+
}
|
|
1463
|
+
return {
|
|
1464
|
+
code: 0,
|
|
1465
|
+
markdown: content,
|
|
1466
|
+
fromCache: true,
|
|
1467
|
+
skipped: false,
|
|
1468
|
+
usage: null
|
|
1469
|
+
};
|
|
1470
|
+
} catch {
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (opts.announce) {
|
|
1474
|
+
process.stderr.write(`agenthud: scanning sessions for ${dateLabel}...
|
|
1475
|
+
`);
|
|
1476
|
+
}
|
|
1477
|
+
const config = loadGlobalConfig();
|
|
1478
|
+
const tree = discoverSessions(config);
|
|
1479
|
+
const flatSessions = [
|
|
1480
|
+
...tree.projects.flatMap((p) => p.sessions),
|
|
1481
|
+
...tree.coldProjects.flatMap((p) => p.sessions)
|
|
1482
|
+
];
|
|
1483
|
+
const reportMarkdown = generateReport(flatSessions, {
|
|
1484
|
+
date: opts.date,
|
|
1485
|
+
include: ["response", "bash", "edit", "thinking"],
|
|
1486
|
+
format: "markdown",
|
|
1487
|
+
detailLimit: 0,
|
|
1488
|
+
withGit: true
|
|
1489
|
+
});
|
|
1490
|
+
if (opts.announce) {
|
|
1491
|
+
const reportLines = reportMarkdown.split("\n");
|
|
1492
|
+
const sessionCount = reportLines.filter((l) => l.startsWith("## ")).length;
|
|
1493
|
+
const activityCount = reportLines.filter(
|
|
1494
|
+
(l) => /^\[\d{2}:\d{2}\]/.test(l)
|
|
1495
|
+
).length;
|
|
1496
|
+
const commitCount = reportLines.filter(
|
|
1497
|
+
(l) => /^\[\d{2}:\d{2}\] ◆/.test(l)
|
|
1498
|
+
).length;
|
|
1499
|
+
const sizeKb = (Buffer.byteLength(reportMarkdown, "utf-8") / 1024).toFixed(
|
|
1500
|
+
1
|
|
1501
|
+
);
|
|
1502
|
+
process.stderr.write(
|
|
1503
|
+
`agenthud: input: ${sessionCount} sessions, ${activityCount} activities, ${commitCount} commits (${reportLines.length} lines, ${sizeKb}KB)
|
|
1504
|
+
`
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
if (opts.confirmBeforeSpawn) {
|
|
1508
|
+
const proceed = await opts.confirmBeforeSpawn();
|
|
1509
|
+
if (!proceed) {
|
|
1510
|
+
return {
|
|
1511
|
+
code: 0,
|
|
1512
|
+
markdown: "",
|
|
1513
|
+
fromCache: false,
|
|
1514
|
+
skipped: true,
|
|
1515
|
+
usage: null
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (opts.announce) {
|
|
1520
|
+
process.stderr.write(
|
|
1521
|
+
`agenthud: sending to claude (this may take a minute)...
|
|
1522
|
+
|
|
1523
|
+
`
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
const prompt = resolvePrompt("daily", opts.promptOverride);
|
|
1527
|
+
const result = await spawnClaude({
|
|
1528
|
+
prompt,
|
|
1529
|
+
stdin: reportMarkdown,
|
|
1530
|
+
cachePath: cached,
|
|
1531
|
+
streamToStdout: opts.streamToStdout
|
|
1532
|
+
});
|
|
1533
|
+
if (opts.announce && result.code === 0) {
|
|
1534
|
+
process.stderr.write("\n");
|
|
1535
|
+
process.stderr.write(`agenthud: saved to ${cached}
|
|
1536
|
+
`);
|
|
1537
|
+
if (result.usage) {
|
|
1538
|
+
process.stderr.write(`agenthud: ${formatUsage(result.usage)}
|
|
1539
|
+
`);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return {
|
|
1543
|
+
code: result.code,
|
|
1544
|
+
markdown: result.text,
|
|
1545
|
+
fromCache: false,
|
|
1546
|
+
skipped: false,
|
|
1547
|
+
usage: result.usage
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
async function runSummary(options2) {
|
|
1551
|
+
const res = await generateDailySummary({
|
|
1552
|
+
date: options2.date,
|
|
1553
|
+
today: options2.today,
|
|
1554
|
+
force: options2.force,
|
|
1555
|
+
promptOverride: options2.prompt,
|
|
1556
|
+
streamToStdout: true,
|
|
1557
|
+
announce: true
|
|
1558
|
+
});
|
|
1559
|
+
return res.code;
|
|
1560
|
+
}
|
|
1561
|
+
async function runRangeSummary(options2) {
|
|
1562
|
+
ensureUserPromptFile("daily");
|
|
1563
|
+
ensureUserPromptFile("range");
|
|
1564
|
+
const dates = enumerateDates(options2.from, options2.to);
|
|
1565
|
+
const fromLabel = dateKey(options2.from);
|
|
1566
|
+
const toLabel = dateKey(options2.to);
|
|
1567
|
+
const rangeCache = rangeCachePath(options2.from, options2.to);
|
|
1568
|
+
if (shouldUseRangeCache(
|
|
1569
|
+
options2.force,
|
|
1570
|
+
dates,
|
|
1571
|
+
options2.today,
|
|
1572
|
+
existsSync4(rangeCache)
|
|
1573
|
+
)) {
|
|
1574
|
+
try {
|
|
1575
|
+
const content = readFileSync5(rangeCache, "utf-8");
|
|
1576
|
+
process.stderr.write(
|
|
1577
|
+
`agenthud: cached range summary from ${rangeCache}
|
|
1578
|
+
`
|
|
1579
|
+
);
|
|
1580
|
+
process.stdout.write(content);
|
|
1581
|
+
if (!content.endsWith("\n")) process.stdout.write("\n");
|
|
1582
|
+
return 0;
|
|
1583
|
+
} catch {
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
let cachedCount = 0;
|
|
1587
|
+
let missingCount = 0;
|
|
1588
|
+
for (const d of dates) {
|
|
1589
|
+
const isToday = isSameLocalDay2(d, options2.today);
|
|
1590
|
+
if (!isToday && existsSync4(dailyCachePath(d))) cachedCount++;
|
|
1591
|
+
else missingCount++;
|
|
1592
|
+
}
|
|
1593
|
+
process.stderr.write(
|
|
1594
|
+
`agenthud: range ${fromLabel} \u2192 ${toLabel} (${dates.length} days)
|
|
1595
|
+
`
|
|
1596
|
+
);
|
|
1597
|
+
process.stderr.write(
|
|
1598
|
+
`agenthud: ${cachedCount} cached, ${missingCount} to generate
|
|
1599
|
+
`
|
|
1600
|
+
);
|
|
1601
|
+
const dailyMarkdowns = [];
|
|
1602
|
+
let skippedCount = 0;
|
|
1603
|
+
for (const d of dates) {
|
|
1604
|
+
const label = dateKey(d);
|
|
1605
|
+
const isToday = isSameLocalDay2(d, options2.today);
|
|
1606
|
+
process.stderr.write(`
|
|
1607
|
+
agenthud: --- ${label} ---
|
|
1608
|
+
`);
|
|
1609
|
+
const willPrompt = !options2.assumeYes && (isToday || !existsSync4(dailyCachePath(d)));
|
|
1610
|
+
const confirmer = willPrompt ? async () => {
|
|
1611
|
+
const hint = isToday ? " (today \u2014 regenerated every time)" : "";
|
|
1612
|
+
return ask(`Generate this summary${hint}? [Y/n] `, true);
|
|
1613
|
+
} : void 0;
|
|
1614
|
+
const res = await generateDailySummary({
|
|
1615
|
+
date: d,
|
|
1616
|
+
today: options2.today,
|
|
1617
|
+
force: false,
|
|
1618
|
+
streamToStdout: false,
|
|
1619
|
+
announce: true,
|
|
1620
|
+
confirmBeforeSpawn: confirmer
|
|
1621
|
+
});
|
|
1622
|
+
if (res.skipped) {
|
|
1623
|
+
process.stderr.write(`agenthud: ${label} \u2014 skipped by user.
|
|
1624
|
+
`);
|
|
1625
|
+
skippedCount++;
|
|
1626
|
+
continue;
|
|
1627
|
+
}
|
|
1628
|
+
if (res.code !== 0) {
|
|
1629
|
+
process.stderr.write(
|
|
1630
|
+
`agenthud: aborted (failed to generate daily summary for ${label}).
|
|
1631
|
+
`
|
|
1632
|
+
);
|
|
1633
|
+
return res.code;
|
|
1634
|
+
}
|
|
1635
|
+
const text = res.markdown.trim();
|
|
1636
|
+
if (text.length === 0 || /^no activity found/i.test(text)) {
|
|
1637
|
+
process.stderr.write(`agenthud: ${label} has no activity \u2014 skipping.
|
|
1638
|
+
`);
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
dailyMarkdowns.push({ date: d, markdown: text });
|
|
1642
|
+
}
|
|
1643
|
+
if (dailyMarkdowns.length === 0) {
|
|
1644
|
+
process.stderr.write("agenthud: no daily summaries to combine.\n");
|
|
1645
|
+
return 1;
|
|
1646
|
+
}
|
|
1647
|
+
const metaInput = dailyMarkdowns.map(({ date, markdown }) => `# ${dateKey(date)}
|
|
1648
|
+
|
|
1649
|
+
${markdown}`).join("\n\n---\n\n");
|
|
1650
|
+
process.stderr.write(
|
|
1651
|
+
`
|
|
1652
|
+
agenthud: combining ${dailyMarkdowns.length} daily summaries into range summary...
|
|
1653
|
+
`
|
|
1654
|
+
);
|
|
1655
|
+
process.stderr.write(
|
|
1656
|
+
`agenthud: sending to claude (this may take a minute)...
|
|
1657
|
+
|
|
1658
|
+
`
|
|
1659
|
+
);
|
|
1660
|
+
const metaPrompt = resolvePrompt("range");
|
|
1661
|
+
const metaResult = await spawnClaude({
|
|
1662
|
+
prompt: metaPrompt,
|
|
1663
|
+
stdin: metaInput,
|
|
1664
|
+
cachePath: rangeCache,
|
|
1665
|
+
streamToStdout: true
|
|
1666
|
+
});
|
|
1667
|
+
if (metaResult.code !== 0) {
|
|
1668
|
+
return metaResult.code;
|
|
1669
|
+
}
|
|
1670
|
+
process.stderr.write("\n");
|
|
1671
|
+
process.stderr.write(`agenthud: saved to ${rangeCache}
|
|
1672
|
+
`);
|
|
1673
|
+
if (metaResult.usage) {
|
|
1674
|
+
process.stderr.write(`agenthud: ${formatUsage(metaResult.usage)}
|
|
1675
|
+
`);
|
|
1676
|
+
}
|
|
1677
|
+
return 0;
|
|
1678
|
+
}
|
|
1252
1679
|
|
|
1253
1680
|
// src/ui/App.tsx
|
|
1254
|
-
import { existsSync as existsSync5, watch
|
|
1255
|
-
import { join as join5 } from "path";
|
|
1681
|
+
import { existsSync as existsSync5, watch } from "fs";
|
|
1256
1682
|
import { Box as Box5, Text as Text5, useApp, useInput, useStdout } from "ink";
|
|
1257
1683
|
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
1258
1684
|
|
|
@@ -1513,7 +1939,7 @@ import { Box as Box3, Text as Text3 } from "ink";
|
|
|
1513
1939
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1514
1940
|
var SECTIONS = [
|
|
1515
1941
|
{
|
|
1516
|
-
title: "
|
|
1942
|
+
title: "Project tree",
|
|
1517
1943
|
rows: [
|
|
1518
1944
|
["\u2191 \u2193 / k j", "Move selection"],
|
|
1519
1945
|
["PgUp / Ctrl+B", "Page up"],
|
|
@@ -1534,8 +1960,7 @@ var SECTIONS = [
|
|
|
1534
1960
|
["G", "Jump to oldest"],
|
|
1535
1961
|
["\u21B5", "Open detail view for selected activity"],
|
|
1536
1962
|
["f", "Cycle filter preset (set in config.yaml)"],
|
|
1537
|
-
["
|
|
1538
|
-
["Tab", "Switch focus to session tree"]
|
|
1963
|
+
["Tab", "Switch focus to project tree"]
|
|
1539
1964
|
]
|
|
1540
1965
|
},
|
|
1541
1966
|
{
|
|
@@ -1545,6 +1970,15 @@ var SECTIONS = [
|
|
|
1545
1970
|
["\u21B5 / Esc / q", "Close"]
|
|
1546
1971
|
]
|
|
1547
1972
|
},
|
|
1973
|
+
{
|
|
1974
|
+
title: "Session status (by recent activity)",
|
|
1975
|
+
rows: [
|
|
1976
|
+
["[hot]", "Updated in the last 30 minutes", "green"],
|
|
1977
|
+
["[warm]", "Updated in the last hour", "yellow"],
|
|
1978
|
+
["[cool]", "Updated earlier today", "cyan"],
|
|
1979
|
+
["[cold]", "Last updated yesterday or earlier (collapsed)", "gray"]
|
|
1980
|
+
]
|
|
1981
|
+
},
|
|
1548
1982
|
{
|
|
1549
1983
|
title: "Always available",
|
|
1550
1984
|
rows: [
|
|
@@ -1565,14 +1999,17 @@ var SECTIONS = [
|
|
|
1565
1999
|
rows: [
|
|
1566
2000
|
["~/.agenthud/config.yaml", "User settings (edit freely)"],
|
|
1567
2001
|
["~/.agenthud/state.yaml", "Hidden items (app-managed)"],
|
|
1568
|
-
["~/.agenthud/summary-prompt.md", "
|
|
1569
|
-
["~/.agenthud/
|
|
2002
|
+
["~/.agenthud/summary-prompt.md", "Daily summary prompt template"],
|
|
2003
|
+
["~/.agenthud/summary-range-prompt.md", "Range summary prompt template"],
|
|
2004
|
+
["~/.agenthud/summaries/", "Cached daily and range summaries"]
|
|
1570
2005
|
]
|
|
1571
2006
|
}
|
|
1572
2007
|
];
|
|
1573
2008
|
function HelpPanel({
|
|
1574
2009
|
width,
|
|
1575
|
-
height
|
|
2010
|
+
height,
|
|
2011
|
+
scrollOffset = 0,
|
|
2012
|
+
onTotalLinesChange
|
|
1576
2013
|
}) {
|
|
1577
2014
|
const allKeys = SECTIONS.flatMap((s) => s.rows.map((r) => r[0]));
|
|
1578
2015
|
const keyColumn = Math.min(
|
|
@@ -1594,21 +2031,35 @@ function HelpPanel({
|
|
|
1594
2031
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: SECTIONS[s].title }, `title-${s}`)
|
|
1595
2032
|
);
|
|
1596
2033
|
for (let r = 0; r < SECTIONS[s].rows.length; r++) {
|
|
1597
|
-
const
|
|
2034
|
+
const row = SECTIONS[s].rows[r];
|
|
2035
|
+
const [key, desc] = row;
|
|
2036
|
+
const explicitColor = row.length === 3 ? row[2] : void 0;
|
|
1598
2037
|
const isCli = key.trim().startsWith("agenthud");
|
|
1599
2038
|
const isFile = key.includes("~/.agenthud");
|
|
2039
|
+
const color = explicitColor ?? (isCli ? "cyan" : isFile ? "green" : void 0);
|
|
1600
2040
|
lines.push(
|
|
1601
2041
|
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1602
2042
|
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " " }),
|
|
1603
|
-
/* @__PURE__ */ jsx3(Text3, { color
|
|
2043
|
+
/* @__PURE__ */ jsx3(Text3, { color, children: padTo(key, keyColumn) }),
|
|
1604
2044
|
/* @__PURE__ */ jsx3(Text3, { children: " " }),
|
|
1605
2045
|
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: desc })
|
|
1606
2046
|
] }, `row-${s}-${r}`)
|
|
1607
2047
|
);
|
|
1608
2048
|
}
|
|
1609
2049
|
}
|
|
1610
|
-
const
|
|
1611
|
-
|
|
2050
|
+
const indicatorReserved = lines.length > height ? 1 : 0;
|
|
2051
|
+
const viewport = Math.max(1, height - indicatorReserved);
|
|
2052
|
+
const maxOffset = Math.max(0, lines.length - viewport);
|
|
2053
|
+
const offset = Math.max(0, Math.min(scrollOffset, maxOffset));
|
|
2054
|
+
if (onTotalLinesChange) onTotalLinesChange(lines.length);
|
|
2055
|
+
const visible = lines.slice(offset, offset + viewport);
|
|
2056
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width, children: [
|
|
2057
|
+
visible,
|
|
2058
|
+
indicatorReserved > 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
2059
|
+
`-- ${offset + viewport} / ${lines.length} `,
|
|
2060
|
+
offset < maxOffset ? "(\u2193 / j / PgDn / Space for more) --" : "(top: g \xB7 \u2191 / k to scroll back) --"
|
|
2061
|
+
] })
|
|
2062
|
+
] });
|
|
1612
2063
|
}
|
|
1613
2064
|
|
|
1614
2065
|
// src/ui/hooks/useHotkeys.ts
|
|
@@ -1625,7 +2076,6 @@ function useHotkeys({
|
|
|
1625
2076
|
onScrollHalfPageDown,
|
|
1626
2077
|
onScrollTop,
|
|
1627
2078
|
onScrollBottom,
|
|
1628
|
-
onSaveLog,
|
|
1629
2079
|
onRefresh,
|
|
1630
2080
|
onQuit,
|
|
1631
2081
|
onEnter,
|
|
@@ -1635,6 +2085,8 @@ function useHotkeys({
|
|
|
1635
2085
|
onDetailScrollDown,
|
|
1636
2086
|
onFilter,
|
|
1637
2087
|
onHelp,
|
|
2088
|
+
onHelpScroll,
|
|
2089
|
+
onHelpScrollToTop,
|
|
1638
2090
|
filterLabel
|
|
1639
2091
|
}) {
|
|
1640
2092
|
const handleInput = (input, key) => {
|
|
@@ -1643,6 +2095,32 @@ function useHotkeys({
|
|
|
1643
2095
|
onHelp();
|
|
1644
2096
|
return;
|
|
1645
2097
|
}
|
|
2098
|
+
if (onHelpScroll) {
|
|
2099
|
+
if (key.downArrow || input === "j" || input === " ") {
|
|
2100
|
+
onHelpScroll(1);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
if (key.upArrow || input === "k") {
|
|
2104
|
+
onHelpScroll(-1);
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
if (key.pageDown || key.ctrl && input === "f") {
|
|
2108
|
+
onHelpScroll(10);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
if (key.pageUp || key.ctrl && input === "b") {
|
|
2112
|
+
onHelpScroll(-10);
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
if (input === "G") {
|
|
2116
|
+
onHelpScroll(Number.MAX_SAFE_INTEGER);
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
if (input === "g" && onHelpScrollToTop) {
|
|
2121
|
+
onHelpScrollToTop();
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
1646
2124
|
return;
|
|
1647
2125
|
}
|
|
1648
2126
|
if (input === "?") {
|
|
@@ -1741,13 +2219,9 @@ function useHotkeys({
|
|
|
1741
2219
|
onScrollBottom();
|
|
1742
2220
|
return;
|
|
1743
2221
|
}
|
|
1744
|
-
if (input === "s") {
|
|
1745
|
-
onSaveLog();
|
|
1746
|
-
return;
|
|
1747
|
-
}
|
|
1748
2222
|
}
|
|
1749
2223
|
};
|
|
1750
|
-
const statusBarItems = helpMode ? ["\u21B5/Esc/q/?: close"] : detailMode ? ["\u2191\u2193/jk: scroll", "\u21B5/Esc: close", "?: help"] : focus === "tree" ? [
|
|
2224
|
+
const statusBarItems = helpMode ? ["\u2191\u2193/jk: scroll", "PgDn/Space: page", "\u21B5/Esc/q/?: close"] : detailMode ? ["\u2191\u2193/jk: scroll", "\u21B5/Esc: close", "?: help"] : focus === "tree" ? [
|
|
1751
2225
|
"Tab: viewer",
|
|
1752
2226
|
"\u2191\u2193/jk: select",
|
|
1753
2227
|
"PgUp/Dn: page",
|
|
@@ -1757,7 +2231,7 @@ function useHotkeys({
|
|
|
1757
2231
|
"?: help",
|
|
1758
2232
|
"q: quit"
|
|
1759
2233
|
] : [
|
|
1760
|
-
"Tab:
|
|
2234
|
+
"Tab: projects",
|
|
1761
2235
|
"\u2191\u2193/jk: scroll",
|
|
1762
2236
|
"PgUp/Dn: page",
|
|
1763
2237
|
"g: live",
|
|
@@ -2052,7 +2526,7 @@ function SessionTreePanel({
|
|
|
2052
2526
|
}) {
|
|
2053
2527
|
const innerWidth = getInnerWidth(width);
|
|
2054
2528
|
const contentWidth = innerWidth - 1;
|
|
2055
|
-
const titleLine = createTitleLine("
|
|
2529
|
+
const titleLine = createTitleLine("Projects", "", width);
|
|
2056
2530
|
const bottomLine = createBottomLine(width);
|
|
2057
2531
|
const totalProjectCount = projects.length + coldProjects.length;
|
|
2058
2532
|
if (totalProjectCount === 0) {
|
|
@@ -2159,7 +2633,8 @@ function subSummarySentinel(parentId) {
|
|
|
2159
2633
|
status: "cold",
|
|
2160
2634
|
modelName: null,
|
|
2161
2635
|
subAgents: [],
|
|
2162
|
-
nonInteractive: false
|
|
2636
|
+
nonInteractive: false,
|
|
2637
|
+
firstUserPrompt: null
|
|
2163
2638
|
};
|
|
2164
2639
|
}
|
|
2165
2640
|
function appendSubAgentRows(result, session, expandedIds) {
|
|
@@ -2197,7 +2672,8 @@ function flattenSessions2(tree, expandedIds) {
|
|
|
2197
2672
|
status: project.hotness,
|
|
2198
2673
|
modelName: null,
|
|
2199
2674
|
subAgents: [],
|
|
2200
|
-
nonInteractive: false
|
|
2675
|
+
nonInteractive: false,
|
|
2676
|
+
firstUserPrompt: null
|
|
2201
2677
|
});
|
|
2202
2678
|
const shouldShowSessions = isCold ? expandedIds.has(`__expanded-${sentinelId}`) : !expandedIds.has(`__collapsed-${sentinelId}`);
|
|
2203
2679
|
if (shouldShowSessions) {
|
|
@@ -2221,7 +2697,8 @@ function flattenSessions2(tree, expandedIds) {
|
|
|
2221
2697
|
status: "cold",
|
|
2222
2698
|
modelName: null,
|
|
2223
2699
|
subAgents: [],
|
|
2224
|
-
nonInteractive: false
|
|
2700
|
+
nonInteractive: false,
|
|
2701
|
+
firstUserPrompt: null
|
|
2225
2702
|
});
|
|
2226
2703
|
if (expandedIds.has("__cold__")) {
|
|
2227
2704
|
for (const project of tree.coldProjects) {
|
|
@@ -2273,6 +2750,8 @@ function App({ mode }) {
|
|
|
2273
2750
|
const [detailScrollOffset, setDetailScrollOffset] = useState2(0);
|
|
2274
2751
|
const [filterIndex, setFilterIndex] = useState2(0);
|
|
2275
2752
|
const [helpMode, setHelpMode] = useState2(false);
|
|
2753
|
+
const [helpScroll, setHelpScroll] = useState2(0);
|
|
2754
|
+
const helpTotalLinesRef = useRef(0);
|
|
2276
2755
|
const allFlat = useMemo(
|
|
2277
2756
|
() => flattenSessions2(sessionTree, expandedIds),
|
|
2278
2757
|
[sessionTree, expandedIds]
|
|
@@ -2434,29 +2913,22 @@ function App({ mode }) {
|
|
|
2434
2913
|
const naturalTreeRows = allFlat.length;
|
|
2435
2914
|
const treeRows = Math.max(1, Math.min(naturalTreeRows, maxTreeRows));
|
|
2436
2915
|
const viewerRows = Math.max(5, height - 7 - treeRows);
|
|
2437
|
-
const saveLog = useCallback(() => {
|
|
2438
|
-
if (!activities.length || !selectedId) return;
|
|
2439
|
-
ensureLogDir(config.logDir);
|
|
2440
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2441
|
-
const filePath = join5(
|
|
2442
|
-
config.logDir,
|
|
2443
|
-
`${date}-${selectedId.slice(0, 8)}.txt`
|
|
2444
|
-
);
|
|
2445
|
-
const lines = activities.map(
|
|
2446
|
-
(a) => `[${a.timestamp.toISOString()}] ${a.icon} ${a.label} ${a.detail}`
|
|
2447
|
-
);
|
|
2448
|
-
try {
|
|
2449
|
-
writeFileSync2(filePath, `${lines.join("\n")}
|
|
2450
|
-
`, "utf-8");
|
|
2451
|
-
} catch {
|
|
2452
|
-
}
|
|
2453
|
-
}, [activities, selectedId, config.logDir]);
|
|
2454
2916
|
const spinner = useSpinner(isWatchMode);
|
|
2917
|
+
const helpViewportRows = Math.max(1, height - 3);
|
|
2918
|
+
const helpScrollStep = (delta) => {
|
|
2919
|
+
const max = Math.max(0, helpTotalLinesRef.current - helpViewportRows);
|
|
2920
|
+
setHelpScroll((s) => Math.max(0, Math.min(max, s + delta)));
|
|
2921
|
+
};
|
|
2455
2922
|
const { handleInput, statusBarItems } = useHotkeys({
|
|
2456
2923
|
focus,
|
|
2457
2924
|
detailMode,
|
|
2458
2925
|
helpMode,
|
|
2459
|
-
onHelp: () =>
|
|
2926
|
+
onHelp: () => {
|
|
2927
|
+
setHelpScroll(0);
|
|
2928
|
+
setHelpMode((m) => !m);
|
|
2929
|
+
},
|
|
2930
|
+
onHelpScroll: helpScrollStep,
|
|
2931
|
+
onHelpScrollToTop: () => setHelpScroll(0),
|
|
2460
2932
|
onSwitchFocus: () => setFocus((f) => f === "tree" ? "viewer" : "tree"),
|
|
2461
2933
|
onScrollUp: () => {
|
|
2462
2934
|
if (focus === "tree") {
|
|
@@ -2711,7 +3183,6 @@ function App({ mode }) {
|
|
|
2711
3183
|
}
|
|
2712
3184
|
}
|
|
2713
3185
|
},
|
|
2714
|
-
onSaveLog: saveLog,
|
|
2715
3186
|
onRefresh: refresh,
|
|
2716
3187
|
onQuit: exit,
|
|
2717
3188
|
onFilter: () => setFilterIndex((i) => (i + 1) % filterPresets.length),
|
|
@@ -2729,17 +3200,48 @@ function App({ mode }) {
|
|
|
2729
3200
|
}
|
|
2730
3201
|
}
|
|
2731
3202
|
const isPlaceholderSelected = !selectedSession || selectedId === "__cold__" || !!selectedId && selectedId.startsWith("__sub-") && selectedId.endsWith("__");
|
|
2732
|
-
const sessionDisplayName = isPlaceholderSelected ? "No session selected" : selectedSession.projectPath ? selectedSession.projectName || selectedSession.id.slice(0, 8) : selectedSession.agentId ?? selectedSession.id.slice(0, 8);
|
|
3203
|
+
const sessionDisplayName = isPlaceholderSelected || !selectedSession ? "No session selected" : selectedSession.projectPath ? selectedSession.projectName || selectedSession.id.slice(0, 8) : selectedSession.agentId ?? selectedSession.id.slice(0, 8);
|
|
3204
|
+
const MIN_WIDTH = 80;
|
|
3205
|
+
const MIN_HEIGHT = 20;
|
|
3206
|
+
if (isWatchMode && (width < MIN_WIDTH || height + 1 < MIN_HEIGHT)) {
|
|
3207
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width, children: [
|
|
3208
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "AgentHUD needs a larger terminal." }),
|
|
3209
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `Minimum: ${MIN_WIDTH} cols \xD7 ${MIN_HEIGHT} rows` }),
|
|
3210
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `Current: ${width} cols \xD7 ${height + 1} rows` }),
|
|
3211
|
+
/* @__PURE__ */ jsx5(Text5, { children: " " }),
|
|
3212
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Resize the window and AgentHUD will redraw automatically." }),
|
|
3213
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Press q to quit." })
|
|
3214
|
+
] });
|
|
3215
|
+
}
|
|
2733
3216
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
2734
|
-
isWatchMode &&
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
3217
|
+
isWatchMode && (() => {
|
|
3218
|
+
const branding = `${spinner} AgentHUD v${getVersion()}`;
|
|
3219
|
+
const sep = " \xB7 ";
|
|
3220
|
+
let items = statusBarItems;
|
|
3221
|
+
let shortcuts = items.join(sep);
|
|
3222
|
+
let showBranding = true;
|
|
3223
|
+
const fits = () => (showBranding ? getDisplayWidth(branding) + 1 : 0) + getDisplayWidth(shortcuts) <= width;
|
|
3224
|
+
if (!fits()) showBranding = false;
|
|
3225
|
+
while (!fits() && items.length > 1) {
|
|
3226
|
+
items = items.slice(1);
|
|
3227
|
+
shortcuts = items.join(sep);
|
|
3228
|
+
}
|
|
3229
|
+
return /* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, justifyContent: "space-between", width, children: [
|
|
3230
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: showBranding ? branding : "" }),
|
|
3231
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: shortcuts })
|
|
3232
|
+
] });
|
|
3233
|
+
})(),
|
|
3234
|
+
helpMode ? /* @__PURE__ */ jsx5(
|
|
3235
|
+
HelpPanel,
|
|
3236
|
+
{
|
|
3237
|
+
width,
|
|
3238
|
+
height: height - 2,
|
|
3239
|
+
scrollOffset: helpScroll,
|
|
3240
|
+
onTotalLinesChange: (n) => {
|
|
3241
|
+
helpTotalLinesRef.current = n;
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2743
3245
|
migrationWarning && /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "Config moved to ~/.agenthud/config.yaml" }) }),
|
|
2744
3246
|
/* @__PURE__ */ jsx5(
|
|
2745
3247
|
SessionTreePanel,
|
|
@@ -2798,13 +3300,13 @@ if (options.command === "version") {
|
|
|
2798
3300
|
process.exit(0);
|
|
2799
3301
|
}
|
|
2800
3302
|
var legacyConfig = join6(process.cwd(), ".agenthud", "config.yaml");
|
|
2801
|
-
if (existsSync6(legacyConfig)) {
|
|
3303
|
+
if (isLegacyProjectConfig(process.cwd(), homedir5()) && existsSync6(legacyConfig)) {
|
|
2802
3304
|
console.log(
|
|
2803
3305
|
"The project-level config file (.agenthud/config.yaml) is no longer supported."
|
|
2804
3306
|
);
|
|
2805
3307
|
console.log("Settings have moved to ~/.agenthud/config.yaml.");
|
|
2806
|
-
const rl =
|
|
2807
|
-
await new Promise((
|
|
3308
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
3309
|
+
await new Promise((resolve2) => {
|
|
2808
3310
|
rl.question("Delete the old config file and continue? [y/N] ", (answer) => {
|
|
2809
3311
|
rl.close();
|
|
2810
3312
|
if (answer.trim().toLowerCase() === "y") {
|
|
@@ -2814,7 +3316,7 @@ if (existsSync6(legacyConfig)) {
|
|
|
2814
3316
|
console.log("Aborted.");
|
|
2815
3317
|
process.exit(0);
|
|
2816
3318
|
}
|
|
2817
|
-
|
|
3319
|
+
resolve2();
|
|
2818
3320
|
});
|
|
2819
3321
|
});
|
|
2820
3322
|
}
|
|
@@ -2826,7 +3328,11 @@ if (options.mode === "report") {
|
|
|
2826
3328
|
}
|
|
2827
3329
|
const config = loadGlobalConfig();
|
|
2828
3330
|
const tree = discoverSessions(config);
|
|
2829
|
-
const
|
|
3331
|
+
const flatSessions = [
|
|
3332
|
+
...tree.projects.flatMap((p) => p.sessions),
|
|
3333
|
+
...tree.coldProjects.flatMap((p) => p.sessions)
|
|
3334
|
+
];
|
|
3335
|
+
const markdown = generateReport(flatSessions, {
|
|
2830
3336
|
date: options.reportDate,
|
|
2831
3337
|
include: options.reportInclude,
|
|
2832
3338
|
format: options.reportFormat,
|
|
@@ -2843,11 +3349,22 @@ if (options.mode === "summary") {
|
|
|
2843
3349
|
`);
|
|
2844
3350
|
process.exit(1);
|
|
2845
3351
|
}
|
|
3352
|
+
const today = /* @__PURE__ */ new Date();
|
|
3353
|
+
if (options.summaryFrom && options.summaryTo) {
|
|
3354
|
+
const exitCode2 = await runRangeSummary({
|
|
3355
|
+
from: options.summaryFrom,
|
|
3356
|
+
to: options.summaryTo,
|
|
3357
|
+
today,
|
|
3358
|
+
force: options.summaryForce ?? false,
|
|
3359
|
+
assumeYes: options.summaryAssumeYes ?? false
|
|
3360
|
+
});
|
|
3361
|
+
process.exit(exitCode2);
|
|
3362
|
+
}
|
|
2846
3363
|
const exitCode = await runSummary({
|
|
2847
3364
|
date: options.summaryDate,
|
|
2848
3365
|
prompt: options.summaryPrompt,
|
|
2849
3366
|
force: options.summaryForce ?? false,
|
|
2850
|
-
today
|
|
3367
|
+
today
|
|
2851
3368
|
});
|
|
2852
3369
|
process.exit(exitCode);
|
|
2853
3370
|
}
|