autonomous-flow-daemon 1.6.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.
- package/CHANGELOG.md +85 -85
- package/LICENSE +21 -21
- package/README-ko.md +282 -0
- package/README.md +282 -266
- package/mcp-config.json +10 -10
- package/package.json +4 -2
- package/src/adapters/index.ts +370 -370
- package/src/cli.ts +162 -127
- package/src/commands/benchmark.ts +187 -187
- package/src/commands/correlate.ts +180 -0
- package/src/commands/dashboard.ts +404 -0
- package/src/commands/evolution.ts +84 -1
- package/src/commands/fix.ts +158 -158
- package/src/commands/lang.ts +41 -41
- package/src/commands/plugin.ts +110 -0
- package/src/commands/restart.ts +14 -14
- package/src/commands/score.ts +276 -276
- package/src/commands/start.ts +155 -155
- package/src/commands/status.ts +157 -157
- package/src/commands/stop.ts +68 -68
- package/src/commands/suggest.ts +211 -0
- package/src/commands/sync.ts +329 -16
- package/src/constants.ts +32 -32
- package/src/core/boast.ts +280 -280
- package/src/core/config.ts +49 -49
- package/src/core/correlation-engine.ts +265 -0
- package/src/core/db.ts +145 -117
- package/src/core/discovery.ts +65 -65
- package/src/core/federation.ts +129 -0
- package/src/core/hologram/engine.ts +71 -71
- package/src/core/hologram/fallback.ts +11 -11
- package/src/core/hologram/go-extractor.ts +203 -0
- package/src/core/hologram/incremental.ts +227 -227
- package/src/core/hologram/py-extractor.ts +132 -132
- package/src/core/hologram/rust-extractor.ts +244 -0
- package/src/core/hologram/ts-extractor.ts +406 -320
- package/src/core/hologram/types.ts +27 -25
- package/src/core/hologram.ts +73 -71
- package/src/core/i18n/messages.ts +309 -309
- package/src/core/locale.ts +88 -88
- package/src/core/log-rotate.ts +33 -33
- package/src/core/log-utils.ts +38 -38
- package/src/core/lru-map.ts +61 -61
- package/src/core/notify.ts +74 -74
- package/src/core/plugin-manager.ts +225 -0
- package/src/core/rule-suggestion.ts +127 -0
- package/src/core/validator-generator.ts +224 -0
- package/src/core/workspace.ts +28 -28
- package/src/daemon/client.ts +78 -65
- package/src/daemon/event-batcher.ts +108 -108
- package/src/daemon/guards.ts +13 -13
- package/src/daemon/http-routes.ts +376 -293
- package/src/daemon/mcp-handler.ts +575 -270
- package/src/daemon/mcp-subscriptions.ts +81 -0
- package/src/daemon/mesh.ts +51 -0
- package/src/daemon/server.ts +655 -590
- package/src/daemon/types.ts +121 -100
- package/src/daemon/workspace-map.ts +104 -92
- package/src/platform.ts +60 -60
- package/src/version.ts +15 -15
- package/README.ko.md +0 -266
package/src/daemon/types.ts
CHANGED
|
@@ -1,100 +1,121 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared types for the afd daemon modules.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { DetectionResult } from "../adapters/index";
|
|
6
|
-
import type { PatchOp } from "../core/immune";
|
|
7
|
-
import type { LruStringMap } from "../core/lru-map";
|
|
8
|
-
|
|
9
|
-
// ── Constants ──
|
|
10
|
-
export const DOUBLE_TAP_WINDOW_MS = 30_000;
|
|
11
|
-
export const MASS_EVENT_THRESHOLD = 3;
|
|
12
|
-
export const MASS_EVENT_WINDOW_MS = 1_000;
|
|
13
|
-
export const TAP_CLEANUP_INTERVAL_MS = 60_000;
|
|
14
|
-
export const SELF_WRITE_DEBOUNCE_MS = 100;
|
|
15
|
-
export const MAX_SSE_CLIENTS = 20;
|
|
16
|
-
export const VALIDATOR_TIMEOUT_MS = 500;
|
|
17
|
-
export const VALIDATORS_DIR = ".afd/validators";
|
|
18
|
-
|
|
19
|
-
// ── Types ──
|
|
20
|
-
export type ValidatorFn = (newContent: string, filePath: string) => boolean;
|
|
21
|
-
|
|
22
|
-
export interface HologramStats {
|
|
23
|
-
totalRequests: number;
|
|
24
|
-
totalOriginalChars: number;
|
|
25
|
-
totalHologramChars: number;
|
|
26
|
-
sessionOriginalChars: number;
|
|
27
|
-
sessionHologramChars: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface MistakeEntry {
|
|
31
|
-
mistake_type: string;
|
|
32
|
-
description: string;
|
|
33
|
-
timestamp: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the afd daemon modules.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DetectionResult } from "../adapters/index";
|
|
6
|
+
import type { PatchOp } from "../core/immune";
|
|
7
|
+
import type { LruStringMap } from "../core/lru-map";
|
|
8
|
+
|
|
9
|
+
// ── Constants ──
|
|
10
|
+
export const DOUBLE_TAP_WINDOW_MS = 30_000;
|
|
11
|
+
export const MASS_EVENT_THRESHOLD = 3;
|
|
12
|
+
export const MASS_EVENT_WINDOW_MS = 1_000;
|
|
13
|
+
export const TAP_CLEANUP_INTERVAL_MS = 60_000;
|
|
14
|
+
export const SELF_WRITE_DEBOUNCE_MS = 100;
|
|
15
|
+
export const MAX_SSE_CLIENTS = 20;
|
|
16
|
+
export const VALIDATOR_TIMEOUT_MS = 500;
|
|
17
|
+
export const VALIDATORS_DIR = ".afd/validators";
|
|
18
|
+
|
|
19
|
+
// ── Types ──
|
|
20
|
+
export type ValidatorFn = (newContent: string, filePath: string) => boolean;
|
|
21
|
+
|
|
22
|
+
export interface HologramStats {
|
|
23
|
+
totalRequests: number;
|
|
24
|
+
totalOriginalChars: number;
|
|
25
|
+
totalHologramChars: number;
|
|
26
|
+
sessionOriginalChars: number;
|
|
27
|
+
sessionHologramChars: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface MistakeEntry {
|
|
31
|
+
mistake_type: string;
|
|
32
|
+
description: string;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** SEAM 이벤트 로그 항목 (afd://events 리소스용) */
|
|
37
|
+
export interface SeamEventEntry {
|
|
38
|
+
phase: string;
|
|
39
|
+
msg: string;
|
|
40
|
+
ts: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 격리(quarantine) 로그 항목 (afd://quarantine 리소스용) */
|
|
44
|
+
export interface QuarantineLogEntry {
|
|
45
|
+
path: string;
|
|
46
|
+
ts: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface DaemonState {
|
|
50
|
+
startedAt: number;
|
|
51
|
+
filesDetected: number;
|
|
52
|
+
lastEvent: string | null;
|
|
53
|
+
lastEventAt: number | null;
|
|
54
|
+
watchedFiles: Set<string>;
|
|
55
|
+
hologramStats: HologramStats;
|
|
56
|
+
ecosystems: DetectionResult[];
|
|
57
|
+
autoHealCount: number;
|
|
58
|
+
autoHealLog: { id: string; at: number; file: string; healMs: number }[];
|
|
59
|
+
recentUnlinks: number[];
|
|
60
|
+
firstTapTimestamps: Map<string, number>;
|
|
61
|
+
suppressionSkippedCount: number;
|
|
62
|
+
dormantTransitions: { antibodyId: string; at: number }[];
|
|
63
|
+
totalFileBytesSaved: number;
|
|
64
|
+
totalSavedTokens: number;
|
|
65
|
+
fileSnapshots: LruStringMap;
|
|
66
|
+
sseClients: Set<ReadableStreamDefaultController<Uint8Array>>;
|
|
67
|
+
customValidators: Map<string, ValidatorFn>;
|
|
68
|
+
mistakeCache: Map<string, MistakeEntry[]>;
|
|
69
|
+
/** v1.9.0: 실시간 SEAM 이벤트 링 버퍼 (최근 200개) */
|
|
70
|
+
seamEventLog: SeamEventEntry[];
|
|
71
|
+
/** v1.9.0: 격리 이벤트 로그 (최근 100개) */
|
|
72
|
+
quarantineLog: QuarantineLogEntry[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface DaemonOptions {
|
|
76
|
+
mcp?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { type PatchOp, type DetectionResult };
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* DaemonContext — shared dependency bag passed to all daemon modules.
|
|
83
|
+
* Created once in main() and threaded through MCP/HTTP handlers.
|
|
84
|
+
*/
|
|
85
|
+
export interface DaemonContext {
|
|
86
|
+
state: DaemonState;
|
|
87
|
+
db: { query: (sql: string) => { get: () => unknown }; prepare: (sql: string) => { run: (...args: unknown[]) => void; get: (...args: unknown[]) => unknown; all: () => unknown[] } };
|
|
88
|
+
ws: { root: string; afdDir: string; pidFile: string; portFile: string; dbFile: string; logFile: string; quarantineDir: string };
|
|
89
|
+
|
|
90
|
+
// Prepared statements
|
|
91
|
+
insertEvent: { run: (...args: unknown[]) => void };
|
|
92
|
+
insertAntibody: { run: (...args: unknown[]) => void };
|
|
93
|
+
listAntibodies: { all: () => unknown[] };
|
|
94
|
+
antibodyIds: { all: () => { id: string }[] };
|
|
95
|
+
countAntibodies: { get: () => { cnt: number } };
|
|
96
|
+
getDailyAll: { all: () => { date: string; requests: number; original_chars: number; hologram_chars: number }[] };
|
|
97
|
+
insertTelemetry: { run: (...args: unknown[]) => void };
|
|
98
|
+
insertMistakeHistory: { run: (...args: unknown[]) => void };
|
|
99
|
+
queryMistakesByFile: { all: (...args: unknown[]) => MistakeEntry[] };
|
|
100
|
+
deleteMistakeOverflow: { run: (...args: unknown[]) => void };
|
|
101
|
+
|
|
102
|
+
// Helper functions
|
|
103
|
+
seam: (phase: string, msg: string) => void;
|
|
104
|
+
persistHologramStats: (originalChars: number, hologramChars: number) => void;
|
|
105
|
+
persistCtxSavings: (type: 'wsmap' | 'pinpoint', originalChars: number, savedChars: number) => void;
|
|
106
|
+
safeHologram: (filePath: string, source: string) => Promise<string>;
|
|
107
|
+
getWorkspaceMap: () => string;
|
|
108
|
+
getWorkspaceMapStats: () => { totalProjectBytes: number; mapBytes: number };
|
|
109
|
+
today: () => string;
|
|
110
|
+
getCtxSavingsDaily: { all: () => { date: string; type: string; requests: number; original_chars: number; saved_chars: number }[] };
|
|
111
|
+
getCtxSavingsLifetime: { all: () => { type: string; total_requests: number; total_original_chars: number; total_saved_chars: number }[] };
|
|
112
|
+
|
|
113
|
+
// Discovery
|
|
114
|
+
discoveryTargets: string[];
|
|
115
|
+
|
|
116
|
+
// Options
|
|
117
|
+
options: DaemonOptions;
|
|
118
|
+
|
|
119
|
+
// Mutable port (set after Bun.serve)
|
|
120
|
+
port: number;
|
|
121
|
+
}
|
|
@@ -1,92 +1,104 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workspace Map — cached project structure with export signatures.
|
|
3
|
-
* Provides afd://workspace-map MCP resource.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readdirSync, readFileSync, lstatSync } from "fs";
|
|
7
|
-
import { join } from "path";
|
|
8
|
-
|
|
9
|
-
const MAX_WALK_DEPTH = 8;
|
|
10
|
-
const SKIP_DIRS = new Set(["node_modules", ".afd", ".git", "dist", "coverage"]);
|
|
11
|
-
const CODE_EXTS = /\.[tj]sx?$/;
|
|
12
|
-
const DOC_EXTS = /\.(ts|js|tsx|jsx|json|md)$/;
|
|
13
|
-
|
|
14
|
-
/** Build a workspace map: file tree with sizes and export signatures */
|
|
15
|
-
function buildWorkspaceMap(): string {
|
|
16
|
-
const cwd = process.cwd();
|
|
17
|
-
const lines: string[] = [`# Workspace Map — ${cwd}`, `# Generated: ${new Date().toISOString()}`, ""];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (lst.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
lines.push(`${prefix}${entry} (${sizeKB}KB)`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Map — cached project structure with export signatures.
|
|
3
|
+
* Provides afd://workspace-map MCP resource.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readdirSync, readFileSync, lstatSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
|
|
9
|
+
const MAX_WALK_DEPTH = 8;
|
|
10
|
+
const SKIP_DIRS = new Set(["node_modules", ".afd", ".git", "dist", "coverage"]);
|
|
11
|
+
const CODE_EXTS = /\.[tj]sx?$/;
|
|
12
|
+
const DOC_EXTS = /\.(ts|js|tsx|jsx|json|md)$/;
|
|
13
|
+
|
|
14
|
+
/** Build a workspace map: file tree with sizes and export signatures */
|
|
15
|
+
function buildWorkspaceMap(): { map: string; totalProjectBytes: number } {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const lines: string[] = [`# Workspace Map — ${cwd}`, `# Generated: ${new Date().toISOString()}`, ""];
|
|
18
|
+
let totalProjectBytes = 0;
|
|
19
|
+
|
|
20
|
+
function walk(dir: string, prefix: string, depth: number) {
|
|
21
|
+
if (depth > MAX_WALK_DEPTH) return;
|
|
22
|
+
let entries: string[];
|
|
23
|
+
try { entries = readdirSync(dir).sort(); } catch { return; }
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (SKIP_DIRS.has(entry)) continue;
|
|
26
|
+
const fullPath = join(dir, entry);
|
|
27
|
+
try {
|
|
28
|
+
const lst = lstatSync(fullPath);
|
|
29
|
+
if (lst.isSymbolicLink()) continue;
|
|
30
|
+
if (lst.isDirectory()) {
|
|
31
|
+
lines.push(`${prefix}${entry}/`);
|
|
32
|
+
walk(fullPath, prefix + " ", depth + 1);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (!DOC_EXTS.test(entry)) continue;
|
|
36
|
+
totalProjectBytes += lst.size;
|
|
37
|
+
const sizeKB = (lst.size / 1024).toFixed(1);
|
|
38
|
+
if (CODE_EXTS.test(entry) && lst.size < 100 * 1024) {
|
|
39
|
+
try {
|
|
40
|
+
const source = readFileSync(fullPath, "utf-8");
|
|
41
|
+
const exports = source.match(/export\s+(?:async\s+)?(?:function|const|class|interface|type|enum)\s+(\w+)/g);
|
|
42
|
+
const sigs = exports ? exports.map(e => e.replace(/^export\s+(async\s+)?/, "").trim()).slice(0, 5).join(", ") : "";
|
|
43
|
+
const extra = exports && exports.length > 5 ? ` +${exports.length - 5} more` : "";
|
|
44
|
+
lines.push(`${prefix}${entry} (${sizeKB}KB)${sigs ? ` → ${sigs}${extra}` : ""}`);
|
|
45
|
+
} catch {
|
|
46
|
+
lines.push(`${prefix}${entry} (${sizeKB}KB)`);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
lines.push(`${prefix}${entry} (${sizeKB}KB)`);
|
|
50
|
+
}
|
|
51
|
+
} catch { /* skip */ }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
walk(join(cwd, "src"), " ", 0);
|
|
56
|
+
|
|
57
|
+
lines.push("", "# Root files");
|
|
58
|
+
for (const f of ["CLAUDE.md", "package.json", ".claudeignore", ".mcp.json"]) {
|
|
59
|
+
try {
|
|
60
|
+
const st = lstatSync(join(cwd, f));
|
|
61
|
+
totalProjectBytes += st.size;
|
|
62
|
+
lines.push(` ${f} (${(st.size / 1024).toFixed(1)}KB)`);
|
|
63
|
+
} catch { /* not found */ }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { map: lines.join("\n"), totalProjectBytes };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates a workspace map manager with lazy caching.
|
|
71
|
+
* Returns getWorkspaceMap() and markMapDirty() functions.
|
|
72
|
+
*/
|
|
73
|
+
export function createWorkspaceMap() {
|
|
74
|
+
let cache = "";
|
|
75
|
+
let dirty = true;
|
|
76
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
77
|
+
let lastTotalProjectBytes = 0;
|
|
78
|
+
let lastMapBytes = 0;
|
|
79
|
+
|
|
80
|
+
function get(): string {
|
|
81
|
+
if (dirty || !cache) {
|
|
82
|
+
const result = buildWorkspaceMap();
|
|
83
|
+
cache = result.map;
|
|
84
|
+
lastTotalProjectBytes = result.totalProjectBytes;
|
|
85
|
+
lastMapBytes = result.map.length;
|
|
86
|
+
dirty = false;
|
|
87
|
+
}
|
|
88
|
+
return cache;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getLastBuildStats() {
|
|
92
|
+
return { totalProjectBytes: lastTotalProjectBytes, mapBytes: lastMapBytes };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function markDirty() {
|
|
96
|
+
dirty = true;
|
|
97
|
+
if (timer) clearTimeout(timer);
|
|
98
|
+
timer = setTimeout(() => { get(); }, 5000);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getTimer() { return timer; }
|
|
102
|
+
|
|
103
|
+
return { get, getLastBuildStats, markDirty, getTimer };
|
|
104
|
+
}
|
package/src/platform.ts
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import { platform } from "os";
|
|
2
|
-
import type { SpawnOptions } from "child_process";
|
|
3
|
-
import { execSync } from "child_process";
|
|
4
|
-
|
|
5
|
-
export const IS_WINDOWS = platform() === "win32";
|
|
6
|
-
export const IS_MACOS = platform() === "darwin";
|
|
7
|
-
export const IS_LINUX = platform() === "linux";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Returns spawn options appropriate for detaching a background daemon.
|
|
11
|
-
* On Windows, `shell: true` is required for `detached` to create a new console.
|
|
12
|
-
*/
|
|
13
|
-
export function detachedSpawnOptions(
|
|
14
|
-
logFd: number,
|
|
15
|
-
): SpawnOptions {
|
|
16
|
-
const base: SpawnOptions = {
|
|
17
|
-
detached: true,
|
|
18
|
-
stdio: ["ignore", logFd, logFd],
|
|
19
|
-
cwd: process.cwd(),
|
|
20
|
-
env: { ...process.env },
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
if (IS_WINDOWS) {
|
|
24
|
-
// Windows needs shell:true for detached to work properly
|
|
25
|
-
// and windowsHide to prevent a console flash
|
|
26
|
-
return { ...base, shell: true, windowsHide: true };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return base;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const DIAGNOSE_ARGS = "diagnose --format a2a --auto-heal";
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Resolve the hook command for invoking afd diagnose.
|
|
36
|
-
* Priority:
|
|
37
|
-
* 1. Global `afd` binary (npm/bun global install)
|
|
38
|
-
* 2. `bunx afd` fallback (Bun environment)
|
|
39
|
-
* 3. `npx afd` fallback (Node environment)
|
|
40
|
-
*/
|
|
41
|
-
export function resolveHookCommand(): string {
|
|
42
|
-
if (isCommandAvailable("afd")) {
|
|
43
|
-
return `afd ${DIAGNOSE_ARGS}`;
|
|
44
|
-
}
|
|
45
|
-
if (isCommandAvailable("bunx")) {
|
|
46
|
-
return `bunx afd ${DIAGNOSE_ARGS}`;
|
|
47
|
-
}
|
|
48
|
-
return `npx -y afd ${DIAGNOSE_ARGS}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Check if a command exists on the system PATH */
|
|
52
|
-
function isCommandAvailable(cmd: string): boolean {
|
|
53
|
-
try {
|
|
54
|
-
const check = IS_WINDOWS ? `where ${cmd}` : `command -v ${cmd}`;
|
|
55
|
-
execSync(check, { stdio: "ignore" });
|
|
56
|
-
return true;
|
|
57
|
-
} catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
import { platform } from "os";
|
|
2
|
+
import type { SpawnOptions } from "child_process";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
|
|
5
|
+
export const IS_WINDOWS = platform() === "win32";
|
|
6
|
+
export const IS_MACOS = platform() === "darwin";
|
|
7
|
+
export const IS_LINUX = platform() === "linux";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns spawn options appropriate for detaching a background daemon.
|
|
11
|
+
* On Windows, `shell: true` is required for `detached` to create a new console.
|
|
12
|
+
*/
|
|
13
|
+
export function detachedSpawnOptions(
|
|
14
|
+
logFd: number,
|
|
15
|
+
): SpawnOptions {
|
|
16
|
+
const base: SpawnOptions = {
|
|
17
|
+
detached: true,
|
|
18
|
+
stdio: ["ignore", logFd, logFd],
|
|
19
|
+
cwd: process.cwd(),
|
|
20
|
+
env: { ...process.env },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (IS_WINDOWS) {
|
|
24
|
+
// Windows needs shell:true for detached to work properly
|
|
25
|
+
// and windowsHide to prevent a console flash
|
|
26
|
+
return { ...base, shell: true, windowsHide: true };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return base;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DIAGNOSE_ARGS = "diagnose --format a2a --auto-heal";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the hook command for invoking afd diagnose.
|
|
36
|
+
* Priority:
|
|
37
|
+
* 1. Global `afd` binary (npm/bun global install)
|
|
38
|
+
* 2. `bunx afd` fallback (Bun environment)
|
|
39
|
+
* 3. `npx afd` fallback (Node environment)
|
|
40
|
+
*/
|
|
41
|
+
export function resolveHookCommand(): string {
|
|
42
|
+
if (isCommandAvailable("afd")) {
|
|
43
|
+
return `afd ${DIAGNOSE_ARGS}`;
|
|
44
|
+
}
|
|
45
|
+
if (isCommandAvailable("bunx")) {
|
|
46
|
+
return `bunx afd ${DIAGNOSE_ARGS}`;
|
|
47
|
+
}
|
|
48
|
+
return `npx -y afd ${DIAGNOSE_ARGS}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Check if a command exists on the system PATH */
|
|
52
|
+
function isCommandAvailable(cmd: string): boolean {
|
|
53
|
+
try {
|
|
54
|
+
const check = IS_WINDOWS ? `where ${cmd}` : `command -v ${cmd}`;
|
|
55
|
+
execSync(check, { stdio: "ignore" });
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
let version = "0.0.0-unknown";
|
|
5
|
-
try {
|
|
6
|
-
const raw = readFileSync(resolve(import.meta.dirname, "..", "package.json"), "utf-8");
|
|
7
|
-
const pkg = JSON.parse(raw);
|
|
8
|
-
if (typeof pkg.version === "string" && pkg.version) {
|
|
9
|
-
version = pkg.version;
|
|
10
|
-
}
|
|
11
|
-
} catch {
|
|
12
|
-
// Bundled binary or missing package.json — fallback silently
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const APP_VERSION: string = version;
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
let version = "0.0.0-unknown";
|
|
5
|
+
try {
|
|
6
|
+
const raw = readFileSync(resolve(import.meta.dirname, "..", "package.json"), "utf-8");
|
|
7
|
+
const pkg = JSON.parse(raw);
|
|
8
|
+
if (typeof pkg.version === "string" && pkg.version) {
|
|
9
|
+
version = pkg.version;
|
|
10
|
+
}
|
|
11
|
+
} catch {
|
|
12
|
+
// Bundled binary or missing package.json — fallback silently
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const APP_VERSION: string = version;
|