mover-os 4.7.7 → 4.7.9
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/README.md +34 -24
- package/install.js +2868 -251
- package/package.json +15 -3
- package/src/dashboard/build.js +1541 -0
- package/src/dashboard/dashboard.js +276 -0
- package/src/dashboard/index.js +319 -0
- package/src/dashboard/lib/activation-log.js +297 -0
- package/src/dashboard/lib/active-context-parser.js +189 -0
- package/src/dashboard/lib/agent-command.js +93 -0
- package/src/dashboard/lib/agent-detect.js +255 -0
- package/src/dashboard/lib/agent-detector.js +92 -0
- package/src/dashboard/lib/agent-session.js +483 -0
- package/src/dashboard/lib/anti-identity-detector.js +116 -0
- package/src/dashboard/lib/approval-registry.js +170 -0
- package/src/dashboard/lib/auto-learnings-parser.js +183 -0
- package/src/dashboard/lib/cli-usage-parser.js +92 -0
- package/src/dashboard/lib/config-parser.js +109 -0
- package/src/dashboard/lib/connect-recommender.js +131 -0
- package/src/dashboard/lib/correlations-parser.js +231 -0
- package/src/dashboard/lib/daily-note-resolver.js +228 -0
- package/src/dashboard/lib/date-utils.js +43 -0
- package/src/dashboard/lib/distribution-parser.js +137 -0
- package/src/dashboard/lib/dossier-parser.js +64 -0
- package/src/dashboard/lib/drift-history.js +88 -0
- package/src/dashboard/lib/drift-score.js +119 -0
- package/src/dashboard/lib/engine-default-fingerprints.json +53 -0
- package/src/dashboard/lib/engine-health.js +173 -0
- package/src/dashboard/lib/engine-writer.js +1831 -0
- package/src/dashboard/lib/execution-plan.js +125 -0
- package/src/dashboard/lib/experiments-parser.js +429 -0
- package/src/dashboard/lib/feed-parser.js +294 -0
- package/src/dashboard/lib/forked-future.js +60 -0
- package/src/dashboard/lib/goal-forecast.js +427 -0
- package/src/dashboard/lib/goals-parser.js +67 -0
- package/src/dashboard/lib/health-protocols-parser.js +133 -0
- package/src/dashboard/lib/hook-activity.js +48 -0
- package/src/dashboard/lib/hook-indexer.js +169 -0
- package/src/dashboard/lib/hourly-activity-parser.js +143 -0
- package/src/dashboard/lib/identity-parser.js +85 -0
- package/src/dashboard/lib/ingestion.js +418 -0
- package/src/dashboard/lib/library-indexer-v2.js +226 -0
- package/src/dashboard/lib/library-indexer.js +105 -0
- package/src/dashboard/lib/library-search.js +290 -0
- package/src/dashboard/lib/log-activation.sh +61 -0
- package/src/dashboard/lib/memory-curator.js +97 -0
- package/src/dashboard/lib/memory-gardener.js +177 -0
- package/src/dashboard/lib/memory-gepa.js +102 -0
- package/src/dashboard/lib/memory-index.js +519 -0
- package/src/dashboard/lib/memory-rerank.js +72 -0
- package/src/dashboard/lib/memory-text.js +136 -0
- package/src/dashboard/lib/metrics-log-parser.js +76 -0
- package/src/dashboard/lib/moves-usage-parser.js +184 -0
- package/src/dashboard/lib/onboarding-forge.js +70 -0
- package/src/dashboard/lib/override-outcome-parser.js +68 -0
- package/src/dashboard/lib/override-summary.js +73 -0
- package/src/dashboard/lib/paths.js +192 -0
- package/src/dashboard/lib/pattern-manifest-loader.js +29 -0
- package/src/dashboard/lib/phantom-strategy.js +129 -0
- package/src/dashboard/lib/pid-markers.js +80 -0
- package/src/dashboard/lib/project-scanner.js +121 -0
- package/src/dashboard/lib/promise-wall.js +88 -0
- package/src/dashboard/lib/record-score.js +173 -0
- package/src/dashboard/lib/redaction.js +140 -0
- package/src/dashboard/lib/refusal-parser.js +44 -0
- package/src/dashboard/lib/regenerate-manifest.js +206 -0
- package/src/dashboard/lib/rewind-snapshots.js +689 -0
- package/src/dashboard/lib/roast-wall-parser.js +200 -0
- package/src/dashboard/lib/run-registry.js +286 -0
- package/src/dashboard/lib/safe-write.js +63 -0
- package/src/dashboard/lib/session-log-parser.js +145 -0
- package/src/dashboard/lib/session-time-parser.js +158 -0
- package/src/dashboard/lib/skill-index.js +171 -0
- package/src/dashboard/lib/skill-indexer.js +118 -0
- package/src/dashboard/lib/skill-recommender.js +689 -0
- package/src/dashboard/lib/state-core/backfill.js +298 -0
- package/src/dashboard/lib/state-core/dryrun.js +188 -0
- package/src/dashboard/lib/state-core/event-log.js +615 -0
- package/src/dashboard/lib/state-core/events.js +265 -0
- package/src/dashboard/lib/state-core/projections.js +376 -0
- package/src/dashboard/lib/state-core/start-close.js +162 -0
- package/src/dashboard/lib/state-core/trial.js +96 -0
- package/src/dashboard/lib/strategy-parser.js +248 -0
- package/src/dashboard/lib/strategy-protocol-parser.js +135 -0
- package/src/dashboard/lib/streak-parser.js +95 -0
- package/src/dashboard/lib/suggested-now.js +254 -0
- package/src/dashboard/lib/tool-awareness.js +125 -0
- package/src/dashboard/lib/transcript-parser.js +371 -0
- package/src/dashboard/lib/vault-graph-parser.js +205 -0
- package/src/dashboard/lib/view-generator.js +163 -0
- package/src/dashboard/lib/voice-dna-parser.js +57 -0
- package/src/dashboard/lib/walkthrough-script.js +140 -0
- package/src/dashboard/lib/workflow-graph-parser.js +170 -0
- package/src/dashboard/lib/workflow-library-parser.js +146 -0
- package/src/dashboard/server.js +2402 -0
- package/src/dashboard/shortcut.js +284 -0
- package/src/dashboard/static/setup-poc.html +306 -0
- package/src/dashboard/static/walkthrough-poc.html +580 -0
- package/src/dashboard/styles.css +1201 -0
- package/src/dashboard/templates/index.html +278 -0
- package/src/dashboard/ui/dist/assets/hanken-grotesk-latin-ext-wght-normal-Dg-wlmqe.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/hanken-grotesk-latin-wght-normal-CaVRRdDk.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/hanken-grotesk-vietnamese-wght-normal-CHiFlh_0.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/hero.png +0 -0
- package/src/dashboard/ui/dist/assets/index-ByVKPvLf.js +34 -0
- package/src/dashboard/ui/dist/assets/index-CCoKjUcC.js +161 -0
- package/src/dashboard/ui/dist/assets/index-CZWNQDt5.css +1 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/src/dashboard/ui/dist/assets/mover-system.css +62 -0
- package/src/dashboard/ui/dist/assets/xterm-CqkleIqs.js +1 -0
- package/src/dashboard/ui/dist/icon.svg +4 -0
- package/src/dashboard/ui/dist/index.html +18 -0
- package/src/dashboard/ui/dist/manifest.webmanifest +1 -0
- package/src/dashboard/ui/dist/registerSW.js +1 -0
- package/src/dashboard/ui/dist/sw.js +1 -0
- package/src/dashboard/ui/dist/workbox-39fa566e.js +1 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* agent-detect.js — D7 agent detection for the onboarding connect step.
|
|
5
|
+
*
|
|
6
|
+
* Scope: claude / codex / gemini ONLY — the three adapters defined in
|
|
7
|
+
* agent-session.js ADAPTERS. Wider agent survey (Cursor, Windsurf, etc.)
|
|
8
|
+
* lives in agent-detector.js (the v5 dashboard Library tab surface).
|
|
9
|
+
*
|
|
10
|
+
* Contract per agent:
|
|
11
|
+
* id — adapter key matching ADAPTERS in agent-session.js
|
|
12
|
+
* present — binary is on PATH (binary probe, non-interactive, 800ms cap)
|
|
13
|
+
* authed — best-effort, non-interactive auth signal (see per-vendor notes)
|
|
14
|
+
* runnable — present AND the ADAPTERS entry is fully wired (not unsupported)
|
|
15
|
+
*
|
|
16
|
+
* Auth signals (non-interactive only — footgun §4: never spawn interactive login):
|
|
17
|
+
* claude : ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, Claude Code keychain
|
|
18
|
+
* credentials on macOS, or ~/.claude/.credentials.json on Windows/Linux.
|
|
19
|
+
* codex : verify-next adapter — authed=false (cannot probe without spawning).
|
|
20
|
+
* gemini : verify-next adapter — authed=false (footgun §4: --acp can prompt
|
|
21
|
+
* for login from a non-terminal parent; never probe here).
|
|
22
|
+
*
|
|
23
|
+
* Never spawns an interactive process. Only uses:
|
|
24
|
+
* - execFile("sh", ["-c", "command -v '<bin>'"], { timeout: 800 }) — async
|
|
25
|
+
* - process.env lookup + credential files/keychain reads
|
|
26
|
+
*
|
|
27
|
+
* Wired adapters (from ADAPTERS in agent-session.js, 2026-06-18):
|
|
28
|
+
* claude-code → wired (cmd + args + normalize + encodeTurn)
|
|
29
|
+
* codex → unsupported (verify-next)
|
|
30
|
+
* gemini → unsupported (verify-next)
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const fs = require("fs");
|
|
34
|
+
const os = require("os");
|
|
35
|
+
const path = require("path");
|
|
36
|
+
const { execFile, execFileSync } = require("child_process");
|
|
37
|
+
|
|
38
|
+
// Mirror the ADAPTERS wiring state from agent-session.js.
|
|
39
|
+
// Only source of truth for "is this adapter ready to run?"
|
|
40
|
+
// Update this table when a verify-next adapter is promoted.
|
|
41
|
+
const ADAPTER_WIRED = {
|
|
42
|
+
"claude-code": true, // verified spike 2026-06-18
|
|
43
|
+
"codex": false, // verify-next (SPEC §2)
|
|
44
|
+
"gemini": false, // verify-next (SPEC §2)
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// ─── PATH probe ──────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns a Promise<boolean> — true if `bin` is callable on PATH.
|
|
51
|
+
* Uses `command -v` via sh — POSIX, cheap, no interactive spawn.
|
|
52
|
+
* 800 ms cap matches agent-detector.js convention (fast enough for
|
|
53
|
+
* the onboarding step, non-blocking for SSE streams).
|
|
54
|
+
*
|
|
55
|
+
* SF-2: single-quote escaping — every single-quote in `bin` is replaced
|
|
56
|
+
* with the '\'' sequence so a caller-supplied name can never inject shell
|
|
57
|
+
* metacharacters ($/backtick/backslash are inert inside single quotes).
|
|
58
|
+
*/
|
|
59
|
+
function binaryPresent(bin) {
|
|
60
|
+
if (!bin || typeof bin !== "string") return Promise.resolve(false);
|
|
61
|
+
// Escape any single-quotes inside the binary name: ' → '\''
|
|
62
|
+
const safeBin = bin.replace(/'/g, "'\\''");
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
const child = execFile(
|
|
65
|
+
"sh",
|
|
66
|
+
["-c", `command -v '${safeBin}'`],
|
|
67
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 800 },
|
|
68
|
+
(err, stdout) => {
|
|
69
|
+
if (err) return resolve(false);
|
|
70
|
+
resolve(Boolean(stdout && stdout.trim()));
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
// Belt-and-suspenders: also resolve false if the child emits 'error'
|
|
74
|
+
// before the callback fires (e.g. ENOENT for sh — extremely unlikely).
|
|
75
|
+
child.on("error", () => resolve(false));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readClaudeOAuthToken() {
|
|
80
|
+
if (process.env.CLAUDE_CODE_OAUTH_TOKEN) return process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
81
|
+
|
|
82
|
+
if (process.platform === "darwin") {
|
|
83
|
+
try {
|
|
84
|
+
const out = execFileSync(
|
|
85
|
+
"security",
|
|
86
|
+
["find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
|
87
|
+
{ encoding: "utf8", timeout: 1200, stdio: ["ignore", "pipe", "ignore"] }
|
|
88
|
+
).trim();
|
|
89
|
+
if (out) {
|
|
90
|
+
try {
|
|
91
|
+
const token = JSON.parse(out)?.claudeAiOauth?.accessToken;
|
|
92
|
+
if (token) return token;
|
|
93
|
+
} catch (_) {
|
|
94
|
+
if (/^ey/.test(out)) return out;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch (_) {}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const cf = path.join(os.homedir(), ".claude", ".credentials.json");
|
|
102
|
+
if (fs.existsSync(cf)) {
|
|
103
|
+
const token = JSON.parse(fs.readFileSync(cf, "utf8"))?.claudeAiOauth?.accessToken;
|
|
104
|
+
if (token) return token;
|
|
105
|
+
}
|
|
106
|
+
} catch (_) {}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const macCf = path.join(os.homedir(), "Library", "Application Support", "Claude", "config.json");
|
|
110
|
+
if (fs.existsSync(macCf)) {
|
|
111
|
+
const cfg = JSON.parse(fs.readFileSync(macCf, "utf8"));
|
|
112
|
+
for (const key of ["oauth:accessToken", "oauthAccessToken", "accessToken"]) {
|
|
113
|
+
if (cfg[key] && /^ey/.test(String(cfg[key]))) return cfg[key];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (_) {}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── per-vendor descriptors ──────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
const VENDORS = [
|
|
124
|
+
{
|
|
125
|
+
id: "claude-code",
|
|
126
|
+
name: "Claude",
|
|
127
|
+
bin: "claude",
|
|
128
|
+
/**
|
|
129
|
+
* Auth: subscription-OAuth is the intended mode (SPEC §2). Do not assume
|
|
130
|
+
* "binary exists" means "signed in"; first-run uses this signal to avoid
|
|
131
|
+
* a failed embedded spawn and show the sign-in recovery card up front.
|
|
132
|
+
*/
|
|
133
|
+
auth() {
|
|
134
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
135
|
+
const keySet = Boolean(apiKey && apiKey.trim());
|
|
136
|
+
const token = readClaudeOAuthToken();
|
|
137
|
+
return {
|
|
138
|
+
authed: keySet || Boolean(token),
|
|
139
|
+
mode: keySet ? "api-key" : "subscription-oauth",
|
|
140
|
+
note: keySet
|
|
141
|
+
? "ANTHROPIC_API_KEY detected — check Claude subscription billing mode"
|
|
142
|
+
: token
|
|
143
|
+
? "Claude CLI sign-in detected"
|
|
144
|
+
: "Claude CLI installed but no saved sign-in found — run `claude` then `/login`",
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "codex",
|
|
150
|
+
name: "Codex",
|
|
151
|
+
bin: "codex",
|
|
152
|
+
/**
|
|
153
|
+
* Auth: verify-next adapter — we deliberately do NOT probe.
|
|
154
|
+
* `codex app-server` (the JSON-RPC transport) hasn't been spiked;
|
|
155
|
+
* probing auth non-interactively isn't safe until we understand the
|
|
156
|
+
* CLI's login flow. Conservative: authed=false until adapter is wired.
|
|
157
|
+
*/
|
|
158
|
+
auth() {
|
|
159
|
+
return {
|
|
160
|
+
authed: false,
|
|
161
|
+
mode: "verify-next",
|
|
162
|
+
note: "Codex adapter is verify-next — spike `codex app-server` before enabling",
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "gemini",
|
|
168
|
+
name: "Gemini",
|
|
169
|
+
bin: "gemini",
|
|
170
|
+
/**
|
|
171
|
+
* Auth: footgun §4 — `gemini --acp` can prompt for login when spawned from
|
|
172
|
+
* a non-terminal parent. Never probe. authed=false until adapter is wired
|
|
173
|
+
* AND we have a non-interactive auth-check mechanism confirmed.
|
|
174
|
+
*/
|
|
175
|
+
auth() {
|
|
176
|
+
return {
|
|
177
|
+
authed: false,
|
|
178
|
+
mode: "verify-next",
|
|
179
|
+
note: "Gemini adapter is verify-next — confirm non-interactive auth path before enabling",
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
// ─── main export ─────────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* detectAgents() → Promise<Array<AgentStatus>>
|
|
189
|
+
*
|
|
190
|
+
* MF-3: now async — three vendor PATH probes run concurrently via
|
|
191
|
+
* Promise.all, so total wall-clock time ≈ max(probe) ≈ 800ms instead
|
|
192
|
+
* of 3 × 800ms = 2.4s. Does not block the Node event loop.
|
|
193
|
+
*
|
|
194
|
+
* AgentStatus {
|
|
195
|
+
* id: string — adapter key (matches ADAPTERS in agent-session.js)
|
|
196
|
+
* name: string — display label
|
|
197
|
+
* present: boolean — binary on PATH
|
|
198
|
+
* authed: boolean — best-effort non-interactive auth signal (see above)
|
|
199
|
+
* runnable: boolean — present AND adapter is wired AND auth is ready
|
|
200
|
+
* authMode: string — "subscription-oauth" | "api-key" | "verify-next"
|
|
201
|
+
* authNote: string — one-line explanation for the UI
|
|
202
|
+
* }
|
|
203
|
+
*/
|
|
204
|
+
async function detectAgents() {
|
|
205
|
+
// Fan-out: all three PATH probes run concurrently.
|
|
206
|
+
const presenceResults = await Promise.all(VENDORS.map((v) => binaryPresent(v.bin)));
|
|
207
|
+
|
|
208
|
+
return VENDORS.map((v, i) => {
|
|
209
|
+
const present = presenceResults[i];
|
|
210
|
+
const authInfo = v.auth();
|
|
211
|
+
const adapterWired = ADAPTER_WIRED[v.id] === true;
|
|
212
|
+
// runnable requires the binary, a wired adapter, and a non-interactive auth signal.
|
|
213
|
+
// For verify-next adapters, presence is informative only.
|
|
214
|
+
const runnable = present && adapterWired && authInfo.authed;
|
|
215
|
+
return {
|
|
216
|
+
id: v.id,
|
|
217
|
+
name: v.name,
|
|
218
|
+
present,
|
|
219
|
+
authed: adapterWired ? authInfo.authed : false,
|
|
220
|
+
runnable,
|
|
221
|
+
authMode: authInfo.mode,
|
|
222
|
+
authNote: authInfo.note,
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = { detectAgents };
|
|
228
|
+
|
|
229
|
+
// ─── self-test (node agent-detect.js) ────────────────────────────────────────
|
|
230
|
+
if (require.main === module) {
|
|
231
|
+
(async () => {
|
|
232
|
+
const results = await detectAgents();
|
|
233
|
+
console.log("\nD7 Agent Detection — " + new Date().toISOString());
|
|
234
|
+
console.log("─".repeat(62));
|
|
235
|
+
for (const r of results) {
|
|
236
|
+
const tick = (b) => (b ? "✓" : "✗");
|
|
237
|
+
console.log(
|
|
238
|
+
`${r.name.padEnd(10)} present=${tick(r.present)} authed=${tick(r.authed)} runnable=${tick(r.runnable)}`
|
|
239
|
+
);
|
|
240
|
+
console.log(` mode: ${r.authMode}`);
|
|
241
|
+
console.log(` note: ${r.authNote}`);
|
|
242
|
+
console.log();
|
|
243
|
+
}
|
|
244
|
+
const runnable = results.filter((r) => r.runnable);
|
|
245
|
+
const present = results.filter((r) => r.present && !r.runnable);
|
|
246
|
+
const none = results.every((r) => !r.present);
|
|
247
|
+
if (runnable.length) {
|
|
248
|
+
console.log("STATUS: READY — runnable adapters: " + runnable.map((r) => r.id).join(", "));
|
|
249
|
+
} else if (present.length) {
|
|
250
|
+
console.log("STATUS: PARTIAL — binaries present but adapters not wired: " + present.map((r) => r.id).join(", "));
|
|
251
|
+
} else if (none) {
|
|
252
|
+
console.log("STATUS: NONE — no supported agent binary found on PATH");
|
|
253
|
+
}
|
|
254
|
+
})();
|
|
255
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const { execFileSync } = require("child_process");
|
|
6
|
+
const { exists, safeStat } = require("./paths");
|
|
7
|
+
|
|
8
|
+
// Detect which AI agents are installed on the user's machine.
|
|
9
|
+
// `configured` = ~/.<dir> present (means USER OR MOVER OS set it up)
|
|
10
|
+
// `installed` = configured AND the actual CLI binary is on PATH
|
|
11
|
+
// `runnable` = installed AND the agent supports CLI invocation (capability flag)
|
|
12
|
+
//
|
|
13
|
+
// The old design treated any config directory as "installed", which made
|
|
14
|
+
// amp/aider/amazon-q show up as installed for users who never installed them
|
|
15
|
+
// (Mover OS itself sometimes creates those directories as side-effects).
|
|
16
|
+
// Codex bug-hunt (2026-05-13) flagged this — now we require an actual binary
|
|
17
|
+
// probe via `command -v <cli>` to count as installed.
|
|
18
|
+
|
|
19
|
+
const AGENTS = [
|
|
20
|
+
{ id: "claude-code", name: "Claude Code", configPaths: [".claude"], cliBin: "claude", capability: true, hookSupport: true },
|
|
21
|
+
{ id: "codex", name: "Codex CLI", configPaths: [".codex"], cliBin: "codex", capability: true, hookSupport: true },
|
|
22
|
+
{ id: "gemini", name: "Gemini CLI", configPaths: [".gemini"], cliBin: "gemini", capability: true, hookSupport: true },
|
|
23
|
+
{ id: "cursor", name: "Cursor", configPaths: [".cursor", "Library/Application Support/Cursor"], cliBin: null, capability: false, hookSupport: false },
|
|
24
|
+
{ id: "windsurf", name: "Windsurf", configPaths: [".windsurf", "Library/Application Support/Windsurf"], cliBin: null, capability: false, hookSupport: false },
|
|
25
|
+
{ id: "amp", name: "Amp", configPaths: [".amp"], cliBin: "amp", capability: true, hookSupport: false },
|
|
26
|
+
{ id: "aider", name: "Aider", configPaths: [".aider", ".aider.conf.yml"], cliBin: "aider", capability: true, hookSupport: false },
|
|
27
|
+
{ id: "amazon-q", name: "Amazon Q", configPaths: [".aws/amazonq"], cliBin: "q", capability: true, hookSupport: false },
|
|
28
|
+
{ id: "cline", name: "Cline", configPaths: [".cline"], cliBin: null, capability: false, hookSupport: false },
|
|
29
|
+
{ id: "continue", name: "Continue", configPaths: [".continue"], cliBin: null, capability: false, hookSupport: false }
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function hasBinary(cmd) {
|
|
33
|
+
if (!cmd) return false;
|
|
34
|
+
try {
|
|
35
|
+
// `command -v` is POSIX, faster than spawning the actual CLI just to probe.
|
|
36
|
+
// We bound it tightly via execFileSync so a hung path entry can't stall the dashboard.
|
|
37
|
+
const out = execFileSync("sh", ["-c", `command -v ${JSON.stringify(cmd)}`], {
|
|
38
|
+
encoding: "utf8",
|
|
39
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
40
|
+
timeout: 800
|
|
41
|
+
});
|
|
42
|
+
return Boolean(out && out.trim());
|
|
43
|
+
} catch (_) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function detectAgents() {
|
|
49
|
+
const home = os.homedir();
|
|
50
|
+
const out = AGENTS.map(a => {
|
|
51
|
+
let configured = false;
|
|
52
|
+
let configPath = null;
|
|
53
|
+
let lastUsed = null;
|
|
54
|
+
for (const cp of a.configPaths) {
|
|
55
|
+
const p = path.join(home, cp);
|
|
56
|
+
if (exists(p)) {
|
|
57
|
+
configured = true;
|
|
58
|
+
configPath = p;
|
|
59
|
+
const stat = safeStat(p);
|
|
60
|
+
if (stat) lastUsed = stat.mtime;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// `installed` requires both a config directory AND a callable binary.
|
|
65
|
+
// GUI-only tools (Cursor, Windsurf, Cline, Continue) have no cliBin —
|
|
66
|
+
// for those, configured-and-on-disk counts as installed.
|
|
67
|
+
const binaryFound = a.cliBin ? hasBinary(a.cliBin) : configured;
|
|
68
|
+
const installed = configured && binaryFound;
|
|
69
|
+
return {
|
|
70
|
+
id: a.id,
|
|
71
|
+
name: a.name,
|
|
72
|
+
configured,
|
|
73
|
+
installed,
|
|
74
|
+
runnable: installed && a.capability,
|
|
75
|
+
hookSupport: a.hookSupport,
|
|
76
|
+
configPath,
|
|
77
|
+
cliBin: a.cliBin || null,
|
|
78
|
+
lastUsed
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
available: true,
|
|
84
|
+
agents: out,
|
|
85
|
+
installedCount: out.filter(a => a.installed).length,
|
|
86
|
+
runnableCount: out.filter(a => a.runnable).length,
|
|
87
|
+
hookSupportedCount: out.filter(a => a.installed && a.hookSupport).length,
|
|
88
|
+
primary: out.find(a => a.runnable) || null
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { detectAgents };
|