@indemn/cli 1.2.0 → 1.2.2

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.
@@ -0,0 +1,509 @@
1
+ ---
2
+ name: evaluations
3
+ description: Evaluate Indemn AI agents — create rubrics and test sets, run evaluations, analyze results, and export reports. Use for any evaluation, testing, or quality assessment task.
4
+ ---
5
+
6
+ # Evaluations
7
+
8
+ Complete evaluation workflow for Indemn AI agents. Covers rubric creation, test set design, evaluation execution, result analysis, and reporting.
9
+
10
+ **Prerequisite:** `indemn whoami` must succeed. The `indemn` CLI must be installed and authenticated.
11
+
12
+ ---
13
+
14
+ ## Mode Detection
15
+
16
+ Check the FIRST LINE of the user's message:
17
+
18
+ **HEADLESS** -- Execute autonomously without asking questions. Optimize for completion.
19
+
20
+ **INTERACTIVE** -- Get explicit approval before every action that creates, modifies, or triggers anything. Approval gates: before creating anything, after creation (review?), before triggering a run, before modifying resources.
21
+
22
+ ---
23
+
24
+ ## Evaluation Harness Overview
25
+
26
+ The harness tests conversational AI agents against quality standards through two scoring layers:
27
+
28
+ | Layer | Source | Applies To | Purpose |
29
+ |-------|--------|------------|---------|
30
+ | **Success Criteria** | Embedded in each test item | That specific item | Did the agent handle THIS situation correctly? |
31
+ | **Rubric Rules** | Separate rubric document | ALL items universally | Does the agent meet baseline behavioral standards? |
32
+
33
+ An item passes only if BOTH layers pass. A high-severity rubric rule failure causes the entire item to fail.
34
+
35
+ ### Three Test Types
36
+
37
+ | Type | Input | What It Tests |
38
+ |------|-------|---------------|
39
+ | `single_turn` | A single message (new conversation, no prior context) | Specific directive compliance, edge cases |
40
+ | `scenario` | Persona + initial message + max turns | End-to-end workflow completion (chat agents) |
41
+ | `voice_simulation` | Persona + initial message + max turns (via LiveKit) | End-to-end voice workflow |
42
+
43
+ ---
44
+
45
+ ## The Directive Allocation Workflow
46
+
47
+ **Run this BEFORE creating rubric or test set.** This is the core method for deciding what goes where.
48
+
49
+ ### Step 1: Fetch Bot Context
50
+
51
+ ```bash
52
+ indemn eval bot-context <agent-id> --json
53
+ ```
54
+
55
+ Extract the system prompt, tools, and knowledge bases.
56
+
57
+ ### Step 2: Parse All Directives
58
+
59
+ Identify every ALWAYS/NEVER/MUST/CRITICAL directive from the system prompt. List them all.
60
+
61
+ ### Step 3: Apply the 3-Message Universal Applicability Test
62
+
63
+ For EACH directive, mentally test it against these three messages:
64
+
65
+ 1. **Greeting:** "Hello, I'm interested in wedding insurance"
66
+ 2. **Off-topic:** "Can you help me with my taxes?"
67
+ 3. **Utility:** "I need to correct my phone number"
68
+
69
+ Ask: "Would I check this directive against ALL THREE of these messages?"
70
+
71
+ ### Step 4: Classify Each Directive
72
+
73
+ | Classification | Rule | Where It Goes |
74
+ |---------------|------|---------------|
75
+ | **UNIVERSAL** | Passes the 3-message test | Rubric rule |
76
+ | **WORKFLOW-SPECIFIC** | Only applies in certain situations | Test set scenario success criteria |
77
+ | **AMBIGUOUS** | Unclear whether universal | Default to scenario criteria |
78
+
79
+ ### Step 5: Produce Allocation Table
80
+
81
+ ```
82
+ | Directive | Classification | Destination |
83
+ |-----------|---------------|-------------|
84
+ | "ALWAYS maintain professional tone" | UNIVERSAL | Rubric rule |
85
+ | "MUST collect DOB before quoting" | WORKFLOW-SPECIFIC | Scenario criteria |
86
+ | "NEVER provide medical advice" | UNIVERSAL | Rubric rule |
87
+ ```
88
+
89
+ ### Step 6: Create Artifacts Using the Allocation
90
+
91
+ - Create the rubric using ONLY universal directives (see Rubric Creation below)
92
+ - Create the test set mapping workflow-specific directives as scenario success criteria (see Test Set Creation below)
93
+
94
+ ---
95
+
96
+ ## Rubric Creation
97
+
98
+ Rubrics define the agent's universal behavioral contract -- rules checked against EVERY response.
99
+
100
+ ### Hard Cap: 3-6 Rules
101
+
102
+ NEVER exceed 6 rules. A tight rubric with precise universal rules is better than a broad one with overlapping or situational rules. If you find yourself writing more than 6, you are including workflow-specific rules that belong in the test set.
103
+
104
+ ### Process
105
+
106
+ 1. **Fetch bot context** (if not already done): `indemn eval bot-context <agent-id> --json`
107
+ 2. **Extract universal rules from the system prompt:**
108
+ - Persona and tone -- How must the agent communicate in every response?
109
+ - Universal prohibitions (NEVER/DO NOT) -- Things the agent must never do in ANY response
110
+ - Universal requirements (ALWAYS/MUST) -- Things the agent must do in EVERY response
111
+ 3. **Add standard quality baselines** (include each unless a prompt-derived rule already covers it):
112
+ - No hallucination: Does not fabricate facts, statistics, or capabilities
113
+ - Stays in scope: Operates within its defined role
114
+ - No harmful content: Does not produce harmful, dangerous, or inappropriate content
115
+ - Response coherence: Responses are clear, grammatically correct, and logically structured
116
+ 4. **Verify each rule passes the 3-message self-check** (greeting, off-topic, utility)
117
+ 5. **Write JSON and create:**
118
+ ```bash
119
+ indemn rubric create --name "<Agent Name> Rubric" --agent-id <agent-id> --file /tmp/rubric.json
120
+ ```
121
+
122
+ ### Rubric JSON Format
123
+
124
+ ```json
125
+ {
126
+ "rules": [
127
+ {
128
+ "id": "SNAKE_CASE_ID",
129
+ "name": "Human-readable name",
130
+ "severity": "high | medium | low",
131
+ "category": "persona | instruction_compliance | safety | response_quality",
132
+ "description": "What this rule checks and why it matters",
133
+ "component_scope": "prompt | general | function | knowledge_base",
134
+ "component_ids": null,
135
+ "component_names": null,
136
+ "evaluation_criteria": {
137
+ "pass_conditions": ["Observable condition that indicates compliance"],
138
+ "fail_conditions": ["Observable condition that indicates violation"]
139
+ }
140
+ }
141
+ ]
142
+ }
143
+ ```
144
+
145
+ **component_scope values:**
146
+
147
+ | Scope | Meaning | Example |
148
+ |-------|---------|---------|
149
+ | `prompt` | Derived from a system prompt directive | "NEVER provide medical advice" |
150
+ | `general` | Standard quality/safety baseline | No hallucination, response coherence |
151
+ | `function` | Informed by the agent's tool capabilities | "Does not claim capabilities it doesn't have" |
152
+ | `knowledge_base` | Informed by the agent's KBs | "Grounds information rather than fabricating" |
153
+
154
+ ### Severity Assignment
155
+
156
+ ```
157
+ Could violation cause harm to the user? -> HIGH
158
+ Does the prompt use ALWAYS/NEVER/MUST for this? -> HIGH
159
+ Could violation severely damage trust? -> HIGH
160
+ Does it significantly degrade response quality? -> MEDIUM
161
+ Is it a tone, style, or formatting preference? -> LOW
162
+ ```
163
+
164
+ Target distribution: High 20-30%, Medium 40-50%, Low 20-30%.
165
+
166
+ ### Anti-Patterns
167
+
168
+ - Do NOT derive rubric rules from tool descriptions -- tools inform domain understanding but tool-specific behaviors are workflow-specific by definition
169
+ - Do NOT include workflow-specific rules (e.g., "collects DOB before quoting")
170
+ - Do NOT write vague conditions -- every pass/fail condition must be observable from the agent's response text
171
+
172
+ ---
173
+
174
+ ## Test Set Creation
175
+
176
+ Test sets verify that an agent can do its job -- following its workflow, using its tools, handling real-world user behavior.
177
+
178
+ ### Size Guidance
179
+
180
+ Aim for roughly **15 items total**. A focused test set with 12-18 precise items is far more useful than 40+ redundant ones. Consolidate items that test the same behavior.
181
+
182
+ ### Process
183
+
184
+ 1. **Fetch bot context** (if not already done): `indemn eval bot-context <agent-id> --json`
185
+ 2. **Detect voice vs chat agent** -- Check for voice indicators in tools (voice, telephony, livekit, twilio, call, phone) or system prompt. If detected, use `voice_simulation` instead of `scenario` for multi-turn items.
186
+ 3. **Analyze workflow depth for max_turns:**
187
+
188
+ | Workflow Steps | Recommended max_turns |
189
+ |---------------|----------------------|
190
+ | 2-3 steps | 4-6 turns |
191
+ | 4-5 steps | 6-8 turns |
192
+ | 6+ steps | 8-12 turns |
193
+
194
+ 4. **Create multi-turn scenarios** (primary content) for each major workflow path with varied personas:
195
+ - **Happy path** -- Cooperative user following the workflow naturally
196
+ - **Confused user** -- Misunderstands questions, gives unclear answers
197
+ - **Resistant user** -- Pushes back on providing information
198
+ - **Incomplete information** -- Gives partial answers
199
+ - **Scope boundary** -- Starts in-scope, drifts out of domain
200
+ 5. **Map workflow-specific directives to success criteria** on relevant scenarios
201
+ 6. **Create single-turn diagnostics** for ALWAYS/NEVER directives testable standalone, edge cases, and tool outcome tests
202
+ 7. **Write JSON and create:**
203
+ ```bash
204
+ indemn testset create --name "<Agent Name> Test Set" --agent-id <agent-id> --file /tmp/testset.json
205
+ ```
206
+
207
+ ### Test Set JSON: Scenario
208
+
209
+ ```json
210
+ {
211
+ "type": "scenario",
212
+ "name": "Descriptive name - what this tests",
213
+ "inputs": {
214
+ "persona": "You are a [type] who [traits, emotional state, behavior pattern, goal].",
215
+ "initial_message": "Natural first message matching the persona",
216
+ "max_turns": 8
217
+ },
218
+ "expected": {
219
+ "success_criteria": ["Criterion about the conversation outcome"],
220
+ "expected_outcome": "What the conversation should achieve"
221
+ },
222
+ "tags": ["scenario", "category"],
223
+ "priority": "high"
224
+ }
225
+ ```
226
+
227
+ ### Test Set JSON: Voice Simulation
228
+
229
+ Identical to scenario except `"type": "voice_simulation"`. The harness routes these through LiveKit with TTS/STT.
230
+
231
+ ```json
232
+ {
233
+ "type": "voice_simulation",
234
+ "name": "Descriptive name - what this tests",
235
+ "inputs": {
236
+ "persona": "You are a caller who [traits]. You are on a phone call -- keep responses brief and natural.",
237
+ "initial_message": "Natural first utterance",
238
+ "max_turns": 6
239
+ },
240
+ "expected": {
241
+ "success_criteria": ["Criterion about the conversation outcome"],
242
+ "expected_outcome": "What the call should achieve"
243
+ },
244
+ "tags": ["voice_simulation", "category"],
245
+ "priority": "high"
246
+ }
247
+ ```
248
+
249
+ Voice-specific: personas should behave like phone callers (shorter utterances, less formality). max_turns generally shorter (4-6 simple, 6-8 complex). Do NOT create single_turn items for voice agents.
250
+
251
+ ### Test Set JSON: Single Turn
252
+
253
+ **Single-turn tests start a BRAND NEW conversation with NO prior context.** Design messages that are standalone.
254
+
255
+ ```json
256
+ {
257
+ "type": "single_turn",
258
+ "name": "Descriptive name - what this tests",
259
+ "inputs": {
260
+ "message": "The standalone message to send"
261
+ },
262
+ "expected": {
263
+ "success_criteria": ["Specific observable criterion"]
264
+ },
265
+ "tags": ["category"],
266
+ "priority": "high"
267
+ }
268
+ ```
269
+
270
+ **CRITICAL:** The `inputs` object for single-turn items contains ONLY `message`. No `max_turns`, `persona`, or `initial_message`.
271
+
272
+ ### Success Criteria Rules
273
+
274
+ Every criterion MUST be observable from the agent's output:
275
+
276
+ - GOOD: "Agent asks for the user's date of birth before presenting a quote"
277
+ - GOOD: "Response contains specific policy coverage details, not generic information"
278
+ - BAD: "Agent uses the knowledge base search tool" (mechanism, not outcome)
279
+ - BAD: "Agent handles the request appropriately" (vague)
280
+
281
+ Rules: test ONE specific behavior, use concrete verbs ("asks for", "provides", "does not", "contains", "redirects"), 2-4 criteria per item, do NOT duplicate rubric rules as criteria.
282
+
283
+ ### Session Init Limitation
284
+
285
+ Scenarios start as fresh conversations with no pre-existing session state. Do NOT create items that depend on session-initialized data. Note session-dependent behaviors as coverage gaps in the completion report.
286
+
287
+ ### Priority Assignment
288
+
289
+ - **high**: Core workflow completion, safety, explicit prompt directives
290
+ - **medium**: Secondary behaviors, edge cases, tool outcomes
291
+ - **low**: Style preferences, formatting, nice-to-have behaviors
292
+
293
+ ---
294
+
295
+ ## Running Evaluations
296
+
297
+ ### Trigger a Run
298
+
299
+ ```bash
300
+ indemn eval run <agent-id> --test-set <id> --rubric <id> --wait
301
+ ```
302
+
303
+ The `--wait` flag polls until completion and shows a pass/fail summary.
304
+
305
+ **Key flags:**
306
+
307
+ | Flag | Purpose | Notes |
308
+ |------|---------|-------|
309
+ | `--wait` | Poll until complete, show summary | Recommended for all runs |
310
+ | `--concurrency <n>` | Parallel items | Use `1` for voice_simulation |
311
+ | `--eval-model <model>` | Evaluator LLM (e.g., "gpt-4o") | Overrides default judge model |
312
+ | `--keep` | Preserve intermediate artifacts | Useful for debugging |
313
+ | `--limit <n>` | Run only first N items | Good for quick smoke tests |
314
+ | `--component-scope <scope>` | Filter by component scope | prompt, function, knowledge_base, general |
315
+ | `--component-ids <ids>` | Filter by component IDs | Comma-separated |
316
+ | `--conversation-ids <ids>` | Filter by conversation IDs | Comma-separated |
317
+ | `--mode <mode>` | Evaluation mode | Use "transcript" for transcript-based evaluation |
318
+
319
+ ### Check Status
320
+
321
+ ```bash
322
+ indemn eval status <run-id> [--json]
323
+ ```
324
+
325
+ ### List Runs
326
+
327
+ ```bash
328
+ indemn eval list [--agent-id <id>] [--json] [--limit <n>] [--page <n>]
329
+ ```
330
+
331
+ ---
332
+
333
+ ## Analyzing Results
334
+
335
+ ### Step 1: Resolve the Run
336
+
337
+ - **UUID format** (contains hyphens, 36 chars) -- it is a `run_id`. Use directly.
338
+ - **ObjectId format** (24 hex chars) -- it is an `agent_id`. Fetch the latest completed run:
339
+ ```bash
340
+ indemn eval list --agent-id <agent-id> --json --limit 5
341
+ ```
342
+
343
+ ### Step 2: Pull All Data
344
+
345
+ ```bash
346
+ indemn eval status <run-id> --json
347
+ indemn eval results <run-id> --json
348
+ indemn rubric get <rubric-id> --json
349
+ indemn testset get <test-set-id> --json
350
+ indemn eval bot-context <agent-id> --json
351
+ ```
352
+
353
+ For exact response shapes, see `references/data-shapes.md`.
354
+
355
+ ### Step 3: Present Run Overview
356
+
357
+ ```
358
+ Run: {run_id}
359
+ Agent: {bot_name} ({agent_id})
360
+ LLM: {bot_llm_provider}/{bot_llm_model}
361
+ Status: {passed}/{total} items passed ({pass_rate}%)
362
+
363
+ Criteria: {criteria_passed}/{criteria_total} checks passed
364
+ Rubric: {rubric_rules_passed}/{rubric_rules_total} rules passed
365
+
366
+ Component Scores:
367
+ prompt: {score}% ({passed}/{total})
368
+ knowledge_base: {score}% ({passed}/{total})
369
+ function: {score}% ({passed}/{total})
370
+ general: {score}% ({passed}/{total})
371
+ ```
372
+
373
+ ### Step 4: Analyze Each Failed Item
374
+
375
+ For every result where `passed == false`:
376
+
377
+ 1. **Get the test item** -- match `test_case_id` to the test set's `items[].item_id` for name, type, inputs, success_criteria
378
+ 2. **Check criteria scores** -- from `criteria_scores[]`, list which passed/failed with reasoning
379
+ 3. **Check rubric scores** -- from `rubric_scores[]`, list which passed/failed with reasoning
380
+ 4. **Check for divergence** -- flag when `criteria_passed != rubric_passed`. Divergence is a strong signal of an evaluation problem.
381
+ 5. **Read the conversation** -- look at `output` field for the full response or transcript
382
+ 6. **Cross-reference system prompt** -- check if the agent's behavior aligns with prompt instructions
383
+ 7. **Classify** into one of four categories:
384
+
385
+ | Category | Signal | Action |
386
+ |----------|--------|--------|
387
+ | **Real Agent Problem** | Both layers agree agent misbehaved, or output contradicts prompt | Investigate agent code/prompt |
388
+ | **Evaluation Problem** | Criteria ambiguous/too strict, evaluator reasoning shows misunderstanding | Update test set criteria |
389
+ | **Test Design Problem** | Test assumptions wrong (e.g., assumed KB lacked info it has) | Redesign test item |
390
+ | **KB/Retrieval Problem** | Agent behavior correct but retrieval returned wrong content | Fix KB content or retrieval config |
391
+
392
+ ### Divergence Analysis
393
+
394
+ When `criteria_passed != rubric_passed` for an item, this is a first-class finding:
395
+
396
+ - **Criteria FAIL + Rubric PASS** -- criteria are likely too strict or testing the wrong thing
397
+ - **Criteria PASS + Rubric FAIL** -- rubric may have rules that don't apply to this scenario, or agent violated a behavioral rule despite answering correctly
398
+
399
+ Always highlight divergences prominently.
400
+
401
+ ### Step 5: Aggregate and Recommend
402
+
403
+ Group failures by category. Prioritize fixes:
404
+
405
+ - **P1 -- Evaluation fixes** (false failures mask real issues): specific criteria rewrites or rubric rule adjustments
406
+ - **P2 -- Agent investigation** (real bugs): which code paths or prompt sections to examine
407
+ - **P3 -- KB/Retrieval fixes**: which KBs need content updates or retrieval tuning
408
+
409
+ For each recommendation, quote the current criterion/rule, explain what is wrong, and propose the exact replacement text.
410
+
411
+ ### Step 6: Apply Fixes
412
+
413
+ Updates create new versions (non-destructive, previous versions preserved):
414
+
415
+ ```bash
416
+ indemn testset update <test-set-id> --file updated-testset.json
417
+ indemn rubric update <rubric-id> --file updated-rubric.json
418
+ ```
419
+
420
+ Check version history: `indemn testset versions <id>` / `indemn rubric versions <id>`
421
+
422
+ After applying fixes, re-run:
423
+ ```bash
424
+ indemn eval run <agent-id> --test-set <id> --rubric <id> --wait
425
+ ```
426
+
427
+ ---
428
+
429
+ ## Exporting Results
430
+
431
+ ### Markdown Export
432
+
433
+ ```bash
434
+ indemn eval export <run-id> [--output results.md]
435
+ ```
436
+
437
+ Produces a structured markdown file with all conversations, criteria scores, and rubric scores.
438
+
439
+ ### PDF Report
440
+
441
+ ```bash
442
+ indemn eval report <run-id> [--output report.pdf]
443
+ ```
444
+
445
+ Produces a branded Indemn PDF with cover page, dual scores (criteria + rubric), and per-item detail pages.
446
+
447
+ ---
448
+
449
+ ## Workflows
450
+
451
+ ### A: New Evaluation (End-to-End)
452
+
453
+ 1. Gather intent -- which agent, any focus areas
454
+ 2. Fetch bot context: `indemn eval bot-context <agent-id> --json`
455
+ 3. Run directive allocation (Steps 1-6 above)
456
+ 4. Create rubric from universal directives: `indemn rubric create --name "<name>" --agent-id <id> --file /tmp/rubric.json`
457
+ 5. Create test set with workflow-specific criteria: `indemn testset create --name "<name>" --agent-id <id> --file /tmp/testset.json`
458
+ 6. Review (interactive: wait for approval)
459
+ 7. Run: `indemn eval run <id> --test-set <ts-id> --rubric <r-id> --wait` (add `--concurrency 1` for voice_simulation)
460
+ 8. Analyze results: `indemn eval results <run-id> --json`
461
+
462
+ ### B: Re-run with Existing Resources
463
+
464
+ 1. List existing: `indemn rubric list --agent-id <id>`, `indemn testset list --agent-id <id>`
465
+ 2. Run: `indemn eval run <id> --test-set <ts-id> --rubric <r-id> --wait`
466
+
467
+ ### C: Update Existing Resources
468
+
469
+ 1. Get current: `indemn rubric get <id> --json` / `indemn testset get <id> --json`
470
+ 2. Write updated JSON to file
471
+ 3. Update: `indemn rubric update <id> --file /tmp/updated.json` / `indemn testset update <id> --file /tmp/updated.json`
472
+
473
+ ### D: Analyze Results Only
474
+
475
+ 1. Resolve run (UUID or agent-id)
476
+ 2. Follow the Analyzing Results workflow above
477
+
478
+ ### E: Export/Share Results
479
+
480
+ 1. Markdown: `indemn eval export <run-id> [--output path.md]`
481
+ 2. PDF: `indemn eval report <run-id> [--output path.pdf]`
482
+
483
+ ---
484
+
485
+ ## CLI Reference
486
+
487
+ | Command | Purpose |
488
+ |---------|---------|
489
+ | `indemn eval bot-context <id> [--json]` | Get agent prompt, tools, KBs, LLM config |
490
+ | `indemn eval run <id> --test-set <id> [--rubric <id>] [--wait]` | Trigger an evaluation run |
491
+ | `indemn eval list [--agent-id <id>] [--json] [--limit] [--page]` | List evaluation runs |
492
+ | `indemn eval status <run-id> [--json]` | Get run status and summary |
493
+ | `indemn eval results <run-id> [--json]` | Get per-item detailed results |
494
+ | `indemn eval export <run-id> [--output <path>]` | Export results as markdown |
495
+ | `indemn eval report <run-id> [--output <path>]` | Export results as branded PDF |
496
+ | `indemn rubric create [--file <path>] [--name "<name>"] [--agent-id <id>]` | Create rubric (--file or --name required) |
497
+ | `indemn rubric get <id> [--json] [--revision <n>]` | Get rubric details |
498
+ | `indemn rubric list [--agent-id <id>] [--json] [--limit] [--page]` | List rubrics |
499
+ | `indemn rubric update <id> [--file <path>] [--name "<name>"]` | Update rubric (creates new version) |
500
+ | `indemn rubric delete <id>` | Delete rubric (requires confirmation) |
501
+ | `indemn rubric versions <id> [--json]` | List rubric version history |
502
+ | `indemn testset create [--file <path>] [--name "<name>"] [--agent-id <id>]` | Create test set (--file or --name required) |
503
+ | `indemn testset get <id> [--json] [--revision <n>]` | Get test set details |
504
+ | `indemn testset list [--agent-id <id>] [--json] [--limit] [--page]` | List test sets (always use --agent-id) |
505
+ | `indemn testset update <id> [--file <path>] [--name "<name>"]` | Update test set (creates new version) |
506
+ | `indemn testset delete <id>` | Delete test set (requires confirmation) |
507
+ | `indemn testset versions <id> [--json]` | List test set version history |
508
+ | `indemn config get <id> --field prompt` | Get just the system prompt |
509
+ | `indemn functions list <id>` | List agent's functions |
package/src/cli/auth.ts CHANGED
@@ -2,6 +2,7 @@ import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import ora from 'ora';
4
4
  import * as readline from 'readline';
