recess-cli 2.9.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 +46 -4
- package/dist/command-schema.js +14 -0
- package/dist/commands/chat-logs.js +43 -0
- package/dist/commands/safe-staff-operations.js +183 -0
- package/dist/help.js +16 -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
|
@@ -9,8 +9,10 @@ import { clearStoredSession, deleteProfile, listProfiles, resolveConfig, savePro
|
|
|
9
9
|
import { agentContext, buildCommandSchema, findCommandSchema, remoteCommands, scopedHelp, validateInvocation, } from "./command-schema.js";
|
|
10
10
|
import { runApplicationsCommand } from "./commands/applications.js";
|
|
11
11
|
import { runAppsCommand } from "./commands/apps.js";
|
|
12
|
+
import { runChatLogsCommand } from "./commands/chat-logs.js";
|
|
12
13
|
import { runMasteryCommand } from "./commands/mastery.js";
|
|
13
14
|
import { runOnboardingCommand } from "./commands/onboarding.js";
|
|
15
|
+
import { runScheduleCommand, runMemoryFilesCommand, } from "./commands/safe-staff-operations.js";
|
|
14
16
|
import { runSchoolCommand } from "./commands/school.js";
|
|
15
17
|
import { runVillageEventsCommand } from "./commands/village-events.js";
|
|
16
18
|
import { assertChoice, flagIdList, positional, readJsonFile, readJsonValue, } from "./commands/shared.js";
|
|
@@ -251,7 +253,10 @@ async function localWriteCommand(parsed, preview, execute) {
|
|
|
251
253
|
action: preview.action,
|
|
252
254
|
fingerprint,
|
|
253
255
|
target: preview.target,
|
|
254
|
-
...(preview.action.startsWith("mastery.")
|
|
256
|
+
...(preview.action.startsWith("mastery.") ||
|
|
257
|
+
preview.request.attendanceTakenAt
|
|
258
|
+
? { preview }
|
|
259
|
+
: {}),
|
|
255
260
|
});
|
|
256
261
|
requireConfirmation(false, boundPreview);
|
|
257
262
|
}
|
|
@@ -656,7 +661,12 @@ async function previewBoundWrite(parsed, preview, execute) {
|
|
|
656
661
|
const result = await withIdempotencyContext(context, execute);
|
|
657
662
|
await appendJobEvent({
|
|
658
663
|
jobId: context.operationKey,
|
|
659
|
-
status: "
|
|
664
|
+
status: typeof result === "object" &&
|
|
665
|
+
result !== null &&
|
|
666
|
+
"outcome" in result &&
|
|
667
|
+
result.outcome === "outcome_unknown"
|
|
668
|
+
? "unknown"
|
|
669
|
+
: "completed",
|
|
660
670
|
timestamp: new Date().toISOString(),
|
|
661
671
|
action: preview.action,
|
|
662
672
|
fingerprint: context.fingerprint,
|
|
@@ -2676,6 +2686,14 @@ export async function executeRecessCommand(argv, options = {}) {
|
|
|
2676
2686
|
if (noun === "school") {
|
|
2677
2687
|
return runSchoolCommand({ parsed, api, writeCommand });
|
|
2678
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
|
+
});
|
|
2679
2697
|
if (noun === "cohorts" && verb === "search") {
|
|
2680
2698
|
const search = parsed.positionals.slice(2).join(" ").trim();
|
|
2681
2699
|
if (!search)
|
|
@@ -3158,7 +3176,16 @@ export async function executeRecessCommand(argv, options = {}) {
|
|
|
3158
3176
|
excused: true,
|
|
3159
3177
|
})),
|
|
3160
3178
|
];
|
|
3161
|
-
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
|
+
};
|
|
3162
3189
|
return writeCommand(parsed, {
|
|
3163
3190
|
action: "take attendance (non-excused absentees may trigger automatic 'we missed you' emails to parents)",
|
|
3164
3191
|
target: { eventId },
|
|
@@ -4047,9 +4074,21 @@ export async function executeRecessCommand(argv, options = {}) {
|
|
|
4047
4074
|
}
|
|
4048
4075
|
if (verb === "list") {
|
|
4049
4076
|
const userId = flagString(parsed, "student", { required: true });
|
|
4050
|
-
|
|
4077
|
+
const result = unwrap(await api.client.GET("/tutor/browser/students/{userId}/goals/", {
|
|
4051
4078
|
params: { path: { userId } },
|
|
4052
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
|
+
};
|
|
4053
4092
|
}
|
|
4054
4093
|
if (verb === "create") {
|
|
4055
4094
|
const requestedStudentId = flagString(parsed, "student");
|
|
@@ -4400,6 +4439,9 @@ export async function executeRecessCommand(argv, options = {}) {
|
|
|
4400
4439
|
}
|
|
4401
4440
|
throw new CliError("invalid_arguments", "Use goals list|create|edit|delete|restore|archive|unarchive|complete|undo-completion|queue|files|pdf.");
|
|
4402
4441
|
}
|
|
4442
|
+
if (noun === "chat-logs") {
|
|
4443
|
+
return runChatLogsCommand({ parsed, api, writeCommand });
|
|
4444
|
+
}
|
|
4403
4445
|
if (noun === "students") {
|
|
4404
4446
|
if (verb === "list") {
|
|
4405
4447
|
const requestedScope = flagString(parsed, "scope");
|
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",
|
|
@@ -138,6 +139,9 @@ const AUTHENTICATED_COMMAND_PREFIXES = [
|
|
|
138
139
|
"village render",
|
|
139
140
|
];
|
|
140
141
|
const FAMILY_AI_COMMANDS = new Set([
|
|
142
|
+
"chat-logs by-todo",
|
|
143
|
+
"chat-logs get",
|
|
144
|
+
"chat-logs list",
|
|
141
145
|
"content-library search",
|
|
142
146
|
"goal-templates apply",
|
|
143
147
|
"goal-templates apply-starter",
|
|
@@ -177,6 +181,16 @@ const FAMILY_AI_COMMANDS = new Set([
|
|
|
177
181
|
"todos edit",
|
|
178
182
|
]);
|
|
179
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",
|
|
180
194
|
"apps assign",
|
|
181
195
|
"apps list",
|
|
182
196
|
"apps preview",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { unwrap } from "../api.js";
|
|
2
|
+
import { flagNumber, flagString } from "../args.js";
|
|
3
|
+
import { CliError } from "../errors.js";
|
|
4
|
+
import { positional } from "./shared.js";
|
|
5
|
+
export async function runChatLogsCommand({ parsed, api, }) {
|
|
6
|
+
const verb = parsed.positionals[1];
|
|
7
|
+
if (verb === "list") {
|
|
8
|
+
const limit = flagNumber(parsed, "limit");
|
|
9
|
+
if (limit !== undefined &&
|
|
10
|
+
(!Number.isInteger(limit) || limit < 1 || limit > 500)) {
|
|
11
|
+
throw new CliError("invalid_arguments", "--limit must be an integer from 1 to 500.");
|
|
12
|
+
}
|
|
13
|
+
return unwrap(await api.client.GET("/ai/chat-logs", {
|
|
14
|
+
params: {
|
|
15
|
+
query: {
|
|
16
|
+
studentId: flagString(parsed, "student", { required: true }),
|
|
17
|
+
limit,
|
|
18
|
+
cursor: flagString(parsed, "cursor"),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
const query = verb === "get"
|
|
24
|
+
? { conversationId: positional(parsed, 2, "conversation ID") }
|
|
25
|
+
: { todoId: positional(parsed, 2, "todo ID") };
|
|
26
|
+
const messages = [];
|
|
27
|
+
let cursor;
|
|
28
|
+
const seenCursors = new Set();
|
|
29
|
+
do {
|
|
30
|
+
const page = unwrap(await api.client.GET("/ai/chat-logs/messages", {
|
|
31
|
+
params: { query: { ...query, limit: 500, cursor } },
|
|
32
|
+
}));
|
|
33
|
+
messages.push(...page.messages);
|
|
34
|
+
if (!page.nextCursor)
|
|
35
|
+
return { ...page, messages, complete: true };
|
|
36
|
+
if (seenCursors.has(page.nextCursor)) {
|
|
37
|
+
throw new CliError("pagination_error", "Chat-log pagination did not advance; no complete download was produced.");
|
|
38
|
+
}
|
|
39
|
+
seenCursors.add(page.nextCursor);
|
|
40
|
+
cursor = page.nextCursor;
|
|
41
|
+
} while (cursor);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=chat-logs.js.map
|
|
@@ -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
|
@@ -51,6 +51,9 @@ Usage:
|
|
|
51
51
|
--first-name TEXT [--last-name TEXT] [--no-invite] [--confirm]
|
|
52
52
|
recess [--json] students upload-map-scores --student <kid-id>
|
|
53
53
|
--file </path/to/map-report.pdf> [--confirm]
|
|
54
|
+
recess [--json] chat-logs list --student <user-id> [--limit 100] [--cursor <cursor>]
|
|
55
|
+
recess [--json] chat-logs get <conversation-id>
|
|
56
|
+
recess [--json] chat-logs by-todo <todo-id>
|
|
54
57
|
recess [--json] students list [--scope mine|family]
|
|
55
58
|
recess [--json] students today --student <kid-id> [--date YYYY-MM-DD]
|
|
56
59
|
recess [--json] students schedule --student <kid-id> [--days 14]
|
|
@@ -140,6 +143,10 @@ Usage:
|
|
|
140
143
|
[--description TEXT] [--date YYYY-MM-DD] [--confirm]
|
|
141
144
|
recess [--json] payout items delete <item-id> [--confirm]
|
|
142
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>
|
|
143
150
|
recess [--json] cohorts parent-emails <cohort-id>
|
|
144
151
|
recess [--json] cohorts end <cohort-id> [--cancel-subscriptions] [--confirm]
|
|
145
152
|
recess [--json] cohorts pause-billing <cohort-id> --weeks 1..6 [--confirm]
|
|
@@ -357,7 +364,7 @@ Usage:
|
|
|
357
364
|
[--dry-run] [--confirm --approval-token TOKEN]
|
|
358
365
|
recess [--json] goal-templates apply-starter <template-id> --student <kid-id>
|
|
359
366
|
[--answers-file <path>] [--confirm]
|
|
360
|
-
recess [--json] goals list --student <kid-id>
|
|
367
|
+
recess [--json] goals list --student <kid-id> [--query <text>] [--include-archived]
|
|
361
368
|
recess [--json] goals create [--student <kid-id>] --title TEXT
|
|
362
369
|
(--description TEXT | --description-file <path>) [--target-date <iso>]
|
|
363
370
|
[--schedule TEXT] [(--draft <draft-slug> | --source-dir <local-checkout>)
|
|
@@ -392,6 +399,14 @@ Usage:
|
|
|
392
399
|
recess [--json] todos generate-learning-analysis <todo-id> [--confirm]
|
|
393
400
|
recess [--json] todos generate-applet <todo-id> --student <kid-id>
|
|
394
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.
|
|
395
410
|
recess [--json] memories context --student <kid-id>
|
|
396
411
|
recess [--json] memories log --student <kid-id> --date YYYY-MM-DD
|
|
397
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:
|