@stage5/lumine 0.2.25 → 0.2.27
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 +1 -0
- package/lib/admin.js +43 -1
- package/lib/commands.js +4 -0
- package/package.json +1 -1
- package/sdk/LUMINE_ADMIN.md +152 -0
package/README.md
CHANGED
|
@@ -180,6 +180,7 @@ lumine admin featured list --json
|
|
|
180
180
|
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
|
+
lumine admin brief --days 3 --json
|
|
183
184
|
lumine admin post recommend comment:456 --anyone-can-reward --reward-twinkles 3 --json
|
|
184
185
|
lumine admin post reward comment:456 --twinkles 3 --json
|
|
185
186
|
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,19 @@ export function parseAdminOperation(options) {
|
|
|
580
585
|
);
|
|
581
586
|
}
|
|
582
587
|
|
|
588
|
+
if (namespace === "brief" && !action) {
|
|
589
|
+
if (options.adminDays) {
|
|
590
|
+
const days = Number(options.adminDays);
|
|
591
|
+
if (!Number.isInteger(days) || days < 1 || days > 30) {
|
|
592
|
+
throw cliValidationError("--days must be an integer between 1 and 30.");
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return readOperation(
|
|
596
|
+
"insights.brief",
|
|
597
|
+
withQuery("/cli/admin/insights/brief", { days: options.adminDays }),
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
583
601
|
if (namespace === "audit" && (!action || action === "list")) {
|
|
584
602
|
const runFilter = String(options.adminRun || "").trim();
|
|
585
603
|
if (runFilter && !["current", "last"].includes(runFilter)) {
|
|
@@ -625,6 +643,30 @@ export function parseAdminOperation(options) {
|
|
|
625
643
|
},
|
|
626
644
|
);
|
|
627
645
|
}
|
|
646
|
+
if (action === "edit") {
|
|
647
|
+
const rawTarget = String(target || "").trim();
|
|
648
|
+
let commentId;
|
|
649
|
+
if (/^\d+$/.test(rawTarget)) {
|
|
650
|
+
commentId = parseRequiredInteger(rawTarget, "comment id", 1);
|
|
651
|
+
} else {
|
|
652
|
+
const parsedTarget = parseRecommendationTarget({
|
|
653
|
+
target,
|
|
654
|
+
explicitType: options.adminType,
|
|
655
|
+
});
|
|
656
|
+
if (parsedTarget.type !== "comment") {
|
|
657
|
+
throw cliValidationError(
|
|
658
|
+
"comment edit targets a comment: lumine admin comment edit <commentId> --file <comment.md>.",
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
commentId = parsedTarget.id;
|
|
662
|
+
}
|
|
663
|
+
return writeOperation(
|
|
664
|
+
"comment.edit",
|
|
665
|
+
"PUT",
|
|
666
|
+
`/cli/admin/comments/${commentId}`,
|
|
667
|
+
{ content: readComposedCommentFile(options.adminFile) },
|
|
668
|
+
);
|
|
669
|
+
}
|
|
628
670
|
if (action === "post") {
|
|
629
671
|
const draftId = parseRequiredInteger(
|
|
630
672
|
options.draftId || target,
|
|
@@ -641,7 +683,7 @@ export function parseAdminOperation(options) {
|
|
|
641
683
|
}
|
|
642
684
|
|
|
643
685
|
throw cliValidationError(
|
|
644
|
-
"Usage: lumine admin identity|daily-run|recommendations|post|subjects|subject|featured|comment|news|audit ...",
|
|
686
|
+
"Usage: lumine admin identity|daily-run|recommendations|post|subjects|subject|featured|comment|news|audit|brief ...",
|
|
645
687
|
);
|
|
646
688
|
}
|
|
647
689
|
|
package/lib/commands.js
CHANGED
|
@@ -2188,6 +2188,7 @@ export function parseArgs(args) {
|
|
|
2188
2188
|
adminTarget: raw.target ? String(raw.target) : "",
|
|
2189
2189
|
adminActions: raw.actions ? String(raw.actions) : "",
|
|
2190
2190
|
adminDate: raw.date ? String(raw.date) : "",
|
|
2191
|
+
adminDays: raw.days ? String(raw.days) : "",
|
|
2191
2192
|
adminEditionId: raw.editionId ? String(raw.editionId) : "",
|
|
2192
2193
|
adminLeaseToken: raw.leaseToken ? String(raw.leaseToken) : "",
|
|
2193
2194
|
adminFile: raw.file ? String(raw.file) : "",
|
|
@@ -2525,6 +2526,8 @@ export function printHelp() {
|
|
|
2525
2526
|
lumine admin comment draft <target> [--type subject|comment|aiStory|dailyReflection] [--identity zero|ciel|auto] [--json]
|
|
2526
2527
|
lumine admin comment reply comment:<id> [--identity zero|ciel|auto] [--json]
|
|
2527
2528
|
lumine admin comment post --draft-id <id> [--json]
|
|
2529
|
+
lumine admin comment edit <comment-id> --file <comment.md> [--json]
|
|
2530
|
+
lumine admin brief [--days <1..30>] [--json]
|
|
2528
2531
|
lumine admin audit [list] [--run current|last|<run-id>] [--target <target>] [--actions <a,b>] [--full] [--cursor <cursor>] [--json]
|
|
2529
2532
|
|
|
2530
2533
|
Examples:
|
|
@@ -2605,6 +2608,7 @@ Options:
|
|
|
2605
2608
|
--reward-twinkles 3 Pair a recommendation with exactly 3 Twinkles
|
|
2606
2609
|
--twinkles 3 Give exactly 3 Twinkles through the normal economy
|
|
2607
2610
|
--draft-id <id> Canonical delegated comment draft ID
|
|
2611
|
+
--file <path> Editorial JSON or composed comment text file
|
|
2608
2612
|
--reason <text> Reason when marking an admin run failed
|
|
2609
2613
|
--force Overwrite server files even if this workspace is stale or missing filesHash
|
|
2610
2614
|
--search <text> Search public open-source Builds
|
package/package.json
CHANGED
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -1036,6 +1036,137 @@ type NewsClaim = Success<{
|
|
|
1036
1036
|
type NewsSubmit = NewsStatus; // "success"; newspaper includes revisionNumber
|
|
1037
1037
|
```
|
|
1038
1038
|
|
|
1039
|
+
## Daily brief (management insights)
|
|
1040
|
+
|
|
1041
|
+
```bash
|
|
1042
|
+
lumine admin brief --json
|
|
1043
|
+
lumine admin brief --days 3 --json
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
Read-only management insights for the delegated workflow, windowed since the
|
|
1047
|
+
operator's last completed run by default (`--days 1..30` overrides; capped at
|
|
1048
|
+
30 days). Call it early in every run — right after the newspaper check — and
|
|
1049
|
+
end every run report with an **"Insights for Mikey"** section carrying only
|
|
1050
|
+
the deltas and anomalies worth his time, next to the escalation list. Never
|
|
1051
|
+
dump raw sections at him.
|
|
1052
|
+
|
|
1053
|
+
Four sections (Mikey's chosen cut, 2026-08-10):
|
|
1054
|
+
|
|
1055
|
+
- `economy` — `topGainers` (coin-ledger aggregation over the window: gained,
|
|
1056
|
+
spent, net, current balance per user, Zero/Ciel excluded) and `topBalances`
|
|
1057
|
+
(current top-ten holders, also excluding Zero/Ciel). This is where
|
|
1058
|
+
alt-account farming, sudden windfalls, and "someone got a million coins in a
|
|
1059
|
+
week" surface with real numbers instead of secondhand kid gossip;
|
|
1060
|
+
cross-check outliers against the
|
|
1061
|
+
economy-manipulation escalation category.
|
|
1062
|
+
- `aiSpending` — a compact projection of the management AI-cost report:
|
|
1063
|
+
`summary`, top spending accounts, top risk groups. Unlike the other sections'
|
|
1064
|
+
exact `window.sinceTs`, this existing report is bucketed into whole UTC days;
|
|
1065
|
+
`aiSpending.startDayIndex` and `endDayIndex` are its canonical bounds. The
|
|
1066
|
+
report period can begin up to one day before or after the exact brief window,
|
|
1067
|
+
so use those bounds when describing it. Flag accounts that jumped tiers or
|
|
1068
|
+
dominate that report period. May be `{ unavailable: true }` if the cost
|
|
1069
|
+
report fails; say so rather than guessing.
|
|
1070
|
+
- `notableCandidates` — kids (never bots, staff `userType`s, or users already
|
|
1071
|
+
on the Notable Users list) ranked by authored activity in the window, with
|
|
1072
|
+
`isNewUser` marking window-new signups. Use it to find the overlooked and
|
|
1073
|
+
rising users the editorial priorities exist for, and propose additions to
|
|
1074
|
+
Mikey's Notable Users list in the report — the run never edits that list
|
|
1075
|
+
itself.
|
|
1076
|
+
- `teachers` — every `userType='supermod'` account with per-window
|
|
1077
|
+
`subjectsPosted`, `commentsPosted`, `recommendationsGiven`, `rewardsGiven`,
|
|
1078
|
+
`rewardTwinklesGiven`, `lastActive`/`daysSinceActive`, ordered by an
|
|
1079
|
+
engagement score. Mikey's standing question here: which teachers genuinely
|
|
1080
|
+
use the website to its fullest and which only work through it. Authored
|
|
1081
|
+
posts and comments signal personal engagement; recommendations and rewards
|
|
1082
|
+
are the "work" verbs — report the contrast, not just the totals, and treat
|
|
1083
|
+
it as notable-users-but-for-teachers.
|
|
1084
|
+
|
|
1085
|
+
The command needs only an active run's `content:read` scope and mutates
|
|
1086
|
+
nothing; reading the brief is not audited content action. Window boundaries
|
|
1087
|
+
on the big append-only ledgers are found by binary-searching the PRIMARY key
|
|
1088
|
+
(several tables have no timeStamp index), avoiding lifetime scans; aggregation
|
|
1089
|
+
is still bounded to the selected 1–30 day window.
|
|
1090
|
+
|
|
1091
|
+
```ts
|
|
1092
|
+
type InsightsBrief = Success<{
|
|
1093
|
+
window: {
|
|
1094
|
+
sinceTs: number;
|
|
1095
|
+
days: number;
|
|
1096
|
+
source: 'requested' | 'since-last-completed-run' | 'default';
|
|
1097
|
+
generatedAt: number;
|
|
1098
|
+
};
|
|
1099
|
+
economy: {
|
|
1100
|
+
topGainers: Array<{
|
|
1101
|
+
userId: number;
|
|
1102
|
+
username: string | null;
|
|
1103
|
+
userType: string | null;
|
|
1104
|
+
gained: number;
|
|
1105
|
+
spent: number;
|
|
1106
|
+
net: number;
|
|
1107
|
+
currentCoins: number;
|
|
1108
|
+
joinedAt: number | null;
|
|
1109
|
+
}>;
|
|
1110
|
+
topBalances: Array<{
|
|
1111
|
+
userId: number;
|
|
1112
|
+
username: string | null;
|
|
1113
|
+
userType: string | null;
|
|
1114
|
+
coins: number;
|
|
1115
|
+
}>;
|
|
1116
|
+
};
|
|
1117
|
+
notableCandidates: Array<{
|
|
1118
|
+
userId: number;
|
|
1119
|
+
username: string | null;
|
|
1120
|
+
subjectsPosted: number;
|
|
1121
|
+
commentsPosted: number;
|
|
1122
|
+
activityScore: number;
|
|
1123
|
+
joinedAt: number | null;
|
|
1124
|
+
isNewUser: boolean;
|
|
1125
|
+
lastActive: number | null;
|
|
1126
|
+
}>;
|
|
1127
|
+
teachers: Array<{
|
|
1128
|
+
userId: number;
|
|
1129
|
+
username: string | null;
|
|
1130
|
+
lastActive: number | null;
|
|
1131
|
+
daysSinceActive: number | null;
|
|
1132
|
+
subjectsPosted: number;
|
|
1133
|
+
commentsPosted: number;
|
|
1134
|
+
recommendationsGiven: number;
|
|
1135
|
+
rewardsGiven: number;
|
|
1136
|
+
rewardTwinklesGiven: number;
|
|
1137
|
+
engagementScore: number;
|
|
1138
|
+
}>;
|
|
1139
|
+
aiSpending:
|
|
1140
|
+
| {
|
|
1141
|
+
days: number;
|
|
1142
|
+
startDayIndex: number;
|
|
1143
|
+
endDayIndex: number;
|
|
1144
|
+
summary: unknown;
|
|
1145
|
+
topAccounts: unknown[];
|
|
1146
|
+
topRiskGroups: unknown[];
|
|
1147
|
+
}
|
|
1148
|
+
| { unavailable: true; error: string };
|
|
1149
|
+
}>;
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
**Editing the bot's own comments.** `comment edit <commentId> --file
|
|
1153
|
+
<comment.md>` replaces the text of a comment the ACTING bot itself authored —
|
|
1154
|
+
for correcting a factual error, an unfulfillable claim, or outdated guidance
|
|
1155
|
+
in Zero/Ciel's own words. It is deliberately not a moderation verb: comments
|
|
1156
|
+
by the other bot, by any human, and hidden notification records are all
|
|
1157
|
+
rejected (`CLI_ADMIN_EDIT_NOT_OWN_COMMENT`,
|
|
1158
|
+
`CLI_ADMIN_EDIT_NOTIFICATION_COMMENT`). The replacement text follows the
|
|
1159
|
+
composed-comment rules (plain UTF-8, 10,000-character limit, truth about what
|
|
1160
|
+
the session actually did) and publishes through the website's canonical
|
|
1161
|
+
comment-edit pipeline — mentions are reprocessed (a newly added `@mikey`
|
|
1162
|
+
notifies him), and Earn-candidate projections resync. Submitting identical
|
|
1163
|
+
text returns `already_done`. Requires the `comment:post` scope of a
|
|
1164
|
+
comment-mode `post` run, and is audited as `comment.edit` with the previous
|
|
1165
|
+
content in `beforeState` and `data.edit.previousContent`. Edit sparingly:
|
|
1166
|
+
kids may have already read the original, so a comment that changed meaning
|
|
1167
|
+
(not just wording) usually deserves a follow-up reply instead of a silent
|
|
1168
|
+
rewrite.
|
|
1169
|
+
|
|
1039
1170
|
## Audit history
|
|
1040
1171
|
|
|
1041
1172
|
```bash
|
|
@@ -1106,6 +1237,9 @@ lumine admin comment reply comment:456 --json
|
|
|
1106
1237
|
|
|
1107
1238
|
lumine admin comment post --draft-id 77 \
|
|
1108
1239
|
--idempotency-key comment-123-post-v1 --json
|
|
1240
|
+
|
|
1241
|
+
# Correct the acting bot's OWN published comment.
|
|
1242
|
+
lumine admin comment edit 342752 --file corrected.md --json
|
|
1109
1243
|
```
|
|
1110
1244
|
|
|
1111
1245
|
**Compose in persona by default (Mikey's standing direction, 2026-08-10).**
|
|
@@ -1127,6 +1261,24 @@ from memory:
|
|
|
1127
1261
|
decisions are yours to make and record with `post skip` or in the run
|
|
1128
1262
|
report).
|
|
1129
1263
|
|
|
1264
|
+
**Lumine Build app posts are composed-only, and only after actually looking
|
|
1265
|
+
(Mikey's direction, 2026-08-10).** When a post is about a Build app — the
|
|
1266
|
+
author shares their app, announces an update, or asks for feedback on their
|
|
1267
|
+
build — never use the server-generated draft path: the API persona cannot
|
|
1268
|
+
open an app, so its drafts either stay generic or fabricate first-hand
|
|
1269
|
+
experience (a real Ciel draft claimed "I clicked over to check it out" on an
|
|
1270
|
+
app nobody had opened). The day's management agent comments instead, and
|
|
1271
|
+
looks first: pull the project with lumine-cli when it is open source (or
|
|
1272
|
+
yours to pull) and read the code, or open the app and actually try it; then
|
|
1273
|
+
compose a comment whose specifics come from what you genuinely saw — a
|
|
1274
|
+
mechanic you liked, a nice touch in their code, a concrete suggestion. Be
|
|
1275
|
+
truthful about what you did: "I read through your code" and "I played a few
|
|
1276
|
+
rounds" are different claims, and a comment must only make the one that
|
|
1277
|
+
happened. If you could not access the app at all, say nothing about having
|
|
1278
|
+
tried it — ask the author about it instead. This is the standing rule for
|
|
1279
|
+
every composed comment, applied to apps: never claim an experience the
|
|
1280
|
+
session did not actually have.
|
|
1281
|
+
|
|
1130
1282
|
A composed draft (`--file`, plain UTF-8 text, at most the website's 10,000
|
|
1131
1283
|
character comment limit) flows through the identical draft lifecycle —
|
|
1132
1284
|
reservation, idempotency, context-revision CAS, publish fencing, audit
|