@stage5/lumine 0.2.64 → 0.2.66
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 +22 -3
- package/lib/admin-workflows.js +232 -16
- package/lib/admin.js +243 -28
- package/lib/commands.js +6 -1
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +13 -9
- package/sdk/LUMINE_ADMIN.md +171 -42
package/lib/admin.js
CHANGED
|
@@ -171,6 +171,7 @@ export async function adminCommand(options) {
|
|
|
171
171
|
scope: operation.mutates ? "build:write" : "build:read",
|
|
172
172
|
});
|
|
173
173
|
let runId = 0;
|
|
174
|
+
let runScope = null;
|
|
174
175
|
let correctionSessionId = 0;
|
|
175
176
|
let correctionSession = null;
|
|
176
177
|
if (adminOperationRequiresRun(operation)) {
|
|
@@ -216,6 +217,10 @@ export async function adminCommand(options) {
|
|
|
216
217
|
}
|
|
217
218
|
throw noActiveRunError();
|
|
218
219
|
}
|
|
220
|
+
if (selectedRun) {
|
|
221
|
+
runScope = canonicalAdminRunScope(selectedRun);
|
|
222
|
+
assertAdminOperationAllowedForRunScope({ operation, runScope });
|
|
223
|
+
}
|
|
219
224
|
if (options.adminIdentity && (selectedRun || correctionSession)) {
|
|
220
225
|
const requestedIdentity = parseIdentity(options.adminIdentity);
|
|
221
226
|
const canonicalIdentity = correctionSession
|
|
@@ -289,18 +294,20 @@ export async function adminCommand(options) {
|
|
|
289
294
|
runId,
|
|
290
295
|
fetchPage: fetchOperation,
|
|
291
296
|
transformPage: transformResult,
|
|
292
|
-
recordCoverage:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
297
|
+
recordCoverage: shouldRecordAdminQueueCoverage(runScope)
|
|
298
|
+
? async (coverage) =>
|
|
299
|
+
requestJson({
|
|
300
|
+
method: "POST",
|
|
301
|
+
url: `${options.apiUrl}/cli/admin/daily-runs/coverage`,
|
|
302
|
+
authToken: auth.token,
|
|
303
|
+
body: coverage,
|
|
304
|
+
headers: {
|
|
305
|
+
"x-lumine-admin-run-id": String(runId),
|
|
306
|
+
"x-lumine-idempotency-key": `cli:queue-coverage:${runId}:${adminValueFingerprint(coverage).slice(0, 32)}`,
|
|
307
|
+
},
|
|
308
|
+
timeoutMs: options.timeoutMs,
|
|
309
|
+
})
|
|
310
|
+
: undefined,
|
|
304
311
|
});
|
|
305
312
|
} else {
|
|
306
313
|
if (options.adminResume) {
|
|
@@ -355,7 +362,7 @@ export async function adminCommand(options) {
|
|
|
355
362
|
assertAiEmailPolicySetResult({ operation, result });
|
|
356
363
|
}
|
|
357
364
|
if (operation.name === "daily-run.start") {
|
|
358
|
-
assertAdminTodoHandoffResult(result);
|
|
365
|
+
assertAdminTodoHandoffResult(result, operation.body.scope);
|
|
359
366
|
}
|
|
360
367
|
if (operation.name === "news.claim") {
|
|
361
368
|
const artifacts = writeNewsClaimArtifacts({
|
|
@@ -449,7 +456,11 @@ async function finishAdminOutput({ options, operation, result }) {
|
|
|
449
456
|
return result;
|
|
450
457
|
}
|
|
451
458
|
if (paginationStorage) {
|
|
452
|
-
await printSpooledAdminResult({
|
|
459
|
+
await printSpooledAdminResult({
|
|
460
|
+
operation,
|
|
461
|
+
result,
|
|
462
|
+
storage: paginationStorage,
|
|
463
|
+
});
|
|
453
464
|
return result;
|
|
454
465
|
}
|
|
455
466
|
printAdminResult({ operation, result });
|
|
@@ -693,6 +704,58 @@ export function resolveOperatorViewFilter({ operation, unviewed, viewed }) {
|
|
|
693
704
|
);
|
|
694
705
|
}
|
|
695
706
|
|
|
707
|
+
function requestedOperatorViewFilter(options) {
|
|
708
|
+
return options.adminUnviewed
|
|
709
|
+
? "unviewed"
|
|
710
|
+
: options.adminViewed
|
|
711
|
+
? "viewed"
|
|
712
|
+
: null;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export function canonicalAdminRunScope(run) {
|
|
716
|
+
const raw = run?.runScope;
|
|
717
|
+
if (raw === undefined || raw === null || raw === "") return "full";
|
|
718
|
+
const scope = String(raw);
|
|
719
|
+
if (scope === "full" || scope === "featured") return scope;
|
|
720
|
+
throw cliValidationError("The API returned an invalid administrator run scope.");
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
export function shouldRecordAdminQueueCoverage(runScope) {
|
|
724
|
+
return runScope === "full";
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const FEATURED_RUN_OPERATIONS = new Set([
|
|
728
|
+
"subjects.candidates",
|
|
729
|
+
"subject.get",
|
|
730
|
+
"subject.comments",
|
|
731
|
+
"subject.reveal",
|
|
732
|
+
"subject.feature",
|
|
733
|
+
"subject.unfeature",
|
|
734
|
+
"featured.list",
|
|
735
|
+
"featured.history",
|
|
736
|
+
"featured.add",
|
|
737
|
+
"featured.reorder",
|
|
738
|
+
"featured.rotate",
|
|
739
|
+
"daily-run.complete",
|
|
740
|
+
"daily-run.fail",
|
|
741
|
+
]);
|
|
742
|
+
|
|
743
|
+
export function assertAdminOperationAllowedForRunScope({
|
|
744
|
+
operation,
|
|
745
|
+
runScope,
|
|
746
|
+
}) {
|
|
747
|
+
if (runScope !== "featured") return;
|
|
748
|
+
const subjectCommentAlias =
|
|
749
|
+
operation.name === "post.comments" &&
|
|
750
|
+
/^\/cli\/admin\/subjects\/\d+\/comments(?:\?|$)/.test(operation.path);
|
|
751
|
+
if (FEATURED_RUN_OPERATIONS.has(operation.name) || subjectCommentAlias) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
throw cliValidationError(
|
|
755
|
+
`A Featured-only run does not authorize ${operation.name}. Complete or fail it before starting a full daily review.`,
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
696
759
|
function adminOperationRequiresRun(operation) {
|
|
697
760
|
return (
|
|
698
761
|
![
|
|
@@ -1095,16 +1158,26 @@ export function parseAdminOperation(options) {
|
|
|
1095
1158
|
|
|
1096
1159
|
if (namespace === "daily-run") {
|
|
1097
1160
|
if (action === "start") {
|
|
1161
|
+
const runScope = parseDailyRunScope(options.adminScope || "full");
|
|
1162
|
+
const commentMode = parseCommentMode(options.commentMode || "off");
|
|
1163
|
+
if (runScope === "featured" && commentMode !== "off") {
|
|
1164
|
+
throw cliValidationError(
|
|
1165
|
+
"A Featured-only run requires --comment-mode off.",
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1098
1168
|
return writeOperation(
|
|
1099
1169
|
"daily-run.start",
|
|
1100
1170
|
"POST",
|
|
1101
|
-
"
|
|
1171
|
+
runScope === "featured"
|
|
1172
|
+
? "/cli/admin/daily-runs/start/featured"
|
|
1173
|
+
: "/cli/admin/daily-runs/start",
|
|
1102
1174
|
{
|
|
1103
1175
|
identity: options.adminIdentity
|
|
1104
1176
|
? parseIdentity(options.adminIdentity)
|
|
1105
1177
|
: undefined,
|
|
1106
|
-
commentMode
|
|
1107
|
-
|
|
1178
|
+
commentMode,
|
|
1179
|
+
scope: runScope,
|
|
1180
|
+
runKey: options.runKey || defaultDailyRunKey(runScope),
|
|
1108
1181
|
},
|
|
1109
1182
|
);
|
|
1110
1183
|
}
|
|
@@ -1286,13 +1359,21 @@ export function parseAdminOperation(options) {
|
|
|
1286
1359
|
if (action === "get") return subjectGetOperation(target, options);
|
|
1287
1360
|
if (action === "comments") {
|
|
1288
1361
|
const subjectId = parseSubjectId(target);
|
|
1362
|
+
const operatorView = requestedOperatorViewFilter(options);
|
|
1289
1363
|
return readOperation(
|
|
1290
1364
|
"subject.comments",
|
|
1291
1365
|
withQuery(`/cli/admin/subjects/${subjectId}/comments`, {
|
|
1292
1366
|
cursor: options.adminCursor,
|
|
1293
1367
|
limit: options.limit,
|
|
1294
1368
|
}),
|
|
1295
|
-
{
|
|
1369
|
+
{
|
|
1370
|
+
pagination: {
|
|
1371
|
+
collectionKey: "comments",
|
|
1372
|
+
filters: {
|
|
1373
|
+
...(operatorView ? { operatorView } : {}),
|
|
1374
|
+
},
|
|
1375
|
+
},
|
|
1376
|
+
},
|
|
1296
1377
|
);
|
|
1297
1378
|
}
|
|
1298
1379
|
if (action === "reveal") {
|
|
@@ -1330,6 +1411,29 @@ export function parseAdminOperation(options) {
|
|
|
1330
1411
|
if (action === "list") {
|
|
1331
1412
|
return readOperation("featured.list", "/cli/admin/subjects/featured");
|
|
1332
1413
|
}
|
|
1414
|
+
if (action === "history") {
|
|
1415
|
+
const subjectIds = parseFeaturedSubjectIds(
|
|
1416
|
+
options.adminIds,
|
|
1417
|
+
"--subject-ids",
|
|
1418
|
+
);
|
|
1419
|
+
return readOperation(
|
|
1420
|
+
"featured.history",
|
|
1421
|
+
withQuery("/cli/admin/subjects/featured/history", {
|
|
1422
|
+
subjectIds: subjectIds.join(","),
|
|
1423
|
+
cursor: options.adminCursor,
|
|
1424
|
+
limit: options.limit,
|
|
1425
|
+
}),
|
|
1426
|
+
{
|
|
1427
|
+
pagination: {
|
|
1428
|
+
collectionKey: "events",
|
|
1429
|
+
filters: { subjectIds },
|
|
1430
|
+
},
|
|
1431
|
+
},
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
if (action === "add") {
|
|
1435
|
+
return featuredAddOperation(options);
|
|
1436
|
+
}
|
|
1333
1437
|
if (action === "reorder") {
|
|
1334
1438
|
return featuredReorderOperation(options);
|
|
1335
1439
|
}
|
|
@@ -1360,13 +1464,21 @@ export function parseAdminOperation(options) {
|
|
|
1360
1464
|
parsedTarget.type === "subject"
|
|
1361
1465
|
? `/cli/admin/subjects/${parsedTarget.id}/comments`
|
|
1362
1466
|
: `/cli/admin/posts/${parsedTarget.type}/${parsedTarget.id}/comments`;
|
|
1467
|
+
const operatorView = requestedOperatorViewFilter(options);
|
|
1363
1468
|
return readOperation(
|
|
1364
1469
|
"post.comments",
|
|
1365
1470
|
withQuery(path, {
|
|
1366
1471
|
cursor: options.adminCursor,
|
|
1367
1472
|
limit: options.limit,
|
|
1368
1473
|
}),
|
|
1369
|
-
{
|
|
1474
|
+
{
|
|
1475
|
+
pagination: {
|
|
1476
|
+
collectionKey: "comments",
|
|
1477
|
+
filters: {
|
|
1478
|
+
...(operatorView ? { operatorView } : {}),
|
|
1479
|
+
},
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1370
1482
|
);
|
|
1371
1483
|
}
|
|
1372
1484
|
if (action === "recommend") {
|
|
@@ -1784,6 +1896,7 @@ export function parseAdminOperation(options) {
|
|
|
1784
1896
|
}
|
|
1785
1897
|
|
|
1786
1898
|
function subjectsListOperation(options) {
|
|
1899
|
+
const operatorView = requestedOperatorViewFilter(options);
|
|
1787
1900
|
return readOperation(
|
|
1788
1901
|
"subjects.candidates",
|
|
1789
1902
|
withQuery("/cli/admin/subjects", {
|
|
@@ -1800,7 +1913,10 @@ function subjectsListOperation(options) {
|
|
|
1800
1913
|
after: options.adminAfter
|
|
1801
1914
|
? parseAfterForCoverage(options.adminAfter)
|
|
1802
1915
|
: null,
|
|
1803
|
-
filters: {
|
|
1916
|
+
filters: {
|
|
1917
|
+
effort: options.adminEffort || "all",
|
|
1918
|
+
...(operatorView ? { operatorView } : {}),
|
|
1919
|
+
},
|
|
1804
1920
|
},
|
|
1805
1921
|
},
|
|
1806
1922
|
);
|
|
@@ -1890,6 +2006,25 @@ function featuredReorderOperation(options) {
|
|
|
1890
2006
|
);
|
|
1891
2007
|
}
|
|
1892
2008
|
|
|
2009
|
+
function featuredAddOperation(options) {
|
|
2010
|
+
const addIds = parseFeaturedSubjectIds(
|
|
2011
|
+
options.adminIds,
|
|
2012
|
+
"--subject-ids",
|
|
2013
|
+
);
|
|
2014
|
+
const postedAfter = String(options.adminPostedAfter || "").trim();
|
|
2015
|
+
if (!postedAfter) {
|
|
2016
|
+
throw cliValidationError(
|
|
2017
|
+
"New Featured additions require --posted-after <ISO-8601-or-Unix-time>.",
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
return writeOperation(
|
|
2021
|
+
"featured.add",
|
|
2022
|
+
"POST",
|
|
2023
|
+
"/cli/admin/subjects/featured/additions",
|
|
2024
|
+
{ addIds, postedAfter },
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
|
|
1893
2028
|
function featuredRotateOperation(options) {
|
|
1894
2029
|
const removeIds = parseFeaturedSubjectIds(
|
|
1895
2030
|
options.adminRemoveIds,
|
|
@@ -2151,12 +2286,40 @@ export function formatAdminJsonError(error) {
|
|
|
2151
2286
|
};
|
|
2152
2287
|
}
|
|
2153
2288
|
|
|
2154
|
-
export function assertAdminTodoHandoffResult(result) {
|
|
2289
|
+
export function assertAdminTodoHandoffResult(result, expectedRunScope = null) {
|
|
2155
2290
|
const runId = Number(result?.data?.run?.id || 0);
|
|
2291
|
+
const runScope = canonicalAdminRunScope(result?.data?.run);
|
|
2292
|
+
if (expectedRunScope && runScope !== expectedRunScope) {
|
|
2293
|
+
const error = cliValidationError(
|
|
2294
|
+
`The API returned a ${runScope} run when the CLI requested ${expectedRunScope}.`,
|
|
2295
|
+
);
|
|
2296
|
+
error.code = "LUMINE_ADMIN_RUN_SCOPE_UNSUPPORTED";
|
|
2297
|
+
throw error;
|
|
2298
|
+
}
|
|
2156
2299
|
const handoff = result?.data?.carryoverTodos;
|
|
2300
|
+
if (runScope === "featured") {
|
|
2301
|
+
if (
|
|
2302
|
+
!runId ||
|
|
2303
|
+
!handoff ||
|
|
2304
|
+
handoff.included !== false ||
|
|
2305
|
+
!Array.isArray(handoff.items) ||
|
|
2306
|
+
handoff.items.length !== 0 ||
|
|
2307
|
+
Number(handoff.count) !== 0 ||
|
|
2308
|
+
handoff.surfacedForRunId !== null ||
|
|
2309
|
+
Number(handoff.newlySurfacedCount) !== 0
|
|
2310
|
+
) {
|
|
2311
|
+
const error = cliValidationError(
|
|
2312
|
+
"The API did not confirm that carry-over telemetry was suppressed for the Featured-only run.",
|
|
2313
|
+
);
|
|
2314
|
+
error.code = "LUMINE_ADMIN_SCOPED_RUN_UNSUPPORTED";
|
|
2315
|
+
throw error;
|
|
2316
|
+
}
|
|
2317
|
+
return handoff;
|
|
2318
|
+
}
|
|
2157
2319
|
if (
|
|
2158
2320
|
!runId ||
|
|
2159
2321
|
!handoff ||
|
|
2322
|
+
handoff.included === false ||
|
|
2160
2323
|
!Array.isArray(handoff.items) ||
|
|
2161
2324
|
Number(handoff.count) !== handoff.items.length ||
|
|
2162
2325
|
Number(handoff.surfacedForRunId) !== runId ||
|
|
@@ -2223,6 +2386,14 @@ function parseCommentMode(value) {
|
|
|
2223
2386
|
return mode;
|
|
2224
2387
|
}
|
|
2225
2388
|
|
|
2389
|
+
function parseDailyRunScope(value) {
|
|
2390
|
+
const scope = String(value || "full").trim().toLowerCase();
|
|
2391
|
+
if (!["full", "featured"].includes(scope)) {
|
|
2392
|
+
throw cliValidationError("--scope must be full or featured.");
|
|
2393
|
+
}
|
|
2394
|
+
return scope;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2226
2397
|
function parseEscalationStatus(value) {
|
|
2227
2398
|
const status = String(value || "")
|
|
2228
2399
|
.trim()
|
|
@@ -2436,7 +2607,7 @@ function parseChoice(value, label, choices) {
|
|
|
2436
2607
|
return normalized;
|
|
2437
2608
|
}
|
|
2438
2609
|
|
|
2439
|
-
function defaultDailyRunKey() {
|
|
2610
|
+
function defaultDailyRunKey(runScope = "full") {
|
|
2440
2611
|
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
2441
2612
|
timeZone: "Asia/Bangkok",
|
|
2442
2613
|
year: "numeric",
|
|
@@ -2446,7 +2617,10 @@ function defaultDailyRunKey() {
|
|
|
2446
2617
|
const value = Object.fromEntries(
|
|
2447
2618
|
parts.map((part) => [part.type, part.value]),
|
|
2448
2619
|
);
|
|
2449
|
-
|
|
2620
|
+
const day = `${value.year}-${value.month}-${value.day}`;
|
|
2621
|
+
return runScope === "full"
|
|
2622
|
+
? `daily:${day}`
|
|
2623
|
+
: `scoped:${runScope}:${day}:${randomUUID()}`;
|
|
2450
2624
|
}
|
|
2451
2625
|
|
|
2452
2626
|
function cliValidationError(message) {
|
|
@@ -2650,10 +2824,16 @@ function printAdminMonthlyMediaCosts(monthlyMediaCosts) {
|
|
|
2650
2824
|
);
|
|
2651
2825
|
}
|
|
2652
2826
|
|
|
2653
|
-
async function printSpooledAdminResult({ result, storage }) {
|
|
2827
|
+
async function printSpooledAdminResult({ operation, result, storage }) {
|
|
2654
2828
|
const data = result?.data || {};
|
|
2655
2829
|
const count = Number(storage.candidateCount || 0);
|
|
2656
|
-
if (
|
|
2830
|
+
if (operation.name === "featured.history") {
|
|
2831
|
+
printFeaturedHistorySummary(data);
|
|
2832
|
+
console.log(`${count} Featured history event(s):`);
|
|
2833
|
+
await forEachPaginatedResultItem(result, async (event) => {
|
|
2834
|
+
printFeaturedHistoryEvent(event);
|
|
2835
|
+
});
|
|
2836
|
+
} else if (storage.collectionKey === "subjects") {
|
|
2657
2837
|
console.log(`${count} subject(s):`);
|
|
2658
2838
|
await forEachPaginatedResultItem(result, async (subject) => {
|
|
2659
2839
|
console.log(
|
|
@@ -2930,13 +3110,13 @@ function printAdminResult({ operation, result }) {
|
|
|
2930
3110
|
console.log("No active delegated administrator daily run.");
|
|
2931
3111
|
if (data.lastRun) {
|
|
2932
3112
|
console.log(
|
|
2933
|
-
`Last run #${data.lastRun.id}: ${data.lastRun.status}; identity ${data.lastRun.identity.key}; comments ${data.lastRun.commentMode}.`,
|
|
3113
|
+
`Last run #${data.lastRun.id}: ${data.lastRun.status}; scope ${canonicalAdminRunScope(data.lastRun)}; identity ${data.lastRun.identity.key}; comments ${data.lastRun.commentMode}.`,
|
|
2934
3114
|
);
|
|
2935
3115
|
}
|
|
2936
3116
|
return;
|
|
2937
3117
|
}
|
|
2938
3118
|
console.log(
|
|
2939
|
-
`Run #${data.run.id}: ${data.run.status}; identity ${data.run.identity.key}; comments ${data.run.commentMode}.`,
|
|
3119
|
+
`Run #${data.run.id}: ${data.run.status}; scope ${canonicalAdminRunScope(data.run)}; identity ${data.run.identity.key}; comments ${data.run.commentMode}.`,
|
|
2940
3120
|
);
|
|
2941
3121
|
if (data.scheduledDay && data.scheduledIdentity) {
|
|
2942
3122
|
console.log(
|
|
@@ -2967,11 +3147,20 @@ function printAdminResult({ operation, result }) {
|
|
|
2967
3147
|
);
|
|
2968
3148
|
console.log(
|
|
2969
3149
|
data.activeRun
|
|
2970
|
-
? `Active run #${data.activeRun.id}: ${data.activeRun.identity.key}; comments ${data.activeRun.commentMode}.`
|
|
3150
|
+
? `Active run #${data.activeRun.id}: scope ${canonicalAdminRunScope(data.activeRun)}; identity ${data.activeRun.identity.key}; comments ${data.activeRun.commentMode}.`
|
|
2971
3151
|
: "No active delegated administrator daily run.",
|
|
2972
3152
|
);
|
|
2973
3153
|
return;
|
|
2974
3154
|
}
|
|
3155
|
+
if (operation.name === "featured.history") {
|
|
3156
|
+
printFeaturedHistorySummary(data);
|
|
3157
|
+
console.log(`${(data.events || []).length} Featured history event(s):`);
|
|
3158
|
+
for (const event of data.events || []) {
|
|
3159
|
+
printFeaturedHistoryEvent(event);
|
|
3160
|
+
}
|
|
3161
|
+
printPagination(data.pagination);
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
2975
3164
|
if (Array.isArray(data.subjects)) {
|
|
2976
3165
|
console.log(`${data.subjects.length} subject(s):`);
|
|
2977
3166
|
for (const subject of data.subjects) {
|
|
@@ -3138,6 +3327,32 @@ function printAdminResult({ operation, result }) {
|
|
|
3138
3327
|
);
|
|
3139
3328
|
}
|
|
3140
3329
|
|
|
3330
|
+
function printFeaturedHistorySummary(data) {
|
|
3331
|
+
const coverage = data.coverage || {};
|
|
3332
|
+
console.log(
|
|
3333
|
+
coverage.complete
|
|
3334
|
+
? `Featured history coverage begins at ${coverage.startedAt}.`
|
|
3335
|
+
: "Featured history coverage is not complete.",
|
|
3336
|
+
);
|
|
3337
|
+
for (const subject of data.subjects || []) {
|
|
3338
|
+
const lifetime = subject.knownFeatured
|
|
3339
|
+
? "previously Featured"
|
|
3340
|
+
: subject.neverFeatured === true
|
|
3341
|
+
? "never Featured"
|
|
3342
|
+
: "history unknown";
|
|
3343
|
+
console.log(
|
|
3344
|
+
`#${subject.id} ${subject.title || "(untitled)"} — ${lifetime}${subject.featured?.member ? ` — current position ${subject.featured.order}` : ""}`,
|
|
3345
|
+
);
|
|
3346
|
+
console.log(` ${subject.url}`);
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
function printFeaturedHistoryEvent(event) {
|
|
3351
|
+
console.log(
|
|
3352
|
+
`#${event.id} subject:${event.subjectId} ${event.action} ${event.fromPosition ?? "-"}->${event.toPosition ?? "-"} — ${event.operation}`,
|
|
3353
|
+
);
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3141
3356
|
function printTodoItems(items, heading) {
|
|
3142
3357
|
console.log(`${heading}: ${items.length} item(s).`);
|
|
3143
3358
|
for (const todo of items) {
|
package/lib/commands.js
CHANGED
|
@@ -2364,6 +2364,7 @@ export function parseArgs(args) {
|
|
|
2364
2364
|
cursor: Math.max(0, Math.floor(Number(raw.cursor) || 0)),
|
|
2365
2365
|
adminCursor: raw.cursor ? String(raw.cursor) : "",
|
|
2366
2366
|
adminAfter: raw.after ? String(raw.after) : "",
|
|
2367
|
+
adminPostedAfter: raw.postedAfter ? String(raw.postedAfter) : "",
|
|
2367
2368
|
adminSinceRun: Boolean(raw.sinceRun),
|
|
2368
2369
|
adminIncludeLegacy: Boolean(raw.includeLegacy),
|
|
2369
2370
|
adminIncludePrivateEvidence: parseBoolean(
|
|
@@ -2457,6 +2458,7 @@ export function parseArgs(args) {
|
|
|
2457
2458
|
adminUnviewed: Boolean(raw.unviewed),
|
|
2458
2459
|
adminViewed: Boolean(raw.viewed),
|
|
2459
2460
|
adminIdentity: raw.identity ? String(raw.identity) : "",
|
|
2461
|
+
adminScope: raw.scope ? String(raw.scope) : "",
|
|
2460
2462
|
commentMode: raw.commentMode ? String(raw.commentMode) : "",
|
|
2461
2463
|
runKey: raw.runKey ? String(raw.runKey) : "",
|
|
2462
2464
|
idempotencyKey: raw.idempotencyKey ? String(raw.idempotencyKey) : "",
|
|
@@ -2818,7 +2820,7 @@ export function printHelp() {
|
|
|
2818
2820
|
lumine admin ai-bucket note set --bucket-id <id> --note <text> [--json]
|
|
2819
2821
|
lumine admin ai-email-policy get --email <address> [--json]
|
|
2820
2822
|
lumine admin ai-email-policy set --email <address> --mode <automatic|separate_accounts> --note <text> [--json]
|
|
2821
|
-
lumine admin daily-run start [--identity zero|ciel|auto] [--comment-mode off|draft|post] [--run-key <key>] [--json]
|
|
2823
|
+
lumine admin daily-run start [--scope full|featured] [--identity zero|ciel|auto] [--comment-mode off|draft|post] [--run-key <key>] [--json]
|
|
2822
2824
|
lumine admin daily-run status|report|complete|fail [--reason <text>] [--json]
|
|
2823
2825
|
lumine admin daily-run escalation add --target <target> --note <summary> [--severity attention|urgent] [--json]
|
|
2824
2826
|
lumine admin escalation list [--status open|acknowledged|resolved|all] [--limit <number>] [--json]
|
|
@@ -2843,6 +2845,8 @@ export function printHelp() {
|
|
|
2843
2845
|
lumine admin subject creator set-made-by-poster <subject-id> [--json]
|
|
2844
2846
|
lumine admin subject feature|unfeature <subject-id> [--json]
|
|
2845
2847
|
lumine admin featured list [--unviewed|--viewed] [--json]
|
|
2848
|
+
lumine admin featured history --subject-ids <id,id,...> [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--json]
|
|
2849
|
+
lumine admin featured add --subject-ids <id,id,...> --posted-after <ISO-8601-or-Unix-time> [--json]
|
|
2846
2850
|
lumine admin featured reorder --subject-ids <id,id,...> [--json]
|
|
2847
2851
|
lumine admin featured rotate --remove-subject-ids <id,id,...> --add-subject-ids <id,id,...> [--json]
|
|
2848
2852
|
lumine admin post get <target> [--type subject|comment|aiStory|dailyReflection] [--json]
|
|
@@ -2913,6 +2917,7 @@ Examples:
|
|
|
2913
2917
|
npx @stage5/lumine@latest thumbnail generate --model gpt-image-2 --yes
|
|
2914
2918
|
npx @stage5/lumine@latest doctor runtime-assets --build 917 --json
|
|
2915
2919
|
npx @stage5/lumine@latest admin daily-run start --identity auto --comment-mode off --json
|
|
2920
|
+
npx @stage5/lumine@latest admin daily-run start --scope featured --identity auto --json
|
|
2916
2921
|
npx @stage5/lumine@latest admin recommendations list --json
|
|
2917
2922
|
npx @stage5/lumine@latest admin subjects candidates --effort unassigned --json
|
|
2918
2923
|
npx @stage5/lumine@latest admin subject get 123 --include-comments --json
|
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Build SDK Index
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
4
|
-
Updated: 2026-
|
|
5
|
-
Generated: 2026-
|
|
3
|
+
Version: 1.39.0
|
|
4
|
+
Updated: 2026-09-03
|
|
5
|
+
Generated: 2026-09-03T04:17:20.106Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -256,8 +256,8 @@ files:read, media:read, media:write, live:read, live:write, user:read, users:rea
|
|
|
256
256
|
- Unsubscribe the current viewer from Build notifications for a subject's new pages or updates.
|
|
257
257
|
|
|
258
258
|
### Twinkle.chess
|
|
259
|
-
- async bestMove({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs? }) | scopes: none
|
|
260
|
-
- Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, error, engine }
|
|
259
|
+
- async bestMove({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs?, multiPv? }) | scopes: none
|
|
260
|
+
- Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, lines: [{ rank, move, from, to, promotion, evaluation, mate, depth, pv }], error, engine }
|
|
261
261
|
- Ask the parent-hosted Stockfish engine for the best move from a FEN position.
|
|
262
262
|
- Always available in the build iframe.
|
|
263
263
|
- Stockfish runs in a parent-managed worker with bounded depth, timeout, and serialized requests.
|
|
@@ -265,14 +265,18 @@ files:read, media:read, media:write, live:read, live:write, user:read, users:rea
|
|
|
265
265
|
- skillLevel 20 defaults to the strongest bounded search budget.
|
|
266
266
|
- maxTimeMs and timeoutMs are clamped between 500 and 60000 milliseconds.
|
|
267
267
|
- This returns engine analysis only. Use app code or a chess rules library to validate legal moves, manage board state, detect game over, and render the board.
|
|
268
|
+
- multiPv (1-10, default 1) returns the top N moves as lines, ranked best first, all from the same search (a movetime stop can leave a line one iteration behind; keep the engine's rank order rather than re-sorting by score). Compare line evaluations against each other to grade a candidate move; do not compare evaluations from separate searches of different positions.
|
|
269
|
+
- evaluation and line evaluations are centipawns from the side to move's point of view; mate is a signed mate distance (positive = side to move mates).
|
|
268
270
|
- Example: const result = await Twinkle.chess.bestMove({ fen: game.fen(), skillLevel: 8, maxTimeMs: 1000 });
|
|
269
271
|
if (result.success) game.move({ from: result.from, to: result.to, promotion: result.promotion || undefined });
|
|
270
|
-
- async evaluate({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs? }) | scopes: none
|
|
271
|
-
- Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, error, engine }
|
|
272
|
+
- async evaluate({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs?, multiPv? }) | scopes: none
|
|
273
|
+
- Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, lines: [{ rank, move, from, to, promotion, evaluation, mate, depth, pv }], error, engine }
|
|
272
274
|
- Analyze a FEN position and return Stockfish's current best move plus centipawn or mate evaluation.
|
|
273
275
|
- Always available in the build iframe.
|
|
274
276
|
- evaluation is the Stockfish centipawn score from the engine output when available; mate is the mate distance when Stockfish reports one.
|
|
275
277
|
- Do not call this from a render loop, animation loop, or high-frequency polling path.
|
|
278
|
+
- multiPv (1-10, default 1) returns the top N moves as lines, ranked best first, all from the same search (a movetime stop can leave a line one iteration behind; keep the engine's rank order rather than re-sorting by score). Compare line evaluations against each other to grade a candidate move; do not compare evaluations from separate searches of different positions.
|
|
279
|
+
- evaluation and line evaluations are centipawns from the side to move's point of view; mate is a signed mate distance (positive = side to move mates).
|
|
276
280
|
- Example: const analysis = await Twinkle.chess.evaluate({ fen: game.fen(), depth: 12 });
|
|
277
281
|
console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
|
|
278
282
|
|
|
@@ -463,11 +467,11 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
|
|
|
463
467
|
- Use this instead of asking Twinkle.ai.chat to return JSON.
|
|
464
468
|
- expectedStructure must be a JSON object that describes the exact returned object shape.
|
|
465
469
|
- mode is accepted as an alias for thinkingMode, and mid is accepted as an alias for medium.
|
|
466
|
-
- Omit model to use the normal Lite/Medium/High routing. model accepts gpt-5.6-sol, claude-opus-5, or claude-fable-5, and every explicit model must be paired with thinkingMode: 'high'; unknown model IDs reject instead of silently falling back.
|
|
470
|
+
- Omit model to use the normal Lite/Medium/High routing. model accepts gpt-5.6-sol, claude-opus-5, or claude-fable-5-1, and every explicit model must be paired with thinkingMode: 'high'; unknown model IDs reject instead of silently falling back.
|
|
467
471
|
- 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.
|
|
468
472
|
- thinkingMode medium uses Grok 4.6 with medium reasoning and consumes normal AI Energy.
|
|
469
473
|
- thinkingMode high without model uses GPT-5.6 Sol with high reasoning and consumes high AI Energy. Explicit model: 'gpt-5.6-sol' selects Sol with xhigh reasoning at the same High AI Energy tier.
|
|
470
|
-
- claude-opus-5 uses Anthropic adaptive High thinking. claude-fable-5 uses Anthropic xhigh thinking and normally consumes more AI Energy for comparable token use. Both debit confirmed provider usage at the High tier.
|
|
474
|
+
- claude-opus-5 uses Anthropic adaptive High thinking. claude-fable-5-1 uses Anthropic xhigh thinking and normally consumes more AI Energy for comparable token use. Both debit confirmed provider usage at the High tier.
|
|
471
475
|
- Pass onStatus, onReasoning, and/or onText to stream progress from the same structured generation. onStatus receives high-level phases such as thinking, searching_web, responding, validating, and completed.
|
|
472
476
|
- onReasoning receives accumulated provider-supplied, app-visible reasoning summaries plus { done, delta, requestId, status }. A provider retry may replace the accumulated summary; treat each callback's first argument as the current source of truth. This callback never exposes hidden/private model chain-of-thought.
|
|
473
477
|
- onText receives accumulated structured-output text plus { done, delta, requestId, status }. Partial output is intentionally incomplete and may include provider formatting; parse only when done is true, when the callback receives the canonical object serialized as JSON, and use the resolved object as the source of truth.
|