codeer-cli 0.1.13__tar.gz → 0.1.15__tar.gz

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 (42) hide show
  1. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/API_REFERENCE.md +34 -14
  2. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/PKG-INFO +86 -6
  3. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/README.md +85 -5
  4. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/pyproject.toml +1 -1
  5. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/cli.py +3 -1
  6. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/commands/eval_cmd.py +52 -5
  7. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/commands/history.py +46 -9
  8. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/commands/kb.py +400 -0
  9. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/eval_.py +18 -2
  10. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/histories.py +93 -11
  11. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/kb.py +0 -1
  12. codeer_cli-0.1.15/tests/test_eval_evaluators.py +230 -0
  13. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_eval_pairs.py +5 -2
  14. codeer_cli-0.1.15/tests/test_history_read.py +299 -0
  15. codeer_cli-0.1.15/tests/test_kb_export.py +185 -0
  16. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_kb_nodes.py +24 -0
  17. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/uv.lock +1 -1
  18. codeer_cli-0.1.13/tests/test_history_read.py +0 -132
  19. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/.gitignore +0 -0
  20. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/__init__.py +0 -0
  21. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/_validate.py +0 -0
  22. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/agents.py +0 -0
  23. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/chats.py +0 -0
  24. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/client.py +0 -0
  25. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/commands/__init__.py +0 -0
  26. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/commands/_util.py +0 -0
  27. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/commands/agent.py +0 -0
  28. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/commands/check.py +0 -0
  29. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/commands/model.py +0 -0
  30. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/commands/profile.py +0 -0
  31. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/constants.py +0 -0
  32. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/models.py +0 -0
  33. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/src/codeer_cli/parse.py +0 -0
  34. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_agent_handoff.py +0 -0
  35. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_agent_model_settings.py +0 -0
  36. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_chats_v2.py +0 -0
  37. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_client_transport.py +0 -0
  38. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_eval_labels.py +0 -0
  39. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_history_send.py +0 -0
  40. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_kb_ranges.py +0 -0
  41. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_models.py +0 -0
  42. {codeer_cli-0.1.13 → codeer_cli-0.1.15}/tests/test_util.py +0 -0
@@ -84,6 +84,20 @@ Base path: `/organizations/{org_id}/workspaces/{ws_id}/knowledge_bases`
84
84
  | `POST .../files/status` | Batch-poll indexing status by node ID |
85
85
  | `GET .../{kb_id}/nodes/{node_id}/content` | Read a file's extracted content |
86
86
 
87
+ The workspace API-key external routes used by `codeer-cli` include:
88
+
89
+ | Method & path | Purpose |
90
+ | --- | --- |
91
+ | `GET /external/knowledge-bases/nodes?parent_id={node_id}` | List KB roots or direct children |
92
+ | `GET /external/knowledge-bases/files/{node_id}/content` | Read one file's extracted snapshot content |
93
+
94
+ `codeer kb export --node-id <file-id> --file <path>` maps directly to the second
95
+ route. `codeer kb export --node-id <folder-id> --dir <path>` recursively follows
96
+ the first route and calls the second route for every file. Any returned text is
97
+ written as UTF-8 Markdown regardless of indexing status, with that status
98
+ preserved in the manifest. A `null` content response cannot be exported. The
99
+ command does not reconstruct the originally uploaded binary file.
100
+
87
101
  Attach KB files to an agent by listing their node IDs in the agent's
88
102
  `unified_tools[].knowledge_node_ids`.
89
103
 
@@ -187,8 +201,8 @@ version; it does not currently replace this draft-pinning path.
187
201
  | `GET /eval/evaluators?wid=<ws>` | List evaluators |
188
202
  | `PUT /eval/evaluators/{id}` | Update |
189
203
  | `DELETE /eval/evaluators/{id}` | Delete |
