agenthud 0.1.0 → 0.2.0

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.
Files changed (2) hide show
  1. package/dist/index.js +145 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,10 +3,11 @@
3
3
  // src/index.ts
4
4
  import React2 from "react";
5
5
  import { render } from "ink";
6
+ import { existsSync } from "fs";
6
7
 
7
8
  // src/ui/App.tsx
8
9
  import { useState, useEffect, useCallback } from "react";
9
- import { Box as Box4, Text as Text4, useApp, useInput } from "ink";
10
+ import { Box as Box5, Text as Text5, useApp, useInput } from "ink";
10
11
 
11
12
  // src/ui/GitPanel.tsx
12
13
  import { Box, Text } from "ink";
@@ -24,7 +25,7 @@ function truncate(text, maxLength) {
24
25
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
25
26
  var MAX_COMMITS = 5;
26
27
  var MAX_MESSAGE_LENGTH = CONTENT_WIDTH - 10;
27
- function GitPanel({ branch, commits, stats }) {
28
+ function GitPanel({ branch, commits, stats, uncommitted = 0 }) {
28
29
  if (branch === null) {
29
30
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: PANEL_WIDTH, children: [
30
31
  /* @__PURE__ */ jsx(Box, { marginTop: -1, children: /* @__PURE__ */ jsx(Text, { children: " Git " }) }),
@@ -35,6 +36,7 @@ function GitPanel({ branch, commits, stats }) {
35
36
  const hasCommits = commits.length > 0;
36
37
  const commitWord = commits.length === 1 ? "commit" : "commits";
37
38
  const fileWord = stats.files === 1 ? "file" : "files";
39
+ const hasUncommitted = uncommitted > 0;
38
40
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: PANEL_WIDTH, children: [
39
41
  /* @__PURE__ */ jsx(Box, { marginTop: -1, children: /* @__PURE__ */ jsx(Text, { children: " Git " }) }),
40
42
  /* @__PURE__ */ jsxs(Text, { children: [
@@ -58,7 +60,12 @@ function GitPanel({ branch, commits, stats }) {
58
60
  " \xB7 ",
59
61
  stats.files,
60
62
  " ",
61
- fileWord
63
+ fileWord,
64
+ hasUncommitted ? ` \xB7 ` : ""
65
+ ] }),
66
+ hasUncommitted && /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
67
+ uncommitted,
68
+ " dirty"
62
69
  ] })
63
70
  ] })
64
71
  ] }),
@@ -243,6 +250,24 @@ function TestPanel({
243
250
  ] });
244
251
  }
245
252
 
253
+ // src/ui/WelcomePanel.tsx
254
+ import { Box as Box4, Text as Text4 } from "ink";
255
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
256
+ function WelcomePanel() {
257
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: PANEL_WIDTH, children: [
258
+ /* @__PURE__ */ jsx4(Box4, { marginTop: -1, children: /* @__PURE__ */ jsx4(Text4, { children: " Welcome to agenthud " }) }),
259
+ /* @__PURE__ */ jsx4(Text4, { children: " " }),
260
+ /* @__PURE__ */ jsx4(Text4, { children: " No .agent/ directory found." }),
261
+ /* @__PURE__ */ jsx4(Text4, { children: " " }),
262
+ /* @__PURE__ */ jsx4(Text4, { children: " Quick setup:" }),
263
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: " npx agenthud init" }),
264
+ /* @__PURE__ */ jsx4(Text4, { children: " " }),
265
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " Or visit: github.com/neochoon/agenthud" }),
266
+ /* @__PURE__ */ jsx4(Text4, { children: " " }),
267
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " Press q to quit" })
268
+ ] });
269
+ }
270
+
246
271
  // src/data/git.ts
247
272
  import { execSync as nodeExecSync } from "child_process";
248
273
  var execFn = (command, options2) => nodeExecSync(command, options2);
@@ -298,6 +323,17 @@ function getTodayStats() {
298
323
  return { added: 0, deleted: 0, files: 0 };
299
324
  }
300
325
  }
326
+ function getUncommittedCount() {
327
+ try {
328
+ const result = execFn("git status --porcelain", {
329
+ encoding: "utf-8"
330
+ });
331
+ const lines = result.trim().split("\n").filter(Boolean);
332
+ return lines.length;
333
+ } catch {
334
+ return 0;
335
+ }
336
+ }
301
337
 
