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.
@@ -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.2.1",
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 v3 installer.
4
- * Copies skills/agents/tools to runtime directories (GSD pattern).
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
- const GREEN = "\x1b[38;2;0;255;136m";
20
- const YELLOW = "\x1b[33m";
21
- const RED = "\x1b[31m";
22
- const DIM = "\x1b[2m";
23
- const BOLD = "\x1b[1m";
24
- const RESET = "\x1b[0m";
25
-
26
- const LOGO = `${BOLD}${GREEN}
27
- ╦╔═╗╦═╗╔╗╔╔═╗╔═╗╔═╗ ╔═╗╦ ╦╔═╗╦ ╦ ╦╔═╗╦═╗
28
- ╠═╣╠═╣╠╦╝║║║║╣ ╚═╗╚═╗ ║╣ ╚╗╔╝║ ║║ ╚╗╔╝║╣ ╠╦╝
29
- ╩ ╩╩ ╩╩╚═╝╚╝╚═╝╚═╝╚═╝ ╚═╝ ╚╝ ╚═╝╩═╝ ╚╝ ╚═╝╩╚═
30
- ${RESET}
31
- ${DIM}${GREEN} LangSmith-native agent optimization v${VERSION}${RESET}
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
- console.log(` ${DIM}Cleaned ${cleaned} items from previous install${RESET}`);
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
- console.log(` ${GREEN}✓${RESET} ${skillName}`);
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
- console.log(` ${GREEN}✓${RESET} agent: ${agentName}`);
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
- console.log(` ${GREEN}✓${RESET} ${count} tools installed to ~/.evolver/tools/`);
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
- console.log(`\n ${YELLOW}Setting up Python environment...${RESET}`);
325
+ step("Setting up Python environment...");
198
326
 
199
- // Create venv if it doesn't exist
200
327
  if (!fs.existsSync(venvPython)) {
201
- console.log(` Creating isolated venv at ~/.evolver/venv/`);
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
- console.log(` ${RED}Failed to create venv.${RESET}`);
218
- console.log(` Run manually: ${BOLD}python3 -m venv ~/.evolver/venv${RESET}`);
344
+ stepError("Failed to create venv");
345
+ barLine(c.dim(`Run manually: python3 -m venv ~/.evolver/venv`));
219
346
  return false;
220
347
  }
221
- console.log(` ${GREEN}✓${RESET} venv created`);
348
+ stepDone("venv created");
222
349
  } else {
223
- console.log(` ${GREEN}✓${RESET} venv exists at ~/.evolver/venv/`);
350
+ stepDone("venv exists at ~/.evolver/venv/");
224
351
  }
225
352
 
226
- // Install/upgrade deps in the venv
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
- console.log(` ${GREEN}✓${RESET} langsmith installed in venv`);
365
+ stepDone("langsmith installed in venv");
237
366
  return true;
238
367
  } catch {
239
368
  continue;
240
369
  }
241
370
  }
242
371
 
243
- console.log(` ${YELLOW}!${RESET} Could not install packages in venv.`);
244
- console.log(` Run manually: ${BOLD}~/.evolver/venv/bin/pip install langsmith${RESET}`);
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
- console.log(` ${GREEN}✓${RESET} LANGSMITH_API_KEY found in environment`);
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
- console.log(` ${GREEN}✓${RESET} API key found in credentials file`);
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
- console.log(` ${BOLD}LangSmith API Key${RESET} — get yours at ${DIM}https://smith.langchain.com/settings${RESET}`);
275
- console.log(` ${DIM}LangSmith is required. The evolver won't work without it.${RESET}\n`);
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, ` ${YELLOW}Paste your LangSmith API key (lsv2_pt_...):${RESET} `);
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
- console.log(` ${GREEN}✓${RESET} API key saved`);
416
+ stepDone("API key saved");
288
417
  hasKey = true;
