@chalksurf/cli 0.3.6 → 0.4.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/docs/agents.md CHANGED
@@ -53,25 +53,29 @@ In `--json` mode:
53
53
  - known failures still produce JSON on stdout
54
54
  - stderr is reserved for unexpected runtime failures
55
55
 
56
- Wait-style failures still include a populated `result` payload, so agents can inspect partial outcomes on exit code `6` or `7`.
57
- When an import requests translations, `--wait` waits for the import job only. Follow-up translation jobs are listed under each completed import job's `translationJobs`.
56
+ Add `--result-file PATH` to any operational command when the complete terminal envelope should survive outside agent context. Both successful and failed envelopes are published. The destination must be new and its parent must already exist; obvious path failures stop the command before it runs. After publication, stdout contains a compact envelope with the absolute `resultFile` path. If publication loses a race or otherwise fails after execution, stdout falls back to the complete envelope with one warning and the original exit code is preserved.
57
+
58
+ Wait-style failures still include a populated `result` payload, so agents can inspect partial outcomes on exit codes `6`–`9` or `130`.
59
+ Import waits observe primary jobs and authoritative translation-import children. Automatic generation and metadata aftercare remain separately reported follow-ups in `result.observation`.
58
60
 
59
61
  ## Shared CLI/MCP Primitive Contract
60
62
 
61
- The CLI and MCP surface are backed by the same agent resource contracts in `packages/shared/src/agent-tools`. Prefer these primitives over UI-only assumptions or direct database access:
63
+ CLI 0.4 content commands use versioned CLI contracts and reuse the existing domain authorization and persistence services. MCP contracts are unchanged. Content results expose `items[]` containing receipts/jobs plus derived `summary` counts; unrelated commands retain their existing receipt formats. Prefer these primitives over direct database access:
62
64
 
63
65
  - Read tools return complete resource details or focused repair queues.
64
66
  - Write tools return a `receipt` with `resource`, `operation`, `changedPaths`, `precondition`, `warnings`, and `details`.
65
67
  - Patch-style writes require `expectedUpdatedAt` from a fresh `get` call.
66
68
  - Exercise updates require shared-usage care. Use `exercise usage` first when the exercise might appear outside the target sheet.
67
- - Agent exercise create/update writes are stricter than the human UI: invalid LaTeX is rejected with `agentErrorCode: "invalid_latex"` instead of being saved with a warning. Validate proposed LaTeX with `exercise validate-latex` before applying content updates.
69
+ - Agent exercise create/update writes are stricter than the human UI: invalid LaTeX is rejected with `agentErrorCode: "invalid_latex"` instead of being saved with a warning. Validate proposed LaTeX with `exercise latex validate` before applying content updates.
68
70
  - For exercise creates, the server validates all submitted normalized translation fields. For exercise updates, the server validates only translation fields explicitly present in `patch.translations`; omitted invalid fields do not block unrelated partial updates.
69
71
  - Successful exercise create/update receipts include `details.latexValidation`. The scope is `all_translation_fields`, `changed_translation_fields`, or `none` when no translation content changed.
70
72
  - Failed invalid-LaTeX writes include `error.agentErrorDetails.results[].patchPath`, `validation.issues[].code`, `message`, `snippet`, offsets, and optional `hint` values so agents can repair and retry without guessing.
71
73
  - Use figure tools to create and attach figures to exercises. Use `figure capabilities` before drafting, visually review `figure render-draft`, then upload and attach only after the figure looks correct.
72
- - Use `exercise labels` / `list_exercise_labels` before writing labels. Exercise create and update reject labels outside the canonical taxonomy.
74
+ - Use `exercise label list` / `list_exercise_labels` before writing labels. Exercise create and update reject labels outside the canonical taxonomy.
73
75
  - Delete and visibility tools are exposed, but destructive writes require exact resource-ID confirmation and current `expectedUpdatedAt` values where applicable.
74
- - After any uncertain bulk-sheet apply response, submit the exact update list to CLI `sheet bulk-update verify` or MCP `verify_bulk_sheet_updates`. It reads normalized target state without writing or creating an audit entry; target matches are classified before stale preconditions so a completed apply can be identified safely. Never retry the apply blindly.
76
+ - After an uncertain CLI apply response, use `plan verify PLAN_FILE`; for direct content requests inspect `job list --request-id ID`, including its durable `acceptance` record. Read failure never authorizes replay.
77
+ - Save executable plans with `--dry-run --plan-out FILE`, review every item, then use `plan apply FILE`. Result envelopes cannot be executed. The CLI binds origin and organization; the server rechecks authority and complete reviewed effects.
78
+
75
79
  - No transfer-between-organization agent primitive is intentionally exposed.
76
80
 
77
81
  Write receipts are the agent audit log. Preserve them in run notes or user-visible summaries when the user needs to double-check what changed.
@@ -84,25 +88,26 @@ Use these commands after authentication and organization selection:
84
88
  | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
85
89
  | Save a complete owned-sheet inventory | `chalksurf sheet list --all --result-file inventory.json --json` |
86
90
  | Fetch one sheet | `chalksurf sheet get SHEET_ID --json` |