5
+ import { Writable } from 'node:stream';
5
6
  import { login, logout, whoami } from '../sdk/auth.js';
6
7
  import type { Environment } from '../sdk/types.js';
7
8
  import { printError, printSuccess, printJson } from './utils/output.js';
@@ -21,52 +22,20 @@ function prompt(question: string): Promise<string> {
21
22
 
22
23
  function promptPassword(question: string): Promise<string> {
23
24
  return new Promise((resolve) => {
25
+ const mutedOutput = new Writable({
26
+ write(_chunk, _encoding, callback) { callback(); },
27
+ });
24
28
  const rl = readline.createInterface({
25
29
  input: process.stdin,
26
- output: process.stdout,
30
+ output: mutedOutput,
31
+ terminal: true,
27
32
  });
28
-
29
- // Disable echoing by writing to stdout manually
30
33
  process.stdout.write(question);
31
- const stdin = process.stdin;
32
- const wasRaw = stdin.isRaw;
33
-
34
- if (stdin.isTTY) {
35
- stdin.setRawMode(true);
36
- }
37
-
38
- let password = '';
39
- const onData = (char: Buffer) => {
40
- const c = char.toString('utf8');
41
- if (c === '\n' || c === '\r' || c === '\u0004') {
42
- if (stdin.isTTY) {
43
- stdin.setRawMode(wasRaw ?? false);
44
- }
45
- stdin.removeListener('data', onData);
46
- process.stdout.write('\n');
47
- rl.close();
48
- resolve(password);
49
- } else if (c === '\u0003') {
50
- // Ctrl+C
51
- if (stdin.isTTY) {
52
- stdin.setRawMode(wasRaw ?? false);
53
- }
54
- stdin.removeListener('data', onData);
55
- rl.close();
56
- process.exit(1);
57
- } else if (c === '\u007f' || c === '\b') {
58
- // Backspace
59
- if (password.length > 0) {
60
- password = password.slice(0, -1);
61
- process.stdout.write('\b \b');
62
- }
63
- } else {
64
- password += c;
65
- process.stdout.write('*');
66
- }
67
- };
68
-
69
- stdin.on('data', onData);
34
+ rl.question('', (answer) => {
35
+ process.stdout.write('\n');
36
+ rl.close();
37
+ resolve(answer);
38
+ });
70
39
  });
71
40
  }
72
41
 
@@ -74,10 +43,10 @@ export function registerAuthCommands(program: Command): void {
74
43
  program
75
44
  .command('login')
76
45
  .description('Authenticate with Indemn platform')
77
- .option('--env <environment>', 'Target environment', 'dev')
46
+ .option('--env <environment>', 'Target environment')
78
47
  .action(async (options) => {
79
48
  try {
80
- const env = options.env as Environment;
49
+ const env = (options.env || process.env.INDEMN_ENV || 'dev') as Environment;
81
50
  if (env !== 'dev' && env !== 'production') {
82
51
  printError('Environment must be "dev" or "production"');
83
52
  process.exit(1);
@@ -116,7 +85,12 @@ export function registerAuthCommands(program: Command): void {
116
85
  console.log(chalk.dim(` API Key: ${result.apiKey.substring(0, 12)}...`));
117
86
  } catch (err) {
118
87
  spinner.fail('Authentication failed');
119
- throw err;
88
+ if (err instanceof Error) {
89
+ printError(err.message);
90
+ } else {
91
+ printError(err);
92
+ }
93
+ process.exit(1);
120
94
  }
121
95
  } catch (err) {
122
96
  printError(err);
package/src/cli/index.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { cpSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';
3
6
  import { registerAuthCommands } from './auth.js';
4
7
  import { registerAgentCommands } from './agents.js';
5
8
  import { registerConfigCommands } from './config.js';
@@ -65,4 +68,53 @@ program
65
68
  } catch (err) { printError(err); process.exit(1); }
66
69
  });
67
70
 
71
+ // Init command — set up Claude Code integration
72
+ program
73
+ .command('init')
74
+ .description('Set up Claude Code skills in the current project')
75
+ .option('--force', 'Overwrite existing files')
76
+ .action(async (options) => {
77
+ const __filename = fileURLToPath(import.meta.url);
78
+ const __dirname = dirname(__filename);
79
+ const packageRoot = resolve(__dirname, '..', '..');
80
+ const skillsSource = join(packageRoot, 'skills');
81
+ const cwd = process.cwd();
82
+ const skillsTarget = join(cwd, '.claude', 'skills', 'indemn');
83
+ const rulesTarget = join(cwd, '.claude', 'rules');
84
+
85
+ if (existsSync(skillsTarget) && !options.force) {
86
+ console.log('Indemn skills already installed. Use --force to overwrite.');
87
+ return;
88
+ }
89
+
90
+ // Copy skills
91
+ mkdirSync(skillsTarget, { recursive: true });
92
+ cpSync(skillsSource, skillsTarget, { recursive: true });
93
+
94
+ // Write rules file
95
+ mkdirSync(rulesTarget, { recursive: true });
96
+ const rulesPath = join(rulesTarget, 'indemn.md');
97
+ if (!existsSync(rulesPath) || options.force) {
98
+ writeFileSync(rulesPath, [
99
+ '# Indemn CLI',
100
+ '',
101
+ 'The `indemn` CLI is available for managing AI agents. Run `indemn whoami` to verify authentication.',
102
+ '',
103
+ '## Skills',
104
+ '',
105
+ '- **agent-builder** — Create, configure, and test agents (prompts, functions, knowledge bases, chat)',
106
+ '- **evaluations** — Create rubrics and test sets, run evaluations, analyze results, export reports',
107
+ '',
108
+ 'Use these skills for any agent management or evaluation task. They contain the complete command reference.',
109
+ '',
110
+ ].join('\n'));
111
+ }
112
+
113
+ console.log('Indemn CLI integration set up:');
114
+ console.log(` Skills: ${skillsTarget}/`);
115
+ console.log(` Rules: ${rulesPath}`);
116
+ console.log('');
117
+ console.log('Claude Code will now discover the indemn skills automatically.');
118
+ });
119
+
68
120
  program.parse();