harness-evolver 3.2.1 → 3.3.1
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/.claude-plugin/plugin.json +1 -1
- package/bin/install.js +283 -110
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-evolver",
|
|
3
3
|
"description": "LangSmith-native autonomous agent optimization — evolves LLM agent code using multi-agent proposers, LangSmith experiments, and git worktrees",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.3.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Raphael Valdetaro"
|
|
7
7
|
},
|
package/bin/install.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Harness Evolver
|
|
4
|
-
* Copies skills/agents/tools to runtime directories
|
|
3
|
+
* Harness Evolver installer.
|
|
4
|
+
* Copies skills/agents/tools to runtime directories.
|
|
5
5
|
* Installs Python dependencies (langsmith) and langsmith-cli.
|
|
6
6
|
*
|
|
7
7
|
* Usage: npx harness-evolver@latest
|
|
@@ -16,20 +16,118 @@ const VERSION = require("../package.json").version;
|
|
|
16
16
|
const PLUGIN_ROOT = path.resolve(__dirname, "..");
|
|
17
17
|
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
// ─── Colors (zero dependencies, inline ANSI) ───────────────────────────────
|
|
20
|
+
|
|
21
|
+
const isColorSupported =
|
|
22
|
+
process.env.FORCE_COLOR !== "0" &&
|
|
23
|
+
!process.env.NO_COLOR &&
|
|
24
|
+
(process.env.FORCE_COLOR !== undefined || process.stdout.isTTY);
|
|
25
|
+
|
|
26
|
+
function ansi(code) {
|
|
27
|
+
return isColorSupported ? `\x1b[${code}m` : "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const reset = ansi("0");
|
|
31
|
+
const bold = ansi("1");
|
|
32
|
+
const dim = ansi("2");
|
|
33
|
+
const red = ansi("31");
|
|
34
|
+
const green = ansi("32");
|
|
35
|
+
const yellow = ansi("33");
|
|
36
|
+
const cyan = ansi("36");
|
|
37
|
+
const gray = ansi("90");
|
|
38
|
+
const bgCyan = ansi("46");
|
|
39
|
+
const black = ansi("30");
|
|
40
|
+
|
|
41
|
+
const c = {
|
|
42
|
+
bold: (s) => `${bold}${s}${reset}`,
|
|
43
|
+
dim: (s) => `${dim}${s}${reset}`,
|
|
44
|
+
red: (s) => `${red}${s}${reset}`,
|
|
45
|
+
green: (s) => `${green}${s}${reset}`,
|
|
46
|
+
yellow: (s) => `${yellow}${s}${reset}`,
|
|
47
|
+
cyan: (s) => `${cyan}${s}${reset}`,
|
|
48
|
+
gray: (s) => `${gray}${s}${reset}`,
|
|
49
|
+
bgCyan: (s) => `${bgCyan}${black}${s}${reset}`,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// ─── Symbols ────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
const S = {
|
|
55
|
+
bar: "\u2502", // │
|
|
56
|
+
barEnd: "\u2514", // └
|
|
57
|
+
barStart: "\u250C", // ┌
|
|
58
|
+
step: "\u25C7", // ◇
|
|
59
|
+
stepActive: "\u25C6",// ◆
|
|
60
|
+
stepDone: "\u25CF", // ●
|
|
61
|
+
stepError: "\u25A0", // ■
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// ─── UI helpers (clack-style) ───────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
function barLine(content = "") {
|
|
67
|
+
console.log(`${c.gray(S.bar)} ${content}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function barEmpty() {
|
|
71
|
+
console.log(`${c.gray(S.bar)}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function header(label) {
|
|
75
|
+
console.log();
|
|
76
|
+
console.log(`${c.gray(S.barStart)} ${c.bgCyan(` ${label} `)}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function footer(message) {
|
|
80
|
+
if (message) {
|
|
81
|
+
console.log(`${c.gray(S.barEnd)} ${message}`);
|
|
82
|
+
} else {
|
|
83
|
+
console.log(`${c.gray(S.barEnd)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function step(content) {
|
|
88
|
+
console.log(`${c.gray(S.step)} ${content}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function stepDone(content) {
|
|
92
|
+
console.log(`${c.green(S.stepDone)} ${content}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function stepError(content) {
|
|
96
|
+
console.log(`${c.red(S.stepError)} ${content}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function stepPrompt(content) {
|
|
100
|
+
console.log(`${c.cyan(S.stepActive)} ${content}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Banner (green gradient dark → bright) ──────────────────────────────────
|
|
104
|
+
|
|
105
|
+
const BANNER_LINES = [
|
|
106
|
+
" \u2566 \u2566\u2554\u2550\u2557\u2566\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557 \u2554\u2550\u2557\u2566 \u2566\u2554\u2550\u2557\u2566 \u2566 \u2566\u2554\u2550\u2557\u2566\u2550\u2557",
|
|
107
|
+
" \u2560\u2550\u2563\u2560\u2550\u2563\u2560\u2566\u255D\u2551\u2551\u2551\u2551\u2563 \u255A\u2550\u2557\u255A\u2550\u2557 \u2551\u2563 \u255A\u2557\u2554\u255D\u2551 \u2551\u2551 \u255A\u2557\u2554\u255D\u2551\u2563 \u2560\u2566\u255D",
|
|
108
|
+
" \u2569 \u2569\u2569 \u2569\u2569\u255A\u2550\u255D\u255A\u255D\u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u255D \u255A\u2550\u255D\u2569\u2550\u255D \u255A\u255D \u255A\u2550\u255D\u2569\u255A\u2550",
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const GRADIENT = [
|
|
112
|
+
[0, 100, 40],
|
|
113
|
+
[0, 180, 85],
|
|
114
|
+
[0, 255, 136],
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
function rgb(r, g, b) {
|
|
118
|
+
return isColorSupported ? `\x1b[38;2;${r};${g};${b}m` : "";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function banner() {
|
|
122
|
+
console.log();
|
|
123
|
+
for (let i = 0; i < BANNER_LINES.length; i++) {
|
|
124
|
+
const [r, g, b] = GRADIENT[i];
|
|
125
|
+
console.log(`${rgb(r, g, b)}${BANNER_LINES[i]}${reset}`);
|
|
126
|
+
}
|
|
127
|
+
console.log(`${c.dim(` LangSmith-native agent optimization v${VERSION}`)}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── Utilities ──────────────────────────────────────────────────────────────
|
|
33
131
|
|
|
34
132
|
function ask(rl, question) {
|
|
35
133
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
@@ -72,6 +170,8 @@ function checkCommand(cmd) {
|
|
|
72
170
|
}
|
|
73
171
|
}
|
|
74
172
|
|
|
173
|
+
// ─── Install logic ──────────────────────────────────────────────────────────
|
|
174
|
+
|
|
75
175
|
function cleanPreviousInstall(runtimeDir, scope) {
|
|
76
176
|
const baseDir = scope === "local"
|
|
77
177
|
? path.join(process.cwd(), runtimeDir)
|
|
@@ -81,7 +181,6 @@ function cleanPreviousInstall(runtimeDir, scope) {
|
|
|
81
181
|
const agentsDir = path.join(baseDir, "agents");
|
|
82
182
|
let cleaned = 0;
|
|
83
183
|
|
|
84
|
-
// Remove ALL evolver/harness-evolver skills (any version)
|
|
85
184
|
if (fs.existsSync(skillsDir)) {
|
|
86
185
|
const ours = ["setup", "evolve", "deploy", "status",
|
|
87
186
|
"init", "architect", "compare", "critic", "diagnose",
|
|
@@ -100,7 +199,6 @@ function cleanPreviousInstall(runtimeDir, scope) {
|
|
|
100
199
|
}
|
|
101
200
|
}
|
|
102
201
|
|
|
103
|
-
// Remove ALL evolver/harness-evolver agents
|
|
104
202
|
if (fs.existsSync(agentsDir)) {
|
|
105
203
|
for (const f of fs.readdirSync(agentsDir)) {
|
|
106
204
|
if (f.startsWith("evolver-") || f.startsWith("harness-evolver-")) {
|
|
@@ -110,14 +208,12 @@ function cleanPreviousInstall(runtimeDir, scope) {
|
|
|
110
208
|
}
|
|
111
209
|
}
|
|
112
210
|
|
|
113
|
-
// Remove old commands/ directory (v1)
|
|
114
211
|
const oldCommandsDir = path.join(baseDir, "commands", "harness-evolver");
|
|
115
212
|
if (fs.existsSync(oldCommandsDir)) {
|
|
116
213
|
fs.rmSync(oldCommandsDir, { recursive: true, force: true });
|
|
117
214
|
cleaned++;
|
|
118
215
|
}
|
|
119
216
|
|
|
120
|
-
// Remove old tools directories
|
|
121
217
|
for (const toolsPath of [
|
|
122
218
|
path.join(HOME, ".evolver", "tools"),
|
|
123
219
|
path.join(HOME, ".harness-evolver"),
|
|
@@ -129,10 +225,39 @@ function cleanPreviousInstall(runtimeDir, scope) {
|
|
|
129
225
|
}
|
|
130
226
|
|
|
131
227
|
if (cleaned > 0) {
|
|
132
|
-
|
|
228
|
+
barLine(c.dim(`Cleaned ${cleaned} items from previous install`));
|
|
133
229
|
}
|
|
134
230
|
}
|
|
135
231
|
|
|
232
|
+
function countInstallables() {
|
|
233
|
+
let skills = 0;
|
|
234
|
+
let agents = 0;
|
|
235
|
+
let tools = 0;
|
|
236
|
+
|
|
237
|
+
const skillsSource = path.join(PLUGIN_ROOT, "skills");
|
|
238
|
+
if (fs.existsSync(skillsSource)) {
|
|
239
|
+
for (const s of fs.readdirSync(skillsSource, { withFileTypes: true })) {
|
|
240
|
+
if (s.isDirectory() && fs.existsSync(path.join(skillsSource, s.name, "SKILL.md"))) skills++;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const agentsSource = path.join(PLUGIN_ROOT, "agents");
|
|
245
|
+
if (fs.existsSync(agentsSource)) {
|
|
246
|
+
for (const a of fs.readdirSync(agentsSource)) {
|
|
247
|
+
if (a.endsWith(".md")) agents++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const toolsSource = path.join(PLUGIN_ROOT, "tools");
|
|
252
|
+
if (fs.existsSync(toolsSource)) {
|
|
253
|
+
for (const t of fs.readdirSync(toolsSource)) {
|
|
254
|
+
if (t.endsWith(".py")) tools++;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { skills, agents, tools };
|
|
259
|
+
}
|
|
260
|
+
|
|
136
261
|
function installSkillsAndAgents(runtimeDir, scope) {
|
|
137
262
|
const baseDir = scope === "local"
|
|
138
263
|
? path.join(process.cwd(), runtimeDir)
|
|
@@ -140,8 +265,8 @@ function installSkillsAndAgents(runtimeDir, scope) {
|
|
|
140
265
|
|
|
141
266
|
const skillsDir = path.join(baseDir, "skills");
|
|
142
267
|
const agentsDir = path.join(baseDir, "agents");
|
|
268
|
+
let installed = 0;
|
|
143
269
|
|
|
144
|
-
// Skills — read SKILL.md name field, use directory name for filesystem
|
|
145
270
|
const skillsSource = path.join(PLUGIN_ROOT, "skills");
|
|
146
271
|
if (fs.existsSync(skillsSource)) {
|
|
147
272
|
for (const skill of fs.readdirSync(skillsSource, { withFileTypes: true })) {
|
|
@@ -150,18 +275,17 @@ function installSkillsAndAgents(runtimeDir, scope) {
|
|
|
150
275
|
const skillMd = path.join(src, "SKILL.md");
|
|
151
276
|
if (!fs.existsSync(skillMd)) continue;
|
|
152
277
|
|
|
153
|
-
// Read the skill name from frontmatter
|
|
154
278
|
const content = fs.readFileSync(skillMd, "utf8");
|
|
155
279
|
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
156
280
|
const skillName = nameMatch ? nameMatch[1].trim() : skill.name;
|
|
157
281
|
|
|
158
282
|
const dest = path.join(skillsDir, skill.name);
|
|
159
283
|
copyDir(src, dest);
|
|
160
|
-
|
|
284
|
+
barLine(`${c.green("\u2714")} ${skillName}`);
|
|
285
|
+
installed++;
|
|
161
286
|
}
|
|
162
287
|
}
|
|
163
288
|
|
|
164
|
-
// Agents
|
|
165
289
|
const agentsSource = path.join(PLUGIN_ROOT, "agents");
|
|
166
290
|
if (fs.existsSync(agentsSource)) {
|
|
167
291
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
@@ -169,9 +293,12 @@ function installSkillsAndAgents(runtimeDir, scope) {
|
|
|
169
293
|
if (!agent.endsWith(".md")) continue;
|
|
170
294
|
copyFile(path.join(agentsSource, agent), path.join(agentsDir, agent));
|
|
171
295
|
const agentName = agent.replace(".md", "");
|
|
172
|
-
|
|
296
|
+
barLine(`${c.green("\u2714")} agent: ${agentName}`);
|
|
297
|
+
installed++;
|
|
173
298
|
}
|
|
174
299
|
}
|
|
300
|
+
|
|
301
|
+
return installed;
|
|
175
302
|
}
|
|
176
303
|
|
|
177
304
|
function installTools() {
|
|
@@ -185,8 +312,9 @@ function installTools() {
|
|
|
185
312
|
copyFile(path.join(toolsSource, tool), path.join(toolsDir, tool));
|
|
186
313
|
count++;
|
|
187
314
|
}
|
|
188
|
-
|
|
315
|
+
return count;
|
|
189
316
|
}
|
|
317
|
+
return 0;
|
|
190
318
|
}
|
|
191
319
|
|
|
192
320
|
function installPythonDeps() {
|
|
@@ -194,11 +322,10 @@ function installPythonDeps() {
|
|
|
194
322
|
const venvPython = path.join(venvDir, "bin", "python");
|
|
195
323
|
const venvPip = path.join(venvDir, "bin", "pip");
|
|
196
324
|
|
|
197
|
-
|
|
325
|
+
step("Setting up Python environment...");
|
|
198
326
|
|
|
199
|
-
// Create venv if it doesn't exist
|
|
200
327
|
if (!fs.existsSync(venvPython)) {
|
|
201
|
-
|
|
328
|
+
barLine("Creating isolated venv at ~/.evolver/venv/");
|
|
202
329
|
const venvCommands = [
|
|
203
330
|
`uv venv "${venvDir}"`,
|
|
204
331
|
`python3 -m venv "${venvDir}"`,
|
|
@@ -214,119 +341,123 @@ function installPythonDeps() {
|
|
|
214
341
|
}
|
|
215
342
|
}
|
|
216
343
|
if (!created) {
|
|
217
|
-
|
|
218
|
-
|
|
344
|
+
stepError("Failed to create venv");
|
|
345
|
+
barLine(c.dim(`Run manually: python3 -m venv ~/.evolver/venv`));
|
|
219
346
|
return false;
|
|
220
347
|
}
|
|
221
|
-
|
|
348
|
+
stepDone("venv created");
|
|
222
349
|
} else {
|
|
223
|
-
|
|
350
|
+
stepDone("venv exists at ~/.evolver/venv/");
|
|
224
351
|
}
|
|
225
352
|
|
|
226
|
-
|
|
353
|
+
barEmpty();
|
|
354
|
+
|
|
227
355
|
const installCommands = [
|
|
228
356
|
`uv pip install --python "${venvPython}" langsmith`,
|
|
229
357
|
`"${venvPip}" install --upgrade langsmith`,
|
|
230
358
|
`"${venvPython}" -m pip install --upgrade langsmith`,
|
|
231
359
|
];
|
|
232
360
|
|
|
361
|
+
step("Installing langsmith...");
|
|
233
362
|
for (const cmd of installCommands) {
|
|
234
363
|
try {
|
|
235
364
|
execSync(cmd, { stdio: "pipe", timeout: 120000 });
|
|
236
|
-
|
|
365
|
+
stepDone("langsmith installed in venv");
|
|
237
366
|
return true;
|
|
238
367
|
} catch {
|
|
239
368
|
continue;
|
|
240
369
|
}
|
|
241
370
|
}
|
|
242
371
|
|
|
243
|
-
|
|
244
|
-
|
|
372
|
+
stepError("Could not install langsmith");
|
|
373
|
+
barLine(c.dim("Run manually: ~/.evolver/venv/bin/pip install langsmith"));
|
|
245
374
|
return false;
|
|
246
375
|
}
|
|
247
376
|
|
|
248
377
|
async function configureLangSmith(rl) {
|
|
249
|
-
console.log(`\n ${BOLD}${GREEN}LangSmith Configuration${RESET} ${DIM}(required)${RESET}\n`);
|
|
250
|
-
|
|
251
378
|
const langsmithCredsDir = process.platform === "darwin"
|
|
252
379
|
? path.join(HOME, "Library", "Application Support", "langsmith-cli")
|
|
253
380
|
: path.join(HOME, ".config", "langsmith-cli");
|
|
254
381
|
const langsmithCredsFile = path.join(langsmithCredsDir, "credentials");
|
|
255
382
|
const hasLangsmithCli = checkCommand("langsmith-cli --version");
|
|
256
383
|
|
|
257
|
-
// --- Step 1: API Key ---
|
|
258
384
|
let hasKey = false;
|
|
259
385
|
|
|
386
|
+
barEmpty();
|
|
387
|
+
step(c.bold("LangSmith API Key") + " " + c.dim("(required)"));
|
|
388
|
+
|
|
260
389
|
if (process.env.LANGSMITH_API_KEY) {
|
|
261
|
-
|
|
390
|
+
stepDone("LANGSMITH_API_KEY found in environment");
|
|
262
391
|
hasKey = true;
|
|
263
392
|
} else if (fs.existsSync(langsmithCredsFile)) {
|
|
264
393
|
try {
|
|
265
394
|
const content = fs.readFileSync(langsmithCredsFile, "utf8");
|
|
266
395
|
if (content.includes("LANGSMITH_API_KEY=lsv2_")) {
|
|
267
|
-
|
|
396
|
+
stepDone("API key found in credentials file");
|
|
268
397
|
hasKey = true;
|
|
269
398
|
}
|
|
270
399
|
} catch {}
|
|
271
400
|
}
|
|
272
401
|
|
|
273
402
|
if (!hasKey) {
|
|
274
|
-
|
|
275
|
-
|
|
403
|
+
barLine(c.dim("Get yours at https://smith.langchain.com/settings"));
|
|
404
|
+
barLine(c.dim("LangSmith is required. The evolver won't work without it."));
|
|
405
|
+
barEmpty();
|
|
276
406
|
|
|
277
|
-
// Keep asking until they provide a key or explicitly skip
|
|
278
407
|
let attempts = 0;
|
|
279
408
|
while (!hasKey && attempts < 3) {
|
|
280
|
-
const apiKey = await ask(rl,
|
|
409
|
+
const apiKey = await ask(rl, `${c.cyan(S.stepActive)} Paste your LangSmith API key (lsv2_pt_...): `);
|
|
281
410
|
const key = apiKey.trim();
|
|
282
411
|
|
|
283
412
|
if (key && key.startsWith("lsv2_")) {
|
|
284
413
|
try {
|
|
285
414
|
fs.mkdirSync(langsmithCredsDir, { recursive: true });
|
|
286
415
|
fs.writeFileSync(langsmithCredsFile, `LANGSMITH_API_KEY=${key}\n`);
|
|
287
|
-
|
|
416
|
+
stepDone("API key saved");
|
|
288
417
|
hasKey = true;
|
|
289
418
|
} catch {
|
|
290
|
-
|
|
291
|
-
|
|
419
|
+
stepError("Failed to save");
|
|
420
|
+
barLine(c.dim(`Add to your shell: export LANGSMITH_API_KEY=${key}`));
|
|
421
|
+
hasKey = true;
|
|
292
422
|
}
|
|
293
423
|
} else if (key) {
|
|
294
|
-
|
|
424
|
+
barLine(c.yellow("Invalid \u2014 LangSmith keys start with lsv2_"));
|
|
295
425
|
attempts++;
|
|
296
426
|
} else {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
console.log(` Run: ${DIM}export LANGSMITH_API_KEY=lsv2_pt_your_key${RESET}\n`);
|
|
427
|
+
stepError("No API key configured");
|
|
428
|
+
barLine(c.dim("/evolver:setup will not work until you set LANGSMITH_API_KEY"));
|
|
429
|
+
barLine(c.dim("Run: export LANGSMITH_API_KEY=lsv2_pt_your_key"));
|
|
301
430
|
break;
|
|
302
431
|
}
|
|
303
432
|
}
|
|
304
433
|
}
|
|
305
434
|
|
|
306
|
-
|
|
435
|
+
barEmpty();
|
|
436
|
+
step(c.bold("langsmith-cli") + " " + c.dim("(required for LLM-as-judge)"));
|
|
437
|
+
|
|
307
438
|
if (hasLangsmithCli) {
|
|
308
|
-
|
|
439
|
+
stepDone("langsmith-cli installed");
|
|
309
440
|
} else {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
console.log(`\n Installing langsmith-cli...`);
|
|
441
|
+
barLine(c.dim("The evaluator agent uses it to read experiment outputs and write scores"));
|
|
442
|
+
step("Installing langsmith-cli...");
|
|
313
443
|
try {
|
|
314
444
|
execSync("uv tool install langsmith-cli 2>/dev/null || pip install langsmith-cli 2>/dev/null || pip3 install langsmith-cli", { stdio: "pipe", timeout: 60000 });
|
|
315
|
-
|
|
445
|
+
stepDone("langsmith-cli installed");
|
|
316
446
|
|
|
317
|
-
// If we have a key, auto-authenticate
|
|
318
447
|
if (hasKey && fs.existsSync(langsmithCredsFile)) {
|
|
319
|
-
|
|
448
|
+
stepDone("langsmith-cli auto-authenticated");
|
|
320
449
|
}
|
|
321
450
|
} catch {
|
|
322
|
-
|
|
323
|
-
|
|
451
|
+
stepError("Could not install langsmith-cli");
|
|
452
|
+
barLine(c.dim("Install manually: uv tool install langsmith-cli"));
|
|
324
453
|
}
|
|
325
454
|
}
|
|
326
455
|
}
|
|
327
456
|
|
|
328
457
|
async function configureOptionalIntegrations(rl) {
|
|
329
|
-
|
|
458
|
+
barEmpty();
|
|
459
|
+
step(c.bold("Optional Integrations"));
|
|
460
|
+
barEmpty();
|
|
330
461
|
|
|
331
462
|
// Context7 MCP
|
|
332
463
|
const hasContext7 = (() => {
|
|
@@ -342,20 +473,24 @@ async function configureOptionalIntegrations(rl) {
|
|
|
342
473
|
})();
|
|
343
474
|
|
|
344
475
|
if (hasContext7) {
|
|
345
|
-
|
|
476
|
+
stepDone("Context7 MCP already configured");
|
|
346
477
|
} else {
|
|
347
|
-
|
|
348
|
-
const c7Answer = await ask(rl,
|
|
478
|
+
barLine(c.bold("Context7 MCP") + " \u2014 " + c.dim("up-to-date library documentation"));
|
|
479
|
+
const c7Answer = await ask(rl, `${c.cyan(S.stepActive)} Install Context7 MCP? [y/N]: `);
|
|
349
480
|
if (c7Answer.trim().toLowerCase() === "y") {
|
|
481
|
+
step("Installing Context7 MCP...");
|
|
350
482
|
try {
|
|
351
483
|
execSync("claude mcp add context7 -- npx -y @upstash/context7-mcp@latest", { stdio: "inherit" });
|
|
352
|
-
|
|
484
|
+
stepDone("Context7 MCP configured");
|
|
353
485
|
} catch {
|
|
354
|
-
|
|
486
|
+
stepError("Failed to install Context7 MCP");
|
|
487
|
+
barLine(c.dim("Run manually: claude mcp add context7 -- npx -y @upstash/context7-mcp@latest"));
|
|
355
488
|
}
|
|
356
489
|
}
|
|
357
490
|
}
|
|
358
491
|
|
|
492
|
+
barEmpty();
|
|
493
|
+
|
|
359
494
|
// LangChain Docs MCP
|
|
360
495
|
const hasLcDocs = (() => {
|
|
361
496
|
try {
|
|
@@ -370,38 +505,50 @@ async function configureOptionalIntegrations(rl) {
|
|
|
370
505
|
})();
|
|
371
506
|
|
|
372
507
|
if (hasLcDocs) {
|
|
373
|
-
|
|
508
|
+
stepDone("LangChain Docs MCP already configured");
|
|
374
509
|
} else {
|
|
375
|
-
|
|
376
|
-
const lcAnswer = await ask(rl,
|
|
510
|
+
barLine(c.bold("LangChain Docs MCP") + " \u2014 " + c.dim("LangChain/LangGraph/LangSmith docs"));
|
|
511
|
+
const lcAnswer = await ask(rl, `${c.cyan(S.stepActive)} Install LangChain Docs MCP? [y/N]: `);
|
|
377
512
|
if (lcAnswer.trim().toLowerCase() === "y") {
|
|
513
|
+
step("Installing LangChain Docs MCP...");
|
|
378
514
|
try {
|
|
379
515
|
execSync("claude mcp add docs-langchain --transport http https://docs.langchain.com/mcp", { stdio: "inherit" });
|
|
380
|
-
|
|
516
|
+
stepDone("LangChain Docs MCP configured");
|
|
381
517
|
} catch {
|
|
382
|
-
|
|
518
|
+
stepError("Failed to install LangChain Docs MCP");
|
|
519
|
+
barLine(c.dim("Run manually: claude mcp add docs-langchain --transport http https://docs.langchain.com/mcp"));
|
|
383
520
|
}
|
|
384
521
|
}
|
|
385
522
|
}
|
|
386
523
|
}
|
|
387
524
|
|
|
525
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
526
|
+
|
|
388
527
|
async function main() {
|
|
389
|
-
|
|
528
|
+
banner();
|
|
390
529
|
|
|
391
|
-
|
|
530
|
+
header("harness-evolver");
|
|
531
|
+
step(`Source: ${c.dim(`v${VERSION} \u2014 LangSmith-native agent optimization`)}`);
|
|
532
|
+
|
|
533
|
+
// Version check
|
|
392
534
|
try {
|
|
393
535
|
const latest = execSync("npm view harness-evolver version", { stdio: "pipe", timeout: 5000 }).toString().trim();
|
|
394
536
|
if (latest && latest !== VERSION) {
|
|
395
|
-
|
|
396
|
-
|
|
537
|
+
barEmpty();
|
|
538
|
+
stepError(`You're running v${VERSION} but v${c.cyan(latest)} is available`);
|
|
539
|
+
barLine(c.dim(`Run: npx harness-evolver@${latest}`));
|
|
397
540
|
}
|
|
398
541
|
} catch {}
|
|
399
542
|
|
|
543
|
+
barEmpty();
|
|
544
|
+
|
|
545
|
+
// Python check
|
|
400
546
|
if (!checkPython()) {
|
|
401
|
-
|
|
547
|
+
stepError("python3 not found. Install Python 3.10+ first.");
|
|
548
|
+
footer();
|
|
402
549
|
process.exit(1);
|
|
403
550
|
}
|
|
404
|
-
|
|
551
|
+
stepDone("python3 found");
|
|
405
552
|
|
|
406
553
|
// Detect runtimes
|
|
407
554
|
const RUNTIMES = [
|
|
@@ -412,22 +559,25 @@ async function main() {
|
|
|
412
559
|
].filter(r => fs.existsSync(path.join(HOME, r.dir)));
|
|
413
560
|
|
|
414
561
|
if (RUNTIMES.length === 0) {
|
|
415
|
-
|
|
416
|
-
|
|
562
|
+
stepError("No supported runtime detected");
|
|
563
|
+
barLine(c.dim("Install Claude Code, Cursor, Codex, or Windsurf first"));
|
|
564
|
+
footer();
|
|
417
565
|
process.exit(1);
|
|
418
566
|
}
|
|
419
567
|
|
|
420
568
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
421
569
|
|
|
422
570
|
// Runtime selection
|
|
423
|
-
|
|
424
|
-
|
|
571
|
+
barEmpty();
|
|
572
|
+
stepPrompt("Which runtime(s) to install for?");
|
|
573
|
+
barEmpty();
|
|
574
|
+
RUNTIMES.forEach((r, i) => barLine(` ${c.bold(String(i + 1))} ${r.name.padEnd(14)} ${c.dim(`~/${r.dir}`)}`));
|
|
425
575
|
if (RUNTIMES.length > 1) {
|
|
426
|
-
|
|
427
|
-
|
|
576
|
+
barLine(` ${c.bold(String(RUNTIMES.length + 1))} All`);
|
|
577
|
+
barLine(c.dim("Select multiple: 1,2 or 1 2"));
|
|
428
578
|
}
|
|
429
579
|
|
|
430
|
-
const runtimeAnswer = await ask(rl,
|
|
580
|
+
const runtimeAnswer = await ask(rl, `${c.cyan(S.stepActive)} Choice [1]: `);
|
|
431
581
|
const runtimeInput = (runtimeAnswer.trim() || "1");
|
|
432
582
|
|
|
433
583
|
let selected;
|
|
@@ -439,31 +589,48 @@ async function main() {
|
|
|
439
589
|
}
|
|
440
590
|
if (selected.length === 0) selected = [RUNTIMES[0]];
|
|
441
591
|
|
|
592
|
+
stepDone(`Target: ${c.cyan(selected.map(r => r.name).join(", "))}`);
|
|
593
|
+
|
|
442
594
|
// Scope selection
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
595
|
+
barEmpty();
|
|
596
|
+
stepPrompt("Where to install?");
|
|
597
|
+
barEmpty();
|
|
598
|
+
barLine(` ${c.bold("1")} Global ${c.dim(`(~/${selected[0].dir})`)}`);
|
|
599
|
+
barLine(` ${c.bold("2")} Local ${c.dim(`(./${selected[0].dir})`)}`);
|
|
446
600
|
|
|
447
|
-
const scopeAnswer = await ask(rl,
|
|
601
|
+
const scopeAnswer = await ask(rl, `${c.cyan(S.stepActive)} Choice [1]: `);
|
|
448
602
|
const scope = (scopeAnswer.trim() === "2") ? "local" : "global";
|
|
449
603
|
|
|
450
|
-
|
|
451
|
-
|
|
604
|
+
stepDone(`Scope: ${c.cyan(scope)}`);
|
|
605
|
+
|
|
606
|
+
// Discover what we're installing
|
|
607
|
+
const counts = countInstallables();
|
|
608
|
+
barEmpty();
|
|
609
|
+
step(`Found ${c.bold(`${counts.skills} skills, ${counts.agents} agents, ${counts.tools} tools`)}`);
|
|
610
|
+
|
|
611
|
+
// Clean previous install
|
|
612
|
+
barEmpty();
|
|
613
|
+
step("Cleaning previous install...");
|
|
452
614
|
for (const runtime of selected) {
|
|
453
615
|
cleanPreviousInstall(runtime.dir, scope);
|
|
454
616
|
}
|
|
617
|
+
stepDone("Clean");
|
|
455
618
|
|
|
456
619
|
// Install skills + agents
|
|
457
|
-
|
|
620
|
+
barEmpty();
|
|
458
621
|
for (const runtime of selected) {
|
|
459
|
-
|
|
622
|
+
step(`Installing to ${c.bold(runtime.name)}...`);
|
|
623
|
+
barEmpty();
|
|
460
624
|
installSkillsAndAgents(runtime.dir, scope);
|
|
461
|
-
|
|
625
|
+
barEmpty();
|
|
626
|
+
stepDone(`${c.cyan(runtime.name)} ready`);
|
|
462
627
|
}
|
|
463
628
|
|
|
464
|
-
// Install tools
|
|
465
|
-
|
|
466
|
-
|
|
629
|
+
// Install tools
|
|
630
|
+
barEmpty();
|
|
631
|
+
step("Installing tools...");
|
|
632
|
+
const toolCount = installTools();
|
|
633
|
+
stepDone(`${toolCount} tools installed to ~/.evolver/tools/`);
|
|
467
634
|
|
|
468
635
|
// Version marker
|
|
469
636
|
const versionPath = path.join(HOME, ".evolver", "VERSION");
|
|
@@ -471,27 +638,33 @@ async function main() {
|
|
|
471
638
|
fs.writeFileSync(versionPath, VERSION);
|
|
472
639
|
|
|
473
640
|
// Install Python deps
|
|
641
|
+
barEmpty();
|
|
474
642
|
installPythonDeps();
|
|
475
643
|
|
|
476
|
-
// Configure LangSmith
|
|
644
|
+
// Configure LangSmith
|
|
477
645
|
await configureLangSmith(rl);
|
|
478
646
|
|
|
479
647
|
// Optional integrations
|
|
480
648
|
await configureOptionalIntegrations(rl);
|
|
481
649
|
|
|
482
650
|
// Done
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
651
|
+
barEmpty();
|
|
652
|
+
stepDone(c.green("Done.") + " Restart your agent tools to load the plugin.");
|
|
653
|
+
barEmpty();
|
|
654
|
+
barLine(c.dim("Commands:"));
|
|
655
|
+
barLine(` ${c.cyan("/evolver:setup")} \u2014 configure LangSmith for your project`);
|
|
656
|
+
barLine(` ${c.cyan("/evolver:evolve")} \u2014 run the optimization loop`);
|
|
657
|
+
barLine(` ${c.cyan("/evolver:status")} \u2014 check progress`);
|
|
658
|
+
barLine(` ${c.cyan("/evolver:deploy")} \u2014 finalize and push`);
|
|
659
|
+
barEmpty();
|
|
660
|
+
barLine(c.dim("GitHub: https://github.com/raphaelchristi/harness-evolver"));
|
|
661
|
+
footer();
|
|
490
662
|
|
|
491
663
|
rl.close();
|
|
492
664
|
}
|
|
493
665
|
|
|
494
666
|
main().catch(err => {
|
|
495
|
-
|
|
667
|
+
stepError(err.message);
|
|
668
|
+
footer();
|
|
496
669
|
process.exit(1);
|
|
497
670
|
});
|