190
- | `POST /eval/case-evaluator-infos:batch` | Read assigned evaluators/rubrics for cases |
191
- | `PUT /eval/cases/{case_id}/case-evaluator-infos` | Replace assigned evaluators/rubrics for one case |
204
+ | `POST /external/eval/case-evaluator-infos:batch` | Read assigned evaluators/rubrics for cases through the API-key facade |
205
+ | `PUT /external/eval/cases/{case_id}/case-evaluator-infos` | Replace assigned evaluators/rubrics for one case through the API-key facade |
192
206
  | `POST /eval/trigger` | Run explicit assigned `case_evaluator_pairs` pinned to `agent_history_id` |
193
207
  | `POST /eval/stop` | Cancel running case+evaluator combo |
194
208
  | `POST /eval/rubric` | Set/override the rubric for one (case, evaluator); also creates assignment |
@@ -275,12 +289,12 @@ the public CLI.
275
289
  | --- | --- |
276
290
  | `POST /api/v2/chats` | Create a persisted history using an agent's current published version |
277
291
  | `POST /api/v2/chats/{id}/messages` | Append a turn through structured SSE using the current published version |
278
- | `GET /api/v2/chats/{id}/messages` | Read persisted Chat V2 conversation parts |
279
- | `GET /histories?agent_id=X&feedback_filter=improve_feedback&external_user_id=…` | List conversations with filters |
280
- | `GET /histories/{id}` | Read one history's metadata |
281
- | `GET /histories/{id}/conversations` | Legacy compact conversation rows; not complete tool I/O |
282
- | `POST /histories/{hid}/conversations/{cid}/feedbacks` | Leave freeform improvement feedback |
283
- | `POST /histories/{hid}/conversations/{cid}/score` | Numeric score |
292
+ | `GET /api/v1/external/histories/{id}/messages` | Export persisted diagnostic parts for workspace editors (`history-parts-v1`) |
293
+ | `GET /api/v2/chats/{id}/messages` | Read client-visible parts under the external client-owner contract |
294
+ | `GET /api/v1/external/histories?agent_id=X&feedback_filter=improve_feedback&external_user_id=…` | List conversations with filters |
295
+ | `GET /api/v1/external/histories/{id}` | Read one history's metadata |
296
+ | `GET /api/v1/external/histories/{id}/conversations` | Legacy compact conversation rows; not complete tool I/O |
297
+ | `POST /api/v1/external/histories/{hid}/conversations/{cid}/feedbacks` | Leave freeform improvement feedback |
284
298
 
285
299
  The CLI exposes the first two operations as `codeer history create` and
286
300
  `codeer history send`. Messages explicitly set `stream: true`, consume Chat V2
@@ -289,6 +303,11 @@ per-message SSE read timeout defaults to 240 seconds. A timeout,
289
303
  `response.failed`, or disconnect before completion has an uncertain write
290
304
  outcome, so read the history before retrying to avoid duplicate turns.
291
305
 
306
+ `codeer history conversations` uses the management export by default. Pass
307
+ `--client-visible --user <external-user-id>` only to select the external Chat
308
+ V2 read contract explicitly. The management export includes persisted tool
309
+ calls/results but excludes system prompts and provider raw traces.
310
+
292
311
  `feedback_filter` accepts the `FeedbackFilterType` enum values:
293
312
  `no_feedback`, `with_feedback`, `helpful_feedback`, `improve_feedback`.
294
313
 
@@ -491,12 +510,13 @@ you can and can't recover from each assistant turn:
491
510
  | Tool **outputs** (raw JSON returned by the tool) | same — stored only as derived `primary_sources` for retrieval tools |
492
511
  | Reasoning steps mid-turn | `meta.reasoning_steps` is currently always `null` |
493
512
 
