autonomous-flow-daemon 1.1.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +85 -46
  2. package/LICENSE +21 -21
  3. package/README-ko.md +282 -0
  4. package/README.md +282 -337
  5. package/mcp-config.json +10 -10
  6. package/package.json +14 -6
  7. package/src/adapters/index.ts +370 -159
  8. package/src/cli.ts +162 -57
  9. package/src/commands/benchmark.ts +187 -0
  10. package/src/commands/correlate.ts +180 -0
  11. package/src/commands/dashboard.ts +404 -0
  12. package/src/commands/diagnose.ts +56 -14
  13. package/src/commands/doctor.ts +243 -0
  14. package/src/commands/evolution.ts +190 -0
  15. package/src/commands/fix.ts +158 -138
  16. package/src/commands/hooks.ts +136 -0
  17. package/src/commands/lang.ts +41 -41
  18. package/src/commands/mcp.ts +129 -0
  19. package/src/commands/plugin.ts +110 -0
  20. package/src/commands/restart.ts +14 -0
  21. package/src/commands/score.ts +276 -208
  22. package/src/commands/start.ts +155 -96
  23. package/src/commands/stats.ts +103 -0
  24. package/src/commands/status.ts +157 -0
  25. package/src/commands/stop.ts +68 -49
  26. package/src/commands/suggest.ts +211 -0
  27. package/src/commands/sync.ts +567 -21
  28. package/src/commands/vaccine.ts +177 -0
  29. package/src/constants.ts +32 -8
  30. package/src/core/boast.ts +280 -265
  31. package/src/core/config.ts +49 -49
  32. package/src/core/correlation-engine.ts +265 -0
  33. package/src/core/db.ts +145 -46
  34. package/src/core/discovery.ts +65 -65
  35. package/src/core/evolution.ts +215 -0
  36. package/src/core/federation.ts +129 -0
  37. package/src/core/hologram/engine.ts +71 -0
  38. package/src/core/hologram/fallback.ts +11 -0
  39. package/src/core/hologram/go-extractor.ts +203 -0
  40. package/src/core/hologram/incremental.ts +227 -0
  41. package/src/core/hologram/py-extractor.ts +132 -0
  42. package/src/core/hologram/rust-extractor.ts +244 -0
  43. package/src/core/hologram/ts-extractor.ts +406 -0
  44. package/src/core/hologram/types.ts +27 -0
  45. package/src/core/hologram.ts +73 -243
  46. package/src/core/hook-manager.ts +259 -0
  47. package/src/core/i18n/messages.ts +309 -266
  48. package/src/core/immune.ts +8 -123
  49. package/src/core/locale.ts +88 -88
  50. package/src/core/log-rotate.ts +33 -0
  51. package/src/core/log-utils.ts +38 -0
  52. package/src/core/lru-map.ts +61 -0
  53. package/src/core/notify.ts +74 -66
  54. package/src/core/plugin-manager.ts +225 -0
  55. package/src/core/rule-engine.ts +287 -0
  56. package/src/core/rule-suggestion.ts +127 -0
  57. package/src/core/semantic-diff.ts +432 -0
  58. package/src/core/telemetry.ts +94 -0
  59. package/src/core/vaccine-registry.ts +212 -0
  60. package/src/core/validator-generator.ts +224 -0
  61. package/src/core/workspace.ts +28 -0
  62. package/src/core/yaml-minimal.ts +176 -0
  63. package/src/daemon/client.ts +78 -37
  64. package/src/daemon/event-batcher.ts +108 -0
  65. package/src/daemon/guards.ts +13 -0
  66. package/src/daemon/http-routes.ts +376 -0
  67. package/src/daemon/mcp-handler.ts +575 -0
  68. package/src/daemon/mcp-subscriptions.ts +81 -0
  69. package/src/daemon/mesh.ts +51 -0
  70. package/src/daemon/server.ts +655 -504
  71. package/src/daemon/types.ts +121 -0
  72. package/src/daemon/workspace-map.ts +104 -0
  73. package/src/platform.ts +60 -39
  74. package/src/version.ts +15 -0
  75. package/README.ko.md +0 -306
