harness-evolver 3.0.1 → 3.0.3
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 +77 -8
- package/package.json +1 -1
- package/skills/setup/SKILL.md +25 -2
- package/tools/read_results.py +25 -0
- package/tools/run_eval.py +37 -0
- package/tools/setup.py +52 -0
package/bin/install.js
CHANGED
|
@@ -72,6 +72,67 @@ function checkCommand(cmd) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function cleanPreviousInstall(runtimeDir, scope) {
|
|
76
|
+
const baseDir = scope === "local"
|
|
77
|
+
? path.join(process.cwd(), runtimeDir)
|
|
78
|
+
: path.join(HOME, runtimeDir);
|
|
79
|
+
|
|
80
|
+
const skillsDir = path.join(baseDir, "skills");
|
|
81
|
+
const agentsDir = path.join(baseDir, "agents");
|
|
82
|
+
let cleaned = 0;
|
|
83
|
+
|
|
84
|
+
// Remove ALL evolver/harness-evolver skills (any version)
|
|
85
|
+
if (fs.existsSync(skillsDir)) {
|
|
86
|
+
const ours = ["setup", "evolve", "deploy", "status",
|
|
87
|
+
"init", "architect", "compare", "critic", "diagnose",
|
|
88
|
+
"import-traces", "evolve-v3", "deploy-v3", "status-v3",
|
|
89
|
+
"harness-evolver:init", "harness-evolver:evolve",
|
|
90
|
+
"harness-evolver:status", "harness-evolver:deploy",
|
|
91
|
+
"harness-evolver:compare", "harness-evolver:diagnose",
|
|
92
|
+
"harness-evolver:architect", "harness-evolver:critic",
|
|
93
|
+
"harness-evolver:import-traces"];
|
|
94
|
+
for (const name of ours) {
|
|
95
|
+
const p = path.join(skillsDir, name);
|
|
96
|
+
if (fs.existsSync(p)) {
|
|
97
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
98
|
+
cleaned++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Remove ALL evolver/harness-evolver agents
|
|
104
|
+
if (fs.existsSync(agentsDir)) {
|
|
105
|
+
for (const f of fs.readdirSync(agentsDir)) {
|
|
106
|
+
if (f.startsWith("evolver-") || f.startsWith("harness-evolver-")) {
|
|
107
|
+
fs.rmSync(path.join(agentsDir, f), { force: true });
|
|
108
|
+
cleaned++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Remove old commands/ directory (v1)
|
|
114
|
+
const oldCommandsDir = path.join(baseDir, "commands", "harness-evolver");
|
|
115
|
+
if (fs.existsSync(oldCommandsDir)) {
|
|
116
|
+
fs.rmSync(oldCommandsDir, { recursive: true, force: true });
|
|
117
|
+
cleaned++;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Remove old tools directories
|
|
121
|
+
for (const toolsPath of [
|
|
122
|
+
path.join(HOME, ".evolver", "tools"),
|
|
123
|
+
path.join(HOME, ".harness-evolver"),
|
|
124
|
+
]) {
|
|
125
|
+
if (fs.existsSync(toolsPath)) {
|
|
126
|
+
fs.rmSync(toolsPath, { recursive: true, force: true });
|
|
127
|
+
cleaned++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (cleaned > 0) {
|
|
132
|
+
console.log(` ${DIM}Cleaned ${cleaned} items from previous install${RESET}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
75
136
|
function installSkillsAndAgents(runtimeDir, scope) {
|
|
76
137
|
const baseDir = scope === "local"
|
|
77
138
|
? path.join(process.cwd(), runtimeDir)
|
|
@@ -100,13 +161,6 @@ function installSkillsAndAgents(runtimeDir, scope) {
|
|
|
100
161
|
}
|
|
101
162
|
}
|
|
102
163
|
|
|
103
|
-
// Cleanup old v2 commands/ directory
|
|
104
|
-
const oldCommandsDir = path.join(baseDir, "commands", "harness-evolver");
|
|
105
|
-
if (fs.existsSync(oldCommandsDir)) {
|
|
106
|
-
fs.rmSync(oldCommandsDir, { recursive: true, force: true });
|
|
107
|
-
console.log(` ${DIM}Cleaned up old commands/ directory${RESET}`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
164
|
// Agents
|
|
111
165
|
const agentsSource = path.join(PLUGIN_ROOT, "agents");
|
|
112
166
|
if (fs.existsSync(agentsSource)) {
|
|
@@ -267,6 +321,15 @@ async function configureOptionalIntegrations(rl) {
|
|
|
267
321
|
async function main() {
|
|
268
322
|
console.log(LOGO);
|
|
269
323
|
|
|
324
|
+
// Check if running latest version (npx may cache an old one)
|
|
325
|
+
try {
|
|
326
|
+
const latest = execSync("npm view harness-evolver version", { stdio: "pipe", timeout: 5000 }).toString().trim();
|
|
327
|
+
if (latest && latest !== VERSION) {
|
|
328
|
+
console.log(` ${YELLOW}!${RESET} You're running v${VERSION} but v${latest} is available.`);
|
|
329
|
+
console.log(` Run: ${BOLD}npx harness-evolver@${latest}${RESET} or ${BOLD}npx --yes harness-evolver@latest${RESET}\n`);
|
|
330
|
+
}
|
|
331
|
+
} catch {}
|
|
332
|
+
|
|
270
333
|
if (!checkPython()) {
|
|
271
334
|
console.error(` ${RED}ERROR:${RESET} python3 not found. Install Python 3.10+ first.`);
|
|
272
335
|
process.exit(1);
|
|
@@ -317,6 +380,12 @@ async function main() {
|
|
|
317
380
|
const scopeAnswer = await ask(rl, `\n ${YELLOW}Choice [1]:${RESET} `);
|
|
318
381
|
const scope = (scopeAnswer.trim() === "2") ? "local" : "global";
|
|
319
382
|
|
|
383
|
+
// Clean previous install (remove ALL old files before installing new ones)
|
|
384
|
+
console.log(`\n ${BOLD}Cleaning previous install${RESET}`);
|
|
385
|
+
for (const runtime of selected) {
|
|
386
|
+
cleanPreviousInstall(runtime.dir, scope);
|
|
387
|
+
}
|
|
388
|
+
|
|
320
389
|
// Install skills + agents
|
|
321
390
|
console.log(`\n ${BOLD}Installing skills & agents${RESET}\n`);
|
|
322
391
|
for (const runtime of selected) {
|
|
@@ -325,7 +394,7 @@ async function main() {
|
|
|
325
394
|
console.log();
|
|
326
395
|
}
|
|
327
396
|
|
|
328
|
-
// Install tools
|
|
397
|
+
// Install tools (fresh — old dir was cleaned above)
|
|
329
398
|
console.log(` ${BOLD}Installing tools${RESET}`);
|
|
330
399
|
installTools();
|
|
331
400
|
|
package/package.json
CHANGED
package/skills/setup/SKILL.md
CHANGED
|
@@ -11,8 +11,31 @@ Set up the Harness Evolver v3 in a project. Explores the codebase, configures La
|
|
|
11
11
|
|
|
12
12
|
## Prerequisites
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Check for LangSmith API key — it can be in the environment, the credentials file, or .env:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python3 -c "
|
|
18
|
+
import os, platform
|
|
19
|
+
key = os.environ.get('LANGSMITH_API_KEY', '')
|
|
20
|
+
if not key:
|
|
21
|
+
creds = os.path.expanduser('~/Library/Application Support/langsmith-cli/credentials') if platform.system() == 'Darwin' else os.path.expanduser('~/.config/langsmith-cli/credentials')
|
|
22
|
+
if os.path.exists(creds):
|
|
23
|
+
for line in open(creds):
|
|
24
|
+
if line.strip().startswith('LANGSMITH_API_KEY='):
|
|
25
|
+
key = line.strip().split('=',1)[1].strip()
|
|
26
|
+
if not key and os.path.exists('.env'):
|
|
27
|
+
for line in open('.env'):
|
|
28
|
+
if line.strip().startswith('LANGSMITH_API_KEY=') and not line.strip().startswith('#'):
|
|
29
|
+
key = line.strip().split('=',1)[1].strip().strip('\"').strip(\"'\")
|
|
30
|
+
print('OK' if key else 'MISSING')
|
|
31
|
+
"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If `MISSING`: "Set your LangSmith API key: `export LANGSMITH_API_KEY=lsv2_pt_...` or run `npx harness-evolver@latest` to configure."
|
|
35
|
+
|
|
36
|
+
The tools auto-load the key from the credentials file, but the env var takes precedence.
|
|
37
|
+
|
|
38
|
+
Python 3.10+ with `langsmith` and `openevals` packages must be installed:
|
|
16
39
|
|
|
17
40
|
```bash
|
|
18
41
|
pip install langsmith openevals 2>/dev/null || uv pip install langsmith openevals
|
package/tools/read_results.py
CHANGED
|
@@ -21,9 +21,33 @@ Requires: pip install langsmith
|
|
|
21
21
|
import argparse
|
|
22
22
|
import json
|
|
23
23
|
import os
|
|
24
|
+
import platform
|
|
24
25
|
import sys
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
def ensure_langsmith_api_key():
|
|
29
|
+
"""Load LANGSMITH_API_KEY from credentials file if not in env."""
|
|
30
|
+
if os.environ.get("LANGSMITH_API_KEY"):
|
|
31
|
+
return True
|
|
32
|
+
if platform.system() == "Darwin":
|
|
33
|
+
creds_path = os.path.expanduser("~/Library/Application Support/langsmith-cli/credentials")
|
|
34
|
+
else:
|
|
35
|
+
creds_path = os.path.expanduser("~/.config/langsmith-cli/credentials")
|
|
36
|
+
if os.path.exists(creds_path):
|
|
37
|
+
try:
|
|
38
|
+
with open(creds_path) as f:
|
|
39
|
+
for line in f:
|
|
40
|
+
line = line.strip()
|
|
41
|
+
if line.startswith("LANGSMITH_API_KEY="):
|
|
42
|
+
key = line.split("=", 1)[1].strip()
|
|
43
|
+
if key:
|
|
44
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
45
|
+
return True
|
|
46
|
+
except OSError:
|
|
47
|
+
pass
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
27
51
|
def read_experiment(client, experiment_name):
|
|
28
52
|
"""Read results from a single LangSmith experiment."""
|
|
29
53
|
try:
|
|
@@ -184,6 +208,7 @@ def main():
|
|
|
184
208
|
parser.add_argument("--output", default=None, help="Output JSON path")
|
|
185
209
|
parser.add_argument("--format", default="json", choices=["json", "markdown"], help="Output format")
|
|
186
210
|
args = parser.parse_args()
|
|
211
|
+
ensure_langsmith_api_key()
|
|
187
212
|
|
|
188
213
|
from langsmith import Client
|
|
189
214
|
client = Client()
|
package/tools/run_eval.py
CHANGED
|
@@ -17,11 +17,47 @@ Requires: pip install langsmith openevals
|
|
|
17
17
|
import argparse
|
|
18
18
|
import json
|
|
19
19
|
import os
|
|
20
|
+
import platform
|
|
20
21
|
import subprocess
|
|
21
22
|
import sys
|
|
22
23
|
import tempfile
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
def ensure_langsmith_api_key():
|
|
27
|
+
"""Load LANGSMITH_API_KEY from credentials file if not in env."""
|
|
28
|
+
if os.environ.get("LANGSMITH_API_KEY"):
|
|
29
|
+
return True
|
|
30
|
+
if platform.system() == "Darwin":
|
|
31
|
+
creds_path = os.path.expanduser("~/Library/Application Support/langsmith-cli/credentials")
|
|
32
|
+
else:
|
|
33
|
+
creds_path = os.path.expanduser("~/.config/langsmith-cli/credentials")
|
|
34
|
+
if os.path.exists(creds_path):
|
|
35
|
+
try:
|
|
36
|
+
with open(creds_path) as f:
|
|
37
|
+
for line in f:
|
|
38
|
+
line = line.strip()
|
|
39
|
+
if line.startswith("LANGSMITH_API_KEY="):
|
|
40
|
+
key = line.split("=", 1)[1].strip()
|
|
41
|
+
if key:
|
|
42
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
43
|
+
return True
|
|
44
|
+
except OSError:
|
|
45
|
+
pass
|
|
46
|
+
if os.path.exists(".env"):
|
|
47
|
+
try:
|
|
48
|
+
with open(".env") as f:
|
|
49
|
+
for line in f:
|
|
50
|
+
line = line.strip()
|
|
51
|
+
if line.startswith("LANGSMITH_API_KEY=") and not line.startswith("#"):
|
|
52
|
+
key = line.split("=", 1)[1].strip().strip("'\"")
|
|
53
|
+
if key:
|
|
54
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
55
|
+
return True
|
|
56
|
+
except OSError:
|
|
57
|
+
pass
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
25
61
|
def make_target(entry_point, cwd):
|
|
26
62
|
"""Create a target function that runs the agent from a specific directory."""
|
|
27
63
|
def target(inputs):
|
|
@@ -132,6 +168,7 @@ def main():
|
|
|
132
168
|
config = json.load(f)
|
|
133
169
|
|
|
134
170
|
os.environ["EVAL_TASK_TIMEOUT"] = str(args.timeout)
|
|
171
|
+
ensure_langsmith_api_key()
|
|
135
172
|
|
|
136
173
|
from langsmith import Client
|
|
137
174
|
client = Client()
|
package/tools/setup.py
CHANGED
|
@@ -25,12 +25,58 @@ Requires: pip install langsmith openevals
|
|
|
25
25
|
import argparse
|
|
26
26
|
import json
|
|
27
27
|
import os
|
|
28
|
+
import platform
|
|
28
29
|
import subprocess
|
|
29
30
|
import sys
|
|
30
31
|
import tempfile
|
|
31
32
|
from datetime import datetime, timezone
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
def ensure_langsmith_api_key():
|
|
36
|
+
"""Load LANGSMITH_API_KEY from credentials file if not in env.
|
|
37
|
+
|
|
38
|
+
The installer saves the key to the langsmith-cli credentials file,
|
|
39
|
+
but the SDK only reads the env var. This bridges the gap.
|
|
40
|
+
"""
|
|
41
|
+
if os.environ.get("LANGSMITH_API_KEY"):
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
# Platform-specific credentials path (matches langsmith-cli)
|
|
45
|
+
if platform.system() == "Darwin":
|
|
46
|
+
creds_path = os.path.expanduser("~/Library/Application Support/langsmith-cli/credentials")
|
|
47
|
+
else:
|
|
48
|
+
creds_path = os.path.expanduser("~/.config/langsmith-cli/credentials")
|
|
49
|
+
|
|
50
|
+
if os.path.exists(creds_path):
|
|
51
|
+
try:
|
|
52
|
+
with open(creds_path) as f:
|
|
53
|
+
for line in f:
|
|
54
|
+
line = line.strip()
|
|
55
|
+
if line.startswith("LANGSMITH_API_KEY="):
|
|
56
|
+
key = line.split("=", 1)[1].strip()
|
|
57
|
+
if key:
|
|
58
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
59
|
+
return True
|
|
60
|
+
except OSError:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# Also check .env in current directory
|
|
64
|
+
if os.path.exists(".env"):
|
|
65
|
+
try:
|
|
66
|
+
with open(".env") as f:
|
|
67
|
+
for line in f:
|
|
68
|
+
line = line.strip()
|
|
69
|
+
if line.startswith("LANGSMITH_API_KEY=") and not line.startswith("#"):
|
|
70
|
+
key = line.split("=", 1)[1].strip().strip("'\"")
|
|
71
|
+
if key:
|
|
72
|
+
os.environ["LANGSMITH_API_KEY"] = key
|
|
73
|
+
return True
|
|
74
|
+
except OSError:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
34
80
|
def check_dependencies():
|
|
35
81
|
"""Verify langsmith and openevals are installed."""
|
|
36
82
|
missing = []
|
|
@@ -294,6 +340,12 @@ def main():
|
|
|
294
340
|
print(f"Install with: pip install {' '.join(missing)}", file=sys.stderr)
|
|
295
341
|
sys.exit(1)
|
|
296
342
|
|
|
343
|
+
# Load API key from credentials file if not in env
|
|
344
|
+
if not ensure_langsmith_api_key():
|
|
345
|
+
print("LANGSMITH_API_KEY not found in environment, credentials file, or .env", file=sys.stderr)
|
|
346
|
+
print("Set it with: export LANGSMITH_API_KEY=lsv2_pt_...", file=sys.stderr)
|
|
347
|
+
sys.exit(1)
|
|
348
|
+
|
|
297
349
|
from langsmith import Client
|
|
298
350
|
client = Client()
|
|
299
351
|
|