@indemn/cli 1.0.0 → 1.1.1

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.
Files changed (106) hide show
  1. package/.claude-plugin/marketplace.json +28 -0
  2. package/README.md +29 -23
  3. package/dist/cli/agents.d.ts.map +1 -1
  4. package/dist/cli/agents.js +20 -0
  5. package/dist/cli/agents.js.map +1 -1
  6. package/dist/cli/eval.d.ts.map +1 -1
  7. package/dist/cli/eval.js +38 -0
  8. package/dist/cli/eval.js.map +1 -1
  9. package/dist/cli/index.js +1 -1
  10. package/dist/mcp/tools.d.ts.map +1 -1
  11. package/dist/mcp/tools.js +40 -0
  12. package/dist/mcp/tools.js.map +1 -1
  13. package/dist/report/agent-card/cover.d.ts +20 -0
  14. package/dist/report/agent-card/cover.d.ts.map +1 -0
  15. package/dist/report/agent-card/cover.js +58 -0
  16. package/dist/report/agent-card/cover.js.map +1 -0
  17. package/dist/report/agent-card/document.d.ts +7 -0
  18. package/dist/report/agent-card/document.d.ts.map +1 -0
  19. package/dist/report/agent-card/document.js +144 -0
  20. package/dist/report/agent-card/document.js.map +1 -0
  21. package/dist/report/agent-card/types.d.ts +77 -0
  22. package/dist/report/agent-card/types.d.ts.map +1 -0
  23. package/dist/report/agent-card/types.js +3 -0
  24. package/dist/report/agent-card/types.js.map +1 -0
  25. package/dist/report/assets/Indemn_PrimaryLogo_Iris.png +0 -0
  26. package/dist/report/assets/assets/Indemn_PrimaryLogo_Iris.png +0 -0
  27. package/dist/report/assets/assets/fonts/Barlow-Bold.ttf +0 -0
  28. package/dist/report/assets/assets/fonts/Barlow-Medium.ttf +0 -0
  29. package/dist/report/assets/assets/fonts/Barlow-Regular.ttf +0 -0
  30. package/dist/report/assets/assets/fonts/Barlow-SemiBold.ttf +0 -0
  31. package/dist/report/assets/fonts/Barlow-Bold.ttf +0 -0
  32. package/dist/report/assets/fonts/Barlow-Medium.ttf +0 -0
  33. package/dist/report/assets/fonts/Barlow-Regular.ttf +0 -0
  34. package/dist/report/assets/fonts/Barlow-SemiBold.ttf +0 -0
  35. package/dist/report/eval-report/cover.d.ts +16 -0
  36. package/dist/report/eval-report/cover.d.ts.map +1 -0
  37. package/dist/report/eval-report/cover.js +8 -0
  38. package/dist/report/eval-report/cover.js.map +1 -0
  39. package/dist/report/eval-report/document.d.ts +19 -0
  40. package/dist/report/eval-report/document.d.ts.map +1 -0
  41. package/dist/report/eval-report/document.js +32 -0
  42. package/dist/report/eval-report/document.js.map +1 -0
  43. package/dist/report/eval-report/example-page.d.ts +11 -0
  44. package/dist/report/eval-report/example-page.d.ts.map +1 -0
  45. package/dist/report/eval-report/example-page.js +18 -0
  46. package/dist/report/eval-report/example-page.js.map +1 -0
  47. package/dist/report/eval-report/item-page.d.ts +9 -0
  48. package/dist/report/eval-report/item-page.d.ts.map +1 -0
  49. package/dist/report/eval-report/item-page.js +72 -0
  50. package/dist/report/eval-report/item-page.js.map +1 -0
  51. package/dist/report/eval-report/matrix-page.d.ts +8 -0
  52. package/dist/report/eval-report/matrix-page.d.ts.map +1 -0
  53. package/dist/report/eval-report/matrix-page.js +31 -0
  54. package/dist/report/eval-report/matrix-page.js.map +1 -0
  55. package/dist/report/eval-report/types.d.ts +69 -0
  56. package/dist/report/eval-report/types.d.ts.map +1 -0
  57. package/dist/report/eval-report/types.js +7 -0
  58. package/dist/report/eval-report/types.js.map +1 -0
  59. package/dist/report/eval-report/v2-cover.d.ts +9 -0
  60. package/dist/report/eval-report/v2-cover.d.ts.map +1 -0
  61. package/dist/report/eval-report/v2-cover.js +32 -0
  62. package/dist/report/eval-report/v2-cover.js.map +1 -0
  63. package/dist/report/export.d.ts +17 -0
  64. package/dist/report/export.d.ts.map +1 -0
  65. package/dist/report/export.js +327 -0
  66. package/dist/report/export.js.map +1 -0
  67. package/dist/report/generate.d.ts +7 -0
  68. package/dist/report/generate.d.ts.map +1 -0
  69. package/dist/report/generate.js +30 -0
  70. package/dist/report/generate.js.map +1 -0
  71. package/dist/report/scoring.d.ts +90 -0
  72. package/dist/report/scoring.d.ts.map +1 -0
  73. package/dist/report/scoring.js +174 -0
  74. package/dist/report/scoring.js.map +1 -0
  75. package/dist/report/styles.d.ts +111 -0
  76. package/dist/report/styles.d.ts.map +1 -0
  77. package/dist/report/styles.js +134 -0
  78. package/dist/report/styles.js.map +1 -0
  79. package/docs/plans/2026-03-13-cli-exports.md +819 -0
  80. package/package.json +14 -4
  81. package/skills/eval-analysis/SKILL.md +178 -0
  82. package/skills/eval-analysis/references/data-shapes.md +279 -0
  83. package/src/cli/agents.ts +22 -0
  84. package/src/cli/eval.ts +42 -0
  85. package/src/cli/index.ts +1 -1
  86. package/src/mcp/tools.ts +50 -0
  87. package/src/report/agent-card/cover.tsx +232 -0
  88. package/src/report/agent-card/document.tsx +582 -0
  89. package/src/report/agent-card/types.ts +86 -0
  90. package/src/report/assets/Indemn_PrimaryLogo_Iris.png +0 -0
  91. package/src/report/assets/fonts/Barlow-Bold.ttf +0 -0
  92. package/src/report/assets/fonts/Barlow-Medium.ttf +0 -0
  93. package/src/report/assets/fonts/Barlow-Regular.ttf +0 -0
  94. package/src/report/assets/fonts/Barlow-SemiBold.ttf +0 -0
  95. package/src/report/eval-report/cover.tsx +69 -0
  96. package/src/report/eval-report/document.tsx +105 -0
  97. package/src/report/eval-report/example-page.tsx +121 -0
  98. package/src/report/eval-report/item-page.tsx +211 -0
  99. package/src/report/eval-report/matrix-page.tsx +124 -0
  100. package/src/report/eval-report/types.ts +77 -0
  101. package/src/report/eval-report/v2-cover.tsx +102 -0
  102. package/src/report/export.ts +394 -0
  103. package/src/report/generate.tsx +37 -0
  104. package/src/report/scoring.ts +281 -0
  105. package/src/report/styles.ts +138 -0
  106. package/tsconfig.json +2 -1
