project-athena 0.1.2 → 0.1.3

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/cli.js CHANGED
@@ -6,6 +6,9 @@ import { Command as Command2, Option } from "commander";
6
6
  // src/services/version.ts
7
7
  import { createRequire } from "module";
8
8
  var PACKAGE_NAME = "project-athena";
9
+ var AUTHOR = "Sabarivasan";
10
+ var NPM_URL = `https://www.npmjs.com/package/${PACKAGE_NAME}`;
11
+ var REPO_URL = "https://github.com/Sabari-Vasan-SM/athena";
9
12
  function readVersion() {
10
13
  try {
11
14
  const require2 = createRequire(import.meta.url);
@@ -91,33 +94,6 @@ function json(value) {
91
94
  out.write(`${JSON.stringify(value, null, 2)}
92
95
  `);
93
96
  }
94
- function banner() {
95
- if (jsonMode || quiet) return;
96
- const title = "ATHENA";
97
- const sub = "Project Intelligence for AI Coding";
98
- const w = sub.length + 4;
99
- const pad = (s) => {
100
- const left = Math.floor((w - s.length) / 2);
101
- return " ".repeat(left) + s + " ".repeat(w - s.length - left);
102
- };
103
- if (unicode) {
104
- line(c.dim(`\u256D${"\u2500".repeat(w)}\u256E`));
105
- line(`${c.dim("\u2502")}${c.bold(c.cyan(pad(title)))}${c.dim("\u2502")}`);
106
- line(`${c.dim("\u2502")}${c.dim(pad(sub))}${c.dim("\u2502")}`);
107
- line(c.dim(`\u2570${"\u2500".repeat(w)}\u256F`));
108
- } else {
109
- line(c.bold(c.cyan(title)));
110
- line(c.dim(sub));
111
- }
112
- line();
113
- }
114
- function tree(rootLabel, items) {
115
- line(rootLabel);
116
- items.forEach((item, i) => {
117
- const last = i === items.length - 1;
118
- line(`${c.dim(unicode ? last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 " : last ? "`-- " : "|-- ")}${item}`);
119
- });
120
- }
121
97
  var FRAMES = unicode ? ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] : ["-", "\\", "|", "/"];
122
98
  function spinner(text) {
123
99
  let current = text;
@@ -611,12 +587,12 @@ async function planJsonChange(root, rel, mutate2) {
611
587
  `;
612
588
  return { path: rel, ownership: "block", action: !existed ? "create" : JSON.stringify(data) === before ? "unchanged" : "update", content };
613
589
  }
614
- async function removeJsonHooks(root, rel, strip, isEmpty) {
590
+ async function removeJsonHooks(root, rel, strip2, isEmpty) {
615
591
  const file = path5.join(root, rel);
616
592
  const { data, existed, malformed } = await readJsonFile(file);
617
593
  if (!existed || malformed) return false;
618
594
  const before = JSON.stringify(data);
619
- strip(data);
595
+ strip2(data);
620
596
  if (JSON.stringify(data) === before) return false;
621
597
  if (isEmpty(data)) await fs3.rm(file, { force: true });
622
598
  else await fs3.writeFile(file, `${JSON.stringify(data, null, 2)}
@@ -749,16 +725,16 @@ async function jsonOnlyAthena(file) {
749
725
  if (Object.keys(servers).length === 0) delete record.mcpServers;
750
726
  }
751
727
  if (!text.includes(ATHENA_MARK) && !hadAthenaMcp) return false;
752
- const strip = (value) => {
728
+ const strip2 = (value) => {
753
729
  if (Array.isArray(value)) {
754
- const kept = value.filter((v) => !JSON.stringify(v ?? "").includes(ATHENA_MARK)).map(strip);
730
+ const kept = value.filter((v) => !JSON.stringify(v ?? "").includes(ATHENA_MARK)).map(strip2);
755
731
  return kept.length ? kept : void 0;
756
732
  }
757
733
  if (value && typeof value === "object") {
758
734
  const out2 = {};
759
735
  for (const [k, v] of Object.entries(value)) {
760
736
  if (JSON.stringify(v ?? "").includes(ATHENA_MARK)) {
761
- const cleaned = strip(v);
737
+ const cleaned = strip2(v);
762
738
  if (cleaned !== void 0) out2[k] = cleaned;
763
739
  continue;
764
740
  }
@@ -769,7 +745,7 @@ async function jsonOnlyAthena(file) {
769
745
  }
770
746
  return value;
771
747
  };
772
- return strip(data) === void 0;
748
+ return strip2(data) === void 0;
773
749
  }
774
750
 
775
751
  // src/agents/common/hook-events.ts
@@ -3569,11 +3545,11 @@ function evidenceList(evidence, max = 3) {
3569
3545
  function status(p) {
3570
3546
  return p.status === "UNKNOWN" ? "UNKNOWN" : `${p.status} \xB7 ${p.confidence}`;
3571
3547
  }
3572
- function table(headers, rows) {
3573
- if (!rows.length) return "";
3548
+ function table(headers, rows2) {
3549
+ if (!rows2.length) return "";
3574
3550
  const head = `| ${headers.join(" | ")} |`;
3575
3551
  const sep = `| ${headers.map(() => "---").join(" | ")} |`;
3576
- return [head, sep, ...rows.map((r) => `| ${r.join(" | ")} |`)].join("\n");
3552
+ return [head, sep, ...rows2.map((r) => `| ${r.join(" | ")} |`)].join("\n");
3577
3553
  }
3578
3554
  function unknown(what, hint) {
3579
3555
  return `**${what}:** UNKNOWN${hint ? ` \u2014 ${hint}` : ""}`;
@@ -3917,8 +3893,8 @@ function renderDependencyRisks(m, scan) {
3917
3893
  const missing = scan.tools.filter((t) => t.status !== "ok");
3918
3894
  const findings = ran.flatMap((t) => t.findings.map((f) => ({ ...f, tool: t.tool })));
3919
3895
  if (findings.length) {
3920
- const rows = findings.slice(0, 60).map((f) => [esc(f.package), f.severity.toUpperCase(), esc(f.title.slice(0, 90)), esc(f.id ?? "\u2014"), esc(f.tool), f.fixAvailable ? "yes" : "unknown"]);
3921
- lines.push(`**Detected by tooling:** ${findings.length} vulnerable dependenc${findings.length === 1 ? "y" : "ies"}.`, "", table(["Package", "Severity", "Advisory", "ID", "Tool", "Fix available"], rows));
3896
+ const rows2 = findings.slice(0, 60).map((f) => [esc(f.package), f.severity.toUpperCase(), esc(f.title.slice(0, 90)), esc(f.id ?? "\u2014"), esc(f.tool), f.fixAvailable ? "yes" : "unknown"]);
3897
+ lines.push(`**Detected by tooling:** ${findings.length} vulnerable dependenc${findings.length === 1 ? "y" : "ies"}.`, "", table(["Package", "Severity", "Advisory", "ID", "Tool", "Fix available"], rows2));
3922
3898
  if (findings.length > 60) lines.push("", `_Showing 60 of ${findings.length}. See \`.athena/security-scan.json\`._`);
3923
3899
  } else if (ran.length) {
3924
3900
  lines.push(`**Detected:** no known vulnerable dependencies reported by ${ran.map((t) => esc(t.tool)).join(", ")} at the time of the scan.`);
@@ -4476,6 +4452,453 @@ async function requireProjectRoot(opts) {
4476
4452
  return root;
4477
4453
  }
4478
4454
 
4455
+ // src/cli/ui/dashboard.ts
4456
+ import pc3 from "picocolors";
4457
+
4458
+ // src/cli/ui/layout.ts
4459
+ import pc2 from "picocolors";
4460
+ var ANSI = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\]8;;[^\x07]*\x07/g;
4461
+ var strip = (s) => s.replace(ANSI, "");
4462
+ var width = (s) => [...strip(s)].length;
4463
+ function termWidth() {
4464
+ const cols = process.stdout.columns || Number(process.env.COLUMNS) || 80;
4465
+ return Math.max(40, Math.min(cols, 160));
4466
+ }
4467
+ function truncate2(s, max) {
4468
+ if (width(s) <= max) return s;
4469
+ if (max <= 0) return "";
4470
+ let out2 = "";
4471
+ let visible = 0;
4472
+ let i = 0;
4473
+ while (i < s.length) {
4474
+ ANSI.lastIndex = 0;
4475
+ const rest = s.slice(i);
4476
+ const m = rest.match(new RegExp(`^(?:${ANSI.source})`));
4477
+ if (m) {
4478
+ out2 += m[0];
4479
+ i += m[0].length;
4480
+ continue;
4481
+ }
4482
+ const ch = String.fromCodePoint(s.codePointAt(i));
4483
+ if (visible === max - 1) {
4484
+ out2 += unicode ? "\u2026" : ".";
4485
+ break;
4486
+ }
4487
+ out2 += ch;
4488
+ visible++;
4489
+ i += ch.length;
4490
+ }
4491
+ return pc2.isColorSupported ? `${out2}\x1B[0m` : out2;
4492
+ }
4493
+ var padEnd = (s, w) => {
4494
+ const cut = truncate2(s, w);
4495
+ return cut + " ".repeat(Math.max(0, w - width(cut)));
4496
+ };
4497
+ var center = (s, w) => {
4498
+ const n = width(s);
4499
+ if (n >= w) return truncate2(s, w);
4500
+ const left = Math.floor((w - n) / 2);
4501
+ return " ".repeat(left) + s + " ".repeat(w - n - left);
4502
+ };
4503
+ var truecolor = /^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? "") || ["iTerm.app", "vscode", "WezTerm", "ghostty", "Hyper"].includes(process.env.TERM_PROGRAM ?? "");
4504
+ var hex = (h) => [parseInt(h.slice(1, 3), 16), parseInt(h.slice(3, 5), 16), parseInt(h.slice(5, 7), 16)];
4505
+ function ansi256([r, g, b]) {
4506
+ const q = (v) => v < 48 ? 0 : v < 115 ? 1 : Math.round((v - 35) / 40);
4507
+ return 16 + 36 * q(r) + 6 * q(g) + q(b);
4508
+ }
4509
+ function paint(color, s, bold = false) {
4510
+ if (!pc2.isColorSupported || !s) return s;
4511
+ const rgb = typeof color === "string" ? hex(color) : color;
4512
+ const fg = truecolor ? `\x1B[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m` : `\x1B[38;5;${ansi256(rgb)}m`;
4513
+ return `${bold ? "\x1B[1m" : ""}${fg}${s}\x1B[0m`;
4514
+ }
4515
+ function cell(ch, fg, bg) {
4516
+ if (!pc2.isColorSupported) return ch;
4517
+ const code2 = (c2, layer) => {
4518
+ const rgb = typeof c2 === "string" ? hex(c2) : c2;
4519
+ return truecolor ? `\x1B[${layer};2;${rgb[0]};${rgb[1]};${rgb[2]}m` : `\x1B[${layer};5;${ansi256(rgb)}m`;
4520
+ };
4521
+ return `${fg ? code2(fg, 38) : ""}${bg ? code2(bg, 48) : ""}${ch}\x1B[0m`;
4522
+ }
4523
+ var PALETTE = {
4524
+ green: "#34d399",
4525
+ cyan: "#22d3ee",
4526
+ blue: "#3b82f6",
4527
+ violet: "#a855f7",
4528
+ pink: "#f472b6",
4529
+ amber: "#fbbf24",
4530
+ slate: "#64748b"
4531
+ };
4532
+ var BRAND = [PALETTE.cyan, PALETTE.blue, PALETTE.violet];
4533
+ function mix(stops, t) {
4534
+ const rgb = stops.map(hex);
4535
+ if (rgb.length === 1) return rgb[0];
4536
+ const x = Math.min(0.9999, Math.max(0, t)) * (rgb.length - 1);
4537
+ const i = Math.floor(x);
4538
+ const f = x - i;
4539
+ const [a, b] = [rgb[i], rgb[i + 1]];
4540
+ return [0, 1, 2].map((k) => Math.round(a[k] + (b[k] - a[k]) * f));
4541
+ }
4542
+ function gradientBlock(lines, stops = BRAND, bold = true) {
4543
+ const w = Math.max(...lines.map((l) => [...l].length));
4544
+ return lines.map((l) => [...l].map((ch, i) => ch === " " ? ch : paint(mix(stops, i / Math.max(1, w - 1)), ch, bold)).join(""));
4545
+ }
4546
+ var gradient = (s, stops = BRAND, bold = false) => gradientBlock([s], stops, bold)[0];
4547
+ var B = unicode ? { tl: "\u256D", tr: "\u256E", bl: "\u2570", br: "\u256F", h: "\u2500", v: "\u2502" } : { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" };
4548
+ var HR = B.h;
4549
+ var VR = B.v;
4550
+ function box(content, w, opts = {}) {
4551
+ const color = opts.color ?? PALETTE.slate;
4552
+ const padX = opts.padX ?? 1;
4553
+ const inner = w - 2;
4554
+ const edge = (s) => paint(color, s);
4555
+ const title = opts.title ? ` ${opts.title} ` : "";
4556
+ const top = title ? edge(`${B.tl}${B.h}`) + title + edge(B.h.repeat(Math.max(0, inner - 1 - width(title))) + B.tr) : edge(B.tl + B.h.repeat(inner) + B.tr);
4557
+ const body = content.map((l) => edge(B.v) + " ".repeat(padX) + padEnd(l, inner - padX * 2) + " ".repeat(padX) + edge(B.v));
4558
+ return [top, ...body, edge(B.bl + B.h.repeat(inner) + B.br)];
4559
+ }
4560
+ function columns(blocks, widths, gap = 2) {
4561
+ const h = Math.max(...blocks.map((b) => b.length));
4562
+ const rows2 = [];
4563
+ for (let r = 0; r < h; r++) rows2.push(blocks.map((b, i) => padEnd(b[r] ?? "", widths[i])).join(" ".repeat(gap)).trimEnd());
4564
+ return rows2;
4565
+ }
4566
+ function vcenter(block, h) {
4567
+ const top = Math.max(0, Math.floor((h - block.length) / 2));
4568
+ return [...Array(top).fill(""), ...block];
4569
+ }
4570
+
4571
+ // src/cli/ui/dashboard.ts
4572
+ var C = PALETTE;
4573
+ var accent = (s) => paint(PALETTE.amber, s, true);
4574
+ var warnText = (s) => paint(PALETTE.amber, s);
4575
+ function sectionTitle(title) {
4576
+ const total = termWidth();
4577
+ const head = ` ${paint(PALETTE.violet, unicode ? "\u2726" : "*", true)} ${gradient(title, [PALETTE.cyan, PALETTE.violet], true)} `;
4578
+ const rest = Math.max(0, total - width(head) - 2);
4579
+ return [head + paint(PALETTE.blue, HR.repeat(rest))];
4580
+ }
4581
+ function rows(items) {
4582
+ const labelW = Math.max(...items.map(([l]) => l.length)) + 2;
4583
+ return (w) => items.map(([label, value]) => pc3.dim(padEnd(label, labelW)) + padEnd(value ? paint(PALETTE.cyan, value) : pc3.dim("Not detected"), w - labelW));
4584
+ }
4585
+ function fileTree(root, files) {
4586
+ const nameW = Math.max(...files.map((f) => f.name.length)) + 2;
4587
+ return (w) => [
4588
+ pc3.bold(root),
4589
+ ...files.map((f, i) => {
4590
+ const branch = unicode ? i === files.length - 1 ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 " : i === files.length - 1 ? "`-- " : "|-- ";
4591
+ const name = f.highlight ? paint(PALETTE.amber, padEnd(f.name, nameW)) : padEnd(f.name, nameW);
4592
+ return padEnd(pc3.dim(branch) + name + pc3.dim(f.note ? `# ${f.note}` : ""), w);
4593
+ })
4594
+ ];
4595
+ }
4596
+ function wrapCode(text, w) {
4597
+ const out2 = [];
4598
+ let cur = "";
4599
+ for (const word of text.split(" ")) {
4600
+ if (cur && cur.length + 1 + word.length > w) {
4601
+ out2.push(cur);
4602
+ cur = word;
4603
+ } else cur = cur ? `${cur} ${word}` : word;
4604
+ }
4605
+ if (cur) out2.push(cur);
4606
+ let inCode = false;
4607
+ return out2.map(
4608
+ (l) => l.split("`").map((part, i) => {
4609
+ if (i > 0) inCode = !inCode;
4610
+ return inCode ? paint(PALETTE.cyan, part) : part;
4611
+ }).join("")
4612
+ );
4613
+ }
4614
+ function numbered(steps) {
4615
+ return (w) => steps.flatMap((s, i) => {
4616
+ const badge = paint(PALETTE.green, ` ${i + 1} `, true);
4617
+ const [first = "", ...rest] = wrapCode(s, Math.max(10, w - 5));
4618
+ return [`${badge} ${first}`, ...rest.map((r) => ` ${r}`)];
4619
+ });
4620
+ }
4621
+ function quote(text) {
4622
+ return (w) => {
4623
+ const lines = wrapCode(`\u201C${text}\u201D`, Math.max(10, w - 6)).map((l) => pc3.italic(l));
4624
+ return box([...lines, paint(PALETTE.cyan, `\u2014 Sabarivasan`)], Math.min(w, Math.max(...lines.map(width)) + 6), { color: PALETTE.slate, padX: 2 });
4625
+ };
4626
+ }
4627
+ function renderBody(body, w) {
4628
+ const parts = Array.isArray(body) && body.length && typeof body[0] !== "string" ? body : [body];
4629
+ return parts.flatMap((p) => typeof p === "function" ? p(w) : p);
4630
+ }
4631
+ function panels(list) {
4632
+ const total = termWidth();
4633
+ const title = (p) => `${paint(p.color, unicode ? p.icon : "*", true)} ${paint(p.color, p.title, true)}`;
4634
+ if (total >= 120) {
4635
+ const gap = 2;
4636
+ const avail = total - gap * (list.length - 1) - 1;
4637
+ const weights = [0.36, 0.32, 0.32];
4638
+ const widths = list.map((_, i) => Math.floor(avail * (weights[i] ?? 1 / list.length)));
4639
+ const bodies = list.map((p, i) => renderBody(p.body, widths[i] - 4));
4640
+ const h = Math.max(...bodies.map((b) => b.length));
4641
+ const boxes = list.map((p, i) => box([title(p), "", ...bodies[i], ...Array(h - bodies[i].length).fill("")], widths[i], { color: p.color, padX: 1 }));
4642
+ return columns(boxes, widths, gap);
4643
+ }
4644
+ const w = Math.min(total - 1, 100);
4645
+ return list.flatMap((p, i) => [...i ? [""] : [], ...box([title(p), "", ...renderBody(p.body, w - 4)], w, { color: p.color })]);
4646
+ }
4647
+
4648
+ // src/cli/ui/brand.ts
4649
+ import pc4 from "picocolors";
4650
+ var LETTERS = {
4651
+ A: [" \u2588\u2588\u2588\u2588\u2588\u2557 ", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557", "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u255A\u2550\u255D \u255A\u2550\u255D"],
4652
+ T: ["\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557", "\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D", " \u2588\u2588\u2551 ", " \u2588\u2588\u2551 ", " \u2588\u2588\u2551 ", " \u255A\u2550\u255D "],
4653
+ H: ["\u2588\u2588\u2557 \u2588\u2588\u2557", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u255A\u2550\u255D \u255A\u2550\u255D"],
4654
+ E: ["\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557", "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D", "\u2588\u2588\u2588\u2588\u2588\u2557 ", "\u2588\u2588\u2554\u2550\u2550\u255D ", "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557", "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"],
4655
+ N: ["\u2588\u2588\u2588\u2557 \u2588\u2588\u2557", "\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551", "\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551", "\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551", "\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551", "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"]
4656
+ };
4657
+ var LOGO = [0, 1, 2, 3, 4, 5].map((r) => [..."ATHENA"].map((ch) => LETTERS[ch][r]).join(""));
4658
+ var LOGO_WIDTH = width(LOGO[0]);
4659
+ var ROBOT_PIXELS = [
4660
+ "...........AA...........",
4661
+ "..........AAAA..........",
4662
+ "...........KK...........",
4663
+ "....KKKKKKKKKKKKKKKK....",
4664
+ "...KBBBBBBBBBBBBBBBBK...",
4665
+ "..KBBBBBBBBBBBBBBBBBBK..",
4666
+ "..KBBBBWWBBBBBBYYYYBBK..",
4667
+ "YYKBBBWBBWBBBBYYDDYYBKYY",
4668
+ "YYKBBBBBBBBBBBYYDDYYBKYY",
4669
+ "YYKBBBBBBBBBBBBYYYYBBKYY",
4670
+ "YYKBBBBBYYYYYYYYBBBBBKYY",
4671
+ "..KBBBBBBYYYYYYBBBBBBK..",
4672
+ "..KBBBBBBBYYYYBBBBBBBK..",
4673
+ "..KBBBBBBBBBBBBBBBBBBK..",
4674
+ "..KBBBBBBBBBBBBBBBBBBK..",
4675
+ "..KBBBBBKKKKKKKKKKKK....",
4676
+ "...KBBBK................",
4677
+ "....KBK.................",
4678
+ ".....K..................",
4679
+ "........................"
4680
+ ];
4681
+ var PIXEL_COLORS = { K: "#1e40af", B: "#4ba3e3", Y: "#f5c542", D: "#2e3f9e", A: "#5b7cfa", W: "#0f172a" };
4682
+ var ROBOT_ASCII = [
4683
+ " \u25CF ",
4684
+ " \u2503 ",
4685
+ " \u256D\u2500\u2500\u2500\u2538\u2500\u2500\u2500\u256E ",
4686
+ " \u2590\u2502 \u25E0 \u25C9 \u2502\u258C ",
4687
+ " \u2590\u2502 \u2570\u2500\u256F \u2502\u258C ",
4688
+ " \u2502 \u2502 ",
4689
+ " \u2570\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u256F ",
4690
+ " \u2570\u256F ",
4691
+ " ",
4692
+ " "
4693
+ ];
4694
+ var ROBOT_HEIGHT = ROBOT_PIXELS.length / 2;
4695
+ var ROBOT_WIDTH = pc4.isColorSupported ? ROBOT_PIXELS[0].length : ROBOT_ASCII[0].length;
4696
+ var BUBBLE = ["\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E", "\u2502 Analyze \u2502", "\u2502 Understand \u2502", "\u2502 Document \u2502", "\u2502 Empower \u2726 \u2502", "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"];
4697
+ var BUBBLE_WIDTH = 14;
4698
+ function paintRobot() {
4699
+ if (!pc4.isColorSupported) return ROBOT_ASCII;
4700
+ const color = (p) => PIXEL_COLORS[p] ?? null;
4701
+ const out2 = [];
4702
+ for (let r = 0; r < ROBOT_PIXELS.length; r += 2) {
4703
+ const top = ROBOT_PIXELS[r];
4704
+ const bottom = ROBOT_PIXELS[r + 1];
4705
+ let row = "";
4706
+ for (let x = 0; x < top.length; x++) {
4707
+ const [t, b] = [color(top[x]), color(bottom[x])];
4708
+ if (!t && !b) row += " ";
4709
+ else if (t === b) row += cell("\u2588", t, null);
4710
+ else if (!b) row += cell("\u2580", t, null);
4711
+ else if (!t) row += cell("\u2584", b, null);
4712
+ else row += cell("\u2580", t, b);
4713
+ }
4714
+ out2.push(row);
4715
+ }
4716
+ return out2;
4717
+ }
4718
+ function paintBubble() {
4719
+ return BUBBLE.map((row) => {
4720
+ const inner = row.slice(1, -1);
4721
+ const edge = (s) => paint(PALETTE.slate, s);
4722
+ if (row.startsWith("\u256D") || row.startsWith("\u2570")) return edge(row);
4723
+ return edge("\u2502") + inner.replace("\u2726", paint(PALETTE.amber, "\u2726")).replace(/[A-Za-z]+/, (w) => paint(PALETTE.cyan, w)) + edge("\u2502");
4724
+ });
4725
+ }
4726
+ function logoBlock(w) {
4727
+ const rule = (label) => {
4728
+ const side = Math.max(2, Math.floor((w - width(label) - 2) / 2));
4729
+ return `${pc4.dim(HR.repeat(side))} ${label} ${pc4.dim(HR.repeat(side))}`;
4730
+ };
4731
+ return [
4732
+ ...gradientBlock(LOGO),
4733
+ "",
4734
+ center(gradient("Project Intelligence for AI Coding", [PALETTE.blue, PALETTE.violet], true), w),
4735
+ center(rule(`${pc4.dim("developed by")} ${paint(PALETTE.cyan, AUTHOR, true)}`), w)
4736
+ ];
4737
+ }
4738
+ function linksBlock() {
4739
+ return [
4740
+ pc4.italic('"Turn any codebase'),
4741
+ pc4.italic(' into clear knowledge."'),
4742
+ "",
4743
+ `${paint(PALETTE.pink, "npm", true)} ${pc4.dim(`v${ATHENA_VERSION}`)}`,
4744
+ paint(PALETTE.blue, NPM_URL),
4745
+ paint(PALETTE.violet, "github", true),
4746
+ paint(PALETTE.blue, REPO_URL)
4747
+ ];
4748
+ }
4749
+ var LINKS_WIDTH = Math.max(NPM_URL.length, REPO_URL.length);
4750
+ function printHeader() {
4751
+ if (isJson() || isQuiet()) return;
4752
+ for (const l of header()) line(l);
4753
+ }
4754
+ function header() {
4755
+ if (!unicode) return [pc4.bold(pc4.cyan("ATHENA")) + pc4.dim(` Project Intelligence for AI Coding \xB7 v${ATHENA_VERSION}`), ""];
4756
+ const total = termWidth();
4757
+ const logoW = LOGO_WIDTH;
4758
+ const divider = (h) => Array.from({ length: h }, () => pc4.dim(VR));
4759
+ const robotBubble = columns([paintRobot(), vcenter(paintBubble(), ROBOT_HEIGHT - 2)], [ROBOT_WIDTH, BUBBLE_WIDTH], 1);
4760
+ const logo = vcenter(logoBlock(logoW), ROBOT_HEIGHT);
4761
+ const links = vcenter(linksBlock(), ROBOT_HEIGHT);
4762
+ const H = ROBOT_HEIGHT;
4763
+ const layouts = [
4764
+ { w: ROBOT_WIDTH + 1 + BUBBLE_WIDTH + 3 + logoW + 3 + 1 + 3 + LINKS_WIDTH, build: () => columns([robotBubble, logo, divider(H), links], [ROBOT_WIDTH + 1 + BUBBLE_WIDTH, logoW, 1, LINKS_WIDTH], 3) },
4765
+ { w: ROBOT_WIDTH + 3 + logoW + 3 + 1 + 3 + LINKS_WIDTH, build: () => columns([paintRobot(), logo, divider(H), links], [ROBOT_WIDTH, logoW, 1, LINKS_WIDTH], 3) },
4766
+ { w: ROBOT_WIDTH + 3 + logoW, build: () => [...columns([paintRobot(), logo], [ROBOT_WIDTH, logoW], 3), "", ...compactLinks()] },
4767
+ { w: logoW, build: () => [...logoBlock(logoW), "", ...compactLinks()] }
4768
+ ];
4769
+ const fit = layouts.find((l) => l.w + 4 <= total);
4770
+ if (!fit) return [pc4.bold(gradient("ATHENA", BRAND, true)) + pc4.dim(` Project Intelligence for AI Coding \xB7 v${ATHENA_VERSION}`), pc4.dim(REPO_URL), ""];
4771
+ const content = fit.build();
4772
+ return [...box(content, Math.min(total, fit.w + 4), { color: PALETTE.blue }), ""];
4773
+ }
4774
+ function compactLinks() {
4775
+ return [`${paint(PALETTE.pink, "npm", true)} ${paint(PALETTE.blue, NPM_URL)}`, `${paint(PALETTE.violet, "github", true)} ${paint(PALETTE.blue, REPO_URL)}`];
4776
+ }
4777
+ function footer(title, subtitle, tone = "success") {
4778
+ const total = termWidth();
4779
+ const color = tone === "success" ? PALETTE.green : PALETTE.amber;
4780
+ const mark = tone === "success" ? "\u2713" : "!";
4781
+ const status2 = [`${paint(color, ` ${mark} `, true)} ${paint(color, title, true)}`, ` ${subtitle}`];
4782
+ const links = compactLinks();
4783
+ const credit = [pc4.dim("Developed by"), `${paint(PALETTE.cyan, AUTHOR, true)} ${paint(PALETTE.pink, "\u2665")}`];
4784
+ const statusW = Math.max(...status2.map(width));
4785
+ const linksW = 8 + LINKS_WIDTH;
4786
+ const wide = statusW + 4 + linksW + 3 + 1 + 3 + 14 + 4;
4787
+ const content = wide <= total ? columns([status2, links, divider2(), credit], [statusW, linksW, 1, 14], 3) : total >= linksW + 4 ? [...status2, "", ...links, credit.join(" ")] : [...status2, pc4.dim(REPO_URL)];
4788
+ const w = wide <= total ? wide : Math.min(total, Math.max(statusW, linksW) + 4);
4789
+ return box(content, w, { color });
4790
+ }
4791
+ var divider2 = () => [pc4.dim(VR), pc4.dim(VR)];
4792
+
4793
+ // src/cli/ui/stages.ts
4794
+ import pc5 from "picocolors";
4795
+ var STAGES = [
4796
+ { id: "scan", label: "Scanning project structure" },
4797
+ { id: "detect", label: "Detecting stack, database, API & auth" },
4798
+ { id: "git", label: "Reading Git metadata" },
4799
+ { id: "finalize", label: "Redacting secrets & validating" },
4800
+ { id: "write", label: "Generating documentation" },
4801
+ { id: "agents", label: "Configuring AI agents" }
4802
+ ];
4803
+ var STAGE_ALIASES = { analyze: "scan" };
4804
+ var FRAMES2 = unicode ? ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] : ["-", "\\", "|", "/"];
4805
+ var BAR_STOPS = [PALETTE.green, PALETTE.cyan, PALETTE.blue, PALETTE.violet];
4806
+ var StageBoard = class {
4807
+ state = new Map(STAGES.map((s) => [s.id, "pending"]));
4808
+ started = /* @__PURE__ */ new Map();
4809
+ elapsed = /* @__PURE__ */ new Map();
4810
+ results = /* @__PURE__ */ new Map();
4811
+ current = null;
4812
+ frame = 0;
4813
+ drawn = 0;
4814
+ timer = null;
4815
+ live = isTTY && !isJson() && !isQuiet();
4816
+ enabled = !isJson() && !isQuiet();
4817
+ restoreCursor = () => process.stdout.write("\x1B[?25h");
4818
+ start() {
4819
+ if (!this.live) return;
4820
+ process.stdout.write("\x1B[?25l");
4821
+ process.once("exit", this.restoreCursor);
4822
+ this.render();
4823
+ this.timer = setInterval(() => {
4824
+ this.frame++;
4825
+ this.render();
4826
+ }, 80);
4827
+ this.timer.unref();
4828
+ }
4829
+ /** Accepts raw pipeline stage names; unknown names are ignored. */
4830
+ enter(raw) {
4831
+ const id = STAGE_ALIASES[raw] ?? raw;
4832
+ if (!this.state.has(id) || this.current === id) return;
4833
+ this.closeCurrent("done");
4834
+ this.current = id;
4835
+ this.state.set(id, "running");
4836
+ this.started.set(id, performance.now());
4837
+ }
4838
+ finish(results) {
4839
+ this.closeCurrent("done");
4840
+ for (const [id, st] of this.state) if (st === "pending") this.state.set(id, "skipped");
4841
+ for (const [id, r] of Object.entries(results)) this.results.set(id, r);
4842
+ this.stop();
4843
+ }
4844
+ fail() {
4845
+ this.closeCurrent("failed");
4846
+ this.stop();
4847
+ }
4848
+ closeCurrent(to) {
4849
+ if (!this.current) return;
4850
+ this.state.set(this.current, to);
4851
+ this.elapsed.set(this.current, performance.now() - this.started.get(this.current));
4852
+ this.current = null;
4853
+ }
4854
+ stop() {
4855
+ if (this.timer) clearInterval(this.timer);
4856
+ this.timer = null;
4857
+ if (!this.enabled) return;
4858
+ if (this.live) {
4859
+ this.render();
4860
+ this.restoreCursor();
4861
+ process.removeListener("exit", this.restoreCursor);
4862
+ } else for (const l of this.lines()) line(l);
4863
+ }
4864
+ render() {
4865
+ const rows2 = this.lines();
4866
+ let s = this.drawn ? `\x1B[${this.drawn}F` : "";
4867
+ for (const r of rows2) s += `\x1B[2K${r}
4868
+ `;
4869
+ process.stdout.write(s);
4870
+ this.drawn = rows2.length;
4871
+ }
4872
+ lines() {
4873
+ const total = termWidth();
4874
+ const showBar = total >= 100;
4875
+ const barW = total >= 130 ? 26 : 18;
4876
+ return STAGES.map((s, i) => {
4877
+ const st = this.state.get(s.id);
4878
+ const icon = st === "done" ? paint(PALETTE.green, sym.ok, true) : st === "running" ? paint(PALETTE.cyan, FRAMES2[this.frame % FRAMES2.length]) : st === "failed" ? pc5.red(sym.err) : pc5.dim(st === "skipped" ? "\u2013" : sym.ring);
4879
+ const label = st === "pending" || st === "skipped" ? pc5.dim(s.label) : s.label;
4880
+ const ms = this.elapsed.get(s.id) ?? (st === "running" ? performance.now() - this.started.get(s.id) : void 0);
4881
+ const time2 = st === "skipped" ? pc5.dim("skipped") : ms === void 0 ? "" : pc5.dim(ms < 1e3 ? `${Math.max(1, Math.round(ms))}ms` : `${(ms / 1e3).toFixed(1)}s`);
4882
+ const r = this.results.get(s.id);
4883
+ const result = r ? r.tone === "warn" ? paint(PALETTE.amber, `${sym.warn} ${r.text}`) : r.tone === "dim" ? pc5.dim(r.text) : `${paint(PALETTE.green, sym.ok)} ${r.text}` : "";
4884
+ const bar = showBar ? `${this.bar(st, barW, i)} ` : "";
4885
+ return truncate2(` ${icon} ${padEnd(label, 40)}${bar}${padEnd(time2, 8)}${result}`, total - 1);
4886
+ });
4887
+ }
4888
+ bar(st, w, row) {
4889
+ const fill = unicode ? "\u2501" : "#";
4890
+ const track = unicode ? "\u2500" : "-";
4891
+ if (st === "done") return paint(mix(BAR_STOPS, row / (STAGES.length - 1)), fill.repeat(w));
4892
+ if (st === "failed") return pc5.red(fill.repeat(w));
4893
+ if (st !== "running") return pc5.dim(track.repeat(w));
4894
+ const seg = 6;
4895
+ const span = w - seg;
4896
+ const t = this.frame % (span * 2);
4897
+ const pos = t <= span ? t : span * 2 - t;
4898
+ return pc5.dim(track.repeat(pos)) + paint(PALETTE.cyan, fill.repeat(seg)) + pc5.dim(track.repeat(w - pos - seg));
4899
+ }
4900
+ };
4901
+
4479
4902
  // src/cli/commands/init.ts
4480
4903
  function parseAgentsOption(value, disabled) {
4481
4904
  if (disabled) return [];
@@ -4495,7 +4918,7 @@ async function initCommand(opts) {
4495
4918
  throw new AthenaError("Athena is already initialized in this project.", "Run `athena analyze` to refresh knowledge, or `athena clean` to start over.");
4496
4919
  }
4497
4920
  const agents = parseAgentsOption(opts.agents, opts.noAgents);
4498
- banner();
4921
+ printHeader();
4499
4922
  const result = await runWithProgress(root, { mode: "init", dryRun: opts.dryRun, agents, signal: opts.signal });
4500
4923
  if (isJson()) {
4501
4924
  json(summarize(result, root, Boolean(opts.dryRun)));
@@ -4504,8 +4927,8 @@ async function initCommand(opts) {
4504
4927
  printSummary(result, Boolean(opts.dryRun), "init");
4505
4928
  }
4506
4929
  async function runWithProgress(root, o) {
4507
- const sp = spinner("Scanning project files...");
4508
- const started = Date.now();
4930
+ const board = new StageBoard();
4931
+ board.start();
4509
4932
  try {
4510
4933
  const result = await runPipeline({
4511
4934
  root,
@@ -4514,22 +4937,50 @@ async function runWithProgress(root, o) {
4514
4937
  force: o.force,
4515
4938
  agents: o.agents,
4516
4939
  signal: o.signal,
4517
- onStage: (stage) => {
4518
- const labels = { analyze: "Scanning project files...", scan: "Scanning project files...", detect: "Detecting technologies, schema, routes and infrastructure...", git: "Reading Git metadata...", finalize: "Redacting and validating...", write: "Writing knowledge...", agents: "Configuring AI agents..." };
4519
- sp.update(labels[stage] ?? stage);
4520
- }
4940
+ onStage: (stage) => board.enter(stage)
4521
4941
  });
4522
- sp.succeed(`Analyzed ${result.analysis.model.stats.filesScanned} files in ${((Date.now() - started) / 1e3).toFixed(1)}s`);
4942
+ board.finish(stageResults(result, o.mode, Boolean(o.dryRun)));
4523
4943
  return result;
4524
4944
  } catch (err) {
4525
- sp.stop();
4945
+ board.fail();
4526
4946
  throw err;
4527
4947
  }
4528
4948
  }
4529
- function detectionLine(label, count, detail) {
4530
- if (count > 0) ok(`${label}${detail ? dim(` \u2014 ${detail}`) : ""}`);
4531
- else line(`${c.dim(sym.ring)} ${c.dim(`${label} \u2014 not detected`)}`);
4949
+ var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
4950
+ function stageResults(result, mode, dryRun) {
4951
+ const m = result.analysis.model;
4952
+ const skipped = m.stats.skippedBinary + m.stats.skippedLarge + m.stats.skippedUnreadable;
4953
+ const found = [
4954
+ m.frameworks.length && plural(new Set(m.frameworks.map((f) => f.name)).size, "framework"),
4955
+ m.dbEntities.length && plural(m.dbEntities.length, "entity").replace("entitys", "entities"),
4956
+ m.routes.length && plural(m.routes.length, "route"),
4957
+ m.auth.length && "auth"
4958
+ ].filter(Boolean);
4959
+ const changed = result.docs.filter((d) => d.status !== "unchanged").length;
4960
+ const agents = result.agentChanges.map((a) => a.adapter.displayName);
4961
+ return {
4962
+ scan: { text: `Found ${plural(m.stats.filesScanned, "file")}${skipped ? ` \xB7 ${skipped} skipped` : ""}` },
4963
+ detect: found.length ? { text: found.join(" \xB7 ") } : { text: "No frameworks detected", tone: "dim" },
4964
+ git: m.git.isRepo ? { text: `On ${m.git.branch ?? "detached HEAD"}${m.git.head ? ` @ ${m.git.head.slice(0, 7)}` : ""}` } : { text: m.git.available ? "Not a Git repository" : "Git not installed", tone: "dim" },
4965
+ finalize: m.security.secrets.length ? { text: `${plural(m.security.secrets.length, "potential secret")} (values redacted)`, tone: "warn" } : { text: "No likely secrets found" },
4966
+ write: dryRun ? { text: "Dry run \u2014 nothing written", tone: "dim" } : { text: mode === "init" ? `${plural(result.docs.length, "document")} generated` : changed ? `${changed} updated \xB7 ${result.docs.length - changed} unchanged` : "Already up to date" },
4967
+ agents: agents.length ? { text: agents.join(", ") } : { text: "None configured", tone: "dim" }
4968
+ };
4532
4969
  }
4970
+ var DOC_NOTES = {
4971
+ "project.md": "Project overview",
4972
+ "architecture.md": "System architecture",
4973
+ "database.md": "Database schema",
4974
+ "api.md": "API reference",
4975
+ "auth.md": "Authentication flow",
4976
+ "security.md": "Security analysis",
4977
+ "testing.md": "Testing strategy",
4978
+ "debugging.md": "Debugging guide",
4979
+ "performance.md": "Performance notes",
4980
+ "code-review.md": "Review checklist",
4981
+ "deployment.md": "Deployment guide",
4982
+ "rules.md": "Your project rules"
4983
+ };
4533
4984
  function printSummary(result, dryRun, mode) {
4534
4985
  const m = result.analysis.model;
4535
4986
  if (isQuiet()) {
@@ -4540,63 +4991,64 @@ function printSummary(result, dryRun, mode) {
4540
4991
  line(dryRun ? "Dry run: nothing written." : changed.length ? `Updated: ${changed.join(", ")}` : "Knowledge up to date.");
4541
4992
  return;
4542
4993
  }
4543
- line();
4544
4994
  const uniq = (xs) => [...new Set(xs)];
4545
- detectionLine("Project", m.stats.filesScanned, `${m.name}${m.languages[0] ? ` \xB7 ${m.languages.slice(0, 3).map((l) => l.name).join(", ")}` : ""}`);
4546
- detectionLine("Frameworks", m.frameworks.length, uniq(m.frameworks.map((f) => f.name)).slice(0, 5).join(", "));
4547
- detectionLine("Workspace", m.workspace.isMonorepo ? m.workspace.packages.length : 0, m.workspace.isMonorepo ? `monorepo \xB7 ${m.workspace.packages.length} packages` : void 0);
4548
- detectionLine("Database", m.databases.length + m.dbEntities.length, `${uniq(m.databases.map((d) => d.name)).slice(0, 3).join(", ")}${m.dbEntities.length ? ` \xB7 ${m.dbEntities.length} entities` : ""}`);
4549
- detectionLine("API", m.routes.length + m.apiSpecs.length, `${m.routes.length} route${m.routes.length === 1 ? "" : "s"}${m.apiSpecs.length ? ` \xB7 ${m.apiSpecs.length} specs` : ""}`);
4550
- detectionLine("Authentication", m.auth.length, uniq(m.auth.map((a) => a.name)).slice(0, 3).join(", "));
4551
- detectionLine("Testing", m.tests.frameworks.length + m.tests.testFileCount, `${uniq(m.tests.frameworks.map((f) => f.name)).slice(0, 3).join(", ")} \xB7 ${m.tests.testFileCount} test files`);
4552
- detectionLine("Deployment", m.containers.dockerfiles.length + m.ci.length + m.infrastructure.length, [m.containers.dockerfiles.length ? "Docker" : "", m.ci.length ? uniq(m.ci.map((j) => j.system)).join(", ") : "", ...uniq(m.infrastructure.filter((i) => i.kind === "hosting").map((i) => i.name))].filter(Boolean).slice(0, 4).join(", "));
4553
- if (m.security.secrets.length) warn(`Security \u2014 ${m.security.secrets.length} potential hardcoded secret(s); see .athena/security.md (values not stored)`);
4554
- else ok(`Security${dim(" \u2014 no likely secrets matched")}`);
4555
- if (m.warnings.length) {
4556
- line();
4557
- for (const w of m.warnings.slice(0, 5)) warn(c.yellow(w));
4558
- if (m.warnings.length > 5) line(dim(` +${m.warnings.length - 5} more warnings (see .athena/project.md)`));
4559
- }
4995
+ const list = (xs, n = 4) => uniq(xs).slice(0, n).join(" \xB7 ");
4560
4996
  line();
4561
- if (dryRun) {
4562
- heading("Dry run \u2014 nothing was written.");
4563
- line("Would create .athena/ with 12 knowledge files, model.json and state.json.");
4564
- for (const { adapter, changes } of result.agentChanges) for (const ch of changes) bullet(`${adapter.displayName}: would ${ch.action} ${ch.path}`);
4565
- return;
4566
- }
4567
- if (mode === "init") {
4568
- tree(c.bold(".athena/"), [...result.docs.map((d) => d.file), "model.json", "state.json"]);
4569
- } else {
4570
- const changed = result.docs.filter((d) => d.status !== "unchanged");
4571
- if (changed.length) {
4572
- heading("Knowledge updated:");
4573
- for (const d of changed) bullet(`${d.file} ${dim(`(${d.status})`)}`);
4574
- } else ok("Knowledge already up to date.");
4575
- }
4576
- const preserved = result.docs.filter((d) => d.preservedModified.length);
4577
- if (preserved.length) {
4578
- line();
4579
- for (const d of preserved) warn(`${d.file}: kept your edits in ${d.preservedModified.join(", ")} ${dim("(use --force to regenerate)")}`);
4580
- }
4997
+ for (const l of sectionTitle(dryRun ? "Dry Run Complete" : "Analysis Complete!")) line(l);
4998
+ line(` Analyzed ${accent(String(m.stats.filesScanned))} files in ${accent(`${(result.analysis.durationMs / 1e3).toFixed(1)}s`)}`);
4581
4999
  line();
4582
- if (result.agentChanges.length) {
4583
- for (const { adapter, changes } of result.agentChanges) {
5000
+ const overview = [
5001
+ ["Project", `${m.name}${m.languages[0] ? ` \xB7 ${list(m.languages.map((l) => l.name), 3)}` : ""}`],
5002
+ ["Frameworks", list(m.frameworks.map((f) => f.name), 5)],
5003
+ ["Workspace", m.workspace.isMonorepo ? `monorepo \xB7 ${plural(m.workspace.packages.length, "package")}` : ""],
5004
+ ["Database", m.databases.length + m.dbEntities.length ? [list(m.databases.map((d) => d.name), 3), m.dbEntities.length ? plural(m.dbEntities.length, "entity").replace("entitys", "entities") : ""].filter(Boolean).join(" \xB7 ") : ""],
5005
+ ["API", m.routes.length + m.apiSpecs.length ? [m.routes.length ? plural(m.routes.length, "route") : "", m.apiSpecs.length ? plural(m.apiSpecs.length, "spec") : ""].filter(Boolean).join(" \xB7 ") : ""],
5006
+ ["Auth", list(m.auth.map((a) => a.name), 3)],
5007
+ ["Testing", m.tests.frameworks.length + m.tests.testFileCount ? [list(m.tests.frameworks.map((f) => f.name), 3), plural(m.tests.testFileCount, "test file")].filter(Boolean).join(" \xB7 ") : ""],
5008
+ ["Deployment", [m.containers.dockerfiles.length ? "Docker" : "", ...uniq(m.ci.map((j) => j.system)), ...uniq(m.infrastructure.filter((i) => i.kind === "hosting").map((i) => i.name))].filter(Boolean).slice(0, 4).join(" \xB7 ")],
5009
+ ["Security", m.security.secrets.length ? warnText(`${plural(m.security.secrets.length, "potential secret")} \u2014 see security.md`) : "No likely secrets matched"]
5010
+ ];
5011
+ const files = dryRun ? ["Would create .athena/ with:", ...[...KNOWLEDGE_DOCS.map((d) => d.file), "model.json", "state.json"].map((f) => ` ${f}`)] : fileTree(
5012
+ ".athena/",
5013
+ result.docs.map((d) => ({ name: d.file, note: mode === "init" ? DOC_NOTES[d.file] ?? "" : d.status, highlight: mode === "analyze" && d.status !== "unchanged" }))
5014
+ );
5015
+ const configured = result.agentChanges.length > 0;
5016
+ const steps = dryRun ? ["Run `athena init` without --dry-run to write the files"] : mode === "init" ? [
5017
+ "Review `.athena/rules.md` and enable the rules that apply",
5018
+ "Run `athena open` to browse and edit in the web UI",
5019
+ configured ? "Your agents now read `.athena/` \u2014 watch with `athena activity`" : "Connect an agent: `athena agents add claude-code`",
5020
+ "After code changes run `athena sync` to keep docs current",
5021
+ "Share feedback and star on GitHub \u2605"
5022
+ ] : ["Review changes with `git diff .athena/`", "Run `athena open` to browse in the web UI", "Use `athena sync --watch` to stay current"];
5023
+ for (const l of panels([
5024
+ { title: "Project Overview", icon: "\u25C6", color: C.blue, body: rows(overview) },
5025
+ { title: mode === "init" ? "Generated Files" : "Knowledge Files", icon: "\u25A4", color: C.violet, body: files },
5026
+ { title: "Next Steps", icon: "\u279C", color: C.green, body: mode === "init" && !dryRun ? [numbered(steps), [""], quote("Better documentation leads to better software.")] : numbered(steps) }
5027
+ ]))
5028
+ line(l);
5029
+ const preserved = result.docs.filter((d) => d.preservedModified.length);
5030
+ const notes = [
5031
+ ...m.warnings.slice(0, 5).map((w) => () => warn(c.yellow(w))),
5032
+ ...m.warnings.length > 5 ? [() => line(dim(` +${m.warnings.length - 5} more warnings (see .athena/project.md)`))] : [],
5033
+ ...preserved.map((d) => () => warn(`${d.file}: kept your edits in ${d.preservedModified.join(", ")} ${dim("(use --force to regenerate)")}`)),
5034
+ ...(dryRun ? [] : result.agentChanges).map(({ adapter, changes }) => () => {
4584
5035
  const touched = changes.filter((c2) => c2.action !== "unchanged").map((c2) => `${c2.path} (${c2.action})`);
4585
5036
  ok(`${adapter.displayName} integration${dim(` \u2014 ${touched.length ? touched.join(", ") : changes.map((c2) => c2.path).join(", ") + " (unchanged)"}`)}`);
4586
- }
4587
- } else if (mode === "init") {
4588
- line(dim("No agent integrations configured. Use `athena init --agents claude,cursor,antigravity` or `athena agents` later."));
4589
- }
4590
- for (const n of result.notes) warn(n);
4591
- if (mode === "init") {
4592
- const unconfigured = ADAPTERS.filter((a) => !result.agentChanges.some((c2) => c2.adapter.id === a.id));
4593
- if (unconfigured.length) line(dim(`Not configured: ${unconfigured.map((a) => a.displayName).join(", ")} (not detected in this project). Add with --agents.`));
5037
+ }),
5038
+ ...mode === "init" && !dryRun ? (() => {
5039
+ const unconfigured = ADAPTERS.filter((a) => !result.agentChanges.some((c2) => c2.adapter.id === a.id));
5040
+ return unconfigured.length ? [() => line(dim(`Not configured: ${unconfigured.map((a) => a.displayName).join(", ")} \u2014 not detected; add with \`athena agents add <id>\``))] : [];
5041
+ })() : [],
5042
+ ...dryRun ? result.agentChanges.flatMap(({ adapter, changes }) => changes.map((ch) => () => bullet(`${adapter.displayName}: would ${ch.action} ${ch.path}`))) : [],
5043
+ ...result.notes.map((n) => () => warn(n))
5044
+ ];
5045
+ if (notes.length) {
5046
+ line();
5047
+ for (const print of notes) print();
4594
5048
  }
4595
5049
  line();
4596
- line(c.bold(c.green(mode === "init" ? "Athena ready." : "Analysis complete.")));
4597
- if (mode === "init") {
4598
- line(dim("Next: review .athena/rules.md and enable the rules that apply, then run `athena open` for the web UI."));
4599
- }
5050
+ const lines = dryRun ? footer("Dry run \u2014 nothing was written.", "Re-run without --dry-run to create .athena/", "warn") : mode === "init" ? footer("Athena is ready!", `Your project intelligence is ready in ${accent(".athena/")}`) : footer("Analysis complete.", `Knowledge in ${accent(".athena/")} is up to date with your code`);
5051
+ for (const l of lines) line(l);
4600
5052
  }
4601
5053
  function summarize(result, root, dryRun) {
4602
5054
  const m = result.analysis.model;
@@ -4625,6 +5077,7 @@ function summarize(result, root, dryRun) {
4625
5077
  async function analyzeCommand(opts) {
4626
5078
  const root = await requireProjectRoot(opts);
4627
5079
  const agents = parseAgentsOption(opts.agents, false);
5080
+ printHeader();
4628
5081
  const result = await runWithProgress(root, { mode: "analyze", force: opts.force, agents, signal: opts.signal });
4629
5082
  if (isJson()) json(summarize(result, root, false));
4630
5083
  else printSummary(result, false, "analyze");
@@ -7654,7 +8107,7 @@ async function openCommand(opts) {
7654
8107
  }
7655
8108
  if (isJson()) json({ running: true, reused: false, url: running.url, port: running.info.port, pid: process.pid });
7656
8109
  else {
7657
- banner();
8110
+ printHeader();
7658
8111
  line(c.bold(c.green("Athena is running")));
7659
8112
  line();
7660
8113
  line(`Local: ${c.cyan(running.url)}`);
@@ -7711,10 +8164,10 @@ function printPlan(plan, opts = {}) {
7711
8164
  return;
7712
8165
  }
7713
8166
  heading("Knowledge to update");
7714
- const width = Math.max(...plan.documents.map((d) => d.file.length));
8167
+ const width2 = Math.max(...plan.documents.map((d) => d.file.length));
7715
8168
  for (const d of plan.documents) {
7716
8169
  const sign = d.status === "created" ? c.green("+") : c.yellow("~");
7717
- line(` ${sign} ${d.file.padEnd(width)} ${c.green(`+${d.additions}`)} ${c.red(`\u2212${d.deletions}`)} ${dim(d.changedSections.join(", "))}`);
8170
+ line(` ${sign} ${d.file.padEnd(width2)} ${c.green(`+${d.additions}`)} ${c.red(`\u2212${d.deletions}`)} ${dim(d.changedSections.join(", "))}`);
7718
8171
  for (const r of d.reasons.slice(0, 3)) line(` ${dim(`${sym.arrow} ${r}`)}`);
7719
8172
  if (d.preservedSections.length) line(` ${c.yellow(sym.warn)} ${dim(`your edits kept in: ${d.preservedSections.join(", ")}`)}`);
7720
8173
  }
@@ -8464,14 +8917,14 @@ async function watchCommand(opts) {
8464
8917
  // src/cli/commands/agents.ts
8465
8918
  async function agentsListCommand(opts) {
8466
8919
  const root = await requireProjectRoot(opts);
8467
- const rows = await listAgents(root);
8920
+ const rows2 = await listAgents(root);
8468
8921
  if (isJson()) {
8469
- json(rows);
8922
+ json(rows2);
8470
8923
  return;
8471
8924
  }
8472
8925
  heading("AI Agent Integrations");
8473
8926
  line();
8474
- for (const r of rows) {
8927
+ for (const r of rows2) {
8475
8928
  const icon = r.configured ? c.green(sym.dot) : c.dim(sym.ring);
8476
8929
  line(`${icon} ${c.bold(r.name)} ${dim(`(${r.id})`)} \u2014 ${r.configured ? "configured" : "not configured"}${r.detectedInProject ? dim(` \xB7 detected: ${r.evidence.join(", ")}`) : ""}`);
8477
8930
  line(` ${dim(r.note)}`);
@@ -8504,6 +8957,10 @@ function onSignal() {
8504
8957
  ${c.yellow("Interrupted.")} ${dim(getInterruptMessage())}
8505
8958
  `);
8506
8959
  }
8960
+ process.stdout.on("error", (err) => {
8961
+ if (err.code === "EPIPE") process.exit(0);
8962
+ throw err;
8963
+ });
8507
8964
  process.on("SIGINT", onSignal);
8508
8965
  process.on("SIGTERM", onSignal);
8509
8966
  var PLANNED = [];