@stage5/lumine 0.2.68 → 0.2.70

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
@@ -23,6 +23,7 @@ import {
23
23
  } from "./build-review.js";
24
24
  import { runAdminRuntimeLogWorkflow } from "./admin-runtime-logs.js";
25
25
  import { readApprovedFeaturedPlan, runFeaturedWorkflow } from "./admin-featured.js";
26
+ import { FEATURED_HISTORY_BATCH_SIZE, runBatchedFeaturedHistory } from "./admin-featured-history.js";
26
27
 
27
28
  const MAX_EDITORIAL_FILE_BYTES = 256 * 1024;
28
29
  const MAX_COMPOSED_TEXT_FILE_BYTES = 64 * 1024;
@@ -300,7 +301,10 @@ export async function adminCommand(options) {
300
301
  "Use --resume with the scan checkpoint instead of combining --all with --cursor.",
301
302
  );
302
303
  }
303
- result = await runAutomaticPagination({
304
+ const paginate = operation.name === "featured.history" &&
305
+ operation.pagination.filters.subjectIds.length > FEATURED_HISTORY_BATCH_SIZE
306
+ ? runBatchedFeaturedHistory : runAutomaticPagination;
307
+ result = await paginate({
304
308
  options,
305
309
  operation,
306
310
  runId,
@@ -329,6 +333,15 @@ export async function adminCommand(options) {
329
333
  result = transformResult(await fetchOperation());
330
334
  }
331
335
  } catch (error) {
336
+ if (operation.name === "runtime.evidence" && error.status === 404) {
337
+ error.code = "CLI_ADMIN_RUNTIME_EVIDENCE_NOT_DEPLOYED";
338
+ error.message = "The runtime evidence route is not deployed on the requested host. Evidence is unknown; deploy the matching API and activate the collector in an authorized primary-generation release. No restart or host substitution was attempted.";
339
+ error.data = {
340
+ ok: false,
341
+ status: "unavailable",
342
+ error: { code: error.code, message: error.message, details: { httpStatus: 404 } },
343
+ };
344
+ }
332
345
  if (operation.featuredWorkflow && error.featuredProgress) {
333
346
  const serverError = error.data?.error;
334
347
  error.data = {
@@ -388,6 +401,9 @@ export async function adminCommand(options) {
388
401
  typeof operation.body.buildReviewUnderstanding === "string",
389
402
  });
390
403
  }
404
+ if (operation.name === "comment.edit") {
405
+ assertComposedCommentEditResult({ result, body: operation.body });
406
+ }
391
407
  if (operation.name === "ai-email-policy.set") {
392
408
  assertAiEmailPolicySetResult({ operation, result });
393
409
  }
@@ -423,6 +439,7 @@ function transformAdminResult({
423
439
  assertSubjectWindowResult({ operation, result: transformed });
424
440
  }
425
441
  if (operation.name === "builds.candidates") {
442
+ assertBuildWindowResult({ operation, result: transformed });
426
443
  transformed = normalizeAdminBuildCandidatesResult({
427
444
  result: transformed,
428
445
  siteUrl: options.siteUrl,
@@ -558,12 +575,11 @@ export function writeAdminResultOutput({ filePath, result, operation }) {
558
575
  }
559
576
 
560
577
  export function normalizeAdminBuildCandidatesResult({ result, siteUrl }) {
561
- const builds = Array.isArray(result?.builds) ? result.builds : [];
562
- const nextCursor = String(result?.cursor || "").trim() || null;
578
+ const builds = result.data.builds;
563
579
  return {
564
- ok: true,
565
- status: "success",
580
+ ...result,
566
581
  data: {
582
+ ...result.data,
567
583
  builds: builds.map((build) => {
568
584
  const id = Number(build?.id || 0);
569
585
  return {
@@ -580,15 +596,128 @@ export function normalizeAdminBuildCandidatesResult({ result, siteUrl }) {
580
596
  },
581
597
  };
582
598
  }),
583
- pagination: {
584
- nextCursor,
585
- hasMore: Boolean(nextCursor),
586
- exhausted: !nextCursor,
587
- },
588
599
  },
589
600
  };
590
601
  }
591
602
 
603
+ export function assertBuildWindowResult({ operation, result }) {
604
+ const p = result?.data?.pagination;
605
+ const mode = operation?.pagination?.coverageMode;
606
+ if (
607
+ result?.ok !== true ||
608
+ !Array.isArray(result?.data?.builds) ||
609
+ !p ||
610
+ p.mode !== mode ||
611
+ !Number.isSafeInteger(p.snapshotMaxId) ||
612
+ p.snapshotMaxId < 0 ||
613
+ !Number.isSafeInteger(p.snapshotTimeStamp) ||
614
+ p.snapshotTimeStamp < 0 ||
615
+ (mode === "legacy"
616
+ ? p.after !== null
617
+ : !Number.isSafeInteger(p.after) || p.after < 0) ||
618
+ (mode === "after" && p.after !== operation.pagination.after) ||
619
+ typeof p.exhausted !== "boolean" ||
620
+ p.hasMore !== !p.exhausted ||
621
+ (p.exhausted
622
+ ? p.nextCursor !== null
623
+ : typeof p.nextCursor !== "string" || !p.nextCursor)
624
+ ) {
625
+ const error = new Error(
626
+ "The API did not confirm the requested published-Build window and snapshot. Deploy the matching API; do not substitute an unbounded public browser scan.",
627
+ );
628
+ error.code = "LUMINE_ADMIN_BUILD_WINDOW_UNSUPPORTED";
629
+ throw error;
630
+ }
631
+ }
632
+
633
+ export function assertComposedCommentEditResult({ result, body }) {
634
+ const edit = result?.data?.edit;
635
+ if (
636
+ result?.ok === true &&
637
+ result?.data?.comment?.content === body.content &&
638
+ (!body.buildReviewUnderstanding ||
639
+ (edit?.buildReviewContextStored === true &&
640
+ edit?.reviewedBuildVersionId === body.reviewedBuildVersionId &&
641
+ Number.isSafeInteger(edit?.managementDraftId) &&
642
+ edit.managementDraftId > 0))
643
+ )
644
+ return;
645
+ const error = new Error(
646
+ "The API did not confirm the exact comment edit and its required Build review context. Inspect the canonical result before retrying; deploy the matching API if unsupported.",
647
+ );
648
+ error.code = "LUMINE_ADMIN_COMMENT_EDIT_UNCONFIRMED";
649
+ error.data = {
650
+ ok: false,
651
+ status: "validation_error",
652
+ error: {
653
+ code: error.code,
654
+ message: error.message,
655
+ details: { canonicalResult: result },
656
+ },
657
+ };
658
+ throw error;
659
+ }
660
+
661
+ function readAdminBuildReviewEvidence(options, parsedTarget) {
662
+ const version = options.adminReviewedBuildVersion
663
+ ? parseRequiredInteger(
664
+ options.adminReviewedBuildVersion,
665
+ "--reviewed-version",
666
+ 1,
667
+ )
668
+ : undefined;
669
+ const method = options.adminBuildReviewMethod
670
+ ? parseAdminBuildReviewMethod(options.adminBuildReviewMethod)
671
+ : undefined;
672
+ const receipt = options.adminReviewReceipt
673
+ ? parseBuildReviewReceipt(options.adminReviewReceipt)
674
+ : null;
675
+ const understanding = options.adminReviewContext
676
+ ? readBuildReviewContextFile(options.adminReviewContext)
677
+ : undefined;
678
+ if (receipt && (version || method)) {
679
+ throw cliValidationError(
680
+ "Pass either --review-receipt or manual --reviewed-version/--reviewed-via evidence, not both.",
681
+ );
682
+ }
683
+ const reviewedBuildVersionId = receipt
684
+ ? Number(receipt.publishedArtifactVersionId)
685
+ : version;
686
+ const buildReviewMethod = receipt ? "runtime" : method;
687
+ if (understanding && (!reviewedBuildVersionId || !buildReviewMethod)) {
688
+ throw cliValidationError(
689
+ "--review-context requires confirmed Build review evidence.",
690
+ );
691
+ }
692
+ if ((reviewedBuildVersionId || buildReviewMethod) && !understanding) {
693
+ throw cliValidationError(
694
+ "Build review evidence requires --review-context <context.json>.",
695
+ );
696
+ }
697
+ if (
698
+ receipt &&
699
+ parsedTarget.type === "build" &&
700
+ Number(receipt.buildId) !== parsedTarget.id
701
+ ) {
702
+ throw cliValidationError(
703
+ "The review receipt belongs to a different Build.",
704
+ );
705
+ }
706
+ if (
707
+ !["build", "comment"].includes(parsedTarget.type) &&
708
+ (reviewedBuildVersionId || buildReviewMethod)
709
+ ) {
710
+ throw cliValidationError(
711
+ "Build review evidence applies only to build:<id> or a comment:<id> inside a Build.",
712
+ );
713
+ }
714
+ return {
715
+ ...(reviewedBuildVersionId ? { reviewedBuildVersionId } : {}),
716
+ ...(buildReviewMethod ? { buildReviewMethod } : {}),
717
+ ...(understanding ? { buildReviewUnderstanding: understanding } : {}),
718
+ };
719
+ }
720
+
592
721
  export function assertComposedCommentDraftResult({
593
722
  result,
594
723
  expectedContent,
@@ -873,6 +1002,7 @@ function adminOperationRequiresRun(operation) {
873
1002
  "identity.use",
874
1003
  "identity.inspect",
875
1004
  "economy.trace",
1005
+ "bot.context",
876
1006
  "rescue.wordle.audit",
877
1007
  "daily-run.start",
878
1008
  "daily-run.status",
@@ -1445,11 +1575,13 @@ export function parseAdminOperation(options) {
1445
1575
  }
1446
1576
 
1447
1577
  if (namespace === "builds" && action === "candidates") {
1578
+ const window = parseBuildWindow(options);
1448
1579
  return readOperation(
1449
1580
  "builds.candidates",
1450
- withQuery("/build/public/list", {
1451
- sort: "recent",
1452
- scope: "all",
1581
+ withQuery("/cli/admin/builds/candidates", {
1582
+ sinceRun: window.mode === "since-run" ? "true" : "",
1583
+ after: window.mode === "after" ? window.after : "",
1584
+ includeLegacy: window.mode === "legacy" ? "true" : "",
1453
1585
  cursor: options.adminCursor,
1454
1586
  limit: options.limit,
1455
1587
  }),
@@ -1457,9 +1589,12 @@ export function parseAdminOperation(options) {
1457
1589
  pagination: {
1458
1590
  collectionKey: "builds",
1459
1591
  coverageQueue: "builds",
1460
- coverageMode: "all",
1461
- after: null,
1462
- filters: { sort: "recent", scope: "all" },
1592
+ coverageMode: window.mode,
1593
+ after:
1594
+ window.mode === "after"
1595
+ ? parseAfterForCoverage(window.after)
1596
+ : null,
1597
+ filters: { sort: "published-release", scope: "all" },
1463
1598
  },
1464
1599
  },
1465
1600
  );
@@ -1478,6 +1613,9 @@ export function parseAdminOperation(options) {
1478
1613
  body: undefined,
1479
1614
  mutates: false,
1480
1615
  buildId: parsed.id,
1616
+ // Public artifact inspection also supports a one-comment correction;
1617
+ // it never needs a full daily-management authorization envelope.
1618
+ requiresRun: false,
1481
1619
  };
1482
1620
  }
1483
1621
 
@@ -1579,6 +1717,12 @@ export function parseAdminOperation(options) {
1579
1717
  options.adminIds,
1580
1718
  "--subject-ids",
1581
1719
  );
1720
+ if (subjectIds.length > 20_000) {
1721
+ throw cliValidationError("Featured history accepts at most 20000 subject IDs per CLI scan.");
1722
+ }
1723
+ if (subjectIds.length > FEATURED_HISTORY_BATCH_SIZE && !options.adminAll) {
1724
+ throw cliValidationError("Pass --all to automatically batch history reads larger than 100 subjects.");
1725
+ }
1582
1726
  return readOperation(
1583
1727
  "featured.history",
1584
1728
  withQuery("/cli/admin/subjects/featured/history", {
@@ -1589,6 +1733,7 @@ export function parseAdminOperation(options) {
1589
1733
  {
1590
1734
  pagination: {
1591
1735
  collectionKey: "events",
1736
+ summaryKeys: ["coverage", "subjects"],
1592
1737
  filters: { subjectIds },
1593
1738
  },
1594
1739
  },
@@ -1860,10 +2005,23 @@ export function parseAdminOperation(options) {
1860
2005
  );
1861
2006
  }
1862
2007
 
2008
+ if (namespace === "runtime") {
2009
+ if (action !== "evidence" || !["primary", "target"].includes(target) || extra) {
2010
+ throw cliValidationError("Usage: lumine admin runtime evidence primary|target [--days 1..7].");
2011
+ }
2012
+ return readOperation(
2013
+ "runtime.evidence",
2014
+ withQuery(`/cli/admin/runtime-logs/hosts/${target}/evidence`, {
2015
+ days: parseRequiredInteger(options.adminDays || "7", "--days", 1, 7),
2016
+ }),
2017
+ { requiresRun: false },
2018
+ );
2019
+ }
2020
+
1863
2021
  if (namespace === "runtime-logs") {
1864
2022
  if (
1865
2023
  (action === "start" || action === "status" || action === "read") &&
1866
- !target &&
2024
+ (!target || (action === "start" && ["primary", "target"].includes(target))) &&
1867
2025
  !extra
1868
2026
  ) {
1869
2027
  if (
@@ -1883,6 +2041,7 @@ export function parseAdminOperation(options) {
1883
2041
  mutates: action !== "status",
1884
2042
  requiresRun: false,
1885
2043
  runtimeLogAction: action === "read" ? "capture" : action,
2044
+ ...(action === "start" && target ? { runtimeLogHost: target } : {}),
1886
2045
  };
1887
2046
  }
1888
2047
  if ((action === "resume" || action === "abandon") && !target && !extra) {
@@ -1978,6 +2137,36 @@ export function parseAdminOperation(options) {
1978
2137
  });
1979
2138
  }
1980
2139
 
2140
+ if (namespace === "bot-output" && action === "context") {
2141
+ const messageId = parseRequiredInteger(target, "bot message ID", 1);
2142
+ const reason = String(options.adminReason || "").trim();
2143
+ if (!reason || reason.length > MAX_IDENTITY_INSPECTION_REASON_LENGTH) {
2144
+ throw cliValidationError(
2145
+ "Explain the private bot investigation with --reason (1–500 characters).",
2146
+ );
2147
+ }
2148
+ if (options.adminAll || options.adminDays) {
2149
+ throw cliValidationError(
2150
+ "Bot context is a narrow message-ID investigation, not an --all or --days scan.",
2151
+ );
2152
+ }
2153
+ return writeOperation(
2154
+ "bot.context",
2155
+ "POST",
2156
+ `/cli/admin/bot-output/${messageId}/context`,
2157
+ {
2158
+ reason,
2159
+ cursor: options.adminCursor || undefined,
2160
+ limit: parseRequiredInteger(
2161
+ options.adminContextLimit ?? 20,
2162
+ "--limit",
2163
+ 1,
2164
+ 40,
2165
+ ),
2166
+ },
2167
+ );
2168
+ }
2169
+
1981
2170
  if (namespace === "bot-output" && !action) {
1982
2171
  if (options.adminDays && options.adminCursor) {
1983
2172
  throw cliValidationError(
@@ -2029,58 +2218,10 @@ export function parseAdminOperation(options) {
2029
2218
  "comment reply targets a comment: lumine admin comment reply comment:<id>.",
2030
2219
  );
2031
2220
  }
2032
- const reviewedBuildVersionId = options.adminReviewedBuildVersion
2033
- ? parseRequiredInteger(
2034
- options.adminReviewedBuildVersion,
2035
- "--reviewed-version",
2036
- 1,
2037
- )
2038
- : undefined;
2039
- const buildReviewMethod = options.adminBuildReviewMethod
2040
- ? parseAdminBuildReviewMethod(options.adminBuildReviewMethod)
2041
- : undefined;
2042
- const reviewReceipt = options.adminReviewReceipt
2043
- ? parseBuildReviewReceipt(options.adminReviewReceipt)
2044
- : null;
2045
- const buildReviewUnderstanding = options.adminReviewContext
2046
- ? readBuildReviewContextFile(options.adminReviewContext)
2047
- : undefined;
2048
- if (reviewReceipt && (reviewedBuildVersionId || buildReviewMethod)) {
2049
- throw cliValidationError(
2050
- "Pass either --review-receipt or manual --reviewed-version/--reviewed-via evidence, not both.",
2051
- );
2052
- }
2053
- const confirmedBuildVersionId = reviewReceipt
2054
- ? Number(reviewReceipt.publishedArtifactVersionId)
2055
- : reviewedBuildVersionId;
2056
- const confirmedBuildReviewMethod = reviewReceipt
2057
- ? "runtime"
2058
- : buildReviewMethod;
2059
- if (
2060
- buildReviewUnderstanding &&
2061
- (!confirmedBuildVersionId || !confirmedBuildReviewMethod)
2062
- ) {
2063
- throw cliValidationError(
2064
- "--review-context requires confirmed Build review evidence.",
2065
- );
2066
- }
2067
- if (
2068
- (confirmedBuildVersionId || confirmedBuildReviewMethod) &&
2069
- !buildReviewUnderstanding
2070
- ) {
2071
- throw cliValidationError(
2072
- "Build review evidence requires --review-context <context.json>.",
2073
- );
2074
- }
2075
- if (
2076
- reviewReceipt &&
2077
- parsedTarget.type === "build" &&
2078
- Number(reviewReceipt.buildId) !== parsedTarget.id
2079
- ) {
2080
- throw cliValidationError(
2081
- "The review receipt belongs to a different Build.",
2082
- );
2083
- }
2221
+ const reviewEvidence = readAdminBuildReviewEvidence(
2222
+ options,
2223
+ parsedTarget,
2224
+ );
2084
2225
  if (parsedTarget.type === "build") {
2085
2226
  if (!options.adminFile) {
2086
2227
  throw cliValidationError(
@@ -2088,21 +2229,14 @@ export function parseAdminOperation(options) {
2088
2229
  );
2089
2230
  }
2090
2231
  if (
2091
- !confirmedBuildVersionId ||
2092
- !confirmedBuildReviewMethod ||
2093
- !buildReviewUnderstanding
2232
+ !reviewEvidence.reviewedBuildVersionId ||
2233
+ !reviewEvidence.buildReviewMethod ||
2234
+ !reviewEvidence.buildReviewUnderstanding
2094
2235
  ) {
2095
2236
  throw cliValidationError(
2096
2237
  "After reviewing the project, pass review evidence and --review-context <context.json>.",
2097
2238
  );
2098
2239
  }
2099
- } else if (
2100
- parsedTarget.type !== "comment" &&
2101
- (confirmedBuildVersionId || confirmedBuildReviewMethod)
2102
- ) {
2103
- throw cliValidationError(
2104
- "Build review evidence applies only to build:<id> or a comment:<id> inside a Build.",
2105
- );
2106
2240
  }
2107
2241
  return writeOperation(
2108
2242
  "comment.draft",
@@ -2117,13 +2251,7 @@ export function parseAdminOperation(options) {
2117
2251
  ...(options.adminFile
2118
2252
  ? { content: readComposedTextFile(options.adminFile) }
2119
2253
  : {}),
2120
- ...(confirmedBuildVersionId
2121
- ? { reviewedBuildVersionId: confirmedBuildVersionId }
2122
- : {}),
2123
- ...(confirmedBuildReviewMethod
2124
- ? { buildReviewMethod: confirmedBuildReviewMethod }
2125
- : {}),
2126
- ...(buildReviewUnderstanding ? { buildReviewUnderstanding } : {}),
2254
+ ...reviewEvidence,
2127
2255
  },
2128
2256
  );
2129
2257
  }
@@ -2148,7 +2276,13 @@ export function parseAdminOperation(options) {
2148
2276
  "comment.edit",
2149
2277
  "PUT",
2150
2278
  `/cli/admin/comments/${commentId}`,
2151
- { content: readComposedTextFile(options.adminFile) },
2279
+ {
2280
+ content: readComposedTextFile(options.adminFile),
2281
+ ...readAdminBuildReviewEvidence(options, {
2282
+ type: "comment",
2283
+ id: commentId,
2284
+ }),
2285
+ },
2152
2286
  {
2153
2287
  correctionEligible: true,
2154
2288
  correctionCommentId: commentId,
@@ -2394,6 +2528,22 @@ export function parseSubjectWindow(options) {
2394
2528
  return { mode, after: mode === "after" ? options.adminAfter : "" };
2395
2529
  }
2396
2530
 
2531
+ export function parseBuildWindow(options) {
2532
+ const selected = [
2533
+ options.adminSinceRun ? "since-run" : "",
2534
+ options.adminAfter ? "after" : "",
2535
+ options.adminIncludeLegacy ? "legacy" : "",
2536
+ ].filter(Boolean);
2537
+ if (selected.length > 1) {
2538
+ throw cliValidationError(
2539
+ "Choose one Build window: --since-run, --after, or --include-legacy.",
2540
+ );
2541
+ }
2542
+ const mode = selected[0] || "since-run";
2543
+ if (mode === "after") parseAfterForCoverage(options.adminAfter);
2544
+ return { mode, after: mode === "after" ? options.adminAfter : "" };
2545
+ }
2546
+
2397
2547
  function parseUtcDayKey(value) {
2398
2548
  const dayKey = String(value || "").trim();
2399
2549
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dayKey);
@@ -3247,6 +3397,16 @@ async function printSpooledAdminResult({ operation, result, storage }) {
3247
3397
 
3248
3398
  function printAdminResult({ operation, result }) {
3249
3399
  const data = result?.data || {};
3400
+ if (operation.name === "runtime.evidence") {
3401
+ const evidence = data.evidence || {};
3402
+ console.log(`Runtime evidence (${data.host?.requested || "unknown"}): ${evidence.status || "unknown"}.`);
3403
+ console.log(`Samples: ${evidence.coverage?.samples ?? "unknown"}; latest: ${evidence.coverage?.lastAtMs ?? "unknown"}; age ms: ${evidence.coverage?.ageMs ?? "unknown"}.`);
3404
+ for (const recycle of evidence.recycles || []) {
3405
+ console.log(` ${recycle.id}: ${recycle.outcome}; under load: ${recycle.observedUnderLoad ?? "unknown"}.`);
3406
+ }
3407
+ console.log("Missing evidence is unknown, not healthy. A recovered topology does not verify interrupted user work. Use --json for full evidence.");
3408
+ return;
3409
+ }
3250
3410
  if (operation.name === "featured.plan") {
3251
3411
  for (const pair of data.plan?.replacements || []) {
3252
3412
  console.log(`${pair.remove.id} ${pair.remove.title} -> ${pair.add.id} ${pair.add.title}`);
@@ -3360,6 +3520,14 @@ function printAdminResult({ operation, result }) {
3360
3520
  `Confirmed Build #${data.review.buildId} runtime at published artifact #${data.review.publishedArtifactVersionId}.`,
3361
3521
  );
3362
3522
  console.log(`Screenshot: ${data.screenshotPath}`);
3523
+ if (data.review.interaction) {
3524
+ console.log(
3525
+ `Interaction script: ${data.review.interaction.stepsCompleted}/${data.review.interaction.stepsPlanned} step(s) ${data.review.interaction.status}.`,
3526
+ );
3527
+ }
3528
+ for (const shot of data.review.screenshots || []) {
3529
+ console.log(` Screenshot [${shot.label}]: ${shot.path}`);
3530
+ }
3363
3531
  console.log(`Review receipt: ${data.receiptPath}`);
3364
3532
  return;
3365
3533
  }
@@ -3508,6 +3676,20 @@ function printAdminResult({ operation, result }) {
3508
3676
  );
3509
3677
  return;
3510
3678
  }
3679
+ if (data.escalation?.summary) {
3680
+ const target =
3681
+ data.escalation.url ||
3682
+ `${data.escalation.targetType || "target"}:${data.escalation.targetId || "?"}`;
3683
+ console.log(
3684
+ `Escalation${data.escalation.auditId ? ` #${data.escalation.auditId}` : ""} recorded: ${String(data.escalation.severity || "attention").toUpperCase()} ${target} — ${data.escalation.summary}`,
3685
+ );
3686
+ if (data.escalation.auditId) {
3687
+ console.log(
3688
+ `Set its disposition later with: lumine admin escalation set ${data.escalation.auditId} --status <status> --note <decision>`,
3689
+ );
3690
+ }
3691
+ return;
3692
+ }
3511
3693
  if (Array.isArray(data.todos)) {
3512
3694
  printTodoItems(data.todos, "Private carry-over work");
3513
3695
  if (data.truncated) {
package/lib/assets.js CHANGED
@@ -56,7 +56,7 @@ export async function assetsCommand(options) {
56
56
  return;
57
57
  }
58
58
  throw new Error(
59
- 'Usage: lumine assets [list] | lumine assets upload <file...> | lumine assets generate "<prompt>" --model <gpt-image-2|nano-banana> | lumine assets delete <assetId> | lumine assets prune [--yes]',
59
+ 'Usage: lumine assets [list] | lumine assets upload <file...> | lumine assets generate "<prompt>" --model <gpt-image-2.5-flare|gpt-image-2.5-sunburst|gpt-image-2|nano-banana> | lumine assets delete <assetId> | lumine assets prune [--yes]',
60
60
  );
61
61
  }
62
62
 
@@ -65,18 +65,21 @@ export function resolveGenerateModel(options) {
65
65
  const model = GENERATE_MODEL_ALIASES[rawModel];
66
66
  if (!model) {
67
67
  throw new Error(
68
- "--model is required: gpt-image-2 (best quality, slower, pricier) or nano-banana (Gemini — faster, cheaper). No default model is applied.",
68
+ "--model is required: gpt-image-2.5-flare (fast generation), gpt-image-2.5-sunburst (precise editing), gpt-image-2, or nano-banana. No default model is applied.",
69
69
  );
70
70
  }
71
71
  const rawQuality = String(options.quality || "").trim();
72
72
  if (rawQuality && !GENERATE_QUALITIES.has(rawQuality)) {
73
- throw new Error("--quality must be low, medium, or high.");
73
+ throw new Error("--quality must be low, medium, high, xhigh, or max.");
74
74
  }
75
- if (rawQuality && model !== "gpt-image-2") {
75
+ if (rawQuality && !model.startsWith("gpt-image-")) {
76
76
  throw new Error(
77
- "--quality only applies to gpt-image-2; nano-banana has a single quality tier.",
77
+ "--quality only applies to GPT Image models; nano-banana has a single quality tier.",
78
78
  );
79
79
  }
80
+ if (model === "gpt-image-2" && ["xhigh", "max"].includes(rawQuality)) {
81
+ throw new Error("xhigh and max require a GPT Image 2.5 model.");
82
+ }
80
83
  return { model, quality: rawQuality || null };
81
84
  }
82
85
 
@@ -118,7 +121,7 @@ export async function assetsGenerate(options) {
118
121
  const prompt = String(options.positional[1] || "").trim();
119
122
  if (!prompt) {
120
123
  throw new Error(
121
- 'Usage: lumine assets generate "<prompt>" --model <gpt-image-2|nano-banana> [--quality low|medium|high] [--name <fileName>] [--yes]',
124
+ 'Usage: lumine assets generate "<prompt>" --model <gpt-image-2.5-flare|gpt-image-2.5-sunburst|gpt-image-2|nano-banana> [--quality low|medium|high|xhigh|max] [--name <fileName>] [--yes]',
122
125
  );
123
126
  }
124
127
  const { model, quality } = resolveGenerateModel(options);
@@ -161,8 +164,8 @@ export async function assetsGenerate(options) {
161
164
  (option) => option.model === model,
162
165
  );
163
166
  const costLine = selectedOption
164
- ? ` Estimated battery cost: ${formatBatteryPercent(selectedOption.energyUnits, estimate?.fullBatteryUnits)} of a full AI battery (~$${Number(selectedOption.estimatedUsd || 0).toFixed(2)})`
165
- : " Estimated battery cost: unavailable";
167
+ ? ` Estimated image output: ${formatBatteryPercent(selectedOption.energyUnits, estimate?.fullBatteryUnits)} of a full AI battery (~$${Number(selectedOption.estimatedUsd || 0).toFixed(2)}). Prompt input uses additional energy.`
168
+ : " Estimated image output: unavailable";
166
169
  const remainingLine = estimate
167
170
  ? ` Battery remaining now: ${formatBatteryPercent(estimate.energyRemaining, estimate.fullBatteryUnits)}`
168
171
  : "";