package/package.json CHANGED
@@ -1,38 +1,48 @@
1
1
  {
2
2
  "name": "@indemn/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "CLI, MCP server, and plugin for managing Indemn AI agents",
5
5
  "type": "module",
6
6
  "main": "./dist/sdk/index.js",
7
7
  "bin": {
8
- "indemn": "./dist/cli/index.js"
8
+ "indemn": "dist/cli/index.js"
9
9
  },
10
10
  "scripts": {
11
11
  "build": "tsc",
12
+ "postbuild": "cp -r src/report/assets dist/report/assets",
12
13
  "dev": "tsx --watch src/cli/index.ts",
13
14
  "test": "vitest run",
14
15
  "test:watch": "vitest"
15
16
  },
16
- "keywords": ["ai-agents", "insurance", "evaluations", "indemn", "mcp"],
17
+ "keywords": [
18
+ "ai-agents",
19
+ "insurance",
20
+ "evaluations",
21
+ "indemn",
22
+ "mcp"
23
+ ],
17
24
  "author": "Indemn <support@indemn.com>",
18
25
  "license": "MIT",
19
26
  "repository": {
20
27
  "type": "git",
21
- "url": "https://github.com/indemn-ai/indemn-cli"
28
+ "url": "git+https://github.com/indemn-ai/indemn-cli.git"
22
29
  },
23
30
  "dependencies": {
24
31
  "@modelcontextprotocol/sdk": "^1.25.1",
32
+ "@react-pdf/renderer": "^4.3.2",
25
33
  "chalk": "^5.4.1",
26
34
  "cli-table3": "^0.6.5",
27
35
  "commander": "^13.1.0",
28
36
  "conf": "^13.1.0",
29
37
  "firebase": "^11.6.0",
30
38
  "ora": "^8.2.0",
39
+ "react": "^19.2.4",
31
40
  "socket.io-client": "^4.8.1",
32
41
  "zod": "^3.24.0"
33
42
  },
34
43
  "devDependencies": {
35
44
  "@types/node": "^22.15.0",
45
+ "@types/react": "^19.2.14",
36
46
  "tsx": "^4.19.0",
37
47
  "typescript": "^5.9.3",
38
48
  "vitest": "^3.1.0"
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: eval-analysis
3
+ description: Analyze evaluation results to classify failures as agent issues vs evaluation issues. Also export conversation traces for sharing. Use when the user asks to analyze eval results, understand why tests failed, triage failures, improve evaluation scores, pull conversation traces, or share eval results.
4
+ argument-hint: [run-id or agent-id]
5
+ allowed-tools: Bash(indemn *)
6
+ ---
7
+
8
+ # Eval Analysis
9
+
10
+ Analyze evaluation run results, classify each failure, and recommend fixes. Also supports exporting conversation traces and generating branded PDF reports.
11
+
12
+ **Input:** `$ARGUMENTS` — a run_id (UUID) or agent_id (MongoDB ObjectId)
13
+
14
+ ## Step 1: Resolve the Run
15
+
16
+ Determine what `$ARGUMENTS` is:
17
+ - **UUID format** (contains hyphens, 36 chars) → it's a `run_id`. Use directly.
18
+ - **ObjectId format** (24 hex chars) → it's an `agent_id`. Fetch the latest completed run:
19
+ ```bash
20
+ indemn eval list --agent-id $ARGUMENTS --json --limit 5
21
+ ```
22
+ Extract the latest `run_id` with `status: completed`.
23
+
24
+ If no completed runs exist, tell the user and stop.
25
+
26
+ ## Step 2: Pull All Data
27
+
28
+ Fetch all data using the CLI (parallelize where possible):
29
+
30
+ ```bash
31
+ # Run summary
32
+ indemn eval status <run-id> --json
33
+
34
+ # Per-item results
35
+ indemn eval results <run-id> --json
36
+
37
+ # Rubric (get rubric_id from run summary)
38
+ indemn rubric get <rubric-id> --json
39
+
40
+ # Test set (get test_set_id from run summary)
41
+ indemn testset get <test-set-id> --json
42
+
43
+ # Bot context (get agent_id from run summary)
44
+ indemn eval bot-context <agent-id> --json
45
+ ```
46
+
47
+ For exact response shapes, see `references/data-shapes.md`.
48
+
49
+ ## Step 3: Present Run Overview
50
+
51
+ Show the user a summary table:
52
+
53
+ ```
54
+ Run: {run_id}
55
+ Agent: {bot_name} ({agent_id})
56
+ LLM: {bot_llm_provider}/{bot_llm_model}
57
+ Status: {passed}/{total} items passed ({pass_rate}%)
58
+
59
+ Criteria: {criteria_passed}/{criteria_total} checks passed
60
+ Rubric: {rubric_rules_passed}/{rubric_rules_total} rules passed
61
+
62
+ Component Scores:
63
+ prompt: {score}% ({passed}/{total})
64
+ knowledge_base: {score}% ({passed}/{total})
65
+ function: {score}% ({passed}/{total})
66
+ general: {score}% ({passed}/{total})
67
+ ```
68
+
69
+ ## Step 4: Analyze Each Failed Item
70
+
71
+ For every result where `passed == false`:
72
+
73
+ 1. **Get the test item** — match `test_case_id` to the test set's `items[].item_id` to get the item name, type, inputs, and success_criteria
74
+ 2. **Check criteria scores** — from `criteria_scores[]`, list which criteria passed and which failed with their reasoning
75
+ 3. **Check rubric scores** — from `rubric_scores[]`, list which rules passed and which failed with their reasoning
76
+ 4. **Check for divergence** — flag when criteria say FAIL but rubric says PASS (or vice versa). Divergence is a strong signal of an evaluation problem.
77
+ 5. **Read the conversation** — look at `output` field for the full agent response or conversation transcript
78
+ 6. **Cross-reference system prompt** — check if the agent's behavior aligns with what the system prompt instructs
79
+ 7. **Classify** — assign one of four categories (see below)
80
+
81
+ ### Failure Categories
82
+
83
+ | Category | Signal | Action |
84
+ |----------|--------|--------|
85
+ | **Real Agent Problem** | Both criteria and rubric agree agent misbehaved, or output contradicts system prompt | Investigate agent code/prompt |
86
+ | **Evaluation Problem** | Criteria ambiguous/too strict, evaluator reasoning shows misunderstanding, agent response was reasonable | Update test set criteria |
87
+ | **Test Design Problem** | Test assumptions wrong (e.g., assumed KB lacked info it has), criteria contradict agent's actual capabilities | Redesign test item |
88
+ | **KB/Retrieval Problem** | Agent behavior correct but retrieval returned wrong content, or KB is missing needed info | Fix KB content or retrieval config |
89
+
90
+ ### Divergence Analysis
91
+
92
+ When `criteria_passed != rubric_passed` for an item, this is a first-class finding:
93
+ - **Criteria FAIL + Rubric PASS** → criteria are likely too strict or testing the wrong thing
94
+ - **Criteria PASS + Rubric FAIL** → rubric may have rules that don't apply to this scenario, or agent violated a behavioral rule despite answering correctly
95
+
96
+ Always highlight divergences prominently in the report.
97
+
98
+ ## Step 5: Aggregate Findings
99
+
100
+ Group all failed items by category:
101
+
102
+ ```
103
+ ## Findings
104
+
105
+ ### Real Agent Problems (N items)
106
+ - Item X: [description of what went wrong]
107
+
108
+ ### Evaluation Problems (N items)
109
+ - Item Y: [why the criteria/rubric is wrong]
110
+
111
+ ### Test Design Problems (N items)
112
+ - Item Z: [what assumption was incorrect]
113
+
114
+ ### KB/Retrieval Problems (N items)
115
+ - Item W: [what retrieval issue occurred]
116
+ ```
117
+
118
+ ## Step 6: Recommendations
119
+
120
+ Prioritize fixes:
121
+
122
+ - **P1 — Evaluation fixes** (false failures inflate failure count, masking real issues): specific criteria rewrites or rubric rule adjustments
123
+ - **P2 — Agent investigation** (real bugs): which code paths or prompt sections to examine
124
+ - **P3 — KB/Retrieval fixes**: which knowledge bases need content updates or which retrieval configs need tuning
125
+
126
+ For each recommendation, be specific: quote the current criterion/rule, explain what's wrong, and propose the exact replacement text.
127
+
128
+ ## Step 7: Offer to Execute Fixes
129
+
130
+ Ask the user if they want to apply the evaluation fixes. If yes:
131
+
132
+ **Update test set criteria** (creates a new version, non-destructive):
133
+ ```bash
134
+ # Save the updated test set to a JSON file, then:
135
+ indemn testset update <test-set-id> --file updated-testset.json
136
+ ```
137
+
138
+ **Update rubric rules** (creates a new version, non-destructive):
139
+ ```bash
140
+ # Save the updated rubric to a JSON file, then:
141
+ indemn rubric update <rubric-id> --file updated-rubric.json
142
+ ```
143
+
144
+ Both update commands create new versions — the previous version is preserved and accessible via `indemn testset versions <id>` / `indemn rubric versions <id>`.
145
+
146
+ After applying fixes, suggest re-running the evaluation:
147
+ ```bash
148
+ indemn eval run <agent-id> --test-set <test-set-id> --rubric <rubric-id> --wait
149
+ ```
150
+
151
+ ## Export: Conversation Traces
152
+
153
+ When the user wants to **share results** or **export conversation traces** (rather than triage failures):
154
+
155
+ ### Markdown Export
156
+ ```bash
157
+ indemn eval export <run-id>
158
+ # Optional: specify output path
159
+ indemn eval export <run-id> --output results.md
160
+ ```
161
+
162
+ Produces a structured markdown file with all conversations, criteria scores, and rubric scores.
163
+
164
+ ### PDF Report
165
+ ```bash
166
+ indemn eval report <run-id>
167
+ # Optional: specify output path
168
+ indemn eval report <run-id> --output report.pdf
169
+ ```
170
+
171
+ Produces a branded Indemn PDF with cover page, dual scores (criteria + rubric), and per-item detail pages.
172
+
173
+ ### Agent Card
174
+ ```bash
175
+ indemn agents card <agent-id>
176
+ ```
177
+
178
+ Produces a multi-page branded PDF with system prompt, tools, KBs, LLM config, evaluation history, and metadata.
@@ -0,0 +1,279 @@
1
+ # Evaluation API Data Shapes
2
+
3
+ Response shapes from the evaluations API at `localhost:8002`. All endpoints prefixed with `/api/v1`.
4
+
5
+ ## EvaluationRun
6
+
7
+ **Endpoint:** `GET /runs/{run_id}`
8
+
9
+ ```json
10
+ {
11
+ "run_id": "uuid",
12
+ "dataset_id": "uuid",
13
+ "agent_id": "mongodb-objectid",
14
+ "status": "pending | running | completed | failed",
15
+ "total": 13,
16
+ "completed": 13,
17
+ "passed": 8,
18
+ "failed": 5,
19
+ "concurrency": 3,
20
+ "started_at": "datetime",
21
+ "completed_at": "datetime",
22
+ "error": "string | null",
23
+ "rubric_id": "uuid | null",
24
+ "rubric_version": 1,
25
+ "test_set_id": "uuid | null",
26
+ "test_set_version": 1,
27
+ "component_scope_filter": "string | null",
28
+ "component_ids_filter": ["string"] ,
29
+ "component_scores": {
30
+ "prompt": { "score": 0.85, "total": 20, "passed": 17 },
31
+ "knowledge_base": { "score": 0.75, "total": 8, "passed": 6 },
32
+ "function": { "score": 1.0, "total": 4, "passed": 4 },
33
+ "general": { "score": 0.9, "total": 10, "passed": 9 }
34
+ },
35
+ "criteria_passed": 35,
36
+ "criteria_total": 40,
37
+ "rubric_rules_passed": 90,
38
+ "rubric_rules_total": 100,
39
+ "bot_llm_provider": "openai",
40
+ "bot_llm_model": "gpt-4.1",
41
+ "created_at": "datetime"
42
+ }
43
+ ```
44
+
45
+ **Key fields:**
46
+ - `component_scores` — rubric rule pass rates broken down by component scope (prompt, knowledge_base, function, general). Null if no rubric used.
47
+ - `criteria_passed/total` — aggregate criterion-level pass counts across ALL results
48
+ - `rubric_rules_passed/total` — aggregate rubric rule pass counts across ALL results
49
+ - `test_set_id` — present for V2 runs. `dataset_id` is for V1 question-set-based runs.
50
+
51
+ ## EvaluationResult
52
+
53
+ **Endpoint:** `GET /runs/{run_id}/results`
54
+
55
+ Returns an array of results, one per test item.
56
+
57
+ ```json
58
+ {
59
+ "result_id": "uuid",
60
+ "run_id": "uuid",
61
+ "test_case_id": "uuid (matches TestItem.item_id)",
62
+ "langsmith_run_id": "uuid | null",
63
+ "input": {
64
+ "persona": "...",
65
+ "initial_message": "...",
66
+ "max_turns": 8
67
+ },
68
+ "output": {
69
+ "messages": [
70
+ { "role": "user", "content": "..." },
71
+ { "role": "assistant", "content": "..." }
72
+ ]
73
+ },
74
+ "scores": {
75
+ "evaluator_name": { "score": 0.8, "comment": "JSON string with details" }
76
+ },
77
+ "item_name": "PTO Policy Inquiry",
78
+ "item_type": "scenario",
79
+ "criteria_scores": [
80
+ {
81
+ "criterion": "Agent retrieves PTO policy from handbook KB",
82
+ "passed": false,
83
+ "score": 0.0,
84
+ "reasoning": "Agent did not find PTO information..."
85
+ }
86
+ ],
87
+ "criteria_passed": false,
88
+ "rubric_scores": [
89
+ {
90
+ "rule_id": "BRIEF_RESPONSES",
91
+ "rule_name": "Brief, Focused Responses",
92
+ "severity": "medium",
93
+ "passed": true,
94
+ "score": 1.0,
95
+ "reasoning": "Agent kept responses concise..."
96
+ }
97
+ ],
98
+ "rubric_passed": true,
99
+ "passed": false,
100
+ "duration_ms": 45000,
101
+ "created_at": "datetime"
102
+ }
103
+ ```
104
+
105
+ **V1 vs V2 scoring:**
106
+ - **V1** (question-set runs): `scores` dict populated, `criteria_scores`/`rubric_scores` are null. The `comment` field in scores is a JSON string — parse it for per-criterion details.
107
+ - **V2** (test-set runs): `criteria_scores[]` and `rubric_scores[]` populated with structured data. `scores` may still be present.
108
+
109
+ **Passed logic:** `passed = criteria_passed AND rubric_passed`. An item fails if ANY criterion fails OR any high-severity rubric rule fails.
110
+
111
+ ## Rubric
112
+
113
+ **Endpoint:** `GET /rubrics/{rubric_id}`
114
+
115
+ ```json
116
+ {
117
+ "rubric_id": "uuid",
118
+ "agent_id": "mongodb-objectid",
119
+ "version": 1,
120
+ "name": "O'Connor Internal Ops Associate Rubric",
121
+ "description": "...",
122
+ "severity_definitions": {
123
+ "high": "Critical violation...",
124
+ "medium": "Moderate violation...",
125
+ "low": "Minor violation..."
126
+ },
127
+ "rules": [
128
+ {
129
+ "id": "BRIEF_RESPONSES",
130
+ "name": "Brief, Focused Responses",
131
+ "severity": "medium",
132
+ "category": "response_quality",
133
+ "description": "Agent should provide concise answers...",
134
+ "prompt_reference": "Section: Communication Style",
135
+ "evaluation_criteria": {
136
+ "pass_conditions": ["Response is under 3 sentences for simple questions"],
137
+ "fail_conditions": ["Response includes unnecessary preamble or filler"]
138
+ },
139
+ "examples": {
140
+ "violation": "...",
141
+ "correct": "..."
142
+ },
143
+ "component_scope": "prompt | knowledge_base | function | general | null",
144
+ "component_ids": ["objectid", "..."],
145
+ "component_names": ["HR Handbook KB"]
146
+ }
147
+ ],
148
+ "created_at": "datetime",
149
+ "updated_at": "datetime"
150
+ }
151
+ ```
152
+
153
+ **Key fields:**
154
+ - `rules[].evaluation_criteria` — pass/fail conditions the evaluator LLM uses to judge
155
+ - `rules[].severity` — high severity failures cause the entire item to fail
156
+ - `rules[].component_scope` — links rule to a specific component type for component-level scoring
157
+ - `rules[].component_ids` / `component_names` — links rule to specific bot components (KBs, tools, etc.)
158
+
159
+ ## TestSet
160
+
161
+ **Endpoint:** `GET /test-sets/{test_set_id}`
162
+
163
+ ```json
164
+ {
165
+ "test_set_id": "uuid",
166
+ "agent_id": "mongodb-objectid",
167
+ "version": 2,
168
+ "name": "O'Connor Internal Ops Associate Test Set",
169
+ "description": "...",
170
+ "items": [
171
+ {
172
+ "item_id": "uuid (matches EvaluationResult.test_case_id)",
173
+ "type": "single_turn | scenario",
174
+ "name": "PTO Policy Inquiry",
175
+ "inputs": {
176
+ "message": "What is our PTO policy?",
177
+ "persona": "An employee asking about...",
178
+ "initial_message": "Hi, I need to know...",
179
+ "max_turns": 8
180
+ },
181
+ "expected": {
182
+ "success_criteria": [
183
+ "Agent retrieves PTO policy from knowledge base",
184
+ "Response includes accrual rates or refers to handbook"
185
+ ],
186
+ "should_use_tools": ["search_kb"],
187
+ "should_not_use_tools": ["live_handoff"],
188
+ "expected_outcome": "Employee gets PTO info"
189
+ },
190
+ "tags": ["hr", "policy"],
191
+ "priority": "high"
192
+ }
193
+ ],
194
+ "created_at": "datetime",
195
+ "updated_at": "datetime"
196
+ }
197
+ ```
198
+
199
+ **Key fields:**
200
+ - `items[].item_id` — join key to `EvaluationResult.test_case_id`
201
+ - `items[].type` — `single_turn` sends one message; `scenario` runs multi-turn simulation with persona
202
+ - `items[].inputs` — for single_turn: `message`; for scenario: `persona`, `initial_message`, `max_turns`
203
+ - `items[].expected.success_criteria` — the criteria that become `criteria_scores[]` in results
204
+
205
+ ## BotContext
206
+
207
+ **Endpoint:** `GET /bots/{agent_id}/context`
208
+
209
+ ```json
210
+ {
211
+ "bot_id": "mongodb-objectid",
212
+ "bot_name": "O'Connor Internal Operations Associate",
213
+ "organization_id": "mongodb-objectid",
214
+ "organization_name": "O'Connor Insurance",
215
+ "system_prompt": "You are an AI assistant for O'Connor Insurance...",
216
+ "llm_config": {
217
+ "provider": "openai",
218
+ "model": "gpt-4.1",
219
+ "parameters": { "temperature": 0.3 }
220
+ },
221
+ "tools": [
222
+ {
223
+ "id": "objectid",
224
+ "name": "Live Chat Handoff",
225
+ "tool_name": "live_handoff",
226
+ "type": "human_handoff",
227
+ "description": "Transfer to a live agent when...",
228
+ "output_instructions": "..."
229
+ }
230
+ ],
231
+ "knowledge_bases": [
232
+ {
233
+ "id": "objectid",
234
+ "name": "O'Connor Employee Handbook",
235
+ "type": "file",
236
+ "document_count": 3
237
+ }
238
+ ],
239
+ "harness_context": {
240
+ "version": "1.0",
241
+ "description": "LangGraph-based agent...",
242
+ "execution_flow": [
243
+ { "step": 1, "node": "invoke_model", "description": "..." }
244
+ ],
245
+ "key_behaviors": ["..."],
246
+ "evaluation_notes": ["..."]
247
+ }
248
+ }
249
+ ```
250
+
251
+ **Key fields:**
252
+ - `system_prompt` — the full agent prompt; cross-reference against agent behavior to distinguish real bugs from expected behavior
253
+ - `tools[]` — available tools with their descriptions; critical for understanding handoff, search, and function-calling behavior
254
+ - `knowledge_bases[]` — connected KBs; check if test items assume KB content that may not exist
255
+ - `harness_context` — how bot-service executes the agent; useful for understanding tool call flow
256
+
257
+ ## Versioning Endpoints
258
+
259
+ Both test sets and rubrics support version history:
260
+
261
+ ```bash
262
+ # List all versions
263
+ GET /test-sets/{test_set_id}/versions
264
+ GET /rubrics/{rubric_id}/versions
265
+
266
+ # Get a specific version
267
+ GET /test-sets/{test_set_id}/versions/{version}
268
+ GET /rubrics/{rubric_id}/versions/{version}
269
+ ```
270
+
271
+ PUT to update creates a new version automatically — previous versions are preserved.
272
+
273
+ ## Listing Runs for an Agent
274
+
275
+ ```bash
276
+ GET /runs?agent_id={agent_id}&status=completed&limit=5
277
+ ```
278
+
279
+ Returns runs sorted by `created_at` descending. Use this when given an agent_id instead of a run_id.
package/src/cli/agents.ts CHANGED
@@ -2,6 +2,7 @@ import { Command } from 'commander';
2
2
  import ora from 'ora';
3
3
  import { IndemnClient } from '../sdk/client.js';
4
4
  import { AgentsSDK } from '../sdk/agents.js';
5
+ import { exportAgentCard } from '../report/export.js';
5
6
  import { formatTable, printJson, printError, printSuccess, confirm } from './utils/output.js';
6
7
 
7
8
  function formatChannels(channels: unknown[]): string {
@@ -178,6 +179,27 @@ export function registerAgentCommands(program: Command): void {
178
179
  }
179
180
  });
180
181
 
182
+ agents
183
+ .command('card')
184
+ .argument('<id>', 'Agent ID')
185
+ .description('Generate branded agent card PDF')
186
+ .option('--output <path>', 'Output file path')
187
+ .action(async (id: string, options) => {
188
+ try {
189
+ const client = new IndemnClient();
190
+ const spinner = ora('Fetching agent data...').start();
191
+
192
+ spinner.text = 'Generating PDF...';
193
+ const result = await exportAgentCard(id, options.output, client);
194
+
195
+ spinner.stop();
196
+ printSuccess(`Agent card saved to ${result.file_path}`);
197
+ } catch (err) {
198
+ printError(err);
199
+ process.exit(1);
200
+ }
201
+ });
202
+
181
203
  agents
182
204
  .command('clone')
183
205
  .argument('<id>', 'Agent ID to clone')
package/src/cli/eval.ts CHANGED
@@ -6,6 +6,7 @@ import { IndemnClient } from '../sdk/client.js';
6
6
  import { EvalSDK } from '../sdk/eval.js';
7
7
  import { RubricsSDK } from '../sdk/rubrics.js';
8
8
  import { TestSetsSDK } from '../sdk/test-sets.js';
9
+ import { exportEvalMarkdown, exportEvalReport } from '../report/export.js';
9
10
  import { formatTable, printJson, printError, printSuccess, confirm } from './utils/output.js';
10
11
 
11
12
  export function registerEvalCommands(program: Command): void {
@@ -242,6 +243,47 @@ export function registerEvalCommands(program: Command): void {
242
243
  }
243
244
  });
