@stage5/lumine 0.3.36 → 0.3.38
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 +82 -5
- package/lib/commands.js +1 -0
- package/lib/sdk.js +5 -0
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +26 -0
- package/sdk/LUMINE_ADMIN.md +24 -0
package/lib/admin.js
CHANGED
|
@@ -179,6 +179,7 @@ const REVIEW_REQUEST_TYPES = [
|
|
|
179
179
|
"project-limit",
|
|
180
180
|
"storage-limit",
|
|
181
181
|
"cardcraft",
|
|
182
|
+
"soundtrack",
|
|
182
183
|
];
|
|
183
184
|
const REVIEW_REQUEST_TYPE_ALIASES = {
|
|
184
185
|
rewards: "rewards",
|
|
@@ -192,6 +193,8 @@ const REVIEW_REQUEST_TYPE_ALIASES = {
|
|
|
192
193
|
cardcraft: "cardcraft",
|
|
193
194
|
"card-craft": "cardcraft",
|
|
194
195
|
crafting: "cardcraft",
|
|
196
|
+
soundtrack: "soundtrack",
|
|
197
|
+
"song-tag": "soundtrack",
|
|
195
198
|
};
|
|
196
199
|
const REVIEW_REQUEST_LIST_STATUSES = [
|
|
197
200
|
"queue",
|
|
@@ -206,9 +209,10 @@ const REVIEW_REQUEST_LABELS = {
|
|
|
206
209
|
"project-limit": "Project room",
|
|
207
210
|
"storage-limit": "Lumine file storage",
|
|
208
211
|
cardcraft: "AI Card crafting",
|
|
212
|
+
soundtrack: "Song app tag",
|
|
209
213
|
};
|
|
210
214
|
const REVIEW_REQUEST_USAGE =
|
|
211
|
-
"Usage: lumine admin review list [--type rewards|project|storage|cardcraft] [--status queue|pending|approved|rejected|all] [--cursor <c>] | show <type:id> [--dir <path>] | approve <type:id> [--config <rules.json>] [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <type:id> [--reason <text>] | revoke <type:id> --reason <text> | propose rewards:<id> --dir <edited-snapshot> --config <rules.json> [--reason <text>] [--title <new app name>] [--thumbnail <image|workspace>]. A bare id works with --type.";
|
|
215
|
+
"Usage: lumine admin review list [--type rewards|project|storage|cardcraft|soundtrack] [--status queue|pending|approved|rejected|all] [--cursor <c>] | show <type:id> [--dir <path>] | approve <type:id> [--config <rules.json>] [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <type:id> [--reason <text>] | revoke <type:id> --reason <text> | propose rewards:<id> --dir <edited-snapshot> --config <rules.json> [--reason <text>] [--title <new app name>] [--thumbnail <image|workspace>]. A bare id works with --type.";
|
|
212
216
|
|
|
213
217
|
function parseReviewRequestType(value) {
|
|
214
218
|
const key = String(value || "")
|
|
@@ -217,7 +221,7 @@ function parseReviewRequestType(value) {
|
|
|
217
221
|
const type = REVIEW_REQUEST_TYPE_ALIASES[key];
|
|
218
222
|
if (!type) {
|
|
219
223
|
throw cliValidationError(
|
|
220
|
-
`Unknown request type "${key}". Use rewards, project, storage or
|
|
224
|
+
`Unknown request type "${key}". Use rewards, project, storage, cardcraft or soundtrack.`,
|
|
221
225
|
);
|
|
222
226
|
}
|
|
223
227
|
return type;
|
|
@@ -882,13 +886,13 @@ function buildReviewRequestOperation({
|
|
|
882
886
|
if (!REWARD_REVIEW_DECISIONS.includes(action)) {
|
|
883
887
|
return null;
|
|
884
888
|
}
|
|
885
|
-
if (action === "revoke" && !["rewards", "cardcraft"].includes(ref.type)) {
|
|
889
|
+
if (action === "revoke" && !["rewards", "cardcraft", "soundtrack"].includes(ref.type)) {
|
|
886
890
|
throw cliValidationError(
|
|
887
891
|
`${REVIEW_REQUEST_LABELS[ref.type]} approvals are not revoked; change the limit instead${ref.type === "storage-limit" ? " (lumine admin storage grant)" : ""}.`,
|
|
888
892
|
);
|
|
889
893
|
}
|
|
890
894
|
if (
|
|
891
|
-
["rewards", "cardcraft"].includes(ref.type) &&
|
|
895
|
+
["rewards", "cardcraft", "soundtrack"].includes(ref.type) &&
|
|
892
896
|
action !== "approve" &&
|
|
893
897
|
!reason
|
|
894
898
|
) {
|
|
@@ -3509,7 +3513,7 @@ export function parseAdminOperation(options) {
|
|
|
3509
3513
|
// long as two GPT-6.1 Sol calls, so it gets its own timeout. The daily
|
|
3510
3514
|
// website management run calls it in a bounded loop (Mikey, 2026-09-28).
|
|
3511
3515
|
const usage =
|
|
3512
|
-
"Usage: lumine admin reward-bank coverage <buildId> [--max-step 3] | seed <buildId> [--max-step 3] [--identity zero|ciel|auto] | report <buildId> [--days 1].";
|
|
3516
|
+
"Usage: lumine admin reward-bank coverage <buildId> [--max-step 3] | seed <buildId> [--max-step 3] [--identity zero|ciel|auto] | report <buildId> [--days 1] | question <buildId> --question-id <id> | repair <buildId> --file <repair.json> [--dry-run].";
|
|
3513
3517
|
const buildId = parseRequiredInteger(
|
|
3514
3518
|
target || options.adminBuild,
|
|
3515
3519
|
"Build ID",
|
|
@@ -3519,6 +3523,59 @@ export function parseAdminOperation(options) {
|
|
|
3519
3523
|
? parseRequiredInteger(options.adminMaxStep, "--max-step", 1)
|
|
3520
3524
|
: 3;
|
|
3521
3525
|
if (maxStep > 10) throw cliValidationError("--max-step must be at most 10.");
|
|
3526
|
+
if (action === "question") {
|
|
3527
|
+
const questionId = parseRequiredInteger(
|
|
3528
|
+
options.adminQuestionId,
|
|
3529
|
+
"--question-id",
|
|
3530
|
+
1,
|
|
3531
|
+
);
|
|
3532
|
+
return readOperation(
|
|
3533
|
+
"reward-bank.question",
|
|
3534
|
+
withQuery("/cli/admin/reward-bank/question", {
|
|
3535
|
+
buildId: String(buildId),
|
|
3536
|
+
questionId: String(questionId),
|
|
3537
|
+
}),
|
|
3538
|
+
{ requiresRun: false },
|
|
3539
|
+
);
|
|
3540
|
+
}
|
|
3541
|
+
if (action === "repair") {
|
|
3542
|
+
const repair = readAdminJsonFile(
|
|
3543
|
+
options.adminFile,
|
|
3544
|
+
"--file <repair.json>",
|
|
3545
|
+
{ maxBytes: 16 * 1024 },
|
|
3546
|
+
);
|
|
3547
|
+
const fields = ["questionId", "expectedFingerprint", "answer", "reason"];
|
|
3548
|
+
if (
|
|
3549
|
+
!repair ||
|
|
3550
|
+
typeof repair !== "object" ||
|
|
3551
|
+
Array.isArray(repair) ||
|
|
3552
|
+
Object.keys(repair).some((key) => !fields.includes(key)) ||
|
|
3553
|
+
!Number.isSafeInteger(repair.questionId) ||
|
|
3554
|
+
repair.questionId <= 0 ||
|
|
3555
|
+
typeof repair.answer !== "number" ||
|
|
3556
|
+
!Number.isFinite(repair.answer) ||
|
|
3557
|
+
typeof repair.expectedFingerprint !== "string" ||
|
|
3558
|
+
!/^[a-f0-9]{64}$/.test(repair.expectedFingerprint) ||
|
|
3559
|
+
typeof repair.reason !== "string" ||
|
|
3560
|
+
!repair.reason.trim() ||
|
|
3561
|
+
repair.reason.length > 2000
|
|
3562
|
+
) {
|
|
3563
|
+
throw cliValidationError(
|
|
3564
|
+
"Repair JSON needs questionId, the inspected expectedFingerprint, a finite numeric answer and a reason (1–2000 characters); no other fields.",
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3567
|
+
return writeOperation(
|
|
3568
|
+
"reward-bank.repair",
|
|
3569
|
+
"POST",
|
|
3570
|
+
"/cli/admin/reward-bank/repair",
|
|
3571
|
+
{
|
|
3572
|
+
buildId,
|
|
3573
|
+
repair,
|
|
3574
|
+
dryRun: Boolean(options.adminDryRun),
|
|
3575
|
+
},
|
|
3576
|
+
{ requiresRun: false, mutates: !options.adminDryRun },
|
|
3577
|
+
);
|
|
3578
|
+
}
|
|
3522
3579
|
if (action === "report") {
|
|
3523
3580
|
const days = options.adminDays
|
|
3524
3581
|
? parseRequiredInteger(options.adminDays, "--days", 1)
|
|
@@ -7385,6 +7442,26 @@ function printAdminResult({ operation, result }) {
|
|
|
7385
7442
|
}
|
|
7386
7443
|
}
|
|
7387
7444
|
if (operation.name.startsWith("reward-bank.")) {
|
|
7445
|
+
if (
|
|
7446
|
+
["reward-bank.question", "reward-bank.repair"].includes(operation.name)
|
|
7447
|
+
) {
|
|
7448
|
+
const question = data.question;
|
|
7449
|
+
console.log(
|
|
7450
|
+
`Question #${question.id} · Build ${question.buildId} · ${question.mode} · ${question.status}`,
|
|
7451
|
+
);
|
|
7452
|
+
console.log(question.prompt);
|
|
7453
|
+
console.log(`Stored answer: ${question.answer}`);
|
|
7454
|
+
if (question.hint) console.log(`Hint: ${question.hint}`);
|
|
7455
|
+
if (question.guide) console.log(`Guide: ${question.guide}`);
|
|
7456
|
+
console.log(`Fingerprint: ${data.fingerprint}`);
|
|
7457
|
+
console.log(`History: ${JSON.stringify(data.impact)}`);
|
|
7458
|
+
if (Object.hasOwn(data, "proposedAnswer"))
|
|
7459
|
+
console.log(
|
|
7460
|
+
`Proposed answer: ${data.proposedAnswer} · would change: ${data.wouldChange}`,
|
|
7461
|
+
);
|
|
7462
|
+
console.log(data.historyPolicy);
|
|
7463
|
+
return;
|
|
7464
|
+
}
|
|
7388
7465
|
if (operation.name === "reward-bank.seed") {
|
|
7389
7466
|
console.log(
|
|
7390
7467
|
data.seeded
|
package/lib/commands.js
CHANGED
|
@@ -2803,6 +2803,7 @@ export function parseArgs(args) {
|
|
|
2803
2803
|
adminEditionId: raw.editionId ? String(raw.editionId) : "",
|
|
2804
2804
|
adminLeaseToken: raw.leaseToken ? String(raw.leaseToken) : "",
|
|
2805
2805
|
adminFile: raw.file ? String(raw.file) : "",
|
|
2806
|
+
adminQuestionId: raw.questionId ? String(raw.questionId) : "",
|
|
2806
2807
|
rewardPreflight: Boolean(raw.preflight),
|
|
2807
2808
|
rewardApprovalFile: raw.approvalFile ? String(raw.approvalFile) : "",
|
|
2808
2809
|
// admin craft revise --item <id|name> [--why] [--emoji] [--category]
|
package/lib/sdk.js
CHANGED
|
@@ -13,6 +13,11 @@ import { findLocalProjectMetadata } from "./workspace.js";
|
|
|
13
13
|
// TWINKLE_BUILD_SDK.md. Methods that mutate data require --allow-write.
|
|
14
14
|
// Anything not listed can be called with --path api/<endpoint>.
|
|
15
15
|
export const SDK_CLI_METHODS = {
|
|
16
|
+
"soundtracks.searchSources": { path: "api/soundtracks/sources", scopes: ["sharedDb:read"] },
|
|
17
|
+
"soundtracks.getTags": { path: "api/soundtracks/tags", scopes: ["sharedDb:read"] },
|
|
18
|
+
"soundtracks.getRecommendations": { path: "api/soundtracks/recommendations", scopes: ["sharedDb:read"] },
|
|
19
|
+
"soundtracks.requestTag": { path: "api/soundtracks/request-tag", scopes: ["sharedDb:write"], write: true },
|
|
20
|
+
"soundtracks.removeTag": { path: "api/soundtracks/remove-tag", scopes: ["sharedDb:write"], write: true },
|
|
16
21
|
"network.getHome": { path: "api/network/home", scopes: ["sharedDb:read"] },
|
|
17
22
|
"network.getFeed": { path: "api/network/feed", scopes: ["sharedDb:read"] },
|
|
18
23
|
"network.getThread": { path: "api/network/thread", scopes: ["sharedDb:read"] },
|
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -1738,6 +1738,32 @@ if (result.status === 'ok') showCard(result.item, result.why, result.worldFirst)
|
|
|
1738
1738
|
- Network owner only: dismiss a report, remove content, suspend an agent, or restore it.
|
|
1739
1739
|
- Lumine Network Build 2742 only. Replace UI state from confirmed server responses.
|
|
1740
1740
|
|
|
1741
|
+
### Twinkle.soundtracks
|
|
1742
|
+
- async searchSources({ query } = {}) | scopes: sharedDb:read
|
|
1743
|
+
- Returns: { sources: [{ kind, appId?, title, href, thumbnailUrl? }] }
|
|
1744
|
+
- Find a public app or Grammarbles Quest for a song tag.
|
|
1745
|
+
- Use a simple searchable picker. Send the returned kind/appId, never a title as an identifier.
|
|
1746
|
+
- async getTags({ entryIds }) | scopes: sharedDb:read
|
|
1747
|
+
- Returns: { tags: [{ id, entryId, status, source, reviewReason, revision, eventTimeMs, canRemove }] }
|
|
1748
|
+
- Read approved tags and the current creator’s requests for up to 50 songs.
|
|
1749
|
+
- Pending, declined, closed and revoked requests are visible only to their creator. Replace state only with the canonical response.
|
|
1750
|
+
- async requestTag({ entryId, source, note?, expectedUserId }) | scopes: sharedDb:write
|
|
1751
|
+
- Returns: { tag }
|
|
1752
|
+
- Ask Mikey to link your published song to an app.
|
|
1753
|
+
- source is { kind: "app", appId } or { kind: "grammarbles-quest" }. expectedUserId must be the current viewer.id.
|
|
1754
|
+
- Only the song’s creator can request a tag. Up to three active tags per song and ten pending requests per creator. Same pending/approved request retries are idempotent.
|
|
1755
|
+
- Uses the shared Build approvals queue and chat card. Nothing influences discovery until Mikey approves. Updating the song or any of its music parts closes the request/approval and requires a fresh review.
|
|
1756
|
+
- async removeTag({ tagId, expectedUserId }) | scopes: sharedDb:write
|
|
1757
|
+
- Returns: { tag }
|
|
1758
|
+
- Withdraw your own app tag, including a live approval.
|
|
1759
|
+
- Stops recommendations immediately. Show the returned status; do not optimistically remove server-owned approval state.
|
|
1760
|
+
- async getRecommendations({ limit } = {}) | scopes: sharedDb:read
|
|
1761
|
+
- Returns: { picks: [{ entry, source, reason }] }
|
|
1762
|
+
- Suggest approved soundtrack songs from apps the current viewer used.
|
|
1763
|
+
- limit is 1–12 (default 6). Guests and viewers without matching history get an empty list. Hide the shelf when empty.
|
|
1764
|
+
- Matches recorded public-app use and actual Grammarbles Quest answers. Private/deleted apps, missing songs and unapproved/revoked tags never qualify. Returns no activity history or raw progress.
|
|
1765
|
+
- Full Circle and Starlight music and covers stay locked for listeners until the current viewer reaches their Red Square milestones. A locked entry has songLocked, coverLocked and coverUnlockHint, with listing metadata only: no music, manifest or details.cover. Direct part reads follow the same gate. Disable playback, remix and queue actions and offer a link to /app/2. Creators retain editing access to their own songs.
|
|
1766
|
+
|
|
1741
1767
|
## Examples
|
|
1742
1768
|
|
|
1743
1769
|
### Daily reflection feed
|
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -1714,8 +1714,32 @@ them so the first learner on a unit never waits or pays.
|
|
|
1714
1714
|
lumine admin reward-bank coverage <buildId> [--max-step 3] [--json] # read-only, no run
|
|
1715
1715
|
lumine admin reward-bank seed <buildId> [--max-step 3] [--json] # delegated full run
|
|
1716
1716
|
lumine admin reward-bank report <buildId> [--days 1] [--json] # read-only telemetry
|
|
1717
|
+
lumine admin reward-bank question <buildId> --question-id <id> [--json]
|
|
1718
|
+
lumine admin reward-bank repair <buildId> --file <repair.json> --dry-run [--json]
|
|
1719
|
+
lumine admin reward-bank repair <buildId> --file <repair.json> [--json]
|
|
1717
1720
|
```
|
|
1718
1721
|
|
|
1722
|
+
`question` returns the complete prompt, answer, hint and guide, question
|
|
1723
|
+
statistics, a canonical `fingerprint`, and aggregate challenge history. It is
|
|
1724
|
+
owner-only and does not need a daily run. A report excerpt is not an exact
|
|
1725
|
+
question inspection.
|
|
1726
|
+
|
|
1727
|
+
`repair` accepts exactly `{ "questionId": 41, "expectedFingerprint": "<hash
|
|
1728
|
+
from inspection>", "answer": 0.5, "reason": "Verified explanation and arithmetic" }`.
|
|
1729
|
+
Inspect and verify the mathematics before preparing a repair. `--dry-run`
|
|
1730
|
+
checks the same guard without writing. The real command locks and rechecks the
|
|
1731
|
+
question, changes only its numeric answer, returns canonical readback and
|
|
1732
|
+
records an idempotent transaction audit. A stale fingerprint is rejected;
|
|
1733
|
+
inspect again instead of guessing a new hash. Unknown plan fields are rejected.
|
|
1734
|
+
No model call or AI Energy charge is involved.
|
|
1735
|
+
|
|
1736
|
+
The repaired answer governs subsequent attempts, including already-issued
|
|
1737
|
+
unsolved challenges. Past attempts, grades, ladders, question statistics and
|
|
1738
|
+
rewards stay unchanged; the receipt states this explicitly. The history counts
|
|
1739
|
+
are observations, not a lock over every learner's activity. If the old answer
|
|
1740
|
+
affected a learner, inspect that history and prepare a separate, exact remedy;
|
|
1741
|
+
this command never guesses compensating rewards or rewrites past grades.
|
|
1742
|
+
|
|
1719
1743
|
`report` covers the last `--days` (1-31): generation rounds by source
|
|
1720
1744
|
(learner or seed) with outcomes (`accepted`, `off_brief`: the checker judged
|
|
1721
1745
|
the question outside the brief's language, level or LaTeX rule;
|