recess-cli 2.3.0 → 2.5.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 +28 -2
- package/dist/args.js +1 -0
- package/dist/cli.js +310 -7
- package/dist/command-schema.js +237 -9
- package/dist/commands/apps.js +413 -0
- package/dist/commands/onboarding.js +15 -0
- package/dist/commands/village-events.js +150 -0
- package/dist/help.js +48 -0
- package/package.json +1 -1
- package/skill/recess-cli/SKILL.md +40 -3
- package/skill/recess-cli/agents/version.json +2 -2
package/dist/command-schema.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { CliError } from "./errors.js";
|
|
2
|
-
export const AGENT_CONTEXT_SCHEMA_VERSION = "
|
|
2
|
+
export const AGENT_CONTEXT_SCHEMA_VERSION = "4";
|
|
3
3
|
const BOOLEAN_FLAGS = new Set([
|
|
4
4
|
"all-references",
|
|
5
|
+
"analyzed",
|
|
5
6
|
"apply",
|
|
7
|
+
"allow-billing",
|
|
6
8
|
"allow-strand",
|
|
7
9
|
"archived",
|
|
8
10
|
"cancel-subscriptions",
|
|
@@ -11,6 +13,8 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
11
13
|
"confirm-destructive-changes",
|
|
12
14
|
"disable-applet-follow-ups",
|
|
13
15
|
"dry-run",
|
|
16
|
+
"force",
|
|
17
|
+
"no-wait",
|
|
14
18
|
"enable-applet-follow-ups",
|
|
15
19
|
"full",
|
|
16
20
|
"help",
|
|
@@ -30,7 +34,9 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
30
34
|
"spec-only",
|
|
31
35
|
"starter-only",
|
|
32
36
|
"supersede",
|
|
37
|
+
"unlink",
|
|
33
38
|
"visual-only",
|
|
39
|
+
"whole-family",
|
|
34
40
|
"wait",
|
|
35
41
|
]);
|
|
36
42
|
const REPEATABLE_FLAGS = new Set(["kid", "unassign"]);
|
|
@@ -50,6 +56,188 @@ const GLOBAL_FLAG_TYPES = {
|
|
|
50
56
|
profile: "string",
|
|
51
57
|
reason: "string",
|
|
52
58
|
};
|
|
59
|
+
const LOCAL_COMMAND_NOUNS = new Set([
|
|
60
|
+
"--version",
|
|
61
|
+
"agent-context",
|
|
62
|
+
"auth",
|
|
63
|
+
"doctor",
|
|
64
|
+
"feedback",
|
|
65
|
+
"jobs",
|
|
66
|
+
"profile",
|
|
67
|
+
"setup",
|
|
68
|
+
]);
|
|
69
|
+
const AUTHENTICATED_COMMAND_PREFIXES = [
|
|
70
|
+
"village build",
|
|
71
|
+
"village library",
|
|
72
|
+
"village objects",
|
|
73
|
+
"village render",
|
|
74
|
+
];
|
|
75
|
+
const FAMILY_AI_COMMANDS = new Set([
|
|
76
|
+
"content-library search",
|
|
77
|
+
"goal-templates apply",
|
|
78
|
+
"goal-templates apply-starter",
|
|
79
|
+
"goal-templates capture-snapshot",
|
|
80
|
+
"goal-templates create",
|
|
81
|
+
"goal-templates get",
|
|
82
|
+
"goal-templates list",
|
|
83
|
+
"goal-templates snapshot-files",
|
|
84
|
+
"goal-templates validate-spec",
|
|
85
|
+
"goals archive",
|
|
86
|
+
"goals create",
|
|
87
|
+
"goals edit",
|
|
88
|
+
"goals files checkout",
|
|
89
|
+
"goals files init",
|
|
90
|
+
"goals files list",
|
|
91
|
+
"goals files push",
|
|
92
|
+
"goals files read",
|
|
93
|
+
"goals files write",
|
|
94
|
+
"goals list",
|
|
95
|
+
"goals pdf upload",
|
|
96
|
+
"goals queue get",
|
|
97
|
+
"goals queue set",
|
|
98
|
+
"goals unarchive",
|
|
99
|
+
"memories context",
|
|
100
|
+
"memories log",
|
|
101
|
+
"onboarding pairing-code",
|
|
102
|
+
"request get",
|
|
103
|
+
"rocky get",
|
|
104
|
+
"rocky set",
|
|
105
|
+
"skills guardian get",
|
|
106
|
+
"skills guardian list",
|
|
107
|
+
"students list",
|
|
108
|
+
"students schedule",
|
|
109
|
+
"students today",
|
|
110
|
+
"students xp-history",
|
|
111
|
+
"todos create",
|
|
112
|
+
"todos edit",
|
|
113
|
+
]);
|
|
114
|
+
const STAFF_COMMANDS = new Set([
|
|
115
|
+
"cohorts email",
|
|
116
|
+
"cohorts end",
|
|
117
|
+
"cohorts get",
|
|
118
|
+
"cohorts parent-emails",
|
|
119
|
+
"cohorts pause-billing",
|
|
120
|
+
"cohorts resume-billing",
|
|
121
|
+
"cohorts search",
|
|
122
|
+
"enrollments create",
|
|
123
|
+
"enrollments register-cohort",
|
|
124
|
+
"enrollments unregister-cohort",
|
|
125
|
+
"events add",
|
|
126
|
+
"events cancel",
|
|
127
|
+
"events get",
|
|
128
|
+
"events reschedule",
|
|
129
|
+
"events set-status",
|
|
130
|
+
"events take-attendance",
|
|
131
|
+
"goal-templates delete",
|
|
132
|
+
"goal-templates set-metadata",
|
|
133
|
+
"goal-templates versions",
|
|
134
|
+
"goals complete",
|
|
135
|
+
"goals undo-completion",
|
|
136
|
+
"onboarding active-tutors",
|
|
137
|
+
"onboarding attest",
|
|
138
|
+
"onboarding clear-for-cohort",
|
|
139
|
+
"onboarding cohort-options",
|
|
140
|
+
"onboarding comms",
|
|
141
|
+
"onboarding contracts",
|
|
142
|
+
"onboarding doctor",
|
|
143
|
+
"onboarding extract",
|
|
144
|
+
"onboarding family",
|
|
145
|
+
"onboarding generate-summaries",
|
|
146
|
+
"onboarding intake-session",
|
|
147
|
+
"onboarding intake-session-create",
|
|
148
|
+
"onboarding ixl-preview",
|
|
149
|
+
"onboarding kids",
|
|
150
|
+
"onboarding lifecycle-prompts",
|
|
151
|
+
"onboarding link-math-academy",
|
|
152
|
+
"onboarding mark-reviewed",
|
|
153
|
+
"onboarding next",
|
|
154
|
+
"onboarding orientation-attendance",
|
|
155
|
+
"onboarding orientation-sessions",
|
|
156
|
+
"onboarding provision-ixl",
|
|
157
|
+
"onboarding provision-math-academy",
|
|
158
|
+
"onboarding queue",
|
|
159
|
+
"onboarding readiness",
|
|
160
|
+
"onboarding register-cohort",
|
|
161
|
+
"onboarding remove-ixl",
|
|
162
|
+
"onboarding review",
|
|
163
|
+
"onboarding reviews",
|
|
164
|
+
"onboarding seed-feed",
|
|
165
|
+
"onboarding send-comms",
|
|
166
|
+
"onboarding send-contract",
|
|
167
|
+
"onboarding set-intake",
|
|
168
|
+
"onboarding set-kid-grade",
|
|
169
|
+
"onboarding set-primary-tutor",
|
|
170
|
+
"onboarding starter-coverage",
|
|
171
|
+
"onboarding status",
|
|
172
|
+
"onboarding timeline",
|
|
173
|
+
"payout invoices get",
|
|
174
|
+
"payout invoices list",
|
|
175
|
+
"payout invoices set-status",
|
|
176
|
+
"payout items add",
|
|
177
|
+
"payout items delete",
|
|
178
|
+
"payout items edit",
|
|
179
|
+
"payout payruns list",
|
|
180
|
+
"payout recipients list",
|
|
181
|
+
"registrations approve",
|
|
182
|
+
"registrations deny",
|
|
183
|
+
"school codes get",
|
|
184
|
+
"school codes list",
|
|
185
|
+
"school create",
|
|
186
|
+
"school credit-transactions",
|
|
187
|
+
"school families",
|
|
188
|
+
"school family-search",
|
|
189
|
+
"school kid-slots",
|
|
190
|
+
"school list",
|
|
191
|
+
"school logo-upload",
|
|
192
|
+
"school partner-family",
|
|
193
|
+
"school representatives add",
|
|
194
|
+
"school representatives list",
|
|
195
|
+
"school representatives remove",
|
|
196
|
+
"school representatives search",
|
|
197
|
+
"school update",
|
|
198
|
+
"store-items list",
|
|
199
|
+
"students upload-map-scores",
|
|
200
|
+
"todos complete",
|
|
201
|
+
"todos delete",
|
|
202
|
+
"todos generate-applet",
|
|
203
|
+
"users get",
|
|
204
|
+
"users tier list-tiers",
|
|
205
|
+
"village events",
|
|
206
|
+
"village link-event",
|
|
207
|
+
]);
|
|
208
|
+
/**
|
|
209
|
+
* Classify the command itself, not the target passed to it. Target-level
|
|
210
|
+
* family, assignment, ownership, and feature-flag checks remain server-owned.
|
|
211
|
+
* A newly added command fails closed to admin-only discovery until its CLI
|
|
212
|
+
* audience is classified here.
|
|
213
|
+
*/
|
|
214
|
+
function commandAccess(path) {
|
|
215
|
+
const key = path.join(" ");
|
|
216
|
+
if (LOCAL_COMMAND_NOUNS.has(path[0]))
|
|
217
|
+
return "local";
|
|
218
|
+
if (AUTHENTICATED_COMMAND_PREFIXES.some((prefix) => key === prefix || key.startsWith(`${prefix} `))) {
|
|
219
|
+
return "authenticated";
|
|
220
|
+
}
|
|
221
|
+
if (FAMILY_AI_COMMANDS.has(key))
|
|
222
|
+
return "family_ai";
|
|
223
|
+
if (STAFF_COMMANDS.has(key))
|
|
224
|
+
return "staff";
|
|
225
|
+
return "admin";
|
|
226
|
+
}
|
|
227
|
+
export function commandIsAvailable(command, scope) {
|
|
228
|
+
if (command.access === "local")
|
|
229
|
+
return true;
|
|
230
|
+
if (!scope)
|
|
231
|
+
return false;
|
|
232
|
+
if (command.access === "authenticated")
|
|
233
|
+
return true;
|
|
234
|
+
if (command.access === "family_ai")
|
|
235
|
+
return scope !== "village_home";
|
|
236
|
+
if (command.access === "staff") {
|
|
237
|
+
return scope === "guide_students" || scope === "full_admin";
|
|
238
|
+
}
|
|
239
|
+
return scope === "full_admin";
|
|
240
|
+
}
|
|
53
241
|
function usageBlocks(help) {
|
|
54
242
|
const lines = help.split("\n");
|
|
55
243
|
const blocks = [];
|
|
@@ -128,8 +316,13 @@ function positionalBounds(usage) {
|
|
|
128
316
|
const rest = usage.replace(/^recess\s+(?:\[--json\]\s+)?/, "");
|
|
129
317
|
const tokens = rest.split(/\s+/);
|
|
130
318
|
let count = 0;
|
|
319
|
+
let optional = 0;
|
|
131
320
|
let variadic = false;
|
|
132
321
|
for (const token of tokens) {
|
|
322
|
+
if (/^\[[a-z][a-z0-9-]*\]$/.test(token)) {
|
|
323
|
+
optional += 1;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
133
326
|
if (token.startsWith("--") ||
|
|
134
327
|
token.startsWith("[") ||
|
|
135
328
|
token.startsWith("(")) {
|
|
@@ -139,7 +332,7 @@ function positionalBounds(usage) {
|
|
|
139
332
|
if (/^<[^,>]+\.\.\.>$/.test(token))
|
|
140
333
|
variadic = true;
|
|
141
334
|
}
|
|
142
|
-
return { min: count, max: variadic ? null : count };
|
|
335
|
+
return { min: count, max: variadic ? null : count + optional };
|
|
143
336
|
}
|
|
144
337
|
export function buildCommandSchema(help) {
|
|
145
338
|
return usageBlocks(help).flatMap((usage) => {
|
|
@@ -161,6 +354,7 @@ export function buildCommandSchema(help) {
|
|
|
161
354
|
usage,
|
|
162
355
|
flags,
|
|
163
356
|
positionals,
|
|
357
|
+
access: commandAccess(path),
|
|
164
358
|
}));
|
|
165
359
|
});
|
|
166
360
|
}
|
|
@@ -172,20 +366,50 @@ export function findCommandSchema(commands, positionals) {
|
|
|
172
366
|
.filter((command) => pathStartsWith(positionals, command.path))
|
|
173
367
|
.sort((left, right) => right.path.length - left.path.length)[0];
|
|
174
368
|
}
|
|
175
|
-
export function scopedHelp(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const matches = commands.filter((command) => pathStartsWith(command.path, scope));
|
|
369
|
+
export function scopedHelp(_help, commands, scope, context) {
|
|
370
|
+
const matches = commands.filter((command) => pathStartsWith(command.path, scope) &&
|
|
371
|
+
commandIsAvailable(command, context.scope));
|
|
179
372
|
if (matches.length === 0) {
|
|
180
373
|
throw new CliError("unknown_command", `Unknown command scope: ${scope.join(" ")}. Run \`recess --help\` for available commands.`);
|
|
181
374
|
}
|
|
375
|
+
const uniqueUsages = Array.from(new Map(matches.map((command) => [command.usage, command])).values());
|
|
376
|
+
const accessLabel = context.scope
|
|
377
|
+
? context.role
|
|
378
|
+
: context.source === "unavailable"
|
|
379
|
+
? "session unavailable (local and authentication commands only)"
|
|
380
|
+
: "signed out (local and authentication commands only)";
|
|
182
381
|
return [
|
|
183
|
-
|
|
382
|
+
scope.length > 0
|
|
383
|
+
? `recess ${scope.join(" ")} — command help`
|
|
384
|
+
: "recess — safe Recess administration and family AI tools",
|
|
385
|
+
"",
|
|
386
|
+
`Access: ${accessLabel}`,
|
|
387
|
+
"Key: ◇ shared command ◆ admin-only command",
|
|
184
388
|
"",
|
|
185
389
|
"Usage:",
|
|
186
|
-
...
|
|
390
|
+
...uniqueUsages.flatMap((command) => wrapUsage(command.usage, command.access === "admin" ? "◆" : "◇")),
|
|
391
|
+
"",
|
|
392
|
+
"Run `recess help <noun> [verb]` for a focused list.",
|
|
187
393
|
].join("\n");
|
|
188
394
|
}
|
|
395
|
+
function wrapUsage(usage, icon, width = 100) {
|
|
396
|
+
const firstPrefix = ` ${icon} `;
|
|
397
|
+
const continuationPrefix = " ";
|
|
398
|
+
const output = [];
|
|
399
|
+
let current = firstPrefix;
|
|
400
|
+
for (const word of usage.split(/\s+/)) {
|
|
401
|
+
const separator = current.trim().length === 1 ? "" : " ";
|
|
402
|
+
if (current.length > firstPrefix.length &&
|
|
403
|
+
current.length + separator.length + word.length > width) {
|
|
404
|
+
output.push(current);
|
|
405
|
+
current = `${continuationPrefix}${word}`;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
current += `${separator}${word}`;
|
|
409
|
+
}
|
|
410
|
+
output.push(current);
|
|
411
|
+
return output;
|
|
412
|
+
}
|
|
189
413
|
export function validateInvocation(parsed, commands) {
|
|
190
414
|
const command = findCommandSchema(commands, parsed.positionals);
|
|
191
415
|
if (!command) {
|
|
@@ -232,14 +456,18 @@ export function agentContext(commands, options) {
|
|
|
232
456
|
return {
|
|
233
457
|
schema_version: AGENT_CONTEXT_SCHEMA_VERSION,
|
|
234
458
|
cli_version: options.cliVersion,
|
|
459
|
+
session: options.discovery,
|
|
235
460
|
commands: Object.fromEntries(commands
|
|
236
|
-
.filter((command) => command.path[0] !== "--version"
|
|
461
|
+
.filter((command) => command.path[0] !== "--version" &&
|
|
462
|
+
commandIsAvailable(command, options.discovery.scope))
|
|
237
463
|
.map((command) => [
|
|
238
464
|
command.path.join(" "),
|
|
239
465
|
{
|
|
240
466
|
usage: command.usage,
|
|
241
467
|
flags: command.flags,
|
|
242
468
|
positionals: command.positionals,
|
|
469
|
+
access: command.access,
|
|
470
|
+
admin_only: command.access === "admin",
|
|
243
471
|
},
|
|
244
472
|
])),
|
|
245
473
|
global_flags: {
|