@@ -0,0 +1,110 @@
1
+ /**
2
+ * afd plugin — Third-party validator plugin manager
3
+ *
4
+ * Sub-commands:
5
+ * afd plugin install <npm-package> — install a validator plugin from npm
6
+ * afd plugin list — list installed plugins
7
+ * afd plugin remove <name> — uninstall a plugin
8
+ */
9
+
10
+ import { installPlugin, listPlugins, removePlugin } from "../core/plugin-manager";
11
+ import { getSystemLanguage } from "../core/locale";
12
+
13
+ const msgs = {
14
+ en: {
15
+ usage: `Usage:
16
+ afd plugin install <npm-package> Install a validator plugin from npm
17
+ afd plugin list List installed plugins
18
+ afd plugin remove <name> Uninstall a plugin`,
19
+ noPlugins: "No plugins installed.",
20
+ installing: "Installing",
21
+ installed: "Installed Plugins",
22
+ },
23
+ ko: {
24
+ usage: `사용법:
25
+ afd plugin install <npm-package> npm 플러그인 설치
26
+ afd plugin list 설치된 플러그인 목록
27
+ afd plugin remove <name> 플러그인 제거`,
28
+ noPlugins: "설치된 플러그인이 없습니다.",
29
+ installing: "설치 중",
30
+ installed: "설치된 플러그인",
31
+ },
32
+ };
33
+
34
+ const BOX = { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", ml: "├", mr: "┤" };
35
+ const W = 58;
36
+ function hline(l: string, r: string) { return `${l}${BOX.h.repeat(W)}${r}`; }
37
+ function row(s: string) {
38
+ const pad = Math.max(0, W - 2 - s.length);
39
+ return `${BOX.v} ${s}${" ".repeat(pad)} ${BOX.v}`;
40
+ }
41
+
42
+ export async function pluginCommand(subcommand?: string, arg?: string) {
43
+ const lang = getSystemLanguage();
44
+ const m = msgs[lang];
45
+
46
+ if (!subcommand) {
47
+ console.log(m.usage);
48
+ return;
49
+ }
50
+
51
+ switch (subcommand) {
52
+ case "install": {
53
+ if (!arg) {
54
+ console.error("Usage: afd plugin install <npm-package>");
55
+ process.exit(1);
56
+ }
57
+ console.log(`🔌 ${m.installing}: ${arg} …`);
58
+ const result = installPlugin(arg);
59
+ console.log(hline(BOX.tl, BOX.tr));
60
+ if (result.success) {
61
+ console.log(row(`✅ ${result.message}`));
62
+ if (result.manifest?.description) {
63
+ console.log(row(` ${result.manifest.description}`));
64
+ }
65
+ console.log(row(" Hot-reload active — daemon picks this up instantly."));
66
+ } else {
67
+ console.log(row(`❌ ${result.message}`));
68
+ }
69
+ console.log(hline(BOX.bl, BOX.br));
70
+ if (!result.success) process.exit(1);
71
+ break;
72
+ }
73
+
74
+ case "list": {
75
+ const plugins = listPlugins();
76
+ console.log(hline(BOX.tl, BOX.tr));
77
+ console.log(row(`🔌 ${m.installed}`));
78
+ console.log(hline(BOX.ml, BOX.mr));
79
+ if (plugins.length === 0) {
80
+ console.log(row(m.noPlugins));
81
+ } else {
82
+ for (const p of plugins) {
83
+ console.log(row(`📦 ${p.package}@${p.version}`));
84
+ if (p.description) console.log(row(` ${p.description}`));
85
+ console.log(row(` validator: .afd/validators/${p.validatorFile}`));
86
+ console.log(row(` installed: ${p.installDate.slice(0, 10)}`));
87
+ console.log(row(""));
88
+ }
89
+ }
90
+ console.log(hline(BOX.bl, BOX.br));
91
+ break;
92
+ }
93
+
94
+ case "remove": {
95
+ if (!arg) {
96
+ console.error("Usage: afd plugin remove <name>");
97
+ process.exit(1);
98
+ }
99
+ const result = removePlugin(arg);
100
+ console.log(hline(BOX.tl, BOX.tr));
101
+ console.log(row(result.success ? `✅ ${result.message}` : `❌ ${result.message}`));
102
+ console.log(hline(BOX.bl, BOX.br));
103
+ if (!result.success) process.exit(1);
104
+ break;
105
+ }
106
+
107
+ default:
108
+ console.log(m.usage);
109
+ }
110
+ }
@@ -0,0 +1,14 @@
1
+ import { stopCommand } from "./stop";
2
+ import { startCommand } from "./start";
3
+ import { getSystemLanguage } from "../core/locale";
4
+ import { getMessages } from "../core/i18n/messages";
5
+
6
+ export async function restartCommand() {
7
+ const lang = getSystemLanguage();
8
+ const msg = getMessages(lang);
9
+
10
+ console.log(msg.DAEMON_RESTARTING);
11
+
12
+ await stopCommand();
13
+ await startCommand();
14
+ }
@@ -1,208 +1,276 @@
1
- import { daemonRequest } from "../daemon/client";
2
- import { fmtNum, visualWidth, localizedBoast } from "../core/boast";
3
- import type { ShiftSummary } from "../core/boast";
4
- import { getSystemLanguage } from "../core/locale";
5
- import { getMessages, t } from "../core/i18n/messages";
6
-
7
- interface HologramScore {
8
- requests: number;
9
- originalChars: number;
10
- hologramChars: number;
11
- savings: number;
12
- }
13
-
14
- interface AutoHealEntry {
15
- id: string;
16
- at: number;
17
- }
18
-
19
- interface ImmuneScore {
20
- antibodies: number;
21
- autoHealed: number;
22
- lastAutoHeal: AutoHealEntry | null;
23
- }
24
-
25
- interface EcosystemEntry {
26
- name: string;
27
- confidence: string;
28
- }
29
-
30
- interface EcosystemScore {
31
- detected: EcosystemEntry[];
32
- primary: string;
33
- }
34
-
35
- interface SuppressionScore {
36
- massEventsSkipped: number;
37
- dormantTransitions: number;
38
- activeTaps: number;
39
- }
40
-
41
- interface ScoreData {
42
- uptime: number;
43
- filesDetected: number;
44
- totalEvents: number;
45
- lastEvent: string | null;
46
- lastEventAt: number | null;
47
- watchedFiles: string[];
48
- watchTargets: string[];
49
- hologram: HologramScore;
50
- immune: ImmuneScore;
51
- ecosystem: EcosystemScore;
52
- suppression: SuppressionScore;
53
- }
54
-
55
- function formatUptime(seconds: number): string {
56
- if (seconds < 60) return `${seconds}s`;
57
- if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
58
- const h = Math.floor(seconds / 3600);
59
- const m = Math.floor((seconds % 3600) / 60);
60
- return `${h}h ${m}m`;
61
- }
62
-
63
- function heatBar(value: number, max: number, width = 20): string {
64
- const filled = Math.min(Math.round((value / Math.max(max, 1)) * width), width);
65
- return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
66
- }
67
-
68
- function formatChars(n: number): string {
69
- if (n < 1000) return `${n}`;
70
- if (n < 1_000_000) return `${(n / 1000).toFixed(1)}K`;
71
- return `${(n / 1_000_000).toFixed(1)}M`;
72
- }
73
-
74
- const W = 46;
75
- const line = "\u2500".repeat(W);
76
- const sep = "\u2500".repeat(30);
77
-
78
- function row(content: string): string {
79
- const vw = visualWidth(content);
80
- const padSize = Math.max(0, W - vw);
81
- return `\u2502${content}${" ".repeat(padSize)}\u2502`;
82
- }
83
-
84
- function vwPad(s: string, target: number): string {
85
- const vw = visualWidth(s);
86
- return s + " ".repeat(Math.max(0, target - vw));
87
- }
88
-
89
- /** Render a labeled key-value row with visual-width-aware padding. */
90
- function kv(label: string, value: string): string {
91
- return row(` ${vwPad(label, 13)}: ${value}`);
92
- }
93
-
94
- export async function scoreCommand() {
95
- const lang = getSystemLanguage();
96
- const i18n = getMessages(lang);
97
-
98
- try {
99
- const data = await daemonRequest<ScoreData>("/score");
100
- const h = data.hologram;
101
-
102
- // Title
103
- console.log(`\u250C${line}\u2510`);
104
- console.log(row(` ${i18n.SCORE_TITLE}`));
105
- console.log(`\u251C${line}\u2524`);
106
-
107
- // Ecosystem
108
- console.log(kv(i18n.SCORE_ECOSYSTEM, data.ecosystem.primary));
109
- if (data.ecosystem.detected.length > 1) {
110
- const others = data.ecosystem.detected.slice(1).map(e => e.name).join(", ");
111
- console.log(kv(i18n.SCORE_ALSO_FOUND, others));
112
- }
113
-
114
- // Uptime / Events / Files
115
- console.log(`\u251C${line}\u2524`);
116
- console.log(kv(i18n.SCORE_UPTIME, formatUptime(data.uptime)));
117
- console.log(kv(i18n.SCORE_EVENTS, String(data.totalEvents)));
118
- console.log(kv(i18n.SCORE_FILES_FOUND, String(data.watchedFiles.length)));
119
- console.log(`\u251C${line}\u2524`);
120
- console.log(row(` ${vwPad(i18n.SCORE_ACTIVITY, 10)}${heatBar(data.totalEvents, 100)}`));
121
-
122
- // Hologram section
123
- console.log(`\u251C${line}\u2524`);
124
- console.log(row(` ${i18n.SCORE_HOLOGRAM_TITLE}`));
125
- console.log(row(` ${sep}`));
126
- if (h.requests > 0) {
127
- const saved = h.originalChars - h.hologramChars;
128
- console.log(kv(i18n.SCORE_HOLOGRAM_REQUESTS, String(h.requests)));
129
- console.log(kv(i18n.SCORE_HOLOGRAM_ORIGINAL, `${formatChars(h.originalChars)} chars`));
130
- console.log(kv(i18n.SCORE_HOLOGRAM_COMPRESSED, `${formatChars(h.hologramChars)} chars`));
131
- console.log(kv(i18n.SCORE_HOLOGRAM_SAVED, `${formatChars(saved)} chars (${h.savings}%)`));
132
- console.log(row(` ${vwPad(i18n.SCORE_HOLOGRAM_EFFICIENCY, 10)}${heatBar(h.savings, 100)}`));
133
- } else {
134
- console.log(row(` ${i18n.SCORE_HOLOGRAM_EMPTY}`));
135
- console.log(row(` ${i18n.SCORE_HOLOGRAM_HINT}`));
136
- }
137
-
138
- // Immune System section
139
- console.log(`\u251C${line}\u2524`);
140
- console.log(row(` ${i18n.SCORE_IMMUNE_TITLE}`));
141
- console.log(row(` ${sep}`));
142
- const ab = data.immune.antibodies;
143
- const ah = data.immune.autoHealed;
144
- const immuneLevel = ab === 0 ? i18n.SCORE_IMMUNE_VULNERABLE
145
- : ab < 3 ? i18n.SCORE_IMMUNE_LEARNING
146
- : ab < 6 ? i18n.SCORE_IMMUNE_GUARDED
147
- : i18n.SCORE_IMMUNE_FORTIFIED;
148
- console.log(kv(i18n.SCORE_ANTIBODIES, String(ab)));
149
- console.log(kv(i18n.SCORE_LEVEL, immuneLevel));
150
- console.log(row(` ${vwPad(i18n.SCORE_IMMUNITY, 10)}${heatBar(ab, 10)}`));
151
- const healedStr = t(i18n.SCORE_AUTO_HEALED, { count: ah, s: ah !== 1 ? "s" : "" });
152
- console.log(kv(i18n.SCORE_AUTO_HEALED_LABEL, healedStr));
153
- if (data.immune.lastAutoHeal) {
154
- const ago = formatUptime(Math.floor((Date.now() - data.immune.lastAutoHeal.at) / 1000));
155
- const healStr = t(i18n.SCORE_LAST_HEAL, { id: data.immune.lastAutoHeal.id, ago });
156
- console.log(kv(i18n.SCORE_LAST_EVENT, healStr));
157
- }
158
-
159
- // Watched files
160
- console.log(`\u251C${line}\u2524`);
161
- if (data.watchedFiles.length > 0) {
162
- console.log(row(` ${i18n.SCORE_WATCHED_FILES}`));
163
- for (const f of data.watchedFiles.slice(0, 8)) {
164
- console.log(row(` ${f.substring(0, W - 6)}`));
165
- }
166
- if (data.watchedFiles.length > 8) {
167
- console.log(row(` ... +${data.watchedFiles.length - 8} more`));
168
- }
169
- } else {
170
- console.log(row(` ${i18n.SCORE_NO_FILES}`));
171
- }
172
-
173
- // Last event
174
- if (data.lastEvent) {
175
- const ago = data.lastEventAt
176
- ? t(i18n.SCORE_AGO, { time: formatUptime(Math.floor((Date.now() - data.lastEventAt) / 1000)) })
177
- : "unknown";
178
- console.log(`\u251C${line}\u2524`);
179
- console.log(row(` ${vwPad(i18n.SCORE_LAST_EVENT, 6)}: ${data.lastEvent.substring(0, 34)}`));
180
- console.log(row(` ${ago}`));
181
- }
182
-
183
- // Value Metrics section
184
- try {
185
- const summary = await daemonRequest<ShiftSummary>("/shift-summary");
186
- console.log(`\u251C${line}\u2524`);
187
- console.log(row(` ${i18n.SCORE_VALUE_TITLE}`));
188
- console.log(row(` ${sep}`));
189
- console.log(kv(i18n.SHIFT_TOKENS, `~${fmtNum(summary.totalTokensSaved)}`));
190
- console.log(kv(i18n.SHIFT_TIME, `~${summary.totalMinutesSaved} min`));
191
- console.log(kv(i18n.SHIFT_COST, `~$${summary.totalCostSaved.toFixed(2)}`));
192
- if (summary.suppressionsSkipped > 0) {
193
- console.log(kv(i18n.SHIFT_SUPPRESSED, `${summary.suppressionsSkipped}`));
194
- }
195
- console.log(`\u251C${line}\u2524`);
196
- const boast = localizedBoast(lang);
197
- console.log(row(` \uD83D\uDDE3\uFE0F ${boast.substring(0, W - 6)}`));
198
- } catch {
199
- // Non-fatal
200
- }
201
-
202
- console.log(`\u2514${line}\u2518`);
203
- } catch (err: unknown) {
204
- const msg = err instanceof Error ? err.message : String(err);
205
- console.error(`[afd] ${msg}`);
206
- process.exit(1);
207
- }
208
- }
1
+ import { daemonRequest } from "../daemon/client";
2
+ import { fmtNum, visualWidth, localizedBoast } from "../core/boast";
3
+ import type { ShiftSummary } from "../core/boast";
4
+ import { getSystemLanguage } from "../core/locale";
5
+ import { getMessages, t } from "../core/i18n/messages";
6
+
7
+ interface HologramEntry {
8
+ requests: number;
9
+ originalChars: number;
10
+ hologramChars: number;
11
+ savings: number;
12
+ }
13
+
14
+ interface HologramDailyRow {
15
+ date: string;
16
+ requests: number;
17
+ originalChars: number;
18
+ hologramChars: number;
19
+ }
20
+
21
+ interface HologramScore {
22
+ lifetime: HologramEntry;
23
+ today: HologramEntry | null;
24
+ daily: HologramDailyRow[];
25
+ }
26
+
27
+ interface AutoHealEntry {
28
+ id: string;
29
+ at: number;
30
+ }
31
+
32
+ interface ImmuneScore {
33
+ antibodies: number;
34
+ autoHealed: number;
35
+ lastAutoHeal: AutoHealEntry | null;
36
+ }
37
+
38
+ interface EcosystemEntry {
39
+ name: string;
40
+ confidence: string;
41
+ }
42
+
43
+ interface EcosystemScore {
44
+ detected: EcosystemEntry[];
45
+ primary: string;
46
+ }
47
+
48
+ interface SuppressionScore {
49
+ massEventsSkipped: number;
50
+ dormantTransitions: number;
51
+ activeTaps: number;
52
+ }
53
+
54
+ interface DynamicImmuneScore {
55
+ activeValidators: number;
56
+ validatorNames: string[];
57
+ }
58
+
59
+ interface ScoreData {
60
+ uptime: number;
61
+ filesDetected: number;
62
+ totalEvents: number;
63
+ lastEvent: string | null;
64
+ lastEventAt: number | null;
65
+ watchedFiles: string[];
66
+ watchTargets: string[];
67
+ hologram: HologramScore;
68
+ immune: ImmuneScore;
69
+ ecosystem: EcosystemScore;
70
+ suppression: SuppressionScore;
71
+ dynamicImmune?: DynamicImmuneScore;
72
+ }
73
+
74
+ // ── ANSI helpers ──
75
+ const C = {
76
+ reset: "\x1b[0m",
77
+ bold: "\x1b[1m",
78
+ dim: "\x1b[2m",
79
+ red: "\x1b[31m",
80
+ green: "\x1b[32m",
81
+ yellow: "\x1b[33m",
82
+ blue: "\x1b[34m",
83
+ magenta: "\x1b[35m",
84
+ cyan: "\x1b[36m",
85
+ white: "\x1b[37m",
86
+ bgRed: "\x1b[41m",
87
+ bgGreen: "\x1b[42m",
88
+ bgYellow: "\x1b[43m",
89
+ };
90
+
91
+ function formatUptime(seconds: number): string {
92
+ if (seconds < 60) return `${seconds}s`;
93
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
94
+ const h = Math.floor(seconds / 3600);
95
+ const m = Math.floor((seconds % 3600) / 60);
96
+ return `${h}h ${m}m`;
97
+ }
98
+
99
+ function gaugeBar(value: number, max: number, width = 20): string {
100
+ const ratio = Math.min(value / Math.max(max, 1), 1);
101
+ const filled = Math.round(ratio * width);
102
+ const empty = width - filled;
103
+ const color = ratio >= 0.7 ? C.green : ratio >= 0.4 ? C.yellow : C.red;
104
+ const pct = Math.round(ratio * 100);
105
+ return `${color}${"█".repeat(filled)}${C.dim}${"░".repeat(empty)}${C.reset} ${pct}%`;
106
+ }
107
+
108
+ function formatChars(n: number): string {
109
+ if (n < 1000) return `${n}`;
110
+ if (n < 1_000_000) return `${(n / 1000).toFixed(1)}K`;
111
+ return `${(n / 1_000_000).toFixed(1)}M`;
112
+ }
113
+
114
+ const W = 52;
115
+ const line = "─".repeat(W);
116
+
117
+ function row(content: string): string {
118
+ // Strip ANSI for width calculation
119
+ const stripped = content.replace(/\x1b\[[0-9;]*m/g, "");
120
+ const vw = visualWidth(stripped);
121
+ const padSize = Math.max(0, W - vw);
122
+ return `│${content}${" ".repeat(padSize)}│`;
123
+ }
124
+
125
+ function section(title: string): string {
126
+ return row(` ${C.bold}${C.cyan}${title}${C.reset}`);
127
+ }
128
+
129
+ function kv(label: string, value: string): string {
130
+ const stripped = label.replace(/\x1b\[[0-9;]*m/g, "");
131
+ const gap = 18 - visualWidth(stripped);
132
+ return row(` ${C.dim}${label}${C.reset}${" ".repeat(Math.max(1, gap))}${value}`);
133
+ }
134
+
135
+ function guardianGrade(antibodies: number, heals: number, holoSavings: number): { grade: string; label: string; color: string } {
136
+ const score = Math.min(antibodies * 15, 40) + Math.min(heals * 10, 30) + Math.min(holoSavings * 0.3, 30);
137
+ if (score >= 80) return { grade: "A+", label: "FORTIFIED", color: C.green };
138
+ if (score >= 60) return { grade: "A", label: "GUARDED", color: C.green };
139
+ if (score >= 40) return { grade: "B", label: "LEARNING", color: C.yellow };
140
+ if (score >= 20) return { grade: "C", label: "EXPOSED", color: C.yellow };
141
+ return { grade: "D", label: "VULNERABLE", color: C.red };
142
+ }
143
+
144
+ export async function scoreCommand() {
145
+ const lang = getSystemLanguage();
146
+ const i18n = getMessages(lang);
147
+
148
+ try {
149
+ const data = await daemonRequest<ScoreData>("/score");
150
+ const h = data.hologram;
151
+ const holoSav = h.lifetime.savings;
152
+ const grade = guardianGrade(data.immune.antibodies, data.immune.autoHealed, holoSav);
153
+
154
+ const out: string[] = [];
155
+
156
+ // ── Header ──
157
+ out.push(`┌${line}┐`);
158
+ out.push(row(` ${C.bold}🛡️ Guardian Status${C.reset} ${grade.color}${C.bold}[ ${grade.grade} ]${C.reset} ${grade.color}${grade.label}${C.reset}`));
159
+ out.push(`├${line}┤`);
160
+
161
+ // ── Health Gauge ──
162
+ const healthPct = Math.min(data.immune.antibodies * 15 + data.immune.autoHealed * 10 + holoSav * 0.3, 100);
163
+ out.push(row(` ${gaugeBar(healthPct, 100, 30)}`));
164
+ out.push(`├${line}┤`);
165
+
166
+ // ── System Info ──
167
+ out.push(section(lang === "ko" ? "시스템 정보" : "System Info"));
168
+ out.push(kv(i18n.SCORE_ECOSYSTEM, `${C.white}${C.bold}${data.ecosystem.primary}${C.reset}`));
169
+ if (data.ecosystem.detected.length > 1) {
170
+ const others = data.ecosystem.detected.slice(1).map(e => e.name).join(", ");
171
+ out.push(kv(i18n.SCORE_ALSO_FOUND, `${C.dim}${others}${C.reset}`));
172
+ }
173
+ out.push(kv(i18n.SCORE_UPTIME, `${C.green}${formatUptime(data.uptime)}${C.reset}`));
174
+ out.push(kv(i18n.SCORE_EVENTS, `${data.totalEvents}`));
175
+ out.push(kv(i18n.SCORE_FILES_FOUND, `${data.watchedFiles.length}`));
176
+
177
+ // ── Immune System ──
178
+ out.push(`├${line}┤`);
179
+ out.push(section(lang === "ko" ? "면역 시스템" : "Immune System"));
180
+ const ab = data.immune.antibodies;
181
+ const ah = data.immune.autoHealed;
182
+ out.push(kv(i18n.SCORE_ANTIBODIES, `${C.bold}${ab}${C.reset}`));
183
+ out.push(row(` ${lang === "ko" ? "면역력" : "Immunity"} ${gaugeBar(ab, 10, 20)}`));
184
+ out.push(kv(lang === "ko" ? "차단 횟수" : "Prevented", `${C.bold}${C.green}${ah}${C.reset}${C.dim} disaster${ah !== 1 ? "s" : ""}${C.reset}`));
185
+ if (data.immune.lastAutoHeal) {
186
+ const ago = formatUptime(Math.floor((Date.now() - data.immune.lastAutoHeal.at) / 1000));
187
+ out.push(kv(lang === "ko" ? "마지막 치유" : "Last Heal", `${data.immune.lastAutoHeal.id} ${C.dim}(${ago} ago)${C.reset}`));
188
+ }
189
+
190
+ // ── Dynamic Immune Synthesis ──
191
+ if (data.dynamicImmune && data.dynamicImmune.activeValidators > 0) {
192
+ out.push(`├${line}┤`);
193
+ out.push(section(lang === "ko" ? "진화 상태 (동적 면역)" : "Evolution (Dynamic Immune)"));
194
+ out.push(kv(
195
+ lang === "ko" ? "활성 검증기" : "Validators",
196
+ `${C.bold}${C.green}${data.dynamicImmune.activeValidators} Active${C.reset}`
197
+ ));
198
+ const names = data.dynamicImmune.validatorNames.slice(0, 3).join(", ");
199
+ const extra = data.dynamicImmune.validatorNames.length > 3
200
+ ? ` ${C.dim}+${data.dynamicImmune.validatorNames.length - 3} more${C.reset}`
201
+ : "";
202
+ out.push(kv(
203
+ lang === "ko" ? "스크립트" : "Scripts",
204
+ `${C.dim}${names}${C.reset}${extra}`
205
+ ));
206
+ }
207
+
208
+ // ── Hologram Efficiency ──
209
+ out.push(`├${line}┤`);
210
+ out.push(section(lang === "ko" ? "토큰 효율 (홀로그램)" : "Token Efficiency (Hologram)"));
211
+ const lt = h.lifetime;
212
+ if (lt.requests > 0) {
213
+ const ltSaved = lt.originalChars - lt.hologramChars;
214
+ out.push(kv(lang === "ko" ? "총 요청" : "Requests", `${lt.requests}`));
215
+ out.push(kv(lang === "ko" ? "절약된 컨텍스트" : "Saved Context", `${C.green}${formatChars(ltSaved)} chars${C.reset}`));
216
+ out.push(row(` ${lang === "ko" ? "효율" : "Efficiency"} ${gaugeBar(lt.savings, 100, 20)}`));
217
+ if (h.today && h.today.requests > 0) {
218
+ const todaySaved = h.today.originalChars - h.today.hologramChars;
219
+ out.push(kv(lang === "ko" ? "오늘" : "Today", `${h.today.requests} req / ${C.green}${formatChars(todaySaved)} saved${C.reset}`));
220
+ }
221
+ } else {
222
+ out.push(row(` ${C.dim}${i18n.SCORE_HOLOGRAM_EMPTY}${C.reset}`));
223
+ out.push(row(` ${C.dim}${i18n.SCORE_HOLOGRAM_HINT}${C.reset}`));
224
+ }
225
+
226
+ // ── Value Delivered (ROI) ──
227
+ try {
228
+ const summary = await daemonRequest<ShiftSummary>("/shift-summary");
229
+ out.push(`├${line}┤`);
230
+ out.push(section(lang === "ko" ? "전달된 가치 (ROI)" : "Value Delivered (ROI)"));
231
+
232
+ // Breakdown: Auto-Heal
233
+ if (summary.healTokensSaved > 0) {
234
+ out.push(kv(
235
+ lang === "ko" ? "🩹 치유 절약" : "🩹 Heal Saved",
236
+ `${C.dim}~${fmtNum(summary.healTokensSaved)} tok / $${summary.healCostSaved.toFixed(2)}${C.reset}`
237
+ ));
238
+ }
239
+
240
+ // Breakdown: Hologram
241
+ if (summary.hologramTokensSaved > 0) {
242
+ out.push(kv(
243
+ lang === "ko" ? "💎 홀로그램" : "💎 Hologram",
244
+ `${C.dim}~${fmtNum(summary.hologramTokensSaved)} tok / $${summary.hologramCostSaved.toFixed(2)}${C.reset}`
245
+ ));
246
+ }
247
+
248
+ // Total
249
+ out.push(kv(
250
+ lang === "ko" ? "총 절약 토큰" : "Total Tokens",
251
+ `${C.bold}${C.green}~${fmtNum(summary.totalTokensSaved)}${C.reset}`
252
+ ));
253
+ out.push(kv(lang === "ko" ? "절약 시간" : "Time Saved", `${C.green}~${summary.totalMinutesSaved} min${C.reset}`));
254
+ out.push(kv(
255
+ lang === "ko" ? "총 절약 비용" : "Total Cost",
256
+ `${C.bold}${C.green}~$${summary.totalCostSaved.toFixed(2)}${C.reset}`
257
+ ));
258
+ if (summary.suppressionsSkipped > 0) {
259
+ out.push(kv(lang === "ko" ? "억제 횟수" : "Suppressed", `${summary.suppressionsSkipped}`));
260
+ }
261
+ } catch { /* non-fatal */ }
262
+
263
+ // ── Boast ──
264
+ out.push(`├${line}┤`);
265
+ const boast = localizedBoast(lang);
266
+ const truncBoast = boast.length > W - 6 ? boast.slice(0, W - 9) + "..." : boast;
267
+ out.push(row(` ${C.magenta}🗣️ ${truncBoast}${C.reset}`));
268
+ out.push(`└${line}┘`);
269
+
270
+ console.log(out.join("\n"));
271
+ } catch (err: unknown) {
272
+ const msg = err instanceof Error ? err.message : String(err);
273
+ console.error(`${C.red}[afd] ${msg}${C.reset}`);
274
+ process.exit(1);
275
+ }
276
+ }