recess-cli 2.6.1 → 2.8.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 +9 -1
- package/dist/api.js +13 -8
- package/dist/args.js +0 -1
- package/dist/cli.js +363 -46
- package/dist/command-schema.js +197 -34
- package/dist/commands/apps.js +282 -23
- package/dist/commands/mastery.js +214 -0
- package/dist/commands/onboarding.js +1 -7
- package/dist/commands/school.js +1 -1
- package/dist/commands/village-events.js +26 -3
- package/dist/engine.js +6 -0
- package/dist/errors.js +13 -4
- package/dist/help.js +23 -6
- package/dist/upload-names.js +19 -0
- package/package.json +8 -1
- package/skill/recess-cli/SKILL.md +31 -1
- package/skill/recess-cli/agents/version.json +2 -2
package/dist/command-schema.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CliError } from "./errors.js";
|
|
2
2
|
export const AGENT_CONTEXT_SCHEMA_VERSION = "4";
|
|
3
3
|
const BOOLEAN_FLAGS = new Set([
|
|
4
|
+
"allow-possible-duplicate",
|
|
4
5
|
"all-references",
|
|
5
6
|
"analyzed",
|
|
6
7
|
"apply",
|
|
@@ -56,6 +57,70 @@ const GLOBAL_FLAG_TYPES = {
|
|
|
56
57
|
profile: "string",
|
|
57
58
|
reason: "string",
|
|
58
59
|
};
|
|
60
|
+
export const RECESS_MCP_SCOPES = {
|
|
61
|
+
templatesRead: "recess:goal-templates:read",
|
|
62
|
+
templatesWrite: "recess:goal-templates:write",
|
|
63
|
+
appsRead: "recess:apps:read",
|
|
64
|
+
appsWrite: "recess:apps:write",
|
|
65
|
+
studentContextRead: "recess:students:read",
|
|
66
|
+
todoAssign: "recess:todos:assign",
|
|
67
|
+
};
|
|
68
|
+
const REMOTE_COMMAND_SCOPES = new Map([
|
|
69
|
+
["goal-templates list", [RECESS_MCP_SCOPES.templatesRead]],
|
|
70
|
+
["goal-templates get", [RECESS_MCP_SCOPES.templatesRead]],
|
|
71
|
+
["goal-templates validate-spec", [RECESS_MCP_SCOPES.templatesWrite]],
|
|
72
|
+
["goal-templates create", [RECESS_MCP_SCOPES.templatesWrite]],
|
|
73
|
+
["skills guardian get", [RECESS_MCP_SCOPES.templatesRead]],
|
|
74
|
+
["doctor", []],
|
|
75
|
+
["help", []],
|
|
76
|
+
["agent-context", []],
|
|
77
|
+
[
|
|
78
|
+
"apps scaffold",
|
|
79
|
+
[RECESS_MCP_SCOPES.appsRead, RECESS_MCP_SCOPES.studentContextRead],
|
|
80
|
+
],
|
|
81
|
+
["apps standards", [RECESS_MCP_SCOPES.appsRead]],
|
|
82
|
+
["apps list", [RECESS_MCP_SCOPES.appsRead]],
|
|
83
|
+
[
|
|
84
|
+
"apps pull",
|
|
85
|
+
[RECESS_MCP_SCOPES.appsRead, RECESS_MCP_SCOPES.studentContextRead],
|
|
86
|
+
],
|
|
87
|
+
["apps preview", [RECESS_MCP_SCOPES.appsRead]],
|
|
88
|
+
["apps status", [RECESS_MCP_SCOPES.appsRead]],
|
|
89
|
+
["apps validate", [RECESS_MCP_SCOPES.appsWrite]],
|
|
90
|
+
["apps publish", [RECESS_MCP_SCOPES.appsWrite]],
|
|
91
|
+
["apps assign", [RECESS_MCP_SCOPES.appsRead, RECESS_MCP_SCOPES.todoAssign]],
|
|
92
|
+
["students list", [RECESS_MCP_SCOPES.studentContextRead]],
|
|
93
|
+
["students todos", [RECESS_MCP_SCOPES.studentContextRead]],
|
|
94
|
+
["students analysis", [RECESS_MCP_SCOPES.studentContextRead]],
|
|
95
|
+
]);
|
|
96
|
+
const REMOTE_USAGE = new Map([
|
|
97
|
+
["goal-templates validate-spec", "recess goal-templates validate-spec"],
|
|
98
|
+
[
|
|
99
|
+
"goal-templates create",
|
|
100
|
+
"recess goal-templates create [--coin-amount N] [--confirm --operation-key <key>]",
|
|
101
|
+
],
|
|
102
|
+
["apps scaffold", "recess apps scaffold [--for-todo <todo-id>]"],
|
|
103
|
+
[
|
|
104
|
+
"apps validate",
|
|
105
|
+
"recess apps validate (input.appBundle) [--confirm --operation-key <key>]",
|
|
106
|
+
],
|
|
107
|
+
["apps pull", "recess apps pull <project-id>"],
|
|
108
|
+
["apps preview", "recess apps preview <project-id> --version <number>"],
|
|
109
|
+
[
|
|
110
|
+
"apps publish",
|
|
111
|
+
"recess apps publish <project-id> --version <number> [--confirm --operation-key <key>]",
|
|
112
|
+
],
|
|
113
|
+
[
|
|
114
|
+
"apps assign",
|
|
115
|
+
"recess apps assign <project-id> --student <kid-id> [--due YYYY-MM-DD] [--confirm --operation-key <key>]",
|
|
116
|
+
],
|
|
117
|
+
]);
|
|
118
|
+
const REMOTE_CONFIRMATION_COMMANDS = new Set([
|
|
119
|
+
"goal-templates create",
|
|
120
|
+
"apps validate",
|
|
121
|
+
"apps publish",
|
|
122
|
+
"apps assign",
|
|
123
|
+
]);
|
|
59
124
|
const LOCAL_COMMAND_NOUNS = new Set([
|
|
60
125
|
"--version",
|
|
61
126
|
"agent-context",
|
|
@@ -112,6 +177,15 @@ const FAMILY_AI_COMMANDS = new Set([
|
|
|
112
177
|
"todos edit",
|
|
113
178
|
]);
|
|
114
179
|
const STAFF_COMMANDS = new Set([
|
|
180
|
+
"apps assign",
|
|
181
|
+
"apps list",
|
|
182
|
+
"apps preview",
|
|
183
|
+
"apps publish",
|
|
184
|
+
"apps pull",
|
|
185
|
+
"apps scaffold",
|
|
186
|
+
"apps standards",
|
|
187
|
+
"apps status",
|
|
188
|
+
"apps validate",
|
|
115
189
|
"cohorts email",
|
|
116
190
|
"cohorts end",
|
|
117
191
|
"cohorts get",
|
|
@@ -197,8 +271,11 @@ const STAFF_COMMANDS = new Set([
|
|
|
197
271
|
"school update",
|
|
198
272
|
"store-items list",
|
|
199
273
|
"students upload-map-scores",
|
|
274
|
+
"students todos",
|
|
275
|
+
"students analysis",
|
|
200
276
|
"todos complete",
|
|
201
277
|
"todos delete",
|
|
278
|
+
"todos restore",
|
|
202
279
|
"todos generate-applet",
|
|
203
280
|
"users get",
|
|
204
281
|
"users tier list-tiers",
|
|
@@ -323,6 +400,11 @@ function positionalBounds(usage) {
|
|
|
323
400
|
optional += 1;
|
|
324
401
|
continue;
|
|
325
402
|
}
|
|
403
|
+
// `[<url>...]`: zero or more positionals (commands that also accept --file).
|
|
404
|
+
if (/^\[<[^,>]+>\.\.\.\]$/.test(token)) {
|
|
405
|
+
variadic = true;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
326
408
|
if (token.startsWith("--") ||
|
|
327
409
|
token.startsWith("[") ||
|
|
328
410
|
token.startsWith("(")) {
|
|
@@ -349,13 +431,26 @@ export function buildCommandSchema(help) {
|
|
|
349
431
|
},
|
|
350
432
|
]));
|
|
351
433
|
const positionals = positionalBounds(usage);
|
|
352
|
-
return commandPath(usage).map((path) =>
|
|
353
|
-
path
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
434
|
+
return commandPath(usage).map((path) => {
|
|
435
|
+
const pathKey = path.join(" ");
|
|
436
|
+
const remoteFlags = {};
|
|
437
|
+
if (pathKey === "apps preview" || pathKey === "apps publish") {
|
|
438
|
+
remoteFlags.version = {
|
|
439
|
+
type: "string",
|
|
440
|
+
repeatable: false,
|
|
441
|
+
required: false,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
path,
|
|
446
|
+
usage,
|
|
447
|
+
flags: { ...flags, ...remoteFlags },
|
|
448
|
+
positionals,
|
|
449
|
+
access: commandAccess(path),
|
|
450
|
+
remoteCapable: REMOTE_COMMAND_SCOPES.has(pathKey),
|
|
451
|
+
oauthScopes: REMOTE_COMMAND_SCOPES.get(pathKey) ?? [],
|
|
452
|
+
};
|
|
453
|
+
});
|
|
359
454
|
});
|
|
360
455
|
}
|
|
361
456
|
function pathStartsWith(path, prefix) {
|
|
@@ -366,11 +461,32 @@ export function findCommandSchema(commands, positionals) {
|
|
|
366
461
|
.filter((command) => pathStartsWith(positionals, command.path))
|
|
367
462
|
.sort((left, right) => right.path.length - left.path.length)[0];
|
|
368
463
|
}
|
|
369
|
-
|
|
464
|
+
function remoteUsage(command) {
|
|
465
|
+
const path = command.path.join(" ");
|
|
466
|
+
const usage = (REMOTE_USAGE.get(path) ?? command.usage)
|
|
467
|
+
.replace(/^recess\s+(?:\[--json\]\s+)?/, "")
|
|
468
|
+
.replace(" (input.appBundle)", "");
|
|
469
|
+
if (path === "help" || path === "agent-context")
|
|
470
|
+
return usage;
|
|
471
|
+
return `${usage} --reason TEXT`;
|
|
472
|
+
}
|
|
473
|
+
const REMOTE_HELP_NOTES = [
|
|
474
|
+
"Send one command string; omit recess and --json. Quote values containing spaces.",
|
|
475
|
+
"Every command except help and agent-context requires --reason TEXT (1–1024 characters).",
|
|
476
|
+
"For writes, provide the reason in the preview; confirm using only the command, --confirm, and --operation-key.",
|
|
477
|
+
"Supply app source as input.appBundle when calling apps validate.",
|
|
478
|
+
"Guides and admins can create reusable goal templates. First load skills guardian get recess-goal-authoring --all-references; use input.template (the complete template document) for goal-templates validate-spec and create, without --file. Preview the full template, then confirm with only goal-templates create --confirm --operation-key <key>.",
|
|
479
|
+
'Example: students todos --student "Student Name" --date today --reason "Show today\'s todos at the user\'s request"',
|
|
480
|
+
"Student names must match exactly (ignoring case and extra spaces) within your roster; use an ID to disambiguate.",
|
|
481
|
+
"--date today uses the student's timezone; an explicit YYYY-MM-DD selects that calendar date. Omit --date for recent todos.",
|
|
482
|
+
"Results include the resolved date/timezone and pagination. If hasMore, repeat with --cursor nextCursor and the same filters; use the resolved date when paging today.",
|
|
483
|
+
"Report recorded todo statuses only. These results do not establish account creation, complete activity history, or whether work happened outside Recess.",
|
|
484
|
+
];
|
|
485
|
+
export function scopedHelp(_help, commands, scope, context, options = {}) {
|
|
370
486
|
const matches = commands.filter((command) => pathStartsWith(command.path, scope) &&
|
|
371
487
|
commandIsAvailable(command, context.scope));
|
|
372
488
|
if (matches.length === 0) {
|
|
373
|
-
throw new CliError("unknown_command", `Unknown command scope: ${scope.join(" ")}. Run
|
|
489
|
+
throw new CliError("unknown_command", `Unknown command scope: ${scope.join(" ")}. Run \`${options.remoteOnly ? "help" : "recess --help"}\` for available commands.`);
|
|
374
490
|
}
|
|
375
491
|
const uniqueUsages = Array.from(new Map(matches.map((command) => [command.usage, command])).values());
|
|
376
492
|
const accessLabel = context.scope
|
|
@@ -387,11 +503,17 @@ export function scopedHelp(_help, commands, scope, context) {
|
|
|
387
503
|
"Key: ◇ shared command ◆ admin-only command",
|
|
388
504
|
"",
|
|
389
505
|
"Usage:",
|
|
390
|
-
...uniqueUsages.flatMap((command) => wrapUsage(command.usage, command.access === "admin" ? "◆" : "◇")),
|
|
506
|
+
...uniqueUsages.flatMap((command) => wrapUsage(options.remoteOnly ? remoteUsage(command) : command.usage, command.access === "admin" ? "◆" : "◇")),
|
|
391
507
|
"",
|
|
392
|
-
|
|
508
|
+
options.remoteOnly
|
|
509
|
+
? "Run `help <noun> [verb]` for a focused list."
|
|
510
|
+
: "Run `recess help <noun> [verb]` for a focused list.",
|
|
511
|
+
...(options.remoteOnly ? ["", ...REMOTE_HELP_NOTES] : []),
|
|
393
512
|
].join("\n");
|
|
394
513
|
}
|
|
514
|
+
export function remoteCommands(commands) {
|
|
515
|
+
return commands.filter((command) => command.remoteCapable);
|
|
516
|
+
}
|
|
395
517
|
function wrapUsage(usage, icon, width = 100) {
|
|
396
518
|
const firstPrefix = ` ${icon} `;
|
|
397
519
|
const continuationPrefix = " ";
|
|
@@ -410,7 +532,7 @@ function wrapUsage(usage, icon, width = 100) {
|
|
|
410
532
|
output.push(current);
|
|
411
533
|
return output;
|
|
412
534
|
}
|
|
413
|
-
export function validateInvocation(parsed, commands) {
|
|
535
|
+
export function validateInvocation(parsed, commands, options = {}) {
|
|
414
536
|
const command = findCommandSchema(commands, parsed.positionals);
|
|
415
537
|
if (!command) {
|
|
416
538
|
const scope = parsed.positionals.join(" ");
|
|
@@ -421,7 +543,14 @@ export function validateInvocation(parsed, commands) {
|
|
|
421
543
|
.map((candidate) => candidate.path.join(" "));
|
|
422
544
|
throw new CliError("unknown_command", `Unknown command: ${scope || "(none)"}.`, 1, suggestions.length > 0 ? { suggestions } : undefined);
|
|
423
545
|
}
|
|
424
|
-
const
|
|
546
|
+
const usage = options.remote ? remoteUsage(command) : command.usage;
|
|
547
|
+
const remoteConfirmation = options.remote && REMOTE_CONFIRMATION_COMMANDS.has(command.path.join(" "));
|
|
548
|
+
const allowed = new Set([
|
|
549
|
+
...Object.keys(command.flags),
|
|
550
|
+
...GLOBAL_FLAGS,
|
|
551
|
+
...(remoteConfirmation ? ["confirm"] : []),
|
|
552
|
+
].filter((flag) => !options.remote ||
|
|
553
|
+
!["json", "profile", "deliver", "file"].includes(flag)));
|
|
425
554
|
const unknown = Array.from(parsed.flags.keys()).filter((name) => name !== "version" && !allowed.has(name));
|
|
426
555
|
if (unknown.length > 0) {
|
|
427
556
|
const validFlags = Array.from(allowed)
|
|
@@ -429,25 +558,27 @@ export function validateInvocation(parsed, commands) {
|
|
|
429
558
|
.map((name) => `--${name}`);
|
|
430
559
|
throw new CliError("unknown_flag", `Unknown flag${unknown.length === 1 ? "" : "s"}: ${unknown
|
|
431
560
|
.map((name) => `--${name}`)
|
|
432
|
-
.join(", ")}.`, 1, { usage
|
|
561
|
+
.join(", ")}.`, 1, { usage, validFlags });
|
|
433
562
|
}
|
|
434
563
|
const duplicate = Array.from(parsed.occurrences).find(([name, count]) => count > 1 && !command.flags[name]?.repeatable);
|
|
435
564
|
if (duplicate) {
|
|
436
|
-
throw new CliError("duplicate_flag", `--${duplicate[0]} may only be passed once.`, 1, { usage
|
|
565
|
+
throw new CliError("duplicate_flag", `--${duplicate[0]} may only be passed once.`, 1, { usage });
|
|
437
566
|
}
|
|
438
567
|
const positionalCount = parsed.positionals.length;
|
|
439
568
|
if (positionalCount < command.positionals.min ||
|
|
440
569
|
(command.positionals.max !== null &&
|
|
441
570
|
positionalCount > command.positionals.max)) {
|
|
442
|
-
throw new CliError("invalid_arguments", `Wrong number of positional arguments for ${command.path.join(" ")}.`, 1, { usage
|
|
571
|
+
throw new CliError("invalid_arguments", `Wrong number of positional arguments for ${command.path.join(" ")}.`, 1, { usage });
|
|
443
572
|
}
|
|
444
573
|
for (const [name, value] of parsed.flags) {
|
|
445
|
-
const type = command.flags[name]?.type ??
|
|
574
|
+
const type = command.flags[name]?.type ??
|
|
575
|
+
GLOBAL_FLAG_TYPES[name] ??
|
|
576
|
+
(remoteConfirmation && name === "confirm" ? "boolean" : undefined);
|
|
446
577
|
if (type === "boolean" && value !== true) {
|
|
447
|
-
throw new CliError("invalid_arguments", `--${name} is a boolean flag and does not take a value.`, 1, { usage
|
|
578
|
+
throw new CliError("invalid_arguments", `--${name} is a boolean flag and does not take a value.`, 1, { usage });
|
|
448
579
|
}
|
|
449
580
|
if (type === "string" && value === true) {
|
|
450
|
-
throw new CliError("invalid_arguments", `--${name} requires a value.`, 1, { usage
|
|
581
|
+
throw new CliError("invalid_arguments", `--${name} requires a value.`, 1, { usage });
|
|
451
582
|
}
|
|
452
583
|
}
|
|
453
584
|
return command;
|
|
@@ -458,33 +589,65 @@ export function agentContext(commands, options) {
|
|
|
458
589
|
cli_version: options.cliVersion,
|
|
459
590
|
session: options.discovery,
|
|
460
591
|
commands: Object.fromEntries(commands
|
|
592
|
+
.filter((command) => !options.remoteOnly || command.remoteCapable)
|
|
461
593
|
.filter((command) => command.path[0] !== "--version" &&
|
|
462
594
|
commandIsAvailable(command, options.discovery.scope))
|
|
463
595
|
.map((command) => [
|
|
464
596
|
command.path.join(" "),
|
|
465
597
|
{
|
|
466
|
-
usage: command.usage,
|
|
467
|
-
flags:
|
|
598
|
+
usage: options.remoteOnly ? remoteUsage(command) : command.usage,
|
|
599
|
+
flags: options.remoteOnly &&
|
|
600
|
+
REMOTE_CONFIRMATION_COMMANDS.has(command.path.join(" "))
|
|
601
|
+
? {
|
|
602
|
+
...Object.fromEntries(Object.entries(command.flags).filter(([name]) => !["json", "file"].includes(name))),
|
|
603
|
+
confirm: {
|
|
604
|
+
type: "boolean",
|
|
605
|
+
repeatable: false,
|
|
606
|
+
required: false,
|
|
607
|
+
},
|
|
608
|
+
}
|
|
609
|
+
: options.remoteOnly
|
|
610
|
+
? Object.fromEntries(Object.entries(command.flags).filter(([name]) => !["json", "file"].includes(name)))
|
|
611
|
+
: command.flags,
|
|
468
612
|
positionals: command.positionals,
|
|
469
613
|
access: command.access,
|
|
470
614
|
admin_only: command.access === "admin",
|
|
615
|
+
remote_capable: command.remoteCapable,
|
|
616
|
+
oauth_scopes: command.oauthScopes,
|
|
471
617
|
},
|
|
472
618
|
])),
|
|
473
|
-
global_flags:
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
619
|
+
global_flags: options.remoteOnly
|
|
620
|
+
? {
|
|
621
|
+
"--operation-key": { type: "string" },
|
|
622
|
+
"--reason": {
|
|
623
|
+
type: "string",
|
|
624
|
+
required_for: "every Recess API request",
|
|
625
|
+
max_length: 1024,
|
|
626
|
+
},
|
|
627
|
+
"--help": { type: "boolean" },
|
|
628
|
+
}
|
|
629
|
+
: {
|
|
630
|
+
"--json": { type: "boolean" },
|
|
631
|
+
"--profile": { type: "string" },
|
|
632
|
+
"--operation-key": { type: "string" },
|
|
633
|
+
"--reason": {
|
|
634
|
+
type: "string",
|
|
635
|
+
required_for: "every Recess API request except authentication",
|
|
636
|
+
max_length: 1024,
|
|
637
|
+
},
|
|
638
|
+
"--deliver": { type: "enum", values: ["stdout", "file:<path>"] },
|
|
639
|
+
"--help": { type: "boolean" },
|
|
481
640
|
},
|
|
482
|
-
|
|
483
|
-
"--help": { type: "boolean" },
|
|
484
|
-
},
|
|
641
|
+
...(options.remoteOnly ? { instructions: REMOTE_HELP_NOTES } : {}),
|
|
485
642
|
available_profiles: options.availableProfiles,
|
|
486
|
-
|
|
487
|
-
|
|
643
|
+
...(options.remoteOnly
|
|
644
|
+
? {}
|
|
645
|
+
: {
|
|
646
|
+
jobs: { commands: ["jobs list", "jobs get", "jobs prune"] },
|
|
647
|
+
feedback: {
|
|
648
|
+
upstream_configured: options.feedbackUpstreamConfigured,
|
|
649
|
+
},
|
|
650
|
+
}),
|
|
488
651
|
};
|
|
489
652
|
}
|
|
490
653
|
//# sourceMappingURL=command-schema.js.map
|