@stage5/lumine 0.3.53 → 0.3.54
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-nonogram.js +45 -0
- package/lib/admin.js +6 -0
- package/lib/commands.js +1 -0
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +5 -1
- package/sdk/LUMINE_ADMIN.md +30 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readAdminJsonFile } from './admin-news.js';
|
|
2
|
+
|
|
3
|
+
const USAGE = 'Usage: lumine admin nonogram status | add --file <pictures.json> | retire <key> | restore <key> [--confirm] [--json]';
|
|
4
|
+
function invalid(message) {
|
|
5
|
+
const error = new Error(message);
|
|
6
|
+
error.code = 'CLI_ADMIN_CLI_VALIDATION';
|
|
7
|
+
throw error;
|
|
8
|
+
}
|
|
9
|
+
export function buildNonogramOperation({ action, target, options }) {
|
|
10
|
+
if (!action || action === 'status') return {
|
|
11
|
+
name: 'nonogram.status', method: 'GET', path: '/cli/admin/nonogram/status',
|
|
12
|
+
mutates: false, requiresRun: false
|
|
13
|
+
};
|
|
14
|
+
if (!['add', 'retire', 'restore'].includes(action)) invalid(USAGE);
|
|
15
|
+
if (options.adminConfirm !== '' && options.adminConfirm !== 'true')
|
|
16
|
+
invalid('Pass --confirm on its own, without a value.');
|
|
17
|
+
const confirm = options.adminConfirm === 'true';
|
|
18
|
+
if (action !== 'add') {
|
|
19
|
+
if (typeof target !== 'string' || !/^[a-z0-9][a-z0-9-]{2,79}$/.test(target)) invalid('Supply one exact picture key.');
|
|
20
|
+
return { name: `nonogram.${action}${confirm ? '' : '.preview'}`, method: 'POST',
|
|
21
|
+
path: '/cli/admin/nonogram/availability', body: { key: target, retire: action === 'retire', dryRun: !confirm },
|
|
22
|
+
mutates: confirm, requiresRun: false };
|
|
23
|
+
}
|
|
24
|
+
const pictures = readAdminJsonFile(options.adminFile, '--file <pictures.json>', { maxBytes: 1024 * 1024 });
|
|
25
|
+
if (!Array.isArray(pictures) || !pictures.length || pictures.length > 150)
|
|
26
|
+
invalid('The file must contain 1–150 reviewed pictures. Split larger refills.');
|
|
27
|
+
return {
|
|
28
|
+
name: confirm ? 'nonogram.add' : 'nonogram.add.preview', method: 'POST',
|
|
29
|
+
path: '/cli/admin/nonogram/add', body: { pictures, dryRun: !confirm },
|
|
30
|
+
mutates: confirm, requiresRun: false, timeoutMs: 120000
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function formatNonogramResult({ operation, data }) {
|
|
34
|
+
if (operation.name === 'nonogram.status') return (data?.sizes || []).map((s) =>
|
|
35
|
+
`${s.size}×${s.size}: ${s.total} pictures; at least ${s.leastUnseen} unseen for every learner (${s.reserveDays} days at 3/day; target ${s.targetDays}).`);
|
|
36
|
+
if (/^nonogram\.(retire|restore)/.test(operation.name)) return [
|
|
37
|
+
`${data?.dryRun ? 'Dry run: would mark' : 'Marked'} ${data?.key} ${data?.retired ? 'retired' : 'available'}. Started puzzles are preserved.`,
|
|
38
|
+
...(data?.dryRun ? ['Nothing was written. Add --confirm to apply.'] : [])
|
|
39
|
+
];
|
|
40
|
+
return [
|
|
41
|
+
`${data?.dryRun ? 'Dry run: would add' : 'Added'} ${data?.added?.length || 0} pictures; skipped ${data?.skipped?.length || 0} duplicates.`,
|
|
42
|
+
...(data?.skipped || []).map((p) => `${p.key}: ${p.reason}`),
|
|
43
|
+
...(data?.dryRun ? ['Nothing was written. Add --confirm to import this reviewed collection.'] : [])
|
|
44
|
+
];
|
|
45
|
+
}
|
package/lib/admin.js
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
} from "./admin-owner-trace.js";
|
|
46
46
|
import { buildRolesOperation, printRoles } from "./admin-roles.js";
|
|
47
47
|
import { buildCraftOperation, formatCraftResult } from "./admin-craft.js";
|
|
48
|
+
import { buildNonogramOperation, formatNonogramResult } from './admin-nonogram.js';
|
|
48
49
|
import {
|
|
49
50
|
buildSlidingBlockOperation,
|
|
50
51
|
formatSlidingBlockResult,
|
|
@@ -2650,6 +2651,7 @@ export function parseAdminOperation(options) {
|
|
|
2650
2651
|
if (namespace === "sliding-block") {
|
|
2651
2652
|
return buildSlidingBlockOperation({ action, options });
|
|
2652
2653
|
}
|
|
2654
|
+
if (namespace === 'nonogram') return buildNonogramOperation({ action, target, options });
|
|
2653
2655
|
// Stuck Lumine Build plans and their free recheck (admin-build-plans.js).
|
|
2654
2656
|
if (namespace === "build-plans") {
|
|
2655
2657
|
return buildBuildPlansOperation({ action, options });
|
|
@@ -7400,6 +7402,10 @@ function printAdminResult({ operation, result }) {
|
|
|
7400
7402
|
console.log(line);
|
|
7401
7403
|
return;
|
|
7402
7404
|
}
|
|
7405
|
+
if (operation.name.startsWith('nonogram.')) {
|
|
7406
|
+
for (const line of formatNonogramResult({ operation, data })) console.log(line);
|
|
7407
|
+
return;
|
|
7408
|
+
}
|
|
7403
7409
|
if (operation.name.startsWith("build-plans.")) {
|
|
7404
7410
|
for (const line of formatBuildPlansResult({ operation, data }))
|
|
7405
7411
|
console.log(line);
|
package/lib/commands.js
CHANGED
|
@@ -3263,6 +3263,7 @@ export function printHelp() {
|
|
|
3263
3263
|
lumine admin cardcraft-review list [--status pending|approved|all] [--cursor <id>] | show <id> | approve|reject|revoke <id> [--reason <text>]
|
|
3264
3264
|
lumine admin craft seed --build <id> --file <pairs.json> [--confirm] | stats --build <id> [--limit <1-100>] | revise --build <id> --item <id|name> [--why <text>] [--emoji <emoji>] [--category <field>] [--confirm] | revise --build <id> --pair <pairId|a+b> --why <text> [--confirm] | retire --build <id> --item <id|name> --reason <text> [--confirm] | unfizzle --build <id> [--since <iso>] [--pair <a+b>] [--confirm] (Twinkle.craft curation: dry run without --confirm)
|
|
3265
3265
|
lumine admin sliding-block status | add --file <levels.json> [--confirm] (sliding-block-v1 campaign refills, RushHour: dry run without --confirm; the server solves each board and appends it)
|
|
3266
|
+
lumine admin nonogram status | add --file <pictures.json> | retire <key> | restore <key> [--confirm] (private reviewed pictures; changes are dry runs without --confirm)
|
|
3266
3267
|
lumine admin build-plans stuck [--days <1-90>] [--all] | recheck <buildId> [<buildId> ...] [--confirm] (Lumine Build plans stuck on one milestone: runs, Energy vs estimate, gradings, loop-guard state; recheck re-grades the current milestone with today's evaluator, free for the creator, dry run without --confirm)
|
|
3267
3268
|
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>]
|
|
3268
3269
|
lumine admin meetup list [--status review|active|completed|all] | show <crewId> | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | set-branch <crewId> <userId> --branch "<name>" [--note <text>] | branches [list [--status pending|official|rejected|all] | show <id> | approve <id> | reject <id> [--note <text>] | merge <id> --into <officialId>] | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | funnel [--days <1-90>] | announce-checkins [--send] [--as zero|ciel] (Bridge Builder crews and check-ins)
|
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Version: 1.68.0
|
|
4
4
|
Updated: 2026-10-16
|
|
5
|
-
Generated: 2026-10-
|
|
5
|
+
Generated: 2026-10-22T03:03:30.790Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -1569,10 +1569,12 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1569
1569
|
- Rules may set maxLifetimeClaims, a per-learner limit for that rule across days and releases. rules[].lifetime contains the server-confirmed limit and remaining claims. App storage never enforces this limit.
|
|
1570
1570
|
- howTo is the rule's optional plain-words steps from rewards.json (up to 300 characters, e.g. "Save a new design of at least 64 blocks in Designs"). The Earn page shows it under the title; show it in the app's own rewards list too so players always know exactly what earns each reward.
|
|
1571
1571
|
- category and series are optional rewards.json labels for a long list. category is a short section header (up to 40 characters, e.g. "Bring friends"); series is a short lowercase key (e.g. "new-friend") tying repeat rules like vigil-friend-1/2/3 together, and rules in one series must share category, verifier, XP and Coins. The Earn page groups rules by category and shows each series as one row ("up to 3 a day", "1 of 3 today"); group the app's own rewards list the same way.
|
|
1572
|
+
- nonogram-picture-v1 uses Twinkle’s private, editorially reviewed picture bank. Set pictureSize to 5, 10 or 15 on each completion rule. Do not bundle daily solutions or a fixed answer catalog in app files. The collection is maintained through lumine admin nonogram; the reward amounts and daily limits still come from the approved rules.
|
|
1572
1573
|
- await Twinkle.rewards.getReceipt({ challengeId }) | scopes: rewards:claim
|
|
1573
1574
|
- Returns: { mode: "live", status: "awarded" | "pending" | "expired" | "not_found", receipt: { id, challengeId, ruleId, reviewId, artifactVersionId, dayKey, xp, coins, attempt, createdAt } | null, balances: { xp, coins } } | { mode: "preview", status: "not_found", receipt: null, message }
|
|
1574
1575
|
- Read an existing receipt for this app and signed-in viewer by server-issued challengeId, including previous UTC days and previous approved versions. Requires the current approved published release and runtime grant; a stale frame must reload first. Never awards, retries a claim, returns answer keys, or restores removed rewards permission.
|
|
1575
1576
|
- Reconcile a durable local reward outbox after a lost claim reply: awarded confirms the exact payment; pending means no receipt yet for a current unexpired challenge, so retry the same challengeId. expired or not_found confirms no paid receipt and no claim possible for that ID under the current release. Never refund app items just because getStatus history omitted an older claim or a network request failed. Preview has no durable paid receipts; keep it separate from live recovery.
|
|
1577
|
+
- For a paid nonogram-picture-v1 receipt, picture { title, category } may also be returned so the app can recover the reveal after a lost response. It contains no solution cells.
|
|
1576
1578
|
- await Twinkle.rewards.start({ ruleId, levelIndex?, completionToken? }) | scopes: rewards:claim
|
|
1577
1579
|
- Returns: { mode: "live", challengeId, questions: [{ prompt, hint?, guide? }], setKey, reward: { xp, coins }, retryReward: { xp, coins }, attempts, maxAttempts, attemptsRemaining, firstTryAvailable, expiresAt }
|
|
1578
1580
|
- Creates or resumes a server-issued challenge for the signed-in viewer. Render its questions (prompt and optional hint) and collect numeric answers in the same order. One daily challenge per rule/review; repeat starts cannot reset attempts. A challenge stays open until the site's daily reset (UTC midnight, 9:00 AM in Korea) (expiresAt). Resuming after a wrong answer includes each question's guide.
|
|
@@ -1580,6 +1582,7 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1580
1582
|
- For a completion rule call start when the activity begins (the moment the stage starts); the challenge's age is what the claim is measured against. In preview mode start also works for the owner (a stateless simulation).
|
|
1581
1583
|
- For completionProof: classic-tower-v1, start also returns completion { profile, token, maxFrames, completed, failed }. A new start resets only the simulated climb to its canonical spawn; it cannot reset daily or lifetime rewards. Record inputs from the first physics frame. The completion token is bound to the viewer, challenge, rule and published release.
|
|
1582
1584
|
- For breadface-v1, breadface-v2 and breadface-v3, pass the zero-based canonical levelIndex. Record [dt, inputBits] from the first physics frame; start returns its server token and maxFrames. For study-record-v1, start returns completion { profile, usesAiEnergy: true }; there is no client-authored proof token. For groove-lab-song-v1, groove-lab-heard-v1, vigil-guest-coplay-v1, minecraft-first-link-v1 and writing-words-v1, start returns completion { profile }; there is no progress step. For groove-lab-listen-v1 and groove-lab-own-listen-v1, call start({ ruleId, completionToken: <published song entry id> }) when that song starts playing; it returns completion { profile, songEntryId, durationSeconds, listenSeconds } and refuses with a plain reason (not published, whose song, too short, already earned for this song today). Starting a different song re-binds the rule's unpaid challenge and restarts its clock. For reading-quiz-v1, call progress({ challengeId, completionToken: <chapter commentId>, record: 'open' }) when the reader opens the chapter (returns readyInSeconds, readingSeconds, alreadyEarned, attemptedToday), then progress({ ..., record: 'quiz' }) once readyInSeconds has passed to get questions [{ question, options[4] }] (never answers). getStatus returns reading { chaptersEarnedToday, readersToday } when the app uses a reading proof. For writing-words-v1, getStatus also returns writing { countedWords, checkingChapters, chapters: [{ commentId, words, countedWords, status: counted|not-counted|checking|duplicate|too-short, english, reason }] } for today. For sliding-block-v1, start deals the board (completion.board, positions, minMoves); render it, record each real slide as [ms, carIndex, newPosition], send moves with progress (frames up to maxFrames per call), then claim with the latest token. For app-check-v1, start returns completion { profile, puzzle }: show that puzzle (a repeat start of the same challenge returns the same one); there is no progress step.
|
|
1585
|
+
- For nonogram-picture-v1, start returns completion { profile, completed: false, readyInSeconds, puzzle: { size, rows: number[][], cols: number[][] } }. Render those row and column clues; there is no progress call. Live readyInSeconds is the remaining approved minimum wait, so reopening a puzzle does not restart its clock. The private picture is durably bound to this exact viewer, app, release, rule, challenge and expiry. Reopening returns the same clues even after a refill. New challenges use unseen pictures; build_reward_picture_collection_complete means this size needs a refill. Offer practice/community puzzles while waiting.
|
|
1583
1586
|
- await Twinkle.rewards.progress({ challengeId, completionToken?, frames?, record?, requestId? }) | scopes: rewards:claim
|
|
1584
1587
|
- Returns: { mode: "live" | "preview", completion: { profile, token?, completed, failed?, decision?, message?, maxFrames? }, aiUsagePolicy? }
|
|
1585
1588
|
- Verify a bounded batch of inputs for an approved server-simulated climb.
|
|
@@ -1599,6 +1602,7 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1599
1602
|
- For study-record-v1, the server verifies the settled private review row and its viewer, app, exact release, challenge and day binding. It never trusts the client’s decision, requested award amount or AI answer. Each successful daily study record is claimable once; display only canonical receipt/balances. For groove-lab-song-v1, pass completionToken as the published song's sharedDb entry id; for groove-lab-heard-v1, pass none; for vigil-guest-coplay-v1, pass the guest's world guestId (from world events) or the co-play record id; for minecraft-first-link-v1, pass nothing or the Minecraft UUID to use; for reading-quiz-v1, pass completionToken as the chapter commentId and answers as the three chosen option indexes (all three must be right; a wrong attempt cannot be retried that day); for groove-lab-listen-v1 and groove-lab-own-listen-v1, pass the same song entry id after listenSeconds of foreground playback; for reading-readers-v1, pass nothing; for writing-words-v1, pass nothing (the server counts today's chapters itself and refuses with a plain reason, e.g. "320 of 500 counted words today" or "still being checked"). The server reads the app's own groove-lab-songs, groove-lab-song-parts and groove-lab-listens rows and refuses with build_reward_completion_proof_required and a plain reason when the song or listeners do not qualify.
|
|
1600
1603
|
- For app-check-v1, pass completionToken as JSON.stringify(record), the record your check reads (at most 64 KB); the server deals the challenge again and runs the approved file's check before paying. A refused record returns { awarded: false, reason, attemptsRemaining } and uses a try; claim only when the app itself sees a finished solve.
|
|
1601
1604
|
- For craft-target-v1 and craft-par-v1, pass completionToken as JSON.stringify({ v: 1, merges: [[a, b], ...] }): the item ids of the run's successful merges in order, starting from the starters (at most 300 merges, 32 KB). The server replays it against this app's known pairs, needs today's target (Twinkle.craft.getState().today) to appear, every result to be in the learner's collection, and at least one second per merge since start; it refuses an unknown pair, an item the run had not made yet, an over-par run (par rule), a par claim on a day the learner revealed a Twinkle.craft.hint (the target rule still pays) or a run on another day. For craft-discovery-v1, pass nothing: the server pays the oldest unpaid World First (receipt slot craft:<itemId>) and puts its name on the bounty story ("discovered <item>"). With tiers, a rule pays only World Firsts of its own tier (refused with a plain reason when the learner has none).
|
|
1605
|
+
- For nonogram-picture-v1, pass completionToken: JSON.stringify({ rows: ["01010", ...] }) with the filled grid. Wrong grids consume a try and never reveal answer cells. A correct grid returns picture { title, category } alongside the ordinary canonical reward receipt, including duplicate receipt recovery. Show the title with the solved pixel picture. The approved minimum time, retry throttle, daily limits and release binding all still apply. Preview validates the same private catalog using separate assignments and never pays rewards.
|
|
1602
1606
|
- await Twinkle.rewards.getLeaderboard({ metric?: "xp" | "coins", period?: "day" | "week" | "all", limit? }) | scopes: rewards:claim
|
|
1603
1607
|
- Returns: { mode: "live", metric, period, limit, dayKey, from, available: { xp, coins }, entries: [{ rank, userId, username, profilePicUrl, xp, coins, claims, lastAt }], me: { rank, xp, coins, claims } | null } | { mode: "preview", metric, period, available, entries: [], me: null, message }
|
|
1604
1608
|
- Standings of who earned the most XP or Coins in THIS app, computed by Twinkle from its own receipts (never from anything the app submits). period 'day' is today (site day, UTC), 'week' the last 7 site days, 'all' (default) every day since approval. limit defaults to 20, max 100.
|
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -5402,6 +5402,36 @@ Since 2026-10-17 Bridge Builder unlocks from QR check-ins: 7 different people, o
|
|
|
5402
5402
|
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.
|
|
5403
5403
|
|
|
5404
5404
|
|
|
5405
|
+
## Nonogram picture collection
|
|
5406
|
+
|
|
5407
|
+
Nonogram's `nonogram-picture-v1` proof uses a private, reviewed picture bank.
|
|
5408
|
+
During full daily management, inspect artwork quality and the least unseen
|
|
5409
|
+
supply at each size; the target reserve is 14 days at three pictures per size
|
|
5410
|
+
per day. This is content maintenance under Mikey's standing daily scope, not
|
|
5411
|
+
permission to change reward amounts or publish a creator's app.
|
|
5412
|
+
|
|
5413
|
+
```
|
|
5414
|
+
lumine admin nonogram status --json
|
|
5415
|
+
lumine admin nonogram add --file <pictures.json> --json
|
|
5416
|
+
lumine admin nonogram add --file <pictures.json> --confirm --json
|
|
5417
|
+
lumine admin nonogram retire <key> [--confirm]
|
|
5418
|
+
lumine admin nonogram restore <key> [--confirm]
|
|
5419
|
+
```
|
|
5420
|
+
|
|
5421
|
+
An import file is an array of 1–150 pictures: `{ key, title, category, rows,
|
|
5422
|
+
artReview, difficultyReview }`. `rows` is a 5×5, 10×10 or 15×15 array of binary
|
|
5423
|
+
strings. Titles are plain text, at most 80 characters. Draw recognizable art
|
|
5424
|
+
first, verify its unique no-guess solve, and visually review it at play size
|
|
5425
|
+
before importing. Record actual visual features and the deduction/difficulty
|
|
5426
|
+
review, not just a pass count. Never pad the reserve with weak art or variants.
|
|
5427
|
+
The server rejects ambiguous puzzles, skips mirror/rotation duplicates, and
|
|
5428
|
+
refuses changing the solution under an existing key. Default operations are
|
|
5429
|
+
dry runs; confirmed writes are audited. Retirement affects future assignments
|
|
5430
|
+
only, preserving started puzzles and receipt recovery. Status separates total
|
|
5431
|
+
active pictures from the least unseen supply for the leading live learners.
|
|
5432
|
+
Previews have separate assignments. Solutions stay private; catalog refills
|
|
5433
|
+
need no app update. Keep prepared inventory distinct from verified live stock.
|
|
5434
|
+
|
|
5405
5435
|
## Word Search vocabulary upkeep
|
|
5406
5436
|
|
|
5407
5437
|
Every full daily website-management run adds reviewed Word Master words across all five difficulty bands, with accurate definitions and natural example sentences. Use `lumine admin word-games status`, `candidates <1-5> --after <id> --limit 100`, and `import <reviewed-words.json> --dry-run` followed by the reviewed import. File shape: `{items:[{wordId,word,wordLevel,definition,example}]}` (up to 100). Candidate exports are unreviewed; use agent judgment and check the current censor table before importing. No paid batch APIs. The import is audited and preserves active generation and today's existing puzzles; future banks include the additions. The app receives all five ready banks in its normal HTML and has a bundled checked fallback, so word selection never creates a loading screen or separate word request. Report additions per difficulty and text corrections. Repository agents follow `agent-guides/word-search-vocabulary.md` for the full procedure. A scoped management request does not start this full-run duty.
|