87
- | List owned sheet versions | `chalksurf sheet versions SHEET_ID --json` |
88
- | Fetch one owned sheet version | `chalksurf sheet version SHEET_ID VERSION_ID --json` |
91
+ | List owned sheet versions | `chalksurf sheet version list SHEET_ID --json` |
92
+ | Fetch one owned sheet version | `chalksurf sheet version get SHEET_ID VERSION_ID --json` |
89
93
  | Get a sheet repair queue | `chalksurf sheet issues SHEET_ID --json` |
90
94
  | Create a sheet | `chalksurf sheet create --input-json JSON --json` |
91
95
  | Copy a sheet | `chalksurf sheet copy SHEET_ID "New name" --exercise-copy-mode keep_references --json` |
92
96
  | Create an editable variant sheet | `chalksurf sheet copy SHEET_ID "Variant name" --exercise-copy-mode copy_all_exercises --json` |
93
- | Append existing exercises to a sheet | `chalksurf sheet append SHEET_ID EXERCISE_ID... --json` |
94
- | Patch sheet metadata | `chalksurf sheet update SHEET_ID --expected-updated-at ISO --patch-json JSON --json` |
97
+ | Append existing exercises to a sheet | `chalksurf sheet exercise append SHEET_ID EXERCISE_ID... --json` |
98
+ | Patch sheet metadata | `chalksurf sheet update SHEET_ID --expected-updated-at ISO --input patch.json --json` |
95
99
  | List valid sheet-series paths | `chalksurf sheet series list --subject math --json` |
96
- | Validate/apply bulk sheet patches | `chalksurf sheet bulk-update --input batch.json --json` |
97
- | Verify an uncertain bulk apply | `chalksurf sheet bulk-update verify --input batch.json --result-file verify.json --json` |
98
- | Set sheet visibility | `chalksurf sheet set-visibility SHEET_ID --public --expected-updated-at ISO --confirm-make-public-resource-id SHEET_ID --json` |
100
+ | Validate/apply bulk sheet patches | `chalksurf sheet update --manifest batch.json --dry-run --plan-out plan.json --json` |
101
+ | Apply a reviewed bulk dry-run | `chalksurf plan apply plan.json --result-file apply.json --json` |
102
+ | Verify an uncertain bulk apply | `chalksurf plan verify plan.json --result-file verify.json --json` |
103
+ | Set sheet visibility | `chalksurf sheet visibility set SHEET_ID --public --expected-updated-at ISO --confirm-make-public-resource-id SHEET_ID --json` |
99
104
  | Delete a sheet | `chalksurf sheet delete SHEET_ID --expected-updated-at ISO --confirm-resource-id SHEET_ID --json` |
100
105
  | List folders | `chalksurf sheet folder list --json` |
101
106
  | Create a folder | `chalksurf sheet folder create "Folder name" --parent-id FOLDER_ID --json` |
102
107
  | Rename a folder | `chalksurf sheet folder rename FOLDER_ID "New name" --json` |
103
108
  | Delete an empty folder | `chalksurf sheet folder delete FOLDER_ID --confirm-resource-id FOLDER_ID --json` |
104
109
  | Fetch one exercise | `chalksurf exercise get EXERCISE_ID --json` |
105
- | List valid exercise labels | `chalksurf exercise labels --json` |
110
+ | List valid exercise labels | `chalksurf exercise label list --json` |
106
111
  | Inspect exercise usage | `chalksurf exercise usage EXERCISE_ID --json` |
107
112
  | List active quality issues | `chalksurf quality-issue list --profile PROFILE --organization ORG_ID --json` |
108
113
  | Resolve a repaired quality issue | `chalksurf quality-issue resolve ISSUE_ID --expected-exercise-updated-at ISO --profile PROFILE --organization ORG_ID --json` |
@@ -113,10 +118,10 @@ Use these commands after authentication and organization selection:
113
118
  | Upload and attach a figure image | `chalksurf exercise figure upload EXERCISE_ID ./figure.png --figure-type text --json` |
114
119
  | Create an exercise | `chalksurf exercise create --input-json JSON --json` |
115
120
  | Copy one exercise | `chalksurf exercise copy EXERCISE_ID --json` |
116
- | Patch exercise fields | `chalksurf exercise update EXERCISE_ID --expected-updated-at ISO --patch-json JSON --json` |
117
- | Set exercise visibility | `chalksurf exercise set-visibility EXERCISE_ID --public --expected-updated-at ISO --confirm-make-public-resource-id EXERCISE_ID --json` |
121
+ | Patch exercise fields | `chalksurf exercise update EXERCISE_ID --expected-updated-at ISO --input patch.json --json` |
122
+ | Set exercise visibility | `chalksurf exercise visibility set EXERCISE_ID --public --expected-updated-at ISO --confirm-make-public-resource-id EXERCISE_ID --json` |
118
123
  | Delete an unused exercise | `chalksurf exercise delete EXERCISE_ID --expected-updated-at ISO --confirm-resource-id EXERCISE_ID --json` |