289
418
  } catch {
290
- console.log(` ${RED}Failed to save.${RESET} Add to your shell: export LANGSMITH_API_KEY=${key}`);
291
- hasKey = true; // they have the key, just couldn't save
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
- console.log(` ${YELLOW}Invalid LangSmith keys start with lsv2_${RESET}`);
424
+ barLine(c.yellow("Invalid \u2014 LangSmith keys start with lsv2_"));
295
425
  attempts++;
296
426
  } else {
297
- // Empty input — skip
298
- console.log(`\n ${RED}WARNING:${RESET} No API key configured.`);
299
- console.log(` ${BOLD}/evolver:setup will not work${RESET} until you set LANGSMITH_API_KEY.`);
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
- // --- Step 2: langsmith-cli (required for evaluator agent) ---
435
+ barEmpty();
436
+ step(c.bold("langsmith-cli") + " " + c.dim("(required for LLM-as-judge)"));
437
+
307
438
  if (hasLangsmithCli) {
308
- console.log(` ${GREEN}✓${RESET} langsmith-cli installed`);
439
+ stepDone("langsmith-cli installed");
309
440
  } else {
310
- console.log(`\n ${BOLD}langsmith-cli${RESET} ${YELLOW}required${RESET} for LLM-as-judge evaluation`);
311
- console.log(` ${DIM}The evaluator agent uses it to read experiment outputs and write scores.${RESET}`);
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
- console.log(` ${GREEN}✓${RESET} langsmith-cli installed`);
445
+ stepDone("langsmith-cli installed");
316
446
 
317
- // If we have a key, auto-authenticate
318
447
  if (hasKey && fs.existsSync(langsmithCredsFile)) {
319
- console.log(` ${GREEN}✓${RESET} langsmith-cli auto-authenticated (credentials file exists)`);
448
+ stepDone("langsmith-cli auto-authenticated");
320
449
  }
321
450
  } catch {
322
- console.log(` ${RED}!${RESET} Could not install langsmith-cli.`);
323
- console.log(` ${BOLD}This is required.${RESET} Install manually: ${DIM}uv tool install langsmith-cli${RESET}`);
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
- console.log(`\n ${YELLOW}Optional Integrations${RESET}\n`);
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
- console.log(` ${GREEN}✓${RESET} Context7 MCP already configured`);
476
+ stepDone("Context7 MCP already configured");
346
477
  } else {
347
- console.log(` ${BOLD}Context7 MCP${RESET} up-to-date library documentation (LangChain, OpenAI, etc.)`);
348
- const c7Answer = await ask(rl, `\n ${YELLOW}Install Context7 MCP? [y/N]:${RESET} `);
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
- console.log(`\n ${GREEN}✓${RESET} Context7 MCP configured`);
484
+ stepDone("Context7 MCP configured");
353
485
  } catch {
354
- console.log(`\n ${RED}Failed.${RESET} Install manually: claude mcp add context7 -- npx -y @upstash/context7-mcp@latest`);
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
- console.log(` ${GREEN}✓${RESET} LangChain Docs MCP already configured`);
508
+ stepDone("LangChain Docs MCP already configured");
374
509
  } else {
375
- console.log(`\n ${BOLD}LangChain Docs MCP${RESET} LangChain/LangGraph/LangSmith documentation`);
376
- const lcAnswer = await ask(rl, `\n ${YELLOW}Install LangChain Docs MCP? [y/N]:${RESET} `);
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
- console.log(`\n ${GREEN}✓${RESET} LangChain Docs MCP configured`);
516
+ stepDone("LangChain Docs MCP configured");
381
517
  } catch {
382
- console.log(`\n ${RED}Failed.${RESET} Install manually: claude mcp add docs-langchain --transport http https://docs.langchain.com/mcp`);
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
- console.log(LOGO);
528
+ banner();
390
529
 
391
- // Check if running latest version (npx may cache an old one)
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
- console.log(` ${YELLOW}!${RESET} You're running v${VERSION} but v${latest} is available.`);
396
- console.log(` Run: ${BOLD}npx harness-evolver@${latest}${RESET} or ${BOLD}npx --yes harness-evolver@latest${RESET}\n`);
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
- console.error(` ${RED}ERROR:${RESET} python3 not found. Install Python 3.10+ first.`);
547
+ stepError("python3 not found. Install Python 3.10+ first.");
548
+ footer();
402
549
  process.exit(1);
403
550
  }
404
- console.log(` ${GREEN}✓${RESET} python3 found`);
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
- console.error(`\n ${RED}ERROR:${RESET} No supported runtime detected.`);
416
- console.error(` Install Claude Code, Cursor, Codex, or Windsurf first.`);
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
- console.log(`\n ${YELLOW}Which runtime(s) to install for?${RESET}\n`);
424
- RUNTIMES.forEach((r, i) => console.log(` ${i + 1}) ${r.name.padEnd(14)} (~/${r.dir})`));
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
- console.log(` ${RUNTIMES.length + 1}) All`);
427
- console.log(`\n ${DIM}Select multiple: 1,2 or 1 2${RESET}`);
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, `\n ${YELLOW}Choice [1]:${RESET} `);
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
- console.log(`\n ${YELLOW}Where to install?${RESET}\n`);
444
- console.log(` 1) Global (~/${selected[0].dir}) — available in all projects`);
445
- console.log(` 2) Local (./${selected[0].dir}) — this project only`);
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, `\n ${YELLOW}Choice [1]:${RESET} `);
601
+ const scopeAnswer = await ask(rl, `${c.cyan(S.stepActive)} Choice [1]: `);
448
602
  const scope = (scopeAnswer.trim() === "2") ? "local" : "global";
449
603
 
450
- // Clean previous install (remove ALL old files before installing new ones)
451
- console.log(`\n ${BOLD}Cleaning previous install${RESET}`);
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
- console.log(`\n ${BOLD}Installing skills & agents${RESET}\n`);
620
+ barEmpty();
458
621
  for (const runtime of selected) {
459
- console.log(` ${GREEN}${runtime.name}${RESET}:`);
622
+ step(`Installing to ${c.bold(runtime.name)}...`);
623
+ barEmpty();
460
624
  installSkillsAndAgents(runtime.dir, scope);
461
- console.log();
625
+ barEmpty();
626
+ stepDone(`${c.cyan(runtime.name)} ready`);
462
627
  }
463
628
 
464
- // Install tools (fresh — old dir was cleaned above)
465
- console.log(` ${BOLD}Installing tools${RESET}`);
466
- installTools();
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 (required)
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
- console.log(`\n ${GREEN}${BOLD}Setup complete!${RESET}\n`);
484
- console.log(` ${DIM}Restart Claude Code, then:${RESET}`);
485
- console.log(` ${GREEN}/evolver:setup${RESET} — configure LangSmith for your project`);
486
- console.log(` ${GREEN}/evolver:evolve${RESET} — run the optimization loop`);
487
- console.log(` ${GREEN}/evolver:status${RESET} check progress`);
488
- console.log(` ${GREEN}/evolver:deploy${RESET} finalize and push`);
489
- console.log(`\n ${DIM}GitHub: https://github.com/raphaelchristi/harness-evolver${RESET}\n`);
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
- console.error(` ${RED}ERROR:${RESET} ${err.message}`);
667
+ stepError(err.message);
668
+ footer();
496
669
  process.exit(1);
497
670
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-evolver",
3
- "version": "3.2.1",
3
+ "version": "3.3.1",
4
4
  "description": "LangSmith-native autonomous agent optimization for Claude Code",
5
5
  "author": "Raphael Valdetaro",
6
6
  "license": "MIT",