agenthud 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,14 +44,14 @@ npx agenthud
44
44
  ## Features
45
45
 
46
46
  - **Git**: Branch, commits, line changes
47
- - **Plan**: Progress, decisions from `.agent/plan.json`
47
+ - **Plan**: Progress, decisions from `.agenthud/plan.json`
48
48
  - **Tests**: Results with outdated detection
49
49
 
50
50
  ## Setup
51
51
 
52
52
  Add to your `CLAUDE.md`:
53
53
  ```markdown
54
- Maintain `.agent/` directory:
54
+ Maintain `.agenthud/` directory:
55
55
  - Update `plan.json` when plan changes
56
56
  - Update `decisions.json` for key decisions
57
57
  ```
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { render } from "ink";
6
6
  import { existsSync } from "fs";
7
7
 
8
8
  // src/ui/App.tsx
9
- import { useState, useEffect, useCallback } from "react";
9
+ import React, { useState, useEffect, useCallback, useMemo } from "react";
10
10
  import { Box as Box5, Text as Text5, useApp, useInput } from "ink";
11
11
 
12
12
  // src/ui/GitPanel.tsx
@@ -15,6 +15,31 @@ import { Box, Text } from "ink";
15
15
  // src/ui/constants.ts
16
16
  var PANEL_WIDTH = 60;
17
17
  var CONTENT_WIDTH = PANEL_WIDTH - 4;
18
+ var INNER_WIDTH = PANEL_WIDTH - 2;
19
+ var BOX = {
20
+ tl: "\u250C",
21
+ tr: "\u2510",
22
+ bl: "\u2514",
23
+ br: "\u2518",
24
+ h: "\u2500",
25
+ v: "\u2502",
26
+ ml: "\u251C",
27
+ mr: "\u2524"
28
+ };
29
+ function createTitleLine(label, suffix = "") {
30
+ const leftPart = BOX.h + " " + label + " ";
31
+ const rightPart = suffix ? " " + suffix + " " + BOX.h : "";
32
+ const dashCount = PANEL_WIDTH - 1 - leftPart.length - rightPart.length - 1;
33
+ const dashes = BOX.h.repeat(Math.max(0, dashCount));
34
+ return BOX.tl + leftPart + dashes + rightPart + BOX.tr;
35
+ }
36
+ function createBottomLine() {
37
+ return BOX.bl + BOX.h.repeat(INNER_WIDTH) + BOX.br;
38
+ }
39
+ function padLine(content) {
40
+ const padding = INNER_WIDTH - content.length;
41
+ return content + " ".repeat(Math.max(0, padding));
42
+ }
18
43
  var SEPARATOR = "\u2500".repeat(CONTENT_WIDTH);
19
44
  function truncate(text, maxLength) {
20
45
  if (text.length <= maxLength) return text;
@@ -25,11 +50,21 @@ function truncate(text, maxLength) {
25
50
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
26
51
  var MAX_COMMITS = 5;
27
52
  var MAX_MESSAGE_LENGTH = CONTENT_WIDTH - 10;
28
- function GitPanel({ branch, commits, stats, uncommitted = 0 }) {
53
+ function formatCountdown(seconds) {
54
+ if (seconds == null) return "";
55
+ return `\u21BB ${seconds}s`;
56
+ }
57
+ function GitPanel({ branch, commits, stats, uncommitted = 0, countdown }) {
58
+ const countdownSuffix = formatCountdown(countdown);
29
59
  if (branch === null) {
30
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: PANEL_WIDTH, children: [
31
- /* @__PURE__ */ jsx(Box, { marginTop: -1, children: /* @__PURE__ */ jsx(Text, { children: " Git " }) }),
32
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Not a git repository" })
60
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: PANEL_WIDTH, children: [
61
+ /* @__PURE__ */ jsx(Text, { children: createTitleLine("Git", countdownSuffix) }),
62
+ /* @__PURE__ */ jsxs(Text, { children: [
63
+ BOX.v,
64
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: padLine(" Not a git repository") }),
65
+ BOX.v
66
+ ] }),
67
+ /* @__PURE__ */ jsx(Text, { children: createBottomLine() })
33
68
  ] });
34
69
  }
35
70
  const displayCommits = commits.slice(0, MAX_COMMITS);
@@ -37,9 +72,19 @@ function GitPanel({ branch, commits, stats, uncommitted = 0 }) {
37
72
  const commitWord = commits.length === 1 ? "commit" : "commits";
38
73
  const fileWord = stats.files === 1 ? "file" : "files";
39
74
  const hasUncommitted = uncommitted > 0;
40
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: PANEL_WIDTH, children: [
41
- /* @__PURE__ */ jsx(Box, { marginTop: -1, children: /* @__PURE__ */ jsx(Text, { children: " Git " }) }),
75
+ let branchLineLength = 1 + branch.length;
76
+ if (hasCommits) {
77
+ branchLineLength += ` \xB7 +${stats.added} -${stats.deleted} \xB7 ${commits.length} ${commitWord} \xB7 ${stats.files} ${fileWord}`.length;
78
+ }
79
+ if (hasUncommitted) {
80
+ branchLineLength += ` \xB7 ${uncommitted} dirty`.length;
81
+ }
82
+ const branchPadding = Math.max(0, INNER_WIDTH - branchLineLength);
83
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: PANEL_WIDTH, children: [
84
+ /* @__PURE__ */ jsx(Text, { children: createTitleLine("Git", countdownSuffix) }),
42
85
  /* @__PURE__ */ jsxs(Text, { children: [
86
+ BOX.v,
87
+ " ",
43
88
  /* @__PURE__ */ jsx(Text, { color: "green", children: branch }),
44
89
  hasCommits && /* @__PURE__ */ jsxs(Fragment, { children: [
45
90
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
@@ -69,21 +114,35 @@ function GitPanel({ branch, commits, stats, uncommitted = 0 }) {
69
114
  uncommitted,
70
115
  " dirty"
71
116
  ] })
72
- ] })
117
+ ] }),
118
+ " ".repeat(branchPadding),
119
+ BOX.v
73
120
  ] }),
