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.
@@ -8,8 +8,9 @@ try {
8
8
  const r = process.stdout.isTTY ? "\x1b[0m" : "";
9
9
  console.log("");
10
10
  console.log(`${y} DME Agent installed.${r}`);
11
- console.log(`${d} Run: npx dme-agent install${r}`);
12
- console.log(`${d} Or: dme install${r}`);
11
+ console.log(`${d} Run: npx dme-agent${r}`);
12
+ console.log(`${d} dme install (detect-only)${r}`);
13
+ console.log(`${d} dme scan${r}`);
13
14
  console.log("");
14
15
  } catch {
15
16
  /* ignore */
package/src/targets.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Install targets for every major AI coding CLI / editor agent surface.
2
+ * Install targets for major AI coding CLI / editor agent surfaces.
3
+ * Each target declares the canonical AGENTS.md path.
3
4
  */
4
5
 
5
6
  import os from "node:os";
@@ -12,11 +13,13 @@ const home = os.homedir();
12
13
  * @property {string} id
13
14
  * @property {string} name
14
15
  * @property {string} root
15
- * @property {"claude"|"cursor"|"codex"|"zed"|"grok"|"windsurf"|"continue"|"aider"|"copilot"|"antigravity"|"generic"} kind
16
+ * @property {string} kind
16
17
  * @property {string[]} skillDirs
17
18
  * @property {string[]} agentFiles
18
19
  * @property {string[]} rulesFiles
20
+ * @property {string|null} agentsMd Canonical AGENTS.md / CLAUDE.md path for this CLI
19
21
  * @property {boolean} [optional]
22
+ * @property {boolean} [projectLocal] Install project-level files when this CLI is selected
20
23
  */
21
24
 
22
25
  /** @returns {Target[]} */
@@ -32,6 +35,8 @@ export function getTargets() {
32
35
  path.join(home, ".claude", "agents", "dme-v7.4.md"),
33
36
  path.join(home, ".claude", "CLAUDE_DME.md"),
34
37
  ],
38
+ // Claude Code: CLAUDE.md + Agents.md are both read in many setups
39
+ agentsMd: path.join(home, ".claude", "CLAUDE.md"),
35
40
  rulesFiles: [
36
41
  path.join(home, ".claude", "CLAUDE.md"),
37
42
  path.join(home, ".claude", "Agents.md"),
@@ -47,7 +52,9 @@ export function getTargets() {
47
52
  path.join(home, ".cursor", "skills-cursor"),
48
53
  ],
49
54
  agentFiles: [path.join(home, ".cursor", "agents", "dme-v7.4.md")],
55
+ agentsMd: path.join(home, ".cursor", "AGENTS.md"),
50
56
  rulesFiles: [
57
+ path.join(home, ".cursor", "AGENTS.md"),
51
58
  path.join(home, ".cursor", "rules", "dme-agent.mdc"),
52
59
  path.join(home, ".cursorrules"),
53
60
  ],
@@ -59,6 +66,7 @@ export function getTargets() {
59
66
  kind: "codex",
60
67
  skillDirs: [path.join(home, ".codex", "skills")],
61
68
  agentFiles: [path.join(home, ".codex", "agents", "dme-v7.4.md")],
69
+ agentsMd: path.join(home, ".codex", "AGENTS.md"),
62
70
  rulesFiles: [
63
71
  path.join(home, ".codex", "AGENTS.md"),
64
72
  path.join(home, ".codex", "instructions", "dme-v7.4.md"),
@@ -70,12 +78,11 @@ export function getTargets() {
70
78
  root: path.join(home, ".config", "zed"),
71
79
  kind: "zed",
72
80
  skillDirs: [path.join(home, ".config", "zed", "skills")],
73
- agentFiles: [
74
- path.join(home, ".config", "zed", "agents", "dme-v7.4.md"),
75
- ],
81
+ agentFiles: [path.join(home, ".config", "zed", "agents", "dme-v7.4.md")],
82
+ agentsMd: path.join(home, ".config", "zed", "AGENTS.md"),
76
83
  rulesFiles: [
77
- path.join(home, ".config", "zed", "rules", "dme.md"),
78
84
  path.join(home, ".config", "zed", "AGENTS.md"),
85
+ path.join(home, ".config", "zed", "rules", "dme.md"),
79
86
  ],
80
87
  },
81
88
  {
@@ -88,6 +95,7 @@ export function getTargets() {
88
95
  path.join(home, ".grok", "bundled", "agents", "dme-v7.4.md"),
89
96
  path.join(home, ".grok", "agents", "dme-v7.4.md"),
90
97
  ],
98
+ agentsMd: path.join(home, ".grok", "AGENTS.md"),
91
99
  rulesFiles: [path.join(home, ".grok", "AGENTS.md")],
92
100
  },
93
101
  {
@@ -99,7 +107,9 @@ export function getTargets() {
99
107
  agentFiles: [
100
108
  path.join(home, ".codeium", "windsurf", "agents", "dme-v7.4.md"),
101
109
  ],
110
+ agentsMd: path.join(home, ".codeium", "windsurf", "AGENTS.md"),
102
111
  rulesFiles: [
112
+ path.join(home, ".codeium", "windsurf", "AGENTS.md"),
103
113
  path.join(home, ".codeium", "windsurf", "memories", "dme-v7.4.md"),
104
114
  path.join(home, ".windsurfrules"),
105
115
  ],
@@ -112,7 +122,11 @@ export function getTargets() {
112
122
  kind: "continue",
113
123
  skillDirs: [path.join(home, ".continue", "skills")],
114
124
  agentFiles: [path.join(home, ".continue", "agents", "dme-v7.4.md")],
115
- rulesFiles: [path.join(home, ".continue", "rules", "dme.md")],
125
+ agentsMd: path.join(home, ".continue", "AGENTS.md"),
126
+ rulesFiles: [
127
+ path.join(home, ".continue", "AGENTS.md"),
128
+ path.join(home, ".continue", "rules", "dme.md"),
129
+ ],
116
130
  optional: true,
117
131
  },
118
132
  {
@@ -122,19 +136,21 @@ export function getTargets() {
122
136
  kind: "aider",
123
137
  skillDirs: [],
124
138
  agentFiles: [],
139
+ agentsMd: path.join(home, ".aider", "CONVENTIONS.md"),
125
140
  rulesFiles: [
141
+ path.join(home, ".aider", "CONVENTIONS.md"),
126
142
  path.join(home, ".aider", "dme-CONVENTIONS.md"),
127
- path.join(home, ".aider.conf.dme.yml"),
128
143
  ],
129
144
  optional: true,
130
145
  },
131
146
  {
132
147
  id: "copilot",
133
- name: "GitHub Copilot (chat instructions)",
148
+ name: "GitHub Copilot",
134
149
  root: path.join(home, ".copilot"),
135
150
  kind: "copilot",
136
151
  skillDirs: [],
137
152
  agentFiles: [],
153
+ agentsMd: path.join(home, ".github", "copilot-instructions.md"),
138
154
  rulesFiles: [
139
155
  path.join(home, ".github", "copilot-instructions.md"),
140
156
  path.join(home, ".copilot", "dme-instructions.md"),
@@ -143,27 +159,29 @@ export function getTargets() {
143
159
  },
144
160
  {
145
161
  id: "antigravity",
146
- name: "Google Antigravity / Gemini CLI",
162
+ name: "Gemini / Antigravity",
147
163
  root: path.join(home, ".gemini"),
148
164
  kind: "antigravity",
149
165
  skillDirs: [path.join(home, ".gemini", "skills")],
150
166
  agentFiles: [path.join(home, ".gemini", "agents", "dme-v7.4.md")],
167
+ agentsMd: path.join(home, ".gemini", "AGENTS.md"),
151
168
  rulesFiles: [
152
- path.join(home, ".gemini", "GEMINI.md"),
153
169
  path.join(home, ".gemini", "AGENTS.md"),
170
+ path.join(home, ".gemini", "GEMINI.md"),
154
171
  ],
155
172
  optional: true,
156
173
  },
157
174
  {
158
175
  id: "global-agents",
159
- name: "Global AGENTS.md (home)",
160
- root: home,
176
+ name: "Global ~/AGENTS.md",
177
+ root: path.join(home, ".agents"),
161
178
  kind: "generic",
162
179
  skillDirs: [
163
180
  path.join(home, ".agents", "skills"),
164
181
  path.join(home, ".config", "agents", "skills"),
165
182
  ],
166
183
  agentFiles: [path.join(home, ".agents", "dme-v7.4.md")],
184
+ agentsMd: path.join(home, "AGENTS.md"),
167
185
  rulesFiles: [path.join(home, "AGENTS.md")],
168
186
  optional: true,
169
187
  },
@@ -175,5 +193,13 @@ export function filterTargets(ids) {
175
193
  if (!ids || ids.length === 0) return all;
176
194
  const set = new Set(ids.map((x) => x.toLowerCase()));
177
195
  if (set.has("all")) return all;
196
+ if (set.has("detected")) {
197
+ // lazy import avoided — caller should use detect.mjs
198
+ return all;
199
+ }
178
200
  return all.filter((t) => set.has(t.id));
179
201
  }
202
+
203
+ export function getTargetById(id) {
204
+ return getTargets().find((t) => t.id === id) || null;
205
+ }
package/src/tui.mjs ADDED
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Navigable TUI — pure Node raw mode, zero dependencies.
3
+ * Arrow keys + enter + space + q.
4
+ */
5
+
6
+ import readline from "node:readline";
7
+ import {
8
+ c,
9
+ clearScreen,
10
+ hideCursor,
11
+ showCursor,
12
+ asciiLogo,
13
+ detectTag,
14
+ keyHint,
15
+ hr,
16
+ box,
17
+ } from "./ui.mjs";
18
+
19
+ function isTTY() {
20
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
21
+ }
22
+
23
+ /**
24
+ * @param {string} question
25
+ * @param {string[]} choices labels
26
+ * @param {number} [initial]
27
+ * @returns {Promise<number>} index
28
+ */
29
+ export async function selectMenu(question, choices, initial = 0) {
30
+ if (!isTTY()) {
31
+ // fallback: print numbered list
32
+ console.log(`\n${c.bold}${question}${c.reset}`);
33
+ choices.forEach((ch, i) => console.log(` ${c.yellow}${i + 1}.${c.reset} ${ch}`));
34
+ const ans = await readLinePlain(` ${c.dim}number >${c.reset} `);
35
+ const n = parseInt(ans, 10);
36
+ if (Number.isFinite(n) && n >= 1 && n <= choices.length) return n - 1;
37
+ return initial;
38
+ }
39
+
40
+ return new Promise((resolve) => {
41
+ let idx = Math.max(0, Math.min(initial, choices.length - 1));
42
+ hideCursor();
43
+
44
+ const draw = () => {
45
+ clearScreen();
46
+ process.stdout.write(asciiLogo());
47
+ process.stdout.write(`${hr()}\n`);
48
+ process.stdout.write(` ${c.brightCyan}${c.bold}${question}${c.reset}\n\n`);
49
+ choices.forEach((ch, i) => {
50
+ if (i === idx) {
51
+ process.stdout.write(
52
+ ` ${c.bgYellow}${c.black}${c.bold} ▸ ${ch.padEnd(40)} ${c.reset}\n`
53
+ );
54
+ } else {
55
+ process.stdout.write(` ${c.dim} ${ch}${c.reset}\n`);
56
+ }
57
+ });
58
+ process.stdout.write(`\n ${keyHint([
59
+ ["↑↓", "move"],
60
+ ["↵", "select"],
61
+ ["q", "quit"],
62
+ ])}\n`);
63
+ };
64
+
65
+ const onData = (buf) => {
66
+ const s = buf.toString("utf8");
67
+ if (s === "\u0003" || s === "q" || s === "Q") {
68
+ cleanup();
69
+ showCursor();
70
+ process.exit(0);
71
+ }
72
+ if (s === "\u001b[A" || s === "k") {
73
+ idx = (idx - 1 + choices.length) % choices.length;
74
+ draw();
75
+ return;
76
+ }
77
+ if (s === "\u001b[B" || s === "j") {
78
+ idx = (idx + 1) % choices.length;
79
+ draw();
80
+ return;
81
+ }
82
+ if (s === "\r" || s === "\n") {
83
+ cleanup();
84
+ showCursor();
85
+ clearScreen();
86
+ resolve(idx);
87
+ }
88
+ };
89
+
90
+ const cleanup = () => {
91
+ process.stdin.setRawMode?.(false);
92
+ process.stdin.pause();
93
+ process.stdin.removeListener("data", onData);
94
+ };
95
+
96
+ process.stdin.resume();
97
+ process.stdin.setRawMode?.(true);
98
+ process.stdin.on("data", onData);
99
+ draw();
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Multi-select checklist
105
+ * @param {string} question
106
+ * @param {{ id: string, label: string, checked?: boolean, detail?: string }[]} items
107
+ * @returns {Promise<string[]>} selected ids
108
+ */
109
+ export async function multiSelect(question, items) {
110
+ const state = items.map((it) => ({
111
+ ...it,
112
+ checked: it.checked !== false,
113
+ }));
114
+
115
+ if (!isTTY()) {
116
+ console.log(`\n${c.bold}${question}${c.reset}`);
117
+ console.log(c.dim + " (non-TTY: installing all listed)" + c.reset);
118
+ state.forEach((it) =>
119
+ console.log(
120
+ ` ${it.checked ? c.green + "[x]" : c.gray + "[ ]"}${c.reset} ${it.label}`
121
+ )
122
+ );
123
+ return state.filter((s) => s.checked).map((s) => s.id);
124
+ }
125
+
126
+ return new Promise((resolve) => {
127
+ let idx = 0;
128
+ hideCursor();
129
+
130
+ const draw = () => {
131
+ clearScreen();
132
+ process.stdout.write(asciiLogo());
133
+ process.stdout.write(`${hr()}\n`);
134
+ process.stdout.write(` ${c.brightMagenta}${c.bold}${question}${c.reset}\n`);
135
+ process.stdout.write(
136
+ ` ${c.dim}Only CLIs detected on this machine are listed.${c.reset}\n\n`
137
+ );
138
+ state.forEach((it, i) => {
139
+ const mark = it.checked
140
+ ? `${c.brightGreen}${c.bold}[✔]${c.reset}`
141
+ : `${c.gray}[ ]${c.reset}`;
142
+ const cursor =
143
+ i === idx
144
+ ? `${c.bgYellow}${c.black}${c.bold} ▸ ${c.reset}`
145
+ : " ";
146
+ const detail = it.detail
147
+ ? ` ${c.dim}${it.detail}${c.reset}`
148
+ : "";
149
+ process.stdout.write(`${cursor}${mark} ${c.brightWhite}${it.label}${c.reset}${detail}\n`);
150
+ });
151
+ const n = state.filter((s) => s.checked).length;
152
+ process.stdout.write(
153
+ `\n ${c.cyan}${n}${c.reset}${c.dim}/${state.length} selected${c.reset}\n`
154
+ );
155
+ process.stdout.write(
156
+ ` ${keyHint([
157
+ ["↑↓", "move"],
158
+ ["space", "toggle"],
159
+ ["a", "all"],
160
+ ["n", "none"],
161
+ ["↵", "install"],
162
+ ["q", "quit"],
163
+ ])}\n`
164
+ );
165
+ };
166
+
167
+ const onData = (buf) => {
168
+ const s = buf.toString("utf8");
169
+ if (s === "\u0003" || s === "q" || s === "Q") {
170
+ cleanup();
171
+ showCursor();
172
+ process.exit(0);
173
+ }
174
+ if (s === "\u001b[A" || s === "k") {
175
+ idx = (idx - 1 + state.length) % state.length;
176
+ draw();
177
+ return;
178
+ }
179
+ if (s === "\u001b[B" || s === "j") {
180
+ idx = (idx + 1) % state.length;
181
+ draw();
182
+ return;
183
+ }
184
+ if (s === " " || s === "x") {
185
+ state[idx].checked = !state[idx].checked;
186
+ draw();
187
+ return;
188
+ }
189
+ if (s === "a" || s === "A") {
190
+ state.forEach((it) => (it.checked = true));
191
+ draw();
192
+ return;
193
+ }
194
+ if (s === "n" || s === "N") {
195
+ state.forEach((it) => (it.checked = false));
196
+ draw();
197
+ return;
198
+ }
199
+ if (s === "\r" || s === "\n") {
200
+ cleanup();
201
+ showCursor();
202
+ clearScreen();
203
+ resolve(state.filter((it) => it.checked).map((it) => it.id));
204
+ }
205
+ };
206
+
207
+ const cleanup = () => {
208
+ process.stdin.setRawMode?.(false);
209
+ process.stdin.pause();
210
+ process.stdin.removeListener("data", onData);
211
+ };
212
+
213
+ process.stdin.resume();
214
+ process.stdin.setRawMode?.(true);
215
+ process.stdin.on("data", onData);
216
+ draw();
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Prompt when AGENTS.md already exists
222
+ * @returns {Promise<"merge"|"overwrite"|"skip">}
223
+ */
224
+ export async function agentsConflictPrompt(filePath) {
225
+ const short = filePath.replace(process.env.USERPROFILE || "", "~").replace(process.env.HOME || "", "~");
226
+ const choices = [
227
+ "Merge — keep your file, inject/update DME block",
228
+ "Overwrite — replace entire AGENTS.md with DME doctrine",
229
+ "Skip — leave this AGENTS.md untouched",
230
+ ];
231
+ const idx = await selectMenu(
232
+ `AGENTS.md already exists:\n ${short}`,
233
+ choices,
234
+ 0
235
+ );
236
+ return ["merge", "overwrite", "skip"][idx];
237
+ }
238
+
239
+ /**
240
+ * Simple single-key prompt without full TUI redraw
241
+ * @returns {Promise<"merge"|"overwrite"|"skip">}
242
+ */
243
+ export async function agentsConflictPromptInline(filePath) {
244
+ if (!isTTY()) {
245
+ console.log(
246
+ `${c.yellow}!${c.reset} Exists: ${filePath}\n defaulting to MERGE (non-TTY). Use --agents overwrite|skip|merge`
247
+ );
248
+ return "merge";
249
+ }
250
+
251
+ console.log("");
252
+ console.log(
253
+ box(
254
+ "AGENTS.md already exists",
255
+ [filePath, "", "[M] Merge [O] Overwrite [S] Skip"],
256
+ "magenta"
257
+ )
258
+ );
259
+
260
+ return new Promise((resolve) => {
261
+ const onData = (buf) => {
262
+ const ch = buf.toString("utf8").toLowerCase();
263
+ if (ch === "\u0003") {
264
+ cleanup();
265
+ process.exit(0);
266
+ }
267
+ if (ch === "m" || ch === "\r" || ch === "\n") {
268
+ cleanup();
269
+ resolve("merge");
270
+ } else if (ch === "o") {
271
+ cleanup();
272
+ resolve("overwrite");
273
+ } else if (ch === "s") {
274
+ cleanup();
275
+ resolve("skip");
276
+ }
277
+ };
278
+ const cleanup = () => {
279
+ process.stdin.setRawMode?.(false);
280
+ process.stdin.pause();
281
+ process.stdin.removeListener("data", onData);
282
+ showCursor();
283
+ };
284
+ hideCursor();
285
+ process.stdin.resume();
286
+ process.stdin.setRawMode?.(true);
287
+ process.stdin.on("data", onData);
288
+ });
289
+ }
290
+
291
+ function readLinePlain(prompt) {
292
+ return new Promise((resolve) => {
293
+ const rl = readline.createInterface({
294
+ input: process.stdin,
295
+ output: process.stdout,
296
+ });
297
+ rl.question(prompt, (ans) => {
298
+ rl.close();
299
+ resolve(ans.trim());
300
+ });
301
+ });
302
+ }
303
+
304
+ /**
305
+ * Main navigable app shell
306
+ * @param {{ version: string, onInstall: Function, onDoctor: Function, onUninstall: Function, onScan: Function }} handlers
307
+ */
308
+ export async function runMainTui(handlers) {
309
+ const { version, onInstall, onDoctor, onUninstall, onScan } = handlers;
310
+
311
+ for (;;) {
312
+ const idx = await selectMenu("What do you want to do?", [
313
+ "Install — detect CLIs & bootstrap DME",
314
+ "Scan — show which CLIs are on this PC",
315
+ "Doctor — verify DME install health",
316
+ "Uninstall— remove DME files & blocks",
317
+ "Quit",
318
+ ]);
319
+
320
+ if (idx === 0) await onInstall();
321
+ else if (idx === 1) await onScan();
322
+ else if (idx === 2) await onDoctor();
323
+ else if (idx === 3) await onUninstall();
324
+ else break;
325
+
326
+ if (isTTY()) {
327
+ await selectMenu("Done. Next?", ["Back to menu", "Quit"], 0).then((i) => {
328
+ if (i === 1) process.exit(0);
329
+ });
330
+ } else break;
331
+ }
332
+
333
+ clearScreen();
334
+ console.log(asciiLogo(version));
335
+ console.log(` ${c.dim}later.${c.reset}\n`);
336
+ }
337
+
338
+ export function printScanTable(report) {
339
+ console.log(asciiLogo());
340
+ console.log(hr());
341
+ console.log(` ${c.bold}${c.brightCyan}CLI detection${c.reset}\n`);
342
+ for (const row of report) {
343
+ const tag = detectTag(row.detected);
344
+ const agents = row.agentsMd
345
+ ? `${c.dim}→ ${row.agentsMd}${c.reset}`
346
+ : "";
347
+ console.log(
348
+ ` ${tag} ${c.brightWhite}${row.name.padEnd(22)}${c.reset} ${c.yellow}${row.id.padEnd(14)}${c.reset} ${agents}`
349
+ );
350
+ }
351
+ console.log("");
352
+ }