@stage5/lumine 0.2.36 → 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;
@@ -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
 
package/lib/commands.js CHANGED
@@ -2541,6 +2541,7 @@ export function printHelp() {
2541
2541
  lumine admin comment edit <comment-id> --file <comment.md> [--json]
2542
2542
  lumine admin brief [--days <1..30>] [--json]
2543
2543
  lumine admin bot-output [--days <1..30>] [--json]
2544
+ lumine admin announcement post --file <announcement.md> [--json]
2544
2545
  lumine admin chat send <user-id|username> --file <message.md> [--json]
2545
2546
  lumine admin notable add <user-id|username> --note <text> [--json]
2546
2547
  lumine admin audit [list] [--run current|last|<run-id>] [--target <target>] [--actions <a,b>] [--full] [--cursor <cursor>] [--json]
@@ -2626,7 +2627,7 @@ Options:
2626
2627
  --reward-twinkles 3 Pair a recommendation with exactly 3 Twinkles
2627
2628
  --twinkles 3 Give exactly 3 Twinkles through the normal economy
2628
2629
  --draft-id <id> Canonical delegated comment draft ID
2629
- --file <path> Editorial JSON or composed comment text file
2630
+ --file <path> Editorial JSON or composed text file
2630
2631
  --reviewed-version <id> Published Build artifact version actually reviewed
2631
2632
  --reviewed-via <method> Build review method: runtime or code
2632
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.36",
3
+ "version": "0.2.37",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1621,6 +1621,30 @@ Ordinary warm follow-ups (checking on a member the bots already know after
1621
1621
  something the run surfaced) are within a run's judgment, sparingly, and are
1622
1622
  always reported in the run report.
1623
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
+
1624
1648
  ## Audit history
1625
1649
 
1626
1650
  ```bash