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.
- package/dist/index.js +145 -19
- 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
|
|
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
|
|
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
|
|
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__ */
|
|
453
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
523
|
+
/* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "q:" }),
|
|
483
524
|
" quit \xB7 ",
|
|
484
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
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(
|
|
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