autonomous-flow-daemon 1.0.0 β 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.ko.md +142 -125
- package/README.md +119 -134
- package/package.json +11 -5
- package/src/adapters/index.ts +247 -35
- package/src/cli.ts +79 -1
- package/src/commands/benchmark.ts +187 -0
- package/src/commands/diagnose.ts +56 -14
- package/src/commands/doctor.ts +243 -0
- package/src/commands/evolution.ts +107 -0
- package/src/commands/fix.ts +22 -2
- package/src/commands/hooks.ts +136 -0
- package/src/commands/lang.ts +41 -0
- package/src/commands/mcp.ts +129 -0
- package/src/commands/restart.ts +14 -0
- package/src/commands/score.ts +192 -64
- package/src/commands/start.ts +137 -37
- package/src/commands/stats.ts +103 -0
- package/src/commands/status.ts +157 -0
- package/src/commands/stop.ts +42 -9
- package/src/commands/sync.ts +253 -20
- package/src/commands/vaccine.ts +177 -0
- package/src/constants.ts +26 -1
- package/src/core/boast.ts +280 -0
- package/src/core/config.ts +49 -0
- package/src/core/db.ts +74 -3
- package/src/core/discovery.ts +65 -0
- package/src/core/evolution.ts +215 -0
- package/src/core/hologram/engine.ts +71 -0
- package/src/core/hologram/fallback.ts +11 -0
- package/src/core/hologram/incremental.ts +227 -0
- package/src/core/hologram/py-extractor.ts +132 -0
- package/src/core/hologram/ts-extractor.ts +320 -0
- package/src/core/hologram/types.ts +25 -0
- package/src/core/hologram.ts +64 -236
- package/src/core/hook-manager.ts +259 -0
- package/src/core/i18n/messages.ts +309 -0
- package/src/core/immune.ts +8 -123
- package/src/core/locale.ts +88 -0
- package/src/core/log-rotate.ts +33 -0
- package/src/core/log-utils.ts +38 -0
- package/src/core/lru-map.ts +61 -0
- package/src/core/notify.ts +53 -14
- package/src/core/rule-engine.ts +287 -0
- package/src/core/semantic-diff.ts +432 -0
- package/src/core/telemetry.ts +94 -0
- package/src/core/vaccine-registry.ts +212 -0
- package/src/core/workspace.ts +28 -0
- package/src/core/yaml-minimal.ts +176 -0
- package/src/daemon/client.ts +34 -6
- package/src/daemon/event-batcher.ts +108 -0
- package/src/daemon/guards.ts +13 -0
- package/src/daemon/http-routes.ts +293 -0
- package/src/daemon/mcp-handler.ts +270 -0
- package/src/daemon/server.ts +492 -273
- package/src/daemon/types.ts +100 -0
- package/src/daemon/workspace-map.ts +92 -0
- package/src/platform.ts +60 -0
- package/src/version.ts +15 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { resolveHookCommand } from "../platform";
|
|
4
|
+
|
|
5
|
+
export type HookOwner = "afd" | "omc" | "user";
|
|
6
|
+
|
|
7
|
+
export interface HookEntry {
|
|
8
|
+
id?: string;
|
|
9
|
+
matcher?: string;
|
|
10
|
+
command: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface HooksConfig {
|
|
15
|
+
hooks?: Record<string, HookEntry[]>;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ManagedHook {
|
|
20
|
+
id: string;
|
|
21
|
+
matcher: string;
|
|
22
|
+
command: string;
|
|
23
|
+
owner: HookOwner;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface HookConflict {
|
|
27
|
+
type: "matcher-overlap" | "duplicate-id";
|
|
28
|
+
hookA: ManagedHook;
|
|
29
|
+
hookB: ManagedHook;
|
|
30
|
+
resolution: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MergeResult {
|
|
34
|
+
merged: HookEntry[];
|
|
35
|
+
conflicts: HookConflict[];
|
|
36
|
+
changes: { added: string[]; removed: string[]; reordered: string[] };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface HookSummary {
|
|
40
|
+
zones: Record<HookOwner, ManagedHook[]>;
|
|
41
|
+
conflicts: HookConflict[];
|
|
42
|
+
orderingOk: boolean;
|
|
43
|
+
total: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Canonical set of afd-managed hooks.
|
|
48
|
+
* Only hooks in this set are removed during `stop --clean`.
|
|
49
|
+
* This prevents accidental deletion of user hooks with an `afd-` prefix
|
|
50
|
+
* (e.g., project-local `afd-read-gate` scripts).
|
|
51
|
+
*/
|
|
52
|
+
export const KNOWN_AFD_HOOKS = new Set(["afd-auto-heal"]);
|
|
53
|
+
|
|
54
|
+
/** afd's canonical desired hooks β authoritative source for merge. */
|
|
55
|
+
export function getAfdDesiredHooks(): HookEntry[] {
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
id: "afd-auto-heal",
|
|
59
|
+
matcher: "Write|Edit|MultiEdit",
|
|
60
|
+
command: resolveHookCommand(),
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Classify a hook's owner by id prefix. */
|
|
66
|
+
export function classifyOwner(id: string): HookOwner {
|
|
67
|
+
if (id.startsWith("afd-")) return "afd";
|
|
68
|
+
if (id.startsWith("omc-")) return "omc";
|
|
69
|
+
return "user";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Classify a list of hook entries into owner zones. */
|
|
73
|
+
export function classifyHooks(entries: HookEntry[]): Map<HookOwner, ManagedHook[]> {
|
|
74
|
+
const zones = new Map<HookOwner, ManagedHook[]>([
|
|
75
|
+
["afd", []],
|
|
76
|
+
["omc", []],
|
|
77
|
+
["user", []],
|
|
78
|
+
]);
|
|
79
|
+
let anonIdx = 0;
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const id = entry.id ?? `user-anonymous-${anonIdx++}`;
|
|
82
|
+
const owner = classifyOwner(id);
|
|
83
|
+
zones.get(owner)!.push({
|
|
84
|
+
id,
|
|
85
|
+
matcher: entry.matcher ?? "",
|
|
86
|
+
command: entry.command,
|
|
87
|
+
owner,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return zones;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Detect conflicts between hooks from different owners. */
|
|
94
|
+
export function detectConflicts(hooks: ManagedHook[]): HookConflict[] {
|
|
95
|
+
const conflicts: HookConflict[] = [];
|
|
96
|
+
|
|
97
|
+
// 1. Duplicate ID check
|
|
98
|
+
const idMap = new Map<string, ManagedHook>();
|
|
99
|
+
for (const hook of hooks) {
|
|
100
|
+
if (idMap.has(hook.id)) {
|
|
101
|
+
conflicts.push({
|
|
102
|
+
type: "duplicate-id",
|
|
103
|
+
hookA: idMap.get(hook.id)!,
|
|
104
|
+
hookB: hook,
|
|
105
|
+
resolution: `Remove or rename one of the duplicate '${hook.id}' hooks`,
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
idMap.set(hook.id, hook);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 2. Matcher overlap check (cross-owner only, O(n^2) β safe for <20 hooks)
|
|
113
|
+
for (let i = 0; i < hooks.length; i++) {
|
|
114
|
+
for (let j = i + 1; j < hooks.length; j++) {
|
|
115
|
+
if (hooks[i].owner === hooks[j].owner) continue;
|
|
116
|
+
const matcherA = hooks[i].matcher || "*";
|
|
117
|
+
const matcherB = hooks[j].matcher || "*";
|
|
118
|
+
const setA = new Set(matcherA.split("|").map(s => s.trim()));
|
|
119
|
+
const setB = new Set(matcherB.split("|").map(s => s.trim()));
|
|
120
|
+
const aIsWild = setA.has("*") || setA.has("");
|
|
121
|
+
const bIsWild = setB.has("*") || setB.has("");
|
|
122
|
+
const overlap =
|
|
123
|
+
aIsWild || bIsWild || [...setA].some(m => setB.has(m));
|
|
124
|
+
if (overlap) {
|
|
125
|
+
const shared =
|
|
126
|
+
aIsWild || bIsWild
|
|
127
|
+
? "*"
|
|
128
|
+
: [...setA].filter(m => setB.has(m)).join("|");
|
|
129
|
+
conflicts.push({
|
|
130
|
+
type: "matcher-overlap",
|
|
131
|
+
hookA: hooks[i],
|
|
132
|
+
hookB: hooks[j],
|
|
133
|
+
resolution: `Both hooks trigger on '${shared}'. Verify they don't conflict in behavior.`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return conflicts;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Read hooks.json from disk, returning empty config on missing/invalid file. */
|
|
143
|
+
export function readHooksFile(hooksPath: string): HooksConfig {
|
|
144
|
+
if (!existsSync(hooksPath)) return { hooks: {} };
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(readFileSync(hooksPath, "utf-8")) as HooksConfig;
|
|
147
|
+
} catch {
|
|
148
|
+
return { hooks: {} };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Write hooks.json to disk, creating parent directory if needed. */
|
|
153
|
+
export function writeHooksFile(hooksPath: string, config: HooksConfig): void {
|
|
154
|
+
mkdirSync(dirname(hooksPath), { recursive: true });
|
|
155
|
+
writeFileSync(hooksPath, JSON.stringify(config, null, 2), "utf-8");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Merge current hook entries with the desired afd hooks.
|
|
160
|
+
* Ordering guarantee: afd β omc β user.
|
|
161
|
+
* afd zone is fully authoritative β desired list overwrites existing afd hooks.
|
|
162
|
+
* omc and user zones are preserved as-is from the current file.
|
|
163
|
+
*/
|
|
164
|
+
export function mergeHooks(
|
|
165
|
+
current: HookEntry[],
|
|
166
|
+
desiredAfd: HookEntry[],
|
|
167
|
+
): MergeResult {
|
|
168
|
+
const changes: MergeResult["changes"] = { added: [], removed: [], reordered: [] };
|
|
169
|
+
|
|
170
|
+
const zones = classifyHooks(current);
|
|
171
|
+
const afdExisting = zones.get("afd")!;
|
|
172
|
+
const omcHooks = zones.get("omc")!;
|
|
173
|
+
const userHooks = zones.get("user")!;
|
|
174
|
+
|
|
175
|
+
// Build merged afd zone from desired list (authoritative)
|
|
176
|
+
const mergedAfd: HookEntry[] = desiredAfd.map(desired => {
|
|
177
|
+
const existing = afdExisting.find(h => h.id === desired.id);
|
|
178
|
+
if (!existing) changes.added.push(desired.id!);
|
|
179
|
+
return desired;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Track explicitly removed afd hooks (only canonical ones)
|
|
183
|
+
for (const existing of afdExisting) {
|
|
184
|
+
if (KNOWN_AFD_HOOKS.has(existing.id) && !desiredAfd.find(d => d.id === existing.id)) {
|
|
185
|
+
changes.removed.push(existing.id);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Detect reordering (only if no add/remove)
|
|
190
|
+
if (changes.added.length === 0 && changes.removed.length === 0) {
|
|
191
|
+
const originalOrder = current.map(h => h.id ?? "");
|
|
192
|
+
const newOrder = [
|
|
193
|
+
...mergedAfd.map(h => h.id!),
|
|
194
|
+
...omcHooks.map(h => h.id),
|
|
195
|
+
...userHooks.map(h => h.id),
|
|
196
|
+
];
|
|
197
|
+
const hasReordering =
|
|
198
|
+
originalOrder.length !== newOrder.length ||
|
|
199
|
+
originalOrder.some((id, i) => id !== (newOrder[i] ?? ""));
|
|
200
|
+
if (hasReordering) {
|
|
201
|
+
changes.reordered.push("hooks reordered to afd β omc β user zones");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const merged: HookEntry[] = [
|
|
206
|
+
...mergedAfd,
|
|
207
|
+
...omcHooks.map(({ id, matcher, command }) => ({ id, matcher, command })),
|
|
208
|
+
...userHooks.map(({ id, matcher, command }) => ({ id, matcher, command })),
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
const allManaged: ManagedHook[] = [
|
|
212
|
+
...mergedAfd.map(h => ({
|
|
213
|
+
id: h.id!,
|
|
214
|
+
matcher: h.matcher ?? "",
|
|
215
|
+
command: h.command,
|
|
216
|
+
owner: "afd" as HookOwner,
|
|
217
|
+
})),
|
|
218
|
+
...omcHooks,
|
|
219
|
+
...userHooks,
|
|
220
|
+
];
|
|
221
|
+
const conflicts = detectConflicts(allManaged);
|
|
222
|
+
|
|
223
|
+
return { merged, conflicts, changes };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Get a summary of current hook state for display and status commands. */
|
|
227
|
+
export function getHookSummary(hooksPath: string): HookSummary {
|
|
228
|
+
const config = readHooksFile(hooksPath);
|
|
229
|
+
const entries = config.hooks?.PreToolUse ?? [];
|
|
230
|
+
const zones = classifyHooks(entries);
|
|
231
|
+
|
|
232
|
+
const allHooks: ManagedHook[] = [
|
|
233
|
+
...(zones.get("afd") ?? []),
|
|
234
|
+
...(zones.get("omc") ?? []),
|
|
235
|
+
...(zones.get("user") ?? []),
|
|
236
|
+
];
|
|
237
|
+
const conflicts = detectConflicts(allHooks);
|
|
238
|
+
|
|
239
|
+
// Check ordering invariant: afd(0) β omc(1) β user(2)
|
|
240
|
+
const ownerPriority: Record<HookOwner, number> = { afd: 0, omc: 1, user: 2 };
|
|
241
|
+
let orderingOk = true;
|
|
242
|
+
let lastPriority = -1;
|
|
243
|
+
for (const entry of entries) {
|
|
244
|
+
const id = entry.id ?? "";
|
|
245
|
+
const priority = ownerPriority[classifyOwner(id)];
|
|
246
|
+
if (priority < lastPriority) {
|
|
247
|
+
orderingOk = false;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
lastPriority = priority;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
zones: Object.fromEntries(zones) as Record<HookOwner, ManagedHook[]>,
|
|
255
|
+
conflicts,
|
|
256
|
+
orderingOk,
|
|
257
|
+
total: entries.length,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bilingual Dictionary β "Boastful Doctor" persona
|
|
3
|
+
*
|
|
4
|
+
* Keys use template literals with {placeholders}.
|
|
5
|
+
* All arrays are variant pools β a random entry is picked at runtime.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SupportedLang } from "../locale";
|
|
9
|
+
|
|
10
|
+
export interface MessageDict {
|
|
11
|
+
// ββ Heal event ββ
|
|
12
|
+
HEAL_LOG: string; // "{fileName}" "{ms}" "{tokens}" "{mins}"
|
|
13
|
+
BOAST_HEAL: string[]; // witty one-liners after heal (1-in-5 chance)
|
|
14
|
+
BOAST_HEAL_PREFIX: string; // e.g. "[afd] π£οΈ"
|
|
15
|
+
|
|
16
|
+
// ββ Dormant transition ββ
|
|
17
|
+
BOAST_DORMANT: string[];
|
|
18
|
+
DORMANT_LOG: string; // "{id}" "{boast}"
|
|
19
|
+
|
|
20
|
+
// ββ Shift summary box ββ
|
|
21
|
+
SHIFT_TITLE: string;
|
|
22
|
+
SHIFT_ON_DUTY: string;
|
|
23
|
+
SHIFT_EVENTS: string;
|
|
24
|
+
SHIFT_HEALS: string;
|
|
25
|
+
SHIFT_TOKENS: string;
|
|
26
|
+
SHIFT_TIME: string;
|
|
27
|
+
SHIFT_COST: string;
|
|
28
|
+
SHIFT_SUPPRESSED: string;
|
|
29
|
+
SHIFT_RETIRED: string;
|
|
30
|
+
BOAST_SHIFT_END: string[];
|
|
31
|
+
|
|
32
|
+
// ββ Score dashboard ββ
|
|
33
|
+
SCORE_TITLE: string;
|
|
34
|
+
SCORE_ECOSYSTEM: string;
|
|
35
|
+
SCORE_ALSO_FOUND: string;
|
|
36
|
+
SCORE_UPTIME: string;
|
|
37
|
+
SCORE_EVENTS: string;
|
|
38
|
+
SCORE_FILES_FOUND: string;
|
|
39
|
+
SCORE_ACTIVITY: string;
|
|
40
|
+
SCORE_HOLOGRAM_TITLE: string;
|
|
41
|
+
SCORE_HOLOGRAM_REQUESTS: string;
|
|
42
|
+
SCORE_HOLOGRAM_ORIGINAL: string;
|
|
43
|
+
SCORE_HOLOGRAM_COMPRESSED: string;
|
|
44
|
+
SCORE_HOLOGRAM_SAVED: string;
|
|
45
|
+
SCORE_HOLOGRAM_EFFICIENCY: string;
|
|
46
|
+
SCORE_HOLOGRAM_EMPTY: string;
|
|
47
|
+
SCORE_HOLOGRAM_HINT: string;
|
|
48
|
+
SCORE_HOLOGRAM_TODAY: string;
|
|
49
|
+
SCORE_HOLOGRAM_LIFETIME: string;
|
|
50
|
+
SCORE_IMMUNE_TITLE: string;
|
|
51
|
+
SCORE_ANTIBODIES: string;
|
|
52
|
+
SCORE_LEVEL: string;
|
|
53
|
+
SCORE_IMMUNITY: string;
|
|
54
|
+
SCORE_AUTO_HEALED_LABEL: string;
|
|
55
|
+
SCORE_AUTO_HEALED: string; // "{count}" "{s}"
|
|
56
|
+
SCORE_LAST_HEAL: string; // "{id}" "{ago}"
|
|
57
|
+
SCORE_WATCHED_FILES: string;
|
|
58
|
+
SCORE_NO_FILES: string;
|
|
59
|
+
SCORE_LAST_EVENT: string;
|
|
60
|
+
SCORE_AGO: string; // "{time}"
|
|
61
|
+
SCORE_VALUE_TITLE: string;
|
|
62
|
+
SCORE_IMMUNE_VULNERABLE: string;
|
|
63
|
+
SCORE_IMMUNE_LEARNING: string;
|
|
64
|
+
SCORE_IMMUNE_GUARDED: string;
|
|
65
|
+
SCORE_IMMUNE_FORTIFIED: string;
|
|
66
|
+
|
|
67
|
+
// ββ Lang command ββ
|
|
68
|
+
LANG_CURRENT: string; // "{lang}"
|
|
69
|
+
LANG_CHANGED: string; // "{lang}"
|
|
70
|
+
LANG_SAVED: string; // "{path}"
|
|
71
|
+
LANG_LIST_TITLE: string;
|
|
72
|
+
LANG_INVALID: string; // "{lang}" "{supported}"
|
|
73
|
+
|
|
74
|
+
// ββ CLI messages ββ
|
|
75
|
+
DAEMON_ALREADY_RUNNING: string;
|
|
76
|
+
DAEMON_STARTED: string; // "{pid}" "{port}"
|
|
77
|
+
DAEMON_WATCHING: string; // "{count}" "{targets}"
|
|
78
|
+
DAEMON_LOGS: string; // "{path}"
|
|
79
|
+
DAEMON_STOPPED: string; // "{pid}"
|
|
80
|
+
DAEMON_KILLED: string; // "{pid}"
|
|
81
|
+
DAEMON_NOT_RUNNING: string;
|
|
82
|
+
DAEMON_NOT_RESPONDING: string;
|
|
83
|
+
DAEMON_START_FAILED: string; // "{path}"
|
|
84
|
+
DAEMON_RESTARTING: string;
|
|
85
|
+
|
|
86
|
+
// ββ Setup checklist ββ
|
|
87
|
+
SETUP_HEADER: string; // "{ecosystem}"
|
|
88
|
+
SETUP_HOOKS_NEW: string;
|
|
89
|
+
SETUP_HOOKS_OK: string;
|
|
90
|
+
SETUP_MCP_NEW: string;
|
|
91
|
+
SETUP_MCP_OK: string;
|
|
92
|
+
SETUP_MCP_SKIP: string;
|
|
93
|
+
SETUP_STATUS_NEW: string;
|
|
94
|
+
SETUP_STATUS_OK: string;
|
|
95
|
+
SETUP_STATUS_SKIP: string;
|
|
96
|
+
SETUP_DONE: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const en: MessageDict = {
|
|
100
|
+
HEAL_LOG: "[afd] π©Ή Healed {fileName} in {ms}ms | π Saved ~{tokens} tokens & {mins} mins of debugging",
|
|
101
|
+
BOAST_HEAL: [
|
|
102
|
+
"Dodged a bullet there! Restored in {ms}ms. You owe me a coffee β",
|
|
103
|
+
"Claude tried to delete a critical config. I said 'Not today.' π‘οΈ",
|
|
104
|
+
"Another mutation neutralized. The flow remains immortal. π",
|
|
105
|
+
"File vanished. I brought it back before you even blinked. ποΈ",
|
|
106
|
+
"That deletion looked suspicious. Good thing I was on shift. π¬",
|
|
107
|
+
"Patched faster than you can say 'git checkout'. No charge. π©Ί",
|
|
108
|
+
"A lesser daemon would have let that one slide. Not me. π¦ βπ‘οΈ",
|
|
109
|
+
"Config restored. Your AI agent will never know it was gone. π€«",
|
|
110
|
+
"Intercepted a fatal mutation mid-flight. Routine procedure. βοΈ",
|
|
111
|
+
"The immune system holds. Another day, another heal. πͺ",
|
|
112
|
+
],
|
|
113
|
+
BOAST_HEAL_PREFIX: "[afd] π£οΈ",
|
|
114
|
+
BOAST_DORMANT: [
|
|
115
|
+
"You deleted that twice? Fine, I'll respect your wishes, doctor. π«‘",
|
|
116
|
+
"Double-tap detected. Standing down β your call, chief. π€",
|
|
117
|
+
"I know when I'm not wanted. Antibody retired gracefully. π",
|
|
118
|
+
],
|
|
119
|
+
DORMANT_LOG: "[afd] π«‘ Antibody {id} retired. {boast}",
|
|
120
|
+
SHIFT_TITLE: "π₯ afd Shift Summary",
|
|
121
|
+
SHIFT_ON_DUTY: "On duty",
|
|
122
|
+
SHIFT_EVENTS: "Events",
|
|
123
|
+
SHIFT_HEALS: "Heals",
|
|
124
|
+
SHIFT_TOKENS: "Tokens saved",
|
|
125
|
+
SHIFT_TIME: "Time saved",
|
|
126
|
+
SHIFT_COST: "Cost saved",
|
|
127
|
+
SHIFT_SUPPRESSED: "Suppressed",
|
|
128
|
+
SHIFT_RETIRED: "Retired",
|
|
129
|
+
BOAST_SHIFT_END: [
|
|
130
|
+
"Another shift complete. The flow is stronger than ever. π",
|
|
131
|
+
"Signing off. Your configs are safe... for now. π",
|
|
132
|
+
"Shift ended. Not a single mutation got past me. Well, almost. π",
|
|
133
|
+
"Clocking out. Remember: I never sleep, I just pause. βΈοΈ",
|
|
134
|
+
"End of watch. Zero casualties on my side. π₯",
|
|
135
|
+
],
|
|
136
|
+
SCORE_TITLE: "afd score β Daemon Diagnostics",
|
|
137
|
+
SCORE_ECOSYSTEM: "Ecosystem",
|
|
138
|
+
SCORE_ALSO_FOUND: "Also found",
|
|
139
|
+
SCORE_UPTIME: "Uptime",
|
|
140
|
+
SCORE_EVENTS: "Events",
|
|
141
|
+
SCORE_FILES_FOUND: "Files Found",
|
|
142
|
+
SCORE_ACTIVITY: "Activity",
|
|
143
|
+
SCORE_HOLOGRAM_TITLE: "Context Efficiency (Hologram)",
|
|
144
|
+
SCORE_HOLOGRAM_REQUESTS: "Requests",
|
|
145
|
+
SCORE_HOLOGRAM_ORIGINAL: "Original",
|
|
146
|
+
SCORE_HOLOGRAM_COMPRESSED: "Hologram",
|
|
147
|
+
SCORE_HOLOGRAM_SAVED: "Saved",
|
|
148
|
+
SCORE_HOLOGRAM_EFFICIENCY: "Efficiency",
|
|
149
|
+
SCORE_HOLOGRAM_EMPTY: "No hologram requests yet.",
|
|
150
|
+
SCORE_HOLOGRAM_HINT: "Use: GET /hologram?file=<path>",
|
|
151
|
+
SCORE_HOLOGRAM_TODAY: "Today",
|
|
152
|
+
SCORE_HOLOGRAM_LIFETIME: "All-time",
|
|
153
|
+
SCORE_IMMUNE_TITLE: "Immune System",
|
|
154
|
+
SCORE_ANTIBODIES: "Antibodies",
|
|
155
|
+
SCORE_LEVEL: "Level",
|
|
156
|
+
SCORE_IMMUNITY: "Immunity",
|
|
157
|
+
SCORE_AUTO_HEALED_LABEL: "Auto-healed",
|
|
158
|
+
SCORE_AUTO_HEALED: "{count} background event{s}",
|
|
159
|
+
SCORE_LAST_HEAL: "{id} ({ago} ago)",
|
|
160
|
+
SCORE_WATCHED_FILES: "Watched Files:",
|
|
161
|
+
SCORE_NO_FILES: "No files detected yet.",
|
|
162
|
+
SCORE_LAST_EVENT: "Last",
|
|
163
|
+
SCORE_AGO: "{time} ago",
|
|
164
|
+
SCORE_VALUE_TITLE: "\uD83D\uDCC8 Value Delivered",
|
|
165
|
+
SCORE_IMMUNE_VULNERABLE: "Vulnerable",
|
|
166
|
+
SCORE_IMMUNE_LEARNING: "Learning",
|
|
167
|
+
SCORE_IMMUNE_GUARDED: "Guarded",
|
|
168
|
+
SCORE_IMMUNE_FORTIFIED: "Fortified",
|
|
169
|
+
LANG_CURRENT: "[afd] Current language: {lang}",
|
|
170
|
+
LANG_CHANGED: "[afd] Language changed to: {lang}",
|
|
171
|
+
LANG_SAVED: "[afd] Saved to {path}",
|
|
172
|
+
LANG_LIST_TITLE: "[afd] Supported languages:",
|
|
173
|
+
LANG_INVALID: "[afd] Unknown language '{lang}'. Supported: {supported}",
|
|
174
|
+
DAEMON_ALREADY_RUNNING: "\uD83D\uDEE1\uFE0F afd daemon is already running",
|
|
175
|
+
DAEMON_STARTED: "[afd] π‘οΈ Daemon started (pid={pid}, port={port})",
|
|
176
|
+
DAEMON_WATCHING: "[afd] π‘οΈ Smart Discovery: Watching {count} AI-context targets",
|
|
177
|
+
DAEMON_LOGS: "[afd] Logs: {path}",
|
|
178
|
+
DAEMON_STOPPED: "[afd] Daemon stopped (pid={pid})",
|
|
179
|
+
DAEMON_KILLED: "[afd] Daemon killed (pid={pid})",
|
|
180
|
+
DAEMON_NOT_RUNNING: "[afd] No daemon running.",
|
|
181
|
+
DAEMON_NOT_RESPONDING: "[afd] Daemon not responding. Cleaning up stale PID files.",
|
|
182
|
+
DAEMON_START_FAILED: "[afd] Failed to start daemon. Check logs: {path}",
|
|
183
|
+
DAEMON_RESTARTING: "[afd] π Restarting daemon...",
|
|
184
|
+
|
|
185
|
+
SETUP_HEADER: "[afd] Setting up {ecosystem} ecosystem:",
|
|
186
|
+
SETUP_HOOKS_NEW: " [+] Hook injected into PreToolUse",
|
|
187
|
+
SETUP_HOOKS_OK: " [=] Hook already present",
|
|
188
|
+
SETUP_MCP_NEW: " [+] MCP server registered in .mcp.json",
|
|
189
|
+
SETUP_MCP_OK: " [=] MCP server already registered",
|
|
190
|
+
SETUP_MCP_SKIP: " [-] MCP registration not available",
|
|
191
|
+
SETUP_STATUS_NEW: " [+] StatusLine configured",
|
|
192
|
+
SETUP_STATUS_OK: " [=] StatusLine already configured",
|
|
193
|
+
SETUP_STATUS_SKIP: " [-] StatusLine not available",
|
|
194
|
+
SETUP_DONE: "[afd] Zero-touch setup complete. All channels active.",
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const ko: MessageDict = {
|
|
198
|
+
HEAL_LOG: "[afd] π©Ή {fileName} μ΄λ €λμ΅λλ€ ({ms}ms) | π ν ν° ~{tokens}κ° & {mins}λΆ μκΌλ€μ",
|
|
199
|
+
BOAST_HEAL: [
|
|
200
|
+
"μνν λ»νλ€μ! {ms}ms λ§μ 볡ꡬ μλ£. μ»€νΌ ν μ μ¬μΈμ β",
|
|
201
|
+
"ν΄λ‘λκ° ν΅μ¬ μ€μ μ μ§μ°λ €κΈΈλ μ κ° μ»·νμ΅λλ€ π‘οΈ",
|
|
202
|
+
"λ³μ΄ ν λ λ μ‘μμ΅λλ€. μ½λ© νλ¦ λκΈ°μ§ μκ² π",
|
|
203
|
+
"νμΌμ΄ μμ λ λ»νμ§λ§ λ κΉμ§ν μ λλλ €λ¨μ΄μ ποΈ",
|
|
204
|
+
"μμν μμ§μμ κ°μ§νμ΅λλ€. μ κ° λΉμ§μ΄λΌ λ€νμΈ μ€ μμΈμ π¬",
|
|
205
|
+
"git checkout μΉκΈ°λ μ μ κ³ μ³λ¨μ΅λλ€. μ΄κ±΄ μλΉμ€μμ π©Ί",
|
|
206
|
+
"μΌλ° λ°λͺ¬μ΄μμΌλ©΄ λ© λλ Έκ² μ§λ§, μ μλμ£ π¦ βπ‘οΈ",
|
|
207
|
+
"μ€μ 볡ꡬ μλ£. AI μμ΄μ νΈλ κ°μͺ½κ°μ΄ λͺ¨λ₯Ό κ±°μμ π€«",
|
|
208
|
+
"μ¬κ°ν λ³μ΄λ₯Ό λΉν μ€μ μ격νμ΅λλ€. λ μλ μΌμ΄μ£ βοΈ",
|
|
209
|
+
"λ©΄μ μ²΄κ³ μ΄μ 무. μ€λλ ν 건 ν΄κ²°νμ΅λλ€ πͺ",
|
|
210
|
+
],
|
|
211
|
+
BOAST_HEAL_PREFIX: "[afd] π£οΈ",
|
|
212
|
+
BOAST_DORMANT: [
|
|
213
|
+
"λ λ²μ΄λ μ§μ°μκ² λ€κ³ μ? μκ² μ΅λλ€, λ»λλ‘ νμΈμ π«‘",
|
|
214
|
+
"λλΈ ν΄λ¦ κ°μ§. μ΄λ²μ μνμλ λλ‘ λ¬Όλ¬λ λ릴κ²μ π€",
|
|
215
|
+
"νμ μμΌμλ€λ©΄μΌ... ν체 μ°μνκ² ν΄μ₯ν©λλ€ π",
|
|
216
|
+
],
|
|
217
|
+
DORMANT_LOG: "[afd] π«‘ ν체 {id} μν΄. {boast}",
|
|
218
|
+
SHIFT_TITLE: "π₯ afd μ€λμ 근무 리ν¬νΈ",
|
|
219
|
+
SHIFT_ON_DUTY: "근무 μκ°",
|
|
220
|
+
SHIFT_EVENTS: "λ°μ μ΄λ²€νΈ",
|
|
221
|
+
SHIFT_HEALS: "μΉλ£ νμ",
|
|
222
|
+
SHIFT_TOKENS: "μ μ½ν ν ν°",
|
|
223
|
+
SHIFT_TIME: "μλ μκ°",
|
|
224
|
+
SHIFT_COST: "μ κ° λΉμ©",
|
|
225
|
+
SHIFT_SUPPRESSED: "μ΅μ λ¨",
|
|
226
|
+
SHIFT_RETIRED: "μν΄ν¨",
|
|
227
|
+
BOAST_SHIFT_END: [
|
|
228
|
+
"μ€λ 근무 λ. λλΆμ νλ‘μ νΈκ° λ νΌνΌν΄μ‘λ€μ π",
|
|
229
|
+
"ν΄κ·Όν©λλ€. μ€μ νμΌλ€μ μμ ν΄μ... μμ§μμ π",
|
|
230
|
+
"근무 μ’
λ£. λ¨ νλμ λ³μ΄λ λμΉμ§ μμμ΅λλ€. (μλ§λμ) π",
|
|
231
|
+
"ν΄κ·Όν κ²μ. μ μλ κ² μλλΌ μ μ λ©μΆλ κ²λλ€ βΈοΈ",
|
|
232
|
+
"λΉμ§ μ’
λ£. μ ꡬμ μ¬μμλ μμ΅λλ€ π₯",
|
|
233
|
+
],
|
|
234
|
+
SCORE_TITLE: "afd score β νλ‘μ νΈ κ±΄κ° κ²μ§",
|
|
235
|
+
SCORE_ECOSYSTEM: "μμ½μμ€ν
",
|
|
236
|
+
SCORE_ALSO_FOUND: "μΆκ° κ°μ§",
|
|
237
|
+
SCORE_UPTIME: "κ°λ μκ°",
|
|
238
|
+
SCORE_EVENTS: "μ΄λ²€νΈ",
|
|
239
|
+
SCORE_FILES_FOUND: "κ°μ§λ νμΌ",
|
|
240
|
+
SCORE_ACTIVITY: "νλλ",
|
|
241
|
+
SCORE_HOLOGRAM_TITLE: "컨ν
μ€νΈ ν¨μ¨ (Hologram)",
|
|
242
|
+
SCORE_HOLOGRAM_REQUESTS: "μμ² μ",
|
|
243
|
+
SCORE_HOLOGRAM_ORIGINAL: "μλ³Έ ν¬κΈ°",
|
|
244
|
+
SCORE_HOLOGRAM_COMPRESSED: "νλ‘κ·Έλ¨",
|
|
245
|
+
SCORE_HOLOGRAM_SAVED: "μ μ½λ¨",
|
|
246
|
+
SCORE_HOLOGRAM_EFFICIENCY: "μμΆ ν¨μ¨",
|
|
247
|
+
SCORE_HOLOGRAM_EMPTY: "μμ§ νλ‘κ·Έλ¨ μμ²μ΄ μμ΅λλ€.",
|
|
248
|
+
SCORE_HOLOGRAM_HINT: "μ¬μ©λ²: GET /hologram?file=<κ²½λ‘>",
|
|
249
|
+
SCORE_HOLOGRAM_TODAY: "μ€λ",
|
|
250
|
+
SCORE_HOLOGRAM_LIFETIME: "λμ ",
|
|
251
|
+
SCORE_IMMUNE_TITLE: "λ©΄μ μμ€ν
",
|
|
252
|
+
SCORE_ANTIBODIES: "ν체 μ",
|
|
253
|
+
SCORE_LEVEL: "λ°©μ΄ λ 벨",
|
|
254
|
+
SCORE_IMMUNITY: "λ©΄μλ ₯",
|
|
255
|
+
SCORE_AUTO_HEALED_LABEL: "μλ μΉμ ",
|
|
256
|
+
SCORE_AUTO_HEALED: "{count}건 λ°±κ·ΈλΌμ΄λ μΉμ λ¨",
|
|
257
|
+
SCORE_LAST_HEAL: "{id} ({ago} μ )",
|
|
258
|
+
SCORE_WATCHED_FILES: "κ°μ μ€μΈ νμΌ:",
|
|
259
|
+
SCORE_NO_FILES: "μμ§ κ°μ§λ νμΌμ΄ μμ΅λλ€.",
|
|
260
|
+
SCORE_LAST_EVENT: "μ΅κ·Ό κΈ°λ‘",
|
|
261
|
+
SCORE_AGO: "{time} μ ",
|
|
262
|
+
SCORE_VALUE_TITLE: "π μ λ¬λ κ°μΉ",
|
|
263
|
+
SCORE_IMMUNE_VULNERABLE: "μ·¨μ½",
|
|
264
|
+
SCORE_IMMUNE_LEARNING: "νμ΅ μ€",
|
|
265
|
+
SCORE_IMMUNE_GUARDED: "κ²½κ³ μ€",
|
|
266
|
+
SCORE_IMMUNE_FORTIFIED: "μ² ν΅ λ°©μ΄",
|
|
267
|
+
LANG_CURRENT: "[afd] νμ¬ μΈμ΄: {lang}",
|
|
268
|
+
LANG_CHANGED: "[afd] μΈμ΄κ° λ³κ²½λμμ΅λλ€: {lang}",
|
|
269
|
+
LANG_SAVED: "[afd] μ μ₯ μλ£ β {path}",
|
|
270
|
+
LANG_LIST_TITLE: "[afd] μ§μνλ μΈμ΄:",
|
|
271
|
+
LANG_INVALID: "[afd] '{lang}' μ(λ) μ μ μλ μΈμ΄μμ. μ§μ: {supported}",
|
|
272
|
+
DAEMON_ALREADY_RUNNING: "π‘οΈ afd λ°λͺ¬μ΄ μ΄λ―Έ μ΄μ¬ν μΌνκ³ μμ΅λλ€",
|
|
273
|
+
DAEMON_STARTED: "[afd] π‘οΈ λ°λͺ¬ μμ (pid={pid}, port={port})",
|
|
274
|
+
DAEMON_WATCHING: "[afd] π‘οΈ μ€λ§νΈ νμ μ€: AI 컨ν
μ€νΈ λμ {count}κ° κ°μ μμ",
|
|
275
|
+
DAEMON_LOGS: "[afd] λ‘κ·Έ μμΉ: {path}",
|
|
276
|
+
DAEMON_STOPPED: "[afd] λ°λͺ¬μ΄ μ€μ§λμμ΅λλ€ (pid={pid})",
|
|
277
|
+
DAEMON_KILLED: "[afd] λ°λͺ¬ κ°μ μ’
λ£ μλ£ (pid={pid})",
|
|
278
|
+
DAEMON_NOT_RUNNING: "[afd] μ€ν μ€μΈ λ°λͺ¬μ μ°Ύμ μ μμ΅λλ€.",
|
|
279
|
+
DAEMON_NOT_RESPONDING: "[afd] λ°λͺ¬μ΄ μλ΅νμ§ μλ€μ. λ¨μ PID νμΌμ μ 리ν©λλ€.",
|
|
280
|
+
DAEMON_START_FAILED: "[afd] λ°λͺ¬ μμ μ€ν¨. λ‘κ·Έλ₯Ό νμΈν΄ 보μΈμ: {path}",
|
|
281
|
+
DAEMON_RESTARTING: "[afd] π λ°λͺ¬μ μ¬μμν©λλ€...",
|
|
282
|
+
|
|
283
|
+
SETUP_HEADER: "[afd] {ecosystem} μμ½μμ€ν
μ€μ μ€:",
|
|
284
|
+
SETUP_HOOKS_NEW: " [+] PreToolUse ν
μ£Όμ
μλ£",
|
|
285
|
+
SETUP_HOOKS_OK: " [=] ν
μ΄λ―Έ μ€μ λ¨",
|
|
286
|
+
SETUP_MCP_NEW: " [+] MCP μλ² λ±λ‘ μλ£ (.mcp.json)",
|
|
287
|
+
SETUP_MCP_OK: " [=] MCP μλ² μ΄λ―Έ λ±λ‘λ¨",
|
|
288
|
+
SETUP_MCP_SKIP: " [-] MCP λ±λ‘ λ―Έμ§μ",
|
|
289
|
+
SETUP_STATUS_NEW: " [+] StatusLine μ€μ μλ£",
|
|
290
|
+
SETUP_STATUS_OK: " [=] StatusLine μ΄λ―Έ μ€μ λ¨",
|
|
291
|
+
SETUP_STATUS_SKIP: " [-] StatusLine λ―Έμ§μ",
|
|
292
|
+
SETUP_DONE: "[afd] μ λ‘ν°μΉ μ€μ μλ£. λͺ¨λ μ±λ νμ±ν.",
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const dictionaries: Record<SupportedLang, MessageDict> = { en, ko };
|
|
296
|
+
|
|
297
|
+
/** Get the full dictionary for a language. */
|
|
298
|
+
export function getMessages(lang: SupportedLang): MessageDict {
|
|
299
|
+
return dictionaries[lang];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Template interpolation: replaces {key} with values. */
|
|
303
|
+
export function t(template: string, vars: Record<string, string | number> = {}): string {
|
|
304
|
+
let result = template;
|
|
305
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
306
|
+
result = result.replaceAll(`{${key}}`, String(val));
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
309
|
+
}
|