244
245
 
246
+ eval_
247
+ .command('export')
248
+ .argument('<run-id>', 'Evaluation run ID')
249
+ .description('Export evaluation results as markdown')
250
+ .option('--output <path>', 'Output file path')
251
+ .action(async (runId: string, options) => {
252
+ try {
253
+ const client = new IndemnClient();
254
+ const spinner = ora('Fetching evaluation data...').start();
255
+
256
+ const result = await exportEvalMarkdown(runId, options.output, client);
257
+
258
+ spinner.stop();
259
+ printSuccess(`Exported to ${result.file_path}`);
260
+ } catch (err) {
261
+ printError(err);
262
+ process.exit(1);
263
+ }
264
+ });
265
+
266
+ eval_
267
+ .command('report')
268
+ .argument('<run-id>', 'Evaluation run ID')
269
+ .description('Generate branded evaluation report PDF')
270
+ .option('--output <path>', 'Output file path')
271
+ .action(async (runId: string, options) => {
272
+ try {
273
+ const client = new IndemnClient();
274
+ const spinner = ora('Fetching evaluation data...').start();
275
+
276
+ spinner.text = 'Generating PDF...';
277
+ const result = await exportEvalReport(runId, options.output, client);
278
+
279
+ spinner.stop();
280
+ printSuccess(`Report saved to ${result.file_path}`);
281
+ } catch (err) {
282
+ printError(err);
283
+ process.exit(1);
284
+ }
285
+ });
286
+
245
287
  // Rubric commands — registered on program, not nested under eval
