recess-cli 2.10.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/args.js +1 -0
- package/dist/cli.js +42 -4
- package/dist/command-schema.js +11 -0
- package/dist/commands/safe-staff-operations.js +183 -0
- package/dist/help.js +13 -1
- package/package.json +1 -1
- package/skill/recess-cli/SKILL.md +25 -26
- package/skill/recess-cli/agents/version.json +2 -2
package/README.md
CHANGED
|
@@ -9,6 +9,13 @@ authenticated Village home building. It uses the web-server OpenAPI document, au
|
|
|
9
9
|
through Recess SSO, emits stable JSON, and refuses live writes until the exact command is rerun with
|
|
10
10
|
`--confirm` plus the preview's operation key after human approval.
|
|
11
11
|
|
|
12
|
+
## Version 3 changes
|
|
13
|
+
|
|
14
|
+
`goals list` now shows ACTIVE and PAUSED goals by default. Add `--include-archived` to
|
|
15
|
+
include COMPLETED goals (both archived and rewarded completions), and `--query` to search.
|
|
16
|
+
Staff gain `cohorts schedule` and versioned `memories` file commands. Deploy their backend
|
|
17
|
+
and database support before publishing this CLI; writes require a server preview.
|
|
18
|
+
|
|
12
19
|
## Install (no checkout needed)
|
|
13
20
|
|
|
14
21
|
Published to npm as [`recess-cli`](https://www.npmjs.com/package/recess-cli). On any machine with Node 20+:
|
package/dist/args.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { runAppsCommand } from "./commands/apps.js";
|
|
|
12
12
|
import { runChatLogsCommand } from "./commands/chat-logs.js";
|
|
13
13
|
import { runMasteryCommand } from "./commands/mastery.js";
|
|
14
14
|
import { runOnboardingCommand } from "./commands/onboarding.js";
|
|
15
|
+
import { runScheduleCommand, runMemoryFilesCommand, } from "./commands/safe-staff-operations.js";
|
|
15
16
|
import { runSchoolCommand } from "./commands/school.js";
|
|
16
17
|
import { runVillageEventsCommand } from "./commands/village-events.js";
|
|
17
18
|
import { assertChoice, flagIdList, positional, readJsonFile, readJsonValue, } from "./commands/shared.js";
|
|
@@ -252,7 +253,10 @@ async function localWriteCommand(parsed, preview, execute) {
|
|
|
252
253
|
action: preview.action,
|
|
253
254
|
fingerprint,
|
|
254
255
|
target: preview.target,
|
|
255
|
-
...(preview.action.startsWith("mastery.")
|
|
256
|
+
...(preview.action.startsWith("mastery.") ||
|
|
257
|
+
preview.request.attendanceTakenAt
|
|
258
|
+
? { preview }
|
|
259
|
+
: {}),
|
|
256
260
|
});
|
|
257
261
|
requireConfirmation(false, boundPreview);
|
|
258
262
|
}
|
|
@@ -657,7 +661,12 @@ async function previewBoundWrite(parsed, preview, execute) {
|
|
|
657
661
|
const result = await withIdempotencyContext(context, execute);
|
|
658
662
|
await appendJobEvent({
|
|
659
663
|
jobId: context.operationKey,
|
|
660
|
-
status: "
|
|
664
|
+
status: typeof result === "object" &&
|
|
665
|
+
result !== null &&
|
|
666
|
+
"outcome" in result &&
|
|
667
|
+
result.outcome === "outcome_unknown"
|
|
668
|
+
? "unknown"
|
|
669
|
+
: "completed",
|
|
661
670
|
timestamp: new Date().toISOString(),
|
|
662
671
|
action: preview.action,
|
|
663
672
|
fingerprint: context.fingerprint,
|
|
@@ -2677,6 +2686,14 @@ export async function executeRecessCommand(argv, options = {}) {
|
|
|
2677
2686
|
if (noun === "school") {
|
|
2678
2687
|
return runSchoolCommand({ parsed, api, writeCommand });
|
|
2679
2688
|
}
|
|
2689
|
+
if (noun === "cohorts" && verb === "schedule")
|
|
2690
|
+
return runScheduleCommand({ parsed, api, writeCommand: previewBoundWrite });
|
|
2691
|
+
if (noun === "memories" && verb !== "context" && verb !== "log")
|
|
2692
|
+
return runMemoryFilesCommand({
|
|
2693
|
+
parsed,
|
|
2694
|
+
api,
|
|
2695
|
+
writeCommand: previewBoundWrite,
|
|
2696
|
+
});
|
|
2680
2697
|
if (noun === "cohorts" && verb === "search") {
|
|
2681
2698
|
const search = parsed.positionals.slice(2).join(" ").trim();
|
|
2682
2699
|
if (!search)
|
|
@@ -3159,7 +3176,16 @@ export async function executeRecessCommand(argv, options = {}) {
|
|
|
3159
3176
|
excused: true,
|
|
3160
3177
|
})),
|
|
3161
3178
|
];
|
|
3162
|
-
const
|
|
3179
|
+
const operationKey = flagString(parsed, "operation-key");
|
|
3180
|
+
const stored = operationKey ? await getJob(operationKey) : undefined;
|
|
3181
|
+
const preparedAt = stored?.history.find((event) => event.preview?.request.attendanceTakenAt)?.preview?.request.attendanceTakenAt;
|
|
3182
|
+
if (hasFlag(parsed, "confirm") && typeof preparedAt !== "string") {
|
|
3183
|
+
throw new CliError("confirmation_required", "The saved attendance preview is unavailable. Request a new preview before confirming.");
|
|
3184
|
+
}
|
|
3185
|
+
const body = {
|
|
3186
|
+
attendanceTakenAt: typeof preparedAt === "string" ? preparedAt : new Date().toISOString(),
|
|
3187
|
+
attendance,
|
|
3188
|
+
};
|
|
3163
3189
|
return writeCommand(parsed, {
|
|
3164
3190
|
action: "take attendance (non-excused absentees may trigger automatic 'we missed you' emails to parents)",
|
|
3165
3191
|
target: { eventId },
|
|
@@ -4048,9 +4074,21 @@ export async function executeRecessCommand(argv, options = {}) {
|
|
|
4048
4074
|
}
|
|
4049
4075
|
if (verb === "list") {
|
|
4050
4076
|
const userId = flagString(parsed, "student", { required: true });
|
|
4051
|
-
|
|
4077
|
+
const result = unwrap(await api.client.GET("/tutor/browser/students/{userId}/goals/", {
|
|
4052
4078
|
params: { path: { userId } },
|
|
4053
4079
|
}));
|
|
4080
|
+
const query = flagString(parsed, "query")?.toLocaleLowerCase();
|
|
4081
|
+
return {
|
|
4082
|
+
...result,
|
|
4083
|
+
goals: result.goals.filter((goal) => (goal.status === "ACTIVE" ||
|
|
4084
|
+
goal.status === "PAUSED" ||
|
|
4085
|
+
(hasFlag(parsed, "include-archived") &&
|
|
4086
|
+
goal.status === "COMPLETED")) &&
|
|
4087
|
+
(!query ||
|
|
4088
|
+
`${goal.title}\n${goal.description ?? ""}`
|
|
4089
|
+
.toLocaleLowerCase()
|
|
4090
|
+
.includes(query))),
|
|
4091
|
+
};
|
|
4054
4092
|
}
|
|
4055
4093
|
if (verb === "create") {
|
|
4056
4094
|
const requestedStudentId = flagString(parsed, "student");
|
package/dist/command-schema.js
CHANGED
|
@@ -22,6 +22,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
22
22
|
"immediate",
|
|
23
23
|
"keep-tokens",
|
|
24
24
|
"include-deleted",
|
|
25
|
+
"include-archived",
|
|
25
26
|
"json",
|
|
26
27
|
"mirrored",
|
|
27
28
|
"no-collision",
|
|
@@ -180,6 +181,16 @@ const FAMILY_AI_COMMANDS = new Set([
|
|
|
180
181
|
"todos edit",
|
|
181
182
|
]);
|
|
182
183
|
const STAFF_COMMANDS = new Set([
|
|
184
|
+
"cohorts schedule get",
|
|
185
|
+
"cohorts schedule set",
|
|
186
|
+
"cohorts schedule changes",
|
|
187
|
+
"cohorts schedule change",
|
|
188
|
+
"memories files",
|
|
189
|
+
"memories read",
|
|
190
|
+
"memories edit",
|
|
191
|
+
"memories history",
|
|
192
|
+
"memories revision",
|
|
193
|
+
"memories restore",
|
|
183
194
|
"apps assign",
|
|
184
195
|
"apps list",
|
|
185
196
|
"apps preview",
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { unwrap } from "../api.js";
|
|
5
|
+
import { flagString, hasFlag } from "../args.js";
|
|
6
|
+
import { CliError } from "../errors.js";
|
|
7
|
+
import { appendJobEvent } from "../jobs.js";
|
|
8
|
+
import { assertChoice, positional } from "./shared.js";
|
|
9
|
+
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
10
|
+
function recoveryKey(ctx) {
|
|
11
|
+
if (!hasFlag(ctx.parsed, "confirm"))
|
|
12
|
+
return undefined;
|
|
13
|
+
const key = flagString(ctx.parsed, "operation-key");
|
|
14
|
+
const token = flagString(ctx.parsed, "approval-token");
|
|
15
|
+
if (key && (!token || !key.endsWith(`_${token}`)))
|
|
16
|
+
throw new CliError("approval_mismatch", "Recovery requires the original operation key and approval token.");
|
|
17
|
+
return key;
|
|
18
|
+
}
|
|
19
|
+
async function recovered(ctx, row, expectedInputHash) {
|
|
20
|
+
if (row.inputHash !== expectedInputHash)
|
|
21
|
+
throw new CliError("approval_mismatch", "This operation key belongs to different input. No operation was retried.");
|
|
22
|
+
await appendJobEvent({
|
|
23
|
+
jobId: row.operationKey,
|
|
24
|
+
status: row.outcome === "outcome_unknown" ? "unknown" : "completed",
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
action: "recover staff operation",
|
|
27
|
+
fingerprint: flagString(ctx.parsed, "approval-token"),
|
|
28
|
+
target: { apiOrigin: ctx.api.config.apiOrigin },
|
|
29
|
+
result: row,
|
|
30
|
+
});
|
|
31
|
+
return row;
|
|
32
|
+
}
|
|
33
|
+
export async function runScheduleCommand(ctx) {
|
|
34
|
+
const { api, parsed, writeCommand } = ctx;
|
|
35
|
+
const action = positional(parsed, 2, "schedule action");
|
|
36
|
+
const id = positional(parsed, 3, "cohort or change ID");
|
|
37
|
+
if (action === "change")
|
|
38
|
+
return unwrap(await api.client.GET("/admin/cohorts/schedule-changes/{changeId}", {
|
|
39
|
+
params: { path: { changeId: id } },
|
|
40
|
+
}));
|
|
41
|
+
const params = { path: { id } };
|
|
42
|
+
if (action === "get")
|
|
43
|
+
return unwrap(await api.client.GET("/admin/cohorts/{id}/schedule/", { params }));
|
|
44
|
+
if (action === "changes")
|
|
45
|
+
return unwrap(await api.client.GET("/admin/cohorts/{id}/schedule/changes", {
|
|
46
|
+
params: { ...params, query: { cursor: flagString(parsed, "cursor") } },
|
|
47
|
+
}));
|
|
48
|
+
if (action !== "set")
|
|
49
|
+
throw new CliError("invalid_arguments", "Use cohorts schedule get|set|changes|change.");
|
|
50
|
+
const input = {
|
|
51
|
+
days: flagString(parsed, "days", { required: true })
|
|
52
|
+
.split(",")
|
|
53
|
+
.map((d) => assertChoice(d.trim().toUpperCase(), ["MO", "TU", "WE", "TH", "FR", "SA", "SU"], "weekday")),
|
|
54
|
+
time: flagString(parsed, "time", { required: true }),
|
|
55
|
+
timezone: flagString(parsed, "timezone"),
|
|
56
|
+
effectiveDate: flagString(parsed, "effective-date", { required: true }),
|
|
57
|
+
notify: assertChoice(flagString(parsed, "notify", { required: true }), ["none", "parents"], "notify"),
|
|
58
|
+
reason: flagString(parsed, "reason", { required: true }).trim(),
|
|
59
|
+
};
|
|
60
|
+
const key = recoveryKey(ctx);
|
|
61
|
+
if (key) {
|
|
62
|
+
const history = unwrap(await api.client.GET("/admin/cohorts/{id}/schedule/changes", {
|
|
63
|
+
params: { ...params, query: { operationKey: key } },
|
|
64
|
+
}));
|
|
65
|
+
if (history.changes[0])
|
|
66
|
+
return recovered(ctx, history.changes[0], hash({ id, input }));
|
|
67
|
+
}
|
|
68
|
+
const capability = unwrap(await api.client.GET("/admin/cohorts/{id}/schedule/", { params }));
|
|
69
|
+
if (capability.capabilityVersion !== 1 || !capability.supported)
|
|
70
|
+
throw new CliError("unsupported_backend", capability.unsupportedReason ??
|
|
71
|
+
"Backend does not support safe schedule changes.");
|
|
72
|
+
const preview = unwrap(await api.client.POST("/admin/cohorts/{id}/schedule/preview", {
|
|
73
|
+
params,
|
|
74
|
+
body: input,
|
|
75
|
+
}));
|
|
76
|
+
return writeCommand(parsed, {
|
|
77
|
+
action: "change recurring cohort schedule",
|
|
78
|
+
target: { cohortId: id, apiOrigin: api.config.apiOrigin },
|
|
79
|
+
request: input,
|
|
80
|
+
details: preview,
|
|
81
|
+
}, async () => {
|
|
82
|
+
const result = unwrap(await api.client.POST("/admin/cohorts/{id}/schedule/apply", {
|
|
83
|
+
params,
|
|
84
|
+
body: {
|
|
85
|
+
...input,
|
|
86
|
+
operationKey: flagString(parsed, "operation-key", {
|
|
87
|
+
required: true,
|
|
88
|
+
}),
|
|
89
|
+
fingerprint: preview.fingerprint,
|
|
90
|
+
},
|
|
91
|
+
}));
|
|
92
|
+
return unwrap(await api.client.GET("/admin/cohorts/schedule-changes/{changeId}", {
|
|
93
|
+
params: { path: { changeId: result.id } },
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export async function runMemoryFilesCommand(ctx) {
|
|
98
|
+
const { parsed, api, writeCommand } = ctx;
|
|
99
|
+
const action = positional(parsed, 1, "memory action");
|
|
100
|
+
const studentId = flagString(parsed, "student", { required: true });
|
|
101
|
+
const params = { path: { studentId } };
|
|
102
|
+
if (action === "files")
|
|
103
|
+
return unwrap(await api.client.GET("/tutor/students/{studentId}/memory-files", {
|
|
104
|
+
params,
|
|
105
|
+
}));
|
|
106
|
+
const memoryPath = flagString(parsed, "path", { required: true });
|
|
107
|
+
if (action === "read")
|
|
108
|
+
return unwrap(await api.client.GET("/tutor/students/{studentId}/memory-files/read", {
|
|
109
|
+
params: { ...params, query: { path: memoryPath } },
|
|
110
|
+
}));
|
|
111
|
+
if (action === "history")
|
|
112
|
+
return unwrap(await api.client.GET("/tutor/students/{studentId}/memory-files/history", {
|
|
113
|
+
params: {
|
|
114
|
+
...params,
|
|
115
|
+
query: { path: memoryPath, cursor: flagString(parsed, "cursor") },
|
|
116
|
+
},
|
|
117
|
+
}));
|
|
118
|
+
if (action === "revision")
|
|
119
|
+
return unwrap(await api.client.GET("/tutor/students/{studentId}/memory-files/revision", {
|
|
120
|
+
params: {
|
|
121
|
+
...params,
|
|
122
|
+
query: {
|
|
123
|
+
path: memoryPath,
|
|
124
|
+
revision: flagString(parsed, "revision", { required: true }),
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}));
|
|
128
|
+
if (action !== "edit" && action !== "restore")
|
|
129
|
+
throw new CliError("invalid_arguments", "Use memories files|read|edit|history|revision|restore.");
|
|
130
|
+
let content;
|
|
131
|
+
let contentFile;
|
|
132
|
+
if (action === "edit") {
|
|
133
|
+
contentFile = path.resolve(flagString(parsed, "content-file", { required: true }));
|
|
134
|
+
const info = await fs.lstat(contentFile);
|
|
135
|
+
if (!info.isFile() ||
|
|
136
|
+
info.isSymbolicLink() ||
|
|
137
|
+
(await fs.realpath(contentFile)) !== contentFile)
|
|
138
|
+
throw new CliError("invalid_file", "Memory input must be a regular file without symlink components.");
|
|
139
|
+
content = new TextDecoder("utf-8", { fatal: true }).decode(await fs.readFile(contentFile));
|
|
140
|
+
}
|
|
141
|
+
const input = {
|
|
142
|
+
path: memoryPath,
|
|
143
|
+
content,
|
|
144
|
+
revision: action === "restore"
|
|
145
|
+
? flagString(parsed, "revision", { required: true })
|
|
146
|
+
: undefined,
|
|
147
|
+
action,
|
|
148
|
+
reason: flagString(parsed, "reason", { required: true }).trim(),
|
|
149
|
+
};
|
|
150
|
+
const key = recoveryKey(ctx);
|
|
151
|
+
if (key) {
|
|
152
|
+
const result = unwrap(await api.client.GET("/tutor/students/{studentId}/memory-files/operation", { params: { ...params, query: { operationKey: key } } }));
|
|
153
|
+
if (result.operation)
|
|
154
|
+
return recovered(ctx, result.operation, hash({ studentId, input }));
|
|
155
|
+
}
|
|
156
|
+
const preview = unwrap(await api.client.POST("/tutor/students/{studentId}/memory-files/preview", {
|
|
157
|
+
params,
|
|
158
|
+
body: input,
|
|
159
|
+
}));
|
|
160
|
+
if (preview.capabilityVersion !== 1 || preview.writeSupported !== true)
|
|
161
|
+
throw new CliError("unsupported_backend", preview.writeUnsupportedReason ??
|
|
162
|
+
"Backend does not support safe staff memory edits and restores.");
|
|
163
|
+
return writeCommand(parsed, {
|
|
164
|
+
action: `${action} student memory file`,
|
|
165
|
+
target: { studentId, path: memoryPath, apiOrigin: api.config.apiOrigin },
|
|
166
|
+
request: { ...input, contentFile },
|
|
167
|
+
details: {
|
|
168
|
+
...preview,
|
|
169
|
+
before: { ...preview.before, revision: undefined },
|
|
170
|
+
},
|
|
171
|
+
}, async () => unwrap(await api.client.POST("/tutor/students/{studentId}/memory-files/apply", {
|
|
172
|
+
params,
|
|
173
|
+
body: {
|
|
174
|
+
...input,
|
|
175
|
+
expectedHash: preview.before.contentSha,
|
|
176
|
+
fingerprint: preview.fingerprint,
|
|
177
|
+
operationKey: flagString(parsed, "operation-key", {
|
|
178
|
+
required: true,
|
|
179
|
+
}),
|
|
180
|
+
},
|
|
181
|
+
})));
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=safe-staff-operations.js.map
|
package/dist/help.js
CHANGED
|
@@ -143,6 +143,10 @@ Usage:
|
|
|
143
143
|
[--description TEXT] [--date YYYY-MM-DD] [--confirm]
|
|
144
144
|
recess [--json] payout items delete <item-id> [--confirm]
|
|
145
145
|
recess [--json] cohorts get <cohort-id> [--events-tab ACTIVE|ENDED|CANCELED|ARCHIVED]
|
|
146
|
+
recess [--json] cohorts schedule get <cohort-id>
|
|
147
|
+
recess [--json] cohorts schedule set <cohort-id> --days <MO,WE> --time <09:30> [--timezone <zone>] --effective-date <YYYY-MM-DD> --notify none|parents [--confirm] [--approval-token <token>]
|
|
148
|
+
recess [--json] cohorts schedule changes <cohort-id> [--cursor <cursor>]
|
|
149
|
+
recess [--json] cohorts schedule change <change-id>
|
|
146
150
|
recess [--json] cohorts parent-emails <cohort-id>
|
|
147
151
|
recess [--json] cohorts end <cohort-id> [--cancel-subscriptions] [--confirm]
|
|
148
152
|
recess [--json] cohorts pause-billing <cohort-id> --weeks 1..6 [--confirm]
|
|
@@ -360,7 +364,7 @@ Usage:
|
|
|
360
364
|
[--dry-run] [--confirm --approval-token TOKEN]
|
|
361
365
|
recess [--json] goal-templates apply-starter <template-id> --student <kid-id>
|
|
362
366
|
[--answers-file <path>] [--confirm]
|
|
363
|
-
recess [--json] goals list --student <kid-id>
|
|
367
|
+
recess [--json] goals list --student <kid-id> [--query <text>] [--include-archived]
|
|
364
368
|
recess [--json] goals create [--student <kid-id>] --title TEXT
|
|
365
369
|
(--description TEXT | --description-file <path>) [--target-date <iso>]
|
|
366
370
|
[--schedule TEXT] [(--draft <draft-slug> | --source-dir <local-checkout>)
|
|
@@ -395,6 +399,14 @@ Usage:
|
|
|
395
399
|
recess [--json] todos generate-learning-analysis <todo-id> [--confirm]
|
|
396
400
|
recess [--json] todos generate-applet <todo-id> --student <kid-id>
|
|
397
401
|
[--due-date YYYY-MM-DD] [--confirm --approval-token TOKEN]
|
|
402
|
+
recess [--json] memories files --student <kid-id>
|
|
403
|
+
recess [--json] memories read --student <kid-id> --path <path>
|
|
404
|
+
recess [--json] memories edit --student <kid-id> --path <path> --content-file <file> [--confirm] [--approval-token <token>]
|
|
405
|
+
recess [--json] memories history --student <kid-id> --path <path> [--cursor <cursor>]
|
|
406
|
+
recess [--json] memories revision --student <kid-id> --path <path> --revision <revision>
|
|
407
|
+
recess [--json] memories restore --student <kid-id> --path <path> --revision <revision> [--confirm] [--approval-token <token>]
|
|
408
|
+
Staff memory edit/restore publication is disabled pending recoverable immutable history.
|
|
409
|
+
Reads, history, and recovery of existing operations remain available.
|
|
398
410
|
recess [--json] memories context --student <kid-id>
|
|
399
411
|
recess [--json] memories log --student <kid-id> --date YYYY-MM-DD
|
|
400
412
|
recess [--json] rocky get --student <kid-id>
|
package/package.json
CHANGED
|
@@ -72,18 +72,21 @@ Any changed target, payload, local file, server revision, amount, recipient, or
|
|
|
72
72
|
|
|
73
73
|
### Hosted MCP transport
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
Use the connector's individually named tools and their discovered argument schemas, for example
|
|
76
|
+
`recess_students_todos({student:"…",reason:"…"})`. Discovery replaces CLI help. OAuth supplies the
|
|
77
|
+
live user and consented scopes; never ask for cookies, local paths or profiles.
|
|
78
|
+
|
|
79
|
+
Preview writes with named arguments and `reason`, show `error.details.preview`, then confirm
|
|
80
|
+
through the same tool with **only** `{confirm:true,operationKey:"…"}`. Stored arguments and source
|
|
81
|
+
bytes are restored server-side. Changed input or consequences require a new preview. Retain the
|
|
82
|
+
key on interruption and inspect `recess_jobs_get`; never start a replacement for an uncertain
|
|
83
|
+
write. `confirmation_required` is normal control flow in the returned `structuredContent`.
|
|
84
|
+
|
|
85
|
+
Supply JSON/text directly, workspaces as file bundles with `baseRevision`, and binaries as
|
|
86
|
+
`{name,content,encoding}` or scoped `{uploadHandle}` references created by `recess_upload_create`.
|
|
87
|
+
Upload bytes to its signed URL(s); size/hash verification happens before use. Download returned
|
|
88
|
+
bundles/authorized URLs. `/mcp/legacy` temporarily retains `recess_exec({command,input?})` for
|
|
89
|
+
30 days after rollout. Refresh connector metadata and consent for expanded capabilities.
|
|
87
90
|
|
|
88
91
|
## Building Studio apps (`recess apps`)
|
|
89
92
|
|
|
@@ -106,20 +109,16 @@ prompts a model for them.
|
|
|
106
109
|
- `recess --json apps standards "<words or notation>"` finds the CCSS standard for `manifest.json`
|
|
107
110
|
(`"grade 4 adding fractions"` → `4.NF.B.3a`…); guides rarely know the codes, look them up.
|
|
108
111
|
|
|
109
|
-
Hosted authoring keeps those same contracts
|
|
110
|
-
|
|
111
|
-
- `
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Preview/confirm `apps publish <project-id> --version <n>`, then poll its build. Publication sends
|
|
120
|
-
no source and reviews exactly that validated version.
|
|
121
|
-
- After publication, preview/confirm `apps assign <project-id> --student <id> [--due YYYY-MM-DD]`.
|
|
122
|
-
Assignment is intentionally separate from hosted publish.
|
|
112
|
+
Hosted authoring keeps those same contracts with named tools:
|
|
113
|
+
|
|
114
|
+
- `recess_apps_scaffold` returns instructions, editable files, and an optional `forTodo` brief.
|
|
115
|
+
- Supply `appBundle` (files, contract, manifest) to `recess_apps_validate`; preview and confirm,
|
|
116
|
+
then keep its project/version/build IDs and poll `recess_apps_status`.
|
|
117
|
+
- Before editing an existing project, call `recess_apps_pull` and preserve its `baseVersion`.
|
|
118
|
+
- `recess_apps_preview` previews creation of a temporary capability URL. Preview and confirm
|
|
119
|
+
`recess_apps_publish` for the exact project/version; it reviews that validated version.
|
|
120
|
+
- Use `assign` and optional `due` on publication for recoverable publish-and-assign, or use
|
|
121
|
+
`recess_apps_assign` separately. A pending build retains the same operation key for retry.
|
|
123
122
|
|
|
124
123
|
Two ways in. If the guide names a kid or a todo, pull the analysis first and build against it;
|
|
125
124
|
otherwise build from their description:
|