project-athena 0.1.1 → 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 +563 -106
- package/dist/cli.js.map +1 -1
- package/package.json +10 -2
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,
|
|
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
|
-
|
|
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
|
|
728
|
+
const strip2 = (value) => {
|
|
753
729
|
if (Array.isArray(value)) {
|
|
754
|
-
const kept = value.filter((v) => !JSON.stringify(v ?? "").includes(ATHENA_MARK)).map(
|
|
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 =
|
|
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
|
|
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,
|
|
3573
|
-
if (!
|
|
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, ...
|
|
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
|
|
3921
|
-
lines.push(`**Detected by tooling:** ${findings.length} vulnerable dependenc${findings.length === 1 ? "y" : "ies"}.`, "", table(["Package", "Severity", "Advisory", "ID", "Tool", "Fix available"],
|
|
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
|
-
|
|
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
|
|
4508
|
-
|
|
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
|
-
|
|
4942
|
+
board.finish(stageResults(result, o.mode, Boolean(o.dryRun)));
|
|
4523
4943
|
return result;
|
|
4524
4944
|
} catch (err) {
|
|
4525
|
-
|
|
4945
|
+
board.fail();
|
|
4526
4946
|
throw err;
|
|
4527
4947
|
}
|
|
4528
4948
|
}
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4562
|
-
|
|
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
|
-
|
|
4583
|
-
|
|
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
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
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
|
-
|
|
4597
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
8920
|
+
const rows2 = await listAgents(root);
|
|
8468
8921
|
if (isJson()) {
|
|
8469
|
-
json(
|
|
8922
|
+
json(rows2);
|
|
8470
8923
|
return;
|
|
8471
8924
|
}
|
|
8472
8925
|
heading("AI Agent Integrations");
|
|
8473
8926
|
line();
|
|
8474
|
-
for (const r of
|
|
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 = [];
|