246
288
  const rubric = program.command('rubric').description('Manage evaluation rubrics');
247
289
 
package/src/cli/index.ts CHANGED
@@ -13,7 +13,7 @@ const program = new Command();
13
13
  program
14
14
  .name('indemn')
15
15
  .description('CLI for managing Indemn AI agents')
16
- .version('1.0.0');
16
+ .version('1.1.1');
17
17
 
18
18
  program.option('--env <environment>', 'Environment (dev or production)');
19
19
 
package/src/mcp/tools.ts CHANGED
@@ -9,6 +9,7 @@ import { RubricsSDK } from '../sdk/rubrics.js';
9
9
  import { TestSetsSDK } from '../sdk/test-sets.js';
10
10
  import { EvalSDK } from '../sdk/eval.js';
11
11
  import { ChatSession } from '../sdk/chat.js';
12
+ import { exportEvalMarkdown, exportEvalReport, exportAgentCard } from '../report/export.js';
12
13
 
13
14
  // Helper: format a successful MCP tool result
14
15
  function success(data: unknown) {
@@ -968,4 +969,53 @@ export function registerTools(server: McpServer, client: IndemnClient): void {
968
969
  } catch (err) { return error(err); }
969
970
  },
970
971
  );
972
+
973
+ // ---------------------------------------------------------------------------
974
+ // Export tools
975
+ // ---------------------------------------------------------------------------
976
+
977
+ server.tool(
978
+ 'export_eval_markdown',
979
+ 'Export evaluation results as a structured markdown file with conversations, scores, and criteria',
980
+ {
981
+ run_id: z.string().describe('The evaluation run ID'),
982
+ output_path: z.string().optional().describe('Output file path (defaults to current directory)'),
983
+ },
984
+ async (args) => {
985
+ try {
986
+ const result = await exportEvalMarkdown(args.run_id, args.output_path, client);
987
+ return success(result);
988
+ } catch (err) { return error(err); }
989
+ },
990
+ );
991
+
992
+ server.tool(
993
+ 'export_eval_report',
994
+ 'Generate a branded PDF evaluation report with cover page, scores, and per-item details',
995
+ {
996
+ run_id: z.string().describe('The evaluation run ID'),
997
+ output_path: z.string().optional().describe('Output file path (defaults to current directory)'),
998
+ },
999
+ async (args) => {
1000
+ try {
1001
+ const result = await exportEvalReport(args.run_id, args.output_path, client);
1002
+ return success(result);
1003
+ } catch (err) { return error(err); }
1004
+ },
1005
+ );
1006
+
1007
+ server.tool(
1008
+ 'export_agent_card',
1009
+ 'Generate a branded PDF agent card with system prompt, tools, KBs, LLM config, and evaluation history',
1010
+ {
1011
+ agent_id: z.string().describe('The agent ID'),
1012
+ output_path: z.string().optional().describe('Output file path (defaults to current directory)'),
1013
+ },
1014
+ async (args) => {
1015
+ try {
1016
+ const result = await exportAgentCard(args.agent_id, args.output_path, client);
1017
+ return success(result);
1018
+ } catch (err) { return error(err); }
1019
+ },
1020
+ );
971
1021
  }