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/postinstall-hint.mjs
CHANGED
|
@@ -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
|
|
12
|
-
console.log(`${d}
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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
|
|
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
|
+
}
|