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 CHANGED
@@ -1,53 +1,55 @@
1
1
  # evals
2
2
 
3
- An npm package for Arize evals functionality.
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
- ## About Arize AI
5
+ ## Quick start
6
6
 
7
- Arize AI is a leading company in AI observability and evaluation, dedicated to ensuring that artificial intelligence systems operate reliably in real-world applications. Founded in 2019, Arize provides tools that help machine learning teams monitor, troubleshoot, and improve model performance across various domains, including structured data, computer vision, and large language models (LLMs).
8
-
9
- ## Arize Phoenix
7
+ ```bash
8
+ npx evals
9
+ ```
10
10
 
11
- **Arize Phoenix** is an open-source library designed for LLM tracing and evaluation. It enables developers to evaluate, experiment, and optimize AI products in real time. Key features include:
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
- - **Application Tracing**: Collects LLM application data with automatic instrumentation, providing comprehensive visibility into model operations
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
- Phoenix is built on OpenTelemetry, ensuring seamless setup, full transparency, and no vendor lock-in. It's perfect for teams who want to get started with LLM observability and evaluation in a fully local, open-source environment.
15
+ ### Without Node
18
16
 
19
- ## Arize AX
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
- **Arize AX** is the enterprise AI engineering platform that extends the capabilities of Phoenix, offering a comprehensive suite for development, evaluation, and observability. Key features include:
19
+ ```bash
20
+ # macOS / Linux
21
+ bash <(curl -fsSL https://cdn.jsdelivr.net/npm/evals/start.sh)
22
+ ```
22
23
 
23
- - **Prompts**: Tools for prompt engineering, including a prompt playground, prompt hub for management and versioning, and a prompt builder powered by AI
24
- - **Experiments**: Systematic A/B testing of prompts against large datasets to optimize model performance
25
- - **Tracing**: Detailed tracing capabilities to monitor and debug AI applications effectively
26
- - **Evaluation**: Comprehensive evaluation metrics and tools to assess model outputs and performance
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
- Arize AX is designed to support teams and organizations with larger data needs, providing robust support, collaboration features, and multiple deployment options including SaaS, Virtual Private Cloud (VPC), and Arize Private Connect.
29
+ When Node **is** present, these hand off to `npx evals` for the richer UI.
30
30
 
31
+ ## What it does
31
32
 
32
- ## Installation
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
- ```bash
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
- The post-install script will automatically launch the CLI interface.
39
+ ## Options
39
40
 
40
- ## Usage
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
- Run the CLI manually:
47
+ The shell launchers also accept `--agent <id>`.
43
48
 
44
- ```bash
45
- npm start
46
- ```
49
+ ## About Arize
47
50
 
48
- Or use the binary:
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
- ```bash
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
- // Diagnostic: Uncomment to verify script is running
11
- console.error('CLI script started, platform:', process.platform, 'isTTY:', process.stdin.isTTY);
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
- const OPTIONS = [
42
- {
43
- name: 'Evals Primer',
44
- subtext: 'Learn the Craft',
45
- url: 'https://arize.com/llm-evaluation/?utm_source=npmevals'
46
- },
47
- {
48
- name: 'Arize AX SaaS',
49
- subtext: 'Enterprise Agent Evaluation, Best in Class',
50
- url: 'https://arize.com/docs/ax?utm_source=npmevals'
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(url) {
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
- // Main App component
137
- function App() {
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 selectedName = OPTIONS[selectedIndex].name;
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 : OPTIONS.length - 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 < OPTIONS.length - 1 ? prev + 1 : 0));
243
+ setSelectedIndex(prev => (prev < items.length - 1 ? prev + 1 : 0));
177
244
  }
178
245
 
179
246
  if (key.return) {
180
- const selected = OPTIONS[selectedIndex];
181
- exit();
182
- setTimeout(() => {
183
- console.log(`\nOpening ${selected.url} in your browser...\n`);
184
- openUrlInBrowser(selected.url);
185
- }, 100);
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' }, 'Select an option:')
270
+ e(Text, { bold: true, color: 'white' }, heading)
193
271
  ),
194
272
  e(Box, { flexDirection: 'column', paddingX: 1 },
195
- ...OPTIONS.map((option, index) =>
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 cancel')
284
+ e(Text, { dimColor: true }, 'Enter to select · ↑↓ to navigate · Esc to quit')
207
285
  )
208
286
  );
209
287
  }
210
288
 
211
- // Run the app
212
- // Check if we have an interactive terminal (required for Ink)
213
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
214
- console.error('Error: This command requires an interactive terminal.');
215
- console.error('Please run `npx evals` from your terminal (not from a script or non-interactive environment).');
216
- process.exit(1);
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
- // Handle uncaught errors
220
- process.on('uncaughtException', (error) => {
221
- console.error('Uncaught exception:', error.message);
222
- console.error(error.stack);
223
- process.exit(1);
224
- });
225
-
226
- process.on('unhandledRejection', (reason, promise) => {
227
- console.error('Unhandled rejection at:', promise);
228
- console.error('Reason:', reason);
229
- process.exit(1);
230
- });
231
-
232
- // Ensure stdout is not buffered (important for Windows)
233
- if (process.stdout.isTTY) {
234
- process.stdout.setEncoding('utf8');
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
- try {
238
- render(e(App), { patchConsole: false });
239
- } catch (error) {
240
- console.error('Failed to render interactive CLI:', error.message);
241
- console.error(error.stack);
242
- if (process.platform === 'win32') {
243
- console.error('\nNote: If you\'re using cmd.exe, try running in PowerShell or Git Bash instead.');
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
- process.exit(1);
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.2.7",
4
- "description": "Arize evals package",
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": "echo \"run npx evals to complete setup\" && node spawn-interactive.js",
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
@@ -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
- }