119
- | Validate LaTeX snippets | `chalksurf exercise validate-latex --snippet "path=value" --json` |
124
+ | Validate LaTeX snippets | `chalksurf exercise latex validate --snippet "path=value" --json` |
120
125
  | Send user-consented feedback | `chalksurf feedback user --message "..." --user-consent --json` |
121
126
  | Send agent-observed feedback | `chalksurf feedback agent --message "..." --json` |
122
127
 
@@ -143,7 +148,7 @@ chalksurf --profile prod-codex sheet issues SHEET_ID --json
143
148
  3. Validate proposed replacements before writing:
144
149
 
145
150
  ```bash
146
- chalksurf --profile prod-codex exercise validate-latex \
151
+ chalksurf --profile prod-codex exercise latex validate \
147
152
  --snippet "translations.english.exercise_text=Find $x^2$." \
148
153
  --json
149
154
  ```
@@ -154,8 +159,9 @@ chalksurf --profile prod-codex exercise validate-latex \
154
159
  chalksurf --profile prod-codex exercise update EXERCISE_ID \
155
160
  --target-sheet-id SHEET_ID \
156
161
  --expected-updated-at 2026-01-01T00:00:00.000Z \
157
- --patch-json '{"translations":{"english":{"exercise_text":"Find $x^2$."}}}' \
158
- --json
162
+ --input - --json <<'JSON'
163
+ {"translations":{"english":{"exercise_text":"Find $x^2$."}}}
164
+ JSON
159
165
  ```
160
166
 
161
167
  5. Re-run `sheet issues SHEET_ID --json` until the invalid LaTeX count is zero or the remaining items need human judgment.
@@ -211,7 +217,7 @@ MCP clients should use tool discovery from the client. The ChalkSurf MCP endpoin
211
217
  | `org list` | `list_organizations` |
212
218
  | `exercise search` | `search_exercises` |
213
219
  | `exercise get` | `get_exercise` |
214
- | `exercise labels` | `list_exercise_labels` |
220
+ | `exercise label list` | `list_exercise_labels` |
215
221
  | `exercise usage` | `get_exercise_usage` |
216
222
  | `quality-issue list` | `list_quality_issues` |
217
223
  | `quality-issue resolve` | `resolve_quality_issue` |
@@ -222,30 +228,32 @@ MCP clients should use tool discovery from the client. The ChalkSurf MCP endpoin
222
228
  | `exercise create` | `create_exercise` |
223
229
  | `exercise copy` | `copy_exercise` |
224
230
  | `exercise update` | `update_exercise` |
225
- | `exercise set-visibility` | `set_exercise_visibility` |
226
- | `exercise translate` | `generate_exercise_translation_job` |
231
+ | `exercise visibility set` | `set_exercise_visibility` |
232
+ | `exercise translation generate` | `generate_exercise_translation_job` |
227
233
  | `exercise delete` | `delete_exercise` |
228
- | `exercise validate-latex` | `validate_latex_snippets` |
234
+ | `exercise latex validate` | `validate_latex_snippets` |
229
235
  | `exercise import` | `import_exercise` |
230
- | `exercise import-solution` | `import_exercise_solution` |
236
+ | `exercise solution import` | `import_exercise_solution` |
231
237
  | `sheet list` | `list_sheets` |
232
238
  | `sheet search` | `search_sheets` |
233
239
  | `sheet get` | `get_sheet` |
234
- | `sheet versions` | `list_sheet_versions` |
235
- | `sheet version` | `get_sheet_version` |
240
+ | `sheet version list` | `list_sheet_versions` |
241
+ | `sheet version get` | `get_sheet_version` |
236
242
  | `sheet issues` | `get_sheet_issues` |
237
243
  | `sheet create` | `create_sheet` |
238
244
  | `sheet copy` | `copy_sheet` |
239
- | `sheet append` | `append_exercises_to_sheet` |
245
+ | `sheet exercise append` | `append_exercises_to_sheet` |
240
246
  | `sheet update` | `update_sheet` |
241
247
  | `sheet series list` | `list_sheet_series` |
242
- | `sheet bulk-update` | `bulk_update_sheets` |
243
- | `sheet bulk-update verify` | `verify_bulk_sheet_updates` |
244
- | `sheet set-visibility` | `set_sheet_visibility` |
245
- | `sheet translate` | `generate_sheet_translation_job` |
248
+ | `sheet update --manifest` | `bulk_update_sheets` |
249
+ | `plan apply` for sheet updates | `bulk_update_sheets` |
250
+ | `plan verify` for sheet updates | `verify_bulk_sheet_updates` |
251
+ | `sheet visibility set` | `set_sheet_visibility` |
252
+ | `sheet translation generate` | `generate_sheet_translation` |
246
253
  | `sheet delete` | `delete_sheet` |
247
254
  | `sheet import` | `import_sheet` |
248
- | `sheet import-solutions` | `import_sheet_solutions` |
255
+ | `sheet solution import` | `import_sheet_solutions` |
256
+ | `sheet translation import` | `import_sheet_translation` |
249
257
  | `sheet folder list` | `list_folders` |
250
258
  | `sheet folder create` | `create_folder` |
251
259
  | `sheet folder rename` | `rename_folder` |
@@ -256,15 +264,13 @@ MCP clients should use tool discovery from the client. The ChalkSurf MCP endpoin
256
264
  | `feedback user` | `send_user_feedback` |
257
265
  | `feedback agent` | `send_agent_feedback` |
258
266
 
259
- `sheet bulk-update` and `sheet bulk-update verify` file/stdin handling is a CLI transport convenience. MCP clients pass the same strict objects directly to `bulk_update_sheets` and `verify_bulk_sheet_updates`; the dry-run, confirmation-digest, batch limit, atomic-apply, and recovery contracts are identical.
267
+ CLI `sheet update --manifest FILE --dry-run --plan-out PLAN` and `plan apply/verify PLAN` use the versioned saved-plan contract. MCP callers continue to pass their existing strict objects to `bulk_update_sheets` and `verify_bulk_sheet_updates`; CLI plan files are not MCP inputs. The underlying atomic sheet persistence and authorization rules remain shared.
260
268
 
261
- The verifier accepts the same `updates` array and returns per-sheet `applied`, `pending`, `conflicting`, or `error` results plus aggregate counts. Although it does not mutate data, it requires write authorization, administrator access, and organization-owned sheets because it verifies the intent of the privileged bulk update operation. Make only one bounded apply attempt; after a lost response, verify the original manifest. Stop for `applied`, rerun dry-run before applying `pending`, and inspect `conflicting` or `error` results.
269
+ The MCP verifier retains `applied`, `pending`, `conflicting` and `error` statuses; CLI verification uses `desired`, `original`, `conflicting` and `unreadable`. Matching current values never prove authorship. Series-assignment changes require administrator access; `expectedSeriesAssignments` alone is a non-mutating precondition. Preserve all affected sheet IDs and per-item receipts rather than attributing a batch to its first sheet.
262
270
 
263
- For `bulk_update_sheets`, `receipt.resource` retains the legacy first-sheet value only for CLI/MCP wire compatibility. Treat `receipt.details.sheetIds` and the per-sheet results as the authoritative affected-resource set; the audit log intentionally does not attribute the operation to that arbitrary first sheet.
271
+ CLI `--result-file` is available on every operational command. It exclusively creates a private full JSON envelope for either success or failure and prints a compact stdout result. Existing evidence is never overwritten. If publication fails after a terminal result, the CLI prints the complete response with a warning and preserves the original exit code; preserve stdout and never retry a confirmed mutation only because artifact publication failed. For complete inventories, use `sheet list --all` and require `result.snapshot.complete: true` in the artifact before deriving writes. Collection uses keyset paging and compares a server membership token on every page, so the flag detects same-count membership drift; it is not a transactionally stable snapshot of every sheet field. `--result-file` and automatic `--all` orchestration are CLI conveniences. MCP callers can page explicitly with `afterId`.
264
272
 
265
- CLI `--result-file` is available on `sheet list`, `sheet bulk-update`, and `sheet bulk-update verify`. It exclusively creates a private full JSON envelope and prints a compact stdout summary. Existing evidence is never overwritten. If publication fails after a confirmed apply, the CLI prints the complete confirmed response with a warning and exits successfully; preserve stdout and never retry that apply. For complete inventories, use `sheet list --all` and require `result.snapshot.complete: true` in the artifact before deriving writes. Collection uses keyset paging and compares a server membership token on every page, so the flag detects same-count membership drift; it is not a transactionally stable snapshot of every sheet field. `--result-file` and automatic `--all` orchestration are CLI conveniences. MCP callers can page explicitly with `afterId`.
266
-
267
- MCP calls authenticate through ChalkSurf OAuth, not CLI tokens. Tools advertise `chalksurf:read` or `chalksurf:write` during authorization, while the editable MCP grant's current per-organization permission is the authoritative runtime read/write boundary. For normal execution, each organization-scoped MCP call must pass a full `organizationId` from `get_auth_status` or `list_organizations`. If it is unknown, pass `organizationId: null`; no operation runs, and the response returns the complete current organization catalog for retry. `organization_selection_required` means a UUID must be selected, while `organization_access_denied` means the supplied UUID is not currently usable. Grant and membership edits take effect on the next HTTP request without reloading tools or issuing a new access token. Never infer the organization from a resource ID. Organization-independent tools omit the field, though `list_exercise_labels`, `list_sheet_series`, and `validate_latex_snippets` temporarily ignore an optional legacy UUID from cached tool definitions. ChalkSurf serves modern MCP `2026-07-28` and stateless legacy `2025-11-25` on the same endpoint.
273
+ MCP calls authenticate through ChalkSurf OAuth, not CLI tokens. Tools advertise the single `chalksurf:mcp` resource scope during authorization, while the editable MCP grant's current per-organization permission is the authoritative runtime read/write boundary. For normal execution, each organization-scoped MCP call must pass a full `organizationId` from `get_auth_status` or `list_organizations`. If it is unknown, pass `organizationId: null`; no operation runs, and the response returns the complete current organization catalog for retry. `organization_selection_required` means a UUID must be selected, while `organization_access_denied` means the supplied UUID is not currently usable. Grant and membership edits take effect on the next HTTP request without reloading tools or issuing a new access token. Never infer the organization from a resource ID. Organization-independent tools omit the field, though `list_exercise_labels`, `list_sheet_series`, and `validate_latex_snippets` temporarily ignore an optional legacy UUID from cached tool definitions. ChalkSurf serves modern MCP `2026-07-28` and stateless legacy `2025-11-25` on the same endpoint.
268
274
 
269
275
  ## Feedback
270
276
 
@@ -281,8 +287,8 @@ Why:
281
287
  - multi-source imports stay explicit
282
288
  - each logical source can carry a stable `sourceId`
283
289
  - sheet imports can group multiple source files into one resulting sheet
284
- - sheet imports carry `targetFolderPath`, `title`, and `translateTo` at the sheet level
285
- - exercise imports can carry top-level `translateTo` for every imported exercise
290
+ - sheet imports carry `targetFolderPath`, `title`, and `targetLanguages` at the sheet level
291
+ - exercise imports can carry item-level `targetLanguages` for every imported exercise
286
292
  - sheet solution imports can reconcile one or more solution files against all updatable exercises in one existing sheet
287
293
  - agents can correlate import results back to the discovered source set
288
294
 
@@ -290,13 +296,14 @@ Use `sourceId` whenever a browsing step or upstream scraper already has a stable
290
296
 
291
297
  ```json