302
338
  // src/data/plan.ts
303
339
  import { readFileSync as nodeReadFileSync } from "fs";
@@ -381,20 +417,22 @@ function getTestData(dir = process.cwd()) {
381
417
  }
382
418
 
383
419
  // src/ui/App.tsx
384
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
420
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
385
421
  var REFRESH_INTERVAL = 5e3;
386
422
  var REFRESH_SECONDS = REFRESH_INTERVAL / 1e3;
387
423
  function useGitData() {
388
424
  const [data, setData] = useState(() => ({
389
425
  branch: getCurrentBranch(),
390
426
  commits: getTodayCommits(),
391
- stats: getTodayStats()
427
+ stats: getTodayStats(),
428
+ uncommitted: getUncommittedCount()
392
429
  }));
393
430
  const refresh = useCallback(() => {
394
431
  setData({
395
432
  branch: getCurrentBranch(),
396
433
  commits: getTodayCommits(),
397
- stats: getTodayStats()
434
+ stats: getTodayStats(),
435
+ uncommitted: getUncommittedCount()
398
436
  });
399
437
  }, []);
400
438
  return [data, refresh];
@@ -413,7 +451,10 @@ function useTestData() {
413
451
  }, []);
414
452
  return [data, refresh];
415
453
  }
416
- function App({ mode }) {
454
+ function WelcomeApp() {
455
+ return /* @__PURE__ */ jsx5(WelcomePanel, {});
456
+ }
457
+ function DashboardApp({ mode }) {
417
458
  const { exit } = useApp();
418
459
  const [gitData, refreshGit] = useGitData();
419
460
  const [planData, refreshPlan] = usePlanData();
@@ -439,7 +480,6 @@ function App({ mode }) {
439
480
  }, [mode]);
440
481
  useInput(
441
482
  (input) => {
442
- if (mode !== "watch") return;
443
483
  if (input === "q") {
444
484
  exit();
445
485
  }
@@ -449,16 +489,17 @@ function App({ mode }) {
449
489
  },
450
490
  { isActive: mode === "watch" }
451
491
  );
452
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
453
- /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(
492
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
493
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(
454
494
  GitPanel,
455
495
  {
456
496
  branch: gitData.branch,
457
497
  commits: gitData.commits,
458
- stats: gitData.stats
498
+ stats: gitData.stats,
499
+ uncommitted: gitData.uncommitted
459
500
  }
460
501
  ) }),
461
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(
502
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(
462
503
  PlanPanel,
463
504
  {
464
505
  plan: planData.plan,
@@ -466,7 +507,7 @@ function App({ mode }) {
466
507
  error: planData.error
467
508
  }
468
509
  ) }),
469
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(
510
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(
470
511
  TestPanel,
471
512
  {
472
513
  results: testData.results,
@@ -475,17 +516,23 @@ function App({ mode }) {
475
516
  error: testData.error
476
517
  }
477
518
  ) }),
478
- mode === "watch" && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, width: PANEL_WIDTH, children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
519
+ mode === "watch" && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, width: PANEL_WIDTH, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
479
520
  "\u21BB ",
480
521
  countdown,
481
522
  "s \xB7 ",
482
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "q:" }),
523
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "q:" }),
483
524
  " quit \xB7 ",
484
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "r:" }),
525
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "r:" }),
485
526
  " refresh"
486
527
  ] }) })
487
528
  ] });
488
529
  }
530
+ function App({ mode, agentDirExists: agentDirExists2 = true }) {
531
+ if (!agentDirExists2) {
532
+ return /* @__PURE__ */ jsx5(WelcomeApp, {});
533
+ }
534
+ return /* @__PURE__ */ jsx5(DashboardApp, { mode });
535
+ }
489
536
 
490
537
  // src/cli.ts
491
538
  var clearFn = () => console.clear();
