@stage5/lumine 0.2.14 → 0.2.15
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 +2 -0
- package/lib/api.js +15 -1
- package/lib/commands.js +70 -3
- package/lib/constants.js +1 -0
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +36 -5
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@ npx @stage5/lumine@latest
|
|
|
7
7
|
npx @stage5/lumine@latest login
|
|
8
8
|
npx @stage5/lumine@latest new "Daily Reflection App"
|
|
9
9
|
npx @stage5/lumine@latest new --title "Daily Reflection App" --description "Private journal with streaks"
|
|
10
|
+
npx @stage5/lumine@latest rename "My New Build Title"
|
|
11
|
+
npx @stage5/lumine@latest rename "New Title" --target 123
|
|
10
12
|
npx @stage5/lumine@latest projects
|
|
11
13
|
npx @stage5/lumine@latest explore --sort forks
|
|
12
14
|
npx @stage5/lumine@latest reference https://www.twin-kle.com/app/123
|
package/lib/api.js
CHANGED
|
@@ -290,12 +290,26 @@ export async function updateBuildThumbnailUrl({
|
|
|
290
290
|
auth,
|
|
291
291
|
buildId,
|
|
292
292
|
thumbnailUrl,
|
|
293
|
+
}) {
|
|
294
|
+
return updateBuildMetadata({
|
|
295
|
+
options,
|
|
296
|
+
auth,
|
|
297
|
+
buildId,
|
|
298
|
+
patch: { thumbnailUrl },
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function updateBuildMetadata({
|
|
303
|
+
options,
|
|
304
|
+
auth,
|
|
305
|
+
buildId,
|
|
306
|
+
patch,
|
|
293
307
|
}) {
|
|
294
308
|
return requestJson({
|
|
295
309
|
method: "PUT",
|
|
296
310
|
url: `${options.apiUrl}/build/${buildId}`,
|
|
297
311
|
authToken: auth.token,
|
|
298
|
-
body:
|
|
312
|
+
body: patch,
|
|
299
313
|
timeoutMs: options.timeoutMs,
|
|
300
314
|
});
|
|
301
315
|
}
|
package/lib/commands.js
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
replaceMainWithContribution,
|
|
41
41
|
resolveBranchBuild,
|
|
42
42
|
saveProjectFiles,
|
|
43
|
+
updateBuildMetadata,
|
|
43
44
|
} from "./api.js";
|
|
44
45
|
import { assetsCommand, writeAssetsManifest } from "./assets.js";
|
|
45
46
|
import { thumbnailCommand } from "./thumbnail.js";
|
|
@@ -121,6 +122,10 @@ export async function main() {
|
|
|
121
122
|
await newBuild(options);
|
|
122
123
|
return;
|
|
123
124
|
}
|
|
125
|
+
if (options.command === "rename") {
|
|
126
|
+
await renameBuild(options);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
124
129
|
if (options.command === "projects") {
|
|
125
130
|
await projects(options);
|
|
126
131
|
return;
|
|
@@ -221,6 +226,58 @@ export async function newBuild(options) {
|
|
|
221
226
|
printNewBuildResult({ createResult, pullResult: result });
|
|
222
227
|
}
|
|
223
228
|
|
|
229
|
+
export async function renameBuild(options) {
|
|
230
|
+
const title = String(options.title || "").trim();
|
|
231
|
+
if (!title) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
'Pass a title: `lumine rename "My New Build Title"` or `lumine rename --title "My New Build Title"`.',
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const auth = await ensureAuth(options);
|
|
237
|
+
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
238
|
+
const localProject = await findLocalProjectMetadata(
|
|
239
|
+
path.resolve(options.dir || process.cwd()),
|
|
240
|
+
);
|
|
241
|
+
const buildId = await resolveRequiredBuildIdOrSelected(options, auth, {
|
|
242
|
+
localProject,
|
|
243
|
+
});
|
|
244
|
+
const currentBuild = await loadBuildMetadata({ options, auth, buildId });
|
|
245
|
+
if (currentBuild.canWrite === false) {
|
|
246
|
+
throw new Error(`You cannot rename Build #${buildId}.`);
|
|
247
|
+
}
|
|
248
|
+
if (Number(currentBuild.contributionRootBuildId || 0) > 0) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
"Contribution branches use the original Build title and cannot be renamed.",
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const result = await updateBuildMetadata({
|
|
254
|
+
options,
|
|
255
|
+
auth,
|
|
256
|
+
buildId,
|
|
257
|
+
patch: { title },
|
|
258
|
+
});
|
|
259
|
+
if (result?.success !== true || !result?.build) {
|
|
260
|
+
throw new Error(result?.error || "Failed to rename the Build.");
|
|
261
|
+
}
|
|
262
|
+
const updatedBuild = { ...currentBuild, ...result.build };
|
|
263
|
+
await saveSelectedBuild({ options, auth, build: updatedBuild });
|
|
264
|
+
if (
|
|
265
|
+
localProject?.rootDir &&
|
|
266
|
+
Number(localProject.metadata?.buildId || 0) === buildId
|
|
267
|
+
) {
|
|
268
|
+
await writeProjectMetadata({
|
|
269
|
+
dir: localProject.rootDir,
|
|
270
|
+
options,
|
|
271
|
+
build: updatedBuild,
|
|
272
|
+
manifest: localProject.metadata.manifest || null,
|
|
273
|
+
pulledAt: localProject.metadata.pulledAt || null,
|
|
274
|
+
lastSavedAt: localProject.metadata.lastSavedAt || null,
|
|
275
|
+
filesHash: localProject.metadata.filesHash || null,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
console.log(`Renamed Build #${buildId} to "${updatedBuild.title}".`);
|
|
279
|
+
}
|
|
280
|
+
|
|
224
281
|
export async function workspace(options) {
|
|
225
282
|
const auth = await ensureAuth(options);
|
|
226
283
|
const selectedBuild = options.target
|
|
@@ -1693,10 +1750,16 @@ export function parseArgs(args) {
|
|
|
1693
1750
|
quality: raw.quality ? String(raw.quality) : "",
|
|
1694
1751
|
assetName: raw.name ? String(raw.name) : "",
|
|
1695
1752
|
out: raw.out ? String(raw.out) : "",
|
|
1696
|
-
target:
|
|
1753
|
+
target:
|
|
1754
|
+
raw.url ||
|
|
1755
|
+
raw.target ||
|
|
1756
|
+
(command === "rename" ? "" : positional[0] || ""),
|
|
1697
1757
|
title:
|
|
1698
1758
|
String(
|
|
1699
|
-
raw.title ||
|
|
1759
|
+
raw.title ||
|
|
1760
|
+
(command === "new" || command === "rename"
|
|
1761
|
+
? positional.join(" ")
|
|
1762
|
+
: ""),
|
|
1700
1763
|
).trim() || "",
|
|
1701
1764
|
description: Object.prototype.hasOwnProperty.call(raw, "description")
|
|
1702
1765
|
? String(raw.description || "").trim()
|
|
@@ -1916,6 +1979,7 @@ export function printHelp() {
|
|
|
1916
1979
|
lumine whoami
|
|
1917
1980
|
lumine logout
|
|
1918
1981
|
lumine new [title]
|
|
1982
|
+
lumine rename [title] [--target <twinkle-build-url-or-id>]
|
|
1919
1983
|
lumine projects
|
|
1920
1984
|
lumine explore [search terms]
|
|
1921
1985
|
lumine select [twinkle-build-url]
|
|
@@ -1949,6 +2013,8 @@ Examples:
|
|
|
1949
2013
|
npx @stage5/lumine@latest
|
|
1950
2014
|
npx @stage5/lumine@latest login
|
|
1951
2015
|
npx @stage5/lumine@latest new "Daily Reflection App"
|
|
2016
|
+
npx @stage5/lumine@latest rename "My New Build Title"
|
|
2017
|
+
npx @stage5/lumine@latest rename "New Title" --target 123
|
|
1952
2018
|
npx @stage5/lumine@latest new --title "Daily Reflection App" --description "Private journal with streaks"
|
|
1953
2019
|
npx @stage5/lumine@latest explore --sort forks
|
|
1954
2020
|
npx @stage5/lumine@latest reference https://www.twin-kle.com/app/123
|
|
@@ -1983,9 +2049,10 @@ Options:
|
|
|
1983
2049
|
--auth-file <path> Saved login path
|
|
1984
2050
|
--auth-token <token> Override saved login
|
|
1985
2051
|
--dir <path> Directory for pulled project files
|
|
2052
|
+
--target <build> Explicit Build URL or ID for rename (positionals are title-only)
|
|
1986
2053
|
--main With pull/versions/restore: target the team project's main
|
|
1987
2054
|
--version <n> With pull: read-only checkout of previous save v<n>
|
|
1988
|
-
--title <text>
|
|
2055
|
+
--title <text> Build title for new/rename
|
|
1989
2056
|
--description <text> Optional New Build description
|
|
1990
2057
|
--no-description Skip the New Build description prompt
|
|
1991
2058
|
--summary <text> Save summary
|
package/lib/constants.js
CHANGED
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.27.0
|
|
4
|
+
Updated: 2026-07-21
|
|
5
|
+
Generated: 2026-07-23T05:57:27.486Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -25,7 +25,7 @@ Generated: 2026-07-10T05:52:15.007Z
|
|
|
25
25
|
- 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
26
|
|
|
27
27
|
## Token Scopes
|
|
28
|
-
files:read, user:read, users:read, dailyReflections:read, content:read, sharedDb:read, sharedDb:write, privateDb:read, privateDb:write, files:write, chat:read, chat:write, notifications:read, notifications:write, notifications:emit, reminders:read, reminders:write
|
|
28
|
+
files:read, user:read, users:read, dailyReflections:read, content:read, content:write, sharedDb:read, sharedDb:write, privateDb:read, privateDb:write, files:write, chat:read, chat:write, notifications:read, notifications:write, notifications:emit, reminders:read, reminders:write
|
|
29
29
|
|
|
30
30
|
## Namespaces
|
|
31
31
|
|
|
@@ -80,6 +80,14 @@ files:read, user:read, users:read, dailyReflections:read, content:read, sharedDb
|
|
|
80
80
|
- The parent validates that the target is still a Build preview URL before navigating.
|
|
81
81
|
- External URLs are rejected and do not receive the Build bridge nonce.
|
|
82
82
|
- Example: await Twinkle.app.navigate('./arena.html');
|
|
83
|
+
- async openContent(target) | scopes: none
|
|
84
|
+
- Returns: { success, url }
|
|
85
|
+
- Open a recognized Twinkle content page in the parent app from a viewer click or tap.
|
|
86
|
+
- Call this directly from a user click or tap handler; calls without an active user action are rejected.
|
|
87
|
+
- The trusted parent displays the canonical destination and requires viewer confirmation before navigating.
|
|
88
|
+
- Only recognized Twinkle content URLs are accepted. The parent preserves its current signed-in origin when opening the content.
|
|
89
|
+
- Use navigate() for routes inside the current Build and openContent() for Twinkle subjects, comments, apps, profiles, and other content pages.
|
|
90
|
+
- Example: await Twinkle.app.openContent('https://www.twin-kle.com/subjects/432');
|
|
83
91
|
|
|
84
92
|
### Twinkle.preview
|
|
85
93
|
- getLayout() | scopes: none
|
|
@@ -310,6 +318,9 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
|
|
|
310
318
|
- Generate or edit an image from a prompt and optional base64/data-URL reference image.
|
|
311
319
|
- Signed-in viewers only.
|
|
312
320
|
- Each successful image generation consumes AI Energy from the signed-in viewer.
|
|
321
|
+
- Call generateImage directly from an explicit viewer action such as a button click. Calls from page load, timers, background work, or programmatic retries are rejected.
|
|
322
|
+
- Twinkle shows a host-owned confirmation for every generation. One approval authorizes exactly one request.
|
|
323
|
+
- Only one image generation may be active at a time. Do not queue or automatically retry cancellation, ai_image_generation_in_progress, USER_ACTIVATION_REQUIRED, or 429 errors.
|
|
313
324
|
- Default engine is openai and default quality is high.
|
|
314
325
|
- The SDK timeout defaults to 390000ms for image generation because high-quality image runs can exceed normal request timing.
|
|
315
326
|
- Pass onStatus to receive real-time stages from the backend: prompt_ready, in_progress, generating, partial_image, completed, and error.
|
|
@@ -382,6 +393,19 @@ const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode:
|
|
|
382
393
|
- Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp }], cursor? }
|
|
383
394
|
- Returns only the current viewer's own comments on the given subject.
|
|
384
395
|
- Supports cursor-based pagination. Pass cursor from previous response to load more.
|
|
396
|
+
- async getWriteStatus({ subjectId, commentId } = {}) | scopes: content:read
|
|
397
|
+
- Returns: { writeStatus: { serverNow, subjectCreate, commentCreate, subjectEdit, commentEdit } }
|
|
398
|
+
- Each operation slice is { cooldownSeconds, availableAt, retryAfterSeconds }.
|
|
399
|
+
- serverNow is unix seconds so progress bars ignore client clock skew.
|
|
400
|
+
- Pass subjectId/commentId to include per-target edit cooldowns.
|
|
401
|
+
- async create({ title, description }) | scopes: content:write
|
|
402
|
+
- Returns: { subject, writeStatus }
|
|
403
|
+
- Creates a normal site subject (title + description only in v1).
|
|
404
|
+
- Site-wide durable cooldown: 600s between creates. 429 includes writeStatus.
|
|
405
|
+
- async edit({ subjectId, title, description }) | scopes: content:write
|
|
406
|
+
- Returns: { subject, writeStatus }
|
|
407
|
+
- Own subjects only (userId === uploader). Never uses moderator edit rights.
|
|
408
|
+
- Per-subject edit cooldown: 10s.
|
|
385
409
|
|
|
386
410
|
### Twinkle.aiCards
|
|
387
411
|
- async list({ limit, cursor, level, minLevel, maxLevel, quality, userId, hasImage, hasExample } = {}) | scopes: content:read
|
|
@@ -468,6 +492,13 @@ const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode:
|
|
|
468
492
|
- For subject-poster books that include poster replies, use author: subjectPoster, includeReplies: true, and replyScope: ownThread so the poster's replies to other people do not become pages.
|
|
469
493
|
- Supports cursor-based pagination. Pass cursor from the previous response to load more.
|
|
470
494
|
- Example: const { subjects } = await Twinkle.subjects.search({ query: searchText, limit: 12 }); const subjectId = pickedSubject.id; const page = await Twinkle.subjectComments.list(subjectId, { sortBy: 'oldest', author: 'subjectPoster', includeReplies: true, replyScope: 'ownThread', limit: 50 });
|
|
495
|
+
- async create({ subjectId, content }) | scopes: content:write
|
|
496
|
+
- Returns: { comment, writeStatus }
|
|
497
|
+
- Adds a top-level subject comment (book page). Own subject only.
|
|
498
|
+
- Site-wide durable cooldown: 20s between comment creates. 429 includes writeStatus.
|
|
499
|
+
- async edit({ commentId, content }) | scopes: content:write
|
|
500
|
+
- Returns: { comment, writeStatus }
|
|
501
|
+
- Own comments only. Per-comment edit cooldown: 10s.
|
|
471
502
|
|
|
472
503
|
### Twinkle.profileComments
|
|
473
504
|
- async getProfileComments({ profileUserId, limit, offset, sortBy, includeReplies, range, since, until } = {}) | scopes: content:read
|
|
@@ -540,7 +571,7 @@ const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode:
|
|
|
540
571
|
- Update a viewer-owned shared row, optionally notifying safe recipients from the canonical write.
|
|
541
572
|
- Updates an entry. Only the entry creator or the build owner can update.
|
|
542
573
|
- data must be a JSON object, max 10 KB.
|
|
543
|
-
- notify may include eventKey, label, summary, recipients, and target. Supported recipients include { kind: 'sharedDbEntryAuthor', entryId }.
|
|
574
|
+
- notify may include eventKey, label, summary, recipients, and target. Supported recipients include { kind: 'sharedDbEntryAuthor', entryId } and { kind: 'subjectAuthor', subjectId } (subject must be referenced by this build via a subject-linked sharedDb entry).
|
|
544
575
|
- async deleteEntry(entryId) | scopes: sharedDb:write
|
|
545
576
|
- Returns: { success: true }
|
|
546
577
|
- Deletes an entry. Only the entry creator or the build owner can delete.
|