@stage5/lumine 0.3.27 → 0.3.28
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/lib/admin.js +61 -2
- package/lib/commands.js +1 -1
- package/package.json +1 -1
- package/sdk/LUMINE_ADMIN.md +3 -0
package/lib/admin.js
CHANGED
|
@@ -370,7 +370,7 @@ function buildApprovalOperation({ action, target, options }) {
|
|
|
370
370
|
// Code agent passes wrote (no model is called), then review what they could
|
|
371
371
|
// not settle (Mikey 2026-10-06: bulk AI work runs on Claude Code agents).
|
|
372
372
|
const ENGLISH_USAGE =
|
|
373
|
-
"Use: lumine admin english import <verdicts.json> --pass a|b | adjudicate <verdicts.json> | add-questions <questions.json> | relevel <moves.json> | mastery <userId> --reason <why> | backfill-distractors | summary | review [--limit <n>] | accept|retire <questionId> [--reason <text>] | retag <questionId> --skill <code> [--reason <text>] | build-candidates [--after <questionId>] [--limit <n>] | build-import <verdicts.json>";
|
|
373
|
+
"Use: lumine admin english import <verdicts.json> --pass a|b | adjudicate <verdicts.json> | add-questions <questions.json> | relevel <moves.json> | mastery <userId> --reason <why> | backfill-distractors | summary | review [--limit <n>] | accept|retire <questionId> [--reason <text>] | retag <questionId> --skill <code> [--reason <text>] | build-candidates [--after <questionId>] [--limit <n>] | build-import <verdicts.json> | check-candidates [--after <questionId>] [--limit <n>] | check-import <verdicts.json> | check-broken [--after <questionId>] [--limit <n>]";
|
|
374
374
|
const ENGLISH_IMPORT_MAX = 500;
|
|
375
375
|
|
|
376
376
|
function buildEnglishOperation({ action, target, options }) {
|
|
@@ -421,6 +421,25 @@ function buildEnglishOperation({ action, target, options }) {
|
|
|
421
421
|
}
|
|
422
422
|
return writeOperation("english.build_import", "POST", "/cli/admin/english/build-chunks/import", { items }, { requiresRun: false, timeoutMs: 180000 });
|
|
423
423
|
}
|
|
424
|
+
// Grammar Quest item checks (Mikey 2026-10-10): every item as each question
|
|
425
|
+
// type shows it, still waiting for the agent pass's verdict; importing its
|
|
426
|
+
// verdicts; and the items it found broken, for Mikey to decide on.
|
|
427
|
+
if (action === "check-candidates" || action === "check-broken") {
|
|
428
|
+
// the parser leaves an omitted --after as ""
|
|
429
|
+
const after = options.adminAfter ? parseRequiredInteger(options.adminAfter, "--after", 0, 2147483647) : 0;
|
|
430
|
+
return action === "check-candidates"
|
|
431
|
+
? readOperation("english.check_candidates", withQuery("/cli/admin/english/item-checks/candidates", { after, limit: limitOf(1000, 200) }), { requiresRun: false })
|
|
432
|
+
: readOperation("english.check_broken", withQuery("/cli/admin/english/item-checks/broken", { after, limit: limitOf(500, 100) }), { requiresRun: false });
|
|
433
|
+
}
|
|
434
|
+
if (action === "check-import") {
|
|
435
|
+
if (!target) throw cliValidationError(ENGLISH_USAGE);
|
|
436
|
+
const raw = readAdminJsonFile(target, "<verdicts.json>", { maxBytes: 4 * 1024 * 1024 });
|
|
437
|
+
const items = Array.isArray(raw) ? raw : Array.isArray(raw?.items) ? raw.items : null;
|
|
438
|
+
if (!items || !items.length || items.length > ENGLISH_IMPORT_MAX) {
|
|
439
|
+
throw cliValidationError(`The file must hold 1 to ${ENGLISH_IMPORT_MAX} verdicts (an array, or { items: [...] }).`);
|
|
440
|
+
}
|
|
441
|
+
return writeOperation("english.check_import", "POST", "/cli/admin/english/item-checks/import", { items }, { requiresRun: false, timeoutMs: 300000 });
|
|
442
|
+
}
|
|
424
443
|
// One learner's per-skill mastery and the sentences a report may use.
|
|
425
444
|
// Private learner data: a reason is required and every read is audited.
|
|
426
445
|
if (action === "mastery") {
|
|
@@ -2701,7 +2720,7 @@ export function parseAdminOperation(options) {
|
|
|
2701
2720
|
// reviews as the admin controls on the website quest page. Mikey's own
|
|
2702
2721
|
// decision, any time, never in a daily run.
|
|
2703
2722
|
const usage =
|
|
2704
|
-
"Usage: lumine admin meetup list [--status review|active|completed|all] | approve-crew <crewId> | approve-grownup <crewId> | request-changes <crewId> --note <text> | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | parent-reply <crewId> <memberUserId> --note <text> | funnel [--days <1-90>] | branches [list|show|approve|reject|merge] | show <crewId> | approve-plan <crewId> [--note <text>] | send-back <crewId> --note <text> | approve <crewId> --attended <userId,userId,...> [--note <text>] | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | slot <crewId> --confirm <slotIndex> | resend-email <crewId>.";
|
|
2723
|
+
"Usage: lumine admin meetup list [--status review|active|completed|all] | approve-crew <crewId> | approve-grownup <crewId> | request-changes <crewId> --note <text> | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | parent-reply <crewId> <memberUserId> --note <text> | funnel [--days <1-90>] | branches [list|show|approve|reject|merge] | show <crewId> | approve-plan <crewId> [--note <text>] | send-back <crewId> --note <text> | approve <crewId> --attended <userId,userId,...> [--note <text>] | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | set-branch <crewId> <userId> --branch <name> [--note <text>] | slot <crewId> --confirm <slotIndex> | resend-email <crewId>.";
|
|
2705
2724
|
const note = String(options.note || "").trim();
|
|
2706
2725
|
if (note.length > 1000) {
|
|
2707
2726
|
throw cliValidationError("--note must be at most 1000 characters.");
|
|
@@ -2889,6 +2908,30 @@ export function parseAdminOperation(options) {
|
|
|
2889
2908
|
{ requiresRun: false },
|
|
2890
2909
|
);
|
|
2891
2910
|
}
|
|
2911
|
+
if (action === "set-branch") {
|
|
2912
|
+
// Staff correct a member's branch (Mikey 2026-10-10) instead of a
|
|
2913
|
+
// database write: an official branch (verified at once), a name in the
|
|
2914
|
+
// branch registry, or "Not a Twinkle student". Former students get the
|
|
2915
|
+
// branch they went to. The note is private to admins; nobody is messaged.
|
|
2916
|
+
const crewId = parseRequiredInteger(target, "Crew ID", 1);
|
|
2917
|
+
const memberId = parseRequiredInteger(extra, "Member user ID", 1);
|
|
2918
|
+
const branch = String(options.adminBranch || "").trim();
|
|
2919
|
+
if (!branch) {
|
|
2920
|
+
throw cliValidationError(
|
|
2921
|
+
'lumine admin meetup set-branch <crewId> <userId> needs --branch "<official branch>" (or --branch "Not a Twinkle student").',
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2924
|
+
if (branch.includes(",")) {
|
|
2925
|
+
throw cliValidationError("set-branch takes exactly one --branch.");
|
|
2926
|
+
}
|
|
2927
|
+
return writeOperation(
|
|
2928
|
+
"meetup.set-branch",
|
|
2929
|
+
"POST",
|
|
2930
|
+
`/cli/admin/meetup-quest/crews/${crewId}/members/${memberId}/branch`,
|
|
2931
|
+
{ branch, note },
|
|
2932
|
+
{ requiresRun: false },
|
|
2933
|
+
);
|
|
2934
|
+
}
|
|
2892
2935
|
if (action === "request-changes") {
|
|
2893
2936
|
const crewId = parseRequiredInteger(target, "Crew ID", 1);
|
|
2894
2937
|
if (!note) throw cliValidationError("request-changes needs --note <what the whole crew must change>.");
|
|
@@ -5973,6 +6016,12 @@ function printMeetupCrew(crew) {
|
|
|
5973
6016
|
` Who-are-you check #${check.checkId} · ${check.username} (${check.userId}) · crew #${check.crewId} · ${check.status}${check.askNote ? ` · asked: "${check.askNote}"` : ""} · ${answer}${check.reviewedBy ? ` · ${check.status} by ${check.reviewedBy}` : ""}${check.staffNote ? ` · note: ${check.staffNote}` : ""}`,
|
|
5974
6017
|
);
|
|
5975
6018
|
}
|
|
6019
|
+
// staff's branch corrections (admins only), newest first
|
|
6020
|
+
for (const change of crew.branchChanges || []) {
|
|
6021
|
+
console.log(
|
|
6022
|
+
` Branch set by ${change.actorUsername || "staff"} ${shortDate(change.createdAt)} · ${change.username} (${change.userId}): "${change.oldBranch || "none"}" -> "${change.newBranch}"${change.note ? ` · private note: ${change.note}` : ""}`,
|
|
6023
|
+
);
|
|
6024
|
+
}
|
|
5976
6025
|
for (const parent of crew.parentContacts || []) {
|
|
5977
6026
|
console.log(
|
|
5978
6027
|
` Parent of ${parent.childUsername}: ${parent.status}${parent.verifiedGuardian ? " (guardian on file)" : parent.staffVerified ? " (staff verified)" : " (child-typed address: NOT verified)"} · ${parent.email}${parent.question ? ` · asks: "${parent.question}"` : ""}`,
|
|
@@ -6129,6 +6178,16 @@ function printMeetupResult({ operation, data, result }) {
|
|
|
6129
6178
|
if (data.crew) printMeetupCrew(data.crew);
|
|
6130
6179
|
return;
|
|
6131
6180
|
}
|
|
6181
|
+
if (operation.name === "meetup.set-branch") {
|
|
6182
|
+
const r = data.result || {};
|
|
6183
|
+
console.log(
|
|
6184
|
+
result?.changed === false
|
|
6185
|
+
? `Member ${r.userId} on crew #${r.crewId} already has branch "${r.branch}". Nothing changed.`
|
|
6186
|
+
: `Member ${r.userId} on crew #${r.crewId}: branch "${r.oldBranch || "none"}" -> "${r.branch}"${r.branchVerified ? " (verified)" : r.branchStatus === "pending" ? " (waits for its branch decision)" : ""}.`,
|
|
6187
|
+
);
|
|
6188
|
+
if (data.crew) printMeetupCrew(data.crew);
|
|
6189
|
+
return;
|
|
6190
|
+
}
|
|
6132
6191
|
if (operation.name === "meetup.show" || operation.name === "meetup.parent-reply") {
|
|
6133
6192
|
printMeetupCrew(data.crew || {});
|
|
6134
6193
|
return;
|
package/lib/commands.js
CHANGED
|
@@ -3200,7 +3200,7 @@ export function printHelp() {
|
|
|
3200
3200
|
lumine admin reward-review list [--status pending|approved|all] [--cursor <id>] [--json]
|
|
3201
3201
|
lumine admin cardcraft-review list [--status pending|approved|all] [--cursor <id>] | show <id> | approve|reject|revoke <id> [--reason <text>]
|
|
3202
3202
|
lumine admin storage list [--status pending|approved|rejected|all] [--cursor <id>] | show <user-id-or-username> | approve <request-id> [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <request-id> [--reason <text>] | grant <user-id-or-username> --size <500MB|1GB|2GB|n MB> [--reason <text>]
|
|
3203
|
-
lumine admin meetup list [--status review|active|completed|all] | approve-crew <crewId> | approve-grownup <crewId> | request-changes <crewId> --note <text> | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | parent-reply <crewId> <memberUserId> --note <text> | funnel [--days <1-90>] | branches [list [--status pending|official|rejected|all] | show <id> | approve <id> | reject <id> [--note <text>] | merge <id> --into <officialId>] | show <crewId> | approve-plan <crewId> [--note <text>] | send-back <crewId> --note <text> | approve <crewId> --attended <userId,userId,...> [--note <text>] | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | slot <crewId> --confirm <slotIndex> | resend-email <crewId> (Bridge Builder meetup quest)
|
|
3203
|
+
lumine admin meetup list [--status review|active|completed|all] | approve-crew <crewId> | approve-grownup <crewId> | request-changes <crewId> --note <text> | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | parent-reply <crewId> <memberUserId> --note <text> | funnel [--days <1-90>] | branches [list [--status pending|official|rejected|all] | show <id> | approve <id> | reject <id> [--note <text>] | merge <id> --into <officialId>] | show <crewId> | approve-plan <crewId> [--note <text>] | send-back <crewId> --note <text> | approve <crewId> --attended <userId,userId,...> [--note <text>] | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | set-branch <crewId> <userId> --branch "<name>" [--note <text>] | slot <crewId> --confirm <slotIndex> | resend-email <crewId> (Bridge Builder meetup quest)
|
|
3204
3204
|
lumine admin meetup story list | show <crewId> | approve <crewId> [--note <text>] [--no-announce] | send-back <crewId> --note <text> | unpublish <crewId> --note <text> (Bridge Builder story pages: publishes only what every tagged member's parent allowed)
|
|
3205
3205
|
lumine admin reward-activity [--date YYYY-MM-DD] [--days <1..31>] [--build <id>] [--json]
|
|
3206
3206
|
lumine admin chat-reports list [--status pending|open|reviewing|resolved|dismissed|all] [--cursor <id>] [--json]
|
package/package.json
CHANGED
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -5083,11 +5083,14 @@ For total JEV cost, include `dailyAiCosts.byProviderModel` for TypeSafe/JEV acro
|
|
|
5083
5083
|
|
|
5084
5084
|
## Bridge Builder crew reviews (no daily run required)
|
|
5085
5085
|
|
|
5086
|
+
On the website, Zero and Ciel acting for an admin can make these staff decisions too (who-are-you checks, branch corrections, crew, plan and branch reviews), through the same routes the admin's own taps use and only after the admin's yes on the approval card (Mikey, 2026-10-10). A member's Zero or Ciel still cannot.
|
|
5087
|
+
|
|
5086
5088
|
- `lumine admin meetup list` shows steps waiting for staff. Use `list --status active` to include crews making requested changes.
|
|
5087
5089
|
- `lumine admin meetup show <crewId>` shows canonical progress, approval status, shared feedback, and private staff evidence.
|
|
5088
5090
|
- `lumine admin meetup approve-crew <crewId>` and `approve-grownup <crewId>` approve the corresponding ready step.
|
|
5089
5091
|
- `lumine admin meetup request-changes <crewId> --note <text>` holds the current crew or grown-up step, displays the note to **every crew member**, and announces it in the crew chat. The note is required (maximum 1,000 characters). Members use **Request review again** after making changes; this alerts staff and returns the step to the queue without approving it.
|
|
5090
5092
|
- `lumine admin meetup send-back <crewId> --note <text>` is for a pending plan or video. Those steps keep their existing revise-and-submit flow.
|
|
5091
5093
|
- `lumine admin meetup info <crewId> <userId> --decision ask|accept|ask-again|withdraw` is a **private identity check**. Its question and answer are visible only to the named member and staff. Use `request-changes`, not `info`, for instructions the whole crew needs to see.
|
|
5094
|
+
- `lumine admin meetup set-branch <crewId> <userId> --branch <name> [--note <text>]` corrects a member's branch instead of a database write. `--branch` must be an official branch (a registry alias such as `Busan` resolves to its official name, `Haeundae`), a name still pending in the branch registry, or `"Not a Twinkle student"`. Former Twinkle students get the branch they went to. An official branch is verified at once; a pending name waits for its branch decision. The crew's progress, tier and staff alerts follow the new branch; approvals already given stay. The same branch again changes nothing. The private note and the change history are shown only to admins (`meetup show`, the crew page's staff panel); nobody is messaged. The command refuses members who left and crews that are not active.
|
|
5092
5095
|
|
|
5093
5096
|
Every decision requires Mikey's authorization for that crew and action. Mutations are audited and idempotent; `--output <path>` is an explicit request to retain the canonical JSON. General write receipts omit private crew details.
|