74
- hasCommits ? /* @__PURE__ */ jsx(Fragment, { children: displayCommits.map((commit) => /* @__PURE__ */ jsxs(Text, { children: [
75
- "\u2022 ",
76
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: commit.hash.slice(0, 7) }),
77
- " ",
78
- truncate(commit.message, MAX_MESSAGE_LENGTH)
79
- ] }, commit.hash)) }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No commits today" })
121
+ hasCommits ? /* @__PURE__ */ jsx(Fragment, { children: displayCommits.map((commit) => {
122
+ const msg = truncate(commit.message, MAX_MESSAGE_LENGTH);
123
+ const lineLength = 3 + 7 + 1 + msg.length;
124
+ const commitPadding = Math.max(0, INNER_WIDTH - lineLength);
125
+ return /* @__PURE__ */ jsxs(Text, { children: [
126
+ BOX.v,
127
+ " \u2022 ",
128
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: commit.hash.slice(0, 7) }),
129
+ " ",
130
+ msg,
131
+ " ".repeat(commitPadding),
132
+ BOX.v
133
+ ] }, commit.hash);
134
+ }) }) : /* @__PURE__ */ jsxs(Text, { children: [
135
+ BOX.v,
136
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: padLine(" No commits today") }),
137
+ BOX.v
138
+ ] }),
139
+ /* @__PURE__ */ jsx(Text, { children: createBottomLine() })
80
140
  ] });
81
141
  }
82
142
 
83
143
  // src/ui/PlanPanel.tsx
84
144
  import { Box as Box2, Text as Text2 } from "ink";
85
145
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
86
- var BOX = { tl: "\u250C", tr: "\u2510", bl: "\u2514", br: "\u2518", h: "\u2500", v: "\u2502" };
87
146
  var PROGRESS_BAR_WIDTH = 10;
88
147
  var MAX_STEP_LENGTH = CONTENT_WIDTH - 2;
89
148
  var MAX_DECISION_LENGTH = CONTENT_WIDTH - 2;
@@ -93,37 +152,37 @@ function createProgressBar(done, total) {
93
152
  const empty = PROGRESS_BAR_WIDTH - filled;
94
153
  return "\u2588".repeat(filled) + "\u2591".repeat(empty);
95
154
  }
