@stage5/lumine 0.2.35 → 0.2.37

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
@@ -4,19 +4,17 @@ import { assertAuthScope, resolveAuth } from "./auth.js";
4
4
  import { requestJson } from "./http.js";
5
5
 
6
6
  const MAX_EDITORIAL_FILE_BYTES = 256 * 1024;
7
- const MAX_COMPOSED_COMMENT_FILE_BYTES = 64 * 1024;
8
- const MAX_COMPOSED_COMMENT_LENGTH = 10_000;
7
+ const MAX_COMPOSED_TEXT_FILE_BYTES = 64 * 1024;
8
+ const MAX_COMPOSED_TEXT_LENGTH = 10_000;
9
9
  const MAX_NOTABLE_NOTE_LENGTH = 2_000;
10
10
 
11
- // Operator-composed persona comment text (plain UTF-8, not JSON). The agent
12
- // writes the comment in the bot's persona itself; the server never invokes
11
+ // Operator-composed persona text (plain UTF-8, not JSON). The agent writes
12
+ // the content in the bot's persona itself; the server never invokes
13
13
  // its model and no AI Energy is spent.
14
- function readComposedCommentFile(filePath) {
14
+ function readComposedTextFile(filePath) {
15
15
  const normalizedPath = String(filePath || "").trim();
16
16
  if (!normalizedPath) {
17
- throw cliValidationError(
18
- "Pass composed comment text with --file <comment.md>.",
19
- );
17
+ throw cliValidationError("Pass composed text with --file <file.md>.");
20
18
  }
21
19
  let contents;
22
20
  try {
@@ -24,18 +22,18 @@ function readComposedCommentFile(filePath) {
24
22
  } catch {
25
23
  throw cliValidationError(`Could not read ${normalizedPath}.`);
26
24
  }
27
- if (Buffer.byteLength(contents, "utf8") > MAX_COMPOSED_COMMENT_FILE_BYTES) {
28
- throw cliValidationError("The composed comment file must be under 64KB.");
25
+ if (Buffer.byteLength(contents, "utf8") > MAX_COMPOSED_TEXT_FILE_BYTES) {
26
+ throw cliValidationError("The composed text file must be under 64KB.");
29
27
  }
30
28
  const normalized = contents.trim();
31
29
  if (!normalized) {
32
30
  throw cliValidationError(
33
- `${normalizedPath} is empty; a composed comment needs text.`,
31
+ `${normalizedPath} is empty; composed text is required.`,
34
32
  );
35
33
  }
36
- if (normalized.length > MAX_COMPOSED_COMMENT_LENGTH) {
34
+ if (normalized.length > MAX_COMPOSED_TEXT_LENGTH) {
37
35
  throw cliValidationError(
38
- `A composed comment must be at most ${MAX_COMPOSED_COMMENT_LENGTH} characters.`,
36
+ `Composed text must be at most ${MAX_COMPOSED_TEXT_LENGTH} characters.`,
39
37
  );
40
38
  }
41
39
  return normalized;
@@ -401,6 +399,17 @@ export function parseAdminOperation(options) {
401
399
  }
402
400
 
403
401
  if (namespace === "ai-bucket" || namespace === "ai-buckets") {
402
+ if (action === "create") {
403
+ return writeOperation(
404
+ "ai-bucket.create",
405
+ "POST",
406
+ "/cli/admin/ai-buckets",
407
+ {
408
+ label: parseAiBucketLabel(options.adminLabel),
409
+ note: parseAiBucketNote(options.note),
410
+ },
411
+ );
412
+ }
404
413
  const bucketId = parseRequiredInteger(
405
414
  options.adminBucketId,
406
415
  "AI bucket ID",
@@ -424,22 +433,11 @@ export function parseAdminOperation(options) {
424
433
  );
425
434
  }
426
435
  if (action === "note" && target === "set") {
427
- const note = String(options.note || "").trim();
428
- if (!note) {
429
- throw cliValidationError(
430
- "Pass the quota-only context with --note <text>.",
431
- );
432
- }
433
- if (note.length > 255) {
434
- throw cliValidationError(
435
- "An AI bucket note can be at most 255 characters.",
436
- );
437
- }
438
436
  return writeOperation(
439
437
  "ai-bucket.note.set",
440
438
  "PUT",
441
439
  `/cli/admin/ai-buckets/${bucketId}/note`,
442
- { note },
440
+ { note: parseAiBucketNote(options.note) },
443
441
  );
444
442
  }
445
443
  }
@@ -724,6 +722,17 @@ export function parseAdminOperation(options) {
724
722
  );
725
723
  }
726
724
 
725
+ if (namespace === "announcement" && action === "post") {
726
+ return writeOperation(
727
+ "announcement.post",
728
+ "POST",
729
+ "/cli/admin/announcements",
730
+ {
731
+ content: readComposedTextFile(options.adminFile),
732
+ },
733
+ );
734
+ }
735
+
727
736
  if (namespace === "chat" && action === "send") {
728
737
  const rawTarget = String(target || "").trim();
729
738
  if (!rawTarget) {
@@ -735,7 +744,7 @@ export function parseAdminOperation(options) {
735
744
  // the bot's voice; the server never invokes a model for it.
736
745
  return writeOperation("chat.send", "POST", "/cli/admin/chat-messages", {
737
746
  target: rawTarget,
738
- content: readComposedCommentFile(options.adminFile),
747
+ content: readComposedTextFile(options.adminFile),
739
748
  });
740
749
  }
741
750
 
@@ -821,7 +830,7 @@ export function parseAdminOperation(options) {
821
830
  ? parseIdentity(options.adminIdentity)
822
831
  : undefined,
823
832
  ...(options.adminFile
824
- ? { content: readComposedCommentFile(options.adminFile) }
833
+ ? { content: readComposedTextFile(options.adminFile) }
825
834
  : {}),
826
835
  ...(reviewedBuildVersionId ? { reviewedBuildVersionId } : {}),
827
836
  ...(buildReviewMethod ? { buildReviewMethod } : {}),
@@ -849,7 +858,7 @@ export function parseAdminOperation(options) {
849
858
  "comment.edit",
850
859
  "PUT",
851
860
  `/cli/admin/comments/${commentId}`,
852
- { content: readComposedCommentFile(options.adminFile) },
861
+ { content: readComposedTextFile(options.adminFile) },
853
862
  );
854
863
  }
855
864
  if (action === "post") {
@@ -868,7 +877,7 @@ export function parseAdminOperation(options) {
868
877
  }
869
878
 
870
879
  throw cliValidationError(
871
- "Usage: lumine admin identity|daily-run|recommendations|builds|post|subjects|subject|featured|comment|chat|news|audit|brief|bot-output|notable ...",
880
+ "Usage: lumine admin identity|daily-run|recommendations|builds|post|subjects|subject|featured|comment|announcement|chat|news|audit|brief|bot-output|notable ...",
872
881
  );
873
882
  }
874
883
 
@@ -1181,6 +1190,32 @@ function parseOrderedIds(value) {
1181
1190
  return ids;
1182
1191
  }
1183
1192
 
1193
+ function parseAiBucketLabel(value) {
1194
+ const label = String(value || "").trim();
1195
+ if (!label) {
1196
+ throw cliValidationError("Pass the bucket name with --label <name>.");
1197
+ }
1198
+ if (label.length > 120) {
1199
+ throw cliValidationError("An AI bucket name can be at most 120 characters.");
1200
+ }
1201
+ return label;
1202
+ }
1203
+
1204
+ function parseAiBucketNote(value) {
1205
+ const note = String(value || "").trim();
1206
+ if (!note) {
1207
+ throw cliValidationError(
1208
+ "Pass the quota-only context with --note <text>.",
1209
+ );
1210
+ }
1211
+ if (note.length > 255) {
1212
+ throw cliValidationError(
1213
+ "An AI bucket note can be at most 255 characters.",
1214
+ );
1215
+ }
1216
+ return note;
1217
+ }
1218
+
1184
1219
  function parseAiBucketUserIds(value) {
1185
1220
  const raw = String(value || "").trim();
1186
1221
  if (!raw) {
package/lib/commands.js CHANGED
@@ -2173,6 +2173,7 @@ export function parseArgs(args) {
2173
2173
  ? String(raw.ids)
2174
2174
  : "",
2175
2175
  adminBucketId: raw.bucketId ? String(raw.bucketId) : "",
2176
+ adminLabel: raw.label ? String(raw.label) : "",
2176
2177
  adminUserIds: raw.userIds ? String(raw.userIds) : "",
2177
2178
  adminType: raw.type ? String(raw.type) : "",
2178
2179
  adminKind: raw.kind ? String(raw.kind) : "",
@@ -2513,6 +2514,7 @@ export function printHelp() {
2513
2514
  lumine thumbnail generate ["<prompt>"] --model <gpt-image-2|nano-banana>
2514
2515
  lumine doctor runtime-assets
2515
2516
  lumine admin identity list|status|use <zero|ciel|auto> [--json]
2517
+ lumine admin ai-bucket create --label <name> --note <text> [--json]
2516
2518
  lumine admin ai-bucket get --bucket-id <id> [--json]
2517
2519
  lumine admin ai-bucket accounts add --bucket-id <id> --user-ids <id,id,...> [--note <text>] [--json]
2518
2520
  lumine admin ai-bucket note set --bucket-id <id> --note <text> [--json]
@@ -2539,6 +2541,7 @@ export function printHelp() {
2539
2541
  lumine admin comment edit <comment-id> --file <comment.md> [--json]
2540
2542
  lumine admin brief [--days <1..30>] [--json]
2541
2543
  lumine admin bot-output [--days <1..30>] [--json]
2544
+ lumine admin announcement post --file <announcement.md> [--json]
2542
2545
  lumine admin chat send <user-id|username> --file <message.md> [--json]
2543
2546
  lumine admin notable add <user-id|username> --note <text> [--json]
2544
2547
  lumine admin audit [list] [--run current|last|<run-id>] [--target <target>] [--actions <a,b>] [--full] [--cursor <cursor>] [--json]
@@ -2616,6 +2619,7 @@ Options:
2616
2619
  --level <1|2|3> Admin subject effort level
2617
2620
  --subject-ids <ids> Complete ordered Featured subject IDs
2618
2621
  --bucket-id <id> Unbanned AI identity bucket for account consolidation
2622
+ --label <name> Name for a new unbanned AI identity bucket
2619
2623
  --user-ids <ids> Explicit user IDs for an AI bucket batch (up to 500)
2620
2624
  --type <type> Admin target: subject, comment, build, aiStory, or dailyReflection
2621
2625
  --kind recommend Admin recommendation queue kind
@@ -2623,7 +2627,7 @@ Options:
2623
2627
  --reward-twinkles 3 Pair a recommendation with exactly 3 Twinkles
2624
2628
  --twinkles 3 Give exactly 3 Twinkles through the normal economy
2625
2629
  --draft-id <id> Canonical delegated comment draft ID
2626
- --file <path> Editorial JSON or composed comment text file
2630
+ --file <path> Editorial JSON or composed text file
2627
2631
  --reviewed-version <id> Published Build artifact version actually reviewed
2628
2632
  --reviewed-via <method> Build review method: runtime or code
2629
2633
  --reason <text> Reason when marking an admin run failed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.35",
3
+ "version": "0.2.37",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  Version: 1.32.0
4
4
  Updated: 2026-08-02
5
- Generated: 2026-08-11T04:29:29.082Z
5
+ Generated: 2026-08-13T00:13:49.827Z
6
6
 
7
7
  ## Notes
8
8
  - This SDK is injected into Build iframes via the Build preview/runtime.
@@ -287,7 +287,7 @@ console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
287
287
  - Returns: { text, response, model, webSearch, aiUsagePolicy }
288
288
  - Generate text with the default Lumine text model, optionally using live web search and streaming text updates through onText.
289
289
  - Signed-in viewers only.
290
- - Uses Grok 4.5 by default.
290
+ - Uses Grok 4.6 by default.
291
291
  - Each successful text generation consumes AI Energy from the signed-in viewer.
292
292
  - history must be an array of { role: 'user' | 'assistant', content: string }. Twinkle.ai.chat does not read a text field.
293
293
  - The server keeps the latest 12 valid history entries.
@@ -307,7 +307,7 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
307
307
  - expectedStructure must be a JSON object that describes the exact returned object shape.
308
308
  - mode is accepted as an alias for thinkingMode, and mid is accepted as an alias for medium.
309
309
  - thinkingMode low uses GPT-5.6 Luna and consumes the viewer's AI Energy from confirmed provider usage; its smaller model is usually cheaper than Medium or High.
310
- - thinkingMode medium uses Grok 4.5 with medium reasoning and consumes normal AI Energy.
310
+ - thinkingMode medium uses Grok 4.6 with medium reasoning and consumes normal AI Energy.
311
311
  - thinkingMode high uses GPT-5.6 Sol with high reasoning and consumes high AI Energy.
312
312
  - When AI Energy is empty, Low, Medium, and High all reject before new provider work; there is no free fallback mode.
313
313
  - Live web search is enabled by default in Medium and High modes. Pass webSearch: false to disable it for the app. Low/Lite Mode remains tool-free; explicitly forcing webSearch: true in Low Mode returns an error.
@@ -353,7 +353,7 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
353
353
  - Pass onText/onStatus for streaming dialogue. Omit callbacks for non-streaming dialogue where the promise resolves with the final response.
354
354
  - Inside Build character chat, thinkingMode low uses Lite Mode: Zero and Ciel both use GPT-5.6 Luna with reasoning disabled; confirmed provider usage consumes the viewer's AI Energy and is usually cheaper than High.
355
355
  - Inside Build character chat, thinkingMode medium uses the same normal chat model routing: Zero and Ciel both use GPT-5.6 Luna with reasoning disabled and normal AI Energy.
356
- - Inside Build character chat, thinkingMode high uses Think Hard chat routing and high AI Energy: Zero uses Grok 4.5 with high reasoning and Ciel uses GPT-5.6 Terra with high reasoning.
356
+ - Inside Build character chat, thinkingMode high uses Think Hard chat routing and high AI Energy: Zero uses Grok 4.6 with high reasoning and Ciel uses GPT-5.6 Terra with high reasoning.
357
357
  - When AI Energy is empty, Low, Medium, and High all reject before new provider work; there is no free fallback mode.
358
358
  - Pass roomContext as a short shared scene transcript so Zero and Ciel can know what happened in the same room.
359
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.
@@ -55,6 +55,8 @@ AI identity buckets are private operator bookkeeping, not a Zero/Ciel public
55
55
  action. They therefore do not require or attach to a delegated daily run:
56
56
 
57
57
  ```bash
58
+ lumine admin ai-bucket create --label Lemon \
59
+ --note "Quota accounting only; not a moderation flag." --json
58
60
  lumine admin ai-bucket get --bucket-id 10 --json
59
61
  lumine admin ai-bucket accounts add --bucket-id 10 \
60
62
  --user-ids 3127,13037,15410,16288 \
@@ -63,6 +65,13 @@ lumine admin ai-bucket note set --bucket-id 10 \
63
65
  --note "Quota accounting only; not a moderation flag." --json
64
66
  ```
65
67
 
68
+ `create` inserts a new unbanned quota bucket through the same helper the
69
+ management page uses. `--label` is required (at most 120 characters). `--note`
70
+ is required and follows the same 255-character quota-context rule as
71
+ `note set`. It cannot create a banned bucket, copy an existing one, or infer
72
+ members. The response returns the canonical bucket, including its id for
73
+ later `get` / `accounts add` / `note set` calls.
74
+
66
75
  `accounts add` accepts 1-500 unique positive user IDs, preflights the complete
67
76
  batch before writing, adds the canonical user and durable verified-email rules,
68
77
  re-attributes current-day AI usage, and returns the canonical bucket members.
@@ -73,11 +82,12 @@ Lumine audit log; no public bot identity is involved.
73
82
  canonical bucket. Use it to distinguish quota bookkeeping from moderation;
74
83
  the note itself changes no access, ban, or identity rules.
75
84
 
76
- This surface intentionally accepts only an existing **unbanned** bucket. It
77
- cannot ban accounts, block signup, add IP/device/risk-key rules, or infer an
78
- account family. Identification remains a human/LLM evidence judgment and must
79
- be explicitly requested by Mikey; routine administrator runs still escalate
80
- suspected alternate accounts and never auto-enforce.
85
+ This surface is quota bookkeeping only. It cannot ban accounts, block signup,
86
+ add IP/device/risk-key rules, or infer an account family. Identification
87
+ remains a human/LLM evidence judgment and must be explicitly requested by
88
+ Mikey; routine administrator runs still escalate suspected alternate accounts
89
+ and never auto-enforce. `accounts add` and `note set` still accept only an
90
+ existing **unbanned** bucket.
81
91
 
82
92
  ## Editorial priorities
83
93
 
@@ -1611,6 +1621,30 @@ Ordinary warm follow-ups (checking on a member the bots already know after
1611
1621
  something the run surfaced) are within a run's judgment, sparingly, and are
1612
1622
  always reported in the run report.
1613
1623
 
1624
+ ## Official announcements
1625
+
1626
+ ```bash
1627
+ lumine admin announcement post --file announcement.md --json
1628
+ ```
1629
+
1630
+ The run's selected bot posts one composed message to General's announcement
1631
+ subchannel (`channelId` 2, `subchannelId` 2). This is the public official
1632
+ board, not a DM and not a Home comment. Mechanics:
1633
+
1634
+ - requires the `chat:post` scope of a comment-mode `post` run;
1635
+ - composed-only (`--file`, same 10,000-character limit as `chat send`);
1636
+ - authors as Zero or Ciel only — the ordinary chat post route and the
1637
+ announcement socket relay now treat those two IDs as allowed announcement
1638
+ authors, same as management-level 3 humans;
1639
+ - persists through the ordinary `msg_chats` insert (channel lock +
1640
+ visibility restore), writes the Twinkle Newspaper `announcement:<messageId>`
1641
+ event, and relays `new_chat_message` on General. No bot socket, session, or
1642
+ presence;
1643
+ - audited as `announcement.post` and idempotent per request key.
1644
+
1645
+ Use this only when Mikey asks for an official announcement. Do not treat a
1646
+ management run as a standing license to post there.
1647
+
1614
1648
  ## Audit history
1615
1649
 
1616
1650
  ```bash