494
- Chat V2 improves this contract: structured SSE emits tool calls and returns as
495
- `response.part.created` / `response.part.completed`, and
496
- `GET /api/v2/chats/{id}/messages` reads the persisted conversation parts.
497
- Capture the SSE artifact with `--out` when exact event order matters; use the
498
- paginated V2 message read for persisted after-the-fact tool I/O. The legacy V1
499
- history read remains useful for compact turn-level compatibility only.
513
+ Native parts improve this contract: structured SSE emits tool calls and returns
514
+ as `response.part.created` / `response.part.completed`, and the management
515
+ `GET /api/v1/external/histories/{id}/messages` export reads persisted
516
+ after-the-fact tool I/O for workspace editors. The client-owner
517
+ `GET /api/v2/chats/{id}/messages` route remains available for explicitly
518
+ client-visible reads. The legacy V1 history read remains useful for compact
519
+ turn-level compatibility only.
500
520
 
501
521
  ### 10. A KB has exactly ONE level of folders — no nesting
502
522
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: codeer-cli
3
- Version: 0.1.13
3
+ Version: 0.1.15
4
4
  Summary: Command line tools for managing Codeer agents over the Codeer API.
5
5
  Project-URL: Homepage, https://www.codeer.ai
6
6
  Author: Codeer.AI
@@ -98,7 +98,8 @@ CODEER_AGENT_ID=<agent-id>
98
98
 
99
99
  ## Development install
100
100
 
101
- Use an editable install while the CLI is changing quickly:
101
+ Codeer contributors should use an editable install from this checkout, not the
102
+ PyPI package, so the `codeer` command always executes the folder being edited:
102
103
 
103
104
  ```bash
104
105
  cd /path/to/codeer-skills/codeer-cli
@@ -123,6 +124,34 @@ List the active cloud models without opening the Codeer web app:
123
124
  codeer model list --type text
124
125
  ```
125
126
 
127
+ ## Custom evaluator judge models
128
+
129
+ Custom evaluator create/update commands can select a judge LLM model by ID:
130
+
131
+ ```bash
132
+ codeer eval evaluator-create \
133
+ --name "Correctness" \
134
+ --system-prompt-template-file evaluator-prompt.txt \
135
+ --judge-model <model-id> \
136
+ --dry-run
137
+
138
+ codeer eval evaluator-update \
139
+ --evaluator <evaluator-id> \
140
+ --judge-model <model-id> \
141
+ --dry-run
142
+ ```
143
+
144
+ Omit the judge-model flags on update to leave the current setting unchanged.
145
+ Use `--clear-judge-model` to explicitly clear the override and return to the
146
+ system default:
147
+
148
+ ```bash
149
+ codeer eval evaluator-update \
150
+ --evaluator <evaluator-id> \
151
+ --clear-judge-model \
152
+ --dry-run
153
+ ```
154
+
126
155
  ## Agent human handoff
127
156
 
128
157
  `codeer agent apply` accepts the same `human_handoff` object as the Agent API.
@@ -197,10 +226,22 @@ Flags:
197
226
  for raw eval results, full conversation turns, full rubric matrices, and
198
227
  other data that can grow with cases, versions, or turns.
199
228
 
200
- `history conversations` reads Chat V2 parts and follows all pages
201
- automatically. Its stdout is still a bounded summary; the `--out` artifact is
202
- the complete client-visible history, including tool calls/results,
203
- attachments, interactions, feedback, and passthrough metadata.
229
+ `history conversations` defaults to the workspace-editor management export and
230
+ follows all pages automatically. Its stdout is a bounded summary that never
231
+ prints tool arguments or results; the `--out` artifact preserves the complete
232
+ `history-parts-v1` payload, including persisted tool calls/results,
233
+ attachments, feedback, and metadata. System prompts and provider raw traces are
234
+ not part of that export contract, and a missing part does not prove that a tool
235
+ was not executed.
236
+
237
+ Use the external client-owner contract only when that distinction is the point
238
+ of the test:
239
+
240
+ ```bash
241
+ codeer history conversations <history-id> \
242
+ --client-visible --user <external-user-id> \
243
+ --out .codeer/current/client-history-<history-id>.json
244
+ ```
204
245
 
205
246
  Avoid piping large raw JSON directly into agent chat. Prefer `--out`, then ask