96
- var INNER_WIDTH = PANEL_WIDTH - 2;
97
- function createTitleLine(done, total) {
155
+ function formatCountdown2(seconds) {
156
+ if (seconds == null) return "";
157
+ return `\u21BB ${seconds}s`;
158
+ }
159
+ function createPlanTitleLine(done, total, countdown) {
98
160
  const label = " Plan ";
99
161
  const count = ` ${done}/${total} `;
100
162
  const bar = createProgressBar(done, total);
101
- const dashCount = PANEL_WIDTH - 3 - label.length - count.length - bar.length;
163
+ const countdownStr = formatCountdown2(countdown);
164
+ const suffix = countdownStr ? ` \xB7 ${countdownStr} ` + BOX.h : "";
165
+ const dashCount = PANEL_WIDTH - 3 - label.length - count.length - bar.length - suffix.length;
102
166
  const dashes = BOX.h.repeat(Math.max(0, dashCount));
103
- return BOX.tl + BOX.h + label + dashes + count + bar + BOX.tr;
104
- }
105
- function createBottomLine() {
106
- return BOX.bl + BOX.h.repeat(INNER_WIDTH) + BOX.br;
167
+ return BOX.tl + BOX.h + label + dashes + count + bar + suffix + BOX.tr;
107
168
  }
108
- function padLine(content) {
109
- const padding = INNER_WIDTH - content.length;
110
- return content + " ".repeat(Math.max(0, padding));
169
+ function createSimpleTitleLine(countdown) {
170
+ const label = " Plan ";
171
+ const countdownStr = formatCountdown2(countdown);
172
+ const suffix = countdownStr ? ` ${countdownStr} ` + BOX.h : "";
173
+ const dashCount = PANEL_WIDTH - 3 - label.length - suffix.length;
174
+ const dashes = BOX.h.repeat(Math.max(0, dashCount));
175
+ return BOX.tl + BOX.h + label + dashes + suffix + BOX.tr;
111
176
  }
112
177
  function createDecisionsHeader() {
113
178
  const label = "\u2500 Decisions ";
114
179
  const dashCount = PANEL_WIDTH - 1 - label.length - 1;
115
180
  return label + "\u2500".repeat(dashCount) + "\u2524";
116
181
  }
117
- function PlanPanel({ plan, decisions, error }) {
182
+ function PlanPanel({ plan, decisions, error, countdown }) {
118
183
  if (error || !plan || !plan.goal || !plan.steps) {
119
184
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: PANEL_WIDTH, children: [
120
- /* @__PURE__ */ jsxs2(Text2, { children: [
121
- BOX.tl,
122
- BOX.h,
123
- " Plan ",
124
- BOX.h.repeat(INNER_WIDTH - 7),
125
- BOX.tr
126
- ] }),
185
+ /* @__PURE__ */ jsx2(Text2, { children: createSimpleTitleLine(countdown) }),
127
186
  /* @__PURE__ */ jsxs2(Text2, { children: [
128
187
  BOX.v,
129
188
  padLine(" " + (error || "No plan found")),
@@ -135,7 +194,7 @@ function PlanPanel({ plan, decisions, error }) {
135
194
  const doneCount = plan.steps.filter((s) => s.status === "done").length;
136
195
  const totalCount = plan.steps.length;
137
196
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: PANEL_WIDTH, children: [
138
- /* @__PURE__ */ jsx2(Text2, { children: createTitleLine(doneCount, totalCount) }),
197
+ /* @__PURE__ */ jsx2(Text2, { children: createPlanTitleLine(doneCount, totalCount, countdown) }),
139
198
  /* @__PURE__ */ jsxs2(Text2, { children: [
140
199
  BOX.v,
141
200
  padLine(" " + truncate(plan.goal, CONTENT_WIDTH)),
@@ -182,6 +241,9 @@ function formatRelativeTime(timestamp) {
182
241
  if (diffHours < 24) return `${diffHours}h ago`;
183
242
  return `${diffDays}d ago`;
184
243
  }
244
+ function createSeparator() {
245
+ return BOX.ml + BOX.h.repeat(INNER_WIDTH) + BOX.mr;
246
+ }
185
247
  function TestPanel({
186
248
  results,
187
249
  isOutdated,
@@ -189,23 +251,37 @@ function TestPanel({
189
251
  error
190
252
  }) {
191
253
  if (error || !results) {
192
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: PANEL_WIDTH, children: [
193
- /* @__PURE__ */ jsx3(Box3, { marginTop: -1, children: /* @__PURE__ */ jsx3(Text3, { children: " Tests " }) }),
194
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: error || "No test results" })
254
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width: PANEL_WIDTH, children: [
255
+ /* @__PURE__ */ jsx3(Text3, { children: createTitleLine("Tests", "") }),
256
+ /* @__PURE__ */ jsxs3(Text3, { children: [
257
+ BOX.v,
258
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: padLine(" " + (error || "No test results")) }),
259
+ BOX.v
260
+ ] }),
261
+ /* @__PURE__ */ jsx3(Text3, { children: createBottomLine() })
195
262
  ] });
196
263
  }
197
264
  const hasFailures = results.failures.length > 0;
198
265
  const relativeTime = formatRelativeTime(results.timestamp);
199
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: PANEL_WIDTH, children: [
200
- /* @__PURE__ */ jsx3(Box3, { marginTop: -1, children: /* @__PURE__ */ jsx3(Text3, { children: " Tests " }) }),
201
- isOutdated && /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
202
- "\u26A0 Outdated (",
203
- commitsBehind,
204
- " ",
205
- commitsBehind === 1 ? "commit" : "commits",
206
- " behind)"
266
+ let summaryLength = 1 + 2 + String(results.passed).length + " passed".length;
267
+ if (results.failed > 0) {
268
+ summaryLength += 2 + 2 + String(results.failed).length + " failed".length;
269
+ }
270
+ if (results.skipped > 0) {
271
+ summaryLength += 2 + 2 + String(results.skipped).length + " skipped".length;
272
+ }
273
+ summaryLength += " \xB7 ".length + results.hash.length;
274
+ const summaryPadding = Math.max(0, INNER_WIDTH - summaryLength);
275
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width: PANEL_WIDTH, children: [
276
+ /* @__PURE__ */ jsx3(Text3, { children: createTitleLine("Tests", relativeTime) }),
277
+ isOutdated && /* @__PURE__ */ jsxs3(Text3, { children: [
278
+ BOX.v,
279
+ /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: padLine(` \u26A0 Outdated (${commitsBehind} ${commitsBehind === 1 ? "commit" : "commits"} behind)`) }),
280
+ BOX.v
207
281
  ] }),
208
282
  /* @__PURE__ */ jsxs3(Text3, { children: [
283
+ BOX.v,
284
+ " ",
209
285
  /* @__PURE__ */ jsxs3(Text3, { color: "green", children: [
210
286
  "\u2713 ",
211
287
  results.passed,
@@ -228,27 +304,42 @@ function TestPanel({
228
304
  ] })
229
305
  ] }),
230
306
  /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
231
- " ",
232
- "\xB7 ",
233
- results.hash,
234
307
  " \xB7 ",
235
- relativeTime
236
- ] })
308
+ results.hash
309
+ ] }),
310
+ " ".repeat(summaryPadding),
311
+ BOX.v
237
312
  ] }),
238
313
  hasFailures && /* @__PURE__ */ jsxs3(Fragment3, { children: [
239
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: SEPARATOR }),
240
- results.failures.map((failure, index) => /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
241
- /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
242
- "\u2717 ",
243
- truncate(failure.file, CONTENT_WIDTH - 2)
244
- ] }),
245
- /* @__PURE__ */ jsxs3(Text3, { children: [
246
- " ",
247
- "\u2022 ",
248
- truncate(failure.name, CONTENT_WIDTH - 4)
249
- ] })
250
- ] }, index))
251
- ] })
314
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: createSeparator() }),
315
+ results.failures.map((failure, index) => {
316
+ const fileName = truncate(failure.file, CONTENT_WIDTH - 3);
317
+ const filePadding = Math.max(0, INNER_WIDTH - 3 - fileName.length);
318
+ const testName = truncate(failure.name, CONTENT_WIDTH - 5);
319
+ const testPadding = Math.max(0, INNER_WIDTH - 5 - testName.length);
320
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
321
+ /* @__PURE__ */ jsxs3(Text3, { children: [
322
+ BOX.v,
323
+ " ",
324
+ /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
325
+ "\u2717 ",
326
+ fileName
327
+ ] }),
328
+ " ".repeat(filePadding),
329
+ BOX.v
330
+ ] }),
331
+ /* @__PURE__ */ jsxs3(Text3, { children: [
332
+ BOX.v,
333
+ " ",
334
+ "\u2022 ",
335
+ testName,
336
+ " ".repeat(testPadding),
337
+ BOX.v
338
+ ] })
339
+ ] }, index);
340
+ })
341
+ ] }),
342
+ /* @__PURE__ */ jsx3(Text3, { children: createBottomLine() })
252
343
  ] });
253
344
  }
254
345
 
@@ -259,7 +350,7 @@ function WelcomePanel() {
259
350
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: PANEL_WIDTH, children: [
260
351
  /* @__PURE__ */ jsx4(Box4, { marginTop: -1, children: /* @__PURE__ */ jsx4(Text4, { children: " Welcome to agenthud " }) }),
261
352
  /* @__PURE__ */ jsx4(Text4, { children: " " }),
262
- /* @__PURE__ */ jsx4(Text4, { children: " No .agent/ directory found." }),
353
+ /* @__PURE__ */ jsx4(Text4, { children: " No .agenthud/ directory found." }),
263
354
  /* @__PURE__ */ jsx4(Text4, { children: " " }),
264
355
  /* @__PURE__ */ jsx4(Text4, { children: " Quick setup:" }),
265
356
  /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: " npx agenthud init" }),
