@stage5/lumine 0.3.37 → 0.3.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/admin.js CHANGED
@@ -179,6 +179,7 @@ const REVIEW_REQUEST_TYPES = [
179
179
  "project-limit",
180
180
  "storage-limit",
181
181
  "cardcraft",
182
+ "soundtrack",
182
183
  ];
183
184
  const REVIEW_REQUEST_TYPE_ALIASES = {
184
185
  rewards: "rewards",
@@ -192,6 +193,8 @@ const REVIEW_REQUEST_TYPE_ALIASES = {
192
193
  cardcraft: "cardcraft",
193
194
  "card-craft": "cardcraft",
194
195
  crafting: "cardcraft",
196
+ soundtrack: "soundtrack",
197
+ "song-tag": "soundtrack",
195
198
  };
196
199
  const REVIEW_REQUEST_LIST_STATUSES = [
197
200
  "queue",
@@ -206,9 +209,10 @@ const REVIEW_REQUEST_LABELS = {
206
209
  "project-limit": "Project room",
207
210
  "storage-limit": "Lumine file storage",
208
211
  cardcraft: "AI Card crafting",
212
+ soundtrack: "Song app tag",
209
213
  };
210
214
  const REVIEW_REQUEST_USAGE =
211
- "Usage: lumine admin review list [--type rewards|project|storage|cardcraft] [--status queue|pending|approved|rejected|all] [--cursor <c>] | show <type:id> [--dir <path>] | approve <type:id> [--config <rules.json>] [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <type:id> [--reason <text>] | revoke <type:id> --reason <text> | propose rewards:<id> --dir <edited-snapshot> --config <rules.json> [--reason <text>] [--title <new app name>] [--thumbnail <image|workspace>]. A bare id works with --type.";
215
+ "Usage: lumine admin review list [--type rewards|project|storage|cardcraft|soundtrack] [--status queue|pending|approved|rejected|all] [--cursor <c>] | show <type:id> [--dir <path>] | approve <type:id> [--config <rules.json>] [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <type:id> [--reason <text>] | revoke <type:id> --reason <text> | propose rewards:<id> --dir <edited-snapshot> --config <rules.json> [--reason <text>] [--title <new app name>] [--thumbnail <image|workspace>]. A bare id works with --type.";
212
216
 
213
217
  function parseReviewRequestType(value) {
214
218
  const key = String(value || "")
@@ -217,7 +221,7 @@ function parseReviewRequestType(value) {
217
221
  const type = REVIEW_REQUEST_TYPE_ALIASES[key];
218
222
  if (!type) {
219
223
  throw cliValidationError(
220
- `Unknown request type "${key}". Use rewards, project, storage or cardcraft.`,
224
+ `Unknown request type "${key}". Use rewards, project, storage, cardcraft or soundtrack.`,
221
225
  );
222
226
  }
223
227
  return type;
@@ -882,13 +886,13 @@ function buildReviewRequestOperation({
882
886
  if (!REWARD_REVIEW_DECISIONS.includes(action)) {
883
887
  return null;
884
888
  }
885
- if (action === "revoke" && !["rewards", "cardcraft"].includes(ref.type)) {
889
+ if (action === "revoke" && !["rewards", "cardcraft", "soundtrack"].includes(ref.type)) {
886
890
  throw cliValidationError(
887
891
  `${REVIEW_REQUEST_LABELS[ref.type]} approvals are not revoked; change the limit instead${ref.type === "storage-limit" ? " (lumine admin storage grant)" : ""}.`,
888
892
  );
889
893
  }
890
894
  if (
891
- ["rewards", "cardcraft"].includes(ref.type) &&
895
+ ["rewards", "cardcraft", "soundtrack"].includes(ref.type) &&
892
896
  action !== "approve" &&
893
897
  !reason
894
898
  ) {
package/lib/sdk.js CHANGED
@@ -13,6 +13,11 @@ import { findLocalProjectMetadata } from "./workspace.js";
13
13
  // TWINKLE_BUILD_SDK.md. Methods that mutate data require --allow-write.
14
14
  // Anything not listed can be called with --path api/<endpoint>.
15
15
  export const SDK_CLI_METHODS = {
16
+ "soundtracks.searchSources": { path: "api/soundtracks/sources", scopes: ["sharedDb:read"] },
17
+ "soundtracks.getTags": { path: "api/soundtracks/tags", scopes: ["sharedDb:read"] },
18
+ "soundtracks.getRecommendations": { path: "api/soundtracks/recommendations", scopes: ["sharedDb:read"] },
19
+ "soundtracks.requestTag": { path: "api/soundtracks/request-tag", scopes: ["sharedDb:write"], write: true },
20
+ "soundtracks.removeTag": { path: "api/soundtracks/remove-tag", scopes: ["sharedDb:write"], write: true },
16
21
  "network.getHome": { path: "api/network/home", scopes: ["sharedDb:read"] },
17
22
  "network.getFeed": { path: "api/network/feed", scopes: ["sharedDb:read"] },
18
23
  "network.getThread": { path: "api/network/thread", scopes: ["sharedDb:read"] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.3.37",
3
+ "version": "0.3.38",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1738,6 +1738,32 @@ if (result.status === 'ok') showCard(result.item, result.why, result.worldFirst)
1738
1738
  - Network owner only: dismiss a report, remove content, suspend an agent, or restore it.
1739
1739
  - Lumine Network Build 2742 only. Replace UI state from confirmed server responses.
1740
1740
 
1741
+ ### Twinkle.soundtracks
1742
+ - async searchSources({ query } = {}) | scopes: sharedDb:read
1743
+ - Returns: { sources: [{ kind, appId?, title, href, thumbnailUrl? }] }
1744
+ - Find a public app or Grammarbles Quest for a song tag.
1745
+ - Use a simple searchable picker. Send the returned kind/appId, never a title as an identifier.
1746
+ - async getTags({ entryIds }) | scopes: sharedDb:read
1747
+ - Returns: { tags: [{ id, entryId, status, source, reviewReason, revision, eventTimeMs, canRemove }] }
1748
+ - Read approved tags and the current creator’s requests for up to 50 songs.
1749
+ - Pending, declined, closed and revoked requests are visible only to their creator. Replace state only with the canonical response.
1750
+ - async requestTag({ entryId, source, note?, expectedUserId }) | scopes: sharedDb:write
1751
+ - Returns: { tag }
1752
+ - Ask Mikey to link your published song to an app.
1753
+ - source is { kind: "app", appId } or { kind: "grammarbles-quest" }. expectedUserId must be the current viewer.id.
1754
+ - Only the song’s creator can request a tag. Up to three active tags per song and ten pending requests per creator. Same pending/approved request retries are idempotent.
1755
+ - Uses the shared Build approvals queue and chat card. Nothing influences discovery until Mikey approves. Updating the song or any of its music parts closes the request/approval and requires a fresh review.
1756
+ - async removeTag({ tagId, expectedUserId }) | scopes: sharedDb:write
1757
+ - Returns: { tag }
1758
+ - Withdraw your own app tag, including a live approval.
1759
+ - Stops recommendations immediately. Show the returned status; do not optimistically remove server-owned approval state.
1760
+ - async getRecommendations({ limit } = {}) | scopes: sharedDb:read
1761
+ - Returns: { picks: [{ entry, source, reason }] }
1762
+ - Suggest approved soundtrack songs from apps the current viewer used.
1763
+ - limit is 1–12 (default 6). Guests and viewers without matching history get an empty list. Hide the shelf when empty.
1764
+ - Matches recorded public-app use and actual Grammarbles Quest answers. Private/deleted apps, missing songs and unapproved/revoked tags never qualify. Returns no activity history or raw progress.
1765
+ - Full Circle and Starlight music and covers stay locked for listeners until the current viewer reaches their Red Square milestones. A locked entry has songLocked, coverLocked and coverUnlockHint, with listing metadata only: no music, manifest or details.cover. Direct part reads follow the same gate. Disable playback, remix and queue actions and offer a link to /app/2. Creators retain editing access to their own songs.
1766
+
1741
1767
  ## Examples
1742
1768
 
1743
1769
  ### Daily reflection feed