@stage5/lumine 0.2.98 → 0.3.0
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 +263 -3
- package/lib/commands.js +7 -0
- package/package.json +1 -1
- package/sdk/LUMINE_ADMIN.md +68 -0
package/lib/admin.js
CHANGED
|
@@ -640,6 +640,8 @@ export async function adminCommand(options) {
|
|
|
640
640
|
let runScope = null;
|
|
641
641
|
let correctionSessionId = 0;
|
|
642
642
|
let correctionSession = null;
|
|
643
|
+
let commentSessionId = 0;
|
|
644
|
+
let commentSession = null;
|
|
643
645
|
if (adminOperationRequiresRun(operation)) {
|
|
644
646
|
if (operation.correctionEligible) {
|
|
645
647
|
const correctionStatus = await requestJson({
|
|
@@ -657,6 +659,15 @@ export async function adminCommand(options) {
|
|
|
657
659
|
correctionSession = correction;
|
|
658
660
|
}
|
|
659
661
|
}
|
|
662
|
+
if (!correctionSessionId && adminOperationUsesCommentSession(operation)) {
|
|
663
|
+
commentSession = await loadActiveCommentSession({
|
|
664
|
+
options,
|
|
665
|
+
authToken: auth.token,
|
|
666
|
+
});
|
|
667
|
+
commentSessionId = Number(commentSession?.id || 0);
|
|
668
|
+
}
|
|
669
|
+
// Look up the daily run too: when both are active the caller must choose
|
|
670
|
+
// one (--via), and exactly one authority is sent.
|
|
660
671
|
const runStatus = correctionSessionId
|
|
661
672
|
? null
|
|
662
673
|
: await requestJson({
|
|
@@ -671,9 +682,39 @@ export async function adminCommand(options) {
|
|
|
671
682
|
].includes(operation.name)
|
|
672
683
|
? runStatus?.data?.lastRun || null
|
|
673
684
|
: null;
|
|
674
|
-
|
|
685
|
+
let selectedRun = activeRun || retryableFinishedRun;
|
|
686
|
+
const via = parseAdminVia(options.adminVia, operation);
|
|
687
|
+
if (via && correctionSessionId) {
|
|
688
|
+
throw cliValidationError(
|
|
689
|
+
`Correction session #${correctionSessionId} is active for this comment and handles it; drop --via or complete the correction first.`,
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
if (commentSessionId && selectedRun) {
|
|
693
|
+
let choice = via;
|
|
694
|
+
if (!choice) {
|
|
695
|
+
if (COMMENT_SESSION_WRITE_OPERATIONS.has(operation.name)) {
|
|
696
|
+
throw cliValidationError(
|
|
697
|
+
`Both comment session #${commentSessionId} (${commentSession.identity?.key || "unknown"}) and daily run #${selectedRun.id} (${selectedRun.identity?.key || "unknown"}) are active. Choose which one this ${operation.name} uses with --via session or --via run.`,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
// Reads create nothing; without --via they keep using the daily run.
|
|
701
|
+
choice = "run";
|
|
702
|
+
}
|
|
703
|
+
if (choice === "session") {
|
|
704
|
+
selectedRun = null;
|
|
705
|
+
} else {
|
|
706
|
+
commentSessionId = 0;
|
|
707
|
+
commentSession = null;
|
|
708
|
+
}
|
|
709
|
+
} else if (via === "session" && !commentSessionId) {
|
|
710
|
+
throw cliValidationError(
|
|
711
|
+
"--via session: no comment session is active. Start one with lumine admin comment session start.",
|
|
712
|
+
);
|
|
713
|
+
} else if (via === "run" && !selectedRun && !correctionSessionId) {
|
|
714
|
+
throw noActiveRunError();
|
|
715
|
+
}
|
|
675
716
|
runId = Number(selectedRun?.id || 0);
|
|
676
|
-
if (!runId && !correctionSessionId) {
|
|
717
|
+
if (!runId && !correctionSessionId && !commentSessionId) {
|
|
677
718
|
if (operation.correctionEligible) {
|
|
678
719
|
const error = new Error(
|
|
679
720
|
`Start a correction session first: lumine admin correction start ${operation.correctionCommentId}.`,
|
|
@@ -687,7 +728,17 @@ export async function adminCommand(options) {
|
|
|
687
728
|
runScope = canonicalAdminRunScope(selectedRun);
|
|
688
729
|
assertAdminOperationAllowedForRunScope({ operation, runScope });
|
|
689
730
|
}
|
|
690
|
-
if (options.adminIdentity &&
|
|
731
|
+
if (options.adminIdentity && commentSession) {
|
|
732
|
+
const requestedIdentity = parseIdentity(options.adminIdentity);
|
|
733
|
+
if (
|
|
734
|
+
requestedIdentity !== "auto" &&
|
|
735
|
+
requestedIdentity !== commentSession.identity?.key
|
|
736
|
+
) {
|
|
737
|
+
throw cliValidationError(
|
|
738
|
+
`--identity ${requestedIdentity} does not match comment session #${commentSessionId} (${commentSession.identity?.key || "unknown"}).`,
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
} else if (options.adminIdentity && (selectedRun || correctionSession)) {
|
|
691
742
|
const requestedIdentity = parseIdentity(options.adminIdentity);
|
|
692
743
|
const canonicalIdentity = correctionSession
|
|
693
744
|
? correctionSession.identity?.key
|
|
@@ -722,6 +773,11 @@ export async function adminCommand(options) {
|
|
|
722
773
|
String(correctionSessionId),
|
|
723
774
|
}
|
|
724
775
|
: {}),
|
|
776
|
+
...(commentSessionId
|
|
777
|
+
? {
|
|
778
|
+
"x-lumine-admin-comment-session-id": String(commentSessionId),
|
|
779
|
+
}
|
|
780
|
+
: {}),
|
|
725
781
|
...(requestId ? { "x-lumine-idempotency-key": requestId } : {}),
|
|
726
782
|
},
|
|
727
783
|
// Long operations (reward-bank seed) carry their own timeout.
|
|
@@ -1529,6 +1585,81 @@ export function assertAdminOperationAllowedForRunScope({
|
|
|
1529
1585
|
);
|
|
1530
1586
|
}
|
|
1531
1587
|
|
|
1588
|
+
// Commands a standalone comment session can serve: drafting, replying,
|
|
1589
|
+
// publishing and editing its own comments, plus the reads needed to compose
|
|
1590
|
+
// on its targets. The API still enforces the session's target lock on each.
|
|
1591
|
+
const COMMENT_SESSION_WRITE_OPERATIONS = new Set([
|
|
1592
|
+
"comment.draft",
|
|
1593
|
+
"comment.post",
|
|
1594
|
+
"comment.edit",
|
|
1595
|
+
]);
|
|
1596
|
+
const COMMENT_SESSION_OPERATIONS = new Set([
|
|
1597
|
+
...COMMENT_SESSION_WRITE_OPERATIONS,
|
|
1598
|
+
"subject.get",
|
|
1599
|
+
"subject.comments",
|
|
1600
|
+
"post.get",
|
|
1601
|
+
"post.comments",
|
|
1602
|
+
]);
|
|
1603
|
+
|
|
1604
|
+
export function adminOperationUsesCommentSession(operation) {
|
|
1605
|
+
return COMMENT_SESSION_OPERATIONS.has(operation?.name);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
export function parseAdminVia(value, operation) {
|
|
1609
|
+
const via = String(value || "").trim().toLowerCase();
|
|
1610
|
+
if (!via) return "";
|
|
1611
|
+
if (via !== "session" && via !== "run") {
|
|
1612
|
+
throw cliValidationError("--via must be session or run.");
|
|
1613
|
+
}
|
|
1614
|
+
if (!adminOperationUsesCommentSession(operation)) {
|
|
1615
|
+
throw cliValidationError(
|
|
1616
|
+
"--via applies only to comment draft/reply/post/edit and the subject/post/comment reads.",
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
return via;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
async function loadActiveCommentSession({ options, authToken }) {
|
|
1623
|
+
let status;
|
|
1624
|
+
try {
|
|
1625
|
+
status = await requestJson({
|
|
1626
|
+
url: `${options.apiUrl}/cli/admin/comment-sessions/status`,
|
|
1627
|
+
authToken,
|
|
1628
|
+
timeoutMs: options.timeoutMs,
|
|
1629
|
+
});
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
// An API without comment sessions yet: keep daily runs working.
|
|
1632
|
+
if (error?.status === 404) return null;
|
|
1633
|
+
throw error;
|
|
1634
|
+
}
|
|
1635
|
+
const session = status?.data?.commentSession || null;
|
|
1636
|
+
return session?.status === "active" && Number(session.id) > 0
|
|
1637
|
+
? session
|
|
1638
|
+
: null;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
export function parseCommentSessionTargets(values) {
|
|
1642
|
+
const raw = values
|
|
1643
|
+
.flatMap((value) => String(value || "").split(","))
|
|
1644
|
+
.map((value) => value.trim())
|
|
1645
|
+
.filter(Boolean);
|
|
1646
|
+
if (!raw.length) {
|
|
1647
|
+
throw cliValidationError(
|
|
1648
|
+
"Name the targets with --target <subject:ID|comment:ID|build:ID|aiStory:ID|dailyReflection:ID>[,...].",
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
const seen = new Set();
|
|
1652
|
+
const targets = [];
|
|
1653
|
+
for (const value of raw) {
|
|
1654
|
+
const target = parseAdminCommentTarget({ target: value });
|
|
1655
|
+
const key = `${target.type}:${target.id}`;
|
|
1656
|
+
if (seen.has(key)) continue;
|
|
1657
|
+
seen.add(key);
|
|
1658
|
+
targets.push(target);
|
|
1659
|
+
}
|
|
1660
|
+
return targets;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1532
1663
|
function adminOperationRequiresRun(operation) {
|
|
1533
1664
|
if (typeof operation.requiresRun === "boolean") {
|
|
1534
1665
|
return operation.requiresRun;
|
|
@@ -1547,6 +1678,9 @@ function adminOperationRequiresRun(operation) {
|
|
|
1547
1678
|
"correction.start",
|
|
1548
1679
|
"correction.status",
|
|
1549
1680
|
"correction.complete",
|
|
1681
|
+
"comment-session.start",
|
|
1682
|
+
"comment-session.status",
|
|
1683
|
+
"comment-session.close",
|
|
1550
1684
|
"escalation.list",
|
|
1551
1685
|
"escalation.set",
|
|
1552
1686
|
"notable.add",
|
|
@@ -1935,6 +2069,44 @@ export function parseAdminOperation(options) {
|
|
|
1935
2069
|
if (note.length > 1000) {
|
|
1936
2070
|
throw cliValidationError("--note must be at most 1000 characters.");
|
|
1937
2071
|
}
|
|
2072
|
+
if (action === "story" || action === "stories") {
|
|
2073
|
+
// A crew's Bridge Builder story page: Mikey's final approval (the
|
|
2074
|
+
// admin bar on the story page does the same). Publishing copies only
|
|
2075
|
+
// the photos/clips every tagged member's parent allowed.
|
|
2076
|
+
const storyUsage =
|
|
2077
|
+
"Usage: lumine admin meetup story list | show <crewId> | approve <crewId> [--note <text>] [--no-announce] | send-back <crewId> --note <text> | unpublish <crewId> --note <text>.";
|
|
2078
|
+
const storyAction = target || "list";
|
|
2079
|
+
if (storyAction === "list") {
|
|
2080
|
+
return readOperation("meetup.story.list", "/cli/admin/meetup-quest/stories", {
|
|
2081
|
+
requiresRun: false,
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
if (!["show", "approve", "send-back", "unpublish"].includes(storyAction)) {
|
|
2085
|
+
throw cliValidationError(storyUsage);
|
|
2086
|
+
}
|
|
2087
|
+
const crewId = parseRequiredInteger(extra, "Crew ID", 1);
|
|
2088
|
+
if (storyAction === "show") {
|
|
2089
|
+
return readOperation(
|
|
2090
|
+
"meetup.story.show",
|
|
2091
|
+
`/cli/admin/meetup-quest/stories/by-crew/${crewId}`,
|
|
2092
|
+
{ requiresRun: false },
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
if (storyAction !== "approve" && !note) {
|
|
2096
|
+
throw cliValidationError(
|
|
2097
|
+
`lumine admin meetup story ${storyAction} <crewId> needs --note <text> (the crew sees it).`,
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
return writeOperation(
|
|
2101
|
+
`meetup.story.${storyAction}`,
|
|
2102
|
+
"POST",
|
|
2103
|
+
`/cli/admin/meetup-quest/stories/by-crew/${crewId}/${storyAction}`,
|
|
2104
|
+
storyAction === "approve"
|
|
2105
|
+
? { note, announce: !options.adminNoAnnounce }
|
|
2106
|
+
: { note },
|
|
2107
|
+
{ requiresRun: false },
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
1938
2110
|
if (!action || action === "list") {
|
|
1939
2111
|
return readOperation(
|
|
1940
2112
|
"meetup.list",
|
|
@@ -3323,6 +3495,51 @@ export function parseAdminOperation(options) {
|
|
|
3323
3495
|
}
|
|
3324
3496
|
|
|
3325
3497
|
if (namespace === "comment") {
|
|
3498
|
+
if (action === "session") {
|
|
3499
|
+
const sessionAction = target || "status";
|
|
3500
|
+
if (sessionAction === "status") {
|
|
3501
|
+
return readOperation(
|
|
3502
|
+
"comment-session.status",
|
|
3503
|
+
"/cli/admin/comment-sessions/status",
|
|
3504
|
+
);
|
|
3505
|
+
}
|
|
3506
|
+
if (sessionAction === "start") {
|
|
3507
|
+
const identity = String(options.adminIdentity || "")
|
|
3508
|
+
.trim()
|
|
3509
|
+
.toLowerCase();
|
|
3510
|
+
if (identity !== "zero" && identity !== "ciel") {
|
|
3511
|
+
throw cliValidationError(
|
|
3512
|
+
"comment session start needs --identity zero|ciel.",
|
|
3513
|
+
);
|
|
3514
|
+
}
|
|
3515
|
+
const targets = parseCommentSessionTargets([
|
|
3516
|
+
options.adminTarget,
|
|
3517
|
+
...(options.positional || []).slice(3),
|
|
3518
|
+
]);
|
|
3519
|
+
const reason = String(options.adminReason || "").trim();
|
|
3520
|
+
return writeOperation(
|
|
3521
|
+
"comment-session.start",
|
|
3522
|
+
"POST",
|
|
3523
|
+
"/cli/admin/comment-sessions",
|
|
3524
|
+
{
|
|
3525
|
+
identity,
|
|
3526
|
+
targets,
|
|
3527
|
+
...(reason ? { reason } : {}),
|
|
3528
|
+
},
|
|
3529
|
+
);
|
|
3530
|
+
}
|
|
3531
|
+
if (sessionAction === "close") {
|
|
3532
|
+
return writeOperation(
|
|
3533
|
+
"comment-session.close",
|
|
3534
|
+
"POST",
|
|
3535
|
+
"/cli/admin/comment-sessions/close",
|
|
3536
|
+
{},
|
|
3537
|
+
);
|
|
3538
|
+
}
|
|
3539
|
+
throw cliValidationError(
|
|
3540
|
+
"Usage: lumine admin comment session start --identity zero|ciel --target <type:id>[,<type:id>...] [--reason <text>] | comment session status | comment session close.",
|
|
3541
|
+
);
|
|
3542
|
+
}
|
|
3326
3543
|
if (action === "draft" || action === "reply") {
|
|
3327
3544
|
const parsedTarget = parseAdminCommentTarget({
|
|
3328
3545
|
target,
|
|
@@ -4892,6 +5109,49 @@ function printMeetupCrew(crew) {
|
|
|
4892
5109
|
}
|
|
4893
5110
|
|
|
4894
5111
|
function printMeetupResult({ operation, data, result }) {
|
|
5112
|
+
if (operation.name === "meetup.story.list") {
|
|
5113
|
+
const stories = Array.isArray(data.stories) ? data.stories : [];
|
|
5114
|
+
console.log(`${stories.length} Bridge Builder stor${stories.length === 1 ? "y" : "ies"} waiting for approval:`);
|
|
5115
|
+
for (const story of stories) {
|
|
5116
|
+
console.log(
|
|
5117
|
+
` crew #${story.crewId} · "${story.title}" · ${story.crewName} (${(story.people || []).join(", ")}) · ${story.itemsPublic} public / ${story.itemsPrivate} private`,
|
|
5118
|
+
);
|
|
5119
|
+
}
|
|
5120
|
+
if (stories.length) console.log("Approve: lumine admin meetup story approve <crewId> (or send-back <crewId> --note).");
|
|
5121
|
+
return;
|
|
5122
|
+
}
|
|
5123
|
+
if (operation.name === "meetup.story.show") {
|
|
5124
|
+
const story = data.story || {};
|
|
5125
|
+
const items = [...(story.photos || []), ...(story.clips || [])];
|
|
5126
|
+
console.log(`Story #${story.storyId} (crew #${story.crewId}) · ${story.status} · "${story.title}"`);
|
|
5127
|
+
console.log(` People: ${(story.people || []).map((p) => `${p.username} (${p.branch})`).join(", ")}`);
|
|
5128
|
+
for (const consent of story.review?.consents || []) {
|
|
5129
|
+
const person = (story.people || []).find((p) => p.userId === consent.userId);
|
|
5130
|
+
console.log(` Parent of ${person?.username || consent.userId}: ${consent.state}`);
|
|
5131
|
+
}
|
|
5132
|
+
for (const item of items) {
|
|
5133
|
+
const gate = item.gate || {};
|
|
5134
|
+
console.log(
|
|
5135
|
+
` ${item.kind} #${item.id}: ${gate.publishable ? "PUBLIC" : `private (${gate.reason || "live"})`}${item.caption ? ` · ${item.caption}` : ""}${item.url ? ` · ${item.url}` : ""}`,
|
|
5136
|
+
);
|
|
5137
|
+
}
|
|
5138
|
+
if ((story.review?.nameWarnings || []).length) {
|
|
5139
|
+
console.log(` Possible real names: ${story.review.nameWarnings.join(", ")}`);
|
|
5140
|
+
}
|
|
5141
|
+
if (story.body) console.log(`\n${story.body}\n`);
|
|
5142
|
+
return;
|
|
5143
|
+
}
|
|
5144
|
+
if (operation.name.startsWith("meetup.story.")) {
|
|
5145
|
+
const r = data.result || {};
|
|
5146
|
+
if (operation.name === "meetup.story.approve") {
|
|
5147
|
+
console.log(
|
|
5148
|
+
`Published story #${r.storyId} (crew #${r.crewId}): ${(r.publicMediaIds || []).length} public, ${(r.privateMediaIds || []).length} private.${r.announcementSubjectId ? ` Announcement: subject #${r.announcementSubjectId}.` : ""}${(r.failed || []).length ? ` FAILED to process: ${r.failed.map((f) => `#${f.mediaId} ${f.error}`).join("; ")}.` : ""}`,
|
|
5149
|
+
);
|
|
5150
|
+
} else {
|
|
5151
|
+
console.log(`${operation.name === "meetup.story.unpublish" ? "Unpublished" : "Sent back"} story #${r.storyId} (crew #${r.crewId}).`);
|
|
5152
|
+
}
|
|
5153
|
+
return;
|
|
5154
|
+
}
|
|
4895
5155
|
if (operation.name === "meetup.list") {
|
|
4896
5156
|
const crews = Array.isArray(data.crews) ? data.crews : [];
|
|
4897
5157
|
console.log(`${crews.length} meetup crew(s) (${data.filter || "review"}):`);
|
package/lib/commands.js
CHANGED
|
@@ -2355,6 +2355,7 @@ export function parseArgs(args) {
|
|
|
2355
2355
|
const raw = {};
|
|
2356
2356
|
const positional = [];
|
|
2357
2357
|
const booleanFlags = new Set([
|
|
2358
|
+
"noAnnounce",
|
|
2358
2359
|
"noLeaseKeeper",
|
|
2359
2360
|
"noOpen",
|
|
2360
2361
|
"open",
|
|
@@ -2596,6 +2597,7 @@ export function parseArgs(args) {
|
|
|
2596
2597
|
adminUnviewed: Boolean(raw.unviewed),
|
|
2597
2598
|
adminViewed: Boolean(raw.viewed),
|
|
2598
2599
|
adminIdentity: raw.identity ? String(raw.identity) : "",
|
|
2600
|
+
adminVia: raw.via ? String(raw.via) : "",
|
|
2599
2601
|
adminScope: raw.scope ? String(raw.scope) : "",
|
|
2600
2602
|
commentMode: raw.commentMode ? String(raw.commentMode) : "",
|
|
2601
2603
|
runKey: raw.runKey ? String(raw.runKey) : "",
|
|
@@ -2606,6 +2608,7 @@ export function parseArgs(args) {
|
|
|
2606
2608
|
storageSize: raw.size ? String(raw.size) : "",
|
|
2607
2609
|
adminAttended: raw.attended ? String(raw.attended) : "",
|
|
2608
2610
|
adminConfirm: raw.confirm === undefined ? "" : String(raw.confirm),
|
|
2611
|
+
adminNoAnnounce: Boolean(raw.noAnnounce),
|
|
2609
2612
|
adminContextLimit: raw.limit === undefined ? 20 : Number(raw.limit),
|
|
2610
2613
|
adminRun: raw.run ? String(raw.run) : "",
|
|
2611
2614
|
adminTarget: raw.target ? String(raw.target) : "",
|
|
@@ -3012,6 +3015,7 @@ export function printHelp() {
|
|
|
3012
3015
|
lumine admin cardcraft-review list [--status pending|approved|all] [--cursor <id>] | show <id> | approve|reject|revoke <id> [--reason <text>]
|
|
3013
3016
|
lumine admin storage list [--status pending|approved|rejected|all] [--cursor <id>] | show <user-id-or-username> | approve <request-id> [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <request-id> [--reason <text>] | grant <user-id-or-username> --size <500MB|1GB|2GB|n MB> [--reason <text>]
|
|
3014
3017
|
lumine admin meetup list [--status review|active|completed|all] | show <crewId> | approve-plan <crewId> [--note <text>] | send-back <crewId> --note <text> | approve <crewId> --attended <userId,userId,...> [--note <text>] | slot <crewId> --confirm <slotIndex> | resend-email <crewId> (Bridge Builder meetup quest)
|
|
3018
|
+
lumine admin meetup story list | show <crewId> | approve <crewId> [--note <text>] [--no-announce] | send-back <crewId> --note <text> | unpublish <crewId> --note <text> (Bridge Builder story pages: publishes only what every tagged member's parent allowed)
|
|
3015
3019
|
lumine admin reward-activity [--date YYYY-MM-DD] [--days <1..31>] [--build <id>] [--json]
|
|
3016
3020
|
lumine admin chat-reports list [--status pending|open|reviewing|resolved|dismissed|all] [--cursor <id>] [--json]
|
|
3017
3021
|
lumine admin chat-reports show <report-id> [--json] (reported message snapshot + surrounding context)
|
|
@@ -3059,6 +3063,9 @@ export function printHelp() {
|
|
|
3059
3063
|
lumine admin comment draft <target> [--type subject|comment|build|aiStory|dailyReflection] [--file <comment.md>] [--review-receipt <review.json>|--reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--identity zero|ciel|auto] [--json]
|
|
3060
3064
|
lumine admin comment reply comment:<id> [--file <reply.md>] [--reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--identity zero|ciel|auto] [--json]
|
|
3061
3065
|
lumine admin comment post --draft-id <id> [--json]
|
|
3066
|
+
lumine admin comment session start --identity zero|ciel --target <subject:ID|comment:ID|build:ID|aiStory:ID>[,...] [--reason <text>] [--json]
|
|
3067
|
+
lumine admin comment session status|close [--json]
|
|
3068
|
+
(with both a comment session and a daily run active, comment draft/reply/post/edit need --via session|run)
|
|
3062
3069
|
lumine admin comment edit <comment-id> --file <comment.md> [--review-receipt <review.json> | --reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--json]
|
|
3063
3070
|
lumine admin brief [--days <1..30>] [--json]
|
|
3064
3071
|
lumine admin ai-costs monthly [--output <file.json>] [--json] (no active run required)
|
package/package.json
CHANGED
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -4171,6 +4171,74 @@ canonical review record ID before claiming success. Older APIs that still block
|
|
|
4171
4171
|
Build edits must be deployed first; do not silently substitute a duplicate reply
|
|
4172
4172
|
when Mikey requested an edit.
|
|
4173
4173
|
|
|
4174
|
+
### Standalone comment sessions
|
|
4175
|
+
|
|
4176
|
+
Posting a few new comments or replies as Zero or Ciel does not require a full
|
|
4177
|
+
daily run. An approved operator names the identity and the exact targets:
|
|
4178
|
+
|
|
4179
|
+
```bash
|
|
4180
|
+
lumine admin comment session start --identity ciel \
|
|
4181
|
+
--target subject:123,comment:456 --reason "Mikey asked for a reply" --json
|
|
4182
|
+
lumine admin subject get 123 --include-comments --json
|
|
4183
|
+
lumine admin comment draft subject:123 --file comment.md --json
|
|
4184
|
+
lumine admin comment reply comment:456 --file reply.md --json
|
|
4185
|
+
lumine admin comment post --draft-id <id> --json
|
|
4186
|
+
lumine admin comment edit <commentId> --file corrected.md --json
|
|
4187
|
+
lumine admin comment session status --json
|
|
4188
|
+
lumine admin comment session close --json
|
|
4189
|
+
```
|
|
4190
|
+
|
|
4191
|
+
`--target` takes a comma-separated list (extra space-separated targets after
|
|
4192
|
+
it also count); each is `subject:ID`, `comment:ID`, `build:ID`, `aiStory:ID` or
|
|
4193
|
+
`dailyReflection:ID`, at most ten. Pass them in one `--target`: a repeated
|
|
4194
|
+
`--target` flag keeps only its last value. The server checks every target when
|
|
4195
|
+
the session starts and refuses one the draft path would refuse later.
|
|
4196
|
+
|
|
4197
|
+
What the session authorizes:
|
|
4198
|
+
|
|
4199
|
+
- **Scopes:** only `content:read`, `comment:draft` and `comment:post`. No chat,
|
|
4200
|
+
Featured, reward, recommendation, subject, newspaper or run-completion
|
|
4201
|
+
command works through it; those still need a daily run.
|
|
4202
|
+
- **Target lock:** drafts, replies and the reads above work only on the named
|
|
4203
|
+
targets. A reply target counts when it is a named `comment:ID`, or a comment
|
|
4204
|
+
sitting directly in a named subject, Build, AI Story or Daily Reflection.
|
|
4205
|
+
Anything else fails with `CLI_ADMIN_COMMENT_SESSION_TARGET_FORBIDDEN` and
|
|
4206
|
+
the allowed list. `comment post` accepts only drafts created in the session.
|
|
4207
|
+
- **Edits:** `comment edit <commentId>` works under the session only for a
|
|
4208
|
+
comment this session itself posted, as the same identity
|
|
4209
|
+
(`CLI_ADMIN_COMMENT_SESSION_EDIT_FORBIDDEN` otherwise). The usual edit rules
|
|
4210
|
+
apply unchanged, including fresh Build review evidence for a Build comment.
|
|
4211
|
+
Edits do not count toward the post cap.
|
|
4212
|
+
- **Limits:** 30 minutes, at most five published comments
|
|
4213
|
+
(`CLI_ADMIN_COMMENT_SESSION_POST_CAP`), one active session per operator.
|
|
4214
|
+
`close` or the TTL ends it; start a new session for more.
|
|
4215
|
+
- **Same bar as a daily run:** drafting and publishing use the exact daily-run
|
|
4216
|
+
draft/publish path. Build comments still require composed text, a review
|
|
4217
|
+
receipt or `--reviewed-version/--reviewed-via` evidence of the current
|
|
4218
|
+
published version, and `--review-context`; context and persona revisions
|
|
4219
|
+
are checked at publish time as usual.
|
|
4220
|
+
- **Identity:** the operator must be an approved delegated administrator, and
|
|
4221
|
+
the session posts only as the identity named at start (`--identity auto` is
|
|
4222
|
+
not accepted). It does not change the Bangkok calendar assignment or the
|
|
4223
|
+
identity preference.
|
|
4224
|
+
|
|
4225
|
+
A comment session and a daily run can be active together, but every request
|
|
4226
|
+
carries exactly one authority. The session never claims the daily-run slot.
|
|
4227
|
+
|
|
4228
|
+
- When only one of them is active, the CLI uses it.
|
|
4229
|
+
- When both are active, `comment draft`, `comment reply`, `comment post` and
|
|
4230
|
+
`comment edit` refuse to run until you choose with `--via session` or
|
|
4231
|
+
`--via run`. Target reads (`subject get`, `post get`, `comments get`, their
|
|
4232
|
+
comment lists) default to the daily run and take `--via session` to read
|
|
4233
|
+
through the session instead.
|
|
4234
|
+
- The API refuses a request that names a comment session together with a
|
|
4235
|
+
daily run or correction session (`CLI_ADMIN_AMBIGUOUS_AUTHORITY`); a session
|
|
4236
|
+
request outside the session is refused, never handed to the run.
|
|
4237
|
+
- A draft records the authority that created it, and only that authority can
|
|
4238
|
+
publish it (`CLI_ADMIN_DRAFT_AUTHORITY_MISMATCH`).
|
|
4239
|
+
- A correction session still takes precedence for its one comment; `--via` is
|
|
4240
|
+
refused while it is active for that comment.
|
|
4241
|
+
|
|
4174
4242
|
**Editing the bot's own comments.** `comment edit <commentId> --file
|
|
4175
4243
|
<comment.md>` replaces the text of a comment the acting bot itself authored —
|
|
4176
4244
|
for correcting a factual error, an unfulfillable claim, or outdated guidance
|