@@ -273,23 +364,40 @@ function WelcomePanel() {
273
364
  // src/data/git.ts
274
365
  import { execSync as nodeExecSync } from "child_process";
275
366
  var execFn = (command, options2) => nodeExecSync(command, options2);
276
- function getCurrentBranch() {
367
+ function getUncommittedCount() {
277
368
  try {
278
- const result = execFn("git branch --show-current", {
369
+ const result = execFn("git status --porcelain", {
279
370
  encoding: "utf-8"
280
371
  });
281
- return result.trim();
372
+ const lines = result.trim().split("\n").filter(Boolean);
373
+ return lines.length;
282
374
  } catch {
283
- return null;
375
+ return 0;
284
376
  }
285
377
  }
286
- function getTodayCommits() {
378
+ var DEFAULT_COMMANDS = {
379
+ branch: "git branch --show-current",
380
+ commits: 'git log --since=midnight --format="%h|%aI|%s"',
381
+ stats: 'git log --since=midnight --numstat --format=""'
382
+ };
383
+ function getGitData(config) {
384
+ const commands = {
385
+ branch: config.command?.branch || DEFAULT_COMMANDS.branch,
386
+ commits: config.command?.commits || DEFAULT_COMMANDS.commits,
387
+ stats: config.command?.stats || DEFAULT_COMMANDS.stats
388
+ };
389
+ let branch = null;
287
390
  try {
288
- const result = execFn('git log --since=midnight --format="%h|%aI|%s"', {
289
- encoding: "utf-8"
290
- });
391
+ const result = execFn(commands.branch, { encoding: "utf-8" });
392
+ branch = result.trim();
393
+ } catch {
394
+ branch = null;
395
+ }
396
+ let commits = [];
397
+ try {
398
+ const result = execFn(commands.commits, { encoding: "utf-8" });
291
399
  const lines = result.trim().split("\n").filter(Boolean);
292
- return lines.map((line) => {
400
+ commits = lines.map((line) => {
293
401
  const [hash, timestamp, ...messageParts] = line.split("|");
294
402
  return {
295
403
  hash,
@@ -298,14 +406,11 @@ function getTodayCommits() {
298
406
  };
299
407
  });
300
408
  } catch {
301
- return [];
409
+ commits = [];
302
410
  }
303
- }
304
- function getTodayStats() {
411
+ let stats = { added: 0, deleted: 0, files: 0 };
305
412
  try {
306
- const result = execFn('git log --since=midnight --numstat --format=""', {
307
- encoding: "utf-8"
308
- });
413
+ const result = execFn(commands.stats, { encoding: "utf-8" });
309
414
  const lines = result.trim().split("\n").filter(Boolean);
310
415
  let added = 0;
311
416
  let deleted = 0;
@@ -320,34 +425,23 @@ function getTodayStats() {
320
425
  deleted += parseInt(deletedStr, 10) || 0;
321
426
  if (filename) filesSet.add(filename);
322
427
  }
323
- return { added, deleted, files: filesSet.size };
324
- } catch {
325
- return { added: 0, deleted: 0, files: 0 };
326
- }
327
- }
328
- function getUncommittedCount() {
329
- try {
330
- const result = execFn("git status --porcelain", {
331
- encoding: "utf-8"
332
- });
333
- const lines = result.trim().split("\n").filter(Boolean);
334
- return lines.length;
428
+ stats = { added, deleted, files: filesSet.size };
335
429
  } catch {
336
- return 0;
430
+ stats = { added: 0, deleted: 0, files: 0 };
337
431
  }
432
+ const uncommitted = getUncommittedCount();
433
+ return { branch, commits, stats, uncommitted };
338
434
  }
339
435
 
340
436
  // src/data/plan.ts
341
437
  import { readFileSync as nodeReadFileSync } from "fs";
342
- import { join } from "path";
343
- var AGENT_DIR = ".agent";
344
- var PLAN_FILE = "plan.json";
345
- var DECISIONS_FILE = "decisions.json";
438
+ import { join, dirname } from "path";
346
439
  var MAX_DECISIONS = 3;
347
440
  var readFileFn = (path) => nodeReadFileSync(path, "utf-8");
348
- function getPlanData(dir = process.cwd()) {
349
- const planPath = join(dir, AGENT_DIR, PLAN_FILE);
350
- const decisionsPath = join(dir, AGENT_DIR, DECISIONS_FILE);
441
+ function getPlanDataWithConfig(config) {
442
+ const planPath = config.source;
443
+ const planDir = dirname(planPath);
444
+ const decisionsPath = join(planDir, "decisions.json");
351
445
  let plan = null;
352
446
  let decisions = [];
353
447
  let error;
@@ -376,7 +470,7 @@ function getPlanData(dir = process.cwd()) {
376
470
  import { readFileSync as nodeReadFileSync2 } from "fs";
377
471
  import { execSync as nodeExecSync2 } from "child_process";
378
472
  import { join as join2 } from "path";
379
- var AGENT_DIR2 = ".agent";
473
+ var AGENT_DIR = ".agenthud";
380
474
  var TEST_RESULTS_FILE = "test-results.json";
381
475
  var readFileFn2 = (path) => nodeReadFileSync2(path, "utf-8");
382
476
  var getHeadHashFn = () => {
@@ -389,7 +483,7 @@ var getCommitCountFn = (fromHash) => {
389
483
  return parseInt(result, 10) || 0;
390
484
  };
391
485
  function getTestData(dir = process.cwd()) {
392
- const testResultsPath = join2(dir, AGENT_DIR2, TEST_RESULTS_FILE);
486
+ const testResultsPath = join2(dir, AGENT_DIR, TEST_RESULTS_FILE);
393
487
  let results = null;
394
488
  let isOutdated = false;
395
489
  let commitsBehind = 0;
@@ -418,65 +512,320 @@ function getTestData(dir = process.cwd()) {
418
512
  return { results, isOutdated, commitsBehind, error };
419
513
  }
420
514
 
515
+ // src/runner/command.ts
516
+ import { execSync as nodeExecSync3 } from "child_process";
517
+ var execFn2 = (command, options2) => nodeExecSync3(command, options2);
518
+ function parseVitestOutput(output) {
519
+ try {
520
+ const data = JSON.parse(output);
521
+ if (typeof data.numPassedTests !== "number" || typeof data.numFailedTests !== "number") {
522
+ return null;
523
+ }
524
+ const failures = [];
525
+ for (const testResult of data.testResults || []) {
526
+ for (const assertion of testResult.assertionResults || []) {
527
+ if (assertion.status === "failed") {
528
+ failures.push({
529
+ file: testResult.name,
530
+ name: assertion.title
531
+ });
532
+ }
533
+ }
534
+ }
535
+ return {
536
+ passed: data.numPassedTests,
537
+ failed: data.numFailedTests,
538
+ skipped: data.numPendingTests || 0,
539
+ failures
540
+ };
541
+ } catch {
542
+ return null;
543
+ }
544
+ }
545
+ function getHeadHash() {
546
+ try {
547
+ return execFn2("git rev-parse --short HEAD", {
548
+ encoding: "utf-8",
549
+ stdio: ["pipe", "pipe", "pipe"]
550
+ }).trim();
551
+ } catch {
552
+ return "unknown";
553
+ }
554
+ }
555
+ function runTestCommand(command) {
556
+ let output;
557
+ try {
558
+ output = execFn2(command, {
559
+ encoding: "utf-8",
560
+ stdio: ["pipe", "pipe", "pipe"]
561
+ });
562
+ } catch (error) {
563
+ const message = error instanceof Error ? error.message : String(error);
564
+ return {
565
+ results: null,
566
+ isOutdated: false,
567
+ commitsBehind: 0,
568
+ error: message
569
+ };
570
+ }
571
+ const parsed = parseVitestOutput(output);
572
+ if (!parsed) {
573
+ return {
574
+ results: null,
575
+ isOutdated: false,
576
+ commitsBehind: 0,
577
+ error: "Failed to parse test output"
578
+ };
579
+ }
580
+ const hash = getHeadHash();
581
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
582
+ const results = {
583
+ hash,
584
+ timestamp,
585
+ passed: parsed.passed,
586
+ failed: parsed.failed,
587
+ skipped: parsed.skipped,
588
+ failures: parsed.failures
589
+ };
590
+ return {
591
+ results,
592
+ isOutdated: false,
593
+ commitsBehind: 0
594
+ };
595
+ }
596
+
597
+ // src/config/parser.ts
598
+ import {
599
+ existsSync as nodeExistsSync,
600
+ readFileSync as nodeReadFileSync3
601
+ } from "fs";
602
+ import { parse as parseYaml } from "yaml";
603
+ var fs = {
604
+ existsSync: nodeExistsSync,
605
+ readFileSync: (path) => nodeReadFileSync3(path, "utf-8")
606
+ };
607
+ var CONFIG_PATH = ".agenthud/config.yaml";
608
+ function parseInterval(interval) {
609
+ if (!interval || interval === "manual") {
610
+ return null;
611
+ }
612
+ const match = interval.match(/^(\d+)(s|m)$/);
613
+ if (!match) {
614
+ return null;
615
+ }
616
+ const value = parseInt(match[1], 10);
617
+ const unit = match[2];
618
+ if (unit === "s") {
619
+ return value * 1e3;
620
+ } else if (unit === "m") {
621
+ return value * 60 * 1e3;
622
+ }
623
+ return null;
624
+ }
625
+ function getDefaultConfig() {
626
+ return {
627
+ panels: {
628
+ git: {
629
+ enabled: true,
630
+ interval: 3e4
631
+ // 30s
632
+ },
633
+ plan: {
634
+ enabled: true,
635
+ interval: 1e4,
636
+ // 10s
637
+ source: ".agenthud/plan.json"
638
+ },
639
+ tests: {
640
+ enabled: true,
641
+ interval: null
642
+ // manual
643
+ }
644
+ }
645
+ };
646
+ }
647
+ var VALID_PANELS = ["git", "plan", "tests"];
648
+ function parseConfig() {
649
+ const warnings = [];
650
+ const defaultConfig = getDefaultConfig();
651
+ if (!fs.existsSync(CONFIG_PATH)) {
652
+ return { config: defaultConfig, warnings };
653
+ }
654
+ let rawConfig;
655
+ try {
656
+ const content = fs.readFileSync(CONFIG_PATH);
657
+ rawConfig = parseYaml(content);
658
+ } catch (error) {
659
+ const message = error instanceof Error ? error.message : String(error);
660
+ warnings.push(`Failed to parse config: ${message}`);
661
+ return { config: defaultConfig, warnings };
662
+ }
663
+ if (!rawConfig || typeof rawConfig !== "object") {
664
+ return { config: defaultConfig, warnings };
665
+ }
666
+ const parsed = rawConfig;
667
+ const panels = parsed.panels;
668
+ if (!panels || typeof panels !== "object") {
669
+ return { config: defaultConfig, warnings };
670
+ }
671
+ const config = getDefaultConfig();
672
+ for (const panelName of Object.keys(panels)) {
673
+ if (!VALID_PANELS.includes(panelName)) {
674
+ warnings.push(`Unknown panel '${panelName}' in config`);
675
+ continue;
676
+ }
677
+ const panelConfig = panels[panelName];
678
+ if (!panelConfig || typeof panelConfig !== "object") {
679
+ continue;
680
+ }
681
+ if (panelName === "git") {
682
+ if (typeof panelConfig.enabled === "boolean") {
683
+ config.panels.git.enabled = panelConfig.enabled;
684
+ }
685
+ if (typeof panelConfig.interval === "string") {
686
+ const interval = parseInterval(panelConfig.interval);
687
+ if (interval === null && panelConfig.interval !== "manual") {
688
+ warnings.push(`Invalid interval '${panelConfig.interval}' for git panel, using default`);
689
+ } else {
690
+ config.panels.git.interval = interval;
691
+ }
692
+ }
693
+ }
694
+ if (panelName === "plan") {
695
+ if (typeof panelConfig.enabled === "boolean") {
696
+ config.panels.plan.enabled = panelConfig.enabled;
697
+ }
698
+ if (typeof panelConfig.interval === "string") {
699
+ const interval = parseInterval(panelConfig.interval);
700
+ if (interval === null && panelConfig.interval !== "manual") {
701
+ warnings.push(`Invalid interval '${panelConfig.interval}' for plan panel, using default`);
702
+ } else {
703
+ config.panels.plan.interval = interval;
704
+ }
705
+ }
706
+ if (typeof panelConfig.source === "string") {
707
+ config.panels.plan.source = panelConfig.source;
708
+ }
709
+ }
710
+ if (panelName === "tests") {
711
+ if (typeof panelConfig.enabled === "boolean") {
712
+ config.panels.tests.enabled = panelConfig.enabled;
713
+ }
714
+ if (typeof panelConfig.interval === "string") {
715
+ const interval = parseInterval(panelConfig.interval);
716
+ if (interval === null && panelConfig.interval !== "manual") {
717
+ warnings.push(`Invalid interval '${panelConfig.interval}' for tests panel, using default`);
718
+ } else {
719
+ config.panels.tests.interval = interval;
720
+ }
721
+ }
722
+ if (typeof panelConfig.command === "string") {
723
+ config.panels.tests.command = panelConfig.command;
724
+ }
725
+ }
726
+ }
727
+ return { config, warnings };
728
+ }
729
+
421
730
  // src/ui/App.tsx
422
731
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
423
- var REFRESH_INTERVAL = 5e3;
424
- var REFRESH_SECONDS = REFRESH_INTERVAL / 1e3;
425
- function useGitData() {
426
- const [data, setData] = useState(() => ({
427
- branch: getCurrentBranch(),
428
- commits: getTodayCommits(),
429
- stats: getTodayStats(),
430
- uncommitted: getUncommittedCount()
431
- }));
432
- const refresh = useCallback(() => {
433
- setData({
434
- branch: getCurrentBranch(),
435
- commits: getTodayCommits(),
436
- stats: getTodayStats(),
437
- uncommitted: getUncommittedCount()
438
- });
439
- }, []);
440
- return [data, refresh];
441
- }
442
- function usePlanData() {
443
- const [data, setData] = useState(() => getPlanData());
444
- const refresh = useCallback(() => {
445
- setData(getPlanData());
446
- }, []);
447
- return [data, refresh];
448
- }
449
- function useTestData() {
450
- const [data, setData] = useState(() => getTestData());
451
- const refresh = useCallback(() => {
452
- setData(getTestData());
453
- }, []);
454
- return [data, refresh];
732
+ function generateHotkeys(config, actions) {
733
+ const hotkeys = [];
734
+ const usedKeys = /* @__PURE__ */ new Set();
735
+ if (config.panels.tests.enabled && config.panels.tests.interval === null && actions.tests) {
736
+ const name = "tests";
737
+ for (const char of name.toLowerCase()) {
738
+ if (!usedKeys.has(char)) {
739
+ usedKeys.add(char);
740
+ hotkeys.push({
741
+ key: char,
742
+ label: "run tests",
743
+ action: actions.tests
744
+ });
745
+ break;
746
+ }
747
+ }
748
+ }
749
+ return hotkeys;
455
750
  }
456
751
  function WelcomeApp() {
457
752
  return /* @__PURE__ */ jsx5(WelcomePanel, {});
458
753
  }
459
754
  function DashboardApp({ mode }) {
460
755
  const { exit } = useApp();
461
- const [gitData, refreshGit] = useGitData();
462
- const [planData, refreshPlan] = usePlanData();
463
- const [testData, refreshTest] = useTestData();
464
- const [countdown, setCountdown] = useState(REFRESH_SECONDS);
756
+ const { config, warnings } = useMemo(() => parseConfig(), []);
757
+ const gitIntervalSeconds = config.panels.git.interval ? config.panels.git.interval / 1e3 : null;
758
+ const planIntervalSeconds = config.panels.plan.interval ? config.panels.plan.interval / 1e3 : null;
759
+ const [gitData, setGitData] = useState(() => getGitData(config.panels.git));
760
+ const refreshGit = useCallback(() => {
761
+ setGitData(getGitData(config.panels.git));
762
+ }, [config.panels.git]);
763
+ const [planData, setPlanData] = useState(() => getPlanDataWithConfig(config.panels.plan));
764
+ const refreshPlan = useCallback(() => {
765
+ setPlanData(getPlanDataWithConfig(config.panels.plan));
766
+ }, [config.panels.plan]);
767
+ const getTestDataFromConfig = useCallback(() => {
768
+ if (config.panels.tests.command) {
769
+ return runTestCommand(config.panels.tests.command);
770
+ }
771
+ return getTestData();
772
+ }, [config.panels.tests.command]);
773
+ const [testData, setTestData] = useState(() => getTestDataFromConfig());
774
+ const refreshTest = useCallback(() => {
775
+ setTestData(getTestDataFromConfig());
776
+ }, [getTestDataFromConfig]);
777
+ const [countdowns, setCountdowns] = useState({
778
+ git: gitIntervalSeconds,
779
+ plan: planIntervalSeconds
780
+ });
465
781
  const refreshAll = useCallback(() => {
466
- refreshGit();
467
- refreshPlan();
468
- refreshTest();
469
- setCountdown(REFRESH_SECONDS);
470
- }, [refreshGit, refreshPlan, refreshTest]);
782
+ if (config.panels.git.enabled) {
783
+ refreshGit();
784
+ setCountdowns((prev) => ({ ...prev, git: gitIntervalSeconds }));
785
+ }
786
+ if (config.panels.plan.enabled) {
787
+ refreshPlan();
788
+ setCountdowns((prev) => ({ ...prev, plan: planIntervalSeconds }));
789
+ }
790
+ if (config.panels.tests.enabled) {
791
+ refreshTest();
792
+ }
793
+ }, [refreshGit, refreshPlan, refreshTest, config, gitIntervalSeconds, planIntervalSeconds]);
794
+ const hotkeys = useMemo(
795
+ () => generateHotkeys(config, { tests: refreshTest }),
796
+ [config, refreshTest]
797
+ );
471
798
  useEffect(() => {
472
799
  if (mode !== "watch") return;
473
- const interval = setInterval(refreshAll, REFRESH_INTERVAL);
474
- return () => clearInterval(interval);
475
- }, [mode, refreshAll]);
800
+ const timers = [];
801
+ if (config.panels.git.enabled && config.panels.git.interval !== null) {
802
+ timers.push(
803
+ setInterval(() => {
804
+ refreshGit();
805
+ setCountdowns((prev) => ({ ...prev, git: gitIntervalSeconds }));
806
+ }, config.panels.git.interval)
807
+ );
808
+ }
809
+ if (config.panels.plan.enabled && config.panels.plan.interval !== null) {
810
+ timers.push(
811
+ setInterval(() => {
812
+ refreshPlan();
813
+ setCountdowns((prev) => ({ ...prev, plan: planIntervalSeconds }));
814
+ }, config.panels.plan.interval)
815
+ );
816
+ }
817
+ if (config.panels.tests.enabled && config.panels.tests.interval !== null) {
818
+ timers.push(setInterval(refreshTest, config.panels.tests.interval));
819
+ }
820
+ return () => timers.forEach((t) => clearInterval(t));
821
+ }, [mode, config, refreshGit, refreshPlan, refreshTest, gitIntervalSeconds, planIntervalSeconds]);
476
822
  useEffect(() => {
477
823
  if (mode !== "watch") return;
478
824
  const tick = setInterval(() => {
479
- setCountdown((prev) => prev > 1 ? prev - 1 : REFRESH_SECONDS);
825
+ setCountdowns((prev) => ({
826
+ git: prev.git !== null && prev.git > 1 ? prev.git - 1 : prev.git,
827
+ plan: prev.plan !== null && prev.plan > 1 ? prev.plan - 1 : prev.plan
828
+ }));
480
829
  }, 1e3);
481
830
  return () => clearInterval(tick);
482
831
  }, [mode]);
@@ -488,28 +837,46 @@ function DashboardApp({ mode }) {
488
837
  if (input === "r") {
489
838
  refreshAll();
490
839
  }
840
+ for (const hotkey of hotkeys) {
841
+ if (input === hotkey.key) {
842
+ hotkey.action();
843
+ break;
844
+ }
845
+ }
491
846
  },
492
847
  { isActive: mode === "watch" }
493
848
  );
849
+ const statusBarItems = [];
850
+ for (const hotkey of hotkeys) {
851
+ statusBarItems.push(`${hotkey.key}: ${hotkey.label}`);
852
+ }
853
+ statusBarItems.push("r: refresh");
854
+ statusBarItems.push("q: quit");
494
855
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
495
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
856
+ warnings.length > 0 && /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
857
+ "\u26A0 ",
858
+ warnings.join(", ")
859
+ ] }) }),
860
+ config.panels.git.enabled && /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
496
861
  GitPanel,
497
862
  {
498
863
  branch: gitData.branch,
499
864
  commits: gitData.commits,
500
865
  stats: gitData.stats,
501
- uncommitted: gitData.uncommitted
866
+ uncommitted: gitData.uncommitted,
867
+ countdown: mode === "watch" ? countdowns.git : null
502
868
  }
503
869
  ) }),
504
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(
870
+ config.panels.plan.enabled && /* @__PURE__ */ jsx5(Box5, { marginTop: config.panels.git.enabled ? 1 : 0, children: /* @__PURE__ */ jsx5(
505
871
  PlanPanel,
506
872
  {
507
873
  plan: planData.plan,
508
874
  decisions: planData.decisions,
509
- error: planData.error
875
+ error: planData.error,
876
+ countdown: mode === "watch" ? countdowns.plan : null
510
877
  }
511
878
  ) }),
512
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(
879
+ config.panels.tests.enabled && /* @__PURE__ */ jsx5(Box5, { marginTop: config.panels.git.enabled || config.panels.plan.enabled ? 1 : 0, children: /* @__PURE__ */ jsx5(
513
880
  TestPanel,
514
881
  {
515
882
  results: testData.results,
@@ -518,15 +885,14 @@ function DashboardApp({ mode }) {
518
885
  error: testData.error
519
886
  }
520
887
  ) }),
521
- mode === "watch" && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, width: PANEL_WIDTH, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
522
- "\u21BB ",
523
- countdown,
524
- "s \xB7 ",
525
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "q:" }),
526
- " quit \xB7 ",
527
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "r:" }),
528
- " refresh"
529
- ] }) })
888
+ mode === "watch" && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, width: PANEL_WIDTH, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: statusBarItems.map((item, index) => /* @__PURE__ */ jsxs5(React.Fragment, { children: [
889
+ index > 0 && " \xB7 ",
890
+ /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
891
+ item.split(":")[0],
892
+ ":"
893
+ ] }),
894
+ item.split(":").slice(1).join(":")
895
+ ] }, index)) }) })
530
896
  ] });