292
298
  {
293
- "organizationId": "00000000-0000-4000-8000-000000000001",
294
- "wait": true,
295
- "sheets": [
299
+ "schemaVersion": "v1",
300
+ "operation": "sheet.import",
301
+ "items": [
296
302
  {
303
+ "itemId": "oktv-2014-round-1",
297
304
  "targetFolderPath": "OKTV/2014",
298
305
  "title": "OKTV 2014 Round 1",
299
- "translateTo": ["english"],
306
+ "targetLanguages": ["english"],
300
307
  "sources": [
301
308
  {
302
309
  "sourceId": "oktv-2014-round-1-part-a",
@@ -323,6 +330,7 @@ Further reference:
323
330
  - [Exercise import schema](../schemas/exercise-import-manifest.schema.json)
324
331
  - [Exercise solution import schema](../schemas/exercise-solution-import-manifest.schema.json)
325
332
  - [Exercise sheet solution import schema](../schemas/exercise-sheet-solution-import-manifest.schema.json)
333
+ - [Exercise sheet translation import schema](../schemas/exercise-sheet-translation-import-manifest.schema.json)
326
334
 
327
335
  ## JSON Result Shape
328
336
 
@@ -429,7 +437,7 @@ chalksurf --profile prod-codex exercise search \
429
437
  Import solutions for an already imported sheet when the solution key is separate:
430
438
 
431
439
  ```bash
432
- chalksurf --profile prod-codex sheet import-solutions \
440
+ chalksurf --profile prod-codex sheet solution import \
433
441
  00000000-0000-4000-8000-000000000001 \
434
442
  ./solutions.pdf \
435
443
  --wait \
@@ -448,11 +456,11 @@ Recommended workflow:
448
456
 
449
457
  1. Codex browses the archive page and collects the relevant links.
450
458
  2. Codex filters the links to years `2010` through `2020`.
451
- 3. Codex builds a sheet-import manifest with one `sheets[]` entry per resulting sheet.
459
+ 3. Codex builds a sheet-import manifest with one `items[]` entry per resulting sheet.
452
460
  4. Codex groups multiple source files inside one `sources[]` array when they belong to the same resulting sheet.
453
- 5. Codex sets `translateTo: ["english"]` on the sheet entries that should produce an English translation.
461
+ 5. Codex sets `targetLanguages: ["english"]` on the sheet entries that should produce an English translation.
454
462
  6. Codex runs `chalksurf --profile prod-codex sheet import --manifest - --wait --json`.
455
- 7. Codex inspects `result.summary.failed`, `result.summary.timedOut`, and `jobs` to decide whether to retry or report failures.
463
+ 7. Codex inspects `result.items`, `result.summary`, `result.observation` and the request ID. A timeout or uncertain response is not permission to reimport. Use request/job recovery before any intentional resend.
456
464
 
457
465
  Minimal shell shape:
458
466
 
@@ -471,8 +479,13 @@ Use the exit code and JSON `error.code` together:
471
479
  - `4` / `source_resolution_failed`: local file, directory, or URL preparation failed
472
480
  - `5` / `api_error`: the ChalkSurf API rejected the request
473
481
  - `6` / `wait_timed_out`: jobs did not finish in time
474
- - `7` / `job_failed`: one or more jobs finished with failure
482
+ - `7` / `job_failed`: unsuccessful-only work, including blocked previews, cancellation or not-solved results
483
+ - `8` / `mixed_results`: mixed successful/accepted and unsuccessful items
484
+ - `9` / `unknown_write`: acceptance is uncertain; preserve request evidence and inspect before resending
485
+ - `130` / `interrupted`: local dispatch or observation stopped; remote work continues
475
486
 
476
487
  Reference:
477
488
 
478
489
  - [Exit codes and JSON errors](./exit-codes.md)
490
+
491
+ CLI generation, draft translation, saved-plan eligibility, input limits and file-relative path rules are detailed in [content operations](./content-operations.md). The packaged [content manifest schema](../schemas/content-manifest.schema.json) and [plan schema](../schemas/content-plan.schema.json) describe shape; runtime checks also validate digests and authority.
@@ -0,0 +1,36 @@
1
+ # Content operations in CLI 0.4
2
+
3
+ Content manifests have `schemaVersion: "v1"`, a canonical `operation`, and explicit `items` with unique `itemId` values. Targets, operation settings, and input transports cannot compete. Use `--input FILE` for one structured request, or `--manifest FILE` for repeated requests. Use `-` for stdin. JSON reads stop at 16 MiB. The maximum known expansion is 100 targets; sheet import accepts at most 20 logical groups.
4
+
5
+ Local paths in a manifest resolve relative to the manifest file. Paths read from stdin resolve relative to the invocation directory. Ordered source groups, components and authoritative language groups retain their order and identity.
6
+
7
+ Before dispatch, the CLI exclusively publishes a private attempt file under `operations/` beside its resolved configuration file. `--request-file PATH` selects another destination. Existing files and symlinks are never overwritten. A publication failure prevents submission. Attempt files contain request and item identity plus input digests, not source bytes or credentials; source URL credentials, queries and fragments are redacted, with their digest retained. Keep the original input for an intentional resend.
8
+
9
+ `--request-id UUID` deliberately reuses the identity of an exact prior request. The server returns original accepted work rather than queueing duplicates; changing its input conflicts. The CLI never automatically retries a write. After an uncertain response (exit 9), inspect `job list --request-id UUID` and the attempt evidence before resending. Interrupting local observation or dispatch exits 130 and leaves already accepted remote work running.
10
+
11
+ Generate with `exercise solution generate ID [ID..]`, `sheet solution generate ID [ID..]`, or `exercise description generate ID [ID..]`. Exercise generation also accepts `--sheet-id ID` as its sole selector. Use `--replace-existing` for existing solution fields and `--allow-shared-exercise-update` to acknowledge shared-use impact. Multiple replacements require a reviewed plan. Description generation replaces descriptions by definition.
12
+
13
+ Persisted `exercise translation generate` and `sheet translation generate` use repeatable `--target-language`. Sheet translation optionally takes `--source-language`; the result reports the resolved source. `exercise translation generate --draft --input FILE` translates only explicit draft fields without persistence. Adding `--dry-run` validates without provider execution.
14
+
15
+ For a single update, use `exercise update ID --expected-updated-at TIMESTAMP --input patch.json` or the corresponding sheet command. A repeated update manifest requires preview and plan application. `--patch-json` is removed from these update commands.
16
+
17
+ Remove a published sheet language with `sheet translation remove ID --target-language LANGUAGE --confirm-resource-id ID`. Preview with `--dry-run`. Shared exercise translations and the last ready sheet language are preserved. Use `job cancel` for pending work; there is no translation-specific cancel command.
18
+
19
+ ## Saved plans
20
+
21
+ Only exercise/sheet updates, solution generation and description generation support executable plans. Save one with the operation's `--dry-run --plan-out plan.json`; this does not create remote work. A preview containing blocked entries exits nonzero even when eligible independent entries can later be applied. Imports and translations have previews but no executable plans.
22
+
23
+ ```sh
24
+ chalksurf --profile prod-codex sheet update --manifest patches.json --dry-run --plan-out plan.json --json
25
+ chalksurf --profile prod-codex plan show plan.json --json
26
+ chalksurf --profile prod-codex plan apply plan.json --result-file applied.json --json
27
+ chalksurf --profile prod-codex plan verify plan.json --json
28
+ ```
29
+
30
+ `plan show` is entirely local and requires no authentication. `plan apply` accepts no target, policy, patch or request-ID overrides. The selected API environment and organization must match the artifact. Its digest detects edits; it is not proof of human approval. The server authorizes and revalidates the full reviewed scope. Sheet batches retain native atomicity; independent exercise entries retain explicit per-item outcomes.
31
+
32
+ Use a new private `--request-file` for each apply attempt, or retain the automatically created path. An intentional exact resend uses the saved request ID and returns already accepted results even after later content edits. Never infer permission to replay from a failed lookup.
33
+
34
+ `plan verify` is read-only: updates report `desired`, `original`, `conflicting` or `unreadable` current state; matching values do not establish authorship. Generation verifies recorded acceptance and jobs, including retained identity when detailed results expire. `--wait` is available only on asynchronous `plan apply` and observes primary jobs. Follow-up state is reported separately.
35
+
36
+ Saved plans and `--result-file` envelopes are different formats. Old `sheet bulk-update` dry-run envelopes cannot be applied; regenerate from the original patches using the v1 manifest and `--plan-out`. If result publication fails after a response, the complete stdout fallback and original command exit remain authoritative.
@@ -0,0 +1,48 @@
1
+ {
2
+ "schemaVersion": "v1",
3
+ "operation": "sheet.import",
4
+ "items": [
5
+ {
6
+ "itemId": "sheet-1",
7
+ "targetFolderPath": "School Leaving Examination/2025",
8
+ "title": "2025 Mathematics in a foreign language",
9
+ "base": {
10
+ "language": "hungarian",
11
+ "sources": [
12
+ {
13
+ "sourceId": "mathematics-2025-hungarian-statement",
14
+ "kind": "url",
15
+ "url": "https://example.com/mathematics-2025-hungarian-statement.pdf"
16
+ },
17
+ {
18
+ "sourceId": "mathematics-2025-hungarian-marking-guide",
19
+ "kind": "url",
20
+ "url": "https://example.com/mathematics-2025-hungarian-marking-guide.pdf"
21
+ }
22
+ ]
23
+ },
24
+ "translations": [
25
+ {
26
+ "language": "english",
27
+ "sources": [
28
+ {
29
+ "sourceId": "mathematics-2025-english-statement",
30
+ "kind": "url",
31
+ "url": "https://example.com/mathematics-2025-english-statement.pdf"
32
+ }
33
+ ]
34
+ },
35
+ {
36
+ "language": "german",
37
+ "sources": [
38
+ {
39
+ "sourceId": "mathematics-2025-german-statement",
40
+ "kind": "local",
41
+ "path": "./mathematics-2025-german-statement.pdf"
42
+ }
43
+ ]
44
+ }
45
+ ]
46
+ }
47
+ ]
48
+ }
@@ -1,14 +1,21 @@
1
1
  {
2
- "organizationId": "org_123",
3
- "exerciseSheetId": "00000000-0000-4000-8000-000000000111",
4
- "translateTo": ["english"],
5
- "wait": true,
6
- "sources": [
2
+ "schemaVersion": "v1",
3
+ "operation": "exercise.import",
4
+ "items": [
7
5
  {
8
- "sourceId": "problem-1",
9
- "kind": "local",
10
- "path": "./imports/problem-1.pdf",
11
- "relativePath": "Problem Set/problem-1.pdf"
6
+ "itemId": "one",
7
+ "exerciseSheetId": "00000000-0000-4000-8000-000000000111",
8
+ "targetLanguages": [
9
+ "english"
10
+ ],
11
+ "sources": [
12
+ {
13
+ "sourceId": "problem-1",
14
+ "kind": "local",
15
+ "path": "./imports/problem-1.pdf",
16
+ "relativePath": "Problem Set/problem-1.pdf"
17
+ }
18
+ ]
12
19
  }
13
20
  ]
14
21
  }
@@ -1,13 +1,18 @@
1
1
  {
2
- "organizationId": "org_123",
3
- "exerciseSheetId": "00000000-0000-4000-8000-000000000333",
4
- "wait": true,
5
- "sources": [
2
+ "schemaVersion": "v1",
3
+ "operation": "sheet.solution.import",
4
+ "items": [
6
5
  {
7
- "sourceId": "problem-set-solutions",
8
- "kind": "local",
9
- "path": "./imports/problem-set-solutions.pdf",
10
- "relativePath": "Solutions/problem-set-solutions.pdf"
6
+ "itemId": "one",
7
+ "exerciseSheetId": "00000000-0000-4000-8000-000000000333",
8
+ "sources": [
9
+ {
10
+ "sourceId": "problem-set-solutions",
11
+ "kind": "local",
12
+ "path": "./imports/problem-set-solutions.pdf",
13
+ "relativePath": "Solutions/problem-set-solutions.pdf"
14
+ }
15
+ ]
11
16
  }
12
17
  ]
13
18
  }
@@ -0,0 +1,20 @@
1
+ {
2
+ "schemaVersion": "v1",
3
+ "operation": "sheet.translation.import",
4
+ "items": [
5
+ {
6
+ "itemId": "one",
7
+ "exerciseSheetId": "00000000-0000-4000-8000-000000000333",
8
+ "sourceLanguage": "hungarian",
9
+ "targetLanguage": "german",
10
+ "sources": [
11
+ {
12
+ "sourceId": "official-german-paper",
13
+ "kind": "url",
14
+ "url": "https://www.oktatas.hu/example/official-german-paper.pdf",
15
+ "relativePath": "official-german-paper.pdf"
16
+ }
17
+ ]
18
+ }
19
+ ]
20
+ }
@@ -1,13 +1,18 @@
1
1
  {
2
- "organizationId": "org_123",
3
- "exerciseId": "00000000-0000-4000-8000-000000000222",
4
- "wait": true,
5
- "sources": [
2
+ "schemaVersion": "v1",
3
+ "operation": "exercise.solution.import",
4
+ "items": [
6
5
  {
7
- "sourceId": "problem-1-solution",
8
- "kind": "local",
9
- "path": "./imports/problem-1-solution.pdf",
10
- "relativePath": "Solutions/problem-1.pdf"
6
+ "itemId": "one",
7
+ "exerciseId": "00000000-0000-4000-8000-000000000222",
8
+ "sources": [
9
+ {
10
+ "sourceId": "problem-1-solution",
11
+ "kind": "local",
12
+ "path": "./imports/problem-1-solution.pdf",
13
+ "relativePath": "Solutions/problem-1.pdf"
14
+ }
15
+ ]
11
16
  }
12
17
  ]
13
18
  }
@@ -1,11 +1,14 @@
1
1
  {
2
- "organizationId": "org_123",
3
- "wait": true,
4
- "sheets": [
2
+ "schemaVersion": "v1",
3
+ "operation": "sheet.import",
4
+ "items": [
5
5
  {
6
+ "itemId": "sheet-1",
6
7
  "targetFolderPath": "OKTV/2014",
7
8
  "title": "OKTV 2014 Round 1",
8
- "translateTo": ["english"],
9
+ "targetLanguages": [
10
+ "english"
11
+ ],
9
12
  "sources": [
10
13
  {
11
14
  "sourceId": "oktv-2014-round-1-part-a",
@@ -20,6 +23,7 @@
20
23
  ]
21
24
  },
22
25
  {
26
+ "itemId": "sheet-2",
23
27
  "targetFolderPath": null,
24
28
  "sources": [
25
29
  {
@@ -30,6 +34,7 @@
30
34
  ]
31
35
  },
32
36
  {
37
+ "itemId": "sheet-3",
33
38
  "sources": [
34
39
  {
35
40
  "sourceId": "combined-archive-file",
@@ -43,7 +48,9 @@
43
48
  "description": "The exercise sheet labeled Round 2, Part A.",
44
49
  "targetFolderPath": "OKTV/2014",
45
50
  "title": "OKTV 2014 Round 2 Part A",
46
- "translateTo": ["english"]
51
+ "targetLanguages": [
52
+ "english"
53
+ ]
47
54
  },
48
55
  {
49
56
  "componentId": "round_2_part_b",
@@ -11,9 +11,12 @@ The CLI uses stable exit codes so automation can branch without parsing human te
11
11
  | `2` | `usage_error` | Invalid arguments, invalid manifest, or missing required config such as `--base-url` |
12
12
  | `3` | `not_authenticated` | Missing token or rejected CLI token |
13
13
  | `4` | `source_resolution_failed` | Local file, directory, URL, or relative path resolution failed |
14
- | `5` | `api_error` | The ChalkSurf API returned a non-auth failure |
14
+ | `5` | `api_error` | API rejection or observation failure; inspect retained state |
15
15
  | `6` | `wait_timed_out` | Waiting ended before all jobs reached a terminal state |
16
- | `7` | `job_failed` | One or more jobs completed with failure |
16
+ | `7` | `job_failed` | Unsuccessful-only work or blocked preview, including cancellation and not-solved |
17
+ | `8` | `mixed_results` | Mixed successful/accepted and unsuccessful items |
18
+ | `9` | `unknown_write` | Acceptance is uncertain; inspect saved request evidence before resending |
19
+ | `130` | `interrupted` | Local dispatch/observation stopped; accepted remote work continues |
17
20
 
18
21
  These same codes are mirrored in `error.exitCode` and `error.code` inside the JSON envelope.
19
22
 
@@ -54,11 +57,18 @@ Known failures also return JSON on stdout:
54
57
  }
55
58
  ```
56
59
 
57
- Invalid agent-written exercise LaTeX returns exit code `5` with `error.agentErrorCode = "invalid_latex"`. In JSON mode, inspect `error.agentErrorDetails.results[].patchPath` and `error.agentErrorDetails.results[].validation.issues[]` to repair the submitted fields and retry.
60
+ Agent LaTeX validation rejects invalid submitted fields. Legacy resource commands can expose `error.agentErrorCode = "invalid_latex"` and structured `agentErrorDetails`; content operations also report per-item blocked/failed outcomes and reasons. Inspect the complete result, validate the affected snippets, and prepare a corrected request rather than blindly resending.
58
61
 
59
62
  ## Contract Notes
60
63
 
61
64
  - `warnings` contains informational messages that would otherwise be printed for humans.
62
65
  - In `--json` mode, stderr is reserved for unexpected runtime failures only.
63
66
  - Wait-style commands and imports keep their `result` payload even when `ok` is `false`.
64
- - Import commands normalize their `result` payloads around `request`, `sources`, `jobs`, and `summary`.
67
+ - Content commands normalize `result` around `operation`, `phase`, `requestId`, `items` and `summary`. Item jobs and receipts retain native details; waited snapshots appear in `observation`. A successful enqueue has `unfinished` items.
68
+ - Every operational command accepts `--result-file PATH`. The artifact contains the complete terminal envelope, including full error details on failure; compact stdout includes the absolute `resultFile` path and essential error fields.
69
+ - Result destinations are preflighted before execution, created with private permissions, and never overwritten. A post-execution publication failure prints the complete envelope with a warning and does not change the command's exit code.
70
+
71
+
72
+ A timeout does not mean the remote operation failed. Unknown acceptance (9) stops further dispatch and retains the request/attempt identity. Cancellation is terminal: ordinary generation waits treat it as unsuccessful, while `job cancel --wait` succeeds when the requested jobs cancel. A completed attempt with `not_solved`, stale/conflicting or missing-content output is not successful generation. Metadata aftercare is reported separately from primary success.
73
+
74
+ `plan verify` reads current deterministic state or recorded async acceptance. It never replays a write. Request-filtered job listing retains known acceptance even if later live-job observation fails. Preserve every available item and job ID on nonzero exits.