@stage5/lumine 0.2.52 → 0.2.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/README.md +63 -1
- package/lib/admin.js +421 -7
- package/lib/agent/providers/claude-code.js +104 -3
- package/lib/agent/providers/codex.js +118 -14
- package/lib/agent.js +9 -1
- package/lib/commands.js +77 -27
- package/lib/constants.js +1 -0
- package/lib/sdk.js +39 -0
- package/lib/sponsor.js +1291 -0
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +122 -7
- package/sdk/LUMINE_ADMIN.md +264 -6
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-08-
|
|
5
|
-
Generated: 2026-08-
|
|
3
|
+
Version: 1.38.2
|
|
4
|
+
Updated: 2026-08-27
|
|
5
|
+
Generated: 2026-08-27T12:11:09.595Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -26,9 +26,13 @@ Generated: 2026-08-26T15:17:11.027Z
|
|
|
26
26
|
- 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.
|
|
27
27
|
- 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.
|
|
28
28
|
- 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.
|
|
29
|
+
- Use Twinkle.media for camera photos and camera-only two-second clips. Twinkle confirms each capture or paid processing action. Clips are processed to canonical 480p MP4 assets before they become visible; use sharedDb or privateDb to publish/store the returned asset metadata.
|
|
30
|
+
- Static media published through sharedDb is app-owned feed data. A public user-generated feed must provide a visible report flow and owner removal, and must not claim that Twinkle globally moderates those posts.
|
|
31
|
+
- Use Twinkle.live for one-way app livestreams and Twinkle.chat for the accompanying thread. Free livestreams require a verified host, end after at most 15 minutes, and issue at most 10 private viewer grants. Twinkle keeps platform-owned live-status/end controls above active hosts, so app code cannot hide or replace the broadcaster's Stop path.
|
|
32
|
+
- Media Energy is separate from AI Energy. Replace Media Energy UI only from canonical mediaEnergy/getUsage responses; never decrement, reserve, or synthesize it in app code.
|
|
29
33
|
|
|
30
34
|
## Token Scopes
|
|
31
|
-
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
|
|
35
|
+
files:read, media:read, media:write, live:read, live:write, 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
|
|
32
36
|
|
|
33
37
|
## Namespaces
|
|
34
38
|
|
|
@@ -282,7 +286,7 @@ console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
|
|
|
282
286
|
- Simple visible same-origin images can still use normal browser anchors with href and download.
|
|
283
287
|
- Example: await Twinkle.files.saveAs({ fileName: 'fashion-guide.png', dataUrl: imageUrl, mimeType: 'image/png' });
|
|
284
288
|
- async uploadGenerated({ fileName, url, dataUrl, data, text, json, bytes, blob, file, mimeType } = {}) | scopes: files:write
|
|
285
|
-
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
|
|
289
|
+
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, mediaKind, durationMs, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
|
|
286
290
|
- Upload an app-generated file to Twinkle-hosted cloud storage without opening a picker, then store the returned asset refs in sharedDb/privateDb/userDb.
|
|
287
291
|
- Signed-in viewers only.
|
|
288
292
|
- Uploads generated blobs, files, bytes, data URLs, or fetchable URLs to Twinkle-hosted cloud storage.
|
|
@@ -290,7 +294,7 @@ console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
|
|
|
290
294
|
- Store the returned asset metadata in sharedDb/privateDb/userDb instead of storing raw file bytes in a DB record.
|
|
291
295
|
- Example: const { assets } = await Twinkle.files.uploadGenerated({ fileName: 'fashion-guide.png', dataUrl: generatedImageUrl, mimeType: 'image/png' });
|
|
292
296
|
- async pickAndUpload({ accept, multiple } = {}) | scopes: files:write
|
|
293
|
-
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
|
|
297
|
+
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, mediaKind, durationMs, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
|
|
294
298
|
- Pick supported local files and upload them to Twinkle-hosted cloud storage, then store the returned asset refs in sharedDb/privateDb/userDb.
|
|
295
299
|
- Signed-in viewers only.
|
|
296
300
|
- Uploads to Twinkle-hosted cloud storage and returns asset references.
|
|
@@ -299,7 +303,7 @@ console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
|
|
|
299
303
|
- Store the returned asset metadata in sharedDb/privateDb/userDb instead of storing raw file bytes in a DB record.
|
|
300
304
|
- Example: const { assets, canceled } = await Twinkle.files.pickAndUpload({ accept: 'image/*,.pdf', multiple: true });
|
|
301
305
|
- async list({ cursor, limit } = {}) | scopes: files:read
|
|
302
|
-
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], nextCursor, usage: { totalBytes, fileCount, maxRuntimeFileStorageBytes, remainingBytes } | null }
|
|
306
|
+
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, mediaKind, durationMs, uploadedByUserId, createdAt }], nextCursor, usage: { totalBytes, fileCount, maxRuntimeFileStorageBytes, remainingBytes } | null }
|
|
303
307
|
- List the current viewer's uploaded runtime files for this build.
|
|
304
308
|
- Signed-in viewers only.
|
|
305
309
|
- Lists the current viewer's ready uploads for this build only.
|
|
@@ -311,6 +315,117 @@ console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
|
|
|
311
315
|
- Deletes one of the current viewer's uploaded runtime files and updates quota usage.
|
|
312
316
|
- Example: await Twinkle.files.delete(assetId);
|
|
313
317
|
|
|
318
|
+
### Twinkle.media
|
|
319
|
+
- async capturePhoto({ facingMode?, maxWidth?, quality?, settleMs?, fileName? } = {}) | scopes: files:write
|
|
320
|
+
- Returns: { asset, assets, failed }
|
|
321
|
+
- Ask for camera permission, capture one JPEG photo, and upload it to the current viewer's Twinkle file storage.
|
|
322
|
+
- Signed-in viewers only. Call from an explicit viewer action; Twinkle shows its own one-action confirmation before the browser may show camera permission.
|
|
323
|
+
- The photo is saved in the viewer's Twinkle file storage. The returned asset is canonical server state and can be stored in sharedDb/privateDb/userDb.
|
|
324
|
+
- Example: const { asset } = await Twinkle.media.capturePhoto({ facingMode: 'user' });
|
|
325
|
+
if (asset) await Twinkle.sharedDb.addEntry('photos', asset);
|
|
326
|
+
- async recordClip({ previewElement?, facingMode?, fileName?, waitForReady?, timeoutMs? } = {}) | scopes: media:write
|
|
327
|
+
- Returns: { clip: { id, status, durationMs, failureCode, asset }, mediaEnergy }
|
|
328
|
+
- Record a camera-only short video, upload it, and by default wait for the canonical two-second 480p MP4 asset.
|
|
329
|
+
- Call from an explicit viewer action. Twinkle confirms each recording before requesting camera permission.
|
|
330
|
+
- The recording is camera-only; use Twinkle.live when audio is part of the experience.
|
|
331
|
+
- The server targets a two-second maximum input window and processes it to 480p MP4. Encoder frame boundaries can differ by one frame; app code cannot raise the limit.
|
|
332
|
+
- By default this method polls confirmed server state until ready. Pass waitForReady: false to receive the processing ID immediately, then call getClip().
|
|
333
|
+
- Example: const { clip } = await Twinkle.media.recordClip({ previewElement: '#cameraPreview' });
|
|
334
|
+
await Twinkle.sharedDb.addEntry('clips', clip.asset);
|
|
335
|
+
- async uploadClip({ file?, blob?, fileName?, mimeType?, requestId?, waitForReady?, timeoutMs? }) | scopes: media:write
|
|
336
|
+
- Returns: { clip: { id, status, durationMs, failureCode, asset }, mediaEnergy }
|
|
337
|
+
- Upload a generated or selected video through the same server-bounded two-second clip pipeline.
|
|
338
|
+
- Call from an explicit viewer action. Twinkle confirms each selected or generated video before upload and paid processing.
|
|
339
|
+
- Input is limited to 8 MB. The canonical output is a server-produced 480p MP4 targeting a two-second maximum, with at most a frame of encoder-boundary variance.
|
|
340
|
+
- Use a stable requestId when retrying the same user action.
|
|
341
|
+
- Example: const result = await Twinkle.media.uploadClip({ file: recordedFile });
|
|
342
|
+
- async getClip(assetId) | scopes: media:read
|
|
343
|
+
- Returns: { clip: { id, status, durationMs, failureCode, asset }, mediaEnergy }
|
|
344
|
+
- Load and reconcile canonical processing state for one of the current viewer's clips.
|
|
345
|
+
- Example: const { clip } = await Twinkle.media.getClip(assetId);
|
|
346
|
+
- async listClips({ cursor?, limit? } = {}) | scopes: media:read
|
|
347
|
+
- Returns: { assets, nextCursor, mediaEnergy }
|
|
348
|
+
- List the current viewer's ready short clips for this Build app.
|
|
349
|
+
- Example: const { assets } = await Twinkle.media.listClips({ limit: 20 });
|
|
350
|
+
- async getUsage() | scopes: media:read
|
|
351
|
+
- Returns: { monthKey, resetsAt, global, user, build, energyPercent, energySegments, energySegmentsRemaining }
|
|
352
|
+
- Load canonical current Media Energy for this viewer and app.
|
|
353
|
+
- Replace displayed state only from this response or a newer mediaEnergy response. Never decrement or synthesize the battery locally.
|
|
354
|
+
- global.carryoverMicroUsd accounts for reservations that crossed the UTC month boundary so the global reset cannot double the budget.
|
|
355
|
+
- Example: const mediaEnergy = await Twinkle.media.getUsage();
|
|
356
|
+
renderBattery(mediaEnergy.energyPercent);
|
|
357
|
+
|
|
358
|
+
### Twinkle.live
|
|
359
|
+
- async start({ previewElement?, facingMode?, audio?, durationSeconds?, maxViewers?, saveReplay?, requestId? } = {}) | scopes: live:write
|
|
360
|
+
- Returns: { session: { id, replayId, buildId, hostUserId, status, maxViewers, viewersGranted, durationSeconds, saveReplay, updatedAt, hardEndsAt }, mediaEnergy }
|
|
361
|
+
- Create an IVS channel, attach the camera/microphone, begin broadcasting, and optionally save a seven-day replay.
|
|
362
|
+
- Call from an explicit viewer action. Twinkle confirms each new broadcast before camera/microphone permission or paid channel creation.
|
|
363
|
+
- saveReplay defaults to false. When true, the same action confirmation says the stream will be saved, and Twinkle records it to private storage for seven days after it becomes ready.
|
|
364
|
+
- Replay storage is included in the live Media Energy reservation. Replay viewing has its own Media Energy reservation.
|
|
365
|
+
- previewElement must be a canvas element or selector because the IVS Broadcast SDK draws a composited preview.
|
|
366
|
+
- Free sessions broadcast at 854x480 and are capped server-side at 15 minutes and 10 private viewer grants. Lower durationSeconds/maxViewers values are allowed.
|
|
367
|
+
- Twinkle confirms that a platform-owned live indicator and End stream action are present before returning broadcast credentials to the app, and keeps the control until server cleanup is canonically terminal. Fullscreen and Picture-in-Picture are unavailable while hosting so that Stop control stays visible.
|
|
368
|
+
- The SDK does not include broadcast credentials in its returned value. Credentials are ephemeral, and the SDK stops local broadcasting at hardEndsAt while the API independently stops and deletes the IVS channel.
|
|
369
|
+
- Example: const { session } = await Twinkle.live.start({ previewElement: '#broadcastPreview', audio: true });
|
|
370
|
+
await Twinkle.sharedDb.setKvItems('live', [{ key: 'current', value: session }]);
|
|
371
|
+
- async list() | scopes: live:read
|
|
372
|
+
- Returns: Array<LiveSession>
|
|
373
|
+
- List currently available livestream sessions for this Build app.
|
|
374
|
+
- Only sessions canonically acknowledged as live are listed; channels still being prepared are never advertised to viewers.
|
|
375
|
+
- Example: const sessions = await Twinkle.live.list();
|
|
376
|
+
- async get(sessionId) | scopes: live:read
|
|
377
|
+
- Returns: LiveSession | null
|
|
378
|
+
- Load canonical server status for a livestream in this Build app.
|
|
379
|
+
- Example: const session = await Twinkle.live.get(sessionId);
|
|
380
|
+
- async watch(sessionId, { videoElement, requestId? }) | scopes: live:write
|
|
381
|
+
- Returns: { session, viewerGrantId, mediaEnergy, playbackStarted }
|
|
382
|
+
- Use a private single-use playback grant to attach a livestream to an HTML video element.
|
|
383
|
+
- Call from an explicit viewer action. Twinkle confirms admission before allocating the private viewer grant or using Media Energy.
|
|
384
|
+
- The first watch action consumes one of at most 10 grants for the session. Repeated watch() calls in the same page reuse the local grant until leave().
|
|
385
|
+
- Playback authorization is single-use and capped at SD. The watch() result omits the signed playback URL; bridge traffic is still app-visible and must be treated as ephemeral.
|
|
386
|
+
- playbackStarted becomes true only after IVS or the HTML video element confirms a playing state. If browser autoplay is blocked or no playing state is confirmed, it is false and the video controls remain available so the viewer can start playback explicitly.
|
|
387
|
+
- Viewers may use the video element's ordinary fullscreen and Picture-in-Picture controls.
|
|
388
|
+
- Example: await Twinkle.live.watch(session.id, { videoElement: '#liveVideo' });
|
|
389
|
+
- async leave(sessionId) | scopes: live:write
|
|
390
|
+
- Returns: { success }
|
|
391
|
+
- Destroy the local player and revoke/settle its private viewer session.
|
|
392
|
+
- Example: await Twinkle.live.leave(sessionId);
|
|
393
|
+
- async stop(sessionId) | scopes: live:write
|
|
394
|
+
- Returns: { session, cleanupInProgress, mediaEnergy }
|
|
395
|
+
- Stop local broadcasting and ask the server to stop and delete the host's ephemeral IVS channel.
|
|
396
|
+
- Use the returned canonical session state. Do not locally synthesize an ended status.
|
|
397
|
+
- Example: await Twinkle.live.stop(sessionId);
|
|
398
|
+
- async listReplays({ limit? } = {}) | scopes: live:read
|
|
399
|
+
- Returns: Array<LiveReplay>
|
|
400
|
+
- List canonical saved replays for this Build app.
|
|
401
|
+
- Ready, unexpired replays are visible to viewers in the app. A creator can also see processing or failed state for their own opted-in stream.
|
|
402
|
+
- Replays expire seven days after becoming ready. Replace displayed state from this canonical response; do not synthesize processing or ready state locally.
|
|
403
|
+
- Example: const replays = await Twinkle.live.listReplays({ limit: 20 });
|
|
404
|
+
- async getReplay(replayId) | scopes: live:read
|
|
405
|
+
- Returns: LiveReplay | null
|
|
406
|
+
- Load canonical processing, ready, or failed state for a visible replay.
|
|
407
|
+
- Only the creator can see a processing or failed replay; ready replays are visible to signed-in viewers in this app.
|
|
408
|
+
- Example: const replay = await Twinkle.live.getReplay(replayId);
|
|
409
|
+
- async watchReplay(replayId, { videoElement, requestId? }) | scopes: live:write
|
|
410
|
+
- Returns: { replay, viewerGrantId, mediaEnergy, playbackStarted }
|
|
411
|
+
- Open a short-lived private playback grant and attach a saved replay to an HTML video element.
|
|
412
|
+
- Call from an explicit viewer action. Twinkle confirms each playback grant before using Media Energy.
|
|
413
|
+
- Admission reserves the replay's maximum delivery estimate; leave, page close, or playback end settles the canonical elapsed viewing window instead of charging unused playback time.
|
|
414
|
+
- The grant lasts at most 20 minutes and is settled automatically when playback ends, on leaveReplay(), when the page closes, or at server expiry.
|
|
415
|
+
- playbackStarted becomes true only after IVS or the HTML video element confirms a playing state. If browser autoplay is blocked or no playing state is confirmed, it is false and the video controls remain available so the viewer can start playback explicitly.
|
|
416
|
+
- Viewers may use the video element's ordinary fullscreen and Picture-in-Picture controls.
|
|
417
|
+
- Example: await Twinkle.live.watchReplay(replay.id, { videoElement: '#replayVideo' });
|
|
418
|
+
- async leaveReplay(replayId) | scopes: live:write
|
|
419
|
+
- Returns: { success }
|
|
420
|
+
- Destroy the local replay player and canonically settle its private viewer grant.
|
|
421
|
+
- The returned canonical settlement charges the conservative elapsed playback estimate, capped by the replay duration.
|
|
422
|
+
- Example: await Twinkle.live.leaveReplay(replayId);
|
|
423
|
+
- async deleteReplay(replayId, { requestId? } = {}) | scopes: live:write
|
|
424
|
+
- Returns: { replay, cleanupInProgress, mediaEnergy }
|
|
425
|
+
- Permanently remove an opted-in replay through the canonical private-storage cleanup path.
|
|
426
|
+
- Twinkle asks for an action-specific confirmation. Use the canonical returned state; cleanupInProgress means provider recording finalization or deletion is still being confirmed.
|
|
427
|
+
- Example: await Twinkle.live.deleteReplay(replayId);
|
|
428
|
+
|
|
314
429
|
### Twinkle.ai
|
|
315
430
|
- async getUsagePolicy() | scopes: none
|
|
316
431
|
- Returns: BuildAiUsagePolicy | null
|
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -636,6 +636,40 @@ type DailyRunComplete = Success<{
|
|
|
636
636
|
type DailyRunFail = DailyRunComplete;
|
|
637
637
|
```
|
|
638
638
|
|
|
639
|
+
### Build Workshop sponsor applications and integrity
|
|
640
|
+
|
|
641
|
+
This is the approved Zero/Ciel Build Workshop sponsor role, not the ordinary
|
|
642
|
+
AI Energy sponsor flow. Applications originate only from `lumine sponsor`.
|
|
643
|
+
Website-management agents review them inside an active daily run:
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
lumine admin sponsor applications list --status pending --json
|
|
647
|
+
lumine admin sponsor applications review 12 --decision approve \
|
|
648
|
+
--note "Approved for probationary duty" --json
|
|
649
|
+
lumine admin sponsor status set 45 --status trusted \
|
|
650
|
+
--note "Cleared probationary handoffs" --json
|
|
651
|
+
lumine admin sponsor integrity scan --json
|
|
652
|
+
lumine admin sponsor integrity cases --status open --json
|
|
653
|
+
lumine admin sponsor integrity get 34 --json
|
|
654
|
+
lumine admin sponsor integrity review 34 --decision clear --json
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
Run `sponsor integrity scan` until its bounded snapshot reaches
|
|
658
|
+
`awaiting_review` or `completed`. Every pending completed handoff receives the
|
|
659
|
+
deterministic checks. Probationary work, hard-flagged evidence, and a stable
|
|
660
|
+
random sample become review cases; clean trusted work outside the sample is
|
|
661
|
+
cleared automatically. A case includes the approved structured relay, canonical
|
|
662
|
+
artifact snapshot, branch-notice evidence, and requested/resolved provider,
|
|
663
|
+
model, effort, service-tier, runtime, usage, and agent-tree records. It never
|
|
664
|
+
includes raw Zero/Ciel chat.
|
|
665
|
+
|
|
666
|
+
`clear` qualifies that unique handoff for its flat 50 KP award;
|
|
667
|
+
`disqualify` makes it ineligible. `hold` and `flag` require an evidence note and
|
|
668
|
+
remain open. The scan itself never changes sponsor status or applies a sanction.
|
|
669
|
+
Use the separate, audited `sponsor status set` command for an explicit human
|
|
670
|
+
decision. `daily-run complete` is rejected until the scan has covered its full
|
|
671
|
+
snapshot and no pending, held, or flagged case remains.
|
|
672
|
+
|
|
639
673
|
Record only qualifying escalations as they are confirmed. `daily-run report`
|
|
640
674
|
then composes the active run's canonical audit events, successful mutations,
|
|
641
675
|
completed queue scans, recorded escalations, and the most useful brief deltas
|
|
@@ -740,10 +774,7 @@ type AdminTodo = {
|
|
|
740
774
|
|
|
741
775
|
type AdminTodoList = Success<{
|
|
742
776
|
todos: AdminTodo[];
|
|
743
|
-
statusFilter:
|
|
744
|
-
| "pending"
|
|
745
|
-
| "all"
|
|
746
|
-
| AdminTodo["status"];
|
|
777
|
+
statusFilter: "pending" | "all" | AdminTodo["status"];
|
|
747
778
|
truncated: boolean;
|
|
748
779
|
}>;
|
|
749
780
|
|
|
@@ -1700,6 +1731,8 @@ A run report that skipped the conduct review is incomplete.
|
|
|
1700
1731
|
```bash
|
|
1701
1732
|
lumine admin brief --json
|
|
1702
1733
|
lumine admin brief --days 3 --json
|
|
1734
|
+
lumine admin ai-costs monthly --json
|
|
1735
|
+
lumine admin media-costs monthly --json
|
|
1703
1736
|
lumine admin notable add 12647 --note "Top authored-activity kid of the window: 11 subjects, 61 comments." --json
|
|
1704
1737
|
lumine admin notable add Minecrarft_guy --note "Helped three new builders debug their projects and gave detailed feedback on five posts." --json
|
|
1705
1738
|
```
|
|
@@ -1711,6 +1744,231 @@ end every run report with an **"Insights for Mikey"** section carrying only
|
|
|
1711
1744
|
the deltas and anomalies worth his time, next to the escalation list. Never
|
|
1712
1745
|
dump raw sections at him.
|
|
1713
1746
|
|
|
1747
|
+
### Application AI calendar-month cost (standing duty, every run)
|
|
1748
|
+
|
|
1749
|
+
Run `lumine admin ai-costs monthly --json` during every website-management
|
|
1750
|
+
run. This read-only command requires the active delegated run and returns one
|
|
1751
|
+
server-owned calendar summary from the canonical deduplicated application AI-
|
|
1752
|
+
cost ledger. It deliberately takes no `--days`: all boundaries are UTC calendar
|
|
1753
|
+
months, so the result is directly comparable from one run to the next.
|
|
1754
|
+
|
|
1755
|
+
The previous month is a closed-calendar-month estimated total. Current-month
|
|
1756
|
+
MTD contains only completed UTC days and names its inclusive `throughDayKey`.
|
|
1757
|
+
The current UTC day's still-filling bucket is returned separately as
|
|
1758
|
+
`inProgressDay`; **never add it to MTD or either projection**. A missing daily
|
|
1759
|
+
aggregate inside the covered calendar is a recorded zero-cost day, not a
|
|
1760
|
+
reason to shrink the denominator.
|
|
1761
|
+
|
|
1762
|
+
Two clearly different full-month projections are returned:
|
|
1763
|
+
|
|
1764
|
+
- `allCompletedDaysPace` retains completed-day spend, then applies the average
|
|
1765
|
+
across every completed calendar day (including recorded zero days) to the
|
|
1766
|
+
current and remaining UTC days;
|
|
1767
|
+
- `recentSevenCompletedDaysPace` retains completed-day spend, then applies the
|
|
1768
|
+
average of the latest seven completed UTC days to the current and remaining
|
|
1769
|
+
days. It is `null` until seven days have completed.
|
|
1770
|
+
|
|
1771
|
+
Both projections exclude the partial day's actual cost, replace every not-yet-
|
|
1772
|
+
completed day with their stated daily pace, and compare their projected total
|
|
1773
|
+
with the previous closed month. They are run-rate scenarios, not forecasts
|
|
1774
|
+
from a billing provider. All ledger values are pricing-based estimates rather
|
|
1775
|
+
than invoices and can change if canonical usage attribution or pricing is
|
|
1776
|
+
corrected.
|
|
1777
|
+
|
|
1778
|
+
The stable JSON payload is `data.monthlyAiCosts`:
|
|
1779
|
+
|
|
1780
|
+
```ts
|
|
1781
|
+
type MonthlyAiCosts = {
|
|
1782
|
+
schemaVersion: 1;
|
|
1783
|
+
generatedAt: number; // Unix seconds
|
|
1784
|
+
timezone: "UTC";
|
|
1785
|
+
currency: "USD";
|
|
1786
|
+
source: {
|
|
1787
|
+
basis: "canonical_deduplicated_ai_cost_report";
|
|
1788
|
+
reportDays: number;
|
|
1789
|
+
reportStartDayIndex: number;
|
|
1790
|
+
reportEndDayIndex: number;
|
|
1791
|
+
mtdIncludesInProgressDay: false;
|
|
1792
|
+
projectionsIncludeInProgressDayActual: false;
|
|
1793
|
+
};
|
|
1794
|
+
previousMonth: {
|
|
1795
|
+
status: "closed";
|
|
1796
|
+
monthKey: string; // YYYY-MM
|
|
1797
|
+
startDayKey: string;
|
|
1798
|
+
endDayKeyExclusive: string;
|
|
1799
|
+
calendarDayCount: number;
|
|
1800
|
+
estimatedCostUsd: number;
|
|
1801
|
+
};
|
|
1802
|
+
currentMonth: {
|
|
1803
|
+
status: "in_progress";
|
|
1804
|
+
monthKey: string;
|
|
1805
|
+
startDayKey: string;
|
|
1806
|
+
endDayKeyExclusive: string;
|
|
1807
|
+
calendarDayCount: number;
|
|
1808
|
+
completed: {
|
|
1809
|
+
dayCount: number;
|
|
1810
|
+
throughDayKey: string | null;
|
|
1811
|
+
estimatedCostUsd: number;
|
|
1812
|
+
dailyAverageUsd: number | null;
|
|
1813
|
+
};
|
|
1814
|
+
inProgressDay: {
|
|
1815
|
+
dayIndex: number;
|
|
1816
|
+
dayKey: string;
|
|
1817
|
+
estimatedCostUsd: number;
|
|
1818
|
+
eventCount: number;
|
|
1819
|
+
requestCount: number;
|
|
1820
|
+
};
|
|
1821
|
+
daysToEstimate: number;
|
|
1822
|
+
projections: {
|
|
1823
|
+
allCompletedDaysPace: MonthlyAiCostProjection | null;
|
|
1824
|
+
recentSevenCompletedDaysPace: MonthlyAiCostProjection | null;
|
|
1825
|
+
};
|
|
1826
|
+
};
|
|
1827
|
+
};
|
|
1828
|
+
|
|
1829
|
+
type MonthlyAiCostProjection = {
|
|
1830
|
+
basis: "all_completed_days" | "recent_7_completed_days";
|
|
1831
|
+
basisStartDayKey: string;
|
|
1832
|
+
basisEndDayKey: string;
|
|
1833
|
+
basisDayCount: number;
|
|
1834
|
+
dailyAverageUsd: number;
|
|
1835
|
+
remainingDayCount: number;
|
|
1836
|
+
estimatedMonthTotalUsd: number;
|
|
1837
|
+
comparisonToPreviousMonth: {
|
|
1838
|
+
estimatedCostDeltaUsd: number;
|
|
1839
|
+
percentChange: number | null; // null when the prior total is zero
|
|
1840
|
+
};
|
|
1841
|
+
};
|
|
1842
|
+
```
|
|
1843
|
+
|
|
1844
|
+
Release boundary: `/cli/admin/ai-costs/monthly` and all calendar math are API-
|
|
1845
|
+
owned. Deploy and verify the compatible `twinkle-api` route before publishing
|
|
1846
|
+
or installing the Lumine CLI release that invokes it; an older API will reject
|
|
1847
|
+
the new command instead of synthesizing figures locally.
|
|
1848
|
+
|
|
1849
|
+
### Lumine media feature cost and cleanup watch (standing duty, every run)
|
|
1850
|
+
|
|
1851
|
+
Run `lumine admin media-costs monthly --json` during every website-management
|
|
1852
|
+
run. This read-only, delegated-run-gated command reports the canonical Media
|
|
1853
|
+
Energy ledger for short clips, livestream input/viewer usage, and replay
|
|
1854
|
+
storage/viewing. Include in
|
|
1855
|
+
**"Insights for Mikey"** on every run:
|
|
1856
|
+
|
|
1857
|
+
- current-month settled estimated cost, active reservations, cross-month
|
|
1858
|
+
carryover, guarded total, global limit, remaining headroom, and percent used;
|
|
1859
|
+
- the current UTC day's reservations, settlements, cancellations, and settled
|
|
1860
|
+
estimated cost;
|
|
1861
|
+
- the current UTC day's privacy-safe stream-attempt cohort: attempted,
|
|
1862
|
+
reached-live, ended-after-live, failed, cancelled-before-live, still in
|
|
1863
|
+
progress, and grouped server failure-code counts;
|
|
1864
|
+
- clip, live-input, live-viewer, and replay-viewer action/cost breakdowns;
|
|
1865
|
+
- whether the global usage row reconciles exactly with reservation rows;
|
|
1866
|
+
- every returned alert, plus incomplete clip jobs, cost-bearing IVS channels,
|
|
1867
|
+
active-or-cleanup-pending sessions, possible orphaned sessions, overdue
|
|
1868
|
+
cleanup, replay finalization/deletion state, retained replay bytes/objects,
|
|
1869
|
+
and expired-active live or replay viewer grants;
|
|
1870
|
+
- ready image/clip storage counts and bytes as scale context.
|
|
1871
|
+
|
|
1872
|
+
Treat `status: "critical"`, any reconciliation mismatch, overdue IVS cleanup,
|
|
1873
|
+
or overdue replay finalization/deletion as an operational incident to
|
|
1874
|
+
investigate in the same run. Treat
|
|
1875
|
+
`status: "attention"` as a required finding, not a decorative warning. The
|
|
1876
|
+
server's request-time global Media Energy guardrail defaults to **$40/month**;
|
|
1877
|
+
the separate AWS Budget is **$50/month**, leaving provider-billing and shared-
|
|
1878
|
+
infrastructure headroom. Never infer or locally decrement either value.
|
|
1879
|
+
|
|
1880
|
+
The ledger is the immediate application source of truth and deliberately uses
|
|
1881
|
+
conservative provider-cost estimates. It is not an AWS invoice. Photo capture
|
|
1882
|
+
uses existing Build runtime file storage rather than the paid Media Energy
|
|
1883
|
+
ledger; `operations.runtimeStorage.readyImages` therefore reports all ready
|
|
1884
|
+
Build runtime images, not camera captures alone. Reconcile delayed AWS
|
|
1885
|
+
MediaConvert and IVS service charges every run as described below. S3 is shared
|
|
1886
|
+
with other Twinkle uploads, so report its service-level cost as shared context,
|
|
1887
|
+
not as photo-only spend.
|
|
1888
|
+
|
|
1889
|
+
Release boundary: `/cli/admin/media-costs/monthly` owns the canonical ledger,
|
|
1890
|
+
reconciliation, resource-state checks, and alerts. Deploy and verify that API
|
|
1891
|
+
route before publishing or installing the Lumine CLI release that invokes it.
|
|
1892
|
+
|
|
1893
|
+
`currentUtcDay.streamAttempts` is a `createdAt` cohort for that UTC day. Its
|
|
1894
|
+
outcomes partition every attempt into `endedCount` (reached live, then ended),
|
|
1895
|
+
`failedCount` (failed or cleanup-failed), `cancelledCount` (ended before ever
|
|
1896
|
+
reaching live), or `inProgressCount`; `reachedLiveCount` is the overlapping
|
|
1897
|
+
milestone count. `failureCodeCounts` contains only server-defined codes and
|
|
1898
|
+
counts—never usernames, Build ids, titles, viewer identities, or report
|
|
1899
|
+
identities. `operations.live.stillActiveOrCleanupPendingCount` and
|
|
1900
|
+
`possibleOrphanedCount` are current global counts, not members of the daily
|
|
1901
|
+
cohort.
|
|
1902
|
+
|
|
1903
|
+
Replay storage and write cost is conservatively embedded in an opted-in
|
|
1904
|
+
`live-input` reservation; `replay-viewer` is a separate kind. Report
|
|
1905
|
+
`operations.replays` (pending, processing, ready, failed, deleting,
|
|
1906
|
+
delete-failed, overdue finalization/deletion, expired-ready, bytes, and object
|
|
1907
|
+
count) and `operations.replayViewers` on every run. A replay finalization or
|
|
1908
|
+
deletion alert is an operational incident because private recording cleanup is
|
|
1909
|
+
part of the feature contract.
|
|
1910
|
+
|
|
1911
|
+
### AWS monthly bill expectation (standing duty, every run)
|
|
1912
|
+
|
|
1913
|
+
Starting 2026-08-27, every website-management run must also check AWS Cost
|
|
1914
|
+
Explorer and include the current calendar month's expected AWS bill in
|
|
1915
|
+
**"Insights for Mikey"**. This is an account-level infrastructure cost check,
|
|
1916
|
+
not the `aiSpending` application-cost section above; never substitute one for
|
|
1917
|
+
the other or combine their totals.
|
|
1918
|
+
|
|
1919
|
+
First verify the Twinkle AWS principal exactly as required by the repository
|
|
1920
|
+
agent guide. Use profile `mikey-iam`, pass an explicit region on every command,
|
|
1921
|
+
and stop rather than reading another account if the ARN is not
|
|
1922
|
+
`arn:aws:iam::019490893667:user/twinkle-admin`:
|
|
1923
|
+
|
|
1924
|
+
```bash
|
|
1925
|
+
aws sts get-caller-identity --profile mikey-iam --region us-east-1
|
|
1926
|
+
```
|
|
1927
|
+
|
|
1928
|
+
Then use UTC calendar boundaries and Cost Explorer's unblended-cost metric.
|
|
1929
|
+
`End` is exclusive: the month-to-date query below covers completed dates before
|
|
1930
|
+
`<today-UTC>`. On the first UTC day of a month, report that no completed-day MTD
|
|
1931
|
+
period exists instead of sending an empty interval.
|
|
1932
|
+
|
|
1933
|
+
```bash
|
|
1934
|
+
aws ce get-cost-and-usage --profile mikey-iam --region us-east-1 \
|
|
1935
|
+
--time-period Start=<month-start-YYYY-MM-01>,End=<today-UTC> \
|
|
1936
|
+
--granularity MONTHLY --metrics UnblendedCost
|
|
1937
|
+
|
|
1938
|
+
aws ce get-cost-and-usage --profile mikey-iam --region us-east-1 \
|
|
1939
|
+
--time-period Start=<month-start-YYYY-MM-01>,End=<today-UTC> \
|
|
1940
|
+
--granularity MONTHLY --metrics UnblendedCost \
|
|
1941
|
+
--group-by Type=DIMENSION,Key=SERVICE
|
|
1942
|
+
|
|
1943
|
+
aws ce get-cost-forecast --profile mikey-iam --region us-east-1 \
|
|
1944
|
+
--time-period Start=<today-UTC>,End=<next-month-YYYY-MM-01> \
|
|
1945
|
+
--metric UNBLENDED_COST --granularity DAILY \
|
|
1946
|
+
--prediction-interval-level 80
|
|
1947
|
+
```
|
|
1948
|
+
|
|
1949
|
+
Report the Cost Explorer snapshot date, currency, estimated MTD amount and its
|
|
1950
|
+
through-date, plus the returned **remaining-period** forecast mean and 80%
|
|
1951
|
+
lower/upper bounds. Verify that the first and last returned daily periods cover
|
|
1952
|
+
exactly the requested `Start`-inclusive, `End`-exclusive interval before doing
|
|
1953
|
+
any arithmetic; reject or separately explain a response with expanded or
|
|
1954
|
+
missing dates. Calculate the expected full-calendar-month mean and bounds by
|
|
1955
|
+
adding completed-day MTD to the sums of the returned daily mean, lower, and
|
|
1956
|
+
upper values. Do not use monthly granularity for this mid-month remainder:
|
|
1957
|
+
Cost Explorer can return the whole calendar month even when the requested
|
|
1958
|
+
start is mid-month, and adding MTD to that result would double-count. This
|
|
1959
|
+
split deliberately forecasts the current UTC day instead of mixing its
|
|
1960
|
+
incomplete actual into MTD. Label current-month actuals
|
|
1961
|
+
when `Estimated` is true, and describe the result as a Cost Explorer expectation
|
|
1962
|
+
rather than a final invoice because reporting lags and later credits, refunds,
|
|
1963
|
+
taxes, or adjustments can change the bill. If either the forecast or MTD query
|
|
1964
|
+
is unavailable, report the available component and say why a complete
|
|
1965
|
+
full-month expectation is unavailable instead of extrapolating it locally. A
|
|
1966
|
+
previous closed month may be quoted for context when the difference is
|
|
1967
|
+
material, but it is not a replacement for the current-month expectation.
|
|
1968
|
+
For the media watch, separately identify AWS Elemental MediaConvert and Amazon
|
|
1969
|
+
Interactive Video Service rows when present. Also report Amazon S3 as shared
|
|
1970
|
+
storage context, without attributing the whole S3 row to Lumine media.
|
|
1971
|
+
|
|
1714
1972
|
Ten sections (Mikey's chosen cut 2026-08-10; behavioral-insight and
|
|
1715
1973
|
farm-signal sections added that day; AI Card summon watch added 2026-08-24):
|
|
1716
1974
|
|
|
@@ -1762,8 +2020,8 @@ farm-signal sections added that day; AI Card summon watch added 2026-08-24):
|
|
|
1762
2020
|
every individual summon. Each group reports its maximum same-day account and
|
|
1763
2021
|
charged-summon totals, the multi-account days, and days above the shared
|
|
1764
2022
|
three-card limit. Review every `requiresIdentityInspection` group,
|
|
1765
|
-
prioritizing `daysAboveSharedLimit > 0`, with reason-required
|
|
1766
|
-
inspect`. If
|
|
2023
|
+
prioritizing `daysAboveSharedLimit > 0`, with reason-required
|
|
2024
|
+
`identity inspect`. If
|
|
1767
2025
|
`riskGroupsTruncated` is true, report that the bounded watch has more groups
|
|
1768
2026
|
than it returned rather than calling the review exhaustive. Add only
|
|
1769
2027
|
operator-confirmed exact accounts to an unbanned quota bucket with
|