evals 2.2.7 → 2.3.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 +34 -32
- package/cli.js +204 -71
- package/onboarding-prompt.md +355 -0
- package/package.json +15 -7
- package/start.ps1 +128 -0
- package/start.sh +186 -0
- package/spawn-interactive.js +0 -51
package/README.md
CHANGED
|
@@ -1,53 +1,55 @@
|
|
|
1
1
|
# evals
|
|
2
2
|
|
|
3
|
-
|
|
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, instrumenting your app, and confirming traces land.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Quick start
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npx evals
|
|
9
|
+
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
You'll see a picker of the coding agents installed on your machine (Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI). Pick one and it launches in your current directory, seeded with the onboarding prompt — so run `npx evals` from the project you want to instrument.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- **Interactive Prompt Playground**: Offers a flexible environment for prompt and model iteration, allowing users to compare prompts, visualize outputs, and debug failures within their workflow
|
|
15
|
-
- **Streamlined Evaluations and Annotations**: Facilitates efficient assessment and documentation of model performance
|
|
13
|
+
No coding agent installed? The picker shows install links instead.
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
### Without Node
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
If you don't have Node, use the shell launchers (they detect an agent and fetch the prompt from this published package via jsDelivr):
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
```bash
|
|
20
|
+
# macOS / Linux
|
|
21
|
+
bash <(curl -fsSL https://cdn.jsdelivr.net/npm/evals/start.sh)
|
|
22
|
+
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- **Alyx**: An AI engineering agent embedded within the platform to assist with various tasks
|
|
24
|
+
```powershell
|
|
25
|
+
# Windows (PowerShell)
|
|
26
|
+
irm https://cdn.jsdelivr.net/npm/evals/start.ps1 | iex
|
|
27
|
+
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
When Node **is** present, these hand off to `npx evals` for the richer UI.
|
|
30
30
|
|
|
31
|
+
## What it does
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
1. Detects an installed coding agent (never executes it — just a PATH lookup).
|
|
34
|
+
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 → detect your stack → instrument it → verify your first traces.
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
npm install evals
|
|
36
|
-
```
|
|
37
|
+
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.
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
## Options
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
| Variable | Applies to | Effect |
|
|
42
|
+
|----------|-----------|--------|
|
|
43
|
+
| `ARIZE_AGENT=<id>` | `start.sh` / `start.ps1` | Skip the picker and use this agent (`claude`, `codex`, `cursor-agent`, `copilot`, `gemini`). |
|
|
44
|
+
| `ARIZE_SKIP_NPX=1` | `start.sh` / `start.ps1` | Force the shell path even when Node/npx is available. |
|
|
45
|
+
| `ARIZE_PROMPT_URL=<url>` | `start.sh` / `start.ps1` | Fetch the onboarding prompt from a custom URL (supports `file://`). |
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
The shell launchers also accept `--agent <id>`.
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
npm start
|
|
46
|
-
```
|
|
49
|
+
## About Arize
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
[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).
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
npx evals
|
|
52
|
-
```
|
|
53
|
+
## Contributing
|
|
53
54
|
|
|
55
|
+
Development setup and the release process live in [CONTRIBUTING.md](https://github.com/Arize-ai/npm-evals/blob/main/CONTRIBUTING.md).
|
package/cli.js
CHANGED
|
@@ -3,12 +3,58 @@
|
|
|
3
3
|
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
|
-
import { exec } from 'child_process';
|
|
6
|
+
import { exec, spawn } from 'child_process';
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdtempSync, realpathSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { tmpdir } from 'os';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
7
11
|
|
|
8
12
|
const e = React.createElement;
|
|
9
13
|
|
|
10
|
-
//
|
|
11
|
-
|
|
14
|
+
// The onboarding prompt is bundled with this package (onboarding-prompt.md, a
|
|
15
|
+
// copy of the docs landing-page prompt). We read it, write it to a temp file,
|
|
16
|
+
// and tell the agent to read that file — a ~27 KB prompt is too large to pass
|
|
17
|
+
// reliably as a command-line argument.
|
|
18
|
+
// TODO: sync this copy with the docs source (arize.com/docs) later.
|
|
19
|
+
const BUNDLED_PROMPT_PATH = fileURLToPath(new URL('./onboarding-prompt.md', import.meta.url));
|
|
20
|
+
|
|
21
|
+
// Write the bundled prompt to a temp file and return a short seed instruction
|
|
22
|
+
// that points the agent at it.
|
|
23
|
+
function prepareSeedPrompt() {
|
|
24
|
+
const promptText = readFileSync(BUNDLED_PROMPT_PATH, 'utf8');
|
|
25
|
+
const dir = mkdtempSync(join(tmpdir(), 'arize-onboarding-'));
|
|
26
|
+
const promptFile = join(dir, 'onboarding-prompt.md');
|
|
27
|
+
writeFileSync(promptFile, promptText, 'utf8');
|
|
28
|
+
return `Read the file ${promptFile} and follow it to set up Arize AX tracing in this project, walking me through each step and asking me questions as needed.`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Coding agents we can launch interactively, seeded with the prompt.
|
|
32
|
+
// `args(seed)` returns the argv that starts the agent's REPL pre-loaded with `seed`.
|
|
33
|
+
export const AGENTS = [
|
|
34
|
+
{ id: 'claude', label: 'Claude Code', bin: 'claude', args: (s) => [s], installUrl: 'https://docs.claude.com/en/docs/claude-code' },
|
|
35
|
+
{ id: 'codex', label: 'OpenAI Codex', bin: 'codex', args: (s) => [s], installUrl: 'https://developers.openai.com/codex/cli' },
|
|
36
|
+
{ id: 'cursor-agent', label: 'Cursor', bin: 'cursor-agent', args: (s) => [s], installUrl: 'https://docs.cursor.com/en/cli/overview' },
|
|
37
|
+
{ id: 'copilot', label: 'GitHub Copilot', bin: 'copilot', args: (s) => ['-i', s], installUrl: 'https://github.com/features/copilot/cli' },
|
|
38
|
+
{ id: 'gemini', label: 'Gemini CLI', bin: 'gemini', args: (s) => ['-i', s], installUrl: 'https://github.com/google-gemini/gemini-cli' }
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// PATH scan — detects an agent without executing it (running it could hang).
|
|
42
|
+
export function isInstalled(bin) {
|
|
43
|
+
const path = process.env.PATH || '';
|
|
44
|
+
const dirs = path.split(process.platform === 'win32' ? ';' : ':');
|
|
45
|
+
const exts = process.platform === 'win32'
|
|
46
|
+
? (process.env.PATHEXT || '.EXE;.CMD;.BAT').split(';')
|
|
47
|
+
: [''];
|
|
48
|
+
for (const dir of dirs) {
|
|
49
|
+
if (!dir) continue;
|
|
50
|
+
for (const ext of exts) {
|
|
51
|
+
if (existsSync(join(dir, bin + ext)) || existsSync(join(dir, bin + ext.toLowerCase()))) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
12
58
|
|
|
13
59
|
// "ARIZE EVALS" - EXACTLY matched width (both 44 chars)
|
|
14
60
|
const largeLogo = `
|
|
@@ -38,30 +84,23 @@ const mediumLogo = `
|
|
|
38
84
|
const smallLogo = `ARIZE
|
|
39
85
|
EVALS`;
|
|
40
86
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
name: 'Arize Phoenix',
|
|
54
|
-
subtext: 'OSS Agent Evals & Traces, Fully Local',
|
|
55
|
-
url: 'https://arize.com/docs/phoenix?utm_source=npmevals'
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: 'Book an Eval Assessment',
|
|
59
|
-
subtext: 'Improve Your Agent Today',
|
|
60
|
-
url: 'https://arize.com/get-an-eval-assessment?utm_source=npmevals'
|
|
87
|
+
// Tag every URL the app opens with utm_source=npmevals (idempotent). Applied
|
|
88
|
+
// centrally in openUrlInBrowser so all opened links (the agent install links)
|
|
89
|
+
// carry it. Does not touch URLs inside the onboarding prompt.
|
|
90
|
+
export function withUtm(url) {
|
|
91
|
+
try {
|
|
92
|
+
const u = new URL(url);
|
|
93
|
+
if (!u.searchParams.has('utm_source')) {
|
|
94
|
+
u.searchParams.set('utm_source', 'npmevals');
|
|
95
|
+
}
|
|
96
|
+
return u.toString();
|
|
97
|
+
} catch {
|
|
98
|
+
return url;
|
|
61
99
|
}
|
|
62
|
-
|
|
100
|
+
}
|
|
63
101
|
|
|
64
|
-
function openUrlInBrowser(
|
|
102
|
+
function openUrlInBrowser(rawUrl) {
|
|
103
|
+
const url = withUtm(rawUrl);
|
|
65
104
|
const command = process.platform === 'win32'
|
|
66
105
|
? `start ${url}`
|
|
67
106
|
: process.platform === 'darwin'
|
|
@@ -133,13 +172,41 @@ function MenuItem({ name, subtext, isSelected, shimmerPos }) {
|
|
|
133
172
|
);
|
|
134
173
|
}
|
|
135
174
|
|
|
136
|
-
//
|
|
137
|
-
|
|
175
|
+
// Build the coding-agent picker items — the entry screen for `npx evals`.
|
|
176
|
+
// Detected agents become launch items; if none are found we offer install
|
|
177
|
+
// links instead so the screen is never a dead end.
|
|
178
|
+
export function buildAgentItems() {
|
|
179
|
+
const detected = AGENTS.filter(a => isInstalled(a.bin));
|
|
180
|
+
if (detected.length > 0) {
|
|
181
|
+
return {
|
|
182
|
+
items: detected.map(a => ({
|
|
183
|
+
name: a.label,
|
|
184
|
+
subtext: 'Launch and walk me through setup',
|
|
185
|
+
launch: a
|
|
186
|
+
})),
|
|
187
|
+
none: false
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
items: AGENTS.map(a => ({
|
|
192
|
+
name: `Install ${a.label}`,
|
|
193
|
+
subtext: 'No supported agent detected — open install docs',
|
|
194
|
+
url: a.installUrl
|
|
195
|
+
})),
|
|
196
|
+
none: true
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Main App component — goes straight into the onboarding flow: pick a coding
|
|
201
|
+
// agent and launch it seeded with the prompt (no top-level menu).
|
|
202
|
+
function App({ onDone }) {
|
|
203
|
+
const [agentState] = useState(() => buildAgentItems());
|
|
138
204
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
139
205
|
const [shimmerPos, setShimmerPos] = useState(-7); // Start off-screen
|
|
140
206
|
const { exit } = useApp();
|
|
141
207
|
|
|
142
|
-
const
|
|
208
|
+
const items = agentState.items;
|
|
209
|
+
const selectedName = items[selectedIndex].name;
|
|
143
210
|
|
|
144
211
|
// Character-by-character shimmer animation
|
|
145
212
|
useEffect(() => {
|
|
@@ -169,30 +236,41 @@ function App() {
|
|
|
169
236
|
}
|
|
170
237
|
|
|
171
238
|
if (key.upArrow || input === 'k') {
|
|
172
|
-
setSelectedIndex(prev => (prev > 0 ? prev - 1 :
|
|
239
|
+
setSelectedIndex(prev => (prev > 0 ? prev - 1 : items.length - 1));
|
|
173
240
|
}
|
|
174
241
|
|
|
175
242
|
if (key.downArrow || input === 'j') {
|
|
176
|
-
setSelectedIndex(prev => (prev <
|
|
243
|
+
setSelectedIndex(prev => (prev < items.length - 1 ? prev + 1 : 0));
|
|
177
244
|
}
|
|
178
245
|
|
|
179
246
|
if (key.return) {
|
|
180
|
-
const selected =
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
247
|
+
const selected = items[selectedIndex];
|
|
248
|
+
|
|
249
|
+
if (selected.launch) {
|
|
250
|
+
onDone({ type: 'launch', agent: selected.launch });
|
|
251
|
+
exit();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (selected.url) {
|
|
256
|
+
onDone({ type: 'url', url: selected.url });
|
|
257
|
+
exit();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
186
260
|
}
|
|
187
261
|
});
|
|
188
262
|
|
|
263
|
+
const heading = agentState.none
|
|
264
|
+
? 'No coding agent found on your PATH. Install one, then re-run:'
|
|
265
|
+
: 'Choose your coding agent to instrument your app:';
|
|
266
|
+
|
|
189
267
|
return e(Box, { flexDirection: 'column', padding: 1 },
|
|
190
|
-
e(Static, { items: ['header'] }, () => e(Header)),
|
|
268
|
+
e(Static, { items: ['header'] }, (item) => e(Header, { key: item })),
|
|
191
269
|
e(Box, { marginBottom: 1 },
|
|
192
|
-
e(Text, { bold: true, color: 'white' },
|
|
270
|
+
e(Text, { bold: true, color: 'white' }, heading)
|
|
193
271
|
),
|
|
194
272
|
e(Box, { flexDirection: 'column', paddingX: 1 },
|
|
195
|
-
...
|
|
273
|
+
...items.map((option, index) =>
|
|
196
274
|
e(MenuItem, {
|
|
197
275
|
key: index.toString(),
|
|
198
276
|
name: option.name,
|
|
@@ -203,44 +281,99 @@ function App() {
|
|
|
203
281
|
)
|
|
204
282
|
),
|
|
205
283
|
e(Box, { marginTop: 1 },
|
|
206
|
-
e(Text, { dimColor: true }, 'Enter to select · ↑↓ to navigate · Esc to
|
|
284
|
+
e(Text, { dimColor: true }, 'Enter to select · ↑↓ to navigate · Esc to quit')
|
|
207
285
|
)
|
|
208
286
|
);
|
|
209
287
|
}
|
|
210
288
|
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
289
|
+
// Launch a coding agent on the real terminal, seeded with the onboarding prompt.
|
|
290
|
+
// Ink has fully unmounted by this point, so the child inherits a clean TTY.
|
|
291
|
+
function launchAgent(agent) {
|
|
292
|
+
const isWin = process.platform === 'win32';
|
|
293
|
+
|
|
294
|
+
let seed;
|
|
295
|
+
try {
|
|
296
|
+
seed = prepareSeedPrompt();
|
|
297
|
+
} catch (err) {
|
|
298
|
+
console.error(`Could not read the onboarding prompt: ${err.message}`);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
218
301
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
302
|
+
console.log(`\nLaunching ${agent.label}…\n`);
|
|
303
|
+
|
|
304
|
+
const child = spawn(agent.bin, agent.args(seed), {
|
|
305
|
+
stdio: 'inherit',
|
|
306
|
+
shell: isWin // .cmd/.bat shims on Windows need the shell to resolve
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
child.on('error', (err) => {
|
|
310
|
+
console.error(`Could not launch ${agent.bin}: ${err.message}`);
|
|
311
|
+
console.error(`Make sure "${agent.bin}" is on your PATH, then try again.`);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
child.on('close', (code) => {
|
|
316
|
+
process.exit(code || 0);
|
|
317
|
+
});
|
|
235
318
|
}
|
|
236
319
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (process.
|
|
243
|
-
console.error('
|
|
320
|
+
// Render the interactive app and act on the user's choice. Exported so it can
|
|
321
|
+
// be driven explicitly; only auto-runs when this file is the entry point (see
|
|
322
|
+
// the guard below), so importing it in tests doesn't launch the TUI.
|
|
323
|
+
export async function main() {
|
|
324
|
+
// Ink needs an interactive terminal.
|
|
325
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
326
|
+
console.error('Error: This command requires an interactive terminal.');
|
|
327
|
+
console.error('Please run `npx evals` from your terminal (not from a script or non-interactive environment).');
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Handle uncaught errors
|
|
332
|
+
process.on('uncaughtException', (error) => {
|
|
333
|
+
console.error('Uncaught exception:', error.message);
|
|
334
|
+
console.error(error.stack);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
339
|
+
console.error('Unhandled rejection at:', promise);
|
|
340
|
+
console.error('Reason:', reason);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Ensure stdout is not buffered (important for Windows)
|
|
345
|
+
if (process.stdout.isTTY) {
|
|
346
|
+
process.stdout.setEncoding('utf8');
|
|
244
347
|
}
|
|
245
|
-
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
let result = null;
|
|
351
|
+
const app = render(e(App, { onDone: (r) => { result = r; } }), { patchConsole: false });
|
|
352
|
+
|
|
353
|
+
await app.waitUntilExit();
|
|
354
|
+
|
|
355
|
+
if (!result) {
|
|
356
|
+
process.exit(0); // user quit / cancelled
|
|
357
|
+
} else if (result.type === 'url') {
|
|
358
|
+
console.log(`\nOpening ${result.url} in your browser...\n`);
|
|
359
|
+
openUrlInBrowser(result.url);
|
|
360
|
+
} else if (result.type === 'launch') {
|
|
361
|
+
launchAgent(result.agent);
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.error('Failed to render interactive CLI:', error.message);
|
|
365
|
+
console.error(error.stack);
|
|
366
|
+
if (process.platform === 'win32') {
|
|
367
|
+
console.error('\nNote: If you\'re using cmd.exe, try running in PowerShell or Git Bash instead.');
|
|
368
|
+
}
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Only run when invoked directly (as `evals`/`node cli.js`), not when imported.
|
|
374
|
+
const invokedDirectly =
|
|
375
|
+
process.argv[1] &&
|
|
376
|
+
realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
377
|
+
if (invokedDirectly) {
|
|
378
|
+
await main();
|
|
246
379
|
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# Get Started With Arize AX
|
|
2
|
+
|
|
3
|
+
Guide the user from zero to their first traces in Arize AX. Use the AX CLI, the Arize agent skills, and the official docs.
|
|
4
|
+
|
|
5
|
+
Work through the flow below in order. Installing the AX CLI and Arize skills, authenticating, and listing your spaces are all fine before approval — those are your own tooling. But do not create AX resources (like API keys), edit application files, or install the app's tracing dependencies before the user approves the plan in Step 5.
|
|
6
|
+
|
|
7
|
+
## Step 0: Welcome and confirm
|
|
8
|
+
|
|
9
|
+
Greet the user and show the plan, then ask before doing anything:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Welcome to Arize AX. I'll get you set up with tracing. Here's what I'll do:
|
|
13
|
+
|
|
14
|
+
1. Install the AX CLI and Arize skills
|
|
15
|
+
2. Create a free Arize AX account or sign you in
|
|
16
|
+
3. Instrument this project or a new app
|
|
17
|
+
4. Capture traces from your app
|
|
18
|
+
|
|
19
|
+
Shall I proceed?
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Do not proceed until the user approves.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
The AX CLI must be **arize-ax-cli `0.28.0` or newer** (Step 1 installs the latest) and needs **Python 3.11+** — a hard requirement. If Python is missing, stop and have the user install it from https://www.python.org/downloads/ and re-run. **Node.js 18+ with npx** is optional but installs the Arize skills; without it, don't stop — continue and use the docs paths in Step 6 (the Vercel AI SDK v7 starter needs Node.js 22+).
|
|
27
|
+
|
|
28
|
+
## Step 1: Install or update the AX CLI and Arize skills
|
|
29
|
+
|
|
30
|
+
Check the version with `ax --version`. If it's missing or older than the version above, install or upgrade `arize-ax-cli` — prefer `pipx`, otherwise pip:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pipx install arize-ax-cli # already installed: pipx upgrade arize-ax-cli
|
|
34
|
+
# without pipx:
|
|
35
|
+
python3 -m pip install --upgrade arize-ax-cli
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If npx is available, install the Arize agent skills; otherwise skip and continue (Step 6 falls back to the docs paths):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx skills add Arize-ai/arize-skills --skill '*' --yes
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This writes the skills to disk but does **not** load them into your current session — a mid-session install is not in your invocable skill list, so don't call it by name. When you need a skill (Step 6), load it by reading its `SKILL.md` directly from the install path (the command prints where it wrote them). Skill directories are named `arize-<name>`, e.g. `arize-instrumentation`.
|
|
45
|
+
|
|
46
|
+
## Step 2: Authenticate the CLI
|
|
47
|
+
|
|
48
|
+
The CLI authenticates through a profile named `default`, and every later `ax` command — including the trace check at the end — uses that profile. A bare `ARIZE_API_KEY` in the environment does **not** authenticate the CLI on its own; a profile must exist. Get one working before continuing.
|
|
49
|
+
|
|
50
|
+
First check whether the CLI is already authenticated:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
ax spaces list --limit 1 --output json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If this succeeds, a profile is already set up — skip the rest of this step.
|
|
57
|
+
|
|
58
|
+
If it fails, first check whether a `default` profile already exists from a prior run:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
ax profiles list
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If a `default` profile exists but the probe failed, it's signed out or expired, not missing — for an OAuth profile, refresh it with `ax auth login` (see the OAuth handling below) and re-run the probe rather than recreating it. Only create a new profile when none exists (or an existing api-key profile has an invalid key). To create one, look for an existing key first: check for `ARIZE_API_KEY` in the environment **or** the project's `.env` / `.env.local`.
|
|
65
|
+
|
|
66
|
+
- **A key is available** — the user already has an account and key, so skip the browser flow. Resolve the value (never print it): use `$ARIZE_API_KEY` if it's exported in your shell; if it's only in a dotenv file, load it from there for this one command. Then create an api-key profile and re-run the probe to confirm:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# if the key is only in a dotenv file, load it first (nothing is printed);
|
|
70
|
+
# point at the file the app uses (.env or .env.local):
|
|
71
|
+
export ARIZE_API_KEY="$(grep -E '^ARIZE_API_KEY=' .env 2>/dev/null | tail -1 | cut -d= -f2-)"
|
|
72
|
+
ax profiles create default --auth-method api-key --api-key "$ARIZE_API_KEY"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Reuse this key throughout — do **not** create a new one in Step 6.
|
|
76
|
+
|
|
77
|
+
- **No key anywhere** — sign up or sign in with browser OAuth:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
ax profiles create default --auth-method oauth
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Always pass the positional profile name `default`. Without it, the CLI prompts `profile name [default]:`, receives EOF from an agent-run command, and exits with `Goodbye!` without creating a profile.
|
|
84
|
+
|
|
85
|
+
The rest of this step applies only to the **browser OAuth** branch.
|
|
86
|
+
|
|
87
|
+
Creating an OAuth profile **is** the browser login flow. Treat it as an interactive browser handoff: it opens a browser, starts a localhost callback server such as `127.0.0.1:<port>/callback`, and waits for the browser redirect. The command must stay alive until the redirect lands and the CLI exits on its own.
|
|
88
|
+
|
|
89
|
+
While the OAuth command is waiting, do not close its stdin, send Ctrl-C, `pkill` the AX process, start a second auth command, or run an auth probe. Any of these aborts the in-progress browser flow. There is one deliberate exception — the new email/password signup case described below, where the callback never lands and the command must be restarted.
|
|
90
|
+
|
|
91
|
+
After launching the OAuth command, tell the user:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
A browser window is opening for Arize AX sign-in.
|
|
95
|
+
|
|
96
|
+
- Already have an account, or signing in with Google/SSO or an existing
|
|
97
|
+
email/password? Just finish in the browser and I'll continue automatically.
|
|
98
|
+
- Creating a BRAND-NEW account with email and password? Arize emails you a
|
|
99
|
+
validation link. That link does NOT complete the CLI login, so this command
|
|
100
|
+
will hang. Click the link to finish creating your account, then tell me.
|
|
101
|
+
|
|
102
|
+
Did you just sign up for a new account with email and password?
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**If the user says yes (new email/password signup):** the localhost callback will never fire and the command will wait forever, so this is the one time you break the "keep it alive" rule. Once they confirm they've clicked the validation link and their account exists, terminate the waiting OAuth command (send Ctrl-C / kill that process), then re-run it:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
ax profiles create default --auth-method oauth
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The second run is now a plain sign-in for the validated account. Its callback lands normally, so handle it with all the standard rules below — keep it alive and wait for it to exit on its own.
|
|
112
|
+
|
|
113
|
+
**Otherwise (existing account, SSO, or existing email/password):** wait for the command to exit on its own. Treat exit code `0`, or CLI success output such as `Configuration saved to profile 'default'` or `Active profile set`, as the primary completion signal.
|
|
114
|
+
|
|
115
|
+
Only after the OAuth command completes, verify authentication with a non-secret probe:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
ax spaces list --limit 1 --output json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
If the probe succeeds, continue. If the default OAuth profile already existed and the probe returns an authentication error, the profile is signed out or expired — run the fallback:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
ax auth login
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Handle `ax auth login` with the same rules: keep its callback server alive, wait for it to exit or print a success line, then rerun the probe. Do not run `ax auth login` immediately after creating an OAuth profile; the profile creation already performed login.
|
|
128
|
+
|
|
129
|
+
Do not treat `ax profiles show` alone as proof that OAuth completed; it can show profile configuration even when the user still needs to authenticate. Use it only for troubleshooting profile configuration, and never with `--expand`.
|
|
130
|
+
|
|
131
|
+
Run no other remote `ax` command (creating keys, listing all spaces, inspecting resources) until the probe succeeds.
|
|
132
|
+
|
|
133
|
+
## Step 3: Select the AX space
|
|
134
|
+
|
|
135
|
+
List the spaces the authenticated profile can access:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
ax spaces list --output json
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
If `ARIZE_SPACE_ID` is already set (environment or `.env` / `.env.local`), use it **only if it appears in that list** — that confirms the active profile can reach it (the app's existing key and your CLI profile may point at different spaces). If it's set but not in the list, don't trust it; select from the list instead. Otherwise: if exactly one space is returned, use it; if multiple, ask the user which to use; if none, guide the user to create a space in the Arize AX UI (or with an organization ID if available), then re-list.
|
|
142
|
+
|
|
143
|
+
Capture the space's **ID** (not its display name) for `ARIZE_SPACE_ID`. The `arize-otel` tracing config requires the space ID; a name will silently fail to route traces.
|
|
144
|
+
|
|
145
|
+
## Step 4: Inspect the folder and choose a path
|
|
146
|
+
|
|
147
|
+
Inspect the current folder to decide whether an app already exists. Do not change files during inspection. Look for:
|
|
148
|
+
|
|
149
|
+
- Python: `pyproject.toml`, `requirements.txt`, `setup.py`, `Pipfile`, imports.
|
|
150
|
+
- TypeScript/JavaScript: `package.json`, lockfiles, `src`, `app`, `pages`, provider imports.
|
|
151
|
+
- Go: `go.mod`. Java: `pom.xml`, `build.gradle`, `build.gradle.kts`.
|
|
152
|
+
- Existing observability: `opentelemetry`, `TracerProvider`, `ARIZE_*`, `OTEL_*`, `OTLP_*`, Datadog, Honeycomb, Sentry, or other tracing.
|
|
153
|
+
- Agent framework: identify it by its import/package — e.g. `langchain` / `langgraph`, `llama_index`, `crewai`, `autogen`, `semantic_kernel`, `pydantic_ai`, `google.adk`, `dspy`, `agent_framework`, and others. **Route on the framework, not the provider client it wraps** — an `openai` or `anthropic` import inside a framework app is not the thing to instrument; the framework almost certainly has its own integration (see Step 6).
|
|
154
|
+
|
|
155
|
+
In a monorepo, check the git root to get oriented, but only instrument apps in or below the current working directory. If the project spans more than one language, instrument each one (route each through its own integration page in Step 6).
|
|
156
|
+
|
|
157
|
+
Then branch on what you found:
|
|
158
|
+
|
|
159
|
+
### If an app exists in the current folder
|
|
160
|
+
|
|
161
|
+
Do not offer the starter-app path. Summarize the detected stack and offer to instrument it:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
I found a <language>/<framework> app in this folder. Want me to add Arize AX
|
|
165
|
+
tracing to it?
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
If the user declines, stop here rather than falling back to a starter app.
|
|
169
|
+
|
|
170
|
+
### If no app exists in the current folder
|
|
171
|
+
|
|
172
|
+
Go straight to the starter-app choice — do not ask whether to instrument the empty folder. Ask which folder to create it in, then offer these choices:
|
|
173
|
+
|
|
174
|
+
- OpenAI — Python or TypeScript (with a tool call)
|
|
175
|
+
- Anthropic — Python (with a tool call; official AX auto-instrumentation)
|
|
176
|
+
- LangChain with OpenAI — Python or TypeScript (a tool-using agent)
|
|
177
|
+
- Vercel AI SDK with OpenAI — TypeScript (a tool-using agent)
|
|
178
|
+
|
|
179
|
+
If the user picks an unsupported pairing, explain the supported options and ask again.
|
|
180
|
+
|
|
181
|
+
## Step 5: Present the plan and get approval
|
|
182
|
+
|
|
183
|
+
Before creating any remote resource, writing files, or installing dependencies, present one consolidated plan and wait for approval. This is the gate the intro refers to — nothing so far has modified the app or created AX resources.
|
|
184
|
+
|
|
185
|
+
For an existing app, cover: detected language and framework, package manager, LLM provider or agent framework, any existing tracing to preserve, the env file that will be updated, the instrumentation packages and files that will change, whether a new AX user API key will be created or the existing `ARIZE_API_KEY` reused, and the project name that will be used.
|
|
186
|
+
|
|
187
|
+
For a starter app, cover: the chosen provider and language, the target folder, the packages that will be installed, and the project name.
|
|
188
|
+
|
|
189
|
+
Choose a default project name from the current folder or app name: lowercase it, replace spaces and unsupported punctuation with hyphens, and append `-arize-tracing` if it is too generic. Do not create an AX project explicitly — it is created on first trace ingestion.
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
Here's my plan. Shall I proceed?
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Do not continue until the user approves.
|
|
196
|
+
|
|
197
|
+
## Step 6: Create the API key, write the env file, and instrument
|
|
198
|
+
|
|
199
|
+
Only after approval, execute the plan in this order.
|
|
200
|
+
|
|
201
|
+
### Choose the env file
|
|
202
|
+
|
|
203
|
+
Pick the env file to match the app and use it for every variable below:
|
|
204
|
+
|
|
205
|
+
- Next.js, Vite, or browser-adjacent TypeScript apps: `.env.local`
|
|
206
|
+
- Python apps, Node scripts, backend services, or unknown type: `.env`
|
|
207
|
+
- If the project already has exactly one of `.env` or `.env.local`, follow the existing convention.
|
|
208
|
+
|
|
209
|
+
Do not read existing env file contents into chat. Preserve unrelated variables and never reveal their values.
|
|
210
|
+
|
|
211
|
+
Make sure the env file is git-ignored before writing the API key to it — if the repo has a `.gitignore`, confirm it covers the file (add `.env` / `.env.local` if not); for a starter app you create, add one. The API key must never be committed to version control.
|
|
212
|
+
|
|
213
|
+
### Write the non-secret variables
|
|
214
|
+
|
|
215
|
+
Add these two lines to the env file you chose — as file contents, not shell commands. Create the file if needed; if a line already exists, update it in place rather than duplicating. They're not secret. Skip `ARIZE_SPACE_ID` if it's already set to the value you're using.
|
|
216
|
+
|
|
217
|
+
```dotenv
|
|
218
|
+
ARIZE_SPACE_ID=<space-id>
|
|
219
|
+
ARIZE_PROJECT_NAME=<chosen-project-name>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Create the API key
|
|
223
|
+
|
|
224
|
+
**Skip this entirely if `ARIZE_API_KEY` was already present in Step 2** — reuse it and leave its env value untouched. Only create a key when you authenticated with browser OAuth and the app has no key yet.
|
|
225
|
+
|
|
226
|
+
Create the key and write it into the env file in one step:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
ax api-keys create --name "Local Arize AX tracing" --env-file .env
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`--env-file` writes `ARIZE_API_KEY` atomically and **never prints it** — no temp file, no secret in your terminal or chat. Use `.env.local` if that's the app's convention; the file is created if missing, an existing `ARIZE_API_KEY` is replaced in place, and other variables are preserved.
|
|
233
|
+
|
|
234
|
+
**Create the key exactly once** — a second `ax api-keys create` just orphans a still-active key. Git-ignore the env file (see above) *before* running this so the key is never committed.
|
|
235
|
+
|
|
236
|
+
If key creation fails, have the user create one in the Arize AX UI and add it to the env file without exposing it in chat. Either way, create only one key.
|
|
237
|
+
|
|
238
|
+
Handle the LLM provider's own key (e.g. `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`) in the same env file — the app can't make a call or produce traces without it:
|
|
239
|
+
|
|
240
|
+
- **Existing app:** confirm the key is already present. If it's missing, ask the user for it and add it without echoing the value.
|
|
241
|
+
- **New starter app:** add a placeholder line for the provider key the app needs (e.g. `OPENAI_API_KEY=` with an empty value) to the env file, so the user knows exactly what to fill in. Tell them to set it before running; if they share it now, fill it in without echoing the value.
|
|
242
|
+
|
|
243
|
+
### Add instrumentation
|
|
244
|
+
|
|
245
|
+
**Prefer the Arize instrumentation skill.** If you installed the skills in Step 1, load it now by reading its `SKILL.md` directly (a mid-session install isn't in your invocable list, so don't call it by name): read `arize-instrumentation/SKILL.md` from your agent's skills directory — project-level (`.claude/skills/`, `.cursor/skills/`, `.codex/…`, `.windsurf/…`) or the matching `~/.<agent>/…` global path. Follow the skill as your source of truth and **skip the rest of this section**. Use the docs fallback below only if the skill file doesn't exist (e.g. npx was missing in Step 1).
|
|
246
|
+
|
|
247
|
+
**Route by the framework you detected — search the index for *its* name and follow that page.** Look up the framework you identified in Step 4 (not the provider it wraps) in the index at https://arize.com/docs/ax/integrations (machine-readable: https://arize.com/docs/llms.txt). Most agent frameworks have a dedicated page even though they aren't in the shortcuts below, so actually search the index before concluding a framework is unsupported — it lists every supported provider and framework with the exact, verified setup. Don't force a stack onto a listed framework's setup, and don't wander into unrelated Arize docs once you're on the right page.
|
|
248
|
+
|
|
249
|
+
Common shortcuts:
|
|
250
|
+
|
|
251
|
+
- OpenAI: https://arize.com/docs/ax/integrations/llm-providers/openai/openai-tracing
|
|
252
|
+
- Anthropic: https://arize.com/docs/ax/integrations/llm-providers/anthropic/anthropic-tracing
|
|
253
|
+
- LangChain Python: https://arize.com/docs/ax/integrations/python-agent-frameworks/langchain/langchain-tracing
|
|
254
|
+
- LangChain.js: https://arize.com/docs/ax/integrations/ts-js-agent-frameworks/langchain/langchain-js
|
|
255
|
+
- Vercel AI SDK v7: https://arize.com/docs/ax/integrations/ts-js-agent-frameworks/vercel/vercel-ai-sdk-v7-tracing
|
|
256
|
+
- Vercel AI SDK v6 and earlier: https://arize.com/docs/ax/integrations/ts-js-agent-frameworks/vercel/vercel-ai-sdk-tracing
|
|
257
|
+
|
|
258
|
+
**Only if the framework genuinely has no page in the index**, fall back in this order — never hand-roll a raw OpenTelemetry `TracerProvider` + OTLP exporter when a helper exists:
|
|
259
|
+
|
|
260
|
+
1. Use a framework-specific OpenInference instrumentor if one exists (`openinference-instrumentation-<name>` / `@arizeai/openinference-instrumentation-<name>`), wired up with `arize-otel` per the manual-instrumentation guide; install it unpinned.
|
|
261
|
+
1. Instrument the underlying provider (OpenAI, Anthropic, Bedrock, …) with its instrumentor **only if the framework calls the provider SDK directly**. Many agent frameworks instead drive the model through their own client layer and emit their own OpenTelemetry spans — a provider instrumentor captures **no traces** for those. Never reach for the provider instrumentor as a blind fallback just because you recognize an `openai`/`anthropic` client.
|
|
262
|
+
1. Otherwise instrument manually via `arize-otel` (see https://arize.com/docs/ax/instrument/manual-instrumentation), or stop and ask the user if you still can't determine a setup.
|
|
263
|
+
|
|
264
|
+
For existing apps:
|
|
265
|
+
|
|
266
|
+
- Use the app's existing package manager and style.
|
|
267
|
+
- Initialize tracing before LLM clients or frameworks are imported or created.
|
|
268
|
+
- Preserve existing OpenTelemetry providers; add Arize as an additional exporter when needed.
|
|
269
|
+
- Do not change business logic except to enable tracing.
|
|
270
|
+
- If the app uses tools/function calls and the integration does not capture tool spans, add manual tool spans only after explaining that in the plan.
|
|
271
|
+
|
|
272
|
+
For starter apps:
|
|
273
|
+
|
|
274
|
+
- Every starter should make a real LLM call that invokes at least one tool (e.g. a calculator or a canned data lookup), with the model deciding to call it and the result fed back for a final answer — so the first traces show a multi-step trajectory (the LLM call plus the tool call as its own span), not one flat span.
|
|
275
|
+
- Agent frameworks (LangChain, Vercel AI SDK): define the tool the framework's way; the instrumentor captures the tool and agent spans automatically.
|
|
276
|
+
- Plain providers (OpenAI, Anthropic): use the provider's native tool/function calling, and add tool spans with the OpenInference decorators — `@tracer.tool` on the tool function and `@tracer.agent` on the top-level loop. These decorators require wrapping the tracer in `OITracer` from `openinference-instrumentation` (the raw `arize.otel.register()` tracer doesn't expose them and `@tracer.tool` will raise `AttributeError`); see https://arize.com/docs/ax/instrument/manual-instrumentation.
|
|
277
|
+
- Ensure short-lived scripts flush/shut down the tracer provider before exit, or spans won't export.
|
|
278
|
+
- Give a clear run command and note which provider key env var is needed.
|
|
279
|
+
|
|
280
|
+
### Package guidance
|
|
281
|
+
|
|
282
|
+
Install into the app's existing environment (its virtualenv if it has one) exactly the packages the detected framework's integration page lists — instrumentor names and peer dependencies differ per framework, so follow that page rather than copying from another stack or guessing versions.
|
|
283
|
+
|
|
284
|
+
## Step 7: Run the app and poll for the first trace
|
|
285
|
+
|
|
286
|
+
First confirm the LLM provider's API key is set in the env file (from Step 6); a missing provider key is the most common reason the run produces no traces.
|
|
287
|
+
|
|
288
|
+
If you created a starter app in this flow, offer to run it for the user:
|
|
289
|
+
|
|
290
|
+
```text
|
|
291
|
+
Your starter app is ready. Want me to run it for you?
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
If they say yes, run it yourself with the run command, then poll. If they say no — or if you instrumented their existing app rather than creating a starter — tell them the exact run command and ask them to run it:
|
|
295
|
+
|
|
296
|
+
```text
|
|
297
|
+
Run your app with:
|
|
298
|
+
|
|
299
|
+
<run command>
|
|
300
|
+
|
|
301
|
+
It should make at least one LLM call. I'll poll Arize AX and let you know as
|
|
302
|
+
soon as your first traces arrive.
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Once the app has run (whether you ran it or the user did), poll for spans. Pass the actual project name and space ID as literal arguments — do not use `$ARIZE_PROJECT_NAME`/`$ARIZE_SPACE_ID`, which live in the env file and are not exported to your shell. Query every ~15 seconds for up to ~3 minutes.
|
|
306
|
+
|
|
307
|
+
Span bodies can contain prompts, completions, tool arguments, and user data, so don't dump them into chat. Pipe the export through a counter that surfaces only how many spans arrived:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
ax spans export "<project-name>" --space "<space-id>" --limit 5 --stdout \
|
|
311
|
+
| python3 -c 'import json,sys; d=json.load(sys.stdin); s=d if isinstance(d,list) else (d.get("spans") or d.get("data") or []); print(f"{len(s)} span(s) found")'
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
A non-zero count confirms traces are arriving. If you need to inspect a span to debug, write the export to a file outside the repo and read only the fields you need — never paste raw span bodies into chat.
|
|
315
|
+
|
|
316
|
+
This uses the CLI profile from Step 2 (OAuth or api-key) — it works the same either way. If the export errors with an authentication failure, the profile isn't valid; re-run the Step 2 probe and re-authenticate, or fall back to having the user open the project in the Arize AX UI to confirm traces.
|
|
317
|
+
|
|
318
|
+
- When spans come back, stop polling and continue to Step 8.
|
|
319
|
+
- On timeout, do not fail silently. Tell the user no traces arrived yet, and give likely causes: app didn't make an LLM call, tracing initialized after the client was created, a short-lived script exited before flushing spans, or the wrong space/project/env file. Offer to re-check once they've run it again.
|
|
320
|
+
|
|
321
|
+
Do not fabricate trace results. Only report traces the export command actually returned.
|
|
322
|
+
|
|
323
|
+
## Step 8: Report the first traces with a link
|
|
324
|
+
|
|
325
|
+
Once spans arrive, report the span count and give the user a link into Arize AX for the project. Point them at the UI to explore the trace contents rather than printing span bodies into chat.
|
|
326
|
+
|
|
327
|
+
```text
|
|
328
|
+
Your first traces are in Arize AX. Open project `<ARIZE_PROJECT_NAME>` here:
|
|
329
|
+
https://app.arize.com/
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
If a space- or project-specific URL is known from the CLI or docs, prefer that deep link over the app root. Do not invent a URL structure you are not sure of; fall back to `https://app.arize.com/` plus instructions to select the project.
|
|
333
|
+
|
|
334
|
+
## Step 9: Point at docs
|
|
335
|
+
|
|
336
|
+
Finish with links so the user can go deeper:
|
|
337
|
+
|
|
338
|
+
```text
|
|
339
|
+
You're set up. To go further:
|
|
340
|
+
|
|
341
|
+
- Docs index: https://arize.com/docs/ax
|
|
342
|
+
- Tracing integrations: https://arize.com/docs/ax/integrations
|
|
343
|
+
- AX CLI: https://arize.com/docs/api-clients/cli/overview
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Critical rules
|
|
347
|
+
|
|
348
|
+
- Get the user's approval (Step 5) before creating AX resources, editing files, or installing dependencies.
|
|
349
|
+
- Authenticate the CLI before any other `ax` call — every command, including the final trace check, uses the active profile; env vars alone don't authenticate it. On the browser OAuth branch, keep the command alive until its localhost callback completes (never close stdin, Ctrl-C, `pkill`, or probe while it waits) — sole exception: a brand-new email/password signup, whose link never calls back, so ask the user, then kill and re-run as a sign-in.
|
|
350
|
+
- Never print, log, or summarize secrets in chat — API keys, env-file contents/values, or span bodies (prompts, completions, tool args, user data) — and never read env files into chat. Only report traces a command actually returned.
|
|
351
|
+
- Create at most one AX API key: skip if `ARIZE_API_KEY` already exists (reuse it), otherwise a **single** `ax api-keys create --env-file <file>`. Git-ignore that file before creating the key; never create a second key.
|
|
352
|
+
- Write the space **ID** (not its name) to `ARIZE_SPACE_ID`, or traces won't route; never create an AX project explicitly (it's made on first ingestion).
|
|
353
|
+
- Initialize tracing before LLM clients are created, and flush/shut down the tracer before short-lived scripts exit. Vercel AI SDK v7 also needs Node.js 22+, `@ai-sdk/otel` registered, and `experimental_telemetry: { isEnabled: true }` per call.
|
|
354
|
+
|
|
355
|
+
Docs: https://arize.com/docs/llms.txt
|
package/package.json
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evals",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Arize
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "Arize AX onboarding — instrument your app with tracing via your coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "cli.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"postinstall": "
|
|
9
|
-
"start": "node cli.js"
|
|
8
|
+
"postinstall": "node -e \"console.log('Arize evals installed — run: npx evals')\"",
|
|
9
|
+
"start": "node cli.js",
|
|
10
|
+
"test": "node --test"
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
|
-
"@arizeai/phoenix-evals": "^0.6.5",
|
|
13
|
-
"@arizeai/phoenix-otel": "*",
|
|
14
13
|
"ink": "^6.0.0",
|
|
15
14
|
"ink-gradient": "^3.0.0",
|
|
16
15
|
"react": "^19.0.0"
|
|
@@ -18,9 +17,18 @@
|
|
|
18
17
|
"bin": {
|
|
19
18
|
"evals": "./cli.js"
|
|
20
19
|
},
|
|
20
|
+
"files": [
|
|
21
|
+
"cli.js",
|
|
22
|
+
"onboarding-prompt.md",
|
|
23
|
+
"start.sh",
|
|
24
|
+
"start.ps1"
|
|
25
|
+
],
|
|
21
26
|
"keywords": [
|
|
22
27
|
"arize",
|
|
23
|
-
"evals"
|
|
28
|
+
"evals",
|
|
29
|
+
"tracing",
|
|
30
|
+
"observability",
|
|
31
|
+
"llm"
|
|
24
32
|
],
|
|
25
33
|
"author": "",
|
|
26
34
|
"license": "ISC"
|
package/start.ps1
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
#
|
|
3
|
+
# Arize AX onboarding launcher (Windows / PowerShell).
|
|
4
|
+
#
|
|
5
|
+
# Lets you pick an installed coding agent and launches it seeded with the
|
|
6
|
+
# onboarding prompt to walk you through signup -> instrument -> first trace.
|
|
7
|
+
# This is the no-npm path; if you have Node, `npx evals` gives the same thing
|
|
8
|
+
# with a nicer UI.
|
|
9
|
+
#
|
|
10
|
+
# Run it:
|
|
11
|
+
# irm https://cdn.jsdelivr.net/npm/evals/start.ps1 | iex
|
|
12
|
+
#
|
|
13
|
+
# Non-interactive (irm|iex can't take params, so use the env var):
|
|
14
|
+
# $env:ARIZE_AGENT='claude'; irm .../start.ps1 | iex
|
|
15
|
+
# Or via the scriptblock form, which does accept -Agent:
|
|
16
|
+
# & ([scriptblock]::Create((irm .../start.ps1))) -Agent claude
|
|
17
|
+
|
|
18
|
+
[CmdletBinding()]
|
|
19
|
+
param(
|
|
20
|
+
[string]$Agent = $env:ARIZE_AGENT
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
$ErrorActionPreference = 'Stop'
|
|
24
|
+
|
|
25
|
+
# The prompt ships inside the public `evals` npm package; jsDelivr serves package
|
|
26
|
+
# files over HTTP, so it's fetchable even though the source repo is private.
|
|
27
|
+
# Override with $env:ARIZE_PROMPT_URL if needed.
|
|
28
|
+
$PromptUrl = if ($env:ARIZE_PROMPT_URL) { $env:ARIZE_PROMPT_URL } else { 'https://cdn.jsdelivr.net/npm/evals/onboarding-prompt.md' }
|
|
29
|
+
|
|
30
|
+
# Prefer the richer, bundled-prompt `npx evals` experience when Node is present.
|
|
31
|
+
# This script is the no-npm fallback; if npx exists, hand off to it.
|
|
32
|
+
# Set $env:ARIZE_SKIP_NPX=1 to force this shell path even when npx is available.
|
|
33
|
+
if (-not $env:ARIZE_SKIP_NPX -and (Get-Command npx -ErrorAction SilentlyContinue)) {
|
|
34
|
+
npx --yes evals
|
|
35
|
+
exit $LASTEXITCODE
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Supported agents: id -> label, install URL, and whether the REPL is seeded with `-i`.
|
|
39
|
+
$Agents = [ordered]@{
|
|
40
|
+
'claude' = @{ Label = 'Claude Code'; Install = 'https://docs.claude.com/en/docs/claude-code'; Flag = $null }
|
|
41
|
+
'codex' = @{ Label = 'OpenAI Codex'; Install = 'https://developers.openai.com/codex/cli'; Flag = $null }
|
|
42
|
+
'cursor-agent' = @{ Label = 'Cursor'; Install = 'https://docs.cursor.com/en/cli/overview'; Flag = $null }
|
|
43
|
+
'copilot' = @{ Label = 'GitHub Copilot'; Install = 'https://github.com/features/copilot/cli'; Flag = '-i' }
|
|
44
|
+
'gemini' = @{ Label = 'Gemini CLI'; Install = 'https://github.com/google-gemini/gemini-cli'; Flag = '-i' }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function Test-Agent([string]$id) {
|
|
48
|
+
return [bool](Get-Command $id -ErrorAction SilentlyContinue)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Resolve which agent to launch.
|
|
52
|
+
function Resolve-Agent {
|
|
53
|
+
if ($Agent) {
|
|
54
|
+
if (Test-Agent $Agent) { return $Agent }
|
|
55
|
+
Write-Host "Requested agent '$Agent' is not on your PATH." -ForegroundColor Yellow
|
|
56
|
+
return $null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$detected = @($Agents.Keys | Where-Object { Test-Agent $_ })
|
|
60
|
+
|
|
61
|
+
if ($detected.Count -eq 0) {
|
|
62
|
+
Write-Host "No supported coding agent found on your PATH." -ForegroundColor Yellow
|
|
63
|
+
Write-Host "Install one of these, then re-run:"
|
|
64
|
+
foreach ($id in $Agents.Keys) {
|
|
65
|
+
"{0,-14} {1}" -f $Agents[$id].Label, $Agents[$id].Install | Write-Host
|
|
66
|
+
}
|
|
67
|
+
return $null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if ($detected.Count -eq 1) { return $detected[0] }
|
|
71
|
+
|
|
72
|
+
Write-Host "Choose your coding agent:"
|
|
73
|
+
for ($i = 0; $i -lt $detected.Count; $i++) {
|
|
74
|
+
" {0}) {1}" -f ($i + 1), $Agents[$detected[$i]].Label | Write-Host
|
|
75
|
+
}
|
|
76
|
+
while ($true) {
|
|
77
|
+
$choice = Read-Host "Enter a number [1-$($detected.Count)]"
|
|
78
|
+
if ($choice -match '^\d+$' -and [int]$choice -ge 1 -and [int]$choice -le $detected.Count) {
|
|
79
|
+
return $detected[[int]$choice - 1]
|
|
80
|
+
}
|
|
81
|
+
Write-Host "Invalid choice."
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# --- Welcome ---
|
|
86
|
+
Write-Host ""
|
|
87
|
+
Write-Host "Arize AX" -ForegroundColor Magenta -NoNewline
|
|
88
|
+
Write-Host " - Evals & Observability for Agentic AI" -ForegroundColor DarkGray
|
|
89
|
+
Write-Host ""
|
|
90
|
+
Write-Host "Let's get you tracing. I'll launch your coding agent with a guided prompt that"
|
|
91
|
+
Write-Host "walks you through signup, instrumenting your app, and seeing your first traces."
|
|
92
|
+
Write-Host ""
|
|
93
|
+
|
|
94
|
+
$chosen = Resolve-Agent
|
|
95
|
+
if (-not $chosen) { exit 1 }
|
|
96
|
+
|
|
97
|
+
# Download the prompt to a temp file (rename the .tmp to .md rather than
|
|
98
|
+
# leaving both behind).
|
|
99
|
+
$tmp = New-TemporaryFile
|
|
100
|
+
$promptFile = [System.IO.Path]::ChangeExtension($tmp.FullName, 'md')
|
|
101
|
+
Rename-Item -Path $tmp.FullName -NewName (Split-Path $promptFile -Leaf)
|
|
102
|
+
try {
|
|
103
|
+
Write-Host "Fetching the onboarding prompt..."
|
|
104
|
+
try {
|
|
105
|
+
Invoke-RestMethod -Uri $PromptUrl -OutFile $promptFile
|
|
106
|
+
} catch {
|
|
107
|
+
Write-Host "Failed to download the onboarding prompt from $PromptUrl" -ForegroundColor Red
|
|
108
|
+
Write-Host "Check your connection and try again."
|
|
109
|
+
exit 1
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
$seed = "Read the file $promptFile and follow it to set up Arize AX tracing in this project, walking me through each step and asking me questions as needed."
|
|
113
|
+
|
|
114
|
+
Write-Host ("Launching {0}..." -f $Agents[$chosen].Label)
|
|
115
|
+
Write-Host ""
|
|
116
|
+
|
|
117
|
+
# Launch interactive + seeded. No skip-permissions — the agent's approval
|
|
118
|
+
# model and the prompt's own approval gate must stay intact. External commands
|
|
119
|
+
# attach to the console, so the agent gets a real terminal.
|
|
120
|
+
$flag = $Agents[$chosen].Flag
|
|
121
|
+
if ($flag) {
|
|
122
|
+
& $chosen $flag $seed
|
|
123
|
+
} else {
|
|
124
|
+
& $chosen $seed
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
Remove-Item $promptFile -ErrorAction SilentlyContinue
|
|
128
|
+
}
|
package/start.sh
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Arize AX onboarding launcher.
|
|
4
|
+
#
|
|
5
|
+
# Lets you pick an installed coding agent and launches it seeded with the
|
|
6
|
+
# onboarding prompt to walk you through signup → instrument → first trace.
|
|
7
|
+
# This is the no-npm path; if you have Node, `npx evals` gives the same thing
|
|
8
|
+
# with a nicer UI.
|
|
9
|
+
#
|
|
10
|
+
# Run it (do NOT use `curl | bash` — that hijacks stdin and breaks the menu
|
|
11
|
+
# and the launched agent). Use process substitution so the menu and the agent
|
|
12
|
+
# both get a real terminal:
|
|
13
|
+
#
|
|
14
|
+
# bash <(curl -fsSL https://cdn.jsdelivr.net/npm/evals/start.sh)
|
|
15
|
+
#
|
|
16
|
+
# Non-interactive: ARIZE_AGENT=claude bash <(curl -fsSL .../start.sh)
|
|
17
|
+
# or: bash <(curl -fsSL .../start.sh) --agent claude
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
# The prompt ships inside the public `evals` npm package; jsDelivr serves package
|
|
22
|
+
# files over HTTP, so it's fetchable even though the source repo is private.
|
|
23
|
+
# Override with ARIZE_PROMPT_URL if needed.
|
|
24
|
+
PROMPT_URL="${ARIZE_PROMPT_URL:-https://cdn.jsdelivr.net/npm/evals/onboarding-prompt.md}"
|
|
25
|
+
|
|
26
|
+
# Prefer the richer, bundled-prompt `npx evals` experience when Node is present.
|
|
27
|
+
# This shell script is the no-npm fallback; if npx exists, hand off to it.
|
|
28
|
+
# Set ARIZE_SKIP_NPX=1 to force this shell path even when npx is available.
|
|
29
|
+
if [ -z "${ARIZE_SKIP_NPX:-}" ] && command -v npx >/dev/null 2>&1; then
|
|
30
|
+
exec npx --yes evals
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Supported agents: id | display label | argv to start the REPL seeded with a prompt.
|
|
34
|
+
# The seed prompt is appended as the final argument at launch.
|
|
35
|
+
AGENT_IDS=(claude codex cursor-agent copilot gemini)
|
|
36
|
+
agent_label() {
|
|
37
|
+
case "$1" in
|
|
38
|
+
claude) echo "Claude Code" ;;
|
|
39
|
+
codex) echo "OpenAI Codex" ;;
|
|
40
|
+
cursor-agent) echo "Cursor" ;;
|
|
41
|
+
copilot) echo "GitHub Copilot" ;;
|
|
42
|
+
gemini) echo "Gemini CLI" ;;
|
|
43
|
+
*) echo "$1" ;;
|
|
44
|
+
esac
|
|
45
|
+
}
|
|
46
|
+
agent_install_url() {
|
|
47
|
+
case "$1" in
|
|
48
|
+
claude) echo "https://docs.claude.com/en/docs/claude-code" ;;
|
|
49
|
+
codex) echo "https://developers.openai.com/codex/cli" ;;
|
|
50
|
+
cursor-agent) echo "https://docs.cursor.com/en/cli/overview" ;;
|
|
51
|
+
copilot) echo "https://github.com/features/copilot/cli" ;;
|
|
52
|
+
gemini) echo "https://github.com/google-gemini/gemini-cli" ;;
|
|
53
|
+
esac
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Parse --agent flag
|
|
57
|
+
AGENT="${ARIZE_AGENT:-}"
|
|
58
|
+
while [[ $# -gt 0 ]]; do
|
|
59
|
+
case "$1" in
|
|
60
|
+
--agent) AGENT="${2:-}"; shift 2 ;;
|
|
61
|
+
--agent=*) AGENT="${1#*=}"; shift ;;
|
|
62
|
+
-h|--help)
|
|
63
|
+
echo "Usage: start.sh [--agent <claude|codex|cursor-agent|copilot|gemini>]"
|
|
64
|
+
exit 0 ;;
|
|
65
|
+
*) echo "Unknown argument: $1" >&2; exit 1 ;;
|
|
66
|
+
esac
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
# --- Welcome ---
|
|
70
|
+
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then
|
|
71
|
+
_pink=$'\033[1;38;2;255;0;140m'; _dim=$'\033[2m'; _reset=$'\033[0m'
|
|
72
|
+
else
|
|
73
|
+
_pink=""; _dim=""; _reset=""
|
|
74
|
+
fi
|
|
75
|
+
printf '\n%sArize AX%s %s— Evals & Observability for Agentic AI%s\n\n' "$_pink" "$_reset" "$_dim" "$_reset"
|
|
76
|
+
printf "Let's get you tracing. I'll launch your coding agent with a guided prompt that\nwalks you through signup, instrumenting your app, and seeing your first traces.\n\n"
|
|
77
|
+
|
|
78
|
+
# Detect installed agents (PATH lookup only — never executes them).
|
|
79
|
+
DETECTED=()
|
|
80
|
+
for id in "${AGENT_IDS[@]}"; do
|
|
81
|
+
if command -v "$id" >/dev/null 2>&1; then
|
|
82
|
+
DETECTED+=("$id")
|
|
83
|
+
fi
|
|
84
|
+
done
|
|
85
|
+
|
|
86
|
+
# Resolve which agent to launch.
|
|
87
|
+
choose_agent() {
|
|
88
|
+
# Explicit choice wins if it's actually installed.
|
|
89
|
+
if [[ -n "$AGENT" ]]; then
|
|
90
|
+
if command -v "$AGENT" >/dev/null 2>&1; then
|
|
91
|
+
echo "$AGENT"; return 0
|
|
92
|
+
fi
|
|
93
|
+
echo "Requested agent '$AGENT' is not on your PATH." >&2
|
|
94
|
+
return 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if [[ ${#DETECTED[@]} -eq 0 ]]; then
|
|
98
|
+
echo "No supported coding agent found on your PATH." >&2
|
|
99
|
+
echo "Install one of these, then re-run:" >&2
|
|
100
|
+
for id in "${AGENT_IDS[@]}"; do
|
|
101
|
+
printf ' %-14s %s\n' "$(agent_label "$id")" "$(agent_install_url "$id")" >&2
|
|
102
|
+
done
|
|
103
|
+
return 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [[ ${#DETECTED[@]} -eq 1 ]]; then
|
|
107
|
+
echo "${DETECTED[0]}"; return 0
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Interactive menu — read from the real terminal, not the (piped) stdin.
|
|
111
|
+
if [[ ! -r /dev/tty ]]; then
|
|
112
|
+
echo "Multiple agents found but no terminal to prompt on. Set ARIZE_AGENT=<id>." >&2
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
{
|
|
116
|
+
echo "Choose your coding agent:"
|
|
117
|
+
local i=1
|
|
118
|
+
for id in "${DETECTED[@]}"; do
|
|
119
|
+
printf ' %d) %s\n' "$i" "$(agent_label "$id")"
|
|
120
|
+
i=$((i + 1))
|
|
121
|
+
done
|
|
122
|
+
} >/dev/tty
|
|
123
|
+
local choice
|
|
124
|
+
while true; do
|
|
125
|
+
printf 'Enter a number [1-%d]: ' "${#DETECTED[@]}" >/dev/tty
|
|
126
|
+
read -r choice </dev/tty || return 1
|
|
127
|
+
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#DETECTED[@]} )); then
|
|
128
|
+
echo "${DETECTED[$((choice - 1))]}"; return 0
|
|
129
|
+
fi
|
|
130
|
+
echo "Invalid choice." >/dev/tty
|
|
131
|
+
done
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
CHOSEN="$(choose_agent)" || exit 1
|
|
135
|
+
|
|
136
|
+
# Download the prompt to a temp file. (mktemp templates must end in X's on
|
|
137
|
+
# macOS, so add the .md extension afterwards.)
|
|
138
|
+
PROMPT_FILE="$(mktemp "${TMPDIR:-/tmp}/arize-onboarding-XXXXXX")"
|
|
139
|
+
mv "$PROMPT_FILE" "$PROMPT_FILE.md"
|
|
140
|
+
PROMPT_FILE="$PROMPT_FILE.md"
|
|
141
|
+
cleanup() { rm -f "$PROMPT_FILE"; }
|
|
142
|
+
trap cleanup EXIT
|
|
143
|
+
|
|
144
|
+
echo "Fetching the onboarding prompt…"
|
|
145
|
+
download_failed() {
|
|
146
|
+
echo "Failed to download the onboarding prompt from $PROMPT_URL" >&2
|
|
147
|
+
echo "Check your connection and try again." >&2
|
|
148
|
+
exit 1
|
|
149
|
+
}
|
|
150
|
+
if command -v curl >/dev/null 2>&1; then
|
|
151
|
+
curl -fsSL "$PROMPT_URL" -o "$PROMPT_FILE" || download_failed
|
|
152
|
+
elif command -v wget >/dev/null 2>&1; then
|
|
153
|
+
wget -qO "$PROMPT_FILE" "$PROMPT_URL" || download_failed
|
|
154
|
+
else
|
|
155
|
+
echo "Need curl or wget to download the prompt." >&2
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
SEED="Read the file $PROMPT_FILE and follow it to set up Arize AX tracing in this project, walking me through each step and asking me questions as needed."
|
|
160
|
+
|
|
161
|
+
echo "Launching $(agent_label "$CHOSEN")…"
|
|
162
|
+
echo
|
|
163
|
+
|
|
164
|
+
# Launch interactive + seeded, on the real terminal. No skip-permissions —
|
|
165
|
+
# the agent's approval model and the prompt's own approval gate must stay intact.
|
|
166
|
+
# Run as a child (not exec) so the EXIT trap cleans up the temp prompt file.
|
|
167
|
+
run_agent() {
|
|
168
|
+
case "$CHOSEN" in
|
|
169
|
+
copilot|gemini) "$CHOSEN" -i "$SEED" ;;
|
|
170
|
+
*) "$CHOSEN" "$SEED" ;;
|
|
171
|
+
esac
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# Prefer inheriting stdin: TUI agents (e.g. codex/crossterm) need a read-write
|
|
175
|
+
# terminal on fd 0. A `< /dev/tty` redirect opens fd 0 read-only and makes
|
|
176
|
+
# crossterm panic ("reader source not set"). Only reach for /dev/tty when stdin
|
|
177
|
+
# isn't already a terminal (the discouraged `curl | bash`), and open it
|
|
178
|
+
# read-write with `<>`.
|
|
179
|
+
if [ -t 0 ]; then
|
|
180
|
+
run_agent
|
|
181
|
+
elif [ -r /dev/tty ]; then
|
|
182
|
+
run_agent <>/dev/tty
|
|
183
|
+
else
|
|
184
|
+
echo "No interactive terminal available to launch the agent." >&2
|
|
185
|
+
exit 1
|
|
186
|
+
fi
|
package/spawn-interactive.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import { openSync } from 'fs';
|
|
3
|
-
|
|
4
|
-
function tryOpenTTY() {
|
|
5
|
-
if (process.platform === 'win32') {
|
|
6
|
-
// On Windows, avoid CON device (issues in Node.js 22)
|
|
7
|
-
// Use isTTY check instead, and stdio: 'inherit' for spawning
|
|
8
|
-
if (process.stdin.isTTY && process.stdout.isTTY && process.stderr.isTTY) {
|
|
9
|
-
return { available: true, useInherit: true };
|
|
10
|
-
}
|
|
11
|
-
return { available: false };
|
|
12
|
-
} else {
|
|
13
|
-
// On macOS/Linux, use direct /dev/tty access
|
|
14
|
-
try {
|
|
15
|
-
const device = '/dev/tty';
|
|
16
|
-
openSync(device, 'r');
|
|
17
|
-
return { device, available: true, useInherit: false };
|
|
18
|
-
} catch (err) {
|
|
19
|
-
return { available: false };
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const tty = tryOpenTTY();
|
|
25
|
-
|
|
26
|
-
if (tty.available) {
|
|
27
|
-
const spawnOptions = tty.useInherit
|
|
28
|
-
? { stdio: 'inherit', shell: true }
|
|
29
|
-
: {
|
|
30
|
-
stdio: [
|
|
31
|
-
openSync(tty.device, 'r'),
|
|
32
|
-
openSync(tty.device, 'w'),
|
|
33
|
-
openSync(tty.device, 'w')
|
|
34
|
-
]
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const child = spawn('node', ['./cli.js'], spawnOptions);
|
|
38
|
-
|
|
39
|
-
child.on('close', (code) => {
|
|
40
|
-
process.exit(code || 0);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
child.on('error', (err) => {
|
|
44
|
-
console.error('Failed to start interactive CLI:', err.message);
|
|
45
|
-
console.log('Run `npx evals` to complete setup.');
|
|
46
|
-
process.exit(1);
|
|
47
|
-
});
|
|
48
|
-
} else {
|
|
49
|
-
console.log('Run `npx evals` to complete setup.');
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|