@stage5/lumine 0.2.15 → 0.2.17
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/README.md +5 -0
- package/lib/api.js +15 -0
- package/lib/commands.js +65 -1
- package/lib/constants.js +3 -1
- package/lib/workspace.js +17 -4
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +40 -16
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ npx @stage5/lumine@latest new --title "Daily Reflection App" --description "Priv
|
|
|
10
10
|
npx @stage5/lumine@latest rename "My New Build Title"
|
|
11
11
|
npx @stage5/lumine@latest rename "New Title" --target 123
|
|
12
12
|
npx @stage5/lumine@latest projects
|
|
13
|
+
npx @stage5/lumine@latest branches 884
|
|
13
14
|
npx @stage5/lumine@latest explore --sort forks
|
|
14
15
|
npx @stage5/lumine@latest reference https://www.twin-kle.com/app/123
|
|
15
16
|
npx @stage5/lumine@latest fork https://www.twin-kle.com/app/123
|
|
@@ -35,6 +36,10 @@ pulling the owner's main project creates or reuses your contribution branch and
|
|
|
35
36
|
checks out that branch locally. Saves go to your branch, so the project owner
|
|
36
37
|
can merge or replace main from Twinkle.
|
|
37
38
|
|
|
39
|
+
Use `lumine branches <build-url-or-id>` to list the contribution branches you
|
|
40
|
+
can review, including each contributor, branch number, status, and URL. Then use
|
|
41
|
+
`lumine diff <branch-url>` to inspect one branch.
|
|
42
|
+
|
|
38
43
|
Use `lumine explore` to list public open-source Build apps that can be used as
|
|
39
44
|
examples or starting points. It supports `--search` and `--sort forks`,
|
|
40
45
|
`--sort popular`, or `--sort recent`. Use `lumine reference <build-url-or-id>`
|
package/lib/api.js
CHANGED
|
@@ -30,6 +30,21 @@ export async function listOpenSourceBuilds({ options, auth }) {
|
|
|
30
30
|
return Array.isArray(result.builds) ? result.builds : [];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export async function listContributionBranches({
|
|
34
|
+
options,
|
|
35
|
+
auth,
|
|
36
|
+
buildId,
|
|
37
|
+
limit,
|
|
38
|
+
}) {
|
|
39
|
+
const url = new URL(`${options.apiUrl}/build/${buildId}/contributions`);
|
|
40
|
+
url.searchParams.set("limit", String(limit));
|
|
41
|
+
return await requestJson({
|
|
42
|
+
url: url.toString(),
|
|
43
|
+
authToken: auth.token,
|
|
44
|
+
timeoutMs: options.timeoutMs,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
export async function loadBuildMetadata({ options, auth, buildId }) {
|
|
34
49
|
const result = await loadBuildFiles({
|
|
35
50
|
options,
|
package/lib/commands.js
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
loadBuildVersionFiles,
|
|
32
32
|
loadBuildVersions,
|
|
33
33
|
loadContributionDiff,
|
|
34
|
+
listContributionBranches,
|
|
34
35
|
loadLumineCliVersionInfo,
|
|
35
36
|
loadOpenSourceBuildFiles,
|
|
36
37
|
maybeCheckForLumineCliUpdate,
|
|
@@ -76,6 +77,7 @@ import {
|
|
|
76
77
|
collectProjectLimitFindings,
|
|
77
78
|
findLocalProjectMetadata,
|
|
78
79
|
isIndexHtmlPath,
|
|
80
|
+
isReadOnlyProjectMetadata,
|
|
79
81
|
isReadOnlyReferenceMetadata,
|
|
80
82
|
removeLocalProjectFilesNotIn,
|
|
81
83
|
stashLocalProjectFilesBeforePull,
|
|
@@ -130,6 +132,10 @@ export async function main() {
|
|
|
130
132
|
await projects(options);
|
|
131
133
|
return;
|
|
132
134
|
}
|
|
135
|
+
if (options.command === "branches") {
|
|
136
|
+
await branches(options);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
133
139
|
if (options.command === "explore") {
|
|
134
140
|
await explore(options);
|
|
135
141
|
return;
|
|
@@ -263,7 +269,8 @@ export async function renameBuild(options) {
|
|
|
263
269
|
await saveSelectedBuild({ options, auth, build: updatedBuild });
|
|
264
270
|
if (
|
|
265
271
|
localProject?.rootDir &&
|
|
266
|
-
Number(localProject.metadata?.buildId || 0) === buildId
|
|
272
|
+
Number(localProject.metadata?.buildId || 0) === buildId &&
|
|
273
|
+
!isReadOnlyProjectMetadata(localProject.metadata)
|
|
267
274
|
) {
|
|
268
275
|
await writeProjectMetadata({
|
|
269
276
|
dir: localProject.rootDir,
|
|
@@ -303,6 +310,33 @@ export async function projects(options) {
|
|
|
303
310
|
printBuildList(builds);
|
|
304
311
|
}
|
|
305
312
|
|
|
313
|
+
export async function branches(options) {
|
|
314
|
+
const auth = await resolveAuth(options);
|
|
315
|
+
const requestedBuildId = await resolveRequiredBuildIdOrSelected(
|
|
316
|
+
options,
|
|
317
|
+
auth,
|
|
318
|
+
);
|
|
319
|
+
const requestedBuild = await loadBuildMetadata({
|
|
320
|
+
options,
|
|
321
|
+
auth,
|
|
322
|
+
buildId: requestedBuildId,
|
|
323
|
+
});
|
|
324
|
+
const rootBuildId =
|
|
325
|
+
Number(requestedBuild.contributionRootBuildId || 0) ||
|
|
326
|
+
Number(requestedBuild.id || 0);
|
|
327
|
+
const rootBuild =
|
|
328
|
+
rootBuildId === Number(requestedBuild.id || 0)
|
|
329
|
+
? requestedBuild
|
|
330
|
+
: await loadBuildMetadata({ options, auth, buildId: rootBuildId });
|
|
331
|
+
const result = await listContributionBranches({
|
|
332
|
+
options,
|
|
333
|
+
auth,
|
|
334
|
+
buildId: rootBuildId,
|
|
335
|
+
limit: options.limit,
|
|
336
|
+
});
|
|
337
|
+
printContributionBranches({ result, rootBuild, options });
|
|
338
|
+
}
|
|
339
|
+
|
|
306
340
|
export async function explore(options) {
|
|
307
341
|
const auth = await resolveAuth(options);
|
|
308
342
|
const builds = await listOpenSourceBuilds({ options, auth });
|
|
@@ -1618,6 +1652,34 @@ export function printContributionDiff({ result, build }) {
|
|
|
1618
1652
|
}
|
|
1619
1653
|
}
|
|
1620
1654
|
|
|
1655
|
+
export function printContributionBranches({ result, rootBuild, options }) {
|
|
1656
|
+
const contributions = Array.isArray(result?.contributions)
|
|
1657
|
+
? result.contributions
|
|
1658
|
+
: [];
|
|
1659
|
+
console.log(`Branches for ${formatBuildTitle(rootBuild)}:`);
|
|
1660
|
+
if (!contributions.length) {
|
|
1661
|
+
console.log("No contribution branches.");
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
for (const [index, contribution] of contributions.entries()) {
|
|
1665
|
+
const branchNumber = Number(contribution.contributionBranchNumber || 0);
|
|
1666
|
+
const branchLabel = branchNumber
|
|
1667
|
+
? `branch ${branchNumber}`
|
|
1668
|
+
: `branch #${contribution.id}`;
|
|
1669
|
+
const title = String(contribution.title || "Untitled branch").trim();
|
|
1670
|
+
const contributor = String(contribution.username || "unknown").trim();
|
|
1671
|
+
const status = String(contribution.contributionStatus || "draft").trim();
|
|
1672
|
+
console.log(
|
|
1673
|
+
`${index + 1}. ${title} - ${contributor} - ${branchLabel} (${status})`,
|
|
1674
|
+
);
|
|
1675
|
+
if (branchNumber) {
|
|
1676
|
+
console.log(
|
|
1677
|
+
` ${options.siteUrl}/build/${Number(rootBuild.id)}/${branchNumber}`,
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1621
1683
|
export function printUpdateFromMainResult({ result, build, dir, mergedFiles }) {
|
|
1622
1684
|
const branchNumber = Number(build?.contributionBranchNumber || 0) || 0;
|
|
1623
1685
|
const branchLabel = branchNumber
|
|
@@ -1981,6 +2043,7 @@ export function printHelp() {
|
|
|
1981
2043
|
lumine new [title]
|
|
1982
2044
|
lumine rename [title] [--target <twinkle-build-url-or-id>]
|
|
1983
2045
|
lumine projects
|
|
2046
|
+
lumine branches [twinkle-build-url-or-id] [--limit <n>]
|
|
1984
2047
|
lumine explore [search terms]
|
|
1985
2048
|
lumine select [twinkle-build-url]
|
|
1986
2049
|
lumine pull [twinkle-build-url]
|
|
@@ -2016,6 +2079,7 @@ Examples:
|
|
|
2016
2079
|
npx @stage5/lumine@latest rename "My New Build Title"
|
|
2017
2080
|
npx @stage5/lumine@latest rename "New Title" --target 123
|
|
2018
2081
|
npx @stage5/lumine@latest new --title "Daily Reflection App" --description "Private journal with streaks"
|
|
2082
|
+
npx @stage5/lumine@latest branches 884
|
|
2019
2083
|
npx @stage5/lumine@latest explore --sort forks
|
|
2020
2084
|
npx @stage5/lumine@latest reference https://www.twin-kle.com/app/123
|
|
2021
2085
|
npx @stage5/lumine@latest fork https://www.twin-kle.com/app/123
|
package/lib/constants.js
CHANGED
|
@@ -118,7 +118,7 @@ Use these current source-of-truth rules:
|
|
|
118
118
|
- Use Twinkle.sharedDb for shared multi-user JSON data.
|
|
119
119
|
- Use Twinkle.aiCards.list/search/get for existing public AI Card words, exampleText sentence material, and word levels.
|
|
120
120
|
- Use Twinkle.aiStories.list/search/get for existing AI Story passage text, story media, and questions.
|
|
121
|
-
- Use Twinkle.ai.chat with history entries shaped as { role, content }, not { text }.
|
|
121
|
+
- Use Twinkle.ai.chat with history entries shaped as { role, content }, not { text }. Live web search is enabled by default; pass webSearch: false to disable it for the app.
|
|
122
122
|
- Use Twinkle.preview for canvas, WebGL, Three.js, fullscreen, and game layout.
|
|
123
123
|
- Prefer existing documented Twinkle.* methods over guessing names from old code.
|
|
124
124
|
`;
|
|
@@ -308,6 +308,7 @@ export const AGENT_INSTRUCTION_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
|
308
308
|
export const MAIN_CHECKOUT_READONLY_COMMANDS = new Set([
|
|
309
309
|
"pull",
|
|
310
310
|
"check",
|
|
311
|
+
"branches",
|
|
311
312
|
"diff",
|
|
312
313
|
"sdk",
|
|
313
314
|
"select",
|
|
@@ -326,6 +327,7 @@ export const COMMANDS = new Set([
|
|
|
326
327
|
"new",
|
|
327
328
|
"rename",
|
|
328
329
|
"projects",
|
|
330
|
+
"branches",
|
|
329
331
|
"explore",
|
|
330
332
|
"select",
|
|
331
333
|
"pull",
|
package/lib/workspace.js
CHANGED
|
@@ -779,14 +779,15 @@ export function resolveProjectDirForSave({ options, localProject }) {
|
|
|
779
779
|
export function assertLocalProjectCanBeSaved(localProject) {
|
|
780
780
|
const metadata = localProject?.metadata;
|
|
781
781
|
if (!metadata) return;
|
|
782
|
-
|
|
782
|
+
const readOnlyKind = readOnlyProjectMetadataKind(metadata);
|
|
783
|
+
if (readOnlyKind === "main") {
|
|
783
784
|
const rootBuildId =
|
|
784
785
|
Number(metadata.buildId || 0) || Number(metadata.build?.id || 0) || 0;
|
|
785
786
|
throw new Error(
|
|
786
787
|
`This is a read-only checkout of main${rootBuildId ? ` for Build ${rootBuildId}` : ""}. Make edits in your branch workspace (\`lumine pull${rootBuildId ? ` ${rootBuildId}` : ""}\`), and run \`lumine update-from-main\` there to bring main's changes into it.`,
|
|
787
788
|
);
|
|
788
789
|
}
|
|
789
|
-
if (
|
|
790
|
+
if (readOnlyKind === "version") {
|
|
790
791
|
const checkoutBuildId =
|
|
791
792
|
Number(metadata.buildId || 0) || Number(metadata.build?.id || 0) || 0;
|
|
792
793
|
const checkoutVersion = Number(metadata.checkoutVersion || 0) || 0;
|
|
@@ -794,7 +795,7 @@ export function assertLocalProjectCanBeSaved(localProject) {
|
|
|
794
795
|
`This is a read-only checkout of a previous save${checkoutVersion ? ` (v${checkoutVersion})` : ""}${checkoutBuildId ? ` for Build ${checkoutBuildId}` : ""}. To bring this save back, run \`lumine restore${checkoutVersion ? ` ${checkoutVersion}` : " <n>"}\` from the editable workspace, then \`lumine save\`.`,
|
|
795
796
|
);
|
|
796
797
|
}
|
|
797
|
-
if (
|
|
798
|
+
if (readOnlyKind === "reference") {
|
|
798
799
|
const sourceBuildId =
|
|
799
800
|
Number(metadata.reference?.sourceBuildId || 0) ||
|
|
800
801
|
Number(metadata.buildId || 0) ||
|
|
@@ -804,7 +805,7 @@ export function assertLocalProjectCanBeSaved(localProject) {
|
|
|
804
805
|
`This is a read-only Lumine reference${sourceBuildId ? ` for Build ${sourceBuildId}` : ""}. Run \`lumine fork${sourceBuildId ? ` ${sourceBuildId}` : ""}\` to create an editable workspace.`,
|
|
805
806
|
);
|
|
806
807
|
}
|
|
807
|
-
if (
|
|
808
|
+
if (readOnlyKind === "server") {
|
|
808
809
|
throw new Error(
|
|
809
810
|
"This Lumine checkout is read-only for the current CLI login. Pull or diff it for review; project-owner branch edits must go through merge or replace-main.",
|
|
810
811
|
);
|
|
@@ -819,6 +820,18 @@ export function isReadOnlyReferenceMetadata(metadata) {
|
|
|
819
820
|
);
|
|
820
821
|
}
|
|
821
822
|
|
|
823
|
+
export function isReadOnlyProjectMetadata(metadata) {
|
|
824
|
+
return readOnlyProjectMetadataKind(metadata) !== null;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
export function readOnlyProjectMetadataKind(metadata) {
|
|
828
|
+
if (metadata?.mainCheckout === true) return "main";
|
|
829
|
+
if (metadata?.versionCheckout === true) return "version";
|
|
830
|
+
if (isReadOnlyReferenceMetadata(metadata)) return "reference";
|
|
831
|
+
if (metadata?.build?.canWrite === false) return "server";
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
|
|
822
835
|
export function resolveLocalProjectFilePath({ rootDir, projectPath }) {
|
|
823
836
|
const relativePath = projectPathToRelativePath(projectPath);
|
|
824
837
|
const root = path.resolve(rootDir);
|
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-07-
|
|
5
|
-
Generated: 2026-07-
|
|
3
|
+
Version: 1.29.0
|
|
4
|
+
Updated: 2026-07-31
|
|
5
|
+
Generated: 2026-07-31T05:39:44.540Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -12,6 +12,7 @@ Generated: 2026-07-23T05:57:27.486Z
|
|
|
12
12
|
- Match storage to update frequency: privateDb and sharedDb are for LOW-frequency durable state that changes on a user action. NEVER write per-frame/per-tick state to them (camera or cursor position, animation, live movement, presence, autosave every frame/tick). Keep live state in client memory, broadcast realtime/presence via Twinkle.world, and flush only occasional durable snapshots (on an interval or on exit, never per frame). The server enforces per-key write rate limits and returns 429 on excess; never retry-loop a 429.
|
|
13
13
|
- Use Twinkle.userDb only for advanced private SQLite needs such as tables, indexes, many rows, filtered queries, or aggregates.
|
|
14
14
|
- Use Twinkle.leaderboards for public Build scoreboards. Signed-in viewers are ranked by Twinkle username; guests can submit with a display name.
|
|
15
|
+
- Use Twinkle.news to read the globally shared Twinkle Daily edition or let a signed-in viewer queue today's edition. The server permits only one ready edition per Twinkle day and generation does not spend the requesting viewer's AI Energy.
|
|
15
16
|
- Use Twinkle.sharedDb for LOW-frequency durable shared multi-user state such as guestbooks, votes, room settings, submitted records, and append-only run history. It is NOT for high-frequency or per-frame/per-tick writes; keep live/realtime state in Twinkle.world or client memory. The server rate-limits writes and returns 429.
|
|
16
17
|
- Use Twinkle.subjects.search for in-app subject pickers. Twinkle.mount remains an optional host-provided preselection/context shortcut, not a data API.
|
|
17
18
|
- Use Twinkle.aiCards for read-only existing public AI Card words and example texts, including word levels for typing games.
|
|
@@ -21,6 +22,7 @@ Generated: 2026-07-23T05:57:27.486Z
|
|
|
21
22
|
- Use Twinkle.world for realtime multiplayer rooms, avatar presence, movement, emotes, and lightweight actions; world sessions are disposable and durable MMO state belongs in sharedDb/privateDb.
|
|
22
23
|
- Use Twinkle.characters.chat for real Zero/Ciel NPC dialogue with shared room context and AI Energy-aware thinking modes.
|
|
23
24
|
- Twinkle.ai.chat history entries must use { role, content }; map local message.text fields to content before passing history.
|
|
25
|
+
- Live web search is enabled by default for Twinkle.ai.chat and for Medium/High Twinkle.ai.generateObject and Twinkle.characters.chat requests. App authors can pass webSearch: false to disable it for their app. Search uses the provider's live web-search tool and is included in AI Energy usage; structured and character Lite Mode remains tool-free.
|
|
24
26
|
- Interface text must not be selectable on touch devices: apply user-select: none plus -webkit-user-select: none and -webkit-touch-callout: none to interface text (HUD, buttons, labels, menus, scores, game controls) so mobile long-press does not highlight UI. Keep text inputs and genuinely user-copyable content selectable.
|
|
25
27
|
- Build app tab mute is enforced by the host runtime automatically for standard media elements and Web Audio connections to AudioContext.destination. Apps with custom audio engines can also observe Twinkle.onAudioMuteChange and check Twinkle.isAudioMuted.
|
|
26
28
|
|
|
@@ -281,31 +283,34 @@ console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
|
|
|
281
283
|
- async listPrompts() | scopes: none
|
|
282
284
|
- Returns: Array<{ id, title, description }>
|
|
283
285
|
- Legacy helper. Twinkle.ai.chat does not require promptId for default runtime text generation.
|
|
284
|
-
- async chat({ promptId, message, history, systemPrompt, requestId, onText, onStatus } = {}) | scopes: none
|
|
285
|
-
- Returns: { text, response, model, aiUsagePolicy }
|
|
286
|
-
- Generate text with the default Lumine text model, optionally streaming text updates through onText.
|
|
286
|
+
- async chat({ promptId, message, history, systemPrompt, webSearch, requestId, onText, onStatus } = {}) | scopes: none
|
|
287
|
+
- Returns: { text, response, model, webSearch, aiUsagePolicy }
|
|
288
|
+
- Generate text with the default Lumine text model, optionally using live web search and streaming text updates through onText.
|
|
287
289
|
- Signed-in viewers only.
|
|
288
290
|
- Uses Grok 4.5 by default.
|
|
289
291
|
- Each successful text generation consumes AI Energy from the signed-in viewer.
|
|
290
292
|
- history must be an array of { role: 'user' | 'assistant', content: string }. Twinkle.ai.chat does not read a text field.
|
|
291
293
|
- The server keeps the latest 12 valid history entries.
|
|
292
294
|
- Pass systemPrompt to define the app AI's personality, tone, role, or response rules.
|
|
295
|
+
- Live web search is enabled by default, and the model decides whether searching is useful. Pass webSearch: false to disable it for the app.
|
|
296
|
+
- When streaming, onStatus may receive searching_web while the provider is searching.
|
|
293
297
|
- Pass onText to receive streaming accumulated text before the final result resolves.
|
|
294
|
-
- AI Energy is recorded after provider success when final token usage
|
|
298
|
+
- AI Energy is recorded after provider success when final token and web-search tool usage are available.
|
|
295
299
|
- Use this for in-app AI replies instead of creating or fetching app-local endpoints such as /api/chat.
|
|
296
300
|
- Example: const chatHistory = conversation.slice(-12).map((entry) => ({ role: entry.role === 'assistant' ? 'assistant' : 'user', content: entry.text }));
|
|
297
301
|
const result = await Twinkle.ai.chat({ message, history: chatHistory, systemPrompt: 'You are a cheerful pirate helper who answers in one sentence.', onText: (text, meta) => renderReply(text), onStatus: (status) => setThinking(status === 'thinking') });
|
|
298
|
-
- async generateObject({ prompt, expectedStructure, thinkingMode, mode, instructions, systemPrompt } = {}) | scopes: none
|
|
299
|
-
- Returns: { object, result, model, provider, thinkingMode, requestedThinkingMode, aiUsagePolicy }
|
|
300
|
-
- Generate a validated structured JSON object for app decisions, routing, grading, and game-state logic.
|
|
302
|
+
- async generateObject({ prompt, expectedStructure, thinkingMode, mode, instructions, systemPrompt, webSearch } = {}) | scopes: none
|
|
303
|
+
- Returns: { object, result, model, provider, thinkingMode, requestedThinkingMode, webSearch, aiUsagePolicy }
|
|
304
|
+
- Generate a validated structured JSON object for app decisions, routing, grading, and game-state logic, optionally using live web search.
|
|
301
305
|
- Signed-in viewers only.
|
|
302
306
|
- Use this instead of asking Twinkle.ai.chat to return JSON.
|
|
303
307
|
- expectedStructure must be a JSON object that describes the exact returned object shape.
|
|
304
308
|
- mode is accepted as an alias for thinkingMode, and mid is accepted as an alias for medium.
|
|
305
309
|
- thinkingMode low uses GPT-5.6 Luna and records free low-energy usage.
|
|
306
|
-
- thinkingMode medium uses
|
|
307
|
-
- thinkingMode high uses
|
|
310
|
+
- thinkingMode medium uses Grok 4.5 with medium reasoning and normal AI Energy while AI Energy remains.
|
|
311
|
+
- thinkingMode high uses GPT-5.6 Sol with high reasoning and high AI Energy while AI Energy remains.
|
|
308
312
|
- If medium or high is requested after AI Energy is empty, the server falls back to low and returns thinkingMode: low.
|
|
313
|
+
- Live web search is enabled by default in Medium and High modes. Pass webSearch: false to disable it for the app. Omitted/default search turns off automatically when the request falls back to tool-free Lite Mode; explicitly forcing webSearch: true in Lite Mode returns an error.
|
|
309
314
|
- The SDK validates shape and retries malformed JSON, but app code should still validate business-specific enum values.
|
|
310
315
|
- Example: const { object } = await Twinkle.ai.generateObject({ thinkingMode: 'medium', prompt: 'Classify the player intent from: ' + playerText, expectedStructure: { action: 'string', targetCharacter: 'string', confidence: 0, shouldAskFollowUp: false } });
|
|
311
316
|
- onChatStatus(listener) | scopes: none
|
|
@@ -337,9 +342,9 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
|
|
|
337
342
|
- Example: const unsubscribe = Twinkle.ai.onImageGenerationStatus((status) => console.log(status.stage));
|
|
338
343
|
|
|
339
344
|
### Twinkle.characters
|
|
340
|
-
- async chat({ character, thinkingMode, message, history, roomContext, scene, systemPrompt, instructions, includeWebsiteContext, requestId, onText, onStatus } = {}) | scopes: none
|
|
341
|
-
- Returns: { text, response, character, aiUsername, thinkingMode, requestedThinkingMode, includeWebsiteContext, model, provider, aiUsagePolicy }
|
|
342
|
-
- Talk to Zero or Ciel from a Build app,
|
|
345
|
+
- async chat({ character, thinkingMode, message, history, roomContext, scene, systemPrompt, instructions, includeWebsiteContext, webSearch, requestId, onText, onStatus } = {}) | scopes: none
|
|
346
|
+
- Returns: { text, response, character, aiUsername, thinkingMode, requestedThinkingMode, includeWebsiteContext, webSearch, model, provider, aiUsagePolicy }
|
|
347
|
+
- Talk to Zero or Ciel from a Build app, optionally using live web search and streaming RPG-style dialogue text with onText/onStatus.
|
|
343
348
|
- Signed-in viewers only.
|
|
344
349
|
- character must be zero or ciel.
|
|
345
350
|
- Recommended history shape is { role: 'user' | 'assistant', content: string, speaker?: string }; content is the canonical text field.
|
|
@@ -348,10 +353,13 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
|
|
|
348
353
|
- Pass onText/onStatus for streaming dialogue. Omit callbacks for non-streaming dialogue where the promise resolves with the final response.
|
|
349
354
|
- thinkingMode low uses Lite Mode: Zero uses Grok 4.5 with low reasoning and Ciel uses Claude Haiku 4.5; usage is recorded as free low-energy usage.
|
|
350
355
|
- thinkingMode medium uses normal AI Energy: Zero uses Grok 4.5 with medium reasoning and Ciel uses Claude Sonnet 5 while AI Energy remains.
|
|
351
|
-
- thinkingMode high uses high AI Energy: Zero uses Grok 4.5 with high reasoning and Ciel uses Claude Opus
|
|
356
|
+
- thinkingMode high uses high AI Energy: Zero uses Grok 4.5 with high reasoning and Ciel uses Claude Opus 5 with extended thinking while AI Energy remains.
|
|
352
357
|
- If medium or high is requested after AI Energy is empty, the server falls back to low and returns thinkingMode: low.
|
|
353
358
|
- Pass roomContext as a short shared scene transcript so Zero and Ciel can know what happened in the same room.
|
|
354
359
|
- includeWebsiteContext defaults to true. Set includeWebsiteContext: false for in-world NPC dialogue that should only use Zero/Ciel's basic character identity plus your scene/instructions.
|
|
360
|
+
- Live web search is enabled by default in Medium and High modes. Pass webSearch: false to disable it for the app. Omitted/default search turns off automatically when the request falls back to tool-free Lite Mode; explicitly forcing webSearch: true in Lite Mode returns an error.
|
|
361
|
+
- includeWebsiteContext controls Twinkle persona context and is unrelated to webSearch.
|
|
362
|
+
- When streaming, onStatus may receive searching_web while the provider is searching.
|
|
355
363
|
- Use this for real Zero/Ciel NPCs instead of pretending with Twinkle.ai.chat systemPrompt.
|
|
356
364
|
- Example: const dialogueHistory = recentTurns.slice(-16).map((entry) => ({ role: entry.role === 'assistant' ? 'assistant' : 'user', content: entry.text, speaker: entry.speaker }));
|
|
357
365
|
const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode: thinkHard ? 'high' : 'medium', message: playerText, history: dialogueHistory, roomContext, scene: { location: 'classroom', nearbyCharacters: ['zero', 'ciel'] }, includeWebsiteContext: false, onText: (text) => renderDialogue(text) });
|
|
@@ -519,6 +527,22 @@ const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode:
|
|
|
519
527
|
- Atomic step 3: fetches likes/replies aggregates for provided IDs.
|
|
520
528
|
- Accepts either an array of IDs or an object like { ids: [...] }.
|
|
521
529
|
|
|
530
|
+
### Twinkle.news
|
|
531
|
+
- async getCurrentEdition() | scopes: none
|
|
532
|
+
- Returns: { dayIndex, nextEditionAt, generationStatus, edition: { id, dayIndex, status, coverageStartedAt, coverageEndedAt, sourceEventCount, edition, model, provider, generatedAt } | null, pendingEdition }
|
|
533
|
+
- Read today's shared Twinkle newspaper, or the latest ready edition while today's is being generated.
|
|
534
|
+
- Works for signed-in viewers and public-build guests.
|
|
535
|
+
- generationStatus is available, pending, generating, ready, or failed.
|
|
536
|
+
- While today's edition is pending, edition remains the latest ready shared edition and pendingEdition describes today's queued work.
|
|
537
|
+
- Poll gently while generation is pending; once every 5-10 seconds is sufficient.
|
|
538
|
+
- async generateCurrentEdition() | scopes: none
|
|
539
|
+
- Returns: { dayIndex, nextEditionAt, generationStatus, edition, pendingEdition }
|
|
540
|
+
- Atomically queue the current Twinkle day's globally shared edition.
|
|
541
|
+
- Requires a signed-in viewer.
|
|
542
|
+
- The first request for a Twinkle day creates the canonical pending edition; concurrent and later requests return that same server state.
|
|
543
|
+
- Generation is platform-sponsored and does not consume the requesting viewer's AI Energy.
|
|
544
|
+
- A failed attempt may be queued again on the same day. A ready edition is immutable until the next Twinkle day.
|
|
545
|
+
|
|
522
546
|
### Twinkle.leaderboards
|
|
523
547
|
- async get({ boardKey = 'default', limit, cursor } = {}) | scopes: none
|
|
524
548
|
- 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 }
|