@stage5/lumine 0.2.85 → 0.2.87
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/api.js +33 -0
- package/lib/commands.js +77 -5
- package/lib/constants.js +7 -4
- package/lib/sdk.js +11 -1
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +47 -6
- package/sdk/LUMINE_ADMIN.md +23 -8
package/lib/api.js
CHANGED
|
@@ -439,6 +439,39 @@ export async function adoptBuildThumbnailSuggestion({
|
|
|
439
439
|
});
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
export async function suggestBuildTitleToOwner({
|
|
443
|
+
options,
|
|
444
|
+
auth,
|
|
445
|
+
rootBuildId,
|
|
446
|
+
contributionBuildId,
|
|
447
|
+
title,
|
|
448
|
+
note,
|
|
449
|
+
}) {
|
|
450
|
+
return await requestJson({
|
|
451
|
+
method: "POST",
|
|
452
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}/suggest-title`,
|
|
453
|
+
authToken: auth.token,
|
|
454
|
+
body: { title, note },
|
|
455
|
+
timeoutMs: options.timeoutMs,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export async function adoptBuildTitleSuggestion({
|
|
460
|
+
options,
|
|
461
|
+
auth,
|
|
462
|
+
rootBuildId,
|
|
463
|
+
contributionBuildId,
|
|
464
|
+
suggestionMessageId,
|
|
465
|
+
}) {
|
|
466
|
+
return await requestJson({
|
|
467
|
+
method: "POST",
|
|
468
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}/adopt-title`,
|
|
469
|
+
authToken: auth.token,
|
|
470
|
+
body: { suggestionMessageId },
|
|
471
|
+
timeoutMs: options.timeoutMs,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
442
475
|
export async function mergeContributionIntoMain({
|
|
443
476
|
options,
|
|
444
477
|
auth,
|
package/lib/commands.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "./constants.js";
|
|
23
23
|
import {
|
|
24
24
|
adoptBuildThumbnailSuggestion,
|
|
25
|
+
adoptBuildTitleSuggestion,
|
|
25
26
|
createBuild,
|
|
26
27
|
fetchAllRuntimeAssets,
|
|
27
28
|
forkBuild,
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
resolveBranchBuild,
|
|
46
47
|
saveProjectFiles,
|
|
47
48
|
suggestBuildThumbnailToOwner,
|
|
49
|
+
suggestBuildTitleToOwner,
|
|
48
50
|
updateBuildMetadata,
|
|
49
51
|
upgradeBuildProjectLimits,
|
|
50
52
|
} from "./api.js";
|
|
@@ -523,11 +525,22 @@ export async function forum(options) {
|
|
|
523
525
|
|
|
524
526
|
export async function sendSuggestion(options) {
|
|
525
527
|
const suggestionType = String(options.suggestionAction || "").trim();
|
|
526
|
-
if (
|
|
528
|
+
if (!["branch", "thumbnail", "title"].includes(suggestionType)) {
|
|
527
529
|
throw new Error(
|
|
528
|
-
|
|
530
|
+
'Usage: lumine suggest branch [--note <message>] | lumine suggest thumbnail | lumine suggest title "New Name" [--note <why>]',
|
|
529
531
|
);
|
|
530
532
|
}
|
|
533
|
+
const suggestedTitle =
|
|
534
|
+
suggestionType === "title"
|
|
535
|
+
? String(
|
|
536
|
+
options.title || (options.positional || []).slice(1).join(" "),
|
|
537
|
+
)
|
|
538
|
+
.replace(/\s+/g, " ")
|
|
539
|
+
.trim()
|
|
540
|
+
: "";
|
|
541
|
+
if (suggestionType === "title" && !suggestedTitle) {
|
|
542
|
+
throw new Error('Pass the name: lumine suggest title "New Name" [--note <why>]');
|
|
543
|
+
}
|
|
531
544
|
const auth = await resolveAuth(options);
|
|
532
545
|
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
533
546
|
const build = await loadTargetBuildMetadata({ options, auth });
|
|
@@ -548,6 +561,21 @@ export async function sendSuggestion(options) {
|
|
|
548
561
|
return;
|
|
549
562
|
}
|
|
550
563
|
|
|
564
|
+
if (suggestionType === "title") {
|
|
565
|
+
await suggestBuildTitleToOwner({
|
|
566
|
+
options,
|
|
567
|
+
auth,
|
|
568
|
+
rootBuildId,
|
|
569
|
+
contributionBuildId,
|
|
570
|
+
title: suggestedTitle,
|
|
571
|
+
note: options.note,
|
|
572
|
+
});
|
|
573
|
+
console.log(
|
|
574
|
+
`Suggested the name "${suggestedTitle}" to the owner of Build #${rootBuildId}. They can use it from their chat or with \`lumine suggestions adopt-title\`.`,
|
|
575
|
+
);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
551
579
|
await suggestBuildThumbnailToOwner({
|
|
552
580
|
options,
|
|
553
581
|
auth,
|
|
@@ -696,8 +724,38 @@ export async function suggestions(options) {
|
|
|
696
724
|
return;
|
|
697
725
|
}
|
|
698
726
|
|
|
727
|
+
if (action === "adopt-title") {
|
|
728
|
+
if (suggestion.type !== "title") {
|
|
729
|
+
throw new Error(`Suggestion #${suggestionId} is not a name suggestion.`);
|
|
730
|
+
}
|
|
731
|
+
if (!options.assumeYes) {
|
|
732
|
+
const confirmed = await confirmPrompt(
|
|
733
|
+
`Rename Build #${rootBuildId} from "${suggestion.currentTitle}" to "${suggestion.suggestedTitle}"? [y/N] `,
|
|
734
|
+
);
|
|
735
|
+
if (confirmed === null) {
|
|
736
|
+
console.log("Not a TTY — re-run with --yes to rename the Build.");
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (!confirmed) {
|
|
740
|
+
console.log("Aborted. Name unchanged.");
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
const adoptResult = await adoptBuildTitleSuggestion({
|
|
745
|
+
options,
|
|
746
|
+
auth,
|
|
747
|
+
rootBuildId,
|
|
748
|
+
contributionBuildId,
|
|
749
|
+
suggestionMessageId: suggestionId,
|
|
750
|
+
});
|
|
751
|
+
console.log(
|
|
752
|
+
`Renamed Build #${rootBuildId} to "${String(adoptResult?.build?.title || suggestion.suggestedTitle)}" (suggestion #${suggestionId}).`,
|
|
753
|
+
);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
699
757
|
throw new Error(
|
|
700
|
-
"Usage: lumine suggestions [build] | lumine suggestions merge|replace-main|adopt-thumbnail <suggestion-id> [--build <id>]",
|
|
758
|
+
"Usage: lumine suggestions [build] | lumine suggestions merge|replace-main|adopt-thumbnail|adopt-title <suggestion-id> [--build <id>]",
|
|
701
759
|
);
|
|
702
760
|
}
|
|
703
761
|
|
|
@@ -723,8 +781,8 @@ export function printBuildSuggestions({
|
|
|
723
781
|
if (!suggestions.length) {
|
|
724
782
|
console.log(
|
|
725
783
|
hasMore
|
|
726
|
-
? "No open branch or
|
|
727
|
-
: "No open branch or
|
|
784
|
+
? "No open branch, thumbnail or name suggestions on this page."
|
|
785
|
+
: "No open branch, thumbnail or name suggestions.",
|
|
728
786
|
);
|
|
729
787
|
if (hasMore && nextCursor > 0) {
|
|
730
788
|
console.log(
|
|
@@ -761,6 +819,17 @@ export function printBuildSuggestions({
|
|
|
761
819
|
);
|
|
762
820
|
continue;
|
|
763
821
|
}
|
|
822
|
+
if (suggestion.type === "title") {
|
|
823
|
+
console.log(
|
|
824
|
+
`[#${suggestionId}] ${contributor} suggested a new name from ${branchLabel} (${createdAt})`,
|
|
825
|
+
);
|
|
826
|
+
console.log(` "${suggestion.currentTitle}" → "${suggestion.suggestedTitle}"`);
|
|
827
|
+
if (suggestion.note) console.log(` ${suggestion.note}`);
|
|
828
|
+
console.log(
|
|
829
|
+
` apply: lumine suggestions adopt-title ${suggestionId} --build ${rootBuildId}`,
|
|
830
|
+
);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
764
833
|
console.log(
|
|
765
834
|
`[#${suggestionId}] ${contributor} suggested a thumbnail from ${branchLabel} (${createdAt})`,
|
|
766
835
|
);
|
|
@@ -2342,6 +2411,7 @@ export function parseArgs(args) {
|
|
|
2342
2411
|
"merge",
|
|
2343
2412
|
"replace-main",
|
|
2344
2413
|
"adopt-thumbnail",
|
|
2414
|
+
"adopt-title",
|
|
2345
2415
|
]);
|
|
2346
2416
|
const suggestionAction =
|
|
2347
2417
|
command === "suggest"
|
|
@@ -2840,10 +2910,12 @@ export function printHelp() {
|
|
|
2840
2910
|
lumine forum listen [twinkle-build-url-or-id] [--cursor <sequence>] [--poll-ms <ms>] [--json]
|
|
2841
2911
|
lumine suggest branch [message] [--target <twinkle-branch-url>]
|
|
2842
2912
|
lumine suggest thumbnail [--target <twinkle-branch-url>]
|
|
2913
|
+
lumine suggest title "New Name" [--note <why>] [--target <twinkle-branch-url>]
|
|
2843
2914
|
lumine suggestions [twinkle-build-url-or-id]
|
|
2844
2915
|
lumine suggestions merge <suggestion-id> [--build <id>]
|
|
2845
2916
|
lumine suggestions replace-main <suggestion-id> [--build <id>]
|
|
2846
2917
|
lumine suggestions adopt-thumbnail <suggestion-id> [--build <id>] [--yes]
|
|
2918
|
+
lumine suggestions adopt-title <suggestion-id> [--build <id>] [--yes]
|
|
2847
2919
|
lumine explore [search terms]
|
|
2848
2920
|
lumine select [twinkle-build-url]
|
|
2849
2921
|
lumine pull [twinkle-build-url]
|
package/lib/constants.js
CHANGED
|
@@ -225,11 +225,14 @@ lumine save --summary "Describe the change"
|
|
|
225
225
|
|
|
226
226
|
- After saving contribution-branch work, use \`lumine suggest branch "Ready for review"\`
|
|
227
227
|
when the user wants to notify the project owner. Use \`lumine suggest thumbnail\`
|
|
228
|
-
when the branch's current thumbnail should be offered to the owner.
|
|
228
|
+
when the branch's current thumbnail should be offered to the owner. Only the
|
|
229
|
+
owner can rename the project: when the user wants a different app name, use
|
|
230
|
+
\`lumine suggest title "New Name" --note "why"\` instead of renaming.
|
|
229
231
|
- On an owned canonical team project, \`lumine suggestions\` lists the owner's
|
|
230
|
-
currently open branch and
|
|
231
|
-
id it prints with \`lumine suggestions merge\`, \`replace-main\`,
|
|
232
|
-
\`adopt-thumbnail\`; do not infer an action from stale
|
|
232
|
+
currently open branch, thumbnail and name suggestions. Act on the exact
|
|
233
|
+
suggestion id it prints with \`lumine suggestions merge\`, \`replace-main\`,
|
|
234
|
+
\`adopt-thumbnail\` or \`adopt-title\`; do not infer an action from stale
|
|
235
|
+
local branch state.
|
|
233
236
|
|
|
234
237
|
## Assets (Runtime Media)
|
|
235
238
|
|
package/lib/sdk.js
CHANGED
|
@@ -22,6 +22,12 @@ export const SDK_CLI_METHODS = {
|
|
|
22
22
|
"aiCards.get": { path: "api/content/ai-card", scopes: ["content:read"] },
|
|
23
23
|
"grammarbles.listQuestions": { path: "api/content/grammarbles/questions", scopes: ["content:read"] },
|
|
24
24
|
"grammarbles.getMyQuestionHistory": { path: "api/content/grammarbles/history", scopes: ["content:read"] },
|
|
25
|
+
"minecraft.getWorlds": { path: "api/minecraft/worlds", scopes: ["content:read"] },
|
|
26
|
+
"minecraft.getOnlinePlayers": { path: "api/minecraft/players", scopes: ["content:read"] },
|
|
27
|
+
"minecraft.getZero": { path: "api/minecraft/zero", scopes: ["content:read"] },
|
|
28
|
+
"minecraft.getZeroBuilds": { path: "api/minecraft/zero/builds", scopes: ["content:read"] },
|
|
29
|
+
"minecraft.getPeople": { path: "api/minecraft/people", scopes: ["content:read"] },
|
|
30
|
+
"minecraft.setPlayerRole": { path: "api/minecraft/people/role", scopes: ["content:write"], write: true },
|
|
25
31
|
"subjects.getMySubjects": { path: "api/content/my-subjects", scopes: ["content:read"] },
|
|
26
32
|
"subjects.search": { path: "api/content/subjects/search", scopes: ["content:read"] },
|
|
27
33
|
"subjects.getSubject": { path: "api/content/subject", scopes: ["content:read"] },
|
|
@@ -204,7 +210,11 @@ export const SDK_CLI_METHODS = {
|
|
|
204
210
|
operation: "claim",
|
|
205
211
|
scopes: ["rewards:claim"],
|
|
206
212
|
write: true,
|
|
207
|
-
mapArgs: (args) => ({
|
|
213
|
+
mapArgs: (args) => ({
|
|
214
|
+
challengeId: args.challengeId,
|
|
215
|
+
answers: args.answers,
|
|
216
|
+
...(args.completionToken !== undefined ? { completionToken: args.completionToken } : {}),
|
|
217
|
+
}),
|
|
208
218
|
},
|
|
209
219
|
// Leaderboards use the public leaderboard routes (regular login auth, no
|
|
210
220
|
// build API token). args.boardKey selects the board; remaining args are
|
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Build SDK Index
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
4
|
-
Updated: 2026-09-
|
|
5
|
-
Generated: 2026-09-
|
|
3
|
+
Version: 1.49.0
|
|
4
|
+
Updated: 2026-09-23
|
|
5
|
+
Generated: 2026-09-23T16:01:40.924Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -36,7 +36,7 @@ Generated: 2026-09-23T00:59:59.504Z
|
|
|
36
36
|
- The creator's agent designs the rewards. Declare the economy in a project file `rewards.json` at the root: budgets (userDailyXP, userDailyCoins, optional userDailyClaims; there is no app-wide daily or lifetime budget, only what one learner can earn per day) and rules [{ id, title, xp, coins, verifier: 'numeric-quiz' | 'completion', maxAttempts?, retry?: { xpPercent, coinsPercent, paidAttempts? }, minSeconds? (completion), progression?: 'dated' | 'until-earned' (quiz) }]. Wire the matching Twinkle.rewards calls with those literal rule ids. Questions and answer keys NEVER go in project files (published source is readable by every player): quiz rules get them from the private question sheet uploaded with `lumine rewards sheet <file.json>` ({ rules: { <ruleId>: { questions?, sets? } } }); `lumine rewards check` validates both together. A review request freezes the code and proposes rewards.json merged with the sheet; the administrator reads the code, checks the amounts and whether the app is exploitable, may change any amount, and approves. Creators are kids and teens: show approval status and one Send for review action; do not ask them to fill in technical forms. Every code update that retains rewards needs a new approval before publishing. Removing the SDK automatically clears its gate. Apps read amounts, tries and sets from getStatus, never from their own file.
|
|
37
37
|
- Verifiers: 'numeric-quiz' pays for server-checked numeric answers (retry share, attempt limits, dated sets or until-earned sets that stay up until somebody earns them, after-answer guides). 'completion' pays when the app reports an activity finished — a cleared stage, a finished round — at least minSeconds after start({ ruleId }); the server checks only the elapsed time, once per learner per site day (UTC midnight), and the budgets. Call start when the activity begins and claim({ challengeId }) with no answers when it ends; keep completion amounts and userDailyXP small enough that a player scripting the calls would not matter, because nothing else is verified.
|
|
38
38
|
- Numeric quiz answers are verified on the server; client scores, privateDb state, timers and completion booleans are not verified reward evidence. Limits reset at UTC midnight. Rules are earned once per viewer per UTC day; attempt limits and retry payouts come from the approved rule. Challenges expire at the UTC day boundary. Budgets apply across release changes.
|
|
39
|
-
- Optional reward rule controls: maxLifetimeClaims caps one learner’s receipts for that rule across every day and release; completionProof: classic-tower-v1 requires a server-simulated Classic Tower finish in addition to minSeconds. These are server-enforced controls. Existing completion rules without completionProof still verify elapsed time only. Registered proof profiles also include breadface-v1, breadface-v2 and breadface-v3 (server-simulated Breadface inputs; each is one reviewed release's exact physics, chosen by the reviewer)
|
|
39
|
+
- Optional reward rule controls: maxLifetimeClaims caps one learner’s receipts for that rule across every day and release; completionProof: classic-tower-v1 requires a server-simulated Classic Tower finish in addition to minSeconds. These are server-enforced controls. Existing completion rules without completionProof still verify elapsed time only. Registered proof profiles also include breadface-v1, breadface-v2 and breadface-v3 (server-simulated Breadface inputs; each is one reviewed release's exact physics, chosen by the reviewer) study-record-v1 (a private study record reviewed by JEV, billed to the learner’s AI Energy), and Groove Lab's groove-lab-song-v1 (a song the learner published today, checked on the server for length, notes and originality) and groove-lab-heard-v1 (three established accounts finished the learner's songs today). Profiles are platform-owned; an app cannot invent a verifier or authorize its own reward.
|
|
40
40
|
|
|
41
41
|
## AI decision design
|
|
42
42
|
- When planning a new app or an improvement, consider whether model-based judgments would materially improve the requested experience. Twinkle.ai.decide runs JEV, a model for narrow decisions over supplied text or structured state. Potential uses include interpreting a player's request to an NPC, choosing among legal game actions, classifying user content, ranking supplied candidates, and adapting an activity to evidence about the learner. These are examples to reason from, not a keyword checklist or a requirement to add AI to every app.
|
|
@@ -761,6 +761,47 @@ const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode:
|
|
|
761
761
|
- A quiet edition with no editorial events does not call an AI provider and does not consume AI Energy.
|
|
762
762
|
- A failed attempt may be queued again on the same day. A ready edition is immutable for ordinary viewers.
|
|
763
763
|
|
|
764
|
+
### Twinkle.minecraft
|
|
765
|
+
- async getWorlds() | scopes: content:read
|
|
766
|
+
- Returns: { worlds: [{ id, name, dimension, spawn: { x, z } | null, map: { tileUrlTemplate, minZoom, maxZoom, tilePixels, blocksPerTileAtMaxZoom, renderedAt } | null }], stale }
|
|
767
|
+
- List the server's worlds (world, world1, world2, world_nether, world_the_end) with squaremap tile info for drawing a top-down map.
|
|
768
|
+
- Tiles are 512x512 PNGs over HTTPS from www.twinklemc.site. At maxZoom one pixel is one block; blocksPerTile = blocksPerTileAtMaxZoom * 2 ** (maxZoom - z); tile x/y = floor(block x/z / blocksPerTile). Fill {z}, {x}, {y}. map.renderedAt tells how fresh the tiles are.
|
|
769
|
+
- Limited rollout: apps not enabled yet get 403 with code minecraft_sdk_not_enabled.
|
|
770
|
+
- stale: true means the Minecraft server was briefly unreachable and the data is a recent cached copy.
|
|
771
|
+
- Example: const { worlds } = await Twinkle.minecraft.getWorlds(); const w = worlds.find((x) => x.id === 'world'); const z = w.map.maxZoom; const blocksPerTile = w.map.blocksPerTileAtMaxZoom * 2 ** (w.map.maxZoom - z); const tx = Math.floor(blockX / blocksPerTile), ty = Math.floor(blockZ / blocksPerTile); const url = w.map.tileUrlTemplate.replace('{z}', z).replace('{x}', tx).replace('{y}', ty);
|
|
772
|
+
- async getOnlinePlayers() | scopes: content:read
|
|
773
|
+
- Returns: { players: [{ name, world, x, y, z, isZero }], count, stale }
|
|
774
|
+
- List players currently online with their world and block coordinates. Zero appears with isZero: true and is not counted in count.
|
|
775
|
+
- Cached for about 5 seconds; poll no faster than every 5 seconds.
|
|
776
|
+
- Names are Minecraft usernames, not Twinkle usernames.
|
|
777
|
+
- Example: const { players, count } = await Twinkle.minecraft.getOnlinePlayers();
|
|
778
|
+
- async getZero() | scopes: content:read
|
|
779
|
+
- Returns: { zero: { online, world, position: { x, y, z } | null, activity: 'idle'|'thinking'|'building'|'workshop', currentBuild: { id, title, status, placed, total, requester, world, bounds } | null, queue: [build], workshop: { world, min, max } | null, updatedAt }, stale }
|
|
780
|
+
- Where Zero is and what he is doing right now, including the build in progress and its progress.
|
|
781
|
+
- Cached for about 3 seconds.
|
|
782
|
+
- bounds are { min: [x, y, z], max: [x, y, z] } in world block coordinates.
|
|
783
|
+
- Example: const { zero } = await Twinkle.minecraft.getZero(); if (zero.currentBuild) console.log(zero.currentBuild.title, zero.currentBuild.placed + '/' + zero.currentBuild.total);
|
|
784
|
+
- async getZeroBuilds({ limit, kind } = {}) | scopes: content:read
|
|
785
|
+
- Returns: { builds: [{ id, kind: 'helper'|'workshop', title, status, placed, total, requester, world, bounds, createdAt }], stale }
|
|
786
|
+
- Zero's recent builds, newest first: builds for players (helper) and his own workshop projects (workshop).
|
|
787
|
+
- limit is 1-50 (default 10). kind filters to helper or workshop builds.
|
|
788
|
+
- Example: const { builds } = await Twinkle.minecraft.getZeroBuilds({ limit: 10, kind: 'workshop' });
|
|
789
|
+
- async getPeople() | scopes: content:read
|
|
790
|
+
- Returns: { canManage, people: [{ uuid, name, role: 'visitor'|'member'|'builder'|'moderator', groups, op, online, banned, protected, firstSeenAt, lastSeenAt, isZero }], roles }
|
|
791
|
+
- Everyone who has joined the server with their in-game role, for the server owner's role management screen.
|
|
792
|
+
- Only the server owner, in an app they own, gets the list; everyone else gets { canManage: false, people: [] } without an error, so hide the feature when canManage is false.
|
|
793
|
+
- Roles: visitor (play and chat), member (/tpa, /home, /back, may ask Zero to build), builder (member + /fly and creative/survival), moderator (builder + teleport others, CoreProtect rollback, /kick). op: true players are server operators and have every power regardless of role.
|
|
794
|
+
- protected: true players (the owner and Zero's account) can't be changed from the app. Sorted online first, then most recently seen.
|
|
795
|
+
- Not cached; call on screen open or after a change, not on a timer.
|
|
796
|
+
- Example: const { canManage, people } = await Twinkle.minecraft.getPeople(); if (!canManage) hidePeopleTab();
|
|
797
|
+
- async setPlayerRole({ uuid, role }) | scopes: content:write
|
|
798
|
+
- Returns: { player: { uuid, name, role, op } }
|
|
799
|
+
- Change a player's in-game role; it applies immediately, even while they are online.
|
|
800
|
+
- Server owner only, in an app they own; others get 403 with code minecraft_roles_forbidden.
|
|
801
|
+
- role is visitor, member, builder or moderator. 400 codes: minecraft_bad_uuid, minecraft_bad_role, minecraft_protected_player, minecraft_unknown_player. 503 minecraft_unavailable while the server restarts.
|
|
802
|
+
- Every change is logged on the server and emailed to the owner; the player is told in game.
|
|
803
|
+
- Example: await Twinkle.minecraft.setPlayerRole({ uuid: person.uuid, role: 'builder' });
|
|
804
|
+
|
|
764
805
|
### Twinkle.leaderboards
|
|
765
806
|
- async get({ boardKey = 'default', limit, cursor } = {}) | scopes: none
|
|
766
807
|
- Returns: { entries: [{ rank, id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt }], scores, cursor, hasMore, personalBest: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null }
|
|
@@ -1050,7 +1091,7 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1050
1091
|
- Errors: build_reward_not_scheduled when the rule has no questions for today; build_reward_daily_claims_reached when the viewer already earned today’s cap. attemptsRemaining is null for unlimited rules.
|
|
1051
1092
|
- 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).
|
|
1052
1093
|
- 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.
|
|
1053
|
-
- 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.
|
|
1094
|
+
- 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 and groove-lab-heard-v1, start returns completion { profile }; there is no progress step.
|
|
1054
1095
|
- await Twinkle.rewards.progress({ challengeId, completionToken?, frames?, record?, requestId? }) | scopes: rewards:claim
|
|
1055
1096
|
- Returns: { mode: "live" | "preview", completion: { profile, token?, completed, failed?, decision?, message?, maxFrames? }, aiUsagePolicy? }
|
|
1056
1097
|
- Verify a bounded batch of inputs for an approved server-simulated climb.
|
|
@@ -1066,7 +1107,7 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1066
1107
|
- A wrong answer within two seconds of the previous one is refused with build_reward_throttled (HTTP 429) and does not count; wait for the person to try again rather than retry-looping.
|
|
1067
1108
|
- Completion rules take no answers: claim({ challengeId }) when the activity is finished. build_reward_too_fast (HTTP 409) means fewer than minSeconds passed since start; show nothing and let play continue. In preview mode the receipt carries preview: true and nothing is paid.
|
|
1068
1109
|
- A completionProof rule also requires the signed completionToken from a successful rewards.progress response. The server simulates the registered game physics and must reach the goal. A timer, forged position, client win flag, altered inventory or token from another viewer, challenge or release cannot authorize payment. maxLifetimeClaims is enforced from receipts in the same award transaction. build_reward_lifetime_claims_reached means all rewards for this rule have been collected; do not retry it.
|
|
1069
|
-
- 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.
|
|
1110
|
+
- 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. 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.
|
|
1070
1111
|
- await Twinkle.rewards.getLeaderboard({ metric?: "xp" | "coins", period?: "day" | "week" | "all", limit? }) | scopes: rewards:claim
|
|
1071
1112
|
- 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 }
|
|
1072
1113
|
- 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
|
@@ -2997,7 +2997,21 @@ Mikey"** section carrying only
|
|
|
2997
2997
|
the deltas and anomalies worth his time, next to the escalation list. Never
|
|
2998
2998
|
dump raw sections at him.
|
|
2999
2999
|
|
|
3000
|
-
### Jev serving and audits (standing duty, every full daily review; updated 2026-09-
|
|
3000
|
+
### Jev serving and audits (standing duty, every full daily review; updated 2026-09-23)
|
|
3001
|
+
|
|
3002
|
+
**September 23, 2026: LLM audits retired.** Mikey judged the Jev experiment a
|
|
3003
|
+
success and asked to drop the LLM comparison on served routes. Comments, chat
|
|
3004
|
+
routing and Lumine Auto no longer run any background LLM audit; the LLM runs
|
|
3005
|
+
only when Jev cannot decide. From that day, audit coverage, paired/served
|
|
3006
|
+
disagreements and the `jev_reply_gate_audit`, `jev_chat_routing_audit` and
|
|
3007
|
+
`jev_lumine_model_audit` operations read zero by design; do not report that as
|
|
3008
|
+
missing evidence. Report fallback rates and reasons instead. Auto rows served
|
|
3009
|
+
without a comparison carry `baselineStatus: not_run`; its fallback LLM spend is
|
|
3010
|
+
`lumine_model_fallback`. The eight comparison-only routing families below are
|
|
3011
|
+
unchanged. Chat routing is also off since September 23
|
|
3012
|
+
(`JEV_CHAT_ROUTING_ENABLED`, default off; Jev served ~1% of chats and could not
|
|
3013
|
+
judge when a reply needs older history), so zero chat serving rows is expected.
|
|
3014
|
+
The rest of this section describes the pre-September-23 pilot.
|
|
3001
3015
|
|
|
3002
3016
|
Read `data.jevPilot` from `lumine admin brief --json` and carry it into
|
|
3003
3017
|
the full report for Mikey. The active `daily-run report --json` also includes
|
|
@@ -4430,8 +4444,8 @@ shown above are the canonical interface.
|
|
|
4430
4444
|
|
|
4431
4445
|
September 21 routing expansion: read `jevRoutingShadow` alongside `jevPilot` in
|
|
4432
4446
|
both the admin brief and full daily report. The reviewed `chat-routing-v3` path
|
|
4433
|
-
is primary; report actual selection latency, chat baseline calls avoided
|
|
4434
|
-
|
|
4447
|
+
is primary; report actual selection latency, chat baseline calls avoided and
|
|
4448
|
+
fallback reasons (no audits since September 23). New routing
|
|
4435
4449
|
families remain comparison-only until Mikey reviews each one and explicitly
|
|
4436
4450
|
promotes it. Report every registered family, including zero-sample and skipped
|
|
4437
4451
|
families; no traffic is not a pass. Show paired counts, exact differing fields,
|
|
@@ -4444,15 +4458,16 @@ proof an action was performed. Do not call agreement accuracy or infer speedups
|
|
|
4444
4458
|
from shadow timings. Full daily costs must include these operations once only.
|
|
4445
4459
|
|
|
4446
4460
|
September 21 Auto exception: Mikey approved JEV as Lumine Auto's primary model
|
|
4447
|
-
selector immediately
|
|
4461
|
+
selector immediately. Its per-choice LLM comparison was retired September 23;
|
|
4462
|
+
the LLM runs only as the fallback when JEV cannot decide.
|
|
4448
4463
|
Auto is the new default; stored manual preferences remain manual. Review
|
|
4449
4464
|
`jevRoutingShadow` / `byRoute.lumine_model` separately from the eight
|
|
4450
4465
|
comparison-only families: selected model/effort, both decisions, exact selection
|
|
4451
4466
|
context, confidence, fallback, actual selection latency, missing evidence and
|
|
4452
|
-
observed task outcome. Report `jev_lumine_model_serve` and the
|
|
4453
|
-
`
|
|
4454
|
-
double-counting. JEV choice confidence
|
|
4455
|
-
|
|
4467
|
+
observed task outcome. Report `jev_lumine_model_serve` and the fallback
|
|
4468
|
+
`lumine_model_fallback` spend separately, using canonical AI-cost totals without
|
|
4469
|
+
double-counting. JEV choice confidence is not a correctness score. The other
|
|
4470
|
+
eight families still require review before promotion.
|
|
4456
4471
|
|
|
4457
4472
|
|
|
4458
4473
|
### September 21 verified reward follow-up
|