206
247
  the coding agent to inspect targeted summaries, IDs, failing cases, or selected
@@ -228,6 +269,45 @@ paths containing `*` so the shell passes the wildcard to the CLI. Advanced
228
269
  settings can still be passed through `--config-json`; explicit crawler flags
229
270
  override matching JSON keys.
230
271
 
272
+ ## Exporting KB snapshot content
273
+
274
+ Export one file directly from the content endpoint:
275
+
276
+ ```bash
277
+ codeer kb export \
278
+ --node-id <file-node-id> \
279
+ --file guide.md
280
+ ```
281
+
282
+ Or recursively export a folder or an entire KB root:
283
+
284
+ ```bash
285
+ codeer kb export \
286
+ --node-id <folder-or-kb-root-node-id> \
287
+ --dir kb-export \
288
+ --out kb-export-manifest.json
289
+ ```
290
+
291
+ `--file` and `--dir` are mutually exclusive. Single-file mode maps directly to
292
+ the external file-content endpoint and lets the caller choose the exact local
293
+ path. Folder mode recursively lists the node tree, calls that endpoint for each
294
+ file, and writes the extracted snapshot content as UTF-8 Markdown. Existing
295
+ `.md`/`.markdown` names are preserved; other folder-export names receive an
296
+ additional `.md` suffix (for example, `guide.pdf` becomes `guide.pdf.md`) so the
297
+ export is not mistaken for the original binary upload.
298
+
299
+ The command asks the content endpoint for every file regardless of indexing
300
+ status. If the endpoint returns text, it is exported even when the status is
301
+ not `READY`; the full manifest preserves that server status and marks the file
302
+ as `exported_while_not_ready`. If the endpoint returns `content: null`, the file
303
+ is skipped and the command exits non-zero. Existing target files block the
304
+ entire export before any content is written; pass `--overwrite` only when
305
+ replacing those local files is intended.
306
+
307
+ This is a snapshot-content export, not an original-file backup. The server API
308
+ returns processed text and does not return the original PDF, DOCX, or other
309
+ binary bytes through this endpoint.
310
+
231
311
  ## KB node rename and delete
232
312
 
233
313
  Knowledge Base roots, folders, and files are all KnowledgeNodes. Use
@@ -80,7 +80,8 @@ CODEER_AGENT_ID=<agent-id>
80
80
 
81
81
  ## Development install
82
82
 
83
- Use an editable install while the CLI is changing quickly:
83
+ Codeer contributors should use an editable install from this checkout, not the
84
+ PyPI package, so the `codeer` command always executes the folder being edited:
84
85
 
85
86
  ```bash
86
87
  cd /path/to/codeer-skills/codeer-cli
@@ -105,6 +106,34 @@ List the active cloud models without opening the Codeer web app:
105
106
  codeer model list --type text
106
107
  ```
107
108
 
109
+ ## Custom evaluator judge models
110
+
111
+ Custom evaluator create/update commands can select a judge LLM model by ID:
112
+
113
+ ```bash
114
+ codeer eval evaluator-create \
115
+ --name "Correctness" \
116
+ --system-prompt-template-file evaluator-prompt.txt \
117
+ --judge-model <model-id> \
118
+ --dry-run
119
+
120
+ codeer eval evaluator-update \
121
+ --evaluator <evaluator-id> \
122
+ --judge-model <model-id> \
123
+ --dry-run
124
+ ```
125
+
126
+ Omit the judge-model flags on update to leave the current setting unchanged.
127
+ Use `--clear-judge-model` to explicitly clear the override and return to the
128
+ system default:
129
+
130
+ ```bash
131
+ codeer eval evaluator-update \
132
+ --evaluator <evaluator-id> \
133
+ --clear-judge-model \
134
+ --dry-run
135
+ ```
136
+
108
137
  ## Agent human handoff
109
138
 
110
139
  `codeer agent apply` accepts the same `human_handoff` object as the Agent API.
@@ -179,10 +208,22 @@ Flags:
179
208
  for raw eval results, full conversation turns, full rubric matrices, and
