@stage5/lumine 0.2.67 → 0.2.68

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
@@ -8,6 +8,7 @@ import {
8
8
  readAdminJsonFile,
9
9
  validateNewsEditorial,
10
10
  writeNewsClaimArtifacts,
11
+ writeAdminJsonFile,
11
12
  } from "./admin-news.js";
12
13
  import {
13
14
  forEachPaginatedResultItem,
@@ -21,6 +22,7 @@ import {
21
22
  runManagedBuildReview,
22
23
  } from "./build-review.js";
23
24
  import { runAdminRuntimeLogWorkflow } from "./admin-runtime-logs.js";
25
+ import { readApprovedFeaturedPlan, runFeaturedWorkflow } from "./admin-featured.js";
24
26
 
25
27
  const MAX_EDITORIAL_FILE_BYTES = 256 * 1024;
26
28
  const MAX_COMPOSED_TEXT_FILE_BYTES = 64 * 1024;
@@ -270,7 +272,9 @@ export async function adminCommand(options) {
270
272
  recommendationContentTypes,
271
273
  viewFilter,
272
274
  });
273
- if (operation.name === "build.review") {
275
+ if (operation.featuredWorkflow) {
276
+ result = await runFeaturedWorkflow({ options, operation, authToken: auth.token, runId });
277
+ } else if (operation.name === "build.review") {
274
278
  result = await runManagedBuildReview({
275
279
  options,
276
280
  authToken: auth.token,
@@ -325,9 +329,26 @@ export async function adminCommand(options) {
325
329
  result = transformResult(await fetchOperation());
326
330
  }
327
331
  } catch (error) {
332
+ if (operation.featuredWorkflow && error.featuredProgress) {
333
+ const serverError = error.data?.error;
334
+ error.data = {
335
+ ok: false,
336
+ status: "partial_failure",
337
+ error: {
338
+ code: serverError?.code || error.code || "LUMINE_ADMIN_FEATURED_WORKFLOW_FAILED",
339
+ message: error.message,
340
+ details: {
341
+ ...(serverError && typeof serverError === "object" ? serverError.details : {}),
342
+ ...error.featuredProgress,
343
+ retryInstruction: "Resume the exact command with --resume; confirmed items are not replayed.",
344
+ },
345
+ },
346
+ };
347
+ }
328
348
  if (
329
349
  operation.mutates &&
330
350
  requestId &&
351
+ !operation.featuredWorkflow &&
331
352
  operation.name !== "post.skip-batch"
332
353
  ) {
333
354
  const retryInstruction = `Retry with --idempotency-key ${requestId}.`;
@@ -480,6 +501,14 @@ export function assertSubjectWindowResult({ operation, result }) {
480
501
 
481
502
  async function finishAdminOutput({ options, operation, result }) {
482
503
  const paginationStorage = getPaginatedResultStorage(result);
504
+ if (
505
+ options.adminOutput &&
506
+ !paginationStorage &&
507
+ operation.name !== "news.claim" &&
508
+ operation.name !== "post.skip-batch"
509
+ ) {
510
+ writeAdminResultOutput({ filePath: options.adminOutput, result, operation });
511
+ }
483
512
  if (options.json) {
484
513
  if (paginationStorage) {
485
514
  await writePaginatedResultJson({
@@ -507,6 +536,27 @@ async function finishAdminOutput({ options, operation, result }) {
507
536
  return result;
508
537
  }
509
538
 
539
+ export function writeAdminResultOutput({ filePath, result, operation }) {
540
+ try {
541
+ return writeAdminJsonFile(filePath, result, { privateFile: true });
542
+ } catch (cause) {
543
+ const error = new Error(
544
+ `The canonical ${operation.name} result was received, but --output could not be saved: ${cause.message}. Do not repeat a successful mutation just to save its output.`,
545
+ );
546
+ error.code = "LUMINE_ADMIN_OUTPUT_WRITE_FAILED";
547
+ error.data = {
548
+ ok: false,
549
+ status: "error",
550
+ error: {
551
+ code: error.code,
552
+ message: error.message,
553
+ details: { canonicalResult: result, outputPath: String(filePath) },
554
+ },
555
+ };
556
+ throw error;
557
+ }
558
+ }
559
+
510
560
  export function normalizeAdminBuildCandidatesResult({ result, siteUrl }) {
511
561
  const builds = Array.isArray(result?.builds) ? result.builds : [];
512
562
  const nextCursor = String(result?.cursor || "").trim() || null;
@@ -756,7 +806,7 @@ export function canonicalAdminRunScope(run) {
756
806
  const raw = run?.runScope;
757
807
  if (raw === undefined || raw === null || raw === "") return "full";
758
808
  const scope = String(raw);
759
- if (scope === "full" || scope === "featured") return scope;
809
+ if (["full", "featured", "newspaper"].includes(scope)) return scope;
760
810
  throw cliValidationError(
761
811
  "The API returned an invalid administrator run scope.",
762
812
  );
@@ -778,6 +828,12 @@ const FEATURED_RUN_OPERATIONS = new Set([
778
828
  "featured.add",
779
829
  "featured.reorder",
780
830
  "featured.rotate",
831
+ "featured.plan",
832
+ "featured.apply",
833
+ "featured.comments.scan",
834
+ "featured.comments.acknowledge",
835
+ "featured.comments.recommend",
836
+ "featured.comments.report",
781
837
  "daily-run.complete",
782
838
  "daily-run.fail",
783
839
  ]);
@@ -786,6 +842,14 @@ export function assertAdminOperationAllowedForRunScope({
786
842
  operation,
787
843
  runScope,
788
844
  }) {
845
+ if (runScope === "newspaper") {
846
+ if (
847
+ ["news.status", "news.claim", "news.submit", "news.print", "daily-run.complete", "daily-run.fail"].includes(operation.name)
848
+ ) return;
849
+ throw cliValidationError(
850
+ `A newspaper-only run does not authorize ${operation.name}.`,
851
+ );
852
+ }
789
853
  if (runScope !== "featured") return;
790
854
  const subjectCommentAlias =
791
855
  operation.name === "post.comments" &&
@@ -794,7 +858,7 @@ export function assertAdminOperationAllowedForRunScope({
794
858
  return;
795
859
  }
796
860
  throw cliValidationError(
797
- `A Featured-only run does not authorize ${operation.name}. Complete or fail it before starting a full daily review.`,
861
+ `A Featured-only run does not authorize ${operation.name}. Use the review-bound Featured comment workflow for encouragement; do not start unrelated daily work to bypass scope.`,
798
862
  );
799
863
  }
800
864
 
@@ -1216,17 +1280,17 @@ export function parseAdminOperation(options) {
1216
1280
  if (action === "start") {
1217
1281
  const runScope = parseDailyRunScope(options.adminScope || "full");
1218
1282
  const commentMode = parseCommentMode(options.commentMode || "off");
1219
- if (runScope === "featured" && commentMode !== "off") {
1283
+ if (runScope !== "full" && commentMode !== "off") {
1220
1284
  throw cliValidationError(
1221
- "A Featured-only run requires --comment-mode off.",
1285
+ "A scoped run requires --comment-mode off.",
1222
1286
  );
1223
1287
  }
1224
1288
  return writeOperation(
1225
1289
  "daily-run.start",
1226
1290
  "POST",
1227
- runScope === "featured"
1228
- ? "/cli/admin/daily-runs/start/featured"
1229
- : "/cli/admin/daily-runs/start",
1291
+ runScope === "full"
1292
+ ? "/cli/admin/daily-runs/start"
1293
+ : `/cli/admin/daily-runs/start/${runScope}`,
1230
1294
  {
1231
1295
  identity: options.adminIdentity
1232
1296
  ? parseIdentity(options.adminIdentity)
@@ -1483,6 +1547,30 @@ export function parseAdminOperation(options) {
1483
1547
  }
1484
1548
 
1485
1549
  if (namespace === "featured") {
1550
+ if (action === "comments") {
1551
+ if (!["scan", "acknowledge", "recommend", "report"].includes(target)) {
1552
+ throw cliValidationError("Usage: featured comments scan|acknowledge|recommend|report --checkpoint <file>.");
1553
+ }
1554
+ if (options.adminAll || options.adminCursor || options.adminUnviewed || options.adminViewed || options.adminAfter) {
1555
+ throw cliValidationError("The Featured review covers every comment; filtered or caller-supplied cursors are not supported.");
1556
+ }
1557
+ return { ...writeOperation(`featured.comments.${target}`, "POST", "/cli/admin/subjects/featured/reviews", {}),
1558
+ mutates: target !== "report", featuredWorkflow: target };
1559
+ }
1560
+ if (action === "plan") {
1561
+ if (!options.adminPostedAfter) throw cliValidationError("A Featured plan requires --posted-after <timestamp>.");
1562
+ return writeOperation("featured.plan", "POST", "/cli/admin/subjects/featured/plan", {
1563
+ removeIds: parseFeaturedSubjectIds(options.adminRemoveIds, "--remove-subject-ids"),
1564
+ addIds: parseFeaturedSubjectIds(options.adminAddIds, "--add-subject-ids"),
1565
+ ...(options.adminIds ? { finalIds: parseOrderedIds(options.adminIds) } : {}),
1566
+ postedAfter: options.adminPostedAfter,
1567
+ });
1568
+ }
1569
+ if (action === "apply") {
1570
+ readApprovedFeaturedPlan(options.adminFile, options.adminApprove);
1571
+ return { ...writeOperation("featured.apply", "POST", "/cli/admin/subjects/featured/plan/apply", {}),
1572
+ featuredWorkflow: "apply" };
1573
+ }
1486
1574
  if (action === "list") {
1487
1575
  return readOperation("featured.list", "/cli/admin/subjects/featured");
1488
1576
  }
@@ -1528,7 +1616,7 @@ export function parseAdminOperation(options) {
1528
1616
  if (namespace === "post") {
1529
1617
  if (action === "get") return postGetOperation(target, options);
1530
1618
  if (action === "comments") {
1531
- const parsedTarget = parseRecommendationTarget({
1619
+ const parsedTarget = parseAdminCommentTarget({
1532
1620
  target,
1533
1621
  explicitType: options.adminType,
1534
1622
  });
@@ -1729,7 +1817,9 @@ export function parseAdminOperation(options) {
1729
1817
  "ai-costs monthly uses UTC calendar months and does not accept --days.",
1730
1818
  );
1731
1819
  }
1732
- return readOperation("ai-costs.monthly", "/cli/admin/ai-costs/monthly");
1820
+ return readOperation("ai-costs.monthly", "/cli/admin/ai-costs/monthly", {
1821
+ requiresRun: false,
1822
+ });
1733
1823
  }
1734
1824
  if (action === "day" && !extra) {
1735
1825
  if (options.adminDays) {
@@ -2243,6 +2333,7 @@ function featuredRotateOperation(options) {
2243
2333
  {
2244
2334
  removeIds,
2245
2335
  addIds,
2336
+ ...(options.adminPostedAfter ? { postedAfter: options.adminPostedAfter } : {}),
2246
2337
  },
2247
2338
  );
2248
2339
  }
@@ -2524,7 +2615,7 @@ export function assertAdminTodoHandoffResult(result, expectedRunScope = null) {
2524
2615
  throw error;
2525
2616
  }
2526
2617
  const handoff = result?.data?.carryoverTodos;
2527
- if (runScope === "featured") {
2618
+ if (runScope !== "full") {
2528
2619
  if (
2529
2620
  !runId ||
2530
2621
  !handoff ||
@@ -2536,7 +2627,7 @@ export function assertAdminTodoHandoffResult(result, expectedRunScope = null) {
2536
2627
  Number(handoff.newlySurfacedCount) !== 0
2537
2628
  ) {
2538
2629
  const error = cliValidationError(
2539
- "The API did not confirm that carry-over telemetry was suppressed for the Featured-only run.",
2630
+ "The API did not confirm that carry-over telemetry was suppressed for the scoped run.",
2540
2631
  );
2541
2632
  error.code = "LUMINE_ADMIN_SCOPED_RUN_UNSUPPORTED";
2542
2633
  throw error;
@@ -2617,8 +2708,8 @@ function parseDailyRunScope(value) {
2617
2708
  const scope = String(value || "full")
2618
2709
  .trim()
2619
2710
  .toLowerCase();
2620
- if (!["full", "featured"].includes(scope)) {
2621
- throw cliValidationError("--scope must be full or featured.");
2711
+ if (!["full", "featured", "newspaper"].includes(scope)) {
2712
+ throw cliValidationError("--scope must be full, featured, or newspaper.");
2622
2713
  }
2623
2714
  return scope;
2624
2715
  }
@@ -3156,6 +3247,18 @@ async function printSpooledAdminResult({ operation, result, storage }) {
3156
3247
 
3157
3248
  function printAdminResult({ operation, result }) {
3158
3249
  const data = result?.data || {};
3250
+ if (operation.name === "featured.plan") {
3251
+ for (const pair of data.plan?.replacements || []) {
3252
+ console.log(`${pair.remove.id} ${pair.remove.title} -> ${pair.add.id} ${pair.add.title}`);
3253
+ }
3254
+ console.log(`Proposed final order: ${(data.plan?.finalIds || []).join(",")}`);
3255
+ console.log(`Plan hash: ${data.planHash}. Obtain Mikey's approval before using featured apply --file <plan.json> --approve ${data.planHash}.`);
3256
+ return;
3257
+ }
3258
+ if (operation.featuredWorkflow) {
3259
+ console.log(JSON.stringify(data, null, 2));
3260
+ return;
3261
+ }
3159
3262
  if (Array.isArray(data.applications)) {
3160
3263
  console.log(`${data.applications.length} sponsor application(s):`);
3161
3264
  for (const application of data.applications) {
@@ -3300,6 +3403,13 @@ function printAdminResult({ operation, result }) {
3300
3403
  ` ${coverage.queue}: ${coverage.candidateCount} candidate(s), ${coverage.scannedCount} row(s) scanned across ${coverage.pages} page(s).`,
3301
3404
  );
3302
3405
  }
3406
+ for (const item of report.featuredReviews || []) {
3407
+ const coverage = item.coverage;
3408
+ const counts = item.encouragement || {};
3409
+ console.log(
3410
+ ` Featured review #${item.review?.id || coverage?.reviewId}: ${coverage?.reviewed ? `${coverage.coveredSubjectIds.length}/${coverage.subjectCount} Subjects read, ${coverage.commentsRead} comments` : "not acknowledged as read"}; ${coverage?.complete ? "complete" : "incomplete"}; ${counts.newBasicRecommendations || 0} confirmed new basic recommendations, ${counts.alreadyDone || 0} already done, ${counts.rewardEligibilityGrants || 0} reward-permission grants, ${counts.directRewardsCreated || 0} direct rewards.`,
3411
+ );
3412
+ }
3303
3413
  for (const escalation of report.escalations) {
3304
3414
  const target =
3305
3415
  escalation.url ||
package/lib/commands.js CHANGED
@@ -2379,6 +2379,7 @@ export function parseArgs(args) {
2379
2379
  adminCursor: raw.cursor ? String(raw.cursor) : "",
2380
2380
  adminAfter: raw.after ? String(raw.after) : "",
2381
2381
  adminPostedAfter: raw.postedAfter ? String(raw.postedAfter) : "",
2382
+ adminApprove: raw.approve ? String(raw.approve) : "",
2382
2383
  adminSinceRun: Boolean(raw.sinceRun),
2383
2384
  adminIncludeLegacy: Boolean(raw.includeLegacy),
2384
2385
  adminIncludePrivateEvidence: parseBoolean(
@@ -2846,7 +2847,7 @@ export function printHelp() {
2846
2847
  lumine admin ai-bucket note set --bucket-id <id> --note <text> [--json]
2847
2848
  lumine admin ai-email-policy get --email <address> [--json]
2848
2849
  lumine admin ai-email-policy set --email <address> --mode <automatic|separate_accounts> --note <text> [--json]
2849
- lumine admin daily-run start [--scope full|featured] [--identity zero|ciel|auto] [--comment-mode off|draft|post] [--run-key <key>] [--json]
2850
+ lumine admin daily-run start [--scope full|featured|newspaper] [--identity zero|ciel|auto] [--comment-mode off|draft|post] [--run-key <key>] [--json]
2850
2851
  lumine admin daily-run status|report|complete|fail [--run <completed-run-id>] [--reason <text>] [--json]
2851
2852
  lumine admin daily-run escalation add --target <target> --note <summary> [--severity attention|urgent] [--json]
2852
2853
  lumine admin escalation list [--status open|acknowledged|resolved|all] [--limit <number>] [--json]
@@ -2874,9 +2875,15 @@ export function printHelp() {
2874
2875
  lumine admin featured history --subject-ids <id,id,...> [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--json]
2875
2876
  lumine admin featured add --subject-ids <id,id,...> --posted-after <ISO-8601-or-Unix-time> [--json]
2876
2877
  lumine admin featured reorder --subject-ids <id,id,...> [--json]
2877
- lumine admin featured rotate --remove-subject-ids <id,id,...> --add-subject-ids <id,id,...> [--json]
2878
+ lumine admin featured rotate --remove-subject-ids <id,id,...> --add-subject-ids <id,id,...> [--posted-after <timestamp>] [--json]
2879
+ lumine admin featured plan --remove-subject-ids <ids> --add-subject-ids <ids> [--subject-ids <final-order>] --posted-after <timestamp> --output <plan.json> [--json]
2880
+ lumine admin featured apply --file <plan.json> --approve <exact-plan-hash> [--json]
2881
+ lumine admin featured comments scan --checkpoint <file> [--resume] [--json]
2882
+ lumine admin featured comments acknowledge --checkpoint <scan-file> --reviewed [--json]
2883
+ lumine admin featured comments recommend --file <decisions.json> --checkpoint <batch-file> [--resume] [--json]
2884
+ lumine admin featured comments report --checkpoint <scan-file> [--json]
2878
2885
  lumine admin post get <target> [--type subject|comment|aiStory|dailyReflection] [--json]
2879
- lumine admin post comments <target> [--type subject|aiStory|dailyReflection] [--unviewed|--viewed] [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--json]
2886
+ lumine admin post comments <target> [--type subject|build|aiStory|dailyReflection] [--unviewed|--viewed] [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--json]
2880
2887
  lumine admin post recommend <target> [--type subject|comment|aiStory|dailyReflection] [--anyone-can-reward] [--reward-twinkles 3] [--json]
2881
2888
  lumine admin post skip <target> [--type comment|aiStory|dailyReflection] [--reason <text>] [--json]
2882
2889
  lumine admin post skip-batch --target-file <json-or-lines> [--reason <text>] [--checkpoint <file> [--resume]] [--json]
@@ -2886,7 +2893,7 @@ export function printHelp() {
2886
2893
  lumine admin comment post --draft-id <id> [--json]
2887
2894
  lumine admin comment edit <comment-id> --file <comment.md> [--json]
2888
2895
  lumine admin brief [--days <1..30>] [--json]
2889
- lumine admin ai-costs monthly [--json]
2896
+ lumine admin ai-costs monthly [--output <file.json>] [--json] (no active run required)
2890
2897
  lumine admin ai-costs day <YYYY-MM-DD> [--json]
2891
2898
  lumine admin media-costs monthly [--json]
2892
2899
  lumine admin energy-budget [--days <1..31>] [--json]
@@ -2992,7 +2999,7 @@ Options:
2992
2999
  --review-receipt <f> Confirmed managed Build runtime review receipt
2993
3000
  --review-context <f> Private JSON understanding from the reviewed Build
2994
3001
  --review-session <f> Private production-log review lease/checkpoint
2995
- --reviewed Confirm every downloaded production-log byte was reviewed
3002
+ --reviewed Explicitly acknowledge downloaded logs or Featured comment pages as read
2996
3003
  --severity <level> Run escalation severity: attention or urgent
2997
3004
  --status <state> Private escalation or todo lifecycle filter/state
2998
3005
  --wait-ms <ms> Managed Build observation or sponsor watch duration
@@ -3008,6 +3015,7 @@ Options:
3008
3015
  --subject-ids <ids> Complete ordered Featured subject IDs
3009
3016
  --remove-subject-ids <ids> Featured subjects approved for rotation removal
3010
3017
  --add-subject-ids <ids> Ordered replacement subjects for Featured rotation
3018
+ --approve <hash> Exact Featured plan hash, only after Mikey's go-ahead
3011
3019
  --bucket-id <id> Unbanned AI identity bucket for account consolidation
3012
3020
  --label <name> Name for a new unbanned AI identity bucket
3013
3021
  --user-ids <ids> Explicit user IDs for an AI bucket batch (up to 500)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.67",
3
+ "version": "0.2.68",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -467,10 +467,10 @@ const result = await Twinkle.ai.chat({ message, history: chatHistory, systemProm
467
467
  - Use this instead of asking Twinkle.ai.chat to return JSON.
468
468
  - expectedStructure must be a JSON object that describes the exact returned object shape.
469
469
  - mode is accepted as an alias for thinkingMode, and mid is accepted as an alias for medium.
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.
470
+ - Omit model to use the normal Lite/Medium/High routing. model accepts gpt-6-astra, 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.
471
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.
472
472
  - thinkingMode medium uses Grok 4.6 with medium reasoning and consumes normal AI Energy.
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.
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. Explicit model: 'gpt-6-astra' selects GPT-6 Astra with xhigh reasoning and debits confirmed usage at its own model rates in the High tier.
474
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.
475
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.
476
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.