harness-evolver 3.0.2 → 3.0.4

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 CHANGED
@@ -216,45 +216,84 @@ function installPythonDeps() {
216
216
  }
217
217
 
218
218
  async function configureLangSmith(rl) {
219
- console.log(`\n ${YELLOW}LangSmith Configuration${RESET} ${DIM}(required for v3)${RESET}\n`);
219
+ console.log(`\n ${BOLD}${GREEN}LangSmith Configuration${RESET} ${DIM}(required)${RESET}\n`);
220
220
 
221
- // Check if already configured
222
221
  const langsmithCredsDir = process.platform === "darwin"
223
222
  ? path.join(HOME, "Library", "Application Support", "langsmith-cli")
224
223
  : path.join(HOME, ".config", "langsmith-cli");
225
224
  const langsmithCredsFile = path.join(langsmithCredsDir, "credentials");
225
+ const hasLangsmithCli = checkCommand("langsmith-cli --version");
226
+
227
+ // --- Step 1: API Key ---
228
+ let hasKey = false;
226
229
 
227
- // Check env var
228
230
  if (process.env.LANGSMITH_API_KEY) {
229
231
  console.log(` ${GREEN}✓${RESET} LANGSMITH_API_KEY found in environment`);
230
- return;
232
+ hasKey = true;
233
+ } else if (fs.existsSync(langsmithCredsFile)) {
234
+ try {
235
+ const content = fs.readFileSync(langsmithCredsFile, "utf8");
236
+ if (content.includes("LANGSMITH_API_KEY=lsv2_")) {
237
+ console.log(` ${GREEN}✓${RESET} API key found in credentials file`);
238
+ hasKey = true;
239
+ }
240
+ } catch {}
231
241
  }
232
242
 
233
- // Check credentials file
234
- if (fs.existsSync(langsmithCredsFile)) {
235
- console.log(` ${GREEN}✓${RESET} LangSmith credentials found at ${DIM}${langsmithCredsFile}${RESET}`);
236
- return;
243
+ if (!hasKey) {
244
+ console.log(` ${BOLD}LangSmith API Key${RESET} — get yours at ${DIM}https://smith.langchain.com/settings${RESET}`);
245
+ console.log(` ${DIM}LangSmith is required. The evolver won't work without it.${RESET}\n`);
246
+
247
+ // Keep asking until they provide a key or explicitly skip
248
+ let attempts = 0;
249
+ while (!hasKey && attempts < 3) {
250
+ const apiKey = await ask(rl, ` ${YELLOW}Paste your LangSmith API key (lsv2_pt_...):${RESET} `);
251
+ const key = apiKey.trim();
252
+
253
+ if (key && key.startsWith("lsv2_")) {
254
+ try {
255
+ fs.mkdirSync(langsmithCredsDir, { recursive: true });
256
+ fs.writeFileSync(langsmithCredsFile, `LANGSMITH_API_KEY=${key}\n`);
257
+ console.log(` ${GREEN}✓${RESET} API key saved`);
258
+ hasKey = true;
259
+ } catch {
260
+ console.log(` ${RED}Failed to save.${RESET} Add to your shell: export LANGSMITH_API_KEY=${key}`);
261
+ hasKey = true; // they have the key, just couldn't save
262
+ }
263
+ } else if (key) {
264
+ console.log(` ${YELLOW}Invalid — LangSmith keys start with lsv2_${RESET}`);
265
+ attempts++;
266
+ } else {
267
+ // Empty input — skip
268
+ console.log(`\n ${RED}WARNING:${RESET} No API key configured.`);
269
+ console.log(` ${BOLD}/evolver:setup will not work${RESET} until you set LANGSMITH_API_KEY.`);
270
+ console.log(` Run: ${DIM}export LANGSMITH_API_KEY=lsv2_pt_your_key${RESET}\n`);
271
+ break;
272
+ }
273
+ }
237
274
  }
238
275
 
239
- // Ask for API key
240
- console.log(` ${BOLD}LangSmith API Key${RESET} — get yours at ${DIM}https://smith.langchain.com/settings${RESET}`);
241
- console.log(` ${DIM}LangSmith is required for v3 (datasets, experiments, evaluators).${RESET}\n`);
242
- const apiKey = await ask(rl, ` ${YELLOW}Paste your LangSmith API key:${RESET} `);
243
- const key = apiKey.trim();
276
+ // --- Step 2: langsmith-cli ---
277
+ if (hasLangsmithCli) {
278
+ console.log(` ${GREEN}✓${RESET} langsmith-cli installed`);
279
+ } else {
280
+ console.log(`\n ${BOLD}langsmith-cli${RESET} optional but useful for debugging traces`);
281
+ console.log(` ${DIM}Quick project listing, trace inspection, run stats from terminal.${RESET}`);
282
+ const lsCliAnswer = await ask(rl, `\n ${YELLOW}Install langsmith-cli? [Y/n]:${RESET} `);
283
+ if (lsCliAnswer.trim().toLowerCase() !== "n") {
284
+ console.log(`\n Installing langsmith-cli...`);
285
+ try {
286
+ execSync("uv tool install langsmith-cli 2>/dev/null || pip install langsmith-cli 2>/dev/null || pip3 install langsmith-cli", { stdio: "pipe", timeout: 60000 });
287
+ console.log(` ${GREEN}✓${RESET} langsmith-cli installed`);
244
288
 
245
- if (key && key.startsWith("lsv2_")) {
246
- try {
247
- fs.mkdirSync(langsmithCredsDir, { recursive: true });
248
- fs.writeFileSync(langsmithCredsFile, `LANGSMITH_API_KEY=${key}\n`);
249
- console.log(` ${GREEN}✓${RESET} API key saved to ${DIM}${langsmithCredsFile}${RESET}`);
250
- } catch {
251
- console.log(` ${RED}Failed to save.${RESET} Add to your shell: export LANGSMITH_API_KEY=${key}`);
289
+ // If we have a key, auto-authenticate
290
+ if (hasKey && fs.existsSync(langsmithCredsFile)) {
291
+ console.log(` ${GREEN}✓${RESET} langsmith-cli auto-authenticated (credentials file exists)`);
292
+ }
293
+ } catch {
294
+ console.log(` ${YELLOW}!${RESET} Could not install. Try manually: ${DIM}uv tool install langsmith-cli${RESET}`);
295
+ }
252
296
  }
253
- } else if (key) {
254
- console.log(` ${YELLOW}Doesn't look like a LangSmith key (should start with lsv2_).${RESET}`);
255
- console.log(` Add to your shell: ${BOLD}export LANGSMITH_API_KEY=your_key${RESET}`);
256
- } else {
257
- console.log(` ${YELLOW}Skipped.${RESET} You must set LANGSMITH_API_KEY before using /evolver:setup`);
258
297
  }
259
298
  }
260
299
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-evolver",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "LangSmith-native autonomous agent optimization for Claude Code",
5
5
  "author": "Raphael Valdetaro",
6
6
  "license": "MIT",
@@ -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
- - `LANGSMITH_API_KEY` must be set. If not: "Set your LangSmith API key: `export LANGSMITH_API_KEY=lsv2_pt_...`"
15
- - Python 3.10+ with `langsmith` and `openevals` packages. If missing:
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
@@ -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