180
209
  other data that can grow with cases, versions, or turns.
181
210
 
182
- `history conversations` reads Chat V2 parts and follows all pages
183
- automatically. Its stdout is still a bounded summary; the `--out` artifact is
184
- the complete client-visible history, including tool calls/results,
185
- attachments, interactions, feedback, and passthrough metadata.
211
+ `history conversations` defaults to the workspace-editor management export and
212
+ follows all pages automatically. Its stdout is a bounded summary that never
213
+ prints tool arguments or results; the `--out` artifact preserves the complete
214
+ `history-parts-v1` payload, including persisted tool calls/results,
215
+ attachments, feedback, and metadata. System prompts and provider raw traces are
216
+ not part of that export contract, and a missing part does not prove that a tool
217
+ was not executed.
218
+
219
+ Use the external client-owner contract only when that distinction is the point
220
+ of the test:
221
+
222
+ ```bash
223
+ codeer history conversations <history-id> \
224
+ --client-visible --user <external-user-id> \
225
+ --out .codeer/current/client-history-<history-id>.json
226
+ ```
186
227
 
187
228
  Avoid piping large raw JSON directly into agent chat. Prefer `--out`, then ask
188
229
  the coding agent to inspect targeted summaries, IDs, failing cases, or selected
@@ -210,6 +251,45 @@ paths containing `*` so the shell passes the wildcard to the CLI. Advanced
210
251
  settings can still be passed through `--config-json`; explicit crawler flags
211
252
  override matching JSON keys.
212
253
 
254
+ ## Exporting KB snapshot content
255
+
256
+ Export one file directly from the content endpoint:
257
+
258
+ ```bash
259
+ codeer kb export \
260
+ --node-id <file-node-id> \
261
+ --file guide.md
262
+ ```
263
+
264
+ Or recursively export a folder or an entire KB root:
265
+
266
+ ```bash
267
+ codeer kb export \
268
+ --node-id <folder-or-kb-root-node-id> \
269
+ --dir kb-export \
270
+ --out kb-export-manifest.json
271
+ ```
272
+
273
+ `--file` and `--dir` are mutually exclusive. Single-file mode maps directly to
274
+ the external file-content endpoint and lets the caller choose the exact local
275
+ path. Folder mode recursively lists the node tree, calls that endpoint for each
276
+ file, and writes the extracted snapshot content as UTF-8 Markdown. Existing
277
+ `.md`/`.markdown` names are preserved; other folder-export names receive an
278
+ additional `.md` suffix (for example, `guide.pdf` becomes `guide.pdf.md`) so the
279
+ export is not mistaken for the original binary upload.
280
+
281
+ The command asks the content endpoint for every file regardless of indexing
282
+ status. If the endpoint returns text, it is exported even when the status is
283
+ not `READY`; the full manifest preserves that server status and marks the file
284
+ as `exported_while_not_ready`. If the endpoint returns `content: null`, the file
285
+ is skipped and the command exits non-zero. Existing target files block the
286
+ entire export before any content is written; pass `--overwrite` only when
287
+ replacing those local files is intended.
288
+
289
+ This is a snapshot-content export, not an original-file backup. The server API
290
+ returns processed text and does not return the original PDF, DOCX, or other
291
+ binary bytes through this endpoint.
292
+
213
293
  ## KB node rename and delete
214
294
 
215
295
  Knowledge Base roots, folders, and files are all KnowledgeNodes. Use
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codeer-cli"
7
- version = "0.1.13"
7
+ version = "0.1.15"
8
8
  description = "Command line tools for managing Codeer agents over the Codeer API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -3,7 +3,7 @@
3
3
  codeer check
4
4
  codeer agent list|get|apply|diff|versions
5
5
  codeer model list