@@ -495,18 +542,97 @@ function clearScreen() {
495
542
  function parseArgs(args) {
496
543
  const hasOnce = args.includes("--once");
497
544
  const hasWatch = args.includes("--watch") || args.includes("-w");
545
+ const command = args[0] === "init" ? "init" : void 0;
498
546
  if (hasOnce) {
499
- return { mode: "once" };
547
+ return { mode: "once", command };
548
+ }
549
+ return { mode: "watch", command };
550
+ }
551
+
552
+ // src/commands/init.ts
553
+ import {
554
+ existsSync as nodeExistsSync,
555
+ mkdirSync as nodeMkdirSync,
556
+ writeFileSync as nodeWriteFileSync,
557
+ readFileSync as nodeReadFileSync3,
558
+ appendFileSync as nodeAppendFileSync
559
+ } from "fs";
560
+ var fs = {
561
+ existsSync: nodeExistsSync,
562
+ mkdirSync: nodeMkdirSync,
563
+ writeFileSync: nodeWriteFileSync,
564
+ readFileSync: (path) => nodeReadFileSync3(path, "utf-8"),
565
+ appendFileSync: nodeAppendFileSync
566
+ };
567
+ var AGENT_STATE_SECTION = `## Agent State
568
+
569
+ Maintain \`.agent/\` directory:
570
+ - Update \`plan.json\` when plan changes
571
+ - Append to \`decisions.json\` for key decisions
572
+ `;
573
+ function runInit() {
574
+ const result = {
575
+ created: [],
576
+ skipped: []
577
+ };
578
+ if (!fs.existsSync(".agent")) {
579
+ fs.mkdirSync(".agent", { recursive: true });
580
+ result.created.push(".agent/");
581
+ } else {
582
+ result.skipped.push(".agent/");
583
+ }
584
+ if (!fs.existsSync(".agent/plan.json")) {
585
+ fs.writeFileSync(".agent/plan.json", "{}\n");
586
+ result.created.push(".agent/plan.json");
587
+ } else {
588
+ result.skipped.push(".agent/plan.json");
500
589
  }
501
- return { mode: "watch" };
590
+ if (!fs.existsSync(".agent/decisions.json")) {
591
+ fs.writeFileSync(".agent/decisions.json", "[]\n");
592
+ result.created.push(".agent/decisions.json");
593
+ } else {
594
+ result.skipped.push(".agent/decisions.json");
595
+ }
596
+ if (!fs.existsSync("CLAUDE.md")) {
597
+ fs.writeFileSync("CLAUDE.md", AGENT_STATE_SECTION);
598
+ result.created.push("CLAUDE.md");
599
+ } else {
600
+ const content = fs.readFileSync("CLAUDE.md");
601
+ if (!content.includes("## Agent State")) {
602
+ fs.appendFileSync("CLAUDE.md", "\n" + AGENT_STATE_SECTION);
603
+ result.created.push("CLAUDE.md");
604
+ } else {
605
+ result.skipped.push("CLAUDE.md");
606
+ }
607
+ }
608
+ return result;
502
609
  }
503
610
 
504
611
  // src/index.ts
505
612
  var options = parseArgs(process.argv.slice(2));
613
+ if (options.command === "init") {
614
+ const result = runInit();
615
+ console.log("\n\u2713 agenthud initialized\n");
616
+ if (result.created.length > 0) {
617
+ console.log("Created:");
618
+ result.created.forEach((file) => console.log(` ${file}`));
619
+ }
620
+ if (result.skipped.length > 0) {
621
+ console.log("\nSkipped (already exists):");
622
+ result.skipped.forEach((file) => console.log(` ${file}`));
623
+ }
624
+ console.log("\nNext steps:");
625
+ console.log(" 1. Edit .agent/plan.json to add your project plan");
626
+ console.log(" 2. Run: npx agenthud\n");
627
+ process.exit(0);
628
+ }
629
+ var agentDirExists = existsSync(".agent");
506
630
  if (options.mode === "watch") {
507
631
  clearScreen();
508
632
  }
509
- var { waitUntilExit } = render(React2.createElement(App, { mode: options.mode }));
633
+ var { waitUntilExit } = render(
634
+ React2.createElement(App, { mode: options.mode, agentDirExists })
635
+ );
510
636
  if (options.mode === "once") {
511
637
  setTimeout(() => process.exit(0), 100);
512
638
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenthud",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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",