531
897
  }
532
898
  function App({ mode, agentDirExists: agentDirExists2 = true }) {
@@ -553,55 +919,93 @@ function parseArgs(args) {
553
919
 
554
920
  // src/commands/init.ts
555
921
  import {
556
- existsSync as nodeExistsSync,
922
+ existsSync as nodeExistsSync2,
557
923
  mkdirSync as nodeMkdirSync,
558
924
  writeFileSync as nodeWriteFileSync,
559
- readFileSync as nodeReadFileSync3,
925
+ readFileSync as nodeReadFileSync4,
560
926
  appendFileSync as nodeAppendFileSync
561
927
  } from "fs";
562
- var fs = {
563
- existsSync: nodeExistsSync,
928
+ var fs2 = {
929
+ existsSync: nodeExistsSync2,
564
930
  mkdirSync: nodeMkdirSync,
565
931
  writeFileSync: nodeWriteFileSync,
566
- readFileSync: (path) => nodeReadFileSync3(path, "utf-8"),
932
+ readFileSync: (path) => nodeReadFileSync4(path, "utf-8"),
567
933
  appendFileSync: nodeAppendFileSync
568
934
  };
569
935
  var AGENT_STATE_SECTION = `## Agent State
570
936
 
571
- Maintain \`.agent/\` directory:
937
+ Maintain \`.agenthud/\` directory:
572
938
  - Update \`plan.json\` when plan changes
573
939
  - Append to \`decisions.json\` for key decisions
574
940
  `;
941
+ var DEFAULT_CONFIG = `# agenthud configuration
942
+ panels:
943
+ git:
944
+ enabled: true
945
+ interval: 30s
946
+ command:
947
+ branch: git branch --show-current
948
+ commits: git log --since=midnight --pretty=format:"%h|%aI|%s"
949
+ stats: git log --since=midnight --numstat --pretty=format:""
950
+
951
+ plan:
952
+ enabled: true
953
+ interval: 10s
954
+ source: .agenthud/plan.json
955
+
956
+ tests:
957
+ enabled: true
958
+ interval: manual
959
+ command: npx vitest run --reporter=json
960
+ `;
575
961
  function runInit() {
576
962
  const result = {
577
963
  created: [],
578
964
  skipped: []
579
965
  };
580
- if (!fs.existsSync(".agent")) {
581
- fs.mkdirSync(".agent", { recursive: true });
582
- result.created.push(".agent/");
966
+ if (!fs2.existsSync(".agenthud")) {
967
+ fs2.mkdirSync(".agenthud", { recursive: true });
968
+ result.created.push(".agenthud/");
969
+ } else {
970
+ result.skipped.push(".agenthud/");
971
+ }
972
+ if (!fs2.existsSync(".agenthud/plan.json")) {
973
+ fs2.writeFileSync(".agenthud/plan.json", "{}\n");
974
+ result.created.push(".agenthud/plan.json");
975
+ } else {
976
+ result.skipped.push(".agenthud/plan.json");
977
+ }
978
+ if (!fs2.existsSync(".agenthud/decisions.json")) {
979
+ fs2.writeFileSync(".agenthud/decisions.json", "[]\n");
980
+ result.created.push(".agenthud/decisions.json");
583
981
  } else {
584
- result.skipped.push(".agent/");
982
+ result.skipped.push(".agenthud/decisions.json");
585
983
  }
586
- if (!fs.existsSync(".agent/plan.json")) {
587
- fs.writeFileSync(".agent/plan.json", "{}\n");
588
- result.created.push(".agent/plan.json");
984
+ if (!fs2.existsSync(".agenthud/config.yaml")) {
985
+ fs2.writeFileSync(".agenthud/config.yaml", DEFAULT_CONFIG);
986
+ result.created.push(".agenthud/config.yaml");
589
987
  } else {
590
- result.skipped.push(".agent/plan.json");
988
+ result.skipped.push(".agenthud/config.yaml");
591
989
  }
592
- if (!fs.existsSync(".agent/decisions.json")) {
593
- fs.writeFileSync(".agent/decisions.json", "[]\n");
594
- result.created.push(".agent/decisions.json");
990
+ if (!fs2.existsSync(".gitignore")) {
991
+ fs2.writeFileSync(".gitignore", ".agenthud/\n");
992
+ result.created.push(".gitignore");
595
993
  } else {
596
- result.skipped.push(".agent/decisions.json");
994
+ const content = fs2.readFileSync(".gitignore");
995
+ if (!content.includes(".agenthud/")) {
996
+ fs2.appendFileSync(".gitignore", "\n.agenthud/\n");
997
+ result.created.push(".gitignore");
998
+ } else {
999
+ result.skipped.push(".gitignore");
1000
+ }
597
1001
  }
598
- if (!fs.existsSync("CLAUDE.md")) {
599
- fs.writeFileSync("CLAUDE.md", AGENT_STATE_SECTION);
1002
+ if (!fs2.existsSync("CLAUDE.md")) {
1003
+ fs2.writeFileSync("CLAUDE.md", AGENT_STATE_SECTION);
600
1004
  result.created.push("CLAUDE.md");
601
1005
  } else {
602
- const content = fs.readFileSync("CLAUDE.md");
1006
+ const content = fs2.readFileSync("CLAUDE.md");
603
1007
  if (!content.includes("## Agent State")) {
604
- fs.appendFileSync("CLAUDE.md", "\n" + AGENT_STATE_SECTION);
1008
+ fs2.appendFileSync("CLAUDE.md", "\n" + AGENT_STATE_SECTION);
605
1009
  result.created.push("CLAUDE.md");
606
1010
  } else {
607
1011
  result.skipped.push("CLAUDE.md");
@@ -624,11 +1028,11 @@ if (options.command === "init") {
624
1028
  result.skipped.forEach((file) => console.log(` ${file}`));
625
1029
  }
626
1030
  console.log("\nNext steps:");
627
- console.log(" 1. Edit .agent/plan.json to add your project plan");
1031
+ console.log(" 1. Edit .agenthud/plan.json to add your project plan");
628
1032
  console.log(" 2. Run: npx agenthud\n");
629
1033
  process.exit(0);
630
1034
  }
631
- var agentDirExists = existsSync(".agent");
1035
+ var agentDirExists = existsSync(".agenthud");
632
1036
  if (options.mode === "watch") {
633
1037
  clearScreen();
634
1038
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenthud",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "CLI tool to monitor agent status in real-time. Works with Claude Code, multi-agent workflows, and any AI agent system.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -51,6 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "ink": "^6.6.0",
54
- "react": "^19.2.3"
54
+ "react": "^19.2.3",
55
+ "yaml": "^2.8.2"
55
56
  }
56
57
  }
@@ -7,7 +7,7 @@
7
7
  * npm run test:save
8
8
  *
9
9
  * Output:
10
- * .agent/test-results.json
10
+ * .agenthud/test-results.json
11
11
  */
12
12
 
13
13
  import { execSync } from "child_process";
@@ -106,8 +106,8 @@ function main(): void {
106
106
  failures: extractFailures(vitestResult),
107
107
  };
108
108
 
109
- // Ensure .agent directory exists
110
- const agentDir = join(process.cwd(), ".agent");
109
+ // Ensure .agenthud directory exists
110
+ const agentDir = join(process.cwd(), ".agenthud");
111
111
  if (!existsSync(agentDir)) {
112
112
  mkdirSync(agentDir, { recursive: true });
113
113
  }