@chalksurf/cli 0.3.3 → 0.3.6
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 +6 -3
- package/dist/bin/chalksurf.js +1778 -401
- package/docs/agents.md +28 -20
- package/docs/manual.md +57 -4
- package/docs/mcp.md +30 -10
- package/package.json +1 -1
package/dist/bin/chalksurf.js
CHANGED
|
@@ -20,6 +20,27 @@ var ApiClientError = class extends Error {
|
|
|
20
20
|
var normalizeBaseUrl = (baseUrl) => {
|
|
21
21
|
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
22
22
|
};
|
|
23
|
+
var getSafeBaseUrlForError = (baseUrl) => {
|
|
24
|
+
const url = new URL(baseUrl);
|
|
25
|
+
url.username = "";
|
|
26
|
+
url.password = "";
|
|
27
|
+
url.search = "";
|
|
28
|
+
url.hash = "";
|
|
29
|
+
const safeBaseUrl = url.toString();
|
|
30
|
+
return safeBaseUrl.endsWith("/") ? safeBaseUrl : `${safeBaseUrl}/`;
|
|
31
|
+
};
|
|
32
|
+
var getSafeNetworkCause = ({ error, token }) => {
|
|
33
|
+
if (!(error instanceof Error)) {
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
const causeCode = typeof error.cause === "object" && error.cause !== null && "code" in error.cause && typeof error.cause.code === "string" && /^[A-Z0-9_]+$/.test(error.cause.code) ? error.cause.code : void 0;
|
|
37
|
+
const errorMessage = error.message.trim();
|
|
38
|
+
const combinedCause = [causeCode, errorMessage].filter(Boolean).join(": ");
|
|
39
|
+
if (combinedCause.length === 0 || combinedCause.length > 200 || /authorization|bearer|password|secret|token/iu.test(combinedCause) || token && combinedCause.includes(token)) {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
return combinedCause;
|
|
43
|
+
};
|
|
23
44
|
var getErrorMessage = (payload, status) => {
|
|
24
45
|
return payload?.error?.message ?? `API request failed with status ${status}`;
|
|
25
46
|
};
|
|
@@ -51,6 +72,7 @@ var createApiClient = ({
|
|
|
51
72
|
organizationId
|
|
52
73
|
}) => {
|
|
53
74
|
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
75
|
+
const safeBaseUrlForError = getSafeBaseUrlForError(normalizedBaseUrl);
|
|
54
76
|
const createHeaders = ({ includeJsonContentType }) => {
|
|
55
77
|
const headers = new Headers();
|
|
56
78
|
if (token) {
|
|
@@ -64,11 +86,21 @@ var createApiClient = ({
|
|
|
64
86
|
}
|
|
65
87
|
return headers;
|
|
66
88
|
};
|
|
89
|
+
const fetchApi = async (url, init) => {
|
|
90
|
+
try {
|
|
91
|
+
return await fetch(url, init);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
const safeCause = getSafeNetworkCause({ error, token });
|
|
94
|
+
throw new ApiClientError(
|
|
95
|
+
`Could not reach ChalkSurf API at ${safeBaseUrlForError}.${safeCause ? ` Cause: ${safeCause}` : ""}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
67
99
|
const query = async (procedureName, input) => {
|
|
68
100
|
const url = new URL(`trpc/${procedureName}`, normalizedBaseUrl);
|
|
69
101
|
url.searchParams.set("batch", "1");
|
|
70
102
|
url.searchParams.set("input", JSON.stringify({ 0: input ?? null }));
|
|
71
|
-
const response = await
|
|
103
|
+
const response = await fetchApi(url.toString(), {
|
|
72
104
|
method: "GET",
|
|
73
105
|
headers: createHeaders({ includeJsonContentType: false })
|
|
74
106
|
});
|
|
@@ -76,7 +108,7 @@ var createApiClient = ({
|
|
|
76
108
|
};
|
|
77
109
|
const mutation = async (procedureName, input) => {
|
|
78
110
|
const isFormDataInput = input instanceof FormData;
|
|
79
|
-
const response = await
|
|
111
|
+
const response = await fetchApi(new URL(`trpc/${procedureName}`, normalizedBaseUrl).toString(), {
|
|
80
112
|
method: "POST",
|
|
81
113
|
headers: createHeaders({ includeJsonContentType: !isFormDataInput }),
|
|
82
114
|
body: isFormDataInput ? input : JSON.stringify(input ?? {})
|
|
@@ -119,6 +151,15 @@ var createApiClient = ({
|
|
|
119
151
|
agentGetExerciseUsage: async (id) => {
|
|
120
152
|
return await query("agentGetExerciseUsage", { id });
|
|
121
153
|
},
|
|
154
|
+
agentListQualityIssues: async (input) => {
|
|
155
|
+
return await query("agentListQualityIssues", input);
|
|
156
|
+
},
|
|
157
|
+
agentResolveQualityIssue: async (input) => {
|
|
158
|
+
return await mutation("agentResolveQualityIssue", input);
|
|
159
|
+
},
|
|
160
|
+
agentDismissQualityIssue: async (input) => {
|
|
161
|
+
return await mutation("agentDismissQualityIssue", input);
|
|
162
|
+
},
|
|
122
163
|
agentListExerciseLabels: async () => {
|
|
123
164
|
return await query("agentListExerciseLabels", {});
|
|
124
165
|
},
|
|
@@ -140,9 +181,6 @@ var createApiClient = ({
|
|
|
140
181
|
agentUploadExerciseFigure: async (input) => {
|
|
141
182
|
return await mutation("agentUploadExerciseFigure", input);
|
|
142
183
|
},
|
|
143
|
-
agentAttachExerciseFigures: async (input) => {
|
|
144
|
-
return await mutation("agentAttachExerciseFigures", input);
|
|
145
|
-
},
|
|
146
184
|
agentUpdateExercise: async (input) => {
|
|
147
185
|
return await mutation("agentUpdateExercise", input);
|
|
148
186
|
},
|
|
@@ -182,8 +220,14 @@ var createApiClient = ({
|
|
|
182
220
|
agentBulkUpdateSheets: async (input) => {
|
|
183
221
|
return await mutation("agentBulkUpdateSheets", input);
|
|
184
222
|
},
|
|
185
|
-
|
|
186
|
-
return await
|
|
223
|
+
agentVerifyBulkSheetUpdates: async (input) => {
|
|
224
|
+
return await mutation("agentVerifyBulkSheetUpdates", input);
|
|
225
|
+
},
|
|
226
|
+
agentListSheets: async (input) => {
|
|
227
|
+
return await query("agentListSheets", input);
|
|
228
|
+
},
|
|
229
|
+
agentListSheetSeries: async (input = {}) => {
|
|
230
|
+
return await query("agentListSheetSeries", input);
|
|
187
231
|
},
|
|
188
232
|
agentSetSheetVisibility: async (input) => {
|
|
189
233
|
return await mutation("agentSetSheetVisibility", input);
|
|
@@ -855,7 +899,9 @@ var Constants = {
|
|
|
855
899
|
"generate_sheet_translation_completed",
|
|
856
900
|
"upload_exercise_figure",
|
|
857
901
|
"attach_exercise_figures",
|
|
858
|
-
"bulk_update_sheets"
|
|
902
|
+
"bulk_update_sheets",
|
|
903
|
+
"resolve_quality_issue",
|
|
904
|
+
"dismiss_quality_issue"
|
|
859
905
|
],
|
|
860
906
|
agent_audit_outcome: ["started", "success", "failed", "denied"],
|
|
861
907
|
agent_audit_resource_type: ["exercise", "sheet", "folder", "job", "event", "figure_asset"],
|
|
@@ -896,7 +942,6 @@ var Constants = {
|
|
|
896
942
|
"figure_update",
|
|
897
943
|
"automatic_labeling"
|
|
898
944
|
],
|
|
899
|
-
figure_asset_source_kind: ["document_crop", "rendered_figure"],
|
|
900
945
|
job_type: [
|
|
901
946
|
"exercise_sheet_import",
|
|
902
947
|
"exercise_import",
|
|
@@ -931,7 +976,6 @@ import { z as z2 } from "zod";
|
|
|
931
976
|
var exerciseStatusOptions = Constants.public.Enums.exercise_status;
|
|
932
977
|
var exerciseQualityIssueSeverities = Constants.public.Enums.exercise_quality_issue_severity;
|
|
933
978
|
var exerciseQualityIssueStatuses = Constants.public.Enums.exercise_quality_issue_status;
|
|
934
|
-
var figureAssetSourceKinds = Constants.public.Enums.figure_asset_source_kind;
|
|
935
979
|
var exerciseFigureRowSourceKinds = Constants.public.Enums.exercise_figure_source_kind;
|
|
936
980
|
var exerciseFigureRenditionPurposes = Constants.public.Enums.exercise_figure_rendition_purpose;
|
|
937
981
|
var exerciseVersionTypes = Constants.public.Enums.exercise_version_type;
|
|
@@ -1377,6 +1421,18 @@ var agentWriteOperationRegistry = {
|
|
|
1377
1421
|
resourceType: "exercise",
|
|
1378
1422
|
sync: true
|
|
1379
1423
|
},
|
|
1424
|
+
resolve_quality_issue: {
|
|
1425
|
+
agentToolName: "resolve_quality_issue",
|
|
1426
|
+
receiptOperation: "resolve_quality_issue",
|
|
1427
|
+
resourceType: "exercise",
|
|
1428
|
+
sync: true
|
|
1429
|
+
},
|
|
1430
|
+
dismiss_quality_issue: {
|
|
1431
|
+
agentToolName: "dismiss_quality_issue",
|
|
1432
|
+
receiptOperation: "dismiss_quality_issue",
|
|
1433
|
+
resourceType: "exercise",
|
|
1434
|
+
sync: true
|
|
1435
|
+
},
|
|
1380
1436
|
update_sheet: {
|
|
1381
1437
|
agentToolName: "update_sheet",
|
|
1382
1438
|
receiptOperation: "update_sheet",
|
|
@@ -1440,12 +1496,6 @@ var agentWriteOperationRegistry = {
|
|
|
1440
1496
|
upload_exercise_figure: {
|
|
1441
1497
|
agentToolName: "upload_exercise_figure",
|
|
1442
1498
|
receiptOperation: "upload_exercise_figure",
|
|
1443
|
-
resourceType: "figure_asset",
|
|
1444
|
-
sync: true
|
|
1445
|
-
},
|
|
1446
|
-
attach_exercise_figures: {
|
|
1447
|
-
agentToolName: "attach_exercise_figures",
|
|
1448
|
-
receiptOperation: "attach_exercise_figures",
|
|
1449
1499
|
resourceType: "exercise",
|
|
1450
1500
|
sync: true
|
|
1451
1501
|
},
|
|
@@ -1596,15 +1646,17 @@ var exerciseTranslationSchema = strictObject({
|
|
|
1596
1646
|
status: z3.enum(exerciseStatusOptions)
|
|
1597
1647
|
});
|
|
1598
1648
|
var exerciseFigureWidthSchema = z3.number().finite().min(minExerciseFigureWidthInCm).max(maxExerciseFigureWidthInCm);
|
|
1599
|
-
var
|
|
1649
|
+
var agentExerciseFigureInputSchema = strictObject({
|
|
1600
1650
|
type: z3.enum(exerciseFigureTypes),
|
|
1601
1651
|
url: z3.url(),
|
|
1602
1652
|
widthInCm: exerciseFigureWidthSchema,
|
|
1603
1653
|
exerciseFigureId: uuidSchema.optional(),
|
|
1604
|
-
|
|
1654
|
+
orderIndex: z3.number().int().min(0).optional(),
|
|
1655
|
+
rotationClockwiseDegrees: exerciseFigureRotationSchema.optional(),
|
|
1605
1656
|
provenance: exerciseFigureProvenanceSchema.optional()
|
|
1606
1657
|
});
|
|
1607
|
-
var agentExerciseFigureSchema =
|
|
1658
|
+
var agentExerciseFigureSchema = agentExerciseFigureInputSchema.extend({
|
|
1659
|
+
exerciseFigureId: uuidSchema,
|
|
1608
1660
|
orderIndex: z3.number().int().min(0),
|
|
1609
1661
|
rotationClockwiseDegrees: exerciseFigureRotationSchema
|
|
1610
1662
|
});
|
|
@@ -1703,6 +1755,39 @@ var folderSchema = strictObject({
|
|
|
1703
1755
|
parentId: uuidSchema.nullable(),
|
|
1704
1756
|
updatedAt: dateStringSchema
|
|
1705
1757
|
});
|
|
1758
|
+
var folderPathSegmentSchema = strictObject({
|
|
1759
|
+
id: uuidSchema,
|
|
1760
|
+
name: z3.string().trim().min(1)
|
|
1761
|
+
});
|
|
1762
|
+
var listSheetsAgentInputSchema = strictObject({
|
|
1763
|
+
afterId: uuidSchema.optional(),
|
|
1764
|
+
folderId: uuidSchema.optional(),
|
|
1765
|
+
includeDescendants: z3.boolean().optional().default(false),
|
|
1766
|
+
limit: z3.number().int().min(1).max(100).optional().default(50),
|
|
1767
|
+
offset: z3.number().int().min(0).optional().default(0)
|
|
1768
|
+
}).refine(({ folderId, includeDescendants }) => folderId !== void 0 || !includeDescendants, {
|
|
1769
|
+
message: "folderId is required when includeDescendants is true",
|
|
1770
|
+
path: ["folderId"]
|
|
1771
|
+
}).refine(({ afterId, offset }) => afterId === void 0 || offset === 0, {
|
|
1772
|
+
message: "offset must be zero when afterId is provided",
|
|
1773
|
+
path: ["offset"]
|
|
1774
|
+
});
|
|
1775
|
+
var listSheetsAgentOutputSchema = strictObject({
|
|
1776
|
+
sheets: z3.array(
|
|
1777
|
+
strictObject({
|
|
1778
|
+
id: uuidSchema,
|
|
1779
|
+
name: z3.string().trim().min(1),
|
|
1780
|
+
subject: z3.enum(exerciseSubjects),
|
|
1781
|
+
isPublic: z3.boolean(),
|
|
1782
|
+
folderId: uuidSchema.nullable(),
|
|
1783
|
+
folderPath: z3.array(folderPathSegmentSchema),
|
|
1784
|
+
updatedAt: dateStringSchema,
|
|
1785
|
+
seriesAssignments: sheetSeriesAssignmentsSchema
|
|
1786
|
+
})
|
|
1787
|
+
),
|
|
1788
|
+
inventoryToken: z3.string().trim().min(1),
|
|
1789
|
+
totalCount: z3.number().int().min(0)
|
|
1790
|
+
});
|
|
1706
1791
|
var exerciseLabelSchema = strictObject({
|
|
1707
1792
|
id: z3.string().trim().min(1),
|
|
1708
1793
|
subject: z3.enum(exerciseSubjects),
|
|
@@ -1953,9 +2038,44 @@ var getExerciseUsageAgentInputSchema = strictObject({
|
|
|
1953
2038
|
var getExerciseUsageAgentOutputSchema = strictObject({
|
|
1954
2039
|
usage: exerciseUsageSchema
|
|
1955
2040
|
});
|
|
2041
|
+
var qualityIssueSelectorShape = {
|
|
2042
|
+
exerciseIds: z3.array(uuidSchema).min(1).max(100).optional(),
|
|
2043
|
+
issueIds: z3.array(uuidSchema).min(1).max(100).optional(),
|
|
2044
|
+
severities: z3.array(z3.enum(exerciseQualityIssueSeverities)).min(1).optional(),
|
|
2045
|
+
limit: z3.number().int().min(1).max(100).optional().default(50),
|
|
2046
|
+
cursor: z3.string().trim().min(1).optional()
|
|
2047
|
+
};
|
|
2048
|
+
var listQualityIssuesAgentInputSchema = strictObject(qualityIssueSelectorShape).refine(
|
|
2049
|
+
({ exerciseIds, issueIds }) => exerciseIds === void 0 || issueIds === void 0,
|
|
2050
|
+
{ message: "exerciseIds and issueIds are mutually exclusive" }
|
|
2051
|
+
);
|
|
2052
|
+
var qualityIssueListItemSchema = strictObject({
|
|
2053
|
+
id: uuidSchema,
|
|
2054
|
+
severity: z3.enum(exerciseQualityIssueSeverities),
|
|
2055
|
+
comment: z3.string(),
|
|
2056
|
+
sourceLanguage: z3.enum(translationLanguages),
|
|
2057
|
+
sourceInputHash: z3.string(),
|
|
2058
|
+
createdAt: dateStringSchema,
|
|
2059
|
+
exercise: strictObject({
|
|
2060
|
+
id: uuidSchema,
|
|
2061
|
+
updatedAt: dateStringSchema,
|
|
2062
|
+
subject: z3.enum(exerciseSubjects),
|
|
2063
|
+
source: z3.string().nullable(),
|
|
2064
|
+
organizationId: uuidSchema
|
|
2065
|
+
})
|
|
2066
|
+
});
|
|
2067
|
+
var listQualityIssuesAgentOutputSchema = strictObject({
|
|
2068
|
+
issues: z3.array(qualityIssueListItemSchema),
|
|
2069
|
+
nextCursor: z3.string().nullable()
|
|
2070
|
+
});
|
|
2071
|
+
var resolveQualityIssueAgentInputSchema = strictObject({
|
|
2072
|
+
issueId: uuidSchema,
|
|
2073
|
+
expectedExerciseUpdatedAt: expectedUpdatedAtSchema
|
|
2074
|
+
});
|
|
2075
|
+
var dismissQualityIssueAgentInputSchema = resolveQualityIssueAgentInputSchema;
|
|
1956
2076
|
var agentWriteReceiptSchema = strictObject({
|
|
1957
2077
|
resource: strictObject({
|
|
1958
|
-
type: z3.enum(["exercise", "sheet", "folder"
|
|
2078
|
+
type: z3.enum(["exercise", "sheet", "folder"]),
|
|
1959
2079
|
id: uuidSchema
|
|
1960
2080
|
}),
|
|
1961
2081
|
operation: agentWriteReceiptOperationSchema,
|
|
@@ -1978,6 +2098,8 @@ var agentWriteReceiptSchema = strictObject({
|
|
|
1978
2098
|
var agentWriteReceiptOutputSchema = strictObject({
|
|
1979
2099
|
receipt: agentWriteReceiptSchema
|
|
1980
2100
|
});
|
|
2101
|
+
var resolveQualityIssueAgentOutputSchema = agentWriteReceiptOutputSchema;
|
|
2102
|
+
var dismissQualityIssueAgentOutputSchema = agentWriteReceiptOutputSchema;
|
|
1981
2103
|
var copyExerciseAgentInputSchema = strictObject({
|
|
1982
2104
|
id: uuidSchema
|
|
1983
2105
|
});
|
|
@@ -2031,14 +2153,6 @@ var exerciseCreateFigureSchema = strictObject({
|
|
|
2031
2153
|
widthInCm: z3.number().positive().finite().optional(),
|
|
2032
2154
|
provenance: exerciseFigureProvenanceSchema.optional()
|
|
2033
2155
|
});
|
|
2034
|
-
var agentExerciseFigureInputSchema = strictObject({
|
|
2035
|
-
type: z3.enum(exerciseFigureTypes),
|
|
2036
|
-
url: z3.url(),
|
|
2037
|
-
widthInCm: exerciseFigureWidthSchema.optional(),
|
|
2038
|
-
exerciseFigureId: uuidSchema.optional(),
|
|
2039
|
-
figureAssetId: uuidSchema.optional(),
|
|
2040
|
-
provenance: exerciseFigureProvenanceSchema.optional()
|
|
2041
|
-
});
|
|
2042
2156
|
var figureReviewWarningSchema = strictObject({
|
|
2043
2157
|
code: z3.string().trim().min(1),
|
|
2044
2158
|
message: z3.string().trim().min(1),
|
|
@@ -2066,7 +2180,7 @@ var uploadExerciseFigureSourceSchema = z3.discriminatedUnion("kind", [
|
|
|
2066
2180
|
})
|
|
2067
2181
|
]);
|
|
2068
2182
|
var uploadExerciseFigureAgentInputSchema = strictObject({
|
|
2069
|
-
exerciseId: uuidSchema
|
|
2183
|
+
exerciseId: uuidSchema,
|
|
2070
2184
|
figureType: z3.enum(exerciseFigureTypes),
|
|
2071
2185
|
source: uploadExerciseFigureSourceSchema,
|
|
2072
2186
|
widthInCm: exerciseFigureWidthSchema.optional().default(defaultExerciseFigureWidthInCm),
|
|
@@ -2083,53 +2197,10 @@ var figureUploadReviewSchema = strictObject({
|
|
|
2083
2197
|
nextActions: z3.array(z3.string().trim().min(1))
|
|
2084
2198
|
});
|
|
2085
2199
|
var uploadExerciseFigureAgentOutputSchema = strictObject({
|
|
2086
|
-
figure:
|
|
2200
|
+
figure: agentExerciseFigureSchema,
|
|
2087
2201
|
review: figureUploadReviewSchema,
|
|
2088
2202
|
receipt: agentWriteReceiptSchema
|
|
2089
2203
|
});
|
|
2090
|
-
var attachExerciseFiguresAgentInputSchema = strictObject({
|
|
2091
|
-
exerciseId: uuidSchema,
|
|
2092
|
-
expectedUpdatedAt: expectedUpdatedAtSchema,
|
|
2093
|
-
targetSheetId: uuidSchema.nullable().optional(),
|
|
2094
|
-
allowSharedExerciseUpdate: z3.boolean().optional().default(false),
|
|
2095
|
-
mode: z3.enum(["append", "replace_type", "replace_all"]),
|
|
2096
|
-
figureType: z3.enum(exerciseFigureTypes).optional(),
|
|
2097
|
-
figures: z3.array(agentExerciseFigureInputSchema)
|
|
2098
|
-
}).superRefine((input, ctx) => {
|
|
2099
|
-
if (input.mode === "append" && input.figures.length === 0) {
|
|
2100
|
-
ctx.addIssue({
|
|
2101
|
-
code: z3.ZodIssueCode.custom,
|
|
2102
|
-
message: "append mode requires at least one figure.",
|
|
2103
|
-
path: ["figures"]
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
|
-
if (input.mode === "replace_type" && !input.figureType) {
|
|
2107
|
-
ctx.addIssue({
|
|
2108
|
-
code: z3.ZodIssueCode.custom,
|
|
2109
|
-
message: "replace_type mode requires figureType.",
|
|
2110
|
-
path: ["figureType"]
|
|
2111
|
-
});
|
|
2112
|
-
}
|
|
2113
|
-
if (input.mode !== "replace_type" && input.figureType) {
|
|
2114
|
-
ctx.addIssue({
|
|
2115
|
-
code: z3.ZodIssueCode.custom,
|
|
2116
|
-
message: "figureType is only supported for replace_type mode.",
|
|
2117
|
-
path: ["figureType"]
|
|
2118
|
-
});
|
|
2119
|
-
}
|
|
2120
|
-
if (input.mode === "replace_type" && input.figureType) {
|
|
2121
|
-
input.figures.forEach((figure, index) => {
|
|
2122
|
-
if (figure.type !== input.figureType) {
|
|
2123
|
-
ctx.addIssue({
|
|
2124
|
-
code: z3.ZodIssueCode.custom,
|
|
2125
|
-
message: "Replacement figures must match figureType.",
|
|
2126
|
-
path: ["figures", index, "type"]
|
|
2127
|
-
});
|
|
2128
|
-
}
|
|
2129
|
-
});
|
|
2130
|
-
}
|
|
2131
|
-
});
|
|
2132
|
-
var attachExerciseFiguresAgentOutputSchema = agentWriteReceiptOutputSchema;
|
|
2133
2204
|
var exercisePatchMetadataSchema = strictObject({
|
|
2134
2205
|
sourceUrl: patchNullableStringSchema,
|
|
2135
2206
|
solutionUrl: patchNullableStringSchema
|
|
@@ -2214,6 +2285,34 @@ var bulkUpdateSheetsAgentOutputSchema = strictObject({
|
|
|
2214
2285
|
results: z3.array(bulkUpdateSheetResultSchema),
|
|
2215
2286
|
receipt: agentWriteReceiptSchema.nullable()
|
|
2216
2287
|
});
|
|
2288
|
+
var verifyBulkSheetUpdateStatusSchema = z3.enum(["applied", "pending", "conflicting", "error"]);
|
|
2289
|
+
var verifyBulkSheetUpdateConflictSchema = strictObject({
|
|
2290
|
+
field: z3.enum(["expectedUpdatedAt", "expectedSeriesAssignments"]),
|
|
2291
|
+
code: z3.enum(["updated_at_changed", "series_assignments_changed"])
|
|
2292
|
+
});
|
|
2293
|
+
var verifyBulkSheetUpdateResultSchema = strictObject({
|
|
2294
|
+
id: uuidSchema,
|
|
2295
|
+
status: verifyBulkSheetUpdateStatusSchema,
|
|
2296
|
+
matchedPaths: z3.array(z3.string()),
|
|
2297
|
+
unmatchedPaths: z3.array(z3.string()),
|
|
2298
|
+
conflicts: z3.array(verifyBulkSheetUpdateConflictSchema),
|
|
2299
|
+
errorCode: z3.string().nullable(),
|
|
2300
|
+
errorMessage: z3.string().nullable()
|
|
2301
|
+
});
|
|
2302
|
+
var verifyBulkSheetUpdatesAgentInputSchema = strictObject({
|
|
2303
|
+
updates: z3.array(updateSheetAgentInputSchema).min(1).max(100)
|
|
2304
|
+
});
|
|
2305
|
+
var verifyBulkSheetUpdatesAgentOutputSchema = strictObject({
|
|
2306
|
+
state: z3.enum(["applied", "pending", "conflicting", "mixed", "error"]),
|
|
2307
|
+
counts: strictObject({
|
|
2308
|
+
total: z3.number().int().min(0),
|
|
2309
|
+
applied: z3.number().int().min(0),
|
|
2310
|
+
pending: z3.number().int().min(0),
|
|
2311
|
+
conflicting: z3.number().int().min(0),
|
|
2312
|
+
error: z3.number().int().min(0)
|
|
2313
|
+
}),
|
|
2314
|
+
results: z3.array(verifyBulkSheetUpdateResultSchema)
|
|
2315
|
+
});
|
|
2217
2316
|
var createSheetScoredExerciseSchema = strictObject({
|
|
2218
2317
|
id: uuidSchema,
|
|
2219
2318
|
scorePoints: z3.number().min(0).max(99999.9).refine((value) => Number.isInteger(value * 10), "Score can have at most 1 decimal place").nullable()
|
|
@@ -2363,12 +2462,15 @@ var mcpPublicErrorCodes = [
|
|
|
2363
2462
|
"storage_upload_failed",
|
|
2364
2463
|
"unsupported_image",
|
|
2365
2464
|
"validation_failed",
|
|
2465
|
+
"organization_selection_required",
|
|
2466
|
+
"organization_access_denied",
|
|
2366
2467
|
"internal_error"
|
|
2367
2468
|
];
|
|
2469
|
+
var mcpPublicErrorCodeSchema = z5.enum(mcpPublicErrorCodes);
|
|
2368
2470
|
var mcpToolErrorOutputSchema = strictObject2({
|
|
2369
2471
|
isError: z5.literal(true),
|
|
2370
2472
|
error: strictObject2({
|
|
2371
|
-
code:
|
|
2473
|
+
code: mcpPublicErrorCodeSchema,
|
|
2372
2474
|
message: z5.string().trim().min(1),
|
|
2373
2475
|
agentErrorDetails: agentErrorDetailsSchema.optional()
|
|
2374
2476
|
})
|
|
@@ -2416,7 +2518,7 @@ var userSchema = strictObject2({
|
|
|
2416
2518
|
email: z5.string().email().nullable(),
|
|
2417
2519
|
name: z5.string().nullable()
|
|
2418
2520
|
});
|
|
2419
|
-
var
|
|
2521
|
+
var mcpOrganizationSchema = strictObject2({
|
|
2420
2522
|
id: z5.uuid(),
|
|
2421
2523
|
name: z5.string(),
|
|
2422
2524
|
mcpPermission: z5.enum(mcpOAuthGrantPermissions).nullable().optional(),
|
|
@@ -2549,14 +2651,31 @@ var chalksurfMcpScopes = {
|
|
|
2549
2651
|
var getAuthStatusInputSchema = strictObject2({});
|
|
2550
2652
|
var getAuthStatusOutputSchema = strictObject2({
|
|
2551
2653
|
authenticated: z5.literal(true),
|
|
2654
|
+
organizations: z5.array(mcpOrganizationSchema),
|
|
2552
2655
|
selectedOrganizationId: z5.uuid().nullable(),
|
|
2553
2656
|
user: userSchema
|
|
2554
2657
|
});
|
|
2555
2658
|
var listOrganizationsInputSchema = strictObject2({});
|
|
2556
2659
|
var listOrganizationsOutputSchema = strictObject2({
|
|
2557
|
-
organizations: z5.array(
|
|
2660
|
+
organizations: z5.array(mcpOrganizationSchema),
|
|
2558
2661
|
selectedOrganizationId: z5.uuid().nullable()
|
|
2559
2662
|
});
|
|
2663
|
+
var mcpOrganizationRecoveryOutputSchema = strictObject2({
|
|
2664
|
+
isError: z5.literal(true),
|
|
2665
|
+
error: strictObject2({
|
|
2666
|
+
code: z5.enum(["organization_selection_required", "organization_access_denied"]),
|
|
2667
|
+
message: z5.string().trim().min(1)
|
|
2668
|
+
}),
|
|
2669
|
+
organizations: z5.array(
|
|
2670
|
+
strictObject2({
|
|
2671
|
+
id: z5.uuid(),
|
|
2672
|
+
name: z5.string(),
|
|
2673
|
+
permission: z5.enum(mcpOAuthGrantPermissions),
|
|
2674
|
+
role: z5.enum(["owner", "editor", "viewer"]),
|
|
2675
|
+
type: z5.enum(["personal", "team"])
|
|
2676
|
+
})
|
|
2677
|
+
)
|
|
2678
|
+
});
|
|
2560
2679
|
var searchExercisesInputSchema = strictObject2({
|
|
2561
2680
|
organizationId: organizationIdSchema,
|
|
2562
2681
|
age: z5.number().int().nullish(),
|
|
@@ -2587,9 +2706,19 @@ var getExerciseUsageInputSchema = getExerciseUsageAgentInputSchema.extend({
|
|
|
2587
2706
|
organizationId: organizationIdSchema
|
|
2588
2707
|
});
|
|
2589
2708
|
var getExerciseUsageOutputSchema = getExerciseUsageAgentOutputSchema;
|
|
2590
|
-
var
|
|
2709
|
+
var listQualityIssuesInputSchema = listQualityIssuesAgentInputSchema.safeExtend({
|
|
2710
|
+
organizationId: organizationIdSchema
|
|
2711
|
+
});
|
|
2712
|
+
var listQualityIssuesOutputSchema = listQualityIssuesAgentOutputSchema;
|
|
2713
|
+
var resolveQualityIssueInputSchema = resolveQualityIssueAgentInputSchema.extend({
|
|
2714
|
+
organizationId: organizationIdSchema
|
|
2715
|
+
});
|
|
2716
|
+
var resolveQualityIssueOutputSchema = resolveQualityIssueAgentOutputSchema;
|
|
2717
|
+
var dismissQualityIssueInputSchema = dismissQualityIssueAgentInputSchema.extend({
|
|
2591
2718
|
organizationId: organizationIdSchema
|
|
2592
2719
|
});
|
|
2720
|
+
var dismissQualityIssueOutputSchema = dismissQualityIssueAgentOutputSchema;
|
|
2721
|
+
var listExerciseLabelsInputSchema = listExerciseLabelsAgentInputSchema;
|
|
2593
2722
|
var listExerciseLabelsOutputSchema = listExerciseLabelsAgentOutputSchema;
|
|
2594
2723
|
var copyExerciseInputSchema = copyExerciseAgentInputSchema.extend({
|
|
2595
2724
|
organizationId: organizationIdSchema
|
|
@@ -2662,13 +2791,15 @@ var searchSheetsOutputSchema = strictObject2({
|
|
|
2662
2791
|
),
|
|
2663
2792
|
totalCount: z5.number().int().min(0)
|
|
2664
2793
|
});
|
|
2665
|
-
var
|
|
2794
|
+
var listSheetsInputSchema = listSheetsAgentInputSchema.extend({
|
|
2666
2795
|
organizationId: organizationIdSchema
|
|
2667
2796
|
});
|
|
2668
|
-
var
|
|
2669
|
-
var
|
|
2797
|
+
var listSheetsOutputSchema = listSheetsAgentOutputSchema.omit({ inventoryToken: true });
|
|
2798
|
+
var getSheetInputSchema = getSheetAgentInputSchema.extend({
|
|
2670
2799
|
organizationId: organizationIdSchema
|
|
2671
2800
|
});
|
|
2801
|
+
var getSheetOutputSchema = getSheetAgentOutputSchema;
|
|
2802
|
+
var listSheetSeriesInputSchema = listSheetSeriesAgentInputSchema;
|
|
2672
2803
|
var listSheetSeriesOutputSchema = listSheetSeriesAgentOutputSchema;
|
|
2673
2804
|
var listSheetVersionsInputSchema = listSheetVersionsAgentInputSchema.extend({
|
|
2674
2805
|
organizationId: organizationIdSchema
|
|
@@ -2702,6 +2833,10 @@ var bulkUpdateSheetsInputSchema = bulkUpdateSheetsAgentInputSchema.extend({
|
|
|
2702
2833
|
organizationId: organizationIdSchema
|
|
2703
2834
|
});
|
|
2704
2835
|
var bulkUpdateSheetsOutputSchema = bulkUpdateSheetsAgentOutputSchema;
|
|
2836
|
+
var verifyBulkSheetUpdatesInputSchema = verifyBulkSheetUpdatesAgentInputSchema.extend({
|
|
2837
|
+
organizationId: organizationIdSchema
|
|
2838
|
+
});
|
|
2839
|
+
var verifyBulkSheetUpdatesOutputSchema = verifyBulkSheetUpdatesAgentOutputSchema;
|
|
2705
2840
|
var setSheetVisibilityInputSchema = setSheetVisibilityAgentInputSchema.extend({
|
|
2706
2841
|
organizationId: organizationIdSchema
|
|
2707
2842
|
});
|
|
@@ -2738,9 +2873,7 @@ var renameFolderInputSchema = renameFolderAgentInputSchema.extend({
|
|
|
2738
2873
|
organizationId: organizationIdSchema
|
|
2739
2874
|
});
|
|
2740
2875
|
var renameFolderOutputSchema = renameFolderAgentOutputSchema;
|
|
2741
|
-
var validateLatexSnippetsInputSchema = validateLatexSnippetsAgentInputSchema
|
|
2742
|
-
organizationId: organizationIdSchema
|
|
2743
|
-
});
|
|
2876
|
+
var validateLatexSnippetsInputSchema = validateLatexSnippetsAgentInputSchema;
|
|
2744
2877
|
var validateLatexSnippetsOutputSchema = validateLatexSnippetsAgentOutputSchema;
|
|
2745
2878
|
var importSheetInputSchema = strictObject2({
|
|
2746
2879
|
organizationId: organizationIdSchema,
|
|
@@ -3497,6 +3630,7 @@ var uploadExerciseFigureInputSchema = uploadExerciseFigureAgentInputSchema.exten
|
|
|
3497
3630
|
});
|
|
3498
3631
|
var uploadExerciseFigureMcpToolInputSchema = strictObject2({
|
|
3499
3632
|
organizationId: organizationIdSchema,
|
|
3633
|
+
exerciseId: z5.uuid(),
|
|
3500
3634
|
figureType: z5.enum(exerciseFigureTypes),
|
|
3501
3635
|
...chatGptFigureUploadFileParamInputShape,
|
|
3502
3636
|
source: strictObject2({
|
|
@@ -3513,10 +3647,6 @@ var uploadExerciseFigureMcpToolInputSchema = strictObject2({
|
|
|
3513
3647
|
allowNeedsRevision: z5.boolean().optional()
|
|
3514
3648
|
}).passthrough();
|
|
3515
3649
|
var uploadExerciseFigureOutputSchema = uploadExerciseFigureAgentOutputSchema;
|
|
3516
|
-
var attachExerciseFiguresInputSchema = attachExerciseFiguresAgentInputSchema.extend({
|
|
3517
|
-
organizationId: organizationIdSchema
|
|
3518
|
-
});
|
|
3519
|
-
var attachExerciseFiguresOutputSchema = attachExerciseFiguresAgentOutputSchema;
|
|
3520
3650
|
var chalksurfMcpToolCapabilities = {
|
|
3521
3651
|
get_auth_status: {
|
|
3522
3652
|
name: "get_auth_status",
|
|
@@ -3568,6 +3698,16 @@ var chalksurfMcpToolCapabilities = {
|
|
|
3568
3698
|
requiredPermission: "read",
|
|
3569
3699
|
security: { type: "oauth2", scopes: [chalksurfMcpScopes.read] }
|
|
3570
3700
|
},
|
|
3701
|
+
list_quality_issues: {
|
|
3702
|
+
name: "list_quality_issues",
|
|
3703
|
+
title: "List quality issues",
|
|
3704
|
+
description: "List active quality issues for exercises owned by the selected ChalkSurf organization.",
|
|
3705
|
+
inputSchema: listQualityIssuesInputSchema,
|
|
3706
|
+
outputSchema: listQualityIssuesOutputSchema,
|
|
3707
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
3708
|
+
requiredPermission: "read",
|
|
3709
|
+
security: { type: "oauth2", scopes: [chalksurfMcpScopes.read] }
|
|
3710
|
+
},
|
|
3571
3711
|
list_exercise_labels: {
|
|
3572
3712
|
name: "list_exercise_labels",
|
|
3573
3713
|
title: "List exercise labels",
|
|
@@ -3618,6 +3758,26 @@ var chalksurfMcpToolCapabilities = {
|
|
|
3618
3758
|
requiredPermission: "write",
|
|
3619
3759
|
security: { type: "oauth2", scopes: [chalksurfMcpScopes.write] }
|
|
3620
3760
|
},
|
|
3761
|
+
resolve_quality_issue: {
|
|
3762
|
+
name: "resolve_quality_issue",
|
|
3763
|
+
title: "Resolve quality issue",
|
|
3764
|
+
description: "Mark an active quality issue resolved after checking the exercise evidence timestamp.",
|
|
3765
|
+
inputSchema: resolveQualityIssueInputSchema,
|
|
3766
|
+
outputSchema: resolveQualityIssueOutputSchema,
|
|
3767
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
3768
|
+
requiredPermission: "write",
|
|
3769
|
+
security: { type: "oauth2", scopes: [chalksurfMcpScopes.write] }
|
|
3770
|
+
},
|
|
3771
|
+
dismiss_quality_issue: {
|
|
3772
|
+
name: "dismiss_quality_issue",
|
|
3773
|
+
title: "Dismiss quality issue",
|
|
3774
|
+
description: "Dismiss an active quality issue after checking the exercise evidence timestamp.",
|
|
3775
|
+
inputSchema: dismissQualityIssueInputSchema,
|
|
3776
|
+
outputSchema: dismissQualityIssueOutputSchema,
|
|
3777
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
3778
|
+
requiredPermission: "write",
|
|
3779
|
+
security: { type: "oauth2", scopes: [chalksurfMcpScopes.write] }
|
|
3780
|
+
},
|
|
3621
3781
|
set_exercise_visibility: {
|
|
3622
3782
|
name: "set_exercise_visibility",
|
|
3623
3783
|
title: "Set exercise visibility",
|
|
@@ -3672,6 +3832,16 @@ var chalksurfMcpToolCapabilities = {
|
|
|
3672
3832
|
requiredPermission: "read",
|
|
3673
3833
|
security: { type: "oauth2", scopes: [chalksurfMcpScopes.read] }
|
|
3674
3834
|
},
|
|
3835
|
+
list_sheets: {
|
|
3836
|
+
name: "list_sheets",
|
|
3837
|
+
title: "List sheets",
|
|
3838
|
+
description: "Use this to list exercise sheets owned by a ChalkSurf organization and inspect their folder paths and series assignments.",
|
|
3839
|
+
inputSchema: listSheetsInputSchema,
|
|
3840
|
+
outputSchema: listSheetsOutputSchema,
|
|
3841
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
3842
|
+
requiredPermission: "read",
|
|
3843
|
+
security: { type: "oauth2", scopes: [chalksurfMcpScopes.read] }
|
|
3844
|
+
},
|
|
3675
3845
|
get_sheet: {
|
|
3676
3846
|
name: "get_sheet",
|
|
3677
3847
|
title: "Get sheet",
|
|
@@ -3772,6 +3942,16 @@ var chalksurfMcpToolCapabilities = {
|
|
|
3772
3942
|
requiredPermission: "write",
|
|
3773
3943
|
security: { type: "oauth2", scopes: [chalksurfMcpScopes.write] }
|
|
3774
3944
|
},
|
|
3945
|
+
verify_bulk_sheet_updates: {
|
|
3946
|
+
name: "verify_bulk_sheet_updates",
|
|
3947
|
+
title: "Verify bulk sheet updates",
|
|
3948
|
+
description: "Use this after an uncertain bulk apply response to classify each requested sheet target as applied, pending, conflicting, or error without writing.",
|
|
3949
|
+
inputSchema: verifyBulkSheetUpdatesInputSchema,
|
|
3950
|
+
outputSchema: verifyBulkSheetUpdatesOutputSchema,
|
|
3951
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
3952
|
+
requiredPermission: "write",
|
|
3953
|
+
security: { type: "oauth2", scopes: [chalksurfMcpScopes.write] }
|
|
3954
|
+
},
|
|
3775
3955
|
set_sheet_visibility: {
|
|
3776
3956
|
name: "set_sheet_visibility",
|
|
3777
3957
|
title: "Set sheet visibility",
|
|
@@ -3950,7 +4130,7 @@ var chalksurfMcpToolCapabilities = {
|
|
|
3950
4130
|
upload_exercise_figure: {
|
|
3951
4131
|
name: "upload_exercise_figure",
|
|
3952
4132
|
title: "Upload exercise figure",
|
|
3953
|
-
description: "Use this to attach one reviewed image, MCP file, HTTPS image, base64 image, or rendered figure to an exercise by providing exerciseId, figureType, source, and width.
|
|
4133
|
+
description: "Use this to attach one reviewed image, MCP file, HTTPS image, base64 image, or rendered figure to an exercise by providing exerciseId, figureType, source, and width.",
|
|
3954
4134
|
inputSchema: uploadExerciseFigureInputSchema,
|
|
3955
4135
|
mcpInputSchema: uploadExerciseFigureMcpToolInputSchema,
|
|
3956
4136
|
outputSchema: uploadExerciseFigureOutputSchema,
|
|
@@ -3958,16 +4138,6 @@ var chalksurfMcpToolCapabilities = {
|
|
|
3958
4138
|
requiredPermission: "write",
|
|
3959
4139
|
security: { type: "oauth2", scopes: [chalksurfMcpScopes.write] },
|
|
3960
4140
|
fileParams: chatGptFigureUploadFileParamNames
|
|
3961
|
-
},
|
|
3962
|
-
attach_exercise_figures: {
|
|
3963
|
-
name: "attach_exercise_figures",
|
|
3964
|
-
title: "Attach exercise figures",
|
|
3965
|
-
description: "Deprecated compatibility tool. Use upload_exercise_figure with exerciseId for new figures; use this only to attach already-uploaded compatibility URLs or replace existing figure lists.",
|
|
3966
|
-
inputSchema: attachExerciseFiguresInputSchema,
|
|
3967
|
-
outputSchema: attachExerciseFiguresOutputSchema,
|
|
3968
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
3969
|
-
requiredPermission: "write",
|
|
3970
|
-
security: { type: "oauth2", scopes: [chalksurfMcpScopes.write] }
|
|
3971
4141
|
}
|
|
3972
4142
|
};
|
|
3973
4143
|
|
|
@@ -8719,29 +8889,106 @@ ${formatOrganizationSummary(result.organization)}`,
|
|
|
8719
8889
|
};
|
|
8720
8890
|
|
|
8721
8891
|
// src/commands/exercise.ts
|
|
8722
|
-
import { readFile as
|
|
8723
|
-
import { basename as basename2, extname as extname2, isAbsolute as isAbsolute2, resolve as
|
|
8892
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
8893
|
+
import { basename as basename2, extname as extname2, isAbsolute as isAbsolute2, resolve as resolve5 } from "node:path";
|
|
8724
8894
|
import { z as z13 } from "zod";
|
|
8725
8895
|
|
|
8726
|
-
// src/lib/
|
|
8727
|
-
|
|
8728
|
-
|
|
8896
|
+
// src/lib/json-input.ts
|
|
8897
|
+
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
8898
|
+
import { resolve } from "node:path";
|
|
8899
|
+
var isInteractiveStdin2 = (stdin) => {
|
|
8900
|
+
return stdin.isTTY === true;
|
|
8729
8901
|
};
|
|
8730
|
-
var
|
|
8731
|
-
|
|
8732
|
-
|
|
8902
|
+
var readTextFromStdin = async (stdin) => {
|
|
8903
|
+
const chunks = [];
|
|
8904
|
+
for await (const chunk of stdin) {
|
|
8905
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
8733
8906
|
}
|
|
8907
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
8908
|
+
};
|
|
8909
|
+
var parseJsonObjectText = ({
|
|
8910
|
+
invalidJsonMessage,
|
|
8911
|
+
nonObjectMessage,
|
|
8912
|
+
text
|
|
8913
|
+
}) => {
|
|
8734
8914
|
let parsedValue;
|
|
8735
8915
|
try {
|
|
8736
|
-
parsedValue = JSON.parse(
|
|
8916
|
+
parsedValue = JSON.parse(text);
|
|
8737
8917
|
} catch {
|
|
8738
|
-
throw new CliCommandError(
|
|
8918
|
+
throw new CliCommandError(invalidJsonMessage, 2);
|
|
8739
8919
|
}
|
|
8740
8920
|
if (typeof parsedValue !== "object" || parsedValue === null || Array.isArray(parsedValue)) {
|
|
8741
|
-
throw new CliCommandError(
|
|
8921
|
+
throw new CliCommandError(nonObjectMessage, 2);
|
|
8742
8922
|
}
|
|
8743
8923
|
return parsedValue;
|
|
8744
8924
|
};
|
|
8925
|
+
var loadJsonObjectInput = async ({
|
|
8926
|
+
cwd,
|
|
8927
|
+
inputJson,
|
|
8928
|
+
inputPath,
|
|
8929
|
+
stdin
|
|
8930
|
+
}) => {
|
|
8931
|
+
if (inputPath !== void 0 && inputJson !== void 0) {
|
|
8932
|
+
throw new CliCommandError("Pass --input or --input-json, not both.", 2);
|
|
8933
|
+
}
|
|
8934
|
+
if (inputPath === void 0 && inputJson === void 0) {
|
|
8935
|
+
throw new CliCommandError("One of --input or --input-json is required.", 2);
|
|
8936
|
+
}
|
|
8937
|
+
if (inputJson !== void 0) {
|
|
8938
|
+
return parseJsonObjectText({
|
|
8939
|
+
invalidJsonMessage: "--input-json must be valid JSON.",
|
|
8940
|
+
nonObjectMessage: "--input-json must be a JSON object.",
|
|
8941
|
+
text: inputJson
|
|
8942
|
+
});
|
|
8943
|
+
}
|
|
8944
|
+
if (inputPath === "-") {
|
|
8945
|
+
if (isInteractiveStdin2(stdin)) {
|
|
8946
|
+
throw new CliCommandError('No JSON was piped on stdin. Pipe JSON into "--input -".', 2);
|
|
8947
|
+
}
|
|
8948
|
+
const inputText = await readTextFromStdin(stdin);
|
|
8949
|
+
if (inputText.trim().length === 0) {
|
|
8950
|
+
throw new CliCommandError('No JSON was piped on stdin. Pipe JSON into "--input -".', 2);
|
|
8951
|
+
}
|
|
8952
|
+
return parseJsonObjectText({
|
|
8953
|
+
invalidJsonMessage: "Standard input must contain valid JSON.",
|
|
8954
|
+
nonObjectMessage: "Standard input must contain a JSON object.",
|
|
8955
|
+
text: inputText
|
|
8956
|
+
});
|
|
8957
|
+
}
|
|
8958
|
+
const resolvedInputPath = resolve(cwd, inputPath);
|
|
8959
|
+
let inputStats;
|
|
8960
|
+
try {
|
|
8961
|
+
inputStats = await stat(resolvedInputPath);
|
|
8962
|
+
} catch (error) {
|
|
8963
|
+
if (error.code === "ENOENT") {
|
|
8964
|
+
throw new CliCommandError(`Input file "${inputPath}" does not exist.`, 2);
|
|
8965
|
+
}
|
|
8966
|
+
throw error;
|
|
8967
|
+
}
|
|
8968
|
+
if (!inputStats.isFile()) {
|
|
8969
|
+
throw new CliCommandError(`Input path "${inputPath}" is not a file.`, 2);
|
|
8970
|
+
}
|
|
8971
|
+
return parseJsonObjectText({
|
|
8972
|
+
invalidJsonMessage: `Input file "${inputPath}" must contain valid JSON.`,
|
|
8973
|
+
nonObjectMessage: `Input file "${inputPath}" must contain a JSON object.`,
|
|
8974
|
+
text: await readFile2(resolvedInputPath, "utf8")
|
|
8975
|
+
});
|
|
8976
|
+
};
|
|
8977
|
+
|
|
8978
|
+
// src/lib/agent-patch.ts
|
|
8979
|
+
var formatZodIssues = (issues) => {
|
|
8980
|
+
return issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ");
|
|
8981
|
+
};
|
|
8982
|
+
var parseJsonObjectFlag = ({ label, value }) => {
|
|
8983
|
+
if (value === void 0) {
|
|
8984
|
+
throw new CliCommandError(`${label} is required.`, 2);
|
|
8985
|
+
}
|
|
8986
|
+
return parseJsonObjectText({
|
|
8987
|
+
invalidJsonMessage: `${label} must be valid JSON.`,
|
|
8988
|
+
nonObjectMessage: `${label} must be a JSON object.`,
|
|
8989
|
+
text: value
|
|
8990
|
+
});
|
|
8991
|
+
};
|
|
8745
8992
|
var parseUpdateExerciseAgentInput = (input) => {
|
|
8746
8993
|
const parsedInput = updateExerciseAgentInputSchema.safeParse(input);
|
|
8747
8994
|
if (!parsedInput.success) {
|
|
@@ -8777,10 +9024,46 @@ var parseBulkUpdateSheetsAgentInput = (input) => {
|
|
|
8777
9024
|
}
|
|
8778
9025
|
return parsedInput.data;
|
|
8779
9026
|
};
|
|
9027
|
+
var parseVerifyBulkSheetUpdatesAgentInput = (input) => {
|
|
9028
|
+
if (typeof input === "object" && input !== null && "schemaVersion" in input) {
|
|
9029
|
+
throw new CliCommandError(
|
|
9030
|
+
"CLI result envelopes are not verifier manifests. Pass the original bulk update manifest or an object with updates.",
|
|
9031
|
+
2
|
|
9032
|
+
);
|
|
9033
|
+
}
|
|
9034
|
+
if (typeof input === "object" && input !== null && "mode" in input) {
|
|
9035
|
+
const parsedBulkInput = bulkUpdateSheetsAgentInputSchema.safeParse(input);
|
|
9036
|
+
if (!parsedBulkInput.success) {
|
|
9037
|
+
throw new CliCommandError(
|
|
9038
|
+
`Invalid bulk sheet verification input: ${formatZodIssues(parsedBulkInput.error.issues)}`,
|
|
9039
|
+
2
|
|
9040
|
+
);
|
|
9041
|
+
}
|
|
9042
|
+
return { updates: parsedBulkInput.data.updates };
|
|
9043
|
+
}
|
|
9044
|
+
const parsedInput = verifyBulkSheetUpdatesAgentInputSchema.safeParse(input);
|
|
9045
|
+
if (!parsedInput.success) {
|
|
9046
|
+
throw new CliCommandError(`Invalid bulk sheet verification input: ${formatZodIssues(parsedInput.error.issues)}`, 2);
|
|
9047
|
+
}
|
|
9048
|
+
return parsedInput.data;
|
|
9049
|
+
};
|
|
8780
9050
|
|
|
8781
9051
|
// src/lib/agent-receipt.ts
|
|
9052
|
+
var getBulkSheetCount = (details) => {
|
|
9053
|
+
if (typeof details !== "object" || details === null || !("sheetIds" in details) || !Array.isArray(details.sheetIds)) {
|
|
9054
|
+
return null;
|
|
9055
|
+
}
|
|
9056
|
+
return details.sheetIds.filter((sheetId) => typeof sheetId === "string").length;
|
|
9057
|
+
};
|
|
8782
9058
|
var formatAgentWriteReceiptOutput = ({ receipt }) => {
|
|
8783
|
-
|
|
9059
|
+
if (receipt.resource) {
|
|
9060
|
+
return `${receipt.operation} -> ${receipt.resource.type} ${receipt.resource.id}`;
|
|
9061
|
+
}
|
|
9062
|
+
const bulkSheetCount = receipt.operation === "bulk_update_sheets" ? getBulkSheetCount(receipt.details) : null;
|
|
9063
|
+
if (bulkSheetCount !== null) {
|
|
9064
|
+
return `${receipt.operation} -> ${bulkSheetCount} sheet${bulkSheetCount === 1 ? "" : "s"}`;
|
|
9065
|
+
}
|
|
9066
|
+
return `${receipt.operation} completed`;
|
|
8784
9067
|
};
|
|
8785
9068
|
|
|
8786
9069
|
// src/lib/command-options.ts
|
|
@@ -8865,8 +9148,8 @@ var toApiOwnership = (ownership) => {
|
|
|
8865
9148
|
|
|
8866
9149
|
// src/lib/import-files.ts
|
|
8867
9150
|
import { openAsBlob } from "node:fs";
|
|
8868
|
-
import { stat } from "node:fs/promises";
|
|
8869
|
-
import { extname, resolve } from "node:path";
|
|
9151
|
+
import { stat as stat2 } from "node:fs/promises";
|
|
9152
|
+
import { extname, resolve as resolve2 } from "node:path";
|
|
8870
9153
|
var guessMimeType = (fileName) => {
|
|
8871
9154
|
return mimeTypesByExtension[extname(fileName).toLowerCase()] ?? "application/octet-stream";
|
|
8872
9155
|
};
|
|
@@ -8937,7 +9220,7 @@ var buildFileImportCommandSources = async ({
|
|
|
8937
9220
|
continue;
|
|
8938
9221
|
}
|
|
8939
9222
|
try {
|
|
8940
|
-
const pathStats = await
|
|
9223
|
+
const pathStats = await stat2(resolve2(cwd, rawSource));
|
|
8941
9224
|
if (pathStats.isDirectory()) {
|
|
8942
9225
|
if (relativePath) {
|
|
8943
9226
|
throw new CliCommandError("--relative-path cannot be used with directory sources.", 2);
|
|
@@ -9065,8 +9348,8 @@ var formatCliImportTranslationJobs = (translationJobs) => {
|
|
|
9065
9348
|
};
|
|
9066
9349
|
|
|
9067
9350
|
// src/lib/manifest.ts
|
|
9068
|
-
import { readFile as
|
|
9069
|
-
import { resolve as
|
|
9351
|
+
import { readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
9352
|
+
import { resolve as resolve3 } from "node:path";
|
|
9070
9353
|
import { z as z12 } from "zod";
|
|
9071
9354
|
|
|
9072
9355
|
// src/lib/translation-languages.ts
|
|
@@ -9230,10 +9513,10 @@ var assertUniqueSourceIds = (sources) => {
|
|
|
9230
9513
|
seenSourceIds.add(source.sourceId);
|
|
9231
9514
|
}
|
|
9232
9515
|
};
|
|
9233
|
-
var
|
|
9516
|
+
var isInteractiveStdin3 = (stdin) => {
|
|
9234
9517
|
return stdin.isTTY === true;
|
|
9235
9518
|
};
|
|
9236
|
-
var
|
|
9519
|
+
var readTextFromStdin2 = async (stdin) => {
|
|
9237
9520
|
let text = "";
|
|
9238
9521
|
for await (const chunk of stdin) {
|
|
9239
9522
|
text += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
@@ -9400,15 +9683,15 @@ var loadManifest = async ({
|
|
|
9400
9683
|
stdin
|
|
9401
9684
|
}) => {
|
|
9402
9685
|
if (manifestPath === "-") {
|
|
9403
|
-
if (
|
|
9686
|
+
if (isInteractiveStdin3(stdin)) {
|
|
9404
9687
|
throw new CliCommandError('No manifest was piped on stdin. Pipe JSON into "--manifest -".', 2);
|
|
9405
9688
|
}
|
|
9406
|
-
return parseManifest(await
|
|
9689
|
+
return parseManifest(await readTextFromStdin2(stdin));
|
|
9407
9690
|
}
|
|
9408
|
-
const resolvedManifestPath =
|
|
9691
|
+
const resolvedManifestPath = resolve3(cwd, manifestPath);
|
|
9409
9692
|
let fileStats;
|
|
9410
9693
|
try {
|
|
9411
|
-
fileStats = await
|
|
9694
|
+
fileStats = await stat3(resolvedManifestPath);
|
|
9412
9695
|
} catch (error) {
|
|
9413
9696
|
if (error.code === "ENOENT") {
|
|
9414
9697
|
throw new CliCommandError(`Manifest file "${manifestPath}" does not exist.`, 2);
|
|
@@ -9418,7 +9701,7 @@ var loadManifest = async ({
|
|
|
9418
9701
|
if (!fileStats.isFile()) {
|
|
9419
9702
|
throw new CliCommandError(`Manifest path "${manifestPath}" is not a file.`, 2);
|
|
9420
9703
|
}
|
|
9421
|
-
return parseManifest(await
|
|
9704
|
+
return parseManifest(await readFile3(resolvedManifestPath, "utf8"));
|
|
9422
9705
|
};
|
|
9423
9706
|
var loadExerciseImportManifest = async ({
|
|
9424
9707
|
cwd,
|
|
@@ -9471,9 +9754,9 @@ var loadSheetImportManifest = async ({
|
|
|
9471
9754
|
|
|
9472
9755
|
// src/lib/source-resolver.ts
|
|
9473
9756
|
import { createWriteStream } from "node:fs";
|
|
9474
|
-
import { mkdtemp, open, readdir, rm as rm2, stat as
|
|
9757
|
+
import { mkdtemp, open, readdir, rm as rm2, stat as stat4 } from "node:fs/promises";
|
|
9475
9758
|
import { tmpdir } from "node:os";
|
|
9476
|
-
import { basename, isAbsolute, relative, resolve as
|
|
9759
|
+
import { basename, isAbsolute, relative, resolve as resolve4 } from "node:path";
|
|
9477
9760
|
import { Readable } from "node:stream";
|
|
9478
9761
|
import { pipeline } from "node:stream/promises";
|
|
9479
9762
|
import pLimit from "p-limit";
|
|
@@ -9498,7 +9781,7 @@ var normalizeRelativePath = (relativePath) => {
|
|
|
9498
9781
|
var ensureExistingFile = async (filePath, label) => {
|
|
9499
9782
|
let fileStats;
|
|
9500
9783
|
try {
|
|
9501
|
-
fileStats = await
|
|
9784
|
+
fileStats = await stat4(filePath);
|
|
9502
9785
|
} catch (error) {
|
|
9503
9786
|
if (error.code === "ENOENT") {
|
|
9504
9787
|
throw createSourceResolutionError(`${label} "${filePath}" does not exist.`);
|
|
@@ -9512,7 +9795,7 @@ var ensureExistingFile = async (filePath, label) => {
|
|
|
9512
9795
|
var ensureExistingDirectory = async (directoryPath, label) => {
|
|
9513
9796
|
let directoryStats;
|
|
9514
9797
|
try {
|
|
9515
|
-
directoryStats = await
|
|
9798
|
+
directoryStats = await stat4(directoryPath);
|
|
9516
9799
|
} catch (error) {
|
|
9517
9800
|
if (error.code === "ENOENT") {
|
|
9518
9801
|
throw createSourceResolutionError(`${label} "${directoryPath}" does not exist.`);
|
|
@@ -9535,7 +9818,7 @@ var collectFilesRecursively = async (directoryPath) => {
|
|
|
9535
9818
|
const sortedEntries = directoryEntries.sort((leftEntry, rightEntry) => leftEntry.name.localeCompare(rightEntry.name));
|
|
9536
9819
|
const filePaths = [];
|
|
9537
9820
|
for (const directoryEntry of sortedEntries) {
|
|
9538
|
-
const entryPath =
|
|
9821
|
+
const entryPath = resolve4(directoryPath, directoryEntry.name);
|
|
9539
9822
|
if (directoryEntry.isDirectory()) {
|
|
9540
9823
|
filePaths.push(...await collectFilesRecursively(entryPath));
|
|
9541
9824
|
continue;
|
|
@@ -9596,7 +9879,7 @@ var resolveLocalFileSource = async ({
|
|
|
9596
9879
|
cwd,
|
|
9597
9880
|
sourceInputIndex
|
|
9598
9881
|
}) => {
|
|
9599
|
-
const resolvedPath =
|
|
9882
|
+
const resolvedPath = resolve4(cwd, source.path);
|
|
9600
9883
|
await ensureExistingFile(resolvedPath, "Local source");
|
|
9601
9884
|
const relativePath = normalizeRelativePath(source.relativePath ?? basename(resolvedPath));
|
|
9602
9885
|
return [
|
|
@@ -9616,8 +9899,8 @@ var resolveDirectorySource = async ({
|
|
|
9616
9899
|
cwd,
|
|
9617
9900
|
sourceInputIndex
|
|
9618
9901
|
}) => {
|
|
9619
|
-
const resolvedDirectoryPath =
|
|
9620
|
-
const resolvedRelativeRoot =
|
|
9902
|
+
const resolvedDirectoryPath = resolve4(cwd, source.path);
|
|
9903
|
+
const resolvedRelativeRoot = resolve4(cwd, source.relativeRoot ?? source.path);
|
|
9621
9904
|
await ensureExistingDirectory(resolvedDirectoryPath, "Directory source");
|
|
9622
9905
|
await ensureExistingDirectory(resolvedRelativeRoot, "Directory relativeRoot");
|
|
9623
9906
|
if (!isContainedWithin({ childPath: resolvedDirectoryPath, parentPath: resolvedRelativeRoot })) {
|
|
@@ -9656,8 +9939,8 @@ var resolveUrlSource = async ({
|
|
|
9656
9939
|
throw createSourceResolutionError(`URL source "${source.url}" must use http or https.`);
|
|
9657
9940
|
}
|
|
9658
9941
|
const relativePath = normalizeRelativePath(source.relativePath ?? deriveRelativePathFromUrl(parsedUrl));
|
|
9659
|
-
const downloadDirectoryPath = await mkdtemp(
|
|
9660
|
-
const downloadedFilePath =
|
|
9942
|
+
const downloadDirectoryPath = await mkdtemp(resolve4(tmpdir(), "chalksurf-cli-source-"));
|
|
9943
|
+
const downloadedFilePath = resolve4(downloadDirectoryPath, basename(relativePath));
|
|
9661
9944
|
try {
|
|
9662
9945
|
const response = await fetch(source.url);
|
|
9663
9946
|
if (!response.ok) {
|
|
@@ -9979,9 +10262,6 @@ var formatUploadExerciseFigureOutput = ({ figure, receipt, review }) => [
|
|
|
9979
10262
|
`figure: ${figure.type} ${figure.url}`,
|
|
9980
10263
|
`image: ${review.pixelWidth ?? "?"}x${review.pixelHeight ?? "?"} ${review.byteLength} bytes`
|
|
9981
10264
|
].join("\n");
|
|
9982
|
-
var formatAttachExerciseFiguresOutput = ({ receipt }) => {
|
|
9983
|
-
return formatAgentWriteReceiptOutput({ receipt });
|
|
9984
|
-
};
|
|
9985
10265
|
var getExerciseTranslationJobIds = ({ jobs }) => jobs.map((job) => job.jobId);
|
|
9986
10266
|
var formatExerciseTranslationOutput = ({ jobs }) => {
|
|
9987
10267
|
if (jobs.length === 0) {
|
|
@@ -9998,16 +10278,6 @@ var resolveVisibilityFlag = ({ privateFlag, publicFlag }) => {
|
|
|
9998
10278
|
}
|
|
9999
10279
|
return Boolean(publicFlag);
|
|
10000
10280
|
};
|
|
10001
|
-
var parseJsonFlagValue = ({ label, value }) => {
|
|
10002
|
-
if (value === void 0) {
|
|
10003
|
-
throw new CliCommandError(`${label} is required.`, 2);
|
|
10004
|
-
}
|
|
10005
|
-
try {
|
|
10006
|
-
return JSON.parse(value);
|
|
10007
|
-
} catch {
|
|
10008
|
-
throw new CliCommandError(`${label} must be valid JSON.`, 2);
|
|
10009
|
-
}
|
|
10010
|
-
};
|
|
10011
10281
|
var resolveExerciseFigureType = ({
|
|
10012
10282
|
label,
|
|
10013
10283
|
required,
|
|
@@ -10024,13 +10294,6 @@ var resolveExerciseFigureType = ({
|
|
|
10024
10294
|
}
|
|
10025
10295
|
return value;
|
|
10026
10296
|
};
|
|
10027
|
-
var resolveAttachFigureMode = (value) => {
|
|
10028
|
-
const mode = value ?? "append";
|
|
10029
|
-
if (mode !== "append" && mode !== "replace_type" && mode !== "replace_all") {
|
|
10030
|
-
throw new CliCommandError("--mode must be one of: append, replace_type, replace_all.", 2);
|
|
10031
|
-
}
|
|
10032
|
-
return mode;
|
|
10033
|
-
};
|
|
10034
10297
|
var parseUploadExerciseFigureInput = (input) => {
|
|
10035
10298
|
const parsedInput = uploadExerciseFigureAgentInputSchema.safeParse(input);
|
|
10036
10299
|
if (!parsedInput.success) {
|
|
@@ -10038,22 +10301,7 @@ var parseUploadExerciseFigureInput = (input) => {
|
|
|
10038
10301
|
}
|
|
10039
10302
|
return parsedInput.data;
|
|
10040
10303
|
};
|
|
10041
|
-
var
|
|
10042
|
-
const parsedInput = attachExerciseFiguresAgentInputSchema.safeParse(input);
|
|
10043
|
-
if (!parsedInput.success) {
|
|
10044
|
-
throw new CliCommandError(`Invalid figure attach input: ${formatZodIssues(parsedInput.error.issues)}`, 2);
|
|
10045
|
-
}
|
|
10046
|
-
return parsedInput.data;
|
|
10047
|
-
};
|
|
10048
|
-
var parseFigureJsonList = (value) => {
|
|
10049
|
-
const parsedValue = parseJsonFlagValue({ label: "--figure-json", value });
|
|
10050
|
-
const figures = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
|
|
10051
|
-
if (!figures.every((figure) => typeof figure === "object" && figure !== null && !Array.isArray(figure))) {
|
|
10052
|
-
throw new CliCommandError("--figure-json must be a figure object or an array of figure objects.", 2);
|
|
10053
|
-
}
|
|
10054
|
-
return figures;
|
|
10055
|
-
};
|
|
10056
|
-
var resolveLocalPath = ({ cwd, path }) => isAbsolute2(path) ? path : resolve4(cwd, path);
|
|
10304
|
+
var resolveLocalPath = ({ cwd, path }) => isAbsolute2(path) ? path : resolve5(cwd, path);
|
|
10057
10305
|
var getMimeTypeFromFileName = (fileName) => mimeTypesByExtension[extname2(fileName).toLowerCase()] ?? "";
|
|
10058
10306
|
var buildLocalFigureUploadFormData = async ({
|
|
10059
10307
|
context,
|
|
@@ -10062,7 +10310,7 @@ var buildLocalFigureUploadFormData = async ({
|
|
|
10062
10310
|
}) => {
|
|
10063
10311
|
const resolvedSourcePath = resolveLocalPath({ cwd: context.cwd, path: source });
|
|
10064
10312
|
const fileName = input.originalFileName ?? basename2(resolvedSourcePath);
|
|
10065
|
-
const fileBuffer = await
|
|
10313
|
+
const fileBuffer = await readFile4(resolvedSourcePath);
|
|
10066
10314
|
const file = new File([new Uint8Array(fileBuffer)], fileName, { type: getMimeTypeFromFileName(fileName) });
|
|
10067
10315
|
const formData = new FormData();
|
|
10068
10316
|
formData.set("payload", JSON.stringify(input));
|
|
@@ -10442,11 +10690,14 @@ var registerExerciseCommands = (exerciseYargs, context) => {
|
|
|
10442
10690
|
}
|
|
10443
10691
|
).command(
|
|
10444
10692
|
"figure <subcommand>",
|
|
10445
|
-
"Exercise figure upload
|
|
10693
|
+
"Exercise figure upload commands",
|
|
10446
10694
|
(figureYargs) => figureYargs.command(
|
|
10447
|
-
"upload [source]",
|
|
10448
|
-
"Upload a reviewed figure image to exercise
|
|
10449
|
-
(uploadYargs) => uploadYargs.positional("
|
|
10695
|
+
"upload <exerciseId> [source]",
|
|
10696
|
+
"Upload and attach a reviewed figure image to an exercise",
|
|
10697
|
+
(uploadYargs) => uploadYargs.positional("exerciseId", {
|
|
10698
|
+
type: "string",
|
|
10699
|
+
describe: "Exercise ID to attach the figure to"
|
|
10700
|
+
}).positional("source", {
|
|
10450
10701
|
type: "string",
|
|
10451
10702
|
describe: "Local image file path. Omit when using --input-json for HTTPS/base64/rendered sources."
|
|
10452
10703
|
}).option("input-json", {
|
|
@@ -10467,15 +10718,21 @@ var registerExerciseCommands = (exerciseYargs, context) => {
|
|
|
10467
10718
|
default: false,
|
|
10468
10719
|
describe: "Allow uploading a rendered figure whose quality status is needs_revision"
|
|
10469
10720
|
}).example(
|
|
10470
|
-
"chalksurf exercise figure upload ./triangle.png --figure-type text --json",
|
|
10721
|
+
"chalksurf exercise figure upload 00000000-0000-4000-8000-000000000001 ./triangle.png --figure-type text --json",
|
|
10471
10722
|
"Upload a local figure image"
|
|
10472
10723
|
).example(
|
|
10473
|
-
`chalksurf exercise figure upload --input-json '{"figureType":"text","source":{"kind":"https_url","url":"https://example.com/figure.png"}}' --json`,
|
|
10724
|
+
`chalksurf exercise figure upload 00000000-0000-4000-8000-000000000001 --input-json '{"exerciseId":"00000000-0000-4000-8000-000000000001","figureType":"text","source":{"kind":"https_url","url":"https://example.com/figure.png"}}' --json`,
|
|
10474
10725
|
"Upload a figure from an HTTPS image URL"
|
|
10475
10726
|
),
|
|
10476
10727
|
async (argv) => {
|
|
10477
10728
|
const source = argv.source;
|
|
10729
|
+
const exerciseId = resolveRequestedUuid({
|
|
10730
|
+
fallbackValue: argv.exerciseId,
|
|
10731
|
+
label: "exerciseId",
|
|
10732
|
+
required: true
|
|
10733
|
+
});
|
|
10478
10734
|
const input = argv.inputJson === void 0 ? parseUploadExerciseFigureInput({
|
|
10735
|
+
exerciseId,
|
|
10479
10736
|
figureType: resolveExerciseFigureType({
|
|
10480
10737
|
label: "--figure-type",
|
|
10481
10738
|
required: true,
|
|
@@ -10485,9 +10742,10 @@ var registerExerciseCommands = (exerciseYargs, context) => {
|
|
|
10485
10742
|
...argv.widthCm === void 0 ? {} : { widthInCm: argv.widthCm },
|
|
10486
10743
|
...argv.originalFileName === void 0 ? {} : { originalFileName: argv.originalFileName },
|
|
10487
10744
|
allowNeedsRevision: argv.allowNeedsRevision ?? false
|
|
10488
|
-
}) : parseUploadExerciseFigureInput(
|
|
10489
|
-
parseJsonObjectFlag({ label: "--input-json", value: argv.inputJson })
|
|
10490
|
-
|
|
10745
|
+
}) : parseUploadExerciseFigureInput({
|
|
10746
|
+
...parseJsonObjectFlag({ label: "--input-json", value: argv.inputJson }),
|
|
10747
|
+
exerciseId
|
|
10748
|
+
});
|
|
10491
10749
|
if (argv.inputJson === void 0 && !source) {
|
|
10492
10750
|
throw new CliCommandError("source is required unless --input-json is provided.", 2);
|
|
10493
10751
|
}
|
|
@@ -10527,91 +10785,6 @@ var registerExerciseCommands = (exerciseYargs, context) => {
|
|
|
10527
10785
|
}
|
|
10528
10786
|
);
|
|
10529
10787
|
}
|
|
10530
|
-
).command(
|
|
10531
|
-
"attach [exerciseId]",
|
|
10532
|
-
"Attach one or more uploaded figures to an exercise",
|
|
10533
|
-
(attachYargs) => attachYargs.positional("exerciseId", {
|
|
10534
|
-
type: "string",
|
|
10535
|
-
describe: "Exercise ID to update"
|
|
10536
|
-
}).option("expected-updated-at", {
|
|
10537
|
-
type: "string",
|
|
10538
|
-
demandOption: true,
|
|
10539
|
-
describe: "Expected current exercise updated_at ISO timestamp"
|
|
10540
|
-
}).option("figure-json", {
|
|
10541
|
-
type: "string",
|
|
10542
|
-
demandOption: true,
|
|
10543
|
-
describe: "Figure object or array of figure objects, usually from exercise figure upload output"
|
|
10544
|
-
}).option("mode", {
|
|
10545
|
-
type: "string",
|
|
10546
|
-
default: "append",
|
|
10547
|
-
choices: ["append", "replace_type", "replace_all"],
|
|
10548
|
-
describe: "How to merge figures into the exercise figure collection"
|
|
10549
|
-
}).option("figure-type", {
|
|
10550
|
-
type: "string",
|
|
10551
|
-
choices: [...exerciseFigureTypes],
|
|
10552
|
-
describe: "Required for --mode replace_type"
|
|
10553
|
-
}).option("target-sheet-id", {
|
|
10554
|
-
type: "string",
|
|
10555
|
-
describe: "Sheet ID whose variant workflow this update targets"
|
|
10556
|
-
}).option("allow-shared-exercise-update", {
|
|
10557
|
-
type: "boolean",
|
|
10558
|
-
default: false,
|
|
10559
|
-
describe: "Acknowledge updating an exercise used outside --target-sheet-id"
|
|
10560
|
-
}).example(
|
|
10561
|
-
`chalksurf exercise figure attach 00000000-0000-4000-8000-000000000001 --expected-updated-at 2026-01-01T00:00:00.000Z --figure-json '{"type":"text","url":"https://...","widthInCm":6}' --json`,
|
|
10562
|
-
"Append one uploaded figure to an exercise"
|
|
10563
|
-
),
|
|
10564
|
-
async (argv) => {
|
|
10565
|
-
const exerciseId = resolveRequestedUuid({
|
|
10566
|
-
fallbackValue: argv.exerciseId,
|
|
10567
|
-
label: "exerciseId",
|
|
10568
|
-
required: true
|
|
10569
|
-
});
|
|
10570
|
-
const targetSheetId = resolveRequestedUuid({
|
|
10571
|
-
flagValue: argv.targetSheetId,
|
|
10572
|
-
label: "--target-sheet-id"
|
|
10573
|
-
});
|
|
10574
|
-
const mode = resolveAttachFigureMode(argv.mode);
|
|
10575
|
-
const figureType = resolveExerciseFigureType({
|
|
10576
|
-
label: "--figure-type",
|
|
10577
|
-
required: mode === "replace_type",
|
|
10578
|
-
value: argv.figureType
|
|
10579
|
-
});
|
|
10580
|
-
const input = parseAttachExerciseFiguresInput({
|
|
10581
|
-
exerciseId,
|
|
10582
|
-
expectedUpdatedAt: argv.expectedUpdatedAt,
|
|
10583
|
-
...targetSheetId === void 0 ? {} : { targetSheetId },
|
|
10584
|
-
allowSharedExerciseUpdate: argv.allowSharedExerciseUpdate ?? false,
|
|
10585
|
-
mode,
|
|
10586
|
-
...figureType === void 0 ? {} : { figureType },
|
|
10587
|
-
figures: parseFigureJsonList(argv.figureJson)
|
|
10588
|
-
});
|
|
10589
|
-
const { apiClient, organizationId } = await createResolvedApiClient({
|
|
10590
|
-
context,
|
|
10591
|
-
baseUrlFlagValue: argv.baseUrl,
|
|
10592
|
-
organizationFlagValue: argv.organization,
|
|
10593
|
-
profileName: argv.profile
|
|
10594
|
-
});
|
|
10595
|
-
let result;
|
|
10596
|
-
try {
|
|
10597
|
-
result = await apiClient.agentAttachExerciseFigures(input);
|
|
10598
|
-
} catch (error) {
|
|
10599
|
-
throw mapApiErrorToCliError(error);
|
|
10600
|
-
}
|
|
10601
|
-
context.output.print(
|
|
10602
|
-
{
|
|
10603
|
-
request: {
|
|
10604
|
-
...input,
|
|
10605
|
-
organizationId: organizationId ?? null
|
|
10606
|
-
},
|
|
10607
|
-
receipt: result.receipt
|
|
10608
|
-
},
|
|
10609
|
-
(output) => formatAttachExerciseFiguresOutput({ receipt: output.receipt }),
|
|
10610
|
-
{
|
|
10611
|
-
command: "exercise figure attach"
|
|
10612
|
-
}
|
|
10613
|
-
);
|
|
10614
|
-
}
|
|
10615
10788
|
).demandCommand(1).strict(),
|
|
10616
10789
|
() => {
|
|
10617
10790
|
}
|
|
@@ -11511,7 +11684,7 @@ var registerFeedbackCommands = (feedbackYargs, context) => {
|
|
|
11511
11684
|
|
|
11512
11685
|
// src/commands/figure.ts
|
|
11513
11686
|
import { writeFile as writeFile2 } from "node:fs/promises";
|
|
11514
|
-
import { isAbsolute as isAbsolute3, resolve as
|
|
11687
|
+
import { isAbsolute as isAbsolute3, resolve as resolve6 } from "node:path";
|
|
11515
11688
|
var parseFigureRenderDraftInput = (value) => {
|
|
11516
11689
|
const parsedInput = figureRenderDraftInputSchema.safeParse(parseJsonObjectFlag({ label: "--input-json", value }));
|
|
11517
11690
|
if (!parsedInput.success) {
|
|
@@ -11519,7 +11692,7 @@ var parseFigureRenderDraftInput = (value) => {
|
|
|
11519
11692
|
}
|
|
11520
11693
|
return parsedInput.data;
|
|
11521
11694
|
};
|
|
11522
|
-
var resolveOutputPath = ({ cwd, outputPath }) => isAbsolute3(outputPath) ? outputPath :
|
|
11695
|
+
var resolveOutputPath = ({ cwd, outputPath }) => isAbsolute3(outputPath) ? outputPath : resolve6(cwd, outputPath);
|
|
11523
11696
|
var writeFigurePng = async ({
|
|
11524
11697
|
cwd,
|
|
11525
11698
|
outputPath,
|
|
@@ -12045,11 +12218,923 @@ var registerProfileCommands = (profileYargs, context) => {
|
|
|
12045
12218
|
}
|
|
12046
12219
|
);
|
|
12047
12220
|
}
|
|
12048
|
-
).demandCommand(1).strict();
|
|
12221
|
+
).demandCommand(1).strict();
|
|
12222
|
+
};
|
|
12223
|
+
|
|
12224
|
+
// src/commands/quality-issue/index.ts
|
|
12225
|
+
import { z as z15 } from "zod";
|
|
12226
|
+
|
|
12227
|
+
// src/commands/quality-issue/artifact-integrity.ts
|
|
12228
|
+
import { createHash } from "node:crypto";
|
|
12229
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
12230
|
+
import { join as join2 } from "node:path";
|
|
12231
|
+
|
|
12232
|
+
// src/commands/quality-issue/artifact-schemas.ts
|
|
12233
|
+
import { z as z14 } from "zod";
|
|
12234
|
+
var schemaVersion = z14.literal("v1");
|
|
12235
|
+
var uuid = z14.uuid();
|
|
12236
|
+
var commonShape = {
|
|
12237
|
+
schemaVersion,
|
|
12238
|
+
runId: z14.string().trim().min(1),
|
|
12239
|
+
organizationId: uuid
|
|
12240
|
+
};
|
|
12241
|
+
var qualityIssueScopeSchema = z14.object({
|
|
12242
|
+
...commonShape,
|
|
12243
|
+
request: z14.string().trim().min(1),
|
|
12244
|
+
environment: z14.string().trim().min(1),
|
|
12245
|
+
profile: z14.string().trim().min(1),
|
|
12246
|
+
mode: z14.enum(["dry_run", "apply"]),
|
|
12247
|
+
discovery: z14.object({}).catchall(z14.unknown())
|
|
12248
|
+
}).strict();
|
|
12249
|
+
var qualityIssueInventoryRowSchema = z14.object({
|
|
12250
|
+
...commonShape,
|
|
12251
|
+
issueId: uuid,
|
|
12252
|
+
exerciseId: uuid,
|
|
12253
|
+
exerciseUpdatedAt: z14.string().trim().min(1),
|
|
12254
|
+
severity: z14.enum(["warning", "error", "critical"]),
|
|
12255
|
+
subject: z14.enum(["math", "physics", "chemistry"]),
|
|
12256
|
+
evidence: z14.object({}).catchall(z14.unknown())
|
|
12257
|
+
}).strict();
|
|
12258
|
+
var qualityIssueDiscoveryRowSchema = z14.object({
|
|
12259
|
+
...commonShape,
|
|
12260
|
+
tool: z14.string().trim().min(1),
|
|
12261
|
+
evidence: z14.object({}).catchall(z14.unknown())
|
|
12262
|
+
}).strict();
|
|
12263
|
+
var qualityIssueCandidateExerciseRowSchema = z14.object({
|
|
12264
|
+
...commonShape,
|
|
12265
|
+
exerciseId: uuid,
|
|
12266
|
+
evidence: z14.object({}).catchall(z14.unknown())
|
|
12267
|
+
}).strict();
|
|
12268
|
+
var patchSchema = z14.object({
|
|
12269
|
+
path: z14.string().trim().min(1),
|
|
12270
|
+
before: z14.unknown(),
|
|
12271
|
+
after: z14.unknown()
|
|
12272
|
+
}).strict().refine((patch) => Object.hasOwn(patch, "before") && Object.hasOwn(patch, "after"), {
|
|
12273
|
+
message: "Patch before and after values are required; use null for an intentionally empty value."
|
|
12274
|
+
});
|
|
12275
|
+
var classificationBaseShape = {
|
|
12276
|
+
...commonShape,
|
|
12277
|
+
issueId: uuid,
|
|
12278
|
+
reasonCode: z14.string().trim().min(1),
|
|
12279
|
+
rationale: z14.string().trim().min(1)
|
|
12280
|
+
};
|
|
12281
|
+
var qualityIssueClassificationRowSchema = z14.discriminatedUnion("disposition", [
|
|
12282
|
+
z14.object({
|
|
12283
|
+
...classificationBaseShape,
|
|
12284
|
+
disposition: z14.literal("proposed_fix"),
|
|
12285
|
+
patches: z14.array(patchSchema).min(1)
|
|
12286
|
+
}).strict(),
|
|
12287
|
+
z14.object({ ...classificationBaseShape, disposition: z14.literal("proposed_dismissal") }).strict(),
|
|
12288
|
+
z14.object({ ...classificationBaseShape, disposition: z14.literal("needs_human") }).strict(),
|
|
12289
|
+
z14.object({ ...classificationBaseShape, disposition: z14.literal("stale") }).strict(),
|
|
12290
|
+
z14.object({ ...classificationBaseShape, disposition: z14.literal("failed") }).strict(),
|
|
12291
|
+
z14.object({ ...classificationBaseShape, disposition: z14.literal("skipped") }).strict()
|
|
12292
|
+
]);
|
|
12293
|
+
var actionIdentityShape = {
|
|
12294
|
+
actionId: z14.string().trim().min(1),
|
|
12295
|
+
exerciseId: uuid
|
|
12296
|
+
};
|
|
12297
|
+
var updateActionSchema = z14.object({
|
|
12298
|
+
...actionIdentityShape,
|
|
12299
|
+
type: z14.literal("update_exercise"),
|
|
12300
|
+
expectedExerciseUpdatedAt: z14.string().trim().min(1),
|
|
12301
|
+
issueIds: z14.array(uuid).min(1),
|
|
12302
|
+
patches: z14.array(patchSchema).min(1)
|
|
12303
|
+
}).strict();
|
|
12304
|
+
var resolveActionSchema = z14.object({
|
|
12305
|
+
...actionIdentityShape,
|
|
12306
|
+
type: z14.literal("resolve_quality_issue"),
|
|
12307
|
+
issueId: uuid,
|
|
12308
|
+
expectedExerciseUpdatedAtFromActionId: z14.string().trim().min(1)
|
|
12309
|
+
}).strict();
|
|
12310
|
+
var dismissActionSchema = z14.object({
|
|
12311
|
+
...actionIdentityShape,
|
|
12312
|
+
type: z14.literal("dismiss_quality_issue"),
|
|
12313
|
+
issueId: uuid,
|
|
12314
|
+
expectedExerciseUpdatedAt: z14.string().trim().min(1)
|
|
12315
|
+
}).strict();
|
|
12316
|
+
var qualityIssueActionPlanSchema = z14.object({
|
|
12317
|
+
...commonShape,
|
|
12318
|
+
inventory: z14.array(
|
|
12319
|
+
z14.object({
|
|
12320
|
+
issueId: uuid,
|
|
12321
|
+
exerciseId: uuid,
|
|
12322
|
+
exerciseUpdatedAt: z14.string().trim().min(1)
|
|
12323
|
+
}).strict()
|
|
12324
|
+
),
|
|
12325
|
+
actions: z14.array(z14.discriminatedUnion("type", [updateActionSchema, resolveActionSchema, dismissActionSchema])),
|
|
12326
|
+
exclusions: z14.array(
|
|
12327
|
+
z14.object({
|
|
12328
|
+
issueId: uuid,
|
|
12329
|
+
disposition: z14.enum(["needs_human", "stale", "failed", "skipped"]),
|
|
12330
|
+
reasonCode: z14.string().trim().min(1)
|
|
12331
|
+
}).strict()
|
|
12332
|
+
)
|
|
12333
|
+
}).strict();
|
|
12334
|
+
var resultBaseShape = {
|
|
12335
|
+
...commonShape,
|
|
12336
|
+
actionId: z14.string().trim().min(1)
|
|
12337
|
+
};
|
|
12338
|
+
var terminalErrorSchema = z14.object({
|
|
12339
|
+
code: z14.string().trim().min(1),
|
|
12340
|
+
message: z14.string().trim().min(1),
|
|
12341
|
+
recovery: z14.string().trim().min(1)
|
|
12342
|
+
}).strict();
|
|
12343
|
+
var terminalResultSchema = (outcome) => z14.object({
|
|
12344
|
+
...resultBaseShape,
|
|
12345
|
+
outcome: z14.literal(outcome),
|
|
12346
|
+
error: terminalErrorSchema
|
|
12347
|
+
}).strict();
|
|
12348
|
+
var qualityIssueApplicationResultRowSchema = z14.discriminatedUnion("outcome", [
|
|
12349
|
+
z14.object({
|
|
12350
|
+
...resultBaseShape,
|
|
12351
|
+
outcome: z14.literal("success"),
|
|
12352
|
+
receipt: agentWriteReceiptSchema,
|
|
12353
|
+
resultingExerciseUpdatedAt: z14.iso.datetime({ offset: true }).optional()
|
|
12354
|
+
}).strict(),
|
|
12355
|
+
terminalResultSchema("failed"),
|
|
12356
|
+
terminalResultSchema("stale"),
|
|
12357
|
+
terminalResultSchema("skipped")
|
|
12358
|
+
]);
|
|
12359
|
+
|
|
12360
|
+
// src/commands/quality-issue/artifact-integrity.ts
|
|
12361
|
+
var readJson = async (path, schema) => {
|
|
12362
|
+
try {
|
|
12363
|
+
return schema.parse(JSON.parse(await readFile5(path, "utf8")));
|
|
12364
|
+
} catch (error) {
|
|
12365
|
+
throw new CliCommandError(`Artifact validation failed for ${path}: ${String(error)}`, 2, true, {
|
|
12366
|
+
code: "artifact_validation_failed",
|
|
12367
|
+
retryable: false
|
|
12368
|
+
});
|
|
12369
|
+
}
|
|
12370
|
+
};
|
|
12371
|
+
var readJsonLines = async (path, schema, optional = false) => {
|
|
12372
|
+
let contents;
|
|
12373
|
+
try {
|
|
12374
|
+
contents = await readFile5(path, "utf8");
|
|
12375
|
+
} catch (error) {
|
|
12376
|
+
if (optional && typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
12377
|
+
return [];
|
|
12378
|
+
}
|
|
12379
|
+
throw new CliCommandError(`Artifact validation failed for ${path}: ${String(error)}`, 2, true, {
|
|
12380
|
+
code: "artifact_validation_failed",
|
|
12381
|
+
retryable: false
|
|
12382
|
+
});
|
|
12383
|
+
}
|
|
12384
|
+
return contents.split(/\r?\n/u).filter((line) => line.trim().length > 0).map((line, index) => {
|
|
12385
|
+
try {
|
|
12386
|
+
return schema.parse(JSON.parse(line));
|
|
12387
|
+
} catch (error) {
|
|
12388
|
+
throw new CliCommandError(`Artifact validation failed for ${path}:${index + 1}: ${String(error)}`, 2, true, {
|
|
12389
|
+
code: "artifact_validation_failed",
|
|
12390
|
+
retryable: false
|
|
12391
|
+
});
|
|
12392
|
+
}
|
|
12393
|
+
});
|
|
12394
|
+
};
|
|
12395
|
+
var assertUnique = (values, label) => {
|
|
12396
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12397
|
+
for (const value of values) {
|
|
12398
|
+
if (seen.has(value)) {
|
|
12399
|
+
throw new CliCommandError(`Artifact validation failed: duplicate ${label} ${value}`, 2, true, {
|
|
12400
|
+
code: "artifact_validation_failed",
|
|
12401
|
+
retryable: false
|
|
12402
|
+
});
|
|
12403
|
+
}
|
|
12404
|
+
seen.add(value);
|
|
12405
|
+
}
|
|
12406
|
+
};
|
|
12407
|
+
var canonicalize = (value) => {
|
|
12408
|
+
if (Array.isArray(value)) {
|
|
12409
|
+
return value.map(canonicalize);
|
|
12410
|
+
}
|
|
12411
|
+
if (value !== null && typeof value === "object") {
|
|
12412
|
+
return Object.fromEntries(
|
|
12413
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nestedValue]) => [key, canonicalize(nestedValue)])
|
|
12414
|
+
);
|
|
12415
|
+
}
|
|
12416
|
+
return value;
|
|
12417
|
+
};
|
|
12418
|
+
var canonicalJson = (value) => JSON.stringify(canonicalize(value));
|
|
12419
|
+
var assertPartitionIdentity = (scope, rows) => {
|
|
12420
|
+
if (rows.some((row) => row.runId !== scope.runId || row.organizationId !== scope.organizationId)) {
|
|
12421
|
+
throw new CliCommandError("Artifact validation failed: partition identity mismatch", 2, true, {
|
|
12422
|
+
code: "artifact_validation_failed",
|
|
12423
|
+
retryable: false
|
|
12424
|
+
});
|
|
12425
|
+
}
|
|
12426
|
+
};
|
|
12427
|
+
var validateQualityIssueRun = async (runDirectory) => {
|
|
12428
|
+
const scope = await readJson(join2(runDirectory, "scope.json"), qualityIssueScopeSchema);
|
|
12429
|
+
const discovery = await readJsonLines(join2(runDirectory, "discovery.jsonl"), qualityIssueDiscoveryRowSchema);
|
|
12430
|
+
const candidates = await readJsonLines(
|
|
12431
|
+
join2(runDirectory, "candidate-exercises.jsonl"),
|
|
12432
|
+
qualityIssueCandidateExerciseRowSchema
|
|
12433
|
+
);
|
|
12434
|
+
const inventory = await readJsonLines(join2(runDirectory, "inventory.jsonl"), qualityIssueInventoryRowSchema);
|
|
12435
|
+
const classifications = await readJsonLines(
|
|
12436
|
+
join2(runDirectory, "classifications.jsonl"),
|
|
12437
|
+
qualityIssueClassificationRowSchema
|
|
12438
|
+
);
|
|
12439
|
+
const actionPlan = await readJson(join2(runDirectory, "action-plan.json"), qualityIssueActionPlanSchema);
|
|
12440
|
+
const results = await readJsonLines(
|
|
12441
|
+
join2(runDirectory, "application-results.jsonl"),
|
|
12442
|
+
qualityIssueApplicationResultRowSchema,
|
|
12443
|
+
true
|
|
12444
|
+
);
|
|
12445
|
+
assertPartitionIdentity(scope, [
|
|
12446
|
+
...discovery,
|
|
12447
|
+
...candidates,
|
|
12448
|
+
...inventory,
|
|
12449
|
+
...classifications,
|
|
12450
|
+
actionPlan,
|
|
12451
|
+
...results
|
|
12452
|
+
]);
|
|
12453
|
+
assertUnique(
|
|
12454
|
+
candidates.map((row) => row.exerciseId),
|
|
12455
|
+
"candidate exerciseId"
|
|
12456
|
+
);
|
|
12457
|
+
assertUnique(
|
|
12458
|
+
inventory.map((row) => row.issueId),
|
|
12459
|
+
"inventory issueId"
|
|
12460
|
+
);
|
|
12461
|
+
assertUnique(
|
|
12462
|
+
classifications.map((row) => row.issueId),
|
|
12463
|
+
"classification issueId"
|
|
12464
|
+
);
|
|
12465
|
+
assertUnique(
|
|
12466
|
+
actionPlan.actions.map((action) => action.actionId),
|
|
12467
|
+
"actionId"
|
|
12468
|
+
);
|
|
12469
|
+
const inventoryIds = new Set(inventory.map((row) => row.issueId));
|
|
12470
|
+
const classificationIds = new Set(classifications.map((row) => row.issueId));
|
|
12471
|
+
if (inventoryIds.size !== classificationIds.size || [...inventoryIds].some((issueId) => !classificationIds.has(issueId))) {
|
|
12472
|
+
throw new CliCommandError(
|
|
12473
|
+
"Artifact validation failed: every inventory issue needs exactly one classification",
|
|
12474
|
+
2,
|
|
12475
|
+
true,
|
|
12476
|
+
{
|
|
12477
|
+
code: "artifact_validation_failed",
|
|
12478
|
+
retryable: false
|
|
12479
|
+
}
|
|
12480
|
+
);
|
|
12481
|
+
}
|
|
12482
|
+
const planInventoryIds = new Set(actionPlan.inventory.map((row) => row.issueId));
|
|
12483
|
+
assertUnique(
|
|
12484
|
+
actionPlan.inventory.map((row) => row.issueId),
|
|
12485
|
+
"action-plan inventory issueId"
|
|
12486
|
+
);
|
|
12487
|
+
if (inventoryIds.size !== planInventoryIds.size || [...inventoryIds].some((issueId) => !planInventoryIds.has(issueId))) {
|
|
12488
|
+
throw new CliCommandError(
|
|
12489
|
+
"Artifact validation failed: action-plan inventory does not match inventory.jsonl",
|
|
12490
|
+
2,
|
|
12491
|
+
true,
|
|
12492
|
+
{
|
|
12493
|
+
code: "artifact_validation_failed",
|
|
12494
|
+
retryable: false
|
|
12495
|
+
}
|
|
12496
|
+
);
|
|
12497
|
+
}
|
|
12498
|
+
const candidateExerciseIds = new Set(candidates.map((row) => row.exerciseId));
|
|
12499
|
+
if (inventory.some((row) => !candidateExerciseIds.has(row.exerciseId))) {
|
|
12500
|
+
throw new CliCommandError("Artifact validation failed: inventory exercise is missing from candidates", 2, true, {
|
|
12501
|
+
code: "artifact_validation_failed",
|
|
12502
|
+
retryable: false
|
|
12503
|
+
});
|
|
12504
|
+
}
|
|
12505
|
+
const inventoryByIssueId = new Map(inventory.map((row) => [row.issueId, row]));
|
|
12506
|
+
const classificationByIssueId = new Map(classifications.map((row) => [row.issueId, row]));
|
|
12507
|
+
const patchKeys = actionPlan.actions.flatMap(
|
|
12508
|
+
(action) => action.type === "update_exercise" ? action.patches.map((patch) => `${action.exerciseId}\0${patch.path}`) : []
|
|
12509
|
+
);
|
|
12510
|
+
assertUnique(patchKeys, "exercise patch path");
|
|
12511
|
+
assertUnique(
|
|
12512
|
+
classifications.flatMap((classification) => {
|
|
12513
|
+
if (classification.disposition !== "proposed_fix") return [];
|
|
12514
|
+
const inventoryRow = inventoryByIssueId.get(classification.issueId);
|
|
12515
|
+
return classification.patches.map((patch) => `${inventoryRow?.exerciseId ?? ""}\0${patch.path}`);
|
|
12516
|
+
}),
|
|
12517
|
+
"classified exercise patch path"
|
|
12518
|
+
);
|
|
12519
|
+
for (const plannedIssue of actionPlan.inventory) {
|
|
12520
|
+
const source = inventoryByIssueId.get(plannedIssue.issueId);
|
|
12521
|
+
if (!source || source.exerciseId !== plannedIssue.exerciseId || source.exerciseUpdatedAt !== plannedIssue.exerciseUpdatedAt) {
|
|
12522
|
+
throw new CliCommandError("Artifact validation failed: action-plan evidence differs from inventory", 2, true, {
|
|
12523
|
+
code: "artifact_validation_failed",
|
|
12524
|
+
retryable: false
|
|
12525
|
+
});
|
|
12526
|
+
}
|
|
12527
|
+
}
|
|
12528
|
+
const updateActionsByExerciseId = /* @__PURE__ */ new Map();
|
|
12529
|
+
const updateActionsByIssueId = /* @__PURE__ */ new Map();
|
|
12530
|
+
const resolveActionsByIssueId = /* @__PURE__ */ new Map();
|
|
12531
|
+
const dismissActionsByIssueId = /* @__PURE__ */ new Map();
|
|
12532
|
+
const actionIndexById = new Map(actionPlan.actions.map((action, index) => [action.actionId, index]));
|
|
12533
|
+
for (const action of actionPlan.actions) {
|
|
12534
|
+
if (action.type === "update_exercise") {
|
|
12535
|
+
assertUnique(action.issueIds, `update action ${action.actionId} issueId`);
|
|
12536
|
+
if (updateActionsByExerciseId.has(action.exerciseId)) {
|
|
12537
|
+
throw new CliCommandError(
|
|
12538
|
+
`Artifact validation failed: exercise ${action.exerciseId} has more than one update action`,
|
|
12539
|
+
2,
|
|
12540
|
+
true,
|
|
12541
|
+
{ code: "artifact_validation_failed", retryable: false }
|
|
12542
|
+
);
|
|
12543
|
+
}
|
|
12544
|
+
updateActionsByExerciseId.set(action.exerciseId, action);
|
|
12545
|
+
const expectedPatches = action.issueIds.flatMap((actionIssueId) => {
|
|
12546
|
+
const source2 = inventoryByIssueId.get(actionIssueId);
|
|
12547
|
+
const classification = classificationByIssueId.get(actionIssueId);
|
|
12548
|
+
if (!source2 || source2.exerciseId !== action.exerciseId || source2.exerciseUpdatedAt !== action.expectedExerciseUpdatedAt || classification?.disposition !== "proposed_fix") {
|
|
12549
|
+
throw new CliCommandError(
|
|
12550
|
+
"Artifact validation failed: update action evidence differs from inventory",
|
|
12551
|
+
2,
|
|
12552
|
+
true,
|
|
12553
|
+
{
|
|
12554
|
+
code: "artifact_validation_failed",
|
|
12555
|
+
retryable: false
|
|
12556
|
+
}
|
|
12557
|
+
);
|
|
12558
|
+
}
|
|
12559
|
+
updateActionsByIssueId.set(actionIssueId, [...updateActionsByIssueId.get(actionIssueId) ?? [], action]);
|
|
12560
|
+
return classification.patches;
|
|
12561
|
+
});
|
|
12562
|
+
const sortPatches = (patches) => [...patches].sort((left, right) => left.path.localeCompare(right.path));
|
|
12563
|
+
if (canonicalJson(sortPatches(action.patches)) !== canonicalJson(sortPatches(expectedPatches))) {
|
|
12564
|
+
throw new CliCommandError(
|
|
12565
|
+
`Artifact validation failed: update action patches do not match classifications for ${action.exerciseId}`,
|
|
12566
|
+
2,
|
|
12567
|
+
true,
|
|
12568
|
+
{ code: "artifact_validation_failed", retryable: false }
|
|
12569
|
+
);
|
|
12570
|
+
}
|
|
12571
|
+
continue;
|
|
12572
|
+
}
|
|
12573
|
+
const source = inventoryByIssueId.get(action.issueId);
|
|
12574
|
+
if (!source || source.exerciseId !== action.exerciseId) {
|
|
12575
|
+
throw new CliCommandError("Artifact validation failed: triage action evidence differs from inventory", 2, true, {
|
|
12576
|
+
code: "artifact_validation_failed",
|
|
12577
|
+
retryable: false
|
|
12578
|
+
});
|
|
12579
|
+
}
|
|
12580
|
+
if (action.type === "resolve_quality_issue") {
|
|
12581
|
+
resolveActionsByIssueId.set(action.issueId, [...resolveActionsByIssueId.get(action.issueId) ?? [], action]);
|
|
12582
|
+
} else {
|
|
12583
|
+
if (source.exerciseUpdatedAt !== action.expectedExerciseUpdatedAt) {
|
|
12584
|
+
throw new CliCommandError("Artifact validation failed: dismissal evidence differs from inventory", 2, true, {
|
|
12585
|
+
code: "artifact_validation_failed",
|
|
12586
|
+
retryable: false
|
|
12587
|
+
});
|
|
12588
|
+
}
|
|
12589
|
+
dismissActionsByIssueId.set(action.issueId, [...dismissActionsByIssueId.get(action.issueId) ?? [], action]);
|
|
12590
|
+
}
|
|
12591
|
+
}
|
|
12592
|
+
const exclusionsByIssueId = new Map(actionPlan.exclusions.map((exclusion) => [exclusion.issueId, exclusion]));
|
|
12593
|
+
assertUnique(
|
|
12594
|
+
actionPlan.exclusions.map((exclusion) => exclusion.issueId),
|
|
12595
|
+
"exclusion issueId"
|
|
12596
|
+
);
|
|
12597
|
+
for (const classification of classifications) {
|
|
12598
|
+
const source = inventoryByIssueId.get(classification.issueId);
|
|
12599
|
+
const updates = updateActionsByIssueId.get(classification.issueId) ?? [];
|
|
12600
|
+
const resolves = resolveActionsByIssueId.get(classification.issueId) ?? [];
|
|
12601
|
+
const dismissals = dismissActionsByIssueId.get(classification.issueId) ?? [];
|
|
12602
|
+
const exclusion = exclusionsByIssueId.get(classification.issueId);
|
|
12603
|
+
const exerciseUpdate = source ? updateActionsByExerciseId.get(source.exerciseId) : void 0;
|
|
12604
|
+
if (classification.disposition === "proposed_dismissal" && dismissals.length === 1 && exerciseUpdate && (actionIndexById.get(dismissals[0].actionId) ?? Number.POSITIVE_INFINITY) > (actionIndexById.get(exerciseUpdate.actionId) ?? Number.NEGATIVE_INFINITY)) {
|
|
12605
|
+
throw new CliCommandError(
|
|
12606
|
+
`Artifact validation failed: dismissal ${dismissals[0].actionId} must precede update ${exerciseUpdate.actionId}`,
|
|
12607
|
+
2,
|
|
12608
|
+
true,
|
|
12609
|
+
{ code: "artifact_validation_failed", retryable: false }
|
|
12610
|
+
);
|
|
12611
|
+
}
|
|
12612
|
+
const valid = classification.disposition === "proposed_fix" && !exclusion && updates.length === 1 && resolves.length === 1 && dismissals.length === 0 && resolves[0]?.exerciseId === source?.exerciseId && resolves[0]?.expectedExerciseUpdatedAtFromActionId === updates[0]?.actionId && (actionIndexById.get(updates[0]?.actionId ?? "") ?? Number.POSITIVE_INFINITY) < (actionIndexById.get(resolves[0]?.actionId ?? "") ?? Number.NEGATIVE_INFINITY) || classification.disposition === "proposed_dismissal" && !exclusion && updates.length === 0 && resolves.length === 0 && dismissals.length === 1 || ["needs_human", "stale", "failed", "skipped"].includes(classification.disposition) && updates.length === 0 && resolves.length === 0 && dismissals.length === 0 && exclusion?.disposition === classification.disposition && exclusion.reasonCode === classification.reasonCode;
|
|
12613
|
+
if (!valid) {
|
|
12614
|
+
throw new CliCommandError(
|
|
12615
|
+
`Artifact validation failed: action plan does not exactly implement ${classification.disposition} for ${classification.issueId}`,
|
|
12616
|
+
2,
|
|
12617
|
+
true,
|
|
12618
|
+
{ code: "artifact_validation_failed", retryable: false }
|
|
12619
|
+
);
|
|
12620
|
+
}
|
|
12621
|
+
}
|
|
12622
|
+
if ([...exclusionsByIssueId].some(([excludedIssueId]) => !classificationByIssueId.has(excludedIssueId))) {
|
|
12623
|
+
throw new CliCommandError("Artifact validation failed: action plan contains an unexpected exclusion", 2, true, {
|
|
12624
|
+
code: "artifact_validation_failed",
|
|
12625
|
+
retryable: false
|
|
12626
|
+
});
|
|
12627
|
+
}
|
|
12628
|
+
return { scope, discovery, candidates, inventory, classifications, actionPlan, results };
|
|
12629
|
+
};
|
|
12630
|
+
var digestQualityIssueRun = (run) => createHash("sha256").update(
|
|
12631
|
+
JSON.stringify(
|
|
12632
|
+
canonicalize({
|
|
12633
|
+
scope: run.scope,
|
|
12634
|
+
discovery: run.discovery,
|
|
12635
|
+
candidates: run.candidates,
|
|
12636
|
+
inventory: run.inventory,
|
|
12637
|
+
classifications: run.classifications,
|
|
12638
|
+
actionPlan: run.actionPlan
|
|
12639
|
+
})
|
|
12640
|
+
)
|
|
12641
|
+
).digest("hex");
|
|
12642
|
+
var reconcileQualityIssueRun = ({
|
|
12643
|
+
actionPlan,
|
|
12644
|
+
results
|
|
12645
|
+
}) => {
|
|
12646
|
+
const planned = new Set(actionPlan.actions.map((action) => action.actionId));
|
|
12647
|
+
const counts = /* @__PURE__ */ new Map();
|
|
12648
|
+
for (const result of results) counts.set(result.actionId, (counts.get(result.actionId) ?? 0) + 1);
|
|
12649
|
+
const missingActionIds = [...planned].filter((actionId) => !counts.has(actionId));
|
|
12650
|
+
const duplicateActionIds = [...counts].filter(([, count]) => count > 1).map(([actionId]) => actionId);
|
|
12651
|
+
const unexpectedActionIds = [...counts].filter(([actionId]) => !planned.has(actionId)).map(([actionId]) => actionId);
|
|
12652
|
+
const resultByActionId = new Map(
|
|
12653
|
+
results.filter((result) => counts.get(result.actionId) === 1).map((result) => [result.actionId, result])
|
|
12654
|
+
);
|
|
12655
|
+
const resultIndexByActionId = new Map(results.map((result, index) => [result.actionId, index]));
|
|
12656
|
+
const sameStrings = (left, right) => canonicalJson([...left].sort()) === canonicalJson([...right].sort());
|
|
12657
|
+
const semanticInvalidActionIds = actionPlan.actions.flatMap((action) => {
|
|
12658
|
+
const result = resultByActionId.get(action.actionId);
|
|
12659
|
+
if (!result || result.outcome !== "success") return [];
|
|
12660
|
+
const receipt = result.receipt;
|
|
12661
|
+
if (receipt.resource.type !== "exercise" || receipt.resource.id !== action.exerciseId || receipt.operation !== action.type || receipt.precondition.status !== "matched") {
|
|
12662
|
+
return [action.actionId];
|
|
12663
|
+
}
|
|
12664
|
+
if (action.type === "update_exercise") {
|
|
12665
|
+
const valid2 = result.resultingExerciseUpdatedAt !== void 0 && receipt.precondition.expectedUpdatedAt === action.expectedExerciseUpdatedAt && receipt.precondition.actualUpdatedAt === action.expectedExerciseUpdatedAt && sameStrings(
|
|
12666
|
+
receipt.changedPaths,
|
|
12667
|
+
action.patches.map((patch) => patch.path)
|
|
12668
|
+
);
|
|
12669
|
+
return valid2 ? [] : [action.actionId];
|
|
12670
|
+
}
|
|
12671
|
+
const expectedChangedPaths = [`qualityIssues.${action.issueId}.status`];
|
|
12672
|
+
const details = receipt.details;
|
|
12673
|
+
const expectedStatus = action.type === "resolve_quality_issue" ? "resolved" : "dismissed";
|
|
12674
|
+
if (!sameStrings(receipt.changedPaths, expectedChangedPaths) || typeof details !== "object" || details === null || !("issueId" in details) || details.issueId !== action.issueId || !("status" in details) || details.status !== expectedStatus) {
|
|
12675
|
+
return [action.actionId];
|
|
12676
|
+
}
|
|
12677
|
+
if (action.type === "dismiss_quality_issue") {
|
|
12678
|
+
const valid2 = receipt.precondition.expectedUpdatedAt === action.expectedExerciseUpdatedAt && receipt.precondition.actualUpdatedAt === action.expectedExerciseUpdatedAt;
|
|
12679
|
+
return valid2 ? [] : [action.actionId];
|
|
12680
|
+
}
|
|
12681
|
+
const dependencyResult = resultByActionId.get(action.expectedExerciseUpdatedAtFromActionId);
|
|
12682
|
+
const dependencyIndex = resultIndexByActionId.get(action.expectedExerciseUpdatedAtFromActionId);
|
|
12683
|
+
const resultIndex = resultIndexByActionId.get(action.actionId);
|
|
12684
|
+
const expectedUpdatedAt = dependencyResult?.outcome === "success" ? dependencyResult.resultingExerciseUpdatedAt : void 0;
|
|
12685
|
+
const valid = dependencyResult?.outcome === "success" && expectedUpdatedAt !== void 0 && dependencyIndex !== void 0 && resultIndex !== void 0 && dependencyIndex < resultIndex && receipt.precondition.expectedUpdatedAt === expectedUpdatedAt && receipt.precondition.actualUpdatedAt === expectedUpdatedAt;
|
|
12686
|
+
return valid ? [] : [action.actionId];
|
|
12687
|
+
});
|
|
12688
|
+
const orderInvalidActionIds = missingActionIds.length === 0 && duplicateActionIds.length === 0 && unexpectedActionIds.length === 0 ? actionPlan.actions.flatMap(
|
|
12689
|
+
(action, index) => results[index]?.actionId === action.actionId ? [] : [action.actionId]
|
|
12690
|
+
) : [];
|
|
12691
|
+
const invalidActionIds = [.../* @__PURE__ */ new Set([...semanticInvalidActionIds, ...orderInvalidActionIds])];
|
|
12692
|
+
return {
|
|
12693
|
+
complete: missingActionIds.length === 0 && duplicateActionIds.length === 0 && unexpectedActionIds.length === 0 && invalidActionIds.length === 0,
|
|
12694
|
+
missingActionIds,
|
|
12695
|
+
duplicateActionIds,
|
|
12696
|
+
unexpectedActionIds,
|
|
12697
|
+
invalidActionIds
|
|
12698
|
+
};
|
|
12699
|
+
};
|
|
12700
|
+
var verifyQualityIssueRun = (run, approvedDigest) => {
|
|
12701
|
+
const actualDigest = digestQualityIssueRun(run);
|
|
12702
|
+
if (actualDigest !== approvedDigest) {
|
|
12703
|
+
throw new CliCommandError("Approved digest does not match the current action plan", 2, true, {
|
|
12704
|
+
code: "approval_digest_mismatch",
|
|
12705
|
+
retryable: false
|
|
12706
|
+
});
|
|
12707
|
+
}
|
|
12708
|
+
return actualDigest;
|
|
12709
|
+
};
|
|
12710
|
+
|
|
12711
|
+
// src/commands/quality-issue/index.ts
|
|
12712
|
+
var parseUuid = (value, label) => {
|
|
12713
|
+
const result = z15.uuid().safeParse(value);
|
|
12714
|
+
if (!result.success) throw new CliCommandError(`${label} must be a UUID.`, 2);
|
|
12715
|
+
return result.data;
|
|
12716
|
+
};
|
|
12717
|
+
var parseUuidArray = (values, label) => {
|
|
12718
|
+
if (values === void 0) return void 0;
|
|
12719
|
+
const result = z15.array(z15.uuid()).min(1).max(100).safeParse(values);
|
|
12720
|
+
if (!result.success) throw new CliCommandError(`${label} must contain 1 to 100 UUIDs.`, 2);
|
|
12721
|
+
return result.data;
|
|
12722
|
+
};
|
|
12723
|
+
var registerQualityIssueCommands = (yargs2, context) => yargs2.command(
|
|
12724
|
+
"list",
|
|
12725
|
+
"List active quality issues for organization-owned exercises",
|
|
12726
|
+
(listYargs) => listYargs.option("exercise-id", { type: "string", array: true, describe: "Restrict to exercise IDs" }).option("issue-id", { type: "string", array: true, describe: "Restrict to quality issue IDs" }).option("severity", {
|
|
12727
|
+
type: "string",
|
|
12728
|
+
array: true,
|
|
12729
|
+
choices: ["warning", "error", "critical"]
|
|
12730
|
+
}).option("limit", { type: "number", default: 50 }).option("cursor", { type: "string" }),
|
|
12731
|
+
async (argv) => {
|
|
12732
|
+
const common = argv;
|
|
12733
|
+
if (argv.exerciseId && argv.issueId) {
|
|
12734
|
+
throw new CliCommandError("--exercise-id and --issue-id are mutually exclusive.", 2);
|
|
12735
|
+
}
|
|
12736
|
+
const input = {
|
|
12737
|
+
exerciseIds: parseUuidArray(argv.exerciseId, "exercise-id"),
|
|
12738
|
+
issueIds: parseUuidArray(argv.issueId, "issue-id"),
|
|
12739
|
+
severities: argv.severity,
|
|
12740
|
+
limit: normalizeLimit(argv.limit),
|
|
12741
|
+
cursor: argv.cursor
|
|
12742
|
+
};
|
|
12743
|
+
const { apiClient, organizationId } = await createResolvedApiClient({
|
|
12744
|
+
context,
|
|
12745
|
+
baseUrlFlagValue: common.baseUrl,
|
|
12746
|
+
organizationFlagValue: common.organization,
|
|
12747
|
+
profileName: common.profile
|
|
12748
|
+
});
|
|
12749
|
+
try {
|
|
12750
|
+
const result = await apiClient.agentListQualityIssues(input);
|
|
12751
|
+
context.output.print(
|
|
12752
|
+
{ request: { organizationId, ...input }, ...result },
|
|
12753
|
+
`${result.issues.length} active quality issue(s)${result.nextCursor ? "; more available" : ""}`,
|
|
12754
|
+
{ command: "quality-issue list" }
|
|
12755
|
+
);
|
|
12756
|
+
} catch (error) {
|
|
12757
|
+
throw mapApiErrorToCliError(error);
|
|
12758
|
+
}
|
|
12759
|
+
}
|
|
12760
|
+
).command(
|
|
12761
|
+
"resolve <issueId>",
|
|
12762
|
+
"Resolve one quality issue using the approved exercise timestamp",
|
|
12763
|
+
(commandYargs) => commandYargs.positional("issueId", { type: "string", demandOption: true }).option("expected-exercise-updated-at", { type: "string", demandOption: true }),
|
|
12764
|
+
async (argv) => {
|
|
12765
|
+
const common = argv;
|
|
12766
|
+
const input = {
|
|
12767
|
+
issueId: parseUuid(argv.issueId, "issueId"),
|
|
12768
|
+
expectedExerciseUpdatedAt: argv.expectedExerciseUpdatedAt
|
|
12769
|
+
};
|
|
12770
|
+
const { apiClient } = await createResolvedApiClient({
|
|
12771
|
+
context,
|
|
12772
|
+
baseUrlFlagValue: common.baseUrl,
|
|
12773
|
+
organizationFlagValue: common.organization,
|
|
12774
|
+
profileName: common.profile
|
|
12775
|
+
});
|
|
12776
|
+
try {
|
|
12777
|
+
const result = await apiClient.agentResolveQualityIssue(input);
|
|
12778
|
+
context.output.print(result, formatAgentWriteReceiptOutput, { command: "quality-issue resolve" });
|
|
12779
|
+
} catch (error) {
|
|
12780
|
+
throw mapApiErrorToCliError(error);
|
|
12781
|
+
}
|
|
12782
|
+
}
|
|
12783
|
+
).command(
|
|
12784
|
+
"dismiss <issueId>",
|
|
12785
|
+
"Dismiss one quality issue using the approved exercise timestamp",
|
|
12786
|
+
(commandYargs) => commandYargs.positional("issueId", { type: "string", demandOption: true }).option("expected-exercise-updated-at", { type: "string", demandOption: true }),
|
|
12787
|
+
async (argv) => {
|
|
12788
|
+
const common = argv;
|
|
12789
|
+
const input = {
|
|
12790
|
+
issueId: parseUuid(argv.issueId, "issueId"),
|
|
12791
|
+
expectedExerciseUpdatedAt: argv.expectedExerciseUpdatedAt
|
|
12792
|
+
};
|
|
12793
|
+
const { apiClient } = await createResolvedApiClient({
|
|
12794
|
+
context,
|
|
12795
|
+
baseUrlFlagValue: common.baseUrl,
|
|
12796
|
+
organizationFlagValue: common.organization,
|
|
12797
|
+
profileName: common.profile
|
|
12798
|
+
});
|
|
12799
|
+
try {
|
|
12800
|
+
const result = await apiClient.agentDismissQualityIssue(input);
|
|
12801
|
+
context.output.print(result, formatAgentWriteReceiptOutput, { command: "quality-issue dismiss" });
|
|
12802
|
+
} catch (error) {
|
|
12803
|
+
throw mapApiErrorToCliError(error);
|
|
12804
|
+
}
|
|
12805
|
+
}
|
|
12806
|
+
).command(
|
|
12807
|
+
"artifact <subcommand>",
|
|
12808
|
+
"Validate and reconcile local quality-issue run artifacts",
|
|
12809
|
+
(artifactYargs) => artifactYargs.command(
|
|
12810
|
+
"validate",
|
|
12811
|
+
"Validate a quality-issue run partition",
|
|
12812
|
+
(commandYargs) => commandYargs.option("run-dir", { type: "string", demandOption: true }),
|
|
12813
|
+
async (argv) => {
|
|
12814
|
+
const run = await validateQualityIssueRun(argv.runDir);
|
|
12815
|
+
context.output.print(
|
|
12816
|
+
{
|
|
12817
|
+
runId: run.scope.runId,
|
|
12818
|
+
organizationId: run.scope.organizationId,
|
|
12819
|
+
inventoryCount: run.inventory.length,
|
|
12820
|
+
classificationCount: run.classifications.length,
|
|
12821
|
+
actionCount: run.actionPlan.actions.length,
|
|
12822
|
+
resultCount: run.results.length
|
|
12823
|
+
},
|
|
12824
|
+
"Quality-issue run artifacts are valid.",
|
|
12825
|
+
{ command: "quality-issue artifact validate" }
|
|
12826
|
+
);
|
|
12827
|
+
}
|
|
12828
|
+
).command(
|
|
12829
|
+
"digest",
|
|
12830
|
+
"Compute the canonical action-plan digest",
|
|
12831
|
+
(commandYargs) => commandYargs.option("run-dir", { type: "string", demandOption: true }),
|
|
12832
|
+
async (argv) => {
|
|
12833
|
+
const run = await validateQualityIssueRun(argv.runDir);
|
|
12834
|
+
const digest = digestQualityIssueRun(run);
|
|
12835
|
+
context.output.print({ digest }, digest, { command: "quality-issue artifact digest" });
|
|
12836
|
+
}
|
|
12837
|
+
).command(
|
|
12838
|
+
"verify",
|
|
12839
|
+
"Verify an approved action-plan digest before writes",
|
|
12840
|
+
(commandYargs) => commandYargs.option("run-dir", { type: "string", demandOption: true }).option("approved-digest", { type: "string", demandOption: true }),
|
|
12841
|
+
async (argv) => {
|
|
12842
|
+
const run = await validateQualityIssueRun(argv.runDir);
|
|
12843
|
+
const digest = verifyQualityIssueRun(run, argv.approvedDigest);
|
|
12844
|
+
context.output.print({ digest, verified: true }, "Approved digest verified.", {
|
|
12845
|
+
command: "quality-issue artifact verify"
|
|
12846
|
+
});
|
|
12847
|
+
}
|
|
12848
|
+
).command(
|
|
12849
|
+
"reconcile",
|
|
12850
|
+
"Reconcile planned actions with terminal result rows",
|
|
12851
|
+
(commandYargs) => commandYargs.option("run-dir", { type: "string", demandOption: true }),
|
|
12852
|
+
async (argv) => {
|
|
12853
|
+
const run = await validateQualityIssueRun(argv.runDir);
|
|
12854
|
+
const reconciliation = reconcileQualityIssueRun(run);
|
|
12855
|
+
context.output.print(
|
|
12856
|
+
reconciliation,
|
|
12857
|
+
reconciliation.complete ? "Reconciliation complete." : "Reconciliation incomplete.",
|
|
12858
|
+
{
|
|
12859
|
+
command: "quality-issue artifact reconcile",
|
|
12860
|
+
ok: reconciliation.complete
|
|
12861
|
+
}
|
|
12862
|
+
);
|
|
12863
|
+
if (!reconciliation.complete) throwSilentExitCode(2);
|
|
12864
|
+
}
|
|
12865
|
+
).demandCommand(1),
|
|
12866
|
+
() => {
|
|
12867
|
+
}
|
|
12868
|
+
).demandCommand(1);
|
|
12869
|
+
|
|
12870
|
+
// src/commands/sheet.ts
|
|
12871
|
+
import { z as z16 } from "zod";
|
|
12872
|
+
|
|
12873
|
+
// src/lib/result-artifact.ts
|
|
12874
|
+
import { randomUUID } from "node:crypto";
|
|
12875
|
+
import { link, open as open2, unlink } from "node:fs/promises";
|
|
12876
|
+
import { basename as basename3, dirname as dirname2, join as join3, resolve as resolve7 } from "node:path";
|
|
12877
|
+
|
|
12878
|
+
// src/lib/output.ts
|
|
12879
|
+
var cliJsonSchemaVersion = "v1";
|
|
12880
|
+
var ensureTrailingNewline = (value) => {
|
|
12881
|
+
return value.endsWith("\n") ? value : `${value}
|
|
12882
|
+
`;
|
|
12883
|
+
};
|
|
12884
|
+
var writeLine = (writer, value) => {
|
|
12885
|
+
writer.write(ensureTrailingNewline(value));
|
|
12886
|
+
};
|
|
12887
|
+
var serializeCliOutputEnvelope = (envelope) => JSON.stringify(envelope, null, 2);
|
|
12888
|
+
var createCliOutputEnvelope = ({
|
|
12889
|
+
command,
|
|
12890
|
+
error,
|
|
12891
|
+
ok,
|
|
12892
|
+
result,
|
|
12893
|
+
warnings
|
|
12894
|
+
}) => ({
|
|
12895
|
+
schemaVersion: cliJsonSchemaVersion,
|
|
12896
|
+
command,
|
|
12897
|
+
ok,
|
|
12898
|
+
...result === void 0 ? {} : { result },
|
|
12899
|
+
...error ? { error } : {},
|
|
12900
|
+
warnings
|
|
12901
|
+
});
|
|
12902
|
+
var createOutput = ({
|
|
12903
|
+
json,
|
|
12904
|
+
stdout,
|
|
12905
|
+
stderr
|
|
12906
|
+
}) => {
|
|
12907
|
+
let warnings = [];
|
|
12908
|
+
const consumeWarnings = () => {
|
|
12909
|
+
const nextWarnings = warnings;
|
|
12910
|
+
warnings = [];
|
|
12911
|
+
return nextWarnings;
|
|
12912
|
+
};
|
|
12913
|
+
return {
|
|
12914
|
+
json,
|
|
12915
|
+
print: (value, humanFormatter, options) => {
|
|
12916
|
+
if (json) {
|
|
12917
|
+
writeLine(
|
|
12918
|
+
stdout,
|
|
12919
|
+
serializeCliOutputEnvelope(
|
|
12920
|
+
createCliOutputEnvelope({
|
|
12921
|
+
command: options.command,
|
|
12922
|
+
error: options.error,
|
|
12923
|
+
ok: options.ok ?? true,
|
|
12924
|
+
result: value,
|
|
12925
|
+
warnings: consumeWarnings()
|
|
12926
|
+
})
|
|
12927
|
+
)
|
|
12928
|
+
);
|
|
12929
|
+
return;
|
|
12930
|
+
}
|
|
12931
|
+
const message = typeof humanFormatter === "function" ? humanFormatter(value) : humanFormatter;
|
|
12932
|
+
writeLine(stdout, message);
|
|
12933
|
+
},
|
|
12934
|
+
printWithResultArtifact: async (value, compactValue, humanFormatter, options) => {
|
|
12935
|
+
const currentWarnings = [...warnings];
|
|
12936
|
+
await options.publishArtifact(
|
|
12937
|
+
createCliOutputEnvelope({
|
|
12938
|
+
command: options.command,
|
|
12939
|
+
error: options.error,
|
|
12940
|
+
ok: options.ok ?? true,
|
|
12941
|
+
result: value,
|
|
12942
|
+
warnings: currentWarnings
|
|
12943
|
+
})
|
|
12944
|
+
);
|
|
12945
|
+
warnings = warnings.slice(currentWarnings.length);
|
|
12946
|
+
if (json) {
|
|
12947
|
+
writeLine(
|
|
12948
|
+
stdout,
|
|
12949
|
+
serializeCliOutputEnvelope(
|
|
12950
|
+
createCliOutputEnvelope({
|
|
12951
|
+
command: options.command,
|
|
12952
|
+
error: options.error,
|
|
12953
|
+
ok: options.ok ?? true,
|
|
12954
|
+
result: compactValue,
|
|
12955
|
+
warnings: currentWarnings
|
|
12956
|
+
})
|
|
12957
|
+
)
|
|
12958
|
+
);
|
|
12959
|
+
return;
|
|
12960
|
+
}
|
|
12961
|
+
const message = typeof humanFormatter === "function" ? humanFormatter(compactValue) : humanFormatter;
|
|
12962
|
+
writeLine(stdout, message);
|
|
12963
|
+
},
|
|
12964
|
+
printError: ({ command, error, result }) => {
|
|
12965
|
+
if (json) {
|
|
12966
|
+
writeLine(
|
|
12967
|
+
stdout,
|
|
12968
|
+
serializeCliOutputEnvelope(
|
|
12969
|
+
createCliOutputEnvelope({
|
|
12970
|
+
command,
|
|
12971
|
+
ok: false,
|
|
12972
|
+
result,
|
|
12973
|
+
error,
|
|
12974
|
+
warnings: consumeWarnings()
|
|
12975
|
+
})
|
|
12976
|
+
)
|
|
12977
|
+
);
|
|
12978
|
+
return;
|
|
12979
|
+
}
|
|
12980
|
+
writeLine(stderr, formatSerializableCliErrorForHuman(error));
|
|
12981
|
+
},
|
|
12982
|
+
info: (message) => {
|
|
12983
|
+
if (json) {
|
|
12984
|
+
warnings.push(message);
|
|
12985
|
+
return;
|
|
12986
|
+
}
|
|
12987
|
+
writeLine(stderr, message);
|
|
12988
|
+
},
|
|
12989
|
+
error: (message) => {
|
|
12990
|
+
writeLine(stderr, message);
|
|
12991
|
+
}
|
|
12992
|
+
};
|
|
12993
|
+
};
|
|
12994
|
+
|
|
12995
|
+
// src/lib/result-artifact.ts
|
|
12996
|
+
var resolveCliResultFilePath = ({ cwd, inputPath }) => resolve7(cwd, inputPath);
|
|
12997
|
+
var getFilesystemErrorCode = (error) => error.code;
|
|
12998
|
+
var publishCliResultArtifact = async ({
|
|
12999
|
+
destinationPath,
|
|
13000
|
+
envelope
|
|
13001
|
+
}) => {
|
|
13002
|
+
const temporaryPath = join3(dirname2(destinationPath), `.${basename3(destinationPath)}.${randomUUID()}.tmp`);
|
|
13003
|
+
let temporaryFile;
|
|
13004
|
+
try {
|
|
13005
|
+
temporaryFile = await open2(temporaryPath, "wx", 384);
|
|
13006
|
+
await temporaryFile.writeFile(`${serializeCliOutputEnvelope(envelope)}
|
|
13007
|
+
`, "utf8");
|
|
13008
|
+
await temporaryFile.sync();
|
|
13009
|
+
await temporaryFile.close();
|
|
13010
|
+
temporaryFile = void 0;
|
|
13011
|
+
await link(temporaryPath, destinationPath);
|
|
13012
|
+
} catch (error) {
|
|
13013
|
+
if (getFilesystemErrorCode(error) === "EEXIST") {
|
|
13014
|
+
throw new CliCommandError(`Result file "${destinationPath}" already exists; choose another path.`, 2);
|
|
13015
|
+
}
|
|
13016
|
+
const message = error instanceof Error ? error.message : "Unknown filesystem error";
|
|
13017
|
+
throw new CliCommandError(`Could not write result file "${destinationPath}": ${message}`, 1);
|
|
13018
|
+
} finally {
|
|
13019
|
+
await temporaryFile?.close().catch(() => {
|
|
13020
|
+
});
|
|
13021
|
+
await unlink(temporaryPath).catch(() => {
|
|
13022
|
+
});
|
|
13023
|
+
}
|
|
13024
|
+
};
|
|
13025
|
+
|
|
13026
|
+
// src/lib/sheet-list-all.ts
|
|
13027
|
+
var pageSize = 100;
|
|
13028
|
+
var collectAllSheets = async ({
|
|
13029
|
+
fetchPage
|
|
13030
|
+
}) => {
|
|
13031
|
+
const sheets = [];
|
|
13032
|
+
const seenSheetIds = /* @__PURE__ */ new Set();
|
|
13033
|
+
const duplicateSheetIds = /* @__PURE__ */ new Set();
|
|
13034
|
+
const seenPageSignatures = /* @__PURE__ */ new Set();
|
|
13035
|
+
const observedTotalCounts = [];
|
|
13036
|
+
const observedInventoryTokens = [];
|
|
13037
|
+
const issues = [];
|
|
13038
|
+
const warnings = [];
|
|
13039
|
+
let afterId;
|
|
13040
|
+
let pagesFetched = 0;
|
|
13041
|
+
let rowsFetched = 0;
|
|
13042
|
+
const addIssue = (issue, warning) => {
|
|
13043
|
+
if (issues.includes(issue)) {
|
|
13044
|
+
return;
|
|
13045
|
+
}
|
|
13046
|
+
issues.push(issue);
|
|
13047
|
+
warnings.push(warning);
|
|
13048
|
+
};
|
|
13049
|
+
while (true) {
|
|
13050
|
+
const page = await fetchPage({ limit: pageSize, ...afterId ? { afterId } : {} });
|
|
13051
|
+
pagesFetched += 1;
|
|
13052
|
+
rowsFetched += page.sheets.length;
|
|
13053
|
+
observedTotalCounts.push(page.totalCount);
|
|
13054
|
+
observedInventoryTokens.push(page.inventoryToken);
|
|
13055
|
+
const firstObservedTotal = observedTotalCounts[0];
|
|
13056
|
+
if (page.totalCount !== firstObservedTotal) {
|
|
13057
|
+
addIssue(
|
|
13058
|
+
"total_count_changed",
|
|
13059
|
+
`Sheet inventory total changed during collection (${firstObservedTotal} -> ${page.totalCount}).`
|
|
13060
|
+
);
|
|
13061
|
+
}
|
|
13062
|
+
const firstInventoryToken = observedInventoryTokens[0];
|
|
13063
|
+
if (page.inventoryToken !== firstInventoryToken) {
|
|
13064
|
+
addIssue(
|
|
13065
|
+
"inventory_changed",
|
|
13066
|
+
`Sheet inventory membership changed during collection (${firstInventoryToken} -> ${page.inventoryToken}).`
|
|
13067
|
+
);
|
|
13068
|
+
}
|
|
13069
|
+
if (page.sheets.length === 0) {
|
|
13070
|
+
if (sheets.length < page.totalCount) {
|
|
13071
|
+
addIssue(
|
|
13072
|
+
"early_empty_page",
|
|
13073
|
+
`Sheet inventory returned an empty page${afterId ? ` after cursor ${afterId}` : ""} before the observed total of ${page.totalCount}.`
|
|
13074
|
+
);
|
|
13075
|
+
}
|
|
13076
|
+
break;
|
|
13077
|
+
}
|
|
13078
|
+
const pageSignature = page.sheets.map(({ id }) => id).join("\n");
|
|
13079
|
+
if (seenPageSignatures.has(pageSignature)) {
|
|
13080
|
+
addIssue(
|
|
13081
|
+
"repeated_page",
|
|
13082
|
+
`Sheet inventory page${afterId ? ` after cursor ${afterId}` : ""} repeated an earlier page; collection stopped.`
|
|
13083
|
+
);
|
|
13084
|
+
break;
|
|
13085
|
+
}
|
|
13086
|
+
seenPageSignatures.add(pageSignature);
|
|
13087
|
+
for (const sheet of page.sheets) {
|
|
13088
|
+
if (seenSheetIds.has(sheet.id)) {
|
|
13089
|
+
duplicateSheetIds.add(sheet.id);
|
|
13090
|
+
continue;
|
|
13091
|
+
}
|
|
13092
|
+
seenSheetIds.add(sheet.id);
|
|
13093
|
+
sheets.push(sheet);
|
|
13094
|
+
}
|
|
13095
|
+
if (duplicateSheetIds.size > 0) {
|
|
13096
|
+
addIssue(
|
|
13097
|
+
"duplicate_sheet_ids",
|
|
13098
|
+
`Sheet inventory pages repeated ${duplicateSheetIds.size} sheet ID${duplicateSheetIds.size === 1 ? "" : "s"}; the snapshot may be incomplete.`
|
|
13099
|
+
);
|
|
13100
|
+
}
|
|
13101
|
+
if (rowsFetched >= page.totalCount) {
|
|
13102
|
+
break;
|
|
13103
|
+
}
|
|
13104
|
+
if (page.sheets.length < pageSize) {
|
|
13105
|
+
addIssue(
|
|
13106
|
+
"early_short_page",
|
|
13107
|
+
`Sheet inventory returned only ${page.sheets.length} sheets${afterId ? ` after cursor ${afterId}` : ""} before the observed total of ${page.totalCount}.`
|
|
13108
|
+
);
|
|
13109
|
+
break;
|
|
13110
|
+
}
|
|
13111
|
+
afterId = page.sheets.at(-1).id;
|
|
13112
|
+
}
|
|
13113
|
+
const totalCount = observedTotalCounts.at(-1) ?? 0;
|
|
13114
|
+
if (sheets.length !== totalCount) {
|
|
13115
|
+
addIssue(
|
|
13116
|
+
"sheet_count_mismatch",
|
|
13117
|
+
`Collected ${sheets.length} unique sheets but the final observed total was ${totalCount}.`
|
|
13118
|
+
);
|
|
13119
|
+
}
|
|
13120
|
+
return {
|
|
13121
|
+
sheets,
|
|
13122
|
+
totalCount,
|
|
13123
|
+
snapshot: {
|
|
13124
|
+
complete: issues.length === 0,
|
|
13125
|
+
pageSize,
|
|
13126
|
+
pagesFetched,
|
|
13127
|
+
observedTotalCounts,
|
|
13128
|
+
observedInventoryTokens,
|
|
13129
|
+
uniqueSheetCount: sheets.length,
|
|
13130
|
+
duplicateSheetIds: [...duplicateSheetIds],
|
|
13131
|
+
issues
|
|
13132
|
+
},
|
|
13133
|
+
warnings
|
|
13134
|
+
};
|
|
12049
13135
|
};
|
|
12050
13136
|
|
|
12051
13137
|
// src/commands/sheet.ts
|
|
12052
|
-
import { z as z14 } from "zod";
|
|
12053
13138
|
var readinessIssueTypeLabels = {
|
|
12054
13139
|
missing_translation: "missing translations",
|
|
12055
13140
|
missing_solution: "missing solutions",
|
|
@@ -12074,6 +13159,33 @@ var normalizeReadinessFilters = ({
|
|
|
12074
13159
|
}) ?? []
|
|
12075
13160
|
};
|
|
12076
13161
|
};
|
|
13162
|
+
var normalizeSheetSeriesPath = (value) => {
|
|
13163
|
+
const normalizedValue = normalizeOptionalText(value);
|
|
13164
|
+
if (normalizedValue === null) {
|
|
13165
|
+
return null;
|
|
13166
|
+
}
|
|
13167
|
+
const parsedValue = sheetSeriesPathSchema.safeParse(normalizedValue);
|
|
13168
|
+
if (!parsedValue.success) {
|
|
13169
|
+
throw new CliCommandError(
|
|
13170
|
+
'--series-path is unknown. Run "chalksurf sheet series list --json" to inspect valid paths.',
|
|
13171
|
+
2
|
|
13172
|
+
);
|
|
13173
|
+
}
|
|
13174
|
+
return parsedValue.data;
|
|
13175
|
+
};
|
|
13176
|
+
var normalizeSheetSeriesYearFilter = ({ fromYear, toYear }) => {
|
|
13177
|
+
if (fromYear === void 0 && toYear === void 0) {
|
|
13178
|
+
return null;
|
|
13179
|
+
}
|
|
13180
|
+
if (fromYear !== void 0 && toYear !== void 0 && fromYear > toYear) {
|
|
13181
|
+
throw new CliCommandError("--from-year must be less than or equal to --to-year.", 2);
|
|
13182
|
+
}
|
|
13183
|
+
const parsedFilter = sheetSeriesYearFilterSchema.safeParse({ fromYear, toYear });
|
|
13184
|
+
if (!parsedFilter.success) {
|
|
13185
|
+
throw new CliCommandError("--from-year and --to-year must be four-digit years.", 2);
|
|
13186
|
+
}
|
|
13187
|
+
return parsedFilter.data;
|
|
13188
|
+
};
|
|
12077
13189
|
var hasUniqueItems2 = (values) => new Set(values).size === values.length;
|
|
12078
13190
|
var sheetImportComponentIdPattern3 = /^[a-zA-Z0-9_-]{1,80}$/;
|
|
12079
13191
|
var isValidTargetFolderPath2 = (value) => {
|
|
@@ -12174,7 +13286,7 @@ var resolveRequestedUuid2 = ({
|
|
|
12174
13286
|
}
|
|
12175
13287
|
return void 0;
|
|
12176
13288
|
}
|
|
12177
|
-
const parsedValue =
|
|
13289
|
+
const parsedValue = z16.uuid().safeParse(resolvedValue);
|
|
12178
13290
|
if (!parsedValue.success) {
|
|
12179
13291
|
throw new CliCommandError(`${label} must be a valid UUID.`, 2);
|
|
12180
13292
|
}
|
|
@@ -12185,7 +13297,7 @@ var resolveRequestedUuidList = ({ label, values }) => {
|
|
|
12185
13297
|
throw new CliCommandError(`${label} is required at least once.`, 2);
|
|
12186
13298
|
}
|
|
12187
13299
|
return values.map((value, index) => {
|
|
12188
|
-
const parsedValue =
|
|
13300
|
+
const parsedValue = z16.uuid().safeParse(value.trim());
|
|
12189
13301
|
if (!parsedValue.success) {
|
|
12190
13302
|
throw new CliCommandError(`${label}[${index}] must be a valid UUID.`, 2);
|
|
12191
13303
|
}
|
|
@@ -12501,6 +13613,19 @@ var formatSheetSearchOutput = ({
|
|
|
12501
13613
|
)
|
|
12502
13614
|
].join("\n");
|
|
12503
13615
|
};
|
|
13616
|
+
var formatSheetListOutput = ({ sheets, totalCount }) => {
|
|
13617
|
+
if (sheets.length === 0) {
|
|
13618
|
+
return "No sheets found.";
|
|
13619
|
+
}
|
|
13620
|
+
return [
|
|
13621
|
+
`${sheets.length} of ${totalCount} sheet${totalCount === 1 ? "" : "s"} returned.`,
|
|
13622
|
+
...sheets.map((sheet) => {
|
|
13623
|
+
const folderPath = sheet.folderPath.map(({ name }) => name).join(" / ") || "(root)";
|
|
13624
|
+
const assignments = sheet.seriesAssignments.map(({ editionYear, seriesPath }) => `${seriesPath}@${editionYear}`).join(", ") || "unassigned";
|
|
13625
|
+
return `${sheet.id} ${sheet.name} ${folderPath} ${assignments}`;
|
|
13626
|
+
})
|
|
13627
|
+
].join("\n");
|
|
13628
|
+
};
|
|
12504
13629
|
var formatSheetReadinessSummary = (readinessSummary) => {
|
|
12505
13630
|
if (!readinessSummary.hasIssues) {
|
|
12506
13631
|
return "OK";
|
|
@@ -12581,22 +13706,26 @@ var formatDeleteSheetOutput = ({ receipt }) => {
|
|
|
12581
13706
|
var formatUpdateSheetOutput = ({ receipt }) => {
|
|
12582
13707
|
return formatAgentWriteReceiptOutput({ receipt });
|
|
12583
13708
|
};
|
|
12584
|
-
var
|
|
12585
|
-
|
|
12586
|
-
mode,
|
|
12587
|
-
receipt,
|
|
12588
|
-
results
|
|
12589
|
-
}) => {
|
|
12590
|
-
const summary = results.reduce(
|
|
13709
|
+
var getBulkUpdateSheetsCounts = ({ results }) => {
|
|
13710
|
+
const statusCounts = results.reduce(
|
|
12591
13711
|
(counts, result) => ({
|
|
12592
13712
|
...counts,
|
|
12593
13713
|
[result.status]: counts[result.status] + 1
|
|
12594
13714
|
}),
|
|
12595
13715
|
{ ready: 0, unchanged: 0, updated: 0, error: 0 }
|
|
12596
13716
|
);
|
|
13717
|
+
return { total: results.length, ...statusCounts };
|
|
13718
|
+
};
|
|
13719
|
+
var formatBulkUpdateSheetsOutput = ({
|
|
13720
|
+
confirmationDigest,
|
|
13721
|
+
mode,
|
|
13722
|
+
receipt,
|
|
13723
|
+
results
|
|
13724
|
+
}) => {
|
|
13725
|
+
const counts = getBulkUpdateSheetsCounts({ confirmationDigest, mode, receipt, results });
|
|
12597
13726
|
const lines = [
|
|
12598
|
-
`${mode === "dry_run" ? "Validated" : "Applied"} ${
|
|
12599
|
-
`ready: ${
|
|
13727
|
+
`${mode === "dry_run" ? "Validated" : "Applied"} ${counts.total} sheet update${counts.total === 1 ? "" : "s"}.`,
|
|
13728
|
+
`ready: ${counts.ready} updated: ${counts.updated} unchanged: ${counts.unchanged} errors: ${counts.error}`,
|
|
12600
13729
|
`confirmation digest: ${confirmationDigest}`
|
|
12601
13730
|
];
|
|
12602
13731
|
if (receipt) {
|
|
@@ -12604,6 +13733,161 @@ var formatBulkUpdateSheetsOutput = ({
|
|
|
12604
13733
|
}
|
|
12605
13734
|
return lines.join("\n");
|
|
12606
13735
|
};
|
|
13736
|
+
var formatVerifyBulkSheetUpdatesOutput = ({ counts, state }) => [
|
|
13737
|
+
`Verification state: ${state}.`,
|
|
13738
|
+
`total: ${counts.total} applied: ${counts.applied} pending: ${counts.pending} conflicting: ${counts.conflicting} errors: ${counts.error}`
|
|
13739
|
+
].join("\n");
|
|
13740
|
+
var printWithOptionalResultArtifact = async ({
|
|
13741
|
+
command,
|
|
13742
|
+
compactHumanFormatter,
|
|
13743
|
+
compactValue,
|
|
13744
|
+
context,
|
|
13745
|
+
fullHumanFormatter,
|
|
13746
|
+
fullValue,
|
|
13747
|
+
resultFileInput
|
|
13748
|
+
}) => {
|
|
13749
|
+
if (resultFileInput === void 0) {
|
|
13750
|
+
context.output.print(fullValue, fullHumanFormatter, { command });
|
|
13751
|
+
return;
|
|
13752
|
+
}
|
|
13753
|
+
if (!resultFileInput.trim()) {
|
|
13754
|
+
throw new CliCommandError("--result-file must be a non-empty path.", 2);
|
|
13755
|
+
}
|
|
13756
|
+
const resultFile = resolveCliResultFilePath({ cwd: context.cwd, inputPath: resultFileInput });
|
|
13757
|
+
await context.output.printWithResultArtifact(fullValue, { ...compactValue, resultFile }, compactHumanFormatter, {
|
|
13758
|
+
command,
|
|
13759
|
+
publishArtifact: async (envelope) => await publishCliResultArtifact({
|
|
13760
|
+
destinationPath: resultFile,
|
|
13761
|
+
envelope
|
|
13762
|
+
})
|
|
13763
|
+
});
|
|
13764
|
+
};
|
|
13765
|
+
var configureBulkJsonInputOptions = (commandYargs, description) => commandYargs.option("input", {
|
|
13766
|
+
type: "string",
|
|
13767
|
+
nargs: 1,
|
|
13768
|
+
describe: 'Read the JSON object from a UTF-8 file, or "-" for stdin'
|
|
13769
|
+
}).option("input-json", {
|
|
13770
|
+
type: "string",
|
|
13771
|
+
nargs: 1,
|
|
13772
|
+
describe: description
|
|
13773
|
+
}).option("result-file", {
|
|
13774
|
+
type: "string",
|
|
13775
|
+
nargs: 1,
|
|
13776
|
+
describe: "Publish the complete JSON result envelope to a new private file"
|
|
13777
|
+
});
|
|
13778
|
+
var runBulkUpdateCommand = async (argv, context) => {
|
|
13779
|
+
const args = argv;
|
|
13780
|
+
if (args.resultFile !== void 0 && !args.resultFile.trim()) {
|
|
13781
|
+
throw new CliCommandError("--result-file must be a non-empty path.", 2);
|
|
13782
|
+
}
|
|
13783
|
+
const input = parseBulkUpdateSheetsAgentInput(
|
|
13784
|
+
await loadJsonObjectInput({
|
|
13785
|
+
cwd: context.cwd,
|
|
13786
|
+
inputJson: args.inputJson,
|
|
13787
|
+
inputPath: args.input,
|
|
13788
|
+
stdin: context.stdin
|
|
13789
|
+
})
|
|
13790
|
+
);
|
|
13791
|
+
const { apiClient, organizationId } = await createResolvedApiClient({
|
|
13792
|
+
context,
|
|
13793
|
+
baseUrlFlagValue: args.baseUrl,
|
|
13794
|
+
organizationFlagValue: args.organization,
|
|
13795
|
+
profileName: args.profile
|
|
13796
|
+
});
|
|
13797
|
+
let result;
|
|
13798
|
+
try {
|
|
13799
|
+
result = await apiClient.agentBulkUpdateSheets(input);
|
|
13800
|
+
} catch (error) {
|
|
13801
|
+
throw mapApiErrorToCliError(error);
|
|
13802
|
+
}
|
|
13803
|
+
const fullValue = {
|
|
13804
|
+
request: {
|
|
13805
|
+
...input,
|
|
13806
|
+
organizationId: organizationId ?? null
|
|
13807
|
+
},
|
|
13808
|
+
...result
|
|
13809
|
+
};
|
|
13810
|
+
const counts = getBulkUpdateSheetsCounts(result);
|
|
13811
|
+
try {
|
|
13812
|
+
await printWithOptionalResultArtifact({
|
|
13813
|
+
command: "sheet bulk-update",
|
|
13814
|
+
compactHumanFormatter: ({ confirmationDigest, counts: counts2, mode, receiptPresent, resultFile }) => [
|
|
13815
|
+
`${mode === "dry_run" ? "Validated" : "Applied"} ${counts2.total} sheet update${counts2.total === 1 ? "" : "s"}.`,
|
|
13816
|
+
`ready: ${counts2.ready} updated: ${counts2.updated} unchanged: ${counts2.unchanged} errors: ${counts2.error}`,
|
|
13817
|
+
`confirmation digest: ${confirmationDigest}`,
|
|
13818
|
+
`receipt: ${receiptPresent ? "present" : "none"}`,
|
|
13819
|
+
`full result: ${resultFile}`
|
|
13820
|
+
].join("\n"),
|
|
13821
|
+
compactValue: {
|
|
13822
|
+
mode: result.mode,
|
|
13823
|
+
counts,
|
|
13824
|
+
confirmationDigest: result.confirmationDigest,
|
|
13825
|
+
receiptPresent: result.receipt !== null
|
|
13826
|
+
},
|
|
13827
|
+
context,
|
|
13828
|
+
fullHumanFormatter: (output) => formatBulkUpdateSheetsOutput(output),
|
|
13829
|
+
fullValue,
|
|
13830
|
+
resultFileInput: args.resultFile
|
|
13831
|
+
});
|
|
13832
|
+
} catch (error) {
|
|
13833
|
+
if (input.mode !== "apply") {
|
|
13834
|
+
throw error;
|
|
13835
|
+
}
|
|
13836
|
+
const message = error instanceof Error ? error.message : "Unknown result artifact error";
|
|
13837
|
+
context.output.info(
|
|
13838
|
+
`The server confirmed the bulk apply, but the result artifact could not be published: ${message} Do not retry the apply; preserve this stdout response and use "sheet bulk-update verify" with the original manifest if independent recovery is needed.`
|
|
13839
|
+
);
|
|
13840
|
+
context.output.print(fullValue, (output) => formatBulkUpdateSheetsOutput(output), {
|
|
13841
|
+
command: "sheet bulk-update"
|
|
13842
|
+
});
|
|
13843
|
+
}
|
|
13844
|
+
};
|
|
13845
|
+
var runVerifyBulkSheetUpdatesCommand = async (argv, context) => {
|
|
13846
|
+
const args = argv;
|
|
13847
|
+
const input = parseVerifyBulkSheetUpdatesAgentInput(
|
|
13848
|
+
await loadJsonObjectInput({
|
|
13849
|
+
cwd: context.cwd,
|
|
13850
|
+
inputJson: args.inputJson,
|
|
13851
|
+
inputPath: args.input,
|
|
13852
|
+
stdin: context.stdin
|
|
13853
|
+
})
|
|
13854
|
+
);
|
|
13855
|
+
const { apiClient, organizationId } = await createResolvedApiClient({
|
|
13856
|
+
context,
|
|
13857
|
+
baseUrlFlagValue: args.baseUrl,
|
|
13858
|
+
organizationFlagValue: args.organization,
|
|
13859
|
+
profileName: args.profile
|
|
13860
|
+
});
|
|
13861
|
+
let result;
|
|
13862
|
+
try {
|
|
13863
|
+
result = await apiClient.agentVerifyBulkSheetUpdates(input);
|
|
13864
|
+
} catch (error) {
|
|
13865
|
+
throw mapApiErrorToCliError(error);
|
|
13866
|
+
}
|
|
13867
|
+
const fullValue = {
|
|
13868
|
+
request: {
|
|
13869
|
+
...input,
|
|
13870
|
+
organizationId: organizationId ?? null
|
|
13871
|
+
},
|
|
13872
|
+
...result
|
|
13873
|
+
};
|
|
13874
|
+
await printWithOptionalResultArtifact({
|
|
13875
|
+
command: "sheet bulk-update verify",
|
|
13876
|
+
compactHumanFormatter: ({ counts, resultFile, state }) => [
|
|
13877
|
+
`Verification state: ${state}.`,
|
|
13878
|
+
`total: ${counts.total} applied: ${counts.applied} pending: ${counts.pending} conflicting: ${counts.conflicting} errors: ${counts.error}`,
|
|
13879
|
+
`full result: ${resultFile}`
|
|
13880
|
+
].join("\n"),
|
|
13881
|
+
compactValue: {
|
|
13882
|
+
state: result.state,
|
|
13883
|
+
counts: result.counts
|
|
13884
|
+
},
|
|
13885
|
+
context,
|
|
13886
|
+
fullHumanFormatter: (output) => formatVerifyBulkSheetUpdatesOutput(output),
|
|
13887
|
+
fullValue,
|
|
13888
|
+
resultFileInput: args.resultFile
|
|
13889
|
+
});
|
|
13890
|
+
};
|
|
12607
13891
|
var formatSheetSeriesOutput = ({ series }) => series.map(({ depth, names, path }) => `${" ".repeat(depth)}${names.english} ${path}`).join("\n");
|
|
12608
13892
|
var formatSetSheetVisibilityOutput = ({ receipt }) => {
|
|
12609
13893
|
return formatAgentWriteReceiptOutput({ receipt });
|
|
@@ -12649,6 +13933,128 @@ var resolveVisibilityFlag2 = ({ privateFlag, publicFlag }) => {
|
|
|
12649
13933
|
};
|
|
12650
13934
|
var registerSheetCommands = (sheetYargs, context) => {
|
|
12651
13935
|
return sheetYargs.command(
|
|
13936
|
+
"list",
|
|
13937
|
+
"List exercise sheets owned by the selected organization",
|
|
13938
|
+
(listYargs) => listYargs.option("folder-id", {
|
|
13939
|
+
type: "string",
|
|
13940
|
+
describe: "Only include sheets placed directly in this folder"
|
|
13941
|
+
}).option("include-descendants", {
|
|
13942
|
+
type: "boolean",
|
|
13943
|
+
default: false,
|
|
13944
|
+
describe: "Include sheets in descendant folders; requires --folder-id"
|
|
13945
|
+
}).option("limit", {
|
|
13946
|
+
type: "number",
|
|
13947
|
+
describe: "Maximum number of sheets to return"
|
|
13948
|
+
}).option("offset", {
|
|
13949
|
+
type: "number",
|
|
13950
|
+
describe: "Number of organization-owned sheets to skip"
|
|
13951
|
+
}).option("after-id", {
|
|
13952
|
+
type: "string",
|
|
13953
|
+
describe: "Return sheets with IDs after this keyset cursor; cannot be combined with --offset"
|
|
13954
|
+
}).option("all", {
|
|
13955
|
+
type: "boolean",
|
|
13956
|
+
default: false,
|
|
13957
|
+
describe: "Collect every organization-owned sheet in sequential pages of 100"
|
|
13958
|
+
}).option("result-file", {
|
|
13959
|
+
type: "string",
|
|
13960
|
+
nargs: 1,
|
|
13961
|
+
describe: "Publish the complete JSON result envelope to a new private file"
|
|
13962
|
+
}).example("chalksurf --profile prod-codex sheet list --json", "List sheets owned by the selected organization").example(
|
|
13963
|
+
"chalksurf --profile prod-codex sheet list --all --result-file inventory.json --json",
|
|
13964
|
+
"Save a complete reviewed inventory"
|
|
13965
|
+
).example(
|
|
13966
|
+
"chalksurf --profile prod-codex sheet list --folder-id 00000000-0000-4000-8000-000000000001 --include-descendants --json",
|
|
13967
|
+
"List a folder subtree"
|
|
13968
|
+
),
|
|
13969
|
+
async (argv) => {
|
|
13970
|
+
const folderId = resolveRequestedUuid2({
|
|
13971
|
+
flagValue: argv.folderId,
|
|
13972
|
+
label: "--folder-id"
|
|
13973
|
+
});
|
|
13974
|
+
const includeDescendants = Boolean(argv.includeDescendants);
|
|
13975
|
+
if (includeDescendants && !folderId) {
|
|
13976
|
+
throw new CliCommandError("--folder-id is required with --include-descendants.", 2);
|
|
13977
|
+
}
|
|
13978
|
+
const collectAll = Boolean(argv.all);
|
|
13979
|
+
const afterId = resolveRequestedUuid2({
|
|
13980
|
+
flagValue: argv.afterId,
|
|
13981
|
+
label: "--after-id"
|
|
13982
|
+
});
|
|
13983
|
+
const limitArgument = argv.limit;
|
|
13984
|
+
const offsetArgument = argv.offset;
|
|
13985
|
+
if (collectAll && (limitArgument !== void 0 || offsetArgument !== void 0 || afterId !== void 0)) {
|
|
13986
|
+
throw new CliCommandError("--all cannot be combined with --limit, --offset, or --after-id.", 2);
|
|
13987
|
+
}
|
|
13988
|
+
if (afterId !== void 0 && offsetArgument !== void 0 && offsetArgument !== 0) {
|
|
13989
|
+
throw new CliCommandError("--after-id cannot be combined with a non-zero --offset.", 2);
|
|
13990
|
+
}
|
|
13991
|
+
const limit = collectAll ? null : normalizeLimit(limitArgument ?? 50);
|
|
13992
|
+
const offset = collectAll ? null : normalizeOffset(offsetArgument);
|
|
13993
|
+
const { apiClient, organizationId } = await createResolvedApiClient({
|
|
13994
|
+
context,
|
|
13995
|
+
baseUrlFlagValue: argv.baseUrl,
|
|
13996
|
+
organizationFlagValue: argv.organization,
|
|
13997
|
+
profileName: argv.profile
|
|
13998
|
+
});
|
|
13999
|
+
let result;
|
|
14000
|
+
try {
|
|
14001
|
+
if (collectAll) {
|
|
14002
|
+
const collected = await collectAllSheets({
|
|
14003
|
+
fetchPage: async ({ afterId: pageAfterId, limit: pageLimit }) => await apiClient.agentListSheets({
|
|
14004
|
+
afterId: pageAfterId,
|
|
14005
|
+
folderId,
|
|
14006
|
+
includeDescendants,
|
|
14007
|
+
limit: pageLimit,
|
|
14008
|
+
offset: 0
|
|
14009
|
+
})
|
|
14010
|
+
});
|
|
14011
|
+
for (const warning of collected.warnings) {
|
|
14012
|
+
context.output.info(warning);
|
|
14013
|
+
}
|
|
14014
|
+
result = {
|
|
14015
|
+
sheets: collected.sheets,
|
|
14016
|
+
totalCount: collected.totalCount,
|
|
14017
|
+
snapshot: collected.snapshot
|
|
14018
|
+
};
|
|
14019
|
+
} else {
|
|
14020
|
+
result = await apiClient.agentListSheets({
|
|
14021
|
+
afterId,
|
|
14022
|
+
folderId,
|
|
14023
|
+
includeDescendants,
|
|
14024
|
+
limit,
|
|
14025
|
+
offset
|
|
14026
|
+
});
|
|
14027
|
+
}
|
|
14028
|
+
} catch (error) {
|
|
14029
|
+
throw mapApiErrorToCliError(error);
|
|
14030
|
+
}
|
|
14031
|
+
const fullValue = {
|
|
14032
|
+
request: {
|
|
14033
|
+
...collectAll ? { all: true } : {},
|
|
14034
|
+
...afterId ? { afterId } : {},
|
|
14035
|
+
folderId: folderId ?? null,
|
|
14036
|
+
includeDescendants,
|
|
14037
|
+
limit,
|
|
14038
|
+
offset,
|
|
14039
|
+
organizationId: organizationId ?? null
|
|
14040
|
+
},
|
|
14041
|
+
...result
|
|
14042
|
+
};
|
|
14043
|
+
await printWithOptionalResultArtifact({
|
|
14044
|
+
command: "sheet list",
|
|
14045
|
+
compactHumanFormatter: ({ resultFile, sheetCount, snapshotComplete }) => `Saved ${sheetCount} sheet${sheetCount === 1 ? "" : "s"} to ${resultFile}.${snapshotComplete === false ? " Snapshot is incomplete." : ""}`,
|
|
14046
|
+
compactValue: {
|
|
14047
|
+
sheetCount: result.sheets.length,
|
|
14048
|
+
totalCount: result.totalCount,
|
|
14049
|
+
snapshotComplete: "snapshot" in result ? result.snapshot.complete : null
|
|
14050
|
+
},
|
|
14051
|
+
context,
|
|
14052
|
+
fullHumanFormatter: (output) => formatSheetListOutput(output),
|
|
14053
|
+
fullValue,
|
|
14054
|
+
resultFileInput: argv.resultFile
|
|
14055
|
+
});
|
|
14056
|
+
}
|
|
14057
|
+
).command(
|
|
12652
14058
|
"search",
|
|
12653
14059
|
"Search exercise sheets visible to the selected organization",
|
|
12654
14060
|
(searchYargs) => searchYargs.option("text", {
|
|
@@ -12661,6 +14067,15 @@ var registerSheetCommands = (sheetYargs, context) => {
|
|
|
12661
14067
|
}).option("subject", {
|
|
12662
14068
|
type: "string",
|
|
12663
14069
|
describe: `Only include sheets in this subject (${exerciseSubjects.join(", ")})`
|
|
14070
|
+
}).option("series-path", {
|
|
14071
|
+
type: "string",
|
|
14072
|
+
describe: "Only include sheets with an assignment at or below this authoritative series path"
|
|
14073
|
+
}).option("from-year", {
|
|
14074
|
+
type: "number",
|
|
14075
|
+
describe: "Only include matching series assignments from this edition year"
|
|
14076
|
+
}).option("to-year", {
|
|
14077
|
+
type: "number",
|
|
14078
|
+
describe: "Only include matching series assignments through this edition year"
|
|
12664
14079
|
}).option("limit", {
|
|
12665
14080
|
type: "number",
|
|
12666
14081
|
default: 20,
|
|
@@ -12696,6 +14111,11 @@ var registerSheetCommands = (sheetYargs, context) => {
|
|
|
12696
14111
|
label: "--subject",
|
|
12697
14112
|
value: argv.subject
|
|
12698
14113
|
});
|
|
14114
|
+
const seriesPath = normalizeSheetSeriesPath(argv.seriesPath);
|
|
14115
|
+
const yearFilter = normalizeSheetSeriesYearFilter({
|
|
14116
|
+
fromYear: argv.fromYear,
|
|
14117
|
+
toYear: argv.toYear
|
|
14118
|
+
});
|
|
12699
14119
|
const limit = normalizeLimit(argv.limit);
|
|
12700
14120
|
const offset = normalizeOffset(argv.offset);
|
|
12701
14121
|
const readinessFilters = normalizeReadinessFilters({
|
|
@@ -12716,6 +14136,8 @@ var registerSheetCommands = (sheetYargs, context) => {
|
|
|
12716
14136
|
issueTypes: readinessFilters.issueTypes,
|
|
12717
14137
|
text,
|
|
12718
14138
|
subject,
|
|
14139
|
+
seriesPath,
|
|
14140
|
+
yearFilter,
|
|
12719
14141
|
ownership: toApiOwnership(ownership),
|
|
12720
14142
|
limit,
|
|
12721
14143
|
offset
|
|
@@ -12728,6 +14150,8 @@ var registerSheetCommands = (sheetYargs, context) => {
|
|
|
12728
14150
|
request: {
|
|
12729
14151
|
text,
|
|
12730
14152
|
subject,
|
|
14153
|
+
seriesPath,
|
|
14154
|
+
yearFilter,
|
|
12731
14155
|
ownership,
|
|
12732
14156
|
hasIssues: readinessFilters.hasIssues,
|
|
12733
14157
|
issueTypes: readinessFilters.issueTypes,
|
|
@@ -12934,8 +14358,16 @@ var registerSheetCommands = (sheetYargs, context) => {
|
|
|
12934
14358
|
(seriesYargs) => seriesYargs.command(
|
|
12935
14359
|
"list",
|
|
12936
14360
|
"List valid sheet-series paths",
|
|
12937
|
-
(listYargs) => listYargs.
|
|
14361
|
+
(listYargs) => listYargs.option("subject", {
|
|
14362
|
+
type: "string",
|
|
14363
|
+
describe: `Only include series paths valid for this subject (${exerciseSubjects.join(", ")})`
|
|
14364
|
+
}).example("chalksurf sheet series list --json", "Inspect valid paths before assigning sheets").example("chalksurf sheet series list --subject math --json", "Inspect mathematics series paths"),
|
|
12938
14365
|
async (argv) => {
|
|
14366
|
+
const subject = normalizeChoiceValue({
|
|
14367
|
+
allowedValues: exerciseSubjects,
|
|
14368
|
+
label: "--subject",
|
|
14369
|
+
value: argv.subject
|
|
14370
|
+
});
|
|
12939
14371
|
const { apiClient, organizationId } = await createResolvedApiClient({
|
|
12940
14372
|
context,
|
|
12941
14373
|
baseUrlFlagValue: argv.baseUrl,
|
|
@@ -12944,13 +14376,15 @@ var registerSheetCommands = (sheetYargs, context) => {
|
|
|
12944
14376
|
});
|
|
12945
14377
|
let result;
|
|
12946
14378
|
try {
|
|
12947
|
-
result = await apiClient.agentListSheetSeries(
|
|
14379
|
+
result = await apiClient.agentListSheetSeries({
|
|
14380
|
+
subject: subject ?? void 0
|
|
14381
|
+
});
|
|
12948
14382
|
} catch (error) {
|
|
12949
14383
|
throw mapApiErrorToCliError(error);
|
|
12950
14384
|
}
|
|
12951
14385
|
context.output.print(
|
|
12952
14386
|
{
|
|
12953
|
-
request: { organizationId: organizationId ?? null },
|
|
14387
|
+
request: { organizationId: organizationId ?? null, subject },
|
|
12954
14388
|
series: result.series
|
|
12955
14389
|
},
|
|
12956
14390
|
(output) => formatSheetSeriesOutput({ series: output.series }),
|
|
@@ -13610,43 +15044,35 @@ var registerSheetCommands = (sheetYargs, context) => {
|
|
|
13610
15044
|
);
|
|
13611
15045
|
}
|
|
13612
15046
|
).command(
|
|
13613
|
-
"bulk-update",
|
|
13614
|
-
"
|
|
13615
|
-
(bulkUpdateYargs) => bulkUpdateYargs.
|
|
13616
|
-
|
|
13617
|
-
|
|
13618
|
-
|
|
13619
|
-
|
|
13620
|
-
|
|
13621
|
-
|
|
15047
|
+
"bulk-update [subcommand]",
|
|
15048
|
+
"Bulk sheet update and recovery commands",
|
|
15049
|
+
(bulkUpdateYargs) => bulkUpdateYargs.command(
|
|
15050
|
+
"$0",
|
|
15051
|
+
"Dry-run or apply up to 100 sheet patches",
|
|
15052
|
+
(runYargs) => configureBulkJsonInputOptions(
|
|
15053
|
+
runYargs,
|
|
15054
|
+
"Inline JSON with mode, updates, and confirmationDigest for apply mode"
|
|
15055
|
+
).example(
|
|
15056
|
+
"chalksurf sheet bulk-update --input batch-001.json --result-file dry-run.json --json",
|
|
15057
|
+
"Validate a reviewed batch and save the complete result"
|
|
15058
|
+
).example(
|
|
15059
|
+
"cat batch-001.json | chalksurf sheet bulk-update --input - --json",
|
|
15060
|
+
"Validate a reviewed batch from stdin"
|
|
15061
|
+
).example('chalksurf sheet bulk-update --input-json "$BATCH_JSON" --json', "Validate an inline batch"),
|
|
15062
|
+
async (argv) => await runBulkUpdateCommand(argv, context)
|
|
15063
|
+
).command(
|
|
15064
|
+
"verify",
|
|
15065
|
+
"Verify current targets after an uncertain bulk apply response",
|
|
15066
|
+
(verifyYargs) => configureBulkJsonInputOptions(
|
|
15067
|
+
verifyYargs,
|
|
15068
|
+
"Inline JSON containing updates, or a prior dry-run/apply manifest"
|
|
15069
|
+
).example(
|
|
15070
|
+
"chalksurf sheet bulk-update verify --input batch-001.json --result-file verification.json --json",
|
|
15071
|
+
"Classify the current state of every target in a batch"
|
|
15072
|
+
),
|
|
15073
|
+
async (argv) => await runVerifyBulkSheetUpdatesCommand(argv, context)
|
|
13622
15074
|
),
|
|
13623
|
-
|
|
13624
|
-
const input = parseBulkUpdateSheetsAgentInput(
|
|
13625
|
-
parseJsonObjectFlag({ label: "--input-json", value: argv.inputJson })
|
|
13626
|
-
);
|
|
13627
|
-
const { apiClient, organizationId } = await createResolvedApiClient({
|
|
13628
|
-
context,
|
|
13629
|
-
baseUrlFlagValue: argv.baseUrl,
|
|
13630
|
-
organizationFlagValue: argv.organization,
|
|
13631
|
-
profileName: argv.profile
|
|
13632
|
-
});
|
|
13633
|
-
let result;
|
|
13634
|
-
try {
|
|
13635
|
-
result = await apiClient.agentBulkUpdateSheets(input);
|
|
13636
|
-
} catch (error) {
|
|
13637
|
-
throw mapApiErrorToCliError(error);
|
|
13638
|
-
}
|
|
13639
|
-
context.output.print(
|
|
13640
|
-
{
|
|
13641
|
-
request: {
|
|
13642
|
-
...input,
|
|
13643
|
-
organizationId: organizationId ?? null
|
|
13644
|
-
},
|
|
13645
|
-
...result
|
|
13646
|
-
},
|
|
13647
|
-
(output) => formatBulkUpdateSheetsOutput(output),
|
|
13648
|
-
{ command: "sheet bulk-update" }
|
|
13649
|
-
);
|
|
15075
|
+
() => {
|
|
13650
15076
|
}
|
|
13651
15077
|
).command(
|
|
13652
15078
|
"import [sources..]",
|
|
@@ -14036,84 +15462,6 @@ var registerSheetCommands = (sheetYargs, context) => {
|
|
|
14036
15462
|
).demandCommand(1).strict();
|
|
14037
15463
|
};
|
|
14038
15464
|
|
|
14039
|
-
// src/lib/output.ts
|
|
14040
|
-
var cliJsonSchemaVersion = "v1";
|
|
14041
|
-
var ensureTrailingNewline = (value) => {
|
|
14042
|
-
return value.endsWith("\n") ? value : `${value}
|
|
14043
|
-
`;
|
|
14044
|
-
};
|
|
14045
|
-
var writeLine = (writer, value) => {
|
|
14046
|
-
writer.write(ensureTrailingNewline(value));
|
|
14047
|
-
};
|
|
14048
|
-
var createOutput = ({
|
|
14049
|
-
json,
|
|
14050
|
-
stdout,
|
|
14051
|
-
stderr
|
|
14052
|
-
}) => {
|
|
14053
|
-
let warnings = [];
|
|
14054
|
-
const consumeWarnings = () => {
|
|
14055
|
-
const nextWarnings = warnings;
|
|
14056
|
-
warnings = [];
|
|
14057
|
-
return nextWarnings;
|
|
14058
|
-
};
|
|
14059
|
-
return {
|
|
14060
|
-
json,
|
|
14061
|
-
print: (value, humanFormatter, options) => {
|
|
14062
|
-
if (json) {
|
|
14063
|
-
writeLine(
|
|
14064
|
-
stdout,
|
|
14065
|
-
JSON.stringify(
|
|
14066
|
-
{
|
|
14067
|
-
schemaVersion: cliJsonSchemaVersion,
|
|
14068
|
-
command: options.command,
|
|
14069
|
-
ok: options.ok ?? true,
|
|
14070
|
-
result: value,
|
|
14071
|
-
...options.error ? { error: options.error } : {},
|
|
14072
|
-
warnings: consumeWarnings()
|
|
14073
|
-
},
|
|
14074
|
-
null,
|
|
14075
|
-
2
|
|
14076
|
-
)
|
|
14077
|
-
);
|
|
14078
|
-
return;
|
|
14079
|
-
}
|
|
14080
|
-
const message = typeof humanFormatter === "function" ? humanFormatter(value) : humanFormatter;
|
|
14081
|
-
writeLine(stdout, message);
|
|
14082
|
-
},
|
|
14083
|
-
printError: ({ command, error, result }) => {
|
|
14084
|
-
if (json) {
|
|
14085
|
-
writeLine(
|
|
14086
|
-
stdout,
|
|
14087
|
-
JSON.stringify(
|
|
14088
|
-
{
|
|
14089
|
-
schemaVersion: cliJsonSchemaVersion,
|
|
14090
|
-
command,
|
|
14091
|
-
ok: false,
|
|
14092
|
-
...result === void 0 ? {} : { result },
|
|
14093
|
-
error,
|
|
14094
|
-
warnings: consumeWarnings()
|
|
14095
|
-
},
|
|
14096
|
-
null,
|
|
14097
|
-
2
|
|
14098
|
-
)
|
|
14099
|
-
);
|
|
14100
|
-
return;
|
|
14101
|
-
}
|
|
14102
|
-
writeLine(stderr, formatSerializableCliErrorForHuman(error));
|
|
14103
|
-
},
|
|
14104
|
-
info: (message) => {
|
|
14105
|
-
if (json) {
|
|
14106
|
-
warnings.push(message);
|
|
14107
|
-
return;
|
|
14108
|
-
}
|
|
14109
|
-
writeLine(stderr, message);
|
|
14110
|
-
},
|
|
14111
|
-
error: (message) => {
|
|
14112
|
-
writeLine(stderr, message);
|
|
14113
|
-
}
|
|
14114
|
-
};
|
|
14115
|
-
};
|
|
14116
|
-
|
|
14117
15465
|
// src/lib/prompt-secret.ts
|
|
14118
15466
|
import { createInterface } from "node:readline/promises";
|
|
14119
15467
|
import { Writable } from "node:stream";
|
|
@@ -14151,11 +15499,13 @@ var createPromptSecret = ({
|
|
|
14151
15499
|
};
|
|
14152
15500
|
|
|
14153
15501
|
// src/bin/chalksurf.ts
|
|
15502
|
+
var getHelpWidth = (stdout) => stdout.columns === void 0 ? null : Math.min(120, Math.max(80, stdout.columns));
|
|
14154
15503
|
var packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
14155
15504
|
var createCli = ({
|
|
14156
15505
|
cwd,
|
|
14157
15506
|
configPath,
|
|
14158
15507
|
env,
|
|
15508
|
+
helpWidth,
|
|
14159
15509
|
now,
|
|
14160
15510
|
output,
|
|
14161
15511
|
sleep,
|
|
@@ -14172,7 +15522,7 @@ var createCli = ({
|
|
|
14172
15522
|
sleep,
|
|
14173
15523
|
stdin
|
|
14174
15524
|
};
|
|
14175
|
-
return yargs().scriptName("chalksurf").usage("$0 <command> [options]").version(false).option("json", {
|
|
15525
|
+
return yargs().wrap(helpWidth).scriptName("chalksurf").usage("$0 <command> [options]").version(false).option("json", {
|
|
14176
15526
|
type: "boolean",
|
|
14177
15527
|
default: false,
|
|
14178
15528
|
describe: "Write machine-readable JSON to stdout"
|
|
@@ -14216,6 +15566,12 @@ var createCli = ({
|
|
|
14216
15566
|
(exerciseYargs) => registerExerciseCommands(exerciseYargs, commandContext),
|
|
14217
15567
|
() => {
|
|
14218
15568
|
}
|
|
15569
|
+
).command(
|
|
15570
|
+
"quality-issue <subcommand>",
|
|
15571
|
+
"Exercise quality issue review commands",
|
|
15572
|
+
(qualityIssueYargs) => registerQualityIssueCommands(qualityIssueYargs, commandContext),
|
|
15573
|
+
() => {
|
|
15574
|
+
}
|
|
14219
15575
|
).command(
|
|
14220
15576
|
"feedback <subcommand>",
|
|
14221
15577
|
"Feedback commands",
|
|
@@ -14255,7 +15611,7 @@ var parseCliWithCapturedOutput = async ({
|
|
|
14255
15611
|
argv,
|
|
14256
15612
|
stdout
|
|
14257
15613
|
}) => {
|
|
14258
|
-
await new Promise((
|
|
15614
|
+
await new Promise((resolve8, reject) => {
|
|
14259
15615
|
cli.parse(argv, (error, _parsedArgv, output) => {
|
|
14260
15616
|
if (output) {
|
|
14261
15617
|
stdout.write(output);
|
|
@@ -14264,14 +15620,24 @@ var parseCliWithCapturedOutput = async ({
|
|
|
14264
15620
|
reject(error);
|
|
14265
15621
|
return;
|
|
14266
15622
|
}
|
|
14267
|
-
|
|
15623
|
+
resolve8();
|
|
14268
15624
|
});
|
|
14269
15625
|
});
|
|
14270
15626
|
};
|
|
14271
15627
|
var hasFlag = (argv, flags) => {
|
|
14272
15628
|
return argv.some((argument) => flags.includes(argument));
|
|
14273
15629
|
};
|
|
14274
|
-
var topLevelCommands = /* @__PURE__ */ new Set([
|
|
15630
|
+
var topLevelCommands = /* @__PURE__ */ new Set([
|
|
15631
|
+
"auth",
|
|
15632
|
+
"org",
|
|
15633
|
+
"profile",
|
|
15634
|
+
"exercise",
|
|
15635
|
+
"quality-issue",
|
|
15636
|
+
"feedback",
|
|
15637
|
+
"figure",
|
|
15638
|
+
"sheet",
|
|
15639
|
+
"job"
|
|
15640
|
+
]);
|
|
14275
15641
|
var hasTopLevelCommand = (argv) => {
|
|
14276
15642
|
return argv.some((argument) => topLevelCommands.has(argument));
|
|
14277
15643
|
};
|
|
@@ -14281,7 +15647,17 @@ var resolveCommandName = (argv) => {
|
|
|
14281
15647
|
if (!topLevelCommands.has(argument)) {
|
|
14282
15648
|
continue;
|
|
14283
15649
|
}
|
|
14284
|
-
const
|
|
15650
|
+
const commandArguments = argv.slice(index + 1);
|
|
15651
|
+
const subcommandIndex = commandArguments.findIndex(
|
|
15652
|
+
(candidate) => candidate.length > 0 && !candidate.startsWith("-") && !candidate.startsWith("http")
|
|
15653
|
+
);
|
|
15654
|
+
const subcommand = commandArguments[subcommandIndex];
|
|
15655
|
+
if (argument === "sheet" && subcommand === "bulk-update") {
|
|
15656
|
+
const nestedSubcommand = commandArguments.slice(subcommandIndex + 1).find((candidate) => candidate.length > 0 && !candidate.startsWith("-"));
|
|
15657
|
+
if (nestedSubcommand === "verify") {
|
|
15658
|
+
return "sheet bulk-update verify";
|
|
15659
|
+
}
|
|
15660
|
+
}
|
|
14285
15661
|
return subcommand ? `${argument} ${subcommand}` : argument;
|
|
14286
15662
|
}
|
|
14287
15663
|
if (hasFlag(argv, ["--version", "-v"])) {
|
|
@@ -14295,7 +15671,7 @@ var runCli = async ({
|
|
|
14295
15671
|
configPath,
|
|
14296
15672
|
env = process.env,
|
|
14297
15673
|
nowImpl = Date.now,
|
|
14298
|
-
sleepImpl = (milliseconds) => new Promise((
|
|
15674
|
+
sleepImpl = (milliseconds) => new Promise((resolve8) => setTimeout(resolve8, milliseconds)),
|
|
14299
15675
|
stdin = process.stdin,
|
|
14300
15676
|
stdout = process.stdout,
|
|
14301
15677
|
stderr = process.stderr
|
|
@@ -14310,6 +15686,7 @@ var runCli = async ({
|
|
|
14310
15686
|
cwd,
|
|
14311
15687
|
configPath,
|
|
14312
15688
|
env,
|
|
15689
|
+
helpWidth: getHelpWidth(stdout),
|
|
14313
15690
|
now: nowImpl,
|
|
14314
15691
|
output,
|
|
14315
15692
|
sleep: sleepImpl,
|