recess-cli 2.8.0 → 2.10.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/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ 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";
14
15
  import { runSchoolCommand } from "./commands/school.js";
@@ -4400,6 +4401,9 @@ export async function executeRecessCommand(argv, options = {}) {
4400
4401
  }
4401
4402
  throw new CliError("invalid_arguments", "Use goals list|create|edit|delete|restore|archive|unarchive|complete|undo-completion|queue|files|pdf.");
4402
4403
  }
4404
+ if (noun === "chat-logs") {
4405
+ return runChatLogsCommand({ parsed, api, writeCommand });
4406
+ }
4403
4407
  if (noun === "students") {
4404
4408
  if (verb === "list") {
4405
4409
  const requestedScope = flagString(parsed, "scope");
@@ -138,6 +138,9 @@ const AUTHENTICATED_COMMAND_PREFIXES = [
138
138
  "village render",
139
139
  ];
140
140
  const FAMILY_AI_COMMANDS = new Set([
141
+ "chat-logs by-todo",
142
+ "chat-logs get",
143
+ "chat-logs list",
141
144
  "content-library search",
142
145
  "goal-templates apply",
143
146
  "goal-templates apply-starter",
@@ -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
@@ -10,6 +10,18 @@ const hash = (value) => createHash("sha256").update(JSON.stringify(value)).diges
10
10
  async function executeApproved(api, preview) {
11
11
  const body = preview.request;
12
12
  switch (preview.action) {
13
+ case "mastery.delete-draft":
14
+ return unwrap(await api.client.DELETE("/ai/rcs/drafts/{id}", {
15
+ params: {
16
+ path: { id: String(preview.target.id) },
17
+ query: { expectedUpdatedAt: String(body.expectedUpdatedAt) },
18
+ },
19
+ }));
20
+ case "mastery.archive":
21
+ return unwrap(await api.client.POST("/ai/rcs/releases/{id}/archive", {
22
+ params: { path: { id: String(preview.target.id) } },
23
+ body: body,
24
+ }));
13
25
  case "mastery.link":
14
26
  return unwrap(await api.client.POST("/ai/rcs/content-links", {
15
27
  body: body,
@@ -41,7 +53,14 @@ export async function runMasteryCommand({ parsed, api, writeCommand: gate, }) {
41
53
  if (session.user.role !== "ADMIN" || session.cliScope !== "full_admin")
42
54
  throw new CliError("forbidden", "Mastery curriculum commands require a full-admin session.");
43
55
  const verb = parsed.positionals[1];
44
- const isWrite = ["edit", "publish", "link", "unlink"].includes(verb ?? "");
56
+ const isWrite = [
57
+ "edit",
58
+ "publish",
59
+ "link",
60
+ "unlink",
61
+ "delete-draft",
62
+ "archive",
63
+ ].includes(verb ?? "");
45
64
  const file = isWrite ? flagString(parsed, "file") : undefined;
46
65
  const invocation = hash({
47
66
  actorId: session.user.id,
@@ -68,6 +87,27 @@ export async function runMasteryCommand({ parsed, api, writeCommand: gate, }) {
68
87
  params: { query: { domainSlug: flagString(parsed, "domain") } },
69
88
  }));
70
89
  }
90
+ if (verb === "delete-draft" || verb === "archive") {
91
+ const id = positional(parsed, 2, verb === "archive" ? "release ID" : "draft ID");
92
+ const kind = verb === "archive" ? "release" : "draft";
93
+ const impact = unwrap(await api.client.GET("/ai/rcs/graphs/removal-preview", {
94
+ params: { query: { id, kind } },
95
+ }));
96
+ const preview = {
97
+ action: `mastery.${verb}`,
98
+ target: { id, kind, title: impact.title, domainSlug: impact.domainSlug },
99
+ request: verb === "archive"
100
+ ? { expectedPublishedAt: impact.revision }
101
+ : { expectedUpdatedAt: impact.revision },
102
+ details: {
103
+ ...impact,
104
+ consequence: verb === "archive"
105
+ ? "Archives this exact release (DEPRECATED). Removes it from active graph selection and new diagnostics; keeps the graph, learner history, content links, and already-issued work. Does not activate a replacement or disable the whole subject."
106
+ : "Permanently deletes this unpublished draft and its completed assembly parts. Cannot be undone. Does not delete any published graph, learner evidence, applet, or goal.",
107
+ },
108
+ };
109
+ return writeCommand(parsed, preview, () => executeApproved(api, preview));
110
+ }
71
111
  if (verb === "get" || verb === "edit" || verb === "publish") {
72
112
  const id = positional(parsed, 2, "graph artifact ID");
73
113
  const kind = verb === "publish"
package/dist/help.js CHANGED
@@ -6,6 +6,8 @@ Usage:
6
6
  recess [--json] mastery edit <graph-id> [--kind seed|draft|release]
7
7
  [--file <graph.json> | --instruction TEXT] [--node <node-id>] [--confirm]
8
8
  recess [--json] mastery publish <draft-id> --expected-active-release <release-id-or-none> [--confirm]
9
+ recess [--json] mastery delete-draft <draft-id> [--confirm]
10
+ recess [--json] mastery archive <release-id> [--confirm]
9
11
  recess [--json] mastery links --graph <release-id> [--node <node-id>] [--cursor <link-id>]
10
12
  recess [--json] mastery link <content-id> --type applet|goal --graph <release-id>
11
13
  [--node <node-id>] [--confirm]
@@ -49,6 +51,9 @@ Usage:
49
51
  --first-name TEXT [--last-name TEXT] [--no-invite] [--confirm]
50
52
  recess [--json] students upload-map-scores --student <kid-id>
51
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>
52
57
  recess [--json] students list [--scope mine|family]
53
58
  recess [--json] students today --student <kid-id> [--date YYYY-MM-DD]
54
59
  recess [--json] students schedule --student <kid-id> [--days 14]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recess-cli",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "Safe Recess administration and family AI tools from the command line.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {