harness-evolver 0.4.0 → 0.5.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/bin/install.js +155 -52
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -1,26 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Harness Evolver installer.
|
|
4
|
-
*
|
|
4
|
+
* Interactive setup with runtime selection, global/local choice.
|
|
5
5
|
*
|
|
6
6
|
* Usage: npx harness-evolver@latest
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const fs = require("fs");
|
|
10
10
|
const path = require("path");
|
|
11
|
+
const readline = require("readline");
|
|
11
12
|
const { execSync } = require("child_process");
|
|
12
13
|
|
|
14
|
+
const VERSION = require("../package.json").version;
|
|
13
15
|
const PLUGIN_ROOT = path.resolve(__dirname, "..");
|
|
14
16
|
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
// ANSI colors
|
|
19
|
+
const CYAN = "\x1b[36m";
|
|
20
|
+
const GREEN = "\x1b[32m";
|
|
21
|
+
const YELLOW = "\x1b[33m";
|
|
22
|
+
const RED = "\x1b[31m";
|
|
23
|
+
const DIM = "\x1b[2m";
|
|
24
|
+
const BOLD = "\x1b[1m";
|
|
25
|
+
const RESET = "\x1b[0m";
|
|
26
|
+
|
|
27
|
+
const LOGO = `
|
|
28
|
+
${CYAN} ██╗ ██╗ █████╗ ██████╗ ███╗ ██╗███████╗███████╗███████╗
|
|
29
|
+
██║ ██║██╔══██╗██╔══██╗████╗ ██║██╔════╝██╔════╝██╔════╝
|
|
30
|
+
███████║███████║██████╔╝██╔██╗ ██║█████╗ ███████╗███████╗
|
|
31
|
+
██╔══██║██╔══██║██╔══██╗██║╚██╗██║██╔══╝ ╚════██║╚════██║
|
|
32
|
+
██║ ██║██║ ██║██║ ██║██║ ╚████║███████╗███████║███████║
|
|
33
|
+
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝╚══════╝
|
|
34
|
+
${BOLD}███████╗██╗ ██╗ ██████╗ ██╗ ██╗ ██╗███████╗██████╗
|
|
35
|
+
██╔════╝██║ ██║██╔═══██╗██║ ██║ ██║██╔════╝██╔══██╗
|
|
36
|
+
█████╗ ██║ ██║██║ ██║██║ ██║ ██║█████╗ ██████╔╝
|
|
37
|
+
██╔══╝ ╚██╗ ██╔╝██║ ██║██║ ╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
|
38
|
+
███████╗ ╚████╔╝ ╚██████╔╝███████╗╚████╔╝ ███████╗██║ ██║
|
|
39
|
+
╚══════╝ ╚═══╝ ╚═════╝ ╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝${RESET}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const RUNTIMES = [
|
|
43
|
+
{ name: "Claude Code", dir: ".claude", detected: () => fs.existsSync(path.join(HOME, ".claude")) },
|
|
44
|
+
{ name: "Cursor", dir: ".cursor", detected: () => fs.existsSync(path.join(HOME, ".cursor")) },
|
|
45
|
+
{ name: "Codex", dir: ".codex", detected: () => fs.existsSync(path.join(HOME, ".codex")) },
|
|
46
|
+
{ name: "Windsurf", dir: ".windsurf", detected: () => fs.existsSync(path.join(HOME, ".windsurf")) },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
function ask(rl, question) {
|
|
50
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
24
51
|
}
|
|
25
52
|
|
|
26
53
|
function copyDir(src, dest) {
|
|
@@ -50,76 +77,152 @@ function checkPython() {
|
|
|
50
77
|
}
|
|
51
78
|
}
|
|
52
79
|
|
|
53
|
-
function
|
|
54
|
-
|
|
80
|
+
function installForRuntime(runtimeDir, scope) {
|
|
81
|
+
const baseDir = scope === "local"
|
|
82
|
+
? path.join(process.cwd(), runtimeDir)
|
|
83
|
+
: path.join(HOME, runtimeDir);
|
|
55
84
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
log("\u2713 python3 found");
|
|
85
|
+
const commandsDir = path.join(baseDir, "commands", "harness-evolver");
|
|
86
|
+
const agentsDir = path.join(baseDir, "agents");
|
|
61
87
|
|
|
62
|
-
|
|
63
|
-
console.error(` ERROR: Claude Code directory not found at ${CLAUDE_DIR}`);
|
|
64
|
-
console.error(" Install Claude Code first: https://claude.ai/code");
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
log("\u2713 Claude Code detected");
|
|
68
|
-
|
|
69
|
-
// Copy skills
|
|
88
|
+
// Skills
|
|
70
89
|
const skillsSource = path.join(PLUGIN_ROOT, "skills");
|
|
71
90
|
if (fs.existsSync(skillsSource)) {
|
|
72
91
|
for (const skill of fs.readdirSync(skillsSource, { withFileTypes: true })) {
|
|
73
92
|
if (skill.isDirectory()) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
copyDir(src, dest);
|
|
77
|
-
log(` skill: ${skill.name}`);
|
|
93
|
+
copyDir(path.join(skillsSource, skill.name), path.join(commandsDir, skill.name));
|
|
94
|
+
console.log(` ${GREEN}✓${RESET} Installed skill: ${skill.name}`);
|
|
78
95
|
}
|
|
79
96
|
}
|
|
80
97
|
}
|
|
81
98
|
|
|
82
|
-
//
|
|
99
|
+
// Agents
|
|
83
100
|
const agentsSource = path.join(PLUGIN_ROOT, "agents");
|
|
84
101
|
if (fs.existsSync(agentsSource)) {
|
|
85
|
-
fs.mkdirSync(
|
|
102
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
86
103
|
for (const agent of fs.readdirSync(agentsSource)) {
|
|
87
|
-
copyFile(
|
|
88
|
-
|
|
89
|
-
path.join(AGENTS_DIR, agent)
|
|
90
|
-
);
|
|
91
|
-
log(` agent: ${agent}`);
|
|
104
|
+
copyFile(path.join(agentsSource, agent), path.join(agentsDir, agent));
|
|
105
|
+
console.log(` ${GREEN}✓${RESET} Installed agent: ${agent}`);
|
|
92
106
|
}
|
|
93
107
|
}
|
|
108
|
+
}
|
|
94
109
|
|
|
95
|
-
|
|
110
|
+
function installTools() {
|
|
111
|
+
const toolsDir = path.join(HOME, ".harness-evolver", "tools");
|
|
96
112
|
const toolsSource = path.join(PLUGIN_ROOT, "tools");
|
|
97
113
|
if (fs.existsSync(toolsSource)) {
|
|
98
|
-
fs.mkdirSync(
|
|
114
|
+
fs.mkdirSync(toolsDir, { recursive: true });
|
|
99
115
|
for (const tool of fs.readdirSync(toolsSource)) {
|
|
100
116
|
if (tool.endsWith(".py")) {
|
|
101
|
-
copyFile(
|
|
102
|
-
|
|
103
|
-
path.join(TOOLS_DIR, tool)
|
|
104
|
-
);
|
|
105
|
-
log(` tool: ${tool}`);
|
|
117
|
+
copyFile(path.join(toolsSource, tool), path.join(toolsDir, tool));
|
|
118
|
+
console.log(` ${GREEN}✓${RESET} Installed tool: ${tool}`);
|
|
106
119
|
}
|
|
107
120
|
}
|
|
108
121
|
}
|
|
122
|
+
}
|
|
109
123
|
|
|
110
|
-
|
|
124
|
+
function installExamples() {
|
|
125
|
+
const examplesDir = path.join(HOME, ".harness-evolver", "examples");
|
|
111
126
|
const examplesSource = path.join(PLUGIN_ROOT, "examples");
|
|
112
127
|
if (fs.existsSync(examplesSource)) {
|
|
113
|
-
copyDir(examplesSource,
|
|
114
|
-
log(
|
|
128
|
+
copyDir(examplesSource, examplesDir);
|
|
129
|
+
console.log(` ${GREEN}✓${RESET} Installed examples: classifier`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function main() {
|
|
134
|
+
console.log(LOGO);
|
|
135
|
+
console.log(` ${DIM}Harness Evolver v${VERSION}${RESET}`);
|
|
136
|
+
console.log(` ${DIM}Meta-Harness-style autonomous harness optimization${RESET}`);
|
|
137
|
+
console.log();
|
|
138
|
+
|
|
139
|
+
// Check python
|
|
140
|
+
if (!checkPython()) {
|
|
141
|
+
console.error(` ${RED}ERROR:${RESET} python3 not found in PATH. Install Python 3.8+ first.`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
console.log(` ${GREEN}✓${RESET} python3 found`);
|
|
145
|
+
|
|
146
|
+
// Detect runtimes
|
|
147
|
+
const available = RUNTIMES.filter((r) => r.detected());
|
|
148
|
+
if (available.length === 0) {
|
|
149
|
+
console.error(`\n ${RED}ERROR:${RESET} No supported runtime detected.`);
|
|
150
|
+
console.error(` Install Claude Code, Cursor, Codex, or Windsurf first.`);
|
|
151
|
+
process.exit(1);
|
|
115
152
|
}
|
|
116
153
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
console.log(
|
|
121
|
-
|
|
122
|
-
|
|
154
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
155
|
+
|
|
156
|
+
// Runtime selection
|
|
157
|
+
console.log(`\n ${YELLOW}Which runtime(s) would you like to install for?${RESET}\n`);
|
|
158
|
+
available.forEach((r, i) => {
|
|
159
|
+
console.log(` ${i + 1}) ${r.name.padEnd(14)} (~/${r.dir})`);
|
|
160
|
+
});
|
|
161
|
+
if (available.length > 1) {
|
|
162
|
+
console.log(` ${available.length + 1}) All`);
|
|
163
|
+
console.log(`\n ${DIM}Select multiple: 1,2 or 1 2${RESET}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const defaultChoice = "1";
|
|
167
|
+
const runtimeAnswer = await ask(rl, `\n ${YELLOW}Choice [${defaultChoice}]:${RESET} `);
|
|
168
|
+
const runtimeInput = (runtimeAnswer.trim() || defaultChoice);
|
|
169
|
+
|
|
170
|
+
let selectedRuntimes;
|
|
171
|
+
if (runtimeInput === String(available.length + 1)) {
|
|
172
|
+
selectedRuntimes = available;
|
|
173
|
+
} else {
|
|
174
|
+
const indices = runtimeInput.split(/[,\s]+/).map((s) => parseInt(s, 10) - 1);
|
|
175
|
+
selectedRuntimes = indices
|
|
176
|
+
.filter((i) => i >= 0 && i < available.length)
|
|
177
|
+
.map((i) => available[i]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (selectedRuntimes.length === 0) {
|
|
181
|
+
selectedRuntimes = [available[0]];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Scope selection
|
|
185
|
+
console.log(`\n ${YELLOW}Where would you like to install?${RESET}\n`);
|
|
186
|
+
console.log(` 1) Global (~/${selectedRuntimes[0].dir}) - available in all projects`);
|
|
187
|
+
console.log(` 2) Local (./${selectedRuntimes[0].dir}) - this project only`);
|
|
188
|
+
|
|
189
|
+
const scopeAnswer = await ask(rl, `\n ${YELLOW}Choice [1]:${RESET} `);
|
|
190
|
+
const scope = (scopeAnswer.trim() === "2") ? "local" : "global";
|
|
191
|
+
|
|
192
|
+
console.log();
|
|
193
|
+
|
|
194
|
+
// Install for each selected runtime
|
|
195
|
+
for (const runtime of selectedRuntimes) {
|
|
196
|
+
const target = scope === "local" ? `./${runtime.dir}` : `~/${runtime.dir}`;
|
|
197
|
+
console.log(` Installing for ${CYAN}${runtime.name}${RESET} to ${target}`);
|
|
198
|
+
console.log();
|
|
199
|
+
installForRuntime(runtime.dir, scope);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Tools and examples are always global
|
|
203
|
+
installTools();
|
|
204
|
+
installExamples();
|
|
205
|
+
|
|
206
|
+
// Write version file
|
|
207
|
+
const versionPath = path.join(HOME, ".harness-evolver", "VERSION");
|
|
208
|
+
fs.mkdirSync(path.dirname(versionPath), { recursive: true });
|
|
209
|
+
fs.writeFileSync(versionPath, VERSION);
|
|
210
|
+
console.log(` ${GREEN}✓${RESET} Wrote VERSION (${VERSION})`);
|
|
211
|
+
|
|
212
|
+
console.log(`\n ${GREEN}Done!${RESET} Open a project in Claude Code and run ${CYAN}/harness-evolver:init${RESET}`);
|
|
213
|
+
console.log(`\n ${DIM}Quick start with example:${RESET}`);
|
|
214
|
+
console.log(` cp -r ~/.harness-evolver/examples/classifier ./my-project`);
|
|
215
|
+
console.log(` cd my-project && claude`);
|
|
216
|
+
console.log(` /harness-evolver:init`);
|
|
217
|
+
console.log(` /harness-evolver:evolve`);
|
|
218
|
+
|
|
219
|
+
console.log(`\n ${DIM}GitHub: https://github.com/raphaelchristi/harness-evolver${RESET}`);
|
|
220
|
+
console.log();
|
|
221
|
+
|
|
222
|
+
rl.close();
|
|
123
223
|
}
|
|
124
224
|
|
|
125
|
-
main()
|
|
225
|
+
main().catch((err) => {
|
|
226
|
+
console.error(` ${RED}ERROR:${RESET} ${err.message}`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
});
|