6
- codeer kb list|files|upload|node-rename|node-delete|faq-list|faq-get|faq-create|faq-update|faq-delete
6
+ codeer kb list|files|export|upload|node-rename|node-delete|faq-list|faq-get|faq-create|faq-update|faq-delete
7
7
  codeer eval list|label-list|label-create|label-update|label-delete|case-update|case-delete|evaluators|evaluator-create|evaluator-update|run|export|reconcile|cases-apply|rubrics|rubrics-apply
8
8
  codeer history list|get|conversations|negative-feedback|create|send
9
9
  """
@@ -31,6 +31,8 @@ Safe workflow for coding agents:
31
31
  codeer agent get <agent-id> --full
32
32
  codeer kb list
33
33
  codeer kb files --kb-id <kb-id>
34
+ codeer kb export --node-id <file-id> --file guide.md
35
+ codeer kb export --node-id <folder-id> --dir kb-export
34
36
  codeer eval list --agent <agent-id>
35
37
  codeer eval label-list
36
38
  codeer eval case-update --case <case-id> --input "..." --dry-run
@@ -128,6 +128,12 @@ def register(subparsers):
128
128
  g.add_argument("--system-prompt-template", help="Evaluator system prompt template text")
129
129
  g.add_argument("--system-prompt-template-file", help="Path to evaluator system prompt template")
130
130
  p.add_argument("--description", default=None)
131
+ p.add_argument(
132
+ "--judge-model",
133
+ default=None,
134
+ metavar="MODEL_ID",
135
+ help="Judge LLM model ID (default: system default)",
136
+ )
131
137
  p.add_argument("--dry-run", action="store_true",
132
138
  help="Validate inputs and print intended mutation without writing server state.")
133
139
  p.set_defaults(func=run_evaluator_create)
@@ -140,6 +146,18 @@ def register(subparsers):
140
146
  g.add_argument("--system-prompt-template", help="Evaluator system prompt template text")
141
147
  g.add_argument("--system-prompt-template-file", help="Path to evaluator system prompt template")
142
148
  p.add_argument("--description", default=None)
149
+ g = p.add_mutually_exclusive_group()
150
+ g.add_argument(
151
+ "--judge-model",
152
+ default=None,
153
+ metavar="MODEL_ID",
154
+ help="Set the judge LLM model ID",
155
+ )
156
+ g.add_argument(
157
+ "--clear-judge-model",
158
+ action="store_true",
159
+ help="Clear the evaluator override and use the system default judge model",
160
+ )
143
161
  p.add_argument("--dry-run", action="store_true",
144
162
  help="Validate inputs and print intended mutation without writing server state.")
145
163
  p.set_defaults(func=run_evaluator_update)
@@ -249,6 +267,7 @@ def _evaluator_summary(evaluator: dict, *, full: bool = False) -> dict:
249
267
  "id": evaluator.get("id"),
250
268
  "name": evaluator.get("name"),
251
269
  "description": evaluator.get("description"),
270
+ "judge_llm_model_id": evaluator.get("judge_llm_model_id"),
252
271
  "system_prompt_template_chars": len(template),
253
272
  "has_tool_steps_placeholder": "{tool_steps}" in template,
254
273
  "has_output_placeholder": "{output}" in template,
@@ -534,6 +553,10 @@ def run_evaluator_create(args, client) -> int:
534
553
  "workspace_id": workspace_id,
535
554
  "name": args.name,
536
555
  "description": args.description,
556
+ "judge_model": {
557
+ "action": "set" if args.judge_model is not None else "use_system_default",
558
+ "model_id": args.judge_model,
559
+ },
537
560
  "system_prompt_template_chars": len(system_prompt_template or ""),
538
561
  "would_write_server_state": True,
539
562
  "next_step": "Review this summary, then rerun without --dry-run after approval.",
@@ -545,6 +568,7 @@ def run_evaluator_create(args, client) -> int:
545
568
  name=args.name,
546
569
  system_prompt_template=system_prompt_template,
547
570
  description=args.description,
571
+ judge_llm_model_id=args.judge_model,
548
572
  )
549
573
  print_json(_evaluator_summary(evaluator, full=True))
550
574
  return 0
@@ -560,13 +584,27 @@ def run_evaluator_update(args, client) -> int:
560
584
  else:
561
585
  system_prompt_template = args.system_prompt_template
562
586
 
563
- if args.name is None and args.description is None and system_prompt_template is None:
587
+ if (
588
+ args.name is None
589
+ and args.description is None
590
+ and system_prompt_template is None
591
+ and args.judge_model is None
592
+ and not args.clear_judge_model
593
+ ):
564
594
  log(
565
595
  "error: provide at least one of --name, --description, "
566
- "--system-prompt-template, --system-prompt-template-file"
596
+ "--system-prompt-template, --system-prompt-template-file, "
597
+ "--judge-model, --clear-judge-model"
567
598
  )
568
599
  return 2
569
600
 
601
+ if args.clear_judge_model:
602
+ judge_model = {"action": "clear_to_system_default", "model_id": None}
603
+ elif args.judge_model is not None:
604
+ judge_model = {"action": "set", "model_id": args.judge_model}
605
+ else:
606
+ judge_model = {"action": "unchanged"}
607
+
570
608
  if args.dry_run:
571
609
  print_json({
572
610
  "dry_run": True,
@@ -574,6 +612,7 @@ def run_evaluator_update(args, client) -> int:
574
612
  "evaluator_id": args.evaluator,
575
613
  "name": args.name,
576
614
  "description": args.description,
615
+ "judge_model": judge_model,
577
616
  "system_prompt_template_chars": (
578
617
  len(system_prompt_template) if system_prompt_template is not None else None
579
618
  ),
@@ -582,12 +621,20 @@ def run_evaluator_update(args, client) -> int:
582
621
  })
583
622
  return 0
584
623
 
624
+ evaluator_kwargs: dict[str, Any] = {
625
+ "name": args.name,
626
+ "system_prompt_template": system_prompt_template,
627
+ "description": args.description,
628
+ }
629
+ if args.clear_judge_model:
630
+ evaluator_kwargs["judge_llm_model_id"] = None
631
+ elif args.judge_model is not None:
632
+ evaluator_kwargs["judge_llm_model_id"] = args.judge_model
633
+
585
634
  evaluator = eval_mod.update_evaluator(
586
635
  client,
587
636
  evaluator_id=args.evaluator,
588
- name=args.name,
589
- system_prompt_template=system_prompt_template,
590
- description=args.description,
637
+ **evaluator_kwargs,
591
638
  )
592
639
  print_json(_evaluator_summary(evaluator, full=True))
593
640
  return 0
@@ -48,13 +48,17 @@ def register(subparsers):
48
48
  # codeer history conversations <id>
49
49
  p = sub.add_parser(
50
50
  "conversations",
51
- help="Summarize all Chat V2 parts in a history. Complete parts/tool payloads require --out.",
51
+ help="Summarize persisted History parts. Complete parts/tool payloads require --out.",
52
52
  )
53
53
  p.add_argument("history_id", type=int)
54
54
  p.add_argument("--full", action="store_true",
55
55
  help="Require --out and include longer stdout previews; the artifact is always complete.")
56
56
  p.add_argument("--out", default=None,
57
- help="Write every unmodified client-visible Chat V2 part to this file.")
57
+ help="Write every persisted part allowed by the selected export contract.")
58
+ p.add_argument("--client-visible", action="store_true",
59
+ help="Use the external client-owner Chat V2 contract instead of the management export.")
60
+ p.add_argument("--user", default=None,
61
+ help="external_user_id required with --client-visible.")
58
62
  p.set_defaults(func=run_conversations)
59
63
 
60
64
  # codeer history negative-feedback
@@ -158,10 +162,12 @@ def _history_summary(row: dict, *, full: bool = False) -> dict:
158
162
 
159
163
  def _part_summary(part: dict, idx: int, *, full: bool = False) -> dict:
160
164
  raw_content = part.get("content")
161
- if isinstance(raw_content, dict):
165
+ part_kind = part.get("part_kind")
166
+ is_tool_part = part_kind in {"tool-call", "tool-return"}
167
+ if is_tool_part:
168
+ content_value = None
169
+ elif isinstance(raw_content, dict):
162
170
  content_value = raw_content.get("content")
163
- if content_value is None and part.get("part_kind") == "tool-call":
164
- content_value = raw_content.get("args")
165
171
  else:
166
172
  content_value = raw_content
167
173
  if isinstance(content_value, str):
@@ -176,7 +182,7 @@ def _part_summary(part: dict, idx: int, *, full: bool = False) -> dict:
176
182
  "conversation_id": part.get("conversation_id"),
177
183
  "conversation_group_id": part.get("conversation_group_id"),
178
184
  "sequence": part.get("sequence"),
179
- "part_kind": part.get("part_kind"),
185
+ "part_kind": part_kind,
180
186
  "source": part.get("source"),
181
187
  "created_at": part.get("created_at"),
182
188
  "content_preview": truncate(content, 600 if full else 240),
@@ -184,6 +190,12 @@ def _part_summary(part: dict, idx: int, *, full: bool = False) -> dict:
184
190
  "attachment_count": len(part.get("attached_files") or []),
185
191
  "feedback_count": len(part.get("feedbacks") or []),
186
192
  }
193
+ if is_tool_part and isinstance(raw_content, dict):
194
+ row.update({
195
+ "tool_name": raw_content.get("tool_name"),
196
+ "tool_call_id": raw_content.get("tool_call_id"),
197
+ "outcome": raw_content.get("outcome"),
198
+ })
187
199
  if full:
188
200
  row["feedbacks"] = [
189
201
  {
@@ -247,24 +259,49 @@ def run_get(args, client) -> int:
247
259
 
248
260
 
249
261
  def run_conversations(args, client) -> int:
250
- result = chats_mod.list_messages(client, args.history_id)
251
- parts = result.get("messages") or []
252
262
  if args.full and not args.out:
253
263
  log("error: full conversation payloads are unbounded; pass --out <path>")
254
264
  return 2
265
+ client_visible = bool(getattr(args, "client_visible", False))
266
+ external_user_id = getattr(args, "user", None)
267
+ if client_visible and not external_user_id:
268
+ log("error: --client-visible requires --user <external_user_id>")
269
+ return 2
270
+ if external_user_id and not client_visible:
271
+ log("error: --user is only valid with --client-visible")
272
+ return 2
273
+
274
+ if client_visible:
275
+ result = chats_mod.list_messages(
276
+ client,
277
+ args.history_id,
278
+ external_user_id=external_user_id,
279
+ )
280
+ export_mode = "client-visible"
281
+ else:
282
+ result = hist_mod.list_messages(client, args.history_id)
283
+ export_mode = "management"
284
+ parts = result.get("messages") or []
255
285
  write_json(args.out, result)
256
286
  group_ids = {
257
287
  p.get("conversation_group_id")
258
288
  for p in parts
259
289
  if p.get("conversation_group_id")
260
290
  }
291
+ stdout_limit = 50 if args.full else 20
292
+ shown_parts = parts[:stdout_limit]
261
293
  print_json({
262
294
  "history_id": args.history_id,
295
+ "export_mode": export_mode,
296
+ "export_contract": result.get("export_contract"),
297
+ "part_revision": result.get("part_revision"),
263
298
  "turn_count": len(group_ids),
264
299
  "part_count": len(parts),
300
+ "part_summaries_shown": len(shown_parts),
301
+ "part_summaries_truncated": len(parts) > len(shown_parts),
265
302
  "wrote_full_detail": bool(args.out),
266
303
  "stdout_is_summary": True,
267
- "parts": [_part_summary(p, i, full=args.full) for i, p in enumerate(parts)],
304
+ "parts": [_part_summary(p, i, full=args.full) for i, p in enumerate(shown_parts)],
268
305
  })
269
306
  return 0
270
307