@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 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: { thumbnailUrl },
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: raw.url || raw.target || positional[0] || "",
1753
+ target:
1754
+ raw.url ||
1755
+ raw.target ||
1756
+ (command === "rename" ? "" : positional[0] || ""),
1697
1757
  title:
1698
1758
  String(
1699
- raw.title || (command === "new" ? positional.join(" ") : ""),
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> New Build title
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
@@ -324,6 +324,7 @@ export const COMMANDS = new Set([
324
324
  "logout",
325
325
  "whoami",
326
326
  "new",
327
+ "rename",
327
328
  "projects",
328
329
  "explore",
329
330
  "select",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,8 +1,8 @@
1
1
  # Build SDK Index
2
2
 
3
- Version: 1.26.3
4
- Updated: 2026-07-10
5
- Generated: 2026-07-10T05:52:15.007Z
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.