dme-agent 7.4.1 → 7.4.2
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 +43 -44
- package/assets/agent/AGENTS.md +156 -0
- package/assets/agent/PROMPT-COMPACT.md +12 -15
- package/assets/agent/dme-v7.4.md +12 -14
- package/bin/dme.mjs +149 -55
- package/package.json +5 -4
- package/src/detect.mjs +260 -0
- package/src/install.mjs +352 -167
- package/src/postinstall-hint.mjs +3 -2
- package/src/targets.mjs +39 -13
- package/src/tui.mjs +352 -0
- package/src/ui.mjs +139 -52
package/src/detect.mjs
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect which AI CLIs / agent surfaces are actually installed on this machine.
|
|
3
|
+
* Does NOT treat "DME already dropped files here" as proof the CLI exists.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import { getTargets } from "./targets.mjs";
|
|
11
|
+
|
|
12
|
+
const home = os.homedir();
|
|
13
|
+
const isWin = process.platform === "win32";
|
|
14
|
+
|
|
15
|
+
function exists(p) {
|
|
16
|
+
try {
|
|
17
|
+
return Boolean(p && fs.existsSync(p));
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hasCmd(bin) {
|
|
24
|
+
if (!bin) return false;
|
|
25
|
+
try {
|
|
26
|
+
if (isWin) {
|
|
27
|
+
execSync(`where ${bin}`, { stdio: "ignore", windowsHide: true });
|
|
28
|
+
} else {
|
|
29
|
+
execSync(`command -v ${bin}`, {
|
|
30
|
+
stdio: "ignore",
|
|
31
|
+
shell: "/bin/sh",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function anyCmd(bins = []) {
|
|
41
|
+
return bins.some((b) => hasCmd(b));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function anyPath(paths = []) {
|
|
45
|
+
return paths.some((p) => exists(p));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function winLocalAppData(...parts) {
|
|
49
|
+
const base = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
|
|
50
|
+
return path.join(base, ...parts);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function winAppData(...parts) {
|
|
54
|
+
const base = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
55
|
+
return path.join(base, ...parts);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function macApp(...names) {
|
|
59
|
+
return names.map((n) => path.join("/Applications", n));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Files/dirs that prove the CLI itself (not DME) touched this root */
|
|
63
|
+
const ROOT_FINGERPRINTS = {
|
|
64
|
+
claude: [
|
|
65
|
+
"settings.json",
|
|
66
|
+
"history.jsonl",
|
|
67
|
+
"projects",
|
|
68
|
+
"sessions",
|
|
69
|
+
"session-env",
|
|
70
|
+
"plugins",
|
|
71
|
+
"statsig",
|
|
72
|
+
"todos",
|
|
73
|
+
],
|
|
74
|
+
cursor: [
|
|
75
|
+
"argv.json",
|
|
76
|
+
"extensions",
|
|
77
|
+
"ide_state.json",
|
|
78
|
+
"projects",
|
|
79
|
+
"cli-config.json",
|
|
80
|
+
"prompts",
|
|
81
|
+
],
|
|
82
|
+
codex: ["config.toml", "config.json", "sessions", "auth.json", "history.jsonl"],
|
|
83
|
+
zed: ["settings.json", "keymap.json", "themes", "conversations"],
|
|
84
|
+
grok: [
|
|
85
|
+
"config.toml",
|
|
86
|
+
"auth.json",
|
|
87
|
+
"sessions",
|
|
88
|
+
"bin",
|
|
89
|
+
"version.json",
|
|
90
|
+
"models_cache.json",
|
|
91
|
+
"active_sessions.json",
|
|
92
|
+
],
|
|
93
|
+
windsurf: ["config.json", "memories", "cascade", "user_settings.local.json"],
|
|
94
|
+
continue: ["config.json", "index", "sessions", "types"],
|
|
95
|
+
aider: [".aider.chat.history.md", ".aider.input.history"],
|
|
96
|
+
copilot: ["config.json", "hosts.json"],
|
|
97
|
+
antigravity: ["settings.json", "oauth_creds.json", "tmp", "history"],
|
|
98
|
+
generic: [],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function rootHasFingerprint(root, kind) {
|
|
102
|
+
if (!exists(root)) return false;
|
|
103
|
+
const keys = ROOT_FINGERPRINTS[kind] || [];
|
|
104
|
+
for (const k of keys) {
|
|
105
|
+
if (exists(path.join(root, k))) return true;
|
|
106
|
+
}
|
|
107
|
+
// generic: any non-DME child
|
|
108
|
+
try {
|
|
109
|
+
const entries = fs.readdirSync(root);
|
|
110
|
+
const nonDme = entries.filter(
|
|
111
|
+
(e) =>
|
|
112
|
+
!e.startsWith("DME") &&
|
|
113
|
+
e !== "skills" &&
|
|
114
|
+
e !== "agents" &&
|
|
115
|
+
e !== "AGENTS.md" &&
|
|
116
|
+
e !== "Agents.md" &&
|
|
117
|
+
e !== "CLAUDE.md" &&
|
|
118
|
+
e !== "CLAUDE_DME.md"
|
|
119
|
+
);
|
|
120
|
+
// only count as fingerprint if kind has no list and nonDme has substance
|
|
121
|
+
if (keys.length === 0 && nonDme.length > 0) return true;
|
|
122
|
+
} catch {
|
|
123
|
+
/* ignore */
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Detection probes per target id.
|
|
130
|
+
*/
|
|
131
|
+
const PROBES = {
|
|
132
|
+
claude: () =>
|
|
133
|
+
anyCmd(["claude"]) ||
|
|
134
|
+
anyPath([
|
|
135
|
+
...macApp("Claude.app"),
|
|
136
|
+
winLocalAppData("AnthropicClaude"),
|
|
137
|
+
winLocalAppData("claude-cli"),
|
|
138
|
+
]) ||
|
|
139
|
+
rootHasFingerprint(path.join(home, ".claude"), "claude"),
|
|
140
|
+
|
|
141
|
+
cursor: () =>
|
|
142
|
+
anyCmd(["cursor", "cursor-agent"]) ||
|
|
143
|
+
anyPath([
|
|
144
|
+
...macApp("Cursor.app"),
|
|
145
|
+
winLocalAppData("Programs", "cursor"),
|
|
146
|
+
winLocalAppData("Cursor"),
|
|
147
|
+
winAppData("Cursor"),
|
|
148
|
+
]) ||
|
|
149
|
+
rootHasFingerprint(path.join(home, ".cursor"), "cursor"),
|
|
150
|
+
|
|
151
|
+
codex: () =>
|
|
152
|
+
anyCmd(["codex"]) ||
|
|
153
|
+
rootHasFingerprint(path.join(home, ".codex"), "codex"),
|
|
154
|
+
|
|
155
|
+
zed: () =>
|
|
156
|
+
anyCmd(["zed", "zeditor"]) ||
|
|
157
|
+
anyPath([
|
|
158
|
+
...macApp("Zed.app"),
|
|
159
|
+
winLocalAppData("Programs", "Zed"),
|
|
160
|
+
winLocalAppData("Zed"),
|
|
161
|
+
]) ||
|
|
162
|
+
rootHasFingerprint(path.join(home, ".config", "zed"), "zed"),
|
|
163
|
+
|
|
164
|
+
grok: () =>
|
|
165
|
+
anyCmd(["grok", "grok-cli"]) ||
|
|
166
|
+
anyPath([winLocalAppData("grok")]) ||
|
|
167
|
+
rootHasFingerprint(path.join(home, ".grok"), "grok"),
|
|
168
|
+
|
|
169
|
+
windsurf: () =>
|
|
170
|
+
anyCmd(["windsurf"]) ||
|
|
171
|
+
anyPath([
|
|
172
|
+
...macApp("Windsurf.app"),
|
|
173
|
+
winLocalAppData("Programs", "Windsurf"),
|
|
174
|
+
winAppData("Windsurf"),
|
|
175
|
+
]) ||
|
|
176
|
+
rootHasFingerprint(path.join(home, ".codeium", "windsurf"), "windsurf"),
|
|
177
|
+
|
|
178
|
+
continue: () =>
|
|
179
|
+
anyPath([winAppData("Continue")]) ||
|
|
180
|
+
rootHasFingerprint(path.join(home, ".continue"), "continue"),
|
|
181
|
+
|
|
182
|
+
aider: () =>
|
|
183
|
+
anyCmd(["aider"]) ||
|
|
184
|
+
anyPath([path.join(home, ".aider.conf.yml")]) ||
|
|
185
|
+
rootHasFingerprint(path.join(home, ".aider"), "aider"),
|
|
186
|
+
|
|
187
|
+
copilot: () =>
|
|
188
|
+
anyPath([
|
|
189
|
+
path.join(home, ".copilot"),
|
|
190
|
+
path.join(home, ".config", "github-copilot"),
|
|
191
|
+
winAppData("GitHub Copilot"),
|
|
192
|
+
]),
|
|
193
|
+
|
|
194
|
+
antigravity: () =>
|
|
195
|
+
anyCmd(["gemini", "antigravity"]) ||
|
|
196
|
+
rootHasFingerprint(path.join(home, ".gemini"), "antigravity"),
|
|
197
|
+
|
|
198
|
+
// never auto — user must --targets global-agents
|
|
199
|
+
"global-agents": () => false,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @returns {{ target: object, detected: boolean, reasons: string[] }[]}
|
|
204
|
+
*/
|
|
205
|
+
export function scanAll() {
|
|
206
|
+
return getTargets().map((target) => {
|
|
207
|
+
const probe = PROBES[target.id];
|
|
208
|
+
const reasons = [];
|
|
209
|
+
let detected = false;
|
|
210
|
+
|
|
211
|
+
if (probe) {
|
|
212
|
+
try {
|
|
213
|
+
detected = Boolean(probe());
|
|
214
|
+
if (detected) reasons.push("probe:hit");
|
|
215
|
+
} catch {
|
|
216
|
+
detected = false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return { target, detected, reasons };
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function getDetectedTargets() {
|
|
225
|
+
return scanAll()
|
|
226
|
+
.filter((s) => s.detected)
|
|
227
|
+
.map((s) => s.target);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function getDetectedIds() {
|
|
231
|
+
return getDetectedTargets().map((t) => t.id);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Resolve which targets to install.
|
|
236
|
+
* - default / "detected": only detected
|
|
237
|
+
* - "all": everything
|
|
238
|
+
* - explicit list: those ids
|
|
239
|
+
*/
|
|
240
|
+
export function resolveInstallTargets(ids, { force = false } = {}) {
|
|
241
|
+
const all = getTargets();
|
|
242
|
+
if (!ids || ids.length === 0 || ids.includes("detected")) {
|
|
243
|
+
return getDetectedTargets();
|
|
244
|
+
}
|
|
245
|
+
if (ids.includes("all")) {
|
|
246
|
+
return all;
|
|
247
|
+
}
|
|
248
|
+
const set = new Set(ids.map((x) => x.toLowerCase()));
|
|
249
|
+
return all.filter((t) => set.has(t.id));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function formatScanReport() {
|
|
253
|
+
return scanAll().map(({ target, detected, reasons }) => ({
|
|
254
|
+
id: target.id,
|
|
255
|
+
name: target.name,
|
|
256
|
+
detected,
|
|
257
|
+
agentsMd: target.agentsMd || null,
|
|
258
|
+
reasons,
|
|
259
|
+
}));
|
|
260
|
+
}
|