evals 2.4.0 → 2.6.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/README.md +14 -20
- package/bin.js +43 -0
- package/cli.js +196 -17
- package/onboarding-prompt.md +260 -56
- package/package.json +11 -4
- package/start.ps1 +153 -27
- package/start.sh +129 -19
- package/vendor/LICENSE-coding-harness-tracing +202 -0
- package/vendor/MANIFEST +6 -0
- package/vendor/certifi-2026.7.22-py3-none-any.whl +0 -0
- package/vendor/coding_harness_tracing-0.1.0-py3-none-any.whl +0 -0
- package/vendor/harness-install.bat +351 -0
- package/vendor/harness-install.sh +439 -0
- package/vendor/harness-pin.lock.json +18 -0
- package/vendor/python_dotenv-1.2.1-py3-none-any.whl +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# evals
|
|
2
2
|
|
|
3
|
-
Go from zero to your first [Arize AX](https://arize.com/docs/ax) traces in one command. `evals` launches your coding agent, seeded with a guided onboarding prompt that walks you through signing up,
|
|
3
|
+
Go from zero to your first [Arize AX](https://arize.com/docs/ax) traces in one command. `evals` launches your coding agent, seeded with a guided onboarding prompt that walks you through signing up, adding tracing, and confirming traces land — for your app, or for the coding agent itself.
|
|
4
4
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
|
@@ -12,9 +12,11 @@ You'll see a picker of the coding agents installed on your machine (Claude Code,
|
|
|
12
12
|
|
|
13
13
|
No coding agent installed? The picker shows install links instead.
|
|
14
14
|
|
|
15
|
+
Needs **Node.js 20 or newer** (the Ink UI does). On anything older, `npx evals` says so and points you at the shell launcher below rather than failing obscurely.
|
|
16
|
+
|
|
15
17
|
### Without Node
|
|
16
18
|
|
|
17
|
-
If you don't have Node
|
|
19
|
+
If you don't have Node — or have one older than 20 — use the shell launchers (they detect an agent and fetch the prompt from this published package via jsDelivr):
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
22
|
# macOS / Linux
|
|
@@ -26,13 +28,15 @@ bash <(curl -fsSL https://cdn.jsdelivr.net/npm/evals/start.sh)
|
|
|
26
28
|
irm https://cdn.jsdelivr.net/npm/evals/start.ps1 | iex
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
When Node
|
|
31
|
+
When **Node 20+** is present, these hand off to `npx evals` for the richer UI. On an older Node they say so and carry on in the shell, which needs no Node at all.
|
|
30
32
|
|
|
31
33
|
## What it does
|
|
32
34
|
|
|
33
35
|
1. Detects an installed coding agent (never executes it — just a PATH lookup).
|
|
34
36
|
2. Launches the agent in your current directory, seeded with the bundled onboarding prompt ([`onboarding-prompt.md`](./onboarding-prompt.md)).
|
|
35
|
-
3. The agent walks you through: create/sign in to Arize AX →
|
|
37
|
+
3. The agent walks you through: create/sign in to Arize AX → pick what to trace → instrument it → verify your first traces.
|
|
38
|
+
|
|
39
|
+
You can trace an existing app (the one in the current folder, or another one you point it at by path), a starter app the agent creates, or **the coding agent itself** — so every session you run, in any project, shows up in Arize AX. Agent tracing is machine-wide and captures prompts and tool output by default, so the prompt asks for that explicitly and offers per-category opt-outs.
|
|
36
40
|
|
|
37
41
|
The agent runs with its **normal permission model** — `evals` never passes skip-permissions, so you approve each step, and the prompt itself gates real changes on your confirmation.
|
|
38
42
|
|
|
@@ -43,26 +47,16 @@ The agent runs with its **normal permission model** — `evals` never passes ski
|
|
|
43
47
|
| `ARIZE_AGENT=<id>` | `start.sh` / `start.ps1` | Skip the picker and use this agent (`claude`, `codex`, `cursor-agent`, `copilot`, `gemini`). |
|
|
44
48
|
| `ARIZE_SKIP_NPX=1` | `start.sh` / `start.ps1` | Force the shell path even when Node/npx is available. |
|
|
45
49
|
| `ARIZE_PROMPT_URL=<url>` | `start.sh` / `start.ps1` | Fetch the onboarding prompt from a custom URL (supports `file://`). |
|
|
50
|
+
| `ARIZE_ONBOARDING_DIR=<dir>` | all three | Stage the prompt somewhere other than `~/.arize/onboarding`. |
|
|
46
51
|
|
|
47
52
|
The shell launchers also accept `--agent <id>`.
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npm install
|
|
53
|
-
npm test # unit tests for the pure helpers (node --test)
|
|
54
|
-
node cli.js # run the interactive CLI locally
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
`npm test` covers the headless logic (URL tagging, agent detection, the agent catalog, and the picker). The interactive Ink flow and the live agent launch need a real terminal — verify those by running `node cli.js` in a terminal and picking an agent.
|
|
58
|
-
|
|
59
|
-
### Layout
|
|
60
|
-
|
|
61
|
-
- `cli.js` — the interactive `npx evals` entry point (Ink UI). Exports its pure helpers for testing; only launches the UI when run directly.
|
|
62
|
-
- `onboarding-prompt.md` — the bundled onboarding prompt (a copy of the Arize docs landing-page prompt).
|
|
63
|
-
- `start.sh` / `start.ps1` — the no-npm launchers, served to `curl`/`irm` via jsDelivr.
|
|
64
|
-
- `test/` — unit tests.
|
|
54
|
+
The prompt is staged in `~/.arize/onboarding` and handed to your agent by path, since it's too large to pass as a command-line argument. That directory is cleared at the start of each launch and holds nothing but the prompt and the offline tracing bundle. If your home directory isn't writable, the launcher says so and falls back to a temp directory — instrumenting an app still works, but tracing the coding agent itself needs a writable home.
|
|
65
55
|
|
|
66
56
|
## About Arize
|
|
67
57
|
|
|
68
58
|
[Arize AX](https://arize.com/docs/ax) is the AI engineering platform for tracing, evaluating, and observing LLM and agent applications. Learn more at [arize.com](https://arize.com).
|
|
59
|
+
|
|
60
|
+
## Contributing
|
|
61
|
+
|
|
62
|
+
Development setup and the release process live in [CONTRIBUTING.md](https://github.com/Arize-ai/npm-evals/blob/main/CONTRIBUTING.md).
|
package/bin.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//
|
|
3
|
+
// Node version gate for `npx evals`.
|
|
4
|
+
//
|
|
5
|
+
// This is a separate file from cli.js on purpose. ESM `import` statements are
|
|
6
|
+
// hoisted and evaluated before any of the importing module's own code runs, so a
|
|
7
|
+
// check at the top of cli.js would execute *after* Ink's dependency graph loads —
|
|
8
|
+
// too late. On Node 18 that graph dies while parsing string-width with
|
|
9
|
+
// "SyntaxError: Invalid regular expression flags" (the regex `v` flag is Node 20+),
|
|
10
|
+
// which tells the user nothing about Node versions. So: check here, where nothing
|
|
11
|
+
// but Node builtins have loaded, then reach cli.js through a dynamic import.
|
|
12
|
+
//
|
|
13
|
+
// Keep this file dependency-free and conservative in syntax — it has to parse and
|
|
14
|
+
// run on the very versions it exists to reject.
|
|
15
|
+
//
|
|
16
|
+
// MIN_MAJOR tracks ink's own `engines.node`, and package.json's `engines` field
|
|
17
|
+
// must say the same thing; test/cli.test.js asserts they agree.
|
|
18
|
+
|
|
19
|
+
const MIN_MAJOR = 20;
|
|
20
|
+
|
|
21
|
+
const major = Number.parseInt(process.versions.node.split('.')[0], 10);
|
|
22
|
+
|
|
23
|
+
if (!Number.isInteger(major) || major < MIN_MAJOR) {
|
|
24
|
+
const shell = process.platform === 'win32'
|
|
25
|
+
? 'irm https://cdn.jsdelivr.net/npm/evals/start.ps1 | iex'
|
|
26
|
+
: 'bash <(curl -fsSL https://cdn.jsdelivr.net/npm/evals/start.sh)';
|
|
27
|
+
|
|
28
|
+
console.error(`evals needs Node.js ${MIN_MAJOR} or newer — this is ${process.version}.`);
|
|
29
|
+
console.error('');
|
|
30
|
+
console.error('Either upgrade Node (https://nodejs.org), or use the launcher that');
|
|
31
|
+
console.error('needs no Node at all — it runs the same onboarding flow:');
|
|
32
|
+
console.error('');
|
|
33
|
+
console.error(` ${shell}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// cli.js only auto-runs when it is the entry point, so call main() explicitly.
|
|
38
|
+
import('./cli.js')
|
|
39
|
+
.then((cli) => cli.main())
|
|
40
|
+
.catch((err) => {
|
|
41
|
+
console.error(`Could not start evals: ${err && err.message ? err.message : err}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
package/cli.js
CHANGED
|
@@ -4,28 +4,198 @@ import React, { useState, useEffect } from 'react';
|
|
|
4
4
|
import { render, Box, Text, useInput, useApp, Static } from 'ink';
|
|
5
5
|
import Gradient from 'ink-gradient';
|
|
6
6
|
import { exec, spawn } from 'child_process';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
mkdtempSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
realpathSync,
|
|
14
|
+
readdirSync,
|
|
15
|
+
copyFileSync,
|
|
16
|
+
chmodSync,
|
|
17
|
+
renameSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
rmdirSync,
|
|
20
|
+
unlinkSync,
|
|
21
|
+
} from 'fs';
|
|
22
|
+
import { join, isAbsolute, resolve } from 'path';
|
|
23
|
+
import { tmpdir, homedir } from 'os';
|
|
10
24
|
import { fileURLToPath } from 'url';
|
|
11
25
|
|
|
12
26
|
const e = React.createElement;
|
|
13
27
|
|
|
14
|
-
// The onboarding prompt is bundled with this package (onboarding-prompt.md
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// reliably as a command-line
|
|
18
|
-
//
|
|
28
|
+
// The onboarding prompt is bundled with this package (onboarding-prompt.md).
|
|
29
|
+
// This repo is its only home — there is no docs original to sync against, so
|
|
30
|
+
// edit it here. We read it, write it to a temp file, and tell the agent to read
|
|
31
|
+
// that file — a ~35 KB prompt is too large to pass reliably as a command-line
|
|
32
|
+
// argument.
|
|
19
33
|
const BUNDLED_PROMPT_PATH = fileURLToPath(new URL('./onboarding-prompt.md', import.meta.url));
|
|
20
34
|
|
|
21
|
-
//
|
|
22
|
-
//
|
|
35
|
+
// Wheels for the coding-agent tracing harness, built by scripts/build-harness-wheel.mjs.
|
|
36
|
+
// Staging these next to the prompt lets Step 4A install with no network and no
|
|
37
|
+
// remote code execution — which is what keeps it working under permission
|
|
38
|
+
// classifiers that block piping a downloaded script into a shell.
|
|
39
|
+
const VENDOR_DIR = fileURLToPath(new URL('./vendor', import.meta.url));
|
|
40
|
+
const OFFLINE_DIR_NAME = 'arize-offline';
|
|
41
|
+
|
|
42
|
+
// Files the offline install needs. Absent or incomplete vendor/ means we stage
|
|
43
|
+
// nothing — the prompt keys off the directory existing, so a half-populated one
|
|
44
|
+
// would send the agent down the offline path with no wheel to install.
|
|
45
|
+
const OFFLINE_FILES = ['harness-install.sh', 'harness-install.bat', 'LICENSE-coding-harness-tracing'];
|
|
46
|
+
|
|
47
|
+
const PROMPT_FILE_NAME = 'onboarding-prompt.md';
|
|
48
|
+
|
|
49
|
+
// Step 4A writes the harness credentials here and deletes them itself. We name it
|
|
50
|
+
// only so a stray one — left by a run that died between writing and deleting —
|
|
51
|
+
// gets cleared at the next launch instead of sitting around holding a live key.
|
|
52
|
+
const ENV_FILE_NAME = 'harness.env';
|
|
53
|
+
|
|
54
|
+
// MANIFEST only lands in the shell launchers' bundles, never in ours. Naming it
|
|
55
|
+
// anyway keeps one allowlist correct for every implementation, so a directory
|
|
56
|
+
// staged by start.sh can be cleared by `npx evals` and vice versa.
|
|
57
|
+
const BUNDLE_EXTRA_FILES = ['MANIFEST'];
|
|
58
|
+
|
|
59
|
+
// Where the prompt and the offline bundle get staged: `~/.arize/onboarding`.
|
|
60
|
+
// Deliberately stable rather than a fresh temp directory. The agent is asked to
|
|
61
|
+
// read a file outside its workspace, and that is an approval a human can grant
|
|
62
|
+
// with confidence for `~/.arize/onboarding/onboarding-prompt.md`, where a
|
|
63
|
+
// `/var/folders/_s/3_t5nrxs…/T/arize-onboarding-c5uxb8/` path just looks alarming.
|
|
64
|
+
//
|
|
65
|
+
// This sits beside Step 4A's own `~/.arize/harness` install, which the launcher
|
|
66
|
+
// must never touch — clearing our staging directory cannot uninstall a working
|
|
67
|
+
// harness, and that boundary is why we only ever name files under `onboarding/`.
|
|
68
|
+
export function resolveOnboardingDir({
|
|
69
|
+
home = homedir(),
|
|
70
|
+
override = process.env.ARIZE_ONBOARDING_DIR,
|
|
71
|
+
} = {}) {
|
|
72
|
+
if (override) return resolve(override);
|
|
73
|
+
return join(home, '.arize', 'onboarding');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Clear a staged bundle directory by naming every entry we could have put in it,
|
|
77
|
+
// then rmdir. Bails out — deleting nothing further — the moment it meets an entry
|
|
78
|
+
// it doesn't recognise. Wheel names carry a version we can't know ahead of time,
|
|
79
|
+
// so `.whl` is matched by extension, non-recursively, inside this one directory.
|
|
80
|
+
function clearBundleDir(dir) {
|
|
81
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
82
|
+
const known =
|
|
83
|
+
entry.name.endsWith('.whl') ||
|
|
84
|
+
OFFLINE_FILES.includes(entry.name) ||
|
|
85
|
+
BUNDLE_EXTRA_FILES.includes(entry.name);
|
|
86
|
+
if (entry.isDirectory() || !known) return false;
|
|
87
|
+
unlinkSync(join(dir, entry.name));
|
|
88
|
+
}
|
|
89
|
+
rmdirSync(dir);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Empty the staging directory by deleting the exact files we create — never with
|
|
94
|
+
// a recursive delete. Two things fall out of that: a bad path can at worst try to
|
|
95
|
+
// unlink a handful of names that won't exist, and `rmdir` refuses a non-empty
|
|
96
|
+
// directory, so anything unexpected in there stops us instead of being destroyed.
|
|
97
|
+
// Unlinking a symlink removes the link and never the target.
|
|
98
|
+
//
|
|
99
|
+
// Returns true when the directory is gone or empty and safe to stage into.
|
|
100
|
+
export function clearOnboardingDir(dir) {
|
|
101
|
+
if (!isAbsolute(dir)) return false;
|
|
102
|
+
if (!existsSync(dir)) return true;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
106
|
+
const path = join(dir, entry.name);
|
|
107
|
+
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
// arize-offline/ is ours; .staging-* is the leftover of a crashed run.
|
|
110
|
+
const ours = entry.name === OFFLINE_DIR_NAME || entry.name.startsWith('.staging-');
|
|
111
|
+
if (!ours || !clearBundleDir(path)) return false;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (entry.name !== PROMPT_FILE_NAME && entry.name !== ENV_FILE_NAME) return false;
|
|
116
|
+
unlinkSync(path);
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Clear and create the staging directory, degrading to a temp directory when the
|
|
125
|
+
// home path can't be used: a read-only home, a directory owned by another user
|
|
126
|
+
// after a past `sudo npx evals`, a full disk, an offline redirected profile on
|
|
127
|
+
// Windows, or an unrecognised file we refuse to delete. We attempt the work and
|
|
128
|
+
// degrade rather than probing for writability first — a probe lies on NFS and
|
|
129
|
+
// ACL filesystems, races, and can't see ENOSPC at all.
|
|
130
|
+
//
|
|
131
|
+
// Mode 0700 because Step 4A writes credentials into this directory.
|
|
132
|
+
export function prepareOnboardingDir(dir = resolveOnboardingDir()) {
|
|
133
|
+
if (clearOnboardingDir(dir)) {
|
|
134
|
+
try {
|
|
135
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
136
|
+
// mkdir's mode is umask-filtered, so set it outright. Windows has no POSIX
|
|
137
|
+
// mode; there the file inherits the profile's own ACL, as it does today.
|
|
138
|
+
if (process.platform !== 'win32') chmodSync(dir, 0o700);
|
|
139
|
+
return { dir, fellBack: false };
|
|
140
|
+
} catch {
|
|
141
|
+
// Fall through to the temp directory.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { dir: mkdtempSync(join(tmpdir(), 'arize-onboarding-')), fellBack: true };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Copy the bundled wheels next to the prompt. Returns the staged directory, or
|
|
148
|
+
// null when this package has no usable vendor/ (a git checkout that hasn't run
|
|
149
|
+
// the build). The shell launchers stage the same bundle themselves, fetching it
|
|
150
|
+
// from jsDelivr via vendor/MANIFEST.
|
|
151
|
+
//
|
|
152
|
+
// Never throws, and never leaves a partial directory behind. Both matter: the
|
|
153
|
+
// prompt decides which install path to take purely on whether this directory
|
|
154
|
+
// exists, so a half-copied one would send the agent offline with no wheel to
|
|
155
|
+
// install — and this is an optional enhancement, so a broken vendor/ must not
|
|
156
|
+
// take the launcher down with it. Either outcome degrades to the network path.
|
|
157
|
+
export function stageOfflineHarness(dir, vendorDir = VENDOR_DIR) {
|
|
158
|
+
if (!existsSync(vendorDir)) return null;
|
|
159
|
+
|
|
160
|
+
let staging;
|
|
161
|
+
try {
|
|
162
|
+
const wheels = readdirSync(vendorDir).filter((f) => f.endsWith('.whl'));
|
|
163
|
+
const hasHarnessWheel = wheels.some((f) => f.startsWith('coding_harness_tracing-'));
|
|
164
|
+
if (!hasHarnessWheel || !OFFLINE_FILES.every((f) => existsSync(join(vendorDir, f)))) return null;
|
|
165
|
+
|
|
166
|
+
// Build under a temp name and rename into place, so the directory the prompt
|
|
167
|
+
// looks for only ever appears complete. Same parent, so the rename is atomic.
|
|
168
|
+
staging = mkdtempSync(join(dir, '.staging-'));
|
|
169
|
+
for (const name of [...wheels, ...OFFLINE_FILES]) {
|
|
170
|
+
copyFileSync(join(vendorDir, name), join(staging, name));
|
|
171
|
+
}
|
|
172
|
+
chmodSync(join(staging, 'harness-install.sh'), 0o755);
|
|
173
|
+
|
|
174
|
+
const target = join(dir, OFFLINE_DIR_NAME);
|
|
175
|
+
renameSync(staging, target);
|
|
176
|
+
return target;
|
|
177
|
+
} catch {
|
|
178
|
+
if (staging) rmSync(staging, { recursive: true, force: true });
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Stage the bundled prompt and return the seed instruction that points the agent
|
|
184
|
+
// at it, plus whether we had to fall back off the home directory.
|
|
23
185
|
function prepareSeedPrompt() {
|
|
24
186
|
const promptText = readFileSync(BUNDLED_PROMPT_PATH, 'utf8');
|
|
25
|
-
const dir =
|
|
26
|
-
const promptFile = join(dir,
|
|
187
|
+
const { dir, fellBack } = prepareOnboardingDir();
|
|
188
|
+
const promptFile = join(dir, PROMPT_FILE_NAME);
|
|
27
189
|
writeFileSync(promptFile, promptText, 'utf8');
|
|
28
|
-
|
|
190
|
+
stageOfflineHarness(dir);
|
|
191
|
+
// Single quotes, not double: a home path is likelier to contain a space than a
|
|
192
|
+
// temp path was, but Windows spawns through the shell and embedded double
|
|
193
|
+
// quotes don't survive that reliably. cmd.exe leaves single quotes alone.
|
|
194
|
+
// No "in this project": the launcher may well be run from a home directory, and
|
|
195
|
+
// Step 4 is what scopes the work — an app here, an app at another path, a starter
|
|
196
|
+
// app, or the coding agent itself. Keep this wording in step with start.{sh,ps1}.
|
|
197
|
+
const seed = `Read the file '${promptFile}' and follow it to set up Arize AX tracing, walking me through each step and asking me questions as needed.`;
|
|
198
|
+
return { seed, dir, fellBack };
|
|
29
199
|
}
|
|
30
200
|
|
|
31
201
|
// Coding agents we can launch interactively, seeded with the prompt.
|
|
@@ -291,17 +461,26 @@ function App({ onDone }) {
|
|
|
291
461
|
function launchAgent(agent) {
|
|
292
462
|
const isWin = process.platform === 'win32';
|
|
293
463
|
|
|
294
|
-
let
|
|
464
|
+
let prepared;
|
|
295
465
|
try {
|
|
296
|
-
|
|
466
|
+
prepared = prepareSeedPrompt();
|
|
297
467
|
} catch (err) {
|
|
298
|
-
console.error(`Could not
|
|
468
|
+
console.error(`Could not prepare the onboarding prompt: ${err.message}`);
|
|
299
469
|
process.exit(1);
|
|
300
470
|
}
|
|
301
471
|
|
|
472
|
+
// Say so when we couldn't use the home directory. Otherwise the seed path
|
|
473
|
+
// silently reverts to the /var/folders/… form this change exists to avoid, and
|
|
474
|
+
// Step 4A is going to fail later anyway — it writes the harness and its
|
|
475
|
+
// credentials under ~/.arize regardless of where the prompt was staged.
|
|
476
|
+
if (prepared.fellBack) {
|
|
477
|
+
console.log(`\nCouldn't use ${resolveOnboardingDir()} — staged in ${prepared.dir} instead.`);
|
|
478
|
+
console.log('Instrumenting an app still works; tracing this coding agent (Step 4A) needs a writable home directory.');
|
|
479
|
+
}
|
|
480
|
+
|
|
302
481
|
console.log(`\nLaunching ${agent.label}…\n`);
|
|
303
482
|
|
|
304
|
-
const child = spawn(agent.bin, agent.args(seed), {
|
|
483
|
+
const child = spawn(agent.bin, agent.args(prepared.seed), {
|
|
305
484
|
stdio: 'inherit',
|
|
306
485
|
shell: isWin // .cmd/.bat shims on Windows need the shell to resolve
|
|
307
486
|
});
|