@stage5/lumine 0.2.26 → 0.2.28

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
@@ -181,6 +181,7 @@ lumine admin subject feature 123 --json
181
181
  lumine admin subject unfeature 123 --json
182
182
  lumine admin featured reorder --subject-ids 30,20,10 --json
183
183
  lumine admin brief --days 3 --json
184
+ lumine admin notable add Minecrarft_guy --json
184
185
  lumine admin post recommend comment:456 --anyone-can-reward --reward-twinkles 3 --json
185
186
  lumine admin post reward comment:456 --twinkles 3 --json
186
187
  lumine admin daily-run complete --json
package/lib/admin.js CHANGED
@@ -12,6 +12,11 @@ const MAX_COMPOSED_COMMENT_LENGTH = 10_000;
12
12
  // its model and no AI Energy is spent.
13
13
  function readComposedCommentFile(filePath) {
14
14
  const normalizedPath = String(filePath || "").trim();
15
+ if (!normalizedPath) {
16
+ throw cliValidationError(
17
+ "Pass composed comment text with --file <comment.md>.",
18
+ );
19
+ }
15
20
  let contents;
16
21
  try {
17
22
  contents = readFileSync(normalizedPath, "utf8");
@@ -580,6 +585,24 @@ export function parseAdminOperation(options) {
580
585
  );
581
586
  }
582
587
 
588
+ if (namespace === "notable" && action === "add") {
589
+ const rawTarget = String(target || "").trim();
590
+ if (!rawTarget) {
591
+ throw cliValidationError(
592
+ "Usage: lumine admin notable add <userId|username>.",
593
+ );
594
+ }
595
+ const body = /^\d+$/.test(rawTarget)
596
+ ? { userId: parseRequiredInteger(rawTarget, "user ID", 1) }
597
+ : { username: rawTarget };
598
+ return writeOperation(
599
+ "notable.add",
600
+ "POST",
601
+ "/cli/admin/notable-users",
602
+ body,
603
+ );
604
+ }
605
+
583
606
  if (namespace === "brief" && !action) {
584
607
  if (options.adminDays) {
585
608
  const days = Number(options.adminDays);
@@ -638,6 +661,30 @@ export function parseAdminOperation(options) {
638
661
  },
639
662
  );
640
663
  }
664
+ if (action === "edit") {
665
+ const rawTarget = String(target || "").trim();
666
+ let commentId;
667
+ if (/^\d+$/.test(rawTarget)) {
668
+ commentId = parseRequiredInteger(rawTarget, "comment id", 1);
669
+ } else {
670
+ const parsedTarget = parseRecommendationTarget({
671
+ target,
672
+ explicitType: options.adminType,
673
+ });
674
+ if (parsedTarget.type !== "comment") {
675
+ throw cliValidationError(
676
+ "comment edit targets a comment: lumine admin comment edit <commentId> --file <comment.md>.",
677
+ );
678
+ }
679
+ commentId = parsedTarget.id;
680
+ }
681
+ return writeOperation(
682
+ "comment.edit",
683
+ "PUT",
684
+ `/cli/admin/comments/${commentId}`,
685
+ { content: readComposedCommentFile(options.adminFile) },
686
+ );
687
+ }
641
688
  if (action === "post") {
642
689
  const draftId = parseRequiredInteger(
643
690
  options.draftId || target,
@@ -654,7 +701,7 @@ export function parseAdminOperation(options) {
654
701
  }
655
702
 
656
703
  throw cliValidationError(
657
- "Usage: lumine admin identity|daily-run|recommendations|post|subjects|subject|featured|comment|news|audit|brief ...",
704
+ "Usage: lumine admin identity|daily-run|recommendations|post|subjects|subject|featured|comment|news|audit|brief|notable ...",
658
705
  );
659
706
  }
660
707
 
@@ -1059,6 +1106,12 @@ function printAdminResult({ operation, result }) {
1059
1106
  }
1060
1107
  return;
1061
1108
  }
1109
+ if (data.notableUser) {
1110
+ console.log(
1111
+ `${result.status || "success"}: ${data.notableUser.username || "unknown"} (#${data.notableUser.userId}).`,
1112
+ );
1113
+ return;
1114
+ }
1062
1115
  if (data.skip) {
1063
1116
  console.log(
1064
1117
  `${result.status}: ${data.skip.contentType}:${data.skip.contentId} skipped.`,
package/lib/commands.js CHANGED
@@ -2526,7 +2526,9 @@ export function printHelp() {
2526
2526
  lumine admin comment draft <target> [--type subject|comment|aiStory|dailyReflection] [--identity zero|ciel|auto] [--json]
2527
2527
  lumine admin comment reply comment:<id> [--identity zero|ciel|auto] [--json]
2528
2528
  lumine admin comment post --draft-id <id> [--json]
2529
+ lumine admin comment edit <comment-id> --file <comment.md> [--json]
2529
2530
  lumine admin brief [--days <1..30>] [--json]
2531
+ lumine admin notable add <user-id|username> [--json]
2530
2532
  lumine admin audit [list] [--run current|last|<run-id>] [--target <target>] [--actions <a,b>] [--full] [--cursor <cursor>] [--json]
2531
2533
 
2532
2534
  Examples:
@@ -2607,6 +2609,7 @@ Options:
2607
2609
  --reward-twinkles 3 Pair a recommendation with exactly 3 Twinkles
2608
2610
  --twinkles 3 Give exactly 3 Twinkles through the normal economy
2609
2611
  --draft-id <id> Canonical delegated comment draft ID
2612
+ --file <path> Editorial JSON or composed comment text file
2610
2613
  --reason <text> Reason when marking an admin run failed
2611
2614
  --force Overwrite server files even if this workspace is stale or missing filesHash
2612
2615
  --search <text> Search public open-source Builds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.26",
3
+ "version": "0.2.28",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1041,6 +1041,8 @@ type NewsSubmit = NewsStatus; // "success"; newspaper includes revisionNumber
1041
1041
  ```bash
1042
1042
  lumine admin brief --json
1043
1043
  lumine admin brief --days 3 --json
1044
+ lumine admin notable add 12647 --json
1045
+ lumine admin notable add Minecrarft_guy --json
1044
1046
  ```
1045
1047
 
1046
1048
  Read-only management insights for the delegated workflow, windowed since the
@@ -1062,24 +1064,39 @@ Four sections (Mikey's chosen cut, 2026-08-10):
1062
1064
  - `aiSpending` — a compact projection of the management AI-cost report:
1063
1065
  `summary`, top spending accounts, top risk groups. Unlike the other sections'
1064
1066
  exact `window.sinceTs`, this existing report is bucketed into whole UTC days;
1065
- `aiSpending.days` includes today and may begin up to one day before the exact
1066
- brief window. Flag accounts that jumped tiers or dominate that report period.
1067
- May be `{ unavailable: true }` if the cost report fails; say so rather than
1068
- guessing.
1067
+ `aiSpending.startDayIndex` and `endDayIndex` are its canonical bounds. The
1068
+ report period can begin up to one day before or after the exact brief window,
1069
+ so use those bounds when describing it. Flag accounts that jumped tiers or
1070
+ dominate that report period. May be `{ unavailable: true }` if the cost
1071
+ report fails; say so rather than guessing.
1069
1072
  - `notableCandidates` — kids (never bots, staff `userType`s, or users already
1070
1073
  on the Notable Users list) ranked by authored activity in the window, with
1071
1074
  `isNewUser` marking window-new signups. Use it to find the overlooked and
1072
1075
  rising users the editorial priorities exist for, and propose additions to
1073
- Mikey's Notable Users list in the report — the run never edits that list
1074
- itself.
1075
- - `teachers` — every `userType='supermod'` account with per-window
1076
- `subjectsPosted`, `commentsPosted`, `recommendationsGiven`, `rewardsGiven`,
1077
- `rewardTwinklesGiven`, `lastActive`/`daysSinceActive`, ordered by an
1078
- engagement score. Mikey's standing question here: which teachers genuinely
1079
- use the website to its fullest and which only work through it. Authored
1080
- posts and comments signal personal engagement; recommendations and rewards
1081
- are the "work" verbs — report the contrast, not just the totals, and treat
1082
- it as notable-users-but-for-teachers.
1076
+ Mikey's Notable Users list in the report. When Mikey approves additions,
1077
+ execute them with `lumine admin notable add <userId|username>` (idempotent —
1078
+ an existing member returns `already_done`; requires the `notable:write`
1079
+ scope, audited as `notable.add`, and writes through the management page's
1080
+ own canonical service). Without his approval the run only proposes.
1081
+ - `teachers` — the mentor/sage achievement holders (the accounts the website
1082
+ titles teacher/headteacher): real classroom teachers, NOT the
1083
+ `userType='supermod'` Korean operations staff, whose work-only usage is
1084
+ expected and deliberately excluded. Mikey's standing question here: which
1085
+ teachers genuinely use the website for themselves and which only work
1086
+ through it. Recommendations and rewards are classroom "work" verbs and
1087
+ count for nothing toward interest; genuine personal interest is measured by
1088
+ what a teacher does for themselves — authored subjects/comments,
1089
+ `reflections` (Daily Reflections answered), `dailyTasksCompleted`,
1090
+ `wordlePlays`, `buildsTouched` (Lumine builds they own that changed in the
1091
+ window), `aiStories`, and bounded `xpEvents` (XP-ledger activity). Shape:
1092
+ `genuinelyInterested` (interestScore > 0, ordered by it, each row carrying
1093
+ all the per-window counters plus `rank: mentor|sage` and `workScore`),
1094
+ `workOnly` (classroom verbs only, zero personal signals), `activeButSilent`
1095
+ (window-active log-ins with nothing at all, capped at 40), and `totals`.
1096
+ Daily-task counts use canonical whole-day indices and can begin up to one
1097
+ calendar day before the exact `window.sinceTs`.
1098
+ Report the contrast between the buckets, and treat `genuinelyInterested` as
1099
+ notable-users-but-for-teachers.
1083
1100
 
1084
1101
  The command needs only an active run's `content:read` scope and mutates
1085
1102
  nothing; reading the brief is not audited content action. Window boundaries
@@ -1088,6 +1105,27 @@ on the big append-only ledgers are found by binary-searching the PRIMARY key
1088
1105
  is still bounded to the selected 1–30 day window.
1089
1106
 
1090
1107
  ```ts
1108
+ type TeacherInsight = {
1109
+ userId: number;
1110
+ username: string | null;
1111
+ rank: 'mentor' | 'sage';
1112
+ lastActive: number | null;
1113
+ daysSinceActive: number | null;
1114
+ subjectsPosted: number;
1115
+ commentsPosted: number;
1116
+ reflections: number;
1117
+ dailyTasksCompleted: number;
1118
+ wordlePlays: number;
1119
+ buildsTouched: number;
1120
+ aiStories: number;
1121
+ xpEvents: number;
1122
+ recommendationsGiven: number;
1123
+ rewardsGiven: number;
1124
+ rewardTwinklesGiven: number;
1125
+ interestScore: number;
1126
+ workScore: number;
1127
+ };
1128
+
1091
1129
  type InsightsBrief = Success<{
1092
1130
  window: {
1093
1131
  sinceTs: number;
@@ -1123,21 +1161,21 @@ type InsightsBrief = Success<{
1123
1161
  isNewUser: boolean;
1124
1162
  lastActive: number | null;
1125
1163
  }>;
1126
- teachers: Array<{
1127
- userId: number;
1128
- username: string | null;
1129
- lastActive: number | null;
1130
- daysSinceActive: number | null;
1131
- subjectsPosted: number;
1132
- commentsPosted: number;
1133
- recommendationsGiven: number;
1134
- rewardsGiven: number;
1135
- rewardTwinklesGiven: number;
1136
- engagementScore: number;
1137
- }>;
1164
+ teachers: {
1165
+ genuinelyInterested: TeacherInsight[];
1166
+ workOnly: TeacherInsight[];
1167
+ activeButSilent: TeacherInsight[];
1168
+ totals: {
1169
+ total: number;
1170
+ activeInWindow: number;
1171
+ interestedInWindow: number;
1172
+ };
1173
+ };
1138
1174
  aiSpending:
1139
1175
  | {
1140
1176
  days: number;
1177
+ startDayIndex: number;
1178
+ endDayIndex: number;
1141
1179
  summary: unknown;
1142
1180
  topAccounts: unknown[];
1143
1181
  topRiskGroups: unknown[];
@@ -1146,6 +1184,24 @@ type InsightsBrief = Success<{
1146
1184
  }>;
1147
1185
  ```
1148
1186
 
1187
+ **Editing the bot's own comments.** `comment edit <commentId> --file
1188
+ <comment.md>` replaces the text of a comment the ACTING bot itself authored —
1189
+ for correcting a factual error, an unfulfillable claim, or outdated guidance
1190
+ in Zero/Ciel's own words. It is deliberately not a moderation verb: comments
1191
+ by the other bot, by any human, and hidden notification records are all
1192
+ rejected (`CLI_ADMIN_EDIT_NOT_OWN_COMMENT`,
1193
+ `CLI_ADMIN_EDIT_NOTIFICATION_COMMENT`). The replacement text follows the
1194
+ composed-comment rules (plain UTF-8, 10,000-character limit, truth about what
1195
+ the session actually did) and publishes through the website's canonical
1196
+ comment-edit pipeline — mentions are reprocessed (a newly added `@mikey`
1197
+ notifies him), and Earn-candidate projections resync. Submitting identical
1198
+ text returns `already_done`. Requires the `comment:post` scope of a
1199
+ comment-mode `post` run, and is audited as `comment.edit` with the previous
1200
+ content in `beforeState` and `data.edit.previousContent`. Edit sparingly:
1201
+ kids may have already read the original, so a comment that changed meaning
1202
+ (not just wording) usually deserves a follow-up reply instead of a silent
1203
+ rewrite.
1204
+
1149
1205
  ## Audit history
1150
1206
 
1151
1207
  ```bash
@@ -1216,6 +1272,9 @@ lumine admin comment reply comment:456 --json
1216
1272
 
1217
1273
  lumine admin comment post --draft-id 77 \
1218
1274
  --idempotency-key comment-123-post-v1 --json
1275
+
1276
+ # Correct the acting bot's OWN published comment.
1277
+ lumine admin comment edit 342752 --file corrected.md --json
1219
1278
  ```
1220
1279
 
1221
1280
  **Compose in persona by default (Mikey's standing direction, 2026-08-10).**
@@ -1237,6 +1296,24 @@ from memory:
1237
1296
  decisions are yours to make and record with `post skip` or in the run
1238
1297
  report).
1239
1298
 
1299
+ **Lumine Build app posts are composed-only, and only after actually looking
1300
+ (Mikey's direction, 2026-08-10).** When a post is about a Build app — the
1301
+ author shares their app, announces an update, or asks for feedback on their
1302
+ build — never use the server-generated draft path: the API persona cannot
1303
+ open an app, so its drafts either stay generic or fabricate first-hand
1304
+ experience (a real Ciel draft claimed "I clicked over to check it out" on an
1305
+ app nobody had opened). The day's management agent comments instead, and
1306
+ looks first: pull the project with lumine-cli when it is open source (or
1307
+ yours to pull) and read the code, or open the app and actually try it; then
1308
+ compose a comment whose specifics come from what you genuinely saw — a
1309
+ mechanic you liked, a nice touch in their code, a concrete suggestion. Be
1310
+ truthful about what you did: "I read through your code" and "I played a few
1311
+ rounds" are different claims, and a comment must only make the one that
1312
+ happened. If you could not access the app at all, say nothing about having
1313
+ tried it — ask the author about it instead. This is the standing rule for
1314
+ every composed comment, applied to apps: never claim an experience the
1315
+ session did not actually have.
1316
+
1240
1317
  A composed draft (`--file`, plain UTF-8 text, at most the website's 10,000
1241
1318
  character comment limit) flows through the identical draft lifecycle —
1242
1319
  reservation, idempotency, context-revision CAS, publish fencing, audit