@chalksurf/cli 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -16
- package/dist/bin/chalksurf.js +3 -3
- package/dist/commands/exercise.js +178 -6
- package/dist/commands/job.js +108 -40
- package/dist/commands/sheet.js +369 -32
- package/dist/lib/api-client.js +11 -2
- package/dist/lib/command-options.js +61 -0
- package/dist/lib/import-output.js +14 -0
- package/dist/lib/manifest.js +82 -0
- package/dist/lib/session.js +27 -0
- package/dist/lib/translation-languages.js +15 -0
- package/dist/lib/user-jobs.js +14 -0
- package/docs/agents.md +57 -3
- package/docs/examples/exercise-import-manifest.json +1 -0
- package/docs/examples/exercise-sheet-solution-import-manifest.json +13 -0
- package/docs/examples/sheet-import-manifest.json +24 -0
- package/docs/manifest.md +49 -4
- package/docs/manual.md +46 -2
- package/package.json +1 -1
- package/schemas/exercise-import-manifest.schema.json +12 -0
- package/schemas/exercise-sheet-solution-import-manifest.schema.json +104 -0
- package/schemas/sheet-import-manifest.schema.json +78 -22
package/dist/lib/manifest.js
CHANGED
|
@@ -19,6 +19,7 @@ const translationLanguageListSchema = z
|
|
|
19
19
|
.refine((languages) => new Set(languages).size === languages.length, {
|
|
20
20
|
message: 'translateTo languages must be unique.',
|
|
21
21
|
});
|
|
22
|
+
const sheetImportComponentIdPattern = /^[a-zA-Z0-9_-]{1,80}$/;
|
|
22
23
|
const parseTranslateTo = ({ value, label }) => {
|
|
23
24
|
if (value == null) {
|
|
24
25
|
return undefined;
|
|
@@ -70,6 +71,52 @@ const parseTargetFolderPath = ({ value, label }) => {
|
|
|
70
71
|
}
|
|
71
72
|
return normalizedValue;
|
|
72
73
|
};
|
|
74
|
+
const parseOptionalTargetFolderPath = ({ value, label }) => {
|
|
75
|
+
if (value == null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return parseTargetFolderPath({ value, label });
|
|
79
|
+
};
|
|
80
|
+
const parseSheetImportComponents = ({ value, sheetIndex, }) => {
|
|
81
|
+
if (value == null) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
85
|
+
throw new CliCommandError(`Manifest sheet at index ${sheetIndex} components must be a non-empty array.`, 2);
|
|
86
|
+
}
|
|
87
|
+
const seenComponentIds = new Set();
|
|
88
|
+
return value.map((componentValue, componentIndex) => {
|
|
89
|
+
const label = `Manifest sheet at index ${sheetIndex} component at index ${componentIndex}`;
|
|
90
|
+
if (!isObject(componentValue)) {
|
|
91
|
+
throw new CliCommandError(`${label} must be an object.`, 2);
|
|
92
|
+
}
|
|
93
|
+
const componentId = normalizeOptionalString(componentValue.componentId);
|
|
94
|
+
if (!componentId || !sheetImportComponentIdPattern.test(componentId)) {
|
|
95
|
+
throw new CliCommandError(`${label} must include a componentId using 1-80 letters, numbers, underscores, or hyphens.`, 2);
|
|
96
|
+
}
|
|
97
|
+
if (seenComponentIds.has(componentId)) {
|
|
98
|
+
throw new CliCommandError(`Manifest sheet at index ${sheetIndex} componentId "${componentId}" must be unique.`, 2);
|
|
99
|
+
}
|
|
100
|
+
seenComponentIds.add(componentId);
|
|
101
|
+
const description = normalizeOptionalString(componentValue.description);
|
|
102
|
+
if (!description) {
|
|
103
|
+
throw new CliCommandError(`${label} must include a non-empty description.`, 2);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
componentId,
|
|
107
|
+
description,
|
|
108
|
+
targetFolderPath: parseOptionalTargetFolderPath({
|
|
109
|
+
value: componentValue.targetFolderPath,
|
|
110
|
+
label,
|
|
111
|
+
}),
|
|
112
|
+
title: normalizeOptionalString(componentValue.title),
|
|
113
|
+
translateTo: parseTranslateTo({
|
|
114
|
+
value: componentValue.translateTo,
|
|
115
|
+
label,
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
};
|
|
73
120
|
const parseManifestMetadata = ({ parsedManifest, topLevelFields, }) => {
|
|
74
121
|
const wait = typeof parsedManifest.wait === 'boolean' ? parsedManifest.wait : undefined;
|
|
75
122
|
return {
|
|
@@ -161,6 +208,10 @@ const parseExerciseImportManifest = (manifestText) => {
|
|
|
161
208
|
parsedManifest,
|
|
162
209
|
topLevelFields: ['exerciseSheetId'],
|
|
163
210
|
}),
|
|
211
|
+
translateTo: parseTranslateTo({
|
|
212
|
+
value: parsedManifest.translateTo,
|
|
213
|
+
label: 'Manifest',
|
|
214
|
+
}),
|
|
164
215
|
sources,
|
|
165
216
|
};
|
|
166
217
|
};
|
|
@@ -176,6 +227,18 @@ const parseExerciseSolutionImportManifest = (manifestText) => {
|
|
|
176
227
|
sources,
|
|
177
228
|
};
|
|
178
229
|
};
|
|
230
|
+
const parseExerciseSheetSolutionImportManifest = (manifestText) => {
|
|
231
|
+
const parsedManifest = parseSourceManifestObject(manifestText);
|
|
232
|
+
const sources = parsedManifest.sources.map((source, index) => parseBaseManifestSource(source, index));
|
|
233
|
+
assertUniqueSourceIds(sources);
|
|
234
|
+
return {
|
|
235
|
+
...parseManifestMetadata({
|
|
236
|
+
parsedManifest,
|
|
237
|
+
topLevelFields: ['exerciseSheetId'],
|
|
238
|
+
}),
|
|
239
|
+
sources,
|
|
240
|
+
};
|
|
241
|
+
};
|
|
179
242
|
const parseSheetImportManifest = (manifestText) => {
|
|
180
243
|
const parsedManifest = parseManifestObject(manifestText);
|
|
181
244
|
if ('sources' in parsedManifest) {
|
|
@@ -199,6 +262,17 @@ const parseSheetImportManifest = (manifestText) => {
|
|
|
199
262
|
sources.push(parsedSource);
|
|
200
263
|
return flattenedSourceIndex;
|
|
201
264
|
});
|
|
265
|
+
const components = parseSheetImportComponents({ value: sheetValue.components, sheetIndex });
|
|
266
|
+
if (components) {
|
|
267
|
+
if ('targetFolderPath' in sheetValue || 'title' in sheetValue || 'translateTo' in sheetValue) {
|
|
268
|
+
throw new CliCommandError(`Manifest sheet at index ${sheetIndex} must set targetFolderPath, title, and translateTo on components when components are provided.`, 2);
|
|
269
|
+
}
|
|
270
|
+
sheetGroups.push({
|
|
271
|
+
sourceInputIndexes,
|
|
272
|
+
components,
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
202
276
|
sheetGroups.push({
|
|
203
277
|
sourceInputIndexes,
|
|
204
278
|
targetFolderPath: parseTargetFolderPath({
|
|
@@ -261,6 +335,14 @@ export const loadExerciseSolutionImportManifest = async ({ cwd, manifestPath, st
|
|
|
261
335
|
stdin,
|
|
262
336
|
});
|
|
263
337
|
};
|
|
338
|
+
export const loadExerciseSheetSolutionImportManifest = async ({ cwd, manifestPath, stdin, }) => {
|
|
339
|
+
return await loadManifest({
|
|
340
|
+
cwd,
|
|
341
|
+
manifestPath,
|
|
342
|
+
parseManifest: parseExerciseSheetSolutionImportManifest,
|
|
343
|
+
stdin,
|
|
344
|
+
});
|
|
345
|
+
};
|
|
264
346
|
export const loadSheetImportManifest = async ({ cwd, manifestPath, stdin, }) => {
|
|
265
347
|
return await loadManifest({
|
|
266
348
|
cwd,
|
package/dist/lib/session.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createApiClient } from './api-client.js';
|
|
1
2
|
import { CliCommandError } from './cli-error.js';
|
|
2
3
|
import { chalksurfBaseUrlEnvVar, chalksurfOrganizationIdEnvVar, chalksurfTokenEnvVar, resolveBaseUrl, resolveToken, } from './config-store.js';
|
|
3
4
|
const normalizeOptionalString = (value) => {
|
|
@@ -31,6 +32,32 @@ export const resolveRequestedOrganizationId = ({ config, env, fallbackValue, fla
|
|
|
31
32
|
normalizeOptionalString(env[chalksurfOrganizationIdEnvVar]) ??
|
|
32
33
|
normalizeOptionalString(config.organizationId));
|
|
33
34
|
};
|
|
35
|
+
export const createResolvedApiClient = async ({ context, baseUrlFlagValue, organizationFallbackValue, organizationFlagValue, profileName, }) => {
|
|
36
|
+
const config = await context.configStore.loadProfile({ profileName });
|
|
37
|
+
const resolvedBaseUrl = requireResolvedBaseUrl({
|
|
38
|
+
flagValue: baseUrlFlagValue,
|
|
39
|
+
env: context.env,
|
|
40
|
+
config,
|
|
41
|
+
});
|
|
42
|
+
const resolvedToken = requireResolvedToken({
|
|
43
|
+
env: context.env,
|
|
44
|
+
config,
|
|
45
|
+
});
|
|
46
|
+
const organizationId = resolveRequestedOrganizationId({
|
|
47
|
+
flagValue: organizationFlagValue,
|
|
48
|
+
fallbackValue: organizationFallbackValue,
|
|
49
|
+
env: context.env,
|
|
50
|
+
config,
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
apiClient: createApiClient({
|
|
54
|
+
baseUrl: resolvedBaseUrl.value,
|
|
55
|
+
token: resolvedToken.value,
|
|
56
|
+
organizationId,
|
|
57
|
+
}),
|
|
58
|
+
organizationId,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
34
61
|
export const formatUserIdentity = (profile) => {
|
|
35
62
|
if (profile.name && profile.email) {
|
|
36
63
|
return `${profile.name} <${profile.email}>`;
|
|
@@ -1 +1,16 @@
|
|
|
1
|
+
import { CliCommandError } from './cli-error.js';
|
|
1
2
|
export const translationLanguages = ['english', 'hungarian', 'german', 'french', 'spanish', 'italian'];
|
|
3
|
+
export const resolveRequestedTranslateToLanguages = (rawValues) => {
|
|
4
|
+
if (!rawValues || rawValues.length === 0) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const invalidLanguage = rawValues.find((value) => !translationLanguages.includes(value));
|
|
8
|
+
if (invalidLanguage) {
|
|
9
|
+
throw new CliCommandError(`--translate-to must be one of: ${translationLanguages.join(', ')}`, 2);
|
|
10
|
+
}
|
|
11
|
+
const languages = rawValues;
|
|
12
|
+
if (new Set(languages).size !== languages.length) {
|
|
13
|
+
throw new CliCommandError('--translate-to languages must be unique.', 2);
|
|
14
|
+
}
|
|
15
|
+
return languages;
|
|
16
|
+
};
|
package/dist/lib/user-jobs.js
CHANGED
|
@@ -14,7 +14,14 @@ export const serializeCliJob = (job) => {
|
|
|
14
14
|
exerciseId: job.result?.exerciseId,
|
|
15
15
|
exerciseIds: job.result?.exerciseIds,
|
|
16
16
|
exerciseSheetId: job.result?.exerciseSheetId,
|
|
17
|
+
eligibleExerciseCount: job.result?.eligibleExerciseCount,
|
|
18
|
+
nonUpdatableExerciseCount: job.result?.nonUpdatableExerciseCount,
|
|
17
19
|
resultCode: job.result?.resultCode,
|
|
20
|
+
sheets: job.result?.sheets,
|
|
21
|
+
skippedExerciseCount: job.result?.skippedExerciseCount,
|
|
22
|
+
translationJobs: job.result?.translationJobs,
|
|
23
|
+
unmatchedImportedSolutionCount: job.result?.unmatchedImportedSolutionCount,
|
|
24
|
+
updatedExerciseCount: job.result?.updatedExerciseCount,
|
|
18
25
|
};
|
|
19
26
|
};
|
|
20
27
|
export const formatCliJobSummary = (job) => {
|
|
@@ -22,6 +29,13 @@ export const formatCliJobSummary = (job) => {
|
|
|
22
29
|
if (job.exerciseSheetId) {
|
|
23
30
|
return `${job.id} completed -> ${job.exerciseSheetId}`;
|
|
24
31
|
}
|
|
32
|
+
const importedSheets = job.sheets?.filter((sheet) => sheet.status === 'imported') ?? [];
|
|
33
|
+
if (importedSheets.length === 1) {
|
|
34
|
+
return `${job.id} completed -> ${importedSheets[0].exerciseSheetId}`;
|
|
35
|
+
}
|
|
36
|
+
if (importedSheets.length > 1) {
|
|
37
|
+
return `${job.id} completed -> ${importedSheets.length} sheets`;
|
|
38
|
+
}
|
|
25
39
|
if (job.exerciseId) {
|
|
26
40
|
return `${job.id} completed -> ${job.exerciseId}`;
|
|
27
41
|
}
|
package/docs/agents.md
CHANGED
|
@@ -33,7 +33,7 @@ Prefer storing the token in the explicit agent profile for repeated Codex/CI run
|
|
|
33
33
|
For agent-driven imports, always use:
|
|
34
34
|
|
|
35
35
|
- `--manifest -` or a generated manifest file
|
|
36
|
-
- `--wait` when the next step depends on
|
|
36
|
+
- `--wait` when the next step depends on parsed content being saved
|
|
37
37
|
- `--json` so the response stays machine-readable
|
|
38
38
|
|
|
39
39
|
Recommended invocation shape:
|
|
@@ -49,6 +49,7 @@ In `--json` mode:
|
|
|
49
49
|
- stderr is reserved for unexpected runtime failures
|
|
50
50
|
|
|
51
51
|
Wait-style failures still include a populated `result` payload, so agents can inspect partial outcomes on exit code `6` or `7`.
|
|
52
|
+
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`.
|
|
52
53
|
|
|
53
54
|
## Manifest Design
|
|
54
55
|
|
|
@@ -60,6 +61,8 @@ Why:
|
|
|
60
61
|
- each logical source can carry a stable `sourceId`
|
|
61
62
|
- sheet imports can group multiple source files into one resulting sheet
|
|
62
63
|
- sheet imports carry `targetFolderPath`, `title`, and `translateTo` at the sheet level
|
|
64
|
+
- exercise imports can carry top-level `translateTo` for every imported exercise
|
|
65
|
+
- sheet solution imports can reconcile one or more solution files against all updatable exercises in one existing sheet
|
|
63
66
|
- agents can correlate import results back to the discovered source set
|
|
64
67
|
|
|
65
68
|
Use `sourceId` whenever a browsing step or upstream scraper already has a stable identifier:
|
|
@@ -98,6 +101,7 @@ Further reference:
|
|
|
98
101
|
- [Sheet import schema](../schemas/sheet-import-manifest.schema.json)
|
|
99
102
|
- [Exercise import schema](../schemas/exercise-import-manifest.schema.json)
|
|
100
103
|
- [Exercise solution import schema](../schemas/exercise-solution-import-manifest.schema.json)
|
|
104
|
+
- [Exercise sheet solution import schema](../schemas/exercise-sheet-solution-import-manifest.schema.json)
|
|
101
105
|
|
|
102
106
|
## JSON Result Shape
|
|
103
107
|
|
|
@@ -120,6 +124,7 @@ Each job includes:
|
|
|
120
124
|
- `sourceIndexes`
|
|
121
125
|
- `sourceIds`
|
|
122
126
|
- `status`
|
|
127
|
+
- `translationJobs` when requested translations were queued after import
|
|
123
128
|
|
|
124
129
|
`summary` includes:
|
|
125
130
|
|
|
@@ -133,8 +138,9 @@ Each job includes:
|
|
|
133
138
|
2. Filter them to the target scope.
|
|
134
139
|
3. Build a manifest with stable `sourceId` values.
|
|
135
140
|
4. Pipe the manifest into the CLI with `--wait --json`.
|
|
136
|
-
5. Inspect `ok`, `result.summary`,
|
|
137
|
-
6.
|
|
141
|
+
5. Inspect `ok`, `result.summary`, `result.jobs`, and any nested `translationJobs`.
|
|
142
|
+
6. Use `job list`, `job wait`, `sheet search`, and `exercise search` to verify the created resources or translation jobs when needed.
|
|
143
|
+
7. Retry only the failed or timed-out source set.
|
|
138
144
|
|
|
139
145
|
For example:
|
|
140
146
|
|
|
@@ -163,6 +169,54 @@ On success, the envelope looks like:
|
|
|
163
169
|
|
|
164
170
|
On timeout or job failure, `ok` becomes `false`, `error.code` is stable, and `result` still contains the normalized import data needed for retries.
|
|
165
171
|
|
|
172
|
+
## Verification Commands
|
|
173
|
+
|
|
174
|
+
Agents can inspect their own work without opening the web UI. Search commands default to `--ownership own`, which means the selected organization from `--organization`, `CHALKSURF_ORGANIZATION_ID`, or the active profile.
|
|
175
|
+
|
|
176
|
+
List recent jobs, including jobs the UI tray has already viewed or dismissed:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
chalksurf --profile prod-codex job list --include-viewed --include-dismissed --json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Find failed import jobs for retry:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
chalksurf --profile prod-codex job list \
|
|
186
|
+
--status failed \
|
|
187
|
+
--type exercise_sheet_import \
|
|
188
|
+
--include-viewed \
|
|
189
|
+
--include-dismissed \
|
|
190
|
+
--json
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Verify an imported private sheet by title:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
chalksurf --profile prod-codex sheet search --text "OKTV 2014" --json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Verify imported exercises by text. Text search requires an explicit language:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
chalksurf --profile prod-codex exercise search \
|
|
203
|
+
--text "binomial theorem" \
|
|
204
|
+
--language english \
|
|
205
|
+
--json
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Import solutions for an already imported sheet when the solution key is separate:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
chalksurf --profile prod-codex sheet import-solutions \
|
|
212
|
+
00000000-0000-4000-8000-000000000001 \
|
|
213
|
+
./solutions.pdf \
|
|
214
|
+
--wait \
|
|
215
|
+
--json
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Use `--ownership public` for public-library checks and `--ownership all` when the agent intentionally wants public results plus selected-organization results.
|
|
219
|
+
|
|
166
220
|
## Codex Workflow Example
|
|
167
221
|
|
|
168
222
|
Target task:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"organizationId": "org_123",
|
|
3
|
+
"exerciseSheetId": "00000000-0000-4000-8000-000000000333",
|
|
4
|
+
"wait": true,
|
|
5
|
+
"sources": [
|
|
6
|
+
{
|
|
7
|
+
"sourceId": "problem-set-solutions",
|
|
8
|
+
"kind": "local",
|
|
9
|
+
"path": "./imports/problem-set-solutions.pdf",
|
|
10
|
+
"relativePath": "Solutions/problem-set-solutions.pdf"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -28,6 +28,30 @@
|
|
|
28
28
|
"path": "./practice-sheet.pdf"
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"sources": [
|
|
34
|
+
{
|
|
35
|
+
"sourceId": "combined-archive-file",
|
|
36
|
+
"kind": "local",
|
|
37
|
+
"path": "./combined-archive-file.pdf"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"components": [
|
|
41
|
+
{
|
|
42
|
+
"componentId": "round_2_part_a",
|
|
43
|
+
"description": "The exercise sheet labeled Round 2, Part A.",
|
|
44
|
+
"targetFolderPath": "OKTV/2014",
|
|
45
|
+
"title": "OKTV 2014 Round 2 Part A",
|
|
46
|
+
"translateTo": ["english"]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"componentId": "round_2_part_b",
|
|
50
|
+
"description": "The exercise sheet labeled Round 2, Part B.",
|
|
51
|
+
"targetFolderPath": "OKTV/2014",
|
|
52
|
+
"title": "OKTV 2014 Round 2 Part B"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
31
55
|
}
|
|
32
56
|
]
|
|
33
57
|
}
|
package/docs/manifest.md
CHANGED
|
@@ -7,6 +7,7 @@ One manifest file is passed to exactly one command:
|
|
|
7
7
|
- `chalksurf sheet import --manifest <path|->`
|
|
8
8
|
- `chalksurf exercise import --manifest <path|->`
|
|
9
9
|
- `chalksurf exercise import-solution --manifest <path|->`
|
|
10
|
+
- `chalksurf sheet import-solutions --manifest <path|->`
|
|
10
11
|
|
|
11
12
|
Use `--manifest -` to pipe JSON on stdin.
|
|
12
13
|
|
|
@@ -17,9 +18,9 @@ Every manifest is a JSON object with these shared metadata fields:
|
|
|
17
18
|
| Field | Type | Required | Notes |
|
|
18
19
|
| --- | --- | --- | --- |
|
|
19
20
|
| `organizationId` | string | no | Overrides the default organization for this invocation. |
|
|
20
|
-
| `wait` | boolean | no | Behaves like `--wait`. The CLI also accepts `--wait`, which wins if set. |
|
|
21
|
+
| `wait` | boolean | no | Behaves like `--wait`. The CLI also accepts `--wait`, which wins if set. Requested translations run as follow-up jobs listed in the import result. |
|
|
21
22
|
|
|
22
|
-
Every source object supports these common fields. For `sheet import`, sources appear inside `sheets[].sources[]`. For the exercise import commands
|
|
23
|
+
Every source object supports these common fields. For `sheet import`, sources appear inside `sheets[].sources[]`. For the exercise import commands and `sheet import-solutions`, they appear in top-level `sources[]`.
|
|
23
24
|
|
|
24
25
|
| Field | Type | Required | Notes |
|
|
25
26
|
| --- | --- | --- | --- |
|
|
@@ -59,7 +60,7 @@ Top-level fields:
|
|
|
59
60
|
| --- | --- | --- | --- |
|
|
60
61
|
| `sheets` | array | yes | One or more sheets to import. Each sheet may contain one or more source files. |
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
Normal sheet fields:
|
|
63
64
|
|
|
64
65
|
| Field | Type | Required | Notes |
|
|
65
66
|
| --- | --- | --- | --- |
|
|
@@ -72,6 +73,27 @@ Sheet import metadata is sheet-level, not source-level. Do not put `title` or `t
|
|
|
72
73
|
|
|
73
74
|
Source-level `relativePath` is only a source label or filename override. It does not choose the destination folder for grouped sheet imports; `targetFolderPath` is the only destination field.
|
|
74
75
|
|
|
76
|
+
Declared-component sheet fields:
|
|
77
|
+
|
|
78
|
+
Use `components[]` when the same source group contains a known fixed set of component sheets. In this mode, do not put `targetFolderPath`, `title`, or `translateTo` on the sheet itself; put output metadata on each component.
|
|
79
|
+
|
|
80
|
+
| Field | Type | Required | Notes |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| `sources` | array | yes | One or more source files that contain the declared components. |
|
|
83
|
+
| `components` | array | yes | The explicitly requested component sheets to extract from the shared source group. |
|
|
84
|
+
|
|
85
|
+
Component fields:
|
|
86
|
+
|
|
87
|
+
| Field | Type | Required | Notes |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| `componentId` | string | yes | Stable key for this requested component. Must match `/^[a-zA-Z0-9_-]{1,80}$/` and be unique within the sheet group. |
|
|
90
|
+
| `description` | string | yes | Matching instructions that identify this component in the source group. |
|
|
91
|
+
| `targetFolderPath` | string \| null | no | Destination folder for this component. Omit or use `null` for the root folder. |
|
|
92
|
+
| `title` | string | no | Overrides the imported component sheet title. |
|
|
93
|
+
| `translateTo` | string[] | no | Target translation languages for this component. Must be unique and non-empty when present. |
|
|
94
|
+
|
|
95
|
+
The model must return an explicit `import` or `not_present` decision for every declared `componentId`. Jobs complete when at least one component is imported and fail when all declared components are `not_present`.
|
|
96
|
+
|
|
75
97
|
Canonical example:
|
|
76
98
|
|
|
77
99
|
- [docs/examples/sheet-import-manifest.json](./examples/sheet-import-manifest.json)
|
|
@@ -85,11 +107,12 @@ Top-level fields:
|
|
|
85
107
|
| --- | --- | --- | --- |
|
|
86
108
|
| `sources` | array | yes | One or more import sources. |
|
|
87
109
|
| `exerciseSheetId` | string | no | Default target sheet for the imported exercises. Can still be overridden by `--sheet-id`. |
|
|
110
|
+
| `translateTo` | string[] | no | Target translation languages for every imported exercise. Must be unique and non-empty when present. |
|
|
88
111
|
|
|
89
112
|
Per-source fields:
|
|
90
113
|
|
|
91
114
|
- Only the common source fields are allowed.
|
|
92
|
-
- `title` and `translateTo` are rejected for this command.
|
|
115
|
+
- `title` and source-level `translateTo` are rejected for this command.
|
|
93
116
|
|
|
94
117
|
Canonical example:
|
|
95
118
|
|
|
@@ -115,12 +138,34 @@ Canonical example:
|
|
|
115
138
|
- [docs/examples/exercise-solution-import-manifest.json](./examples/exercise-solution-import-manifest.json)
|
|
116
139
|
- [schemas/exercise-solution-import-manifest.schema.json](../schemas/exercise-solution-import-manifest.schema.json)
|
|
117
140
|
|
|
141
|
+
### Exercise Sheet Solution Import
|
|
142
|
+
|
|
143
|
+
Top-level fields:
|
|
144
|
+
|
|
145
|
+
| Field | Type | Required | Notes |
|
|
146
|
+
| --- | --- | --- | --- |
|
|
147
|
+
| `sources` | array | yes | One or more source files containing solutions for some or all exercises in the target sheet. |
|
|
148
|
+
| `exerciseSheetId` | string | no | Default target sheet for the imported solution files. The command still requires a sheet id overall, either here or positionally. |
|
|
149
|
+
|
|
150
|
+
Per-source fields:
|
|
151
|
+
|
|
152
|
+
- Only the common source fields are allowed.
|
|
153
|
+
- `title` and `translateTo` are rejected for this command.
|
|
154
|
+
|
|
155
|
+
Canonical example:
|
|
156
|
+
|
|
157
|
+
- [docs/examples/exercise-sheet-solution-import-manifest.json](./examples/exercise-sheet-solution-import-manifest.json)
|
|
158
|
+
- [schemas/exercise-sheet-solution-import-manifest.schema.json](../schemas/exercise-sheet-solution-import-manifest.schema.json)
|
|
159
|
+
|
|
118
160
|
## Validation Notes
|
|
119
161
|
|
|
120
162
|
The CLI validates more than the JSON schema can express on its own:
|
|
121
163
|
|
|
122
164
|
- `sourceId` values must be unique within one manifest.
|
|
123
165
|
- Sheet import `translateTo` values must be unique within one sheet.
|
|
166
|
+
- Sheet import component `componentId` values must be unique within one sheet group.
|
|
167
|
+
- Sheet import components require non-empty descriptions.
|
|
168
|
+
- Exercise import top-level `translateTo` values must be unique.
|
|
124
169
|
- `relativePath` cannot be empty or contain `..`.
|
|
125
170
|
- Sheet import `targetFolderPath` must be `null` or a normalized folder path without `.` or `..` segments.
|
|
126
171
|
- `directory` imports must resolve to at least one file.
|
package/docs/manual.md
CHANGED
|
@@ -72,7 +72,7 @@ Import one sheet and wait for the background job to finish:
|
|
|
72
72
|
chalksurf sheet import ./fixtures/algebra.pdf --wait
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
Import one sheet, override its title, and request an English translation
|
|
75
|
+
Import one sheet, override its title, and request an English translation after import:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
chalksurf sheet import ./fixtures/algebra.pdf \
|
|
@@ -95,6 +95,7 @@ Import exercises into an existing sheet:
|
|
|
95
95
|
```bash
|
|
96
96
|
chalksurf exercise import ./fixtures/problem-set.pdf \
|
|
97
97
|
--sheet-id 00000000-0000-4000-8000-000000000001 \
|
|
98
|
+
--translate-to english \
|
|
98
99
|
--wait
|
|
99
100
|
```
|
|
100
101
|
|
|
@@ -107,6 +108,15 @@ chalksurf exercise import-solution \
|
|
|
107
108
|
--wait
|
|
108
109
|
```
|
|
109
110
|
|
|
111
|
+
Import a separate solution file for an existing exercise sheet:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
chalksurf sheet import-solutions \
|
|
115
|
+
00000000-0000-4000-8000-000000000001 \
|
|
116
|
+
./fixtures/sheet-solutions.pdf \
|
|
117
|
+
--wait
|
|
118
|
+
```
|
|
119
|
+
|
|
110
120
|
Import from a remote URL:
|
|
111
121
|
|
|
112
122
|
```bash
|
|
@@ -126,6 +136,18 @@ Sheet import manifests use top-level `sheets[]`. Each sheet has one destination
|
|
|
126
136
|
|
|
127
137
|
## Working With Jobs
|
|
128
138
|
|
|
139
|
+
List recent jobs:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
chalksurf job list
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Include jobs already viewed or dismissed in the web UI:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
chalksurf job list --include-viewed --include-dismissed
|
|
149
|
+
```
|
|
150
|
+
|
|
129
151
|
Read one job:
|
|
130
152
|
|
|
131
153
|
```bash
|
|
@@ -138,7 +160,29 @@ Wait on one or more jobs later:
|
|
|
138
160
|
chalksurf job wait job_123 job_124
|
|
139
161
|
```
|
|
140
162
|
|
|
141
|
-
`--wait` on an import command means the
|
|
163
|
+
`--wait` on an import command means the parsed content has been saved before the command exits. Requested translations run as separate jobs; use `--json` to capture their job IDs or `chalksurf job list` to inspect them later.
|
|
164
|
+
|
|
165
|
+
## Searching From The CLI
|
|
166
|
+
|
|
167
|
+
Search commands default to `--ownership own`, which means resources in the selected organization.
|
|
168
|
+
|
|
169
|
+
Find exercise sheets by title:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
chalksurf sheet search --text "OKTV 2014"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Find exercises by text:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
chalksurf exercise search --text "binomial theorem" --language english
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Search public resources instead:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
chalksurf sheet search --text "OKTV" --ownership public
|
|
185
|
+
```
|
|
142
186
|
|
|
143
187
|
## Troubleshooting
|
|
144
188
|
|
package/package.json
CHANGED
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
"type": "string",
|
|
15
15
|
"minLength": 1
|
|
16
16
|
},
|
|
17
|
+
"translateTo": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"minItems": 1,
|
|
20
|
+
"uniqueItems": true,
|
|
21
|
+
"items": {
|
|
22
|
+
"$ref": "#/$defs/translateLanguage"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
17
25
|
"wait": {
|
|
18
26
|
"type": "boolean"
|
|
19
27
|
},
|
|
@@ -30,6 +38,10 @@
|
|
|
30
38
|
"type": "string",
|
|
31
39
|
"minLength": 1
|
|
32
40
|
},
|
|
41
|
+
"translateLanguage": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"enum": ["english", "hungarian", "german", "french", "spanish", "italian"]
|
|
44
|
+
},
|
|
33
45
|
"localSource": {
|
|
34
46
|
"type": "object",
|
|
35
47
|
"additionalProperties": false,
|