@stage5/lumine 0.2.17 → 0.2.19
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 +78 -0
- package/bin/lumine.js +6 -1
- package/lib/admin.js +770 -0
- package/lib/api.js +68 -0
- package/lib/commands.js +491 -21
- package/lib/constants.js +14 -0
- package/lib/http.js +4 -1
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +36 -20
- package/sdk/LUMINE_ADMIN.md +715 -0
package/lib/commands.js
CHANGED
|
@@ -21,10 +21,12 @@ import {
|
|
|
21
21
|
SDK_REFERENCE_FILE,
|
|
22
22
|
} from "./constants.js";
|
|
23
23
|
import {
|
|
24
|
+
adoptBuildThumbnailSuggestion,
|
|
24
25
|
createBuild,
|
|
25
26
|
fetchAllRuntimeAssets,
|
|
26
27
|
forkBuild,
|
|
27
28
|
listBuilds,
|
|
29
|
+
listBuildSuggestions,
|
|
28
30
|
listOpenSourceBuilds,
|
|
29
31
|
loadBuildFiles,
|
|
30
32
|
loadBuildMetadata,
|
|
@@ -37,13 +39,19 @@ import {
|
|
|
37
39
|
maybeCheckForLumineCliUpdate,
|
|
38
40
|
mergeContributionIntoMain,
|
|
39
41
|
mintBuildApiToken,
|
|
42
|
+
notifyBuildOwnerOfContribution,
|
|
40
43
|
publishBuild,
|
|
41
44
|
replaceMainWithContribution,
|
|
42
45
|
resolveBranchBuild,
|
|
43
46
|
saveProjectFiles,
|
|
47
|
+
suggestBuildThumbnailToOwner,
|
|
44
48
|
updateBuildMetadata,
|
|
45
49
|
} from "./api.js";
|
|
46
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
assetsCommand,
|
|
52
|
+
confirmPrompt,
|
|
53
|
+
writeAssetsManifest,
|
|
54
|
+
} from "./assets.js";
|
|
47
55
|
import { thumbnailCommand } from "./thumbnail.js";
|
|
48
56
|
import {
|
|
49
57
|
assertAuthScope,
|
|
@@ -57,6 +65,7 @@ import {
|
|
|
57
65
|
import { probeUrl, requestJson } from "./http.js";
|
|
58
66
|
import { sdkCommand } from "./sdk.js";
|
|
59
67
|
import { doctorCommand, normalizePreviewUrl } from "./doctor.js";
|
|
68
|
+
import { adminCommand } from "./admin.js";
|
|
60
69
|
import {
|
|
61
70
|
defaultMainCheckoutDir,
|
|
62
71
|
defaultReferenceDir,
|
|
@@ -100,10 +109,15 @@ export async function main() {
|
|
|
100
109
|
printHelp();
|
|
101
110
|
return;
|
|
102
111
|
}
|
|
103
|
-
if (options.updateCheck) {
|
|
112
|
+
if (options.updateCheck && !(options.command === "admin" && options.json)) {
|
|
104
113
|
await maybeCheckForLumineCliUpdate({ options });
|
|
105
114
|
}
|
|
106
115
|
|
|
116
|
+
if (options.command === "admin") {
|
|
117
|
+
await adminCommand(options);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
if (options.command === "workspace") {
|
|
108
122
|
await workspace(options);
|
|
109
123
|
return;
|
|
@@ -128,6 +142,10 @@ export async function main() {
|
|
|
128
142
|
await renameBuild(options);
|
|
129
143
|
return;
|
|
130
144
|
}
|
|
145
|
+
if (options.command === "describe") {
|
|
146
|
+
await describeBuild(options);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
131
149
|
if (options.command === "projects") {
|
|
132
150
|
await projects(options);
|
|
133
151
|
return;
|
|
@@ -136,6 +154,14 @@ export async function main() {
|
|
|
136
154
|
await branches(options);
|
|
137
155
|
return;
|
|
138
156
|
}
|
|
157
|
+
if (options.command === "suggest") {
|
|
158
|
+
await sendSuggestion(options);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (options.command === "suggestions") {
|
|
162
|
+
await suggestions(options);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
139
165
|
if (options.command === "explore") {
|
|
140
166
|
await explore(options);
|
|
141
167
|
return;
|
|
@@ -239,6 +265,36 @@ export async function renameBuild(options) {
|
|
|
239
265
|
'Pass a title: `lumine rename "My New Build Title"` or `lumine rename --title "My New Build Title"`.',
|
|
240
266
|
);
|
|
241
267
|
}
|
|
268
|
+
const { buildId, updatedBuild } = await updateOwnedBuildDetails({
|
|
269
|
+
options,
|
|
270
|
+
patch: { title },
|
|
271
|
+
permissionError: (id) => `You cannot rename Build #${id}.`,
|
|
272
|
+
failureMessage: "Failed to rename the Build.",
|
|
273
|
+
});
|
|
274
|
+
console.log(`Renamed Build #${buildId} to "${updatedBuild.title}".`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export async function describeBuild(options) {
|
|
278
|
+
const description = await resolveBuildDescriptionUpdate(options);
|
|
279
|
+
const { updatedBuild } = await updateOwnedBuildDetails({
|
|
280
|
+
options,
|
|
281
|
+
patch: { description },
|
|
282
|
+
permissionError: (id) => `You cannot update Build #${id}.`,
|
|
283
|
+
failureMessage: "Failed to update the Build description.",
|
|
284
|
+
});
|
|
285
|
+
console.log(
|
|
286
|
+
description
|
|
287
|
+
? `Updated description for ${formatBuildTitle(updatedBuild)}.`
|
|
288
|
+
: `Cleared description for ${formatBuildTitle(updatedBuild)}.`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function updateOwnedBuildDetails({
|
|
293
|
+
options,
|
|
294
|
+
patch,
|
|
295
|
+
permissionError,
|
|
296
|
+
failureMessage,
|
|
297
|
+
}) {
|
|
242
298
|
const auth = await ensureAuth(options);
|
|
243
299
|
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
244
300
|
const localProject = await findLocalProjectMetadata(
|
|
@@ -249,21 +305,21 @@ export async function renameBuild(options) {
|
|
|
249
305
|
});
|
|
250
306
|
const currentBuild = await loadBuildMetadata({ options, auth, buildId });
|
|
251
307
|
if (currentBuild.canWrite === false) {
|
|
252
|
-
throw new Error(
|
|
308
|
+
throw new Error(permissionError(buildId));
|
|
253
309
|
}
|
|
254
310
|
if (Number(currentBuild.contributionRootBuildId || 0) > 0) {
|
|
255
311
|
throw new Error(
|
|
256
|
-
"Contribution branches use the original Build
|
|
312
|
+
"Contribution branches use the original Build details and cannot change them.",
|
|
257
313
|
);
|
|
258
314
|
}
|
|
259
315
|
const result = await updateBuildMetadata({
|
|
260
316
|
options,
|
|
261
317
|
auth,
|
|
262
318
|
buildId,
|
|
263
|
-
patch
|
|
319
|
+
patch,
|
|
264
320
|
});
|
|
265
321
|
if (result?.success !== true || !result?.build) {
|
|
266
|
-
throw new Error(result?.error ||
|
|
322
|
+
throw new Error(result?.error || failureMessage);
|
|
267
323
|
}
|
|
268
324
|
const updatedBuild = { ...currentBuild, ...result.build };
|
|
269
325
|
await saveSelectedBuild({ options, auth, build: updatedBuild });
|
|
@@ -282,7 +338,7 @@ export async function renameBuild(options) {
|
|
|
282
338
|
filesHash: localProject.metadata.filesHash || null,
|
|
283
339
|
});
|
|
284
340
|
}
|
|
285
|
-
|
|
341
|
+
return { buildId, updatedBuild };
|
|
286
342
|
}
|
|
287
343
|
|
|
288
344
|
export async function workspace(options) {
|
|
@@ -337,6 +393,262 @@ export async function branches(options) {
|
|
|
337
393
|
printContributionBranches({ result, rootBuild, options });
|
|
338
394
|
}
|
|
339
395
|
|
|
396
|
+
export async function sendSuggestion(options) {
|
|
397
|
+
const suggestionType = String(options.suggestionAction || "").trim();
|
|
398
|
+
if (suggestionType !== "branch" && suggestionType !== "thumbnail") {
|
|
399
|
+
throw new Error(
|
|
400
|
+
"Usage: lumine suggest branch [--note <message>] | lumine suggest thumbnail",
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
const auth = await resolveAuth(options);
|
|
404
|
+
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
405
|
+
const build = await loadTargetBuildMetadata({ options, auth });
|
|
406
|
+
const { rootBuildId, contributionBuildId } =
|
|
407
|
+
resolveContributionActionBuildIds(build);
|
|
408
|
+
|
|
409
|
+
if (suggestionType === "branch") {
|
|
410
|
+
await notifyBuildOwnerOfContribution({
|
|
411
|
+
options,
|
|
412
|
+
auth,
|
|
413
|
+
rootBuildId,
|
|
414
|
+
contributionBuildId,
|
|
415
|
+
note: options.note,
|
|
416
|
+
});
|
|
417
|
+
console.log(
|
|
418
|
+
`Sent branch #${contributionBuildId} to the owner of Build #${rootBuildId} for review.`,
|
|
419
|
+
);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
await suggestBuildThumbnailToOwner({
|
|
424
|
+
options,
|
|
425
|
+
auth,
|
|
426
|
+
rootBuildId,
|
|
427
|
+
contributionBuildId,
|
|
428
|
+
});
|
|
429
|
+
console.log(
|
|
430
|
+
`Suggested branch #${contributionBuildId}'s thumbnail to the owner of Build #${rootBuildId}.`,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export async function suggestions(options) {
|
|
435
|
+
const auth = await resolveAuth(options);
|
|
436
|
+
const rootBuildId = await resolveSuggestionRootBuildId(options, auth);
|
|
437
|
+
const action = String(options.suggestionAction || "list");
|
|
438
|
+
const requestedSuggestionId = Number(options.suggestionId || 0);
|
|
439
|
+
if (
|
|
440
|
+
action !== "list" &&
|
|
441
|
+
(!Number.isSafeInteger(requestedSuggestionId) || requestedSuggestionId <= 0)
|
|
442
|
+
) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`Pass a suggestion ID: lumine suggestions ${action} <suggestion-id>`,
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
const result = await listBuildSuggestions({
|
|
448
|
+
options,
|
|
449
|
+
auth,
|
|
450
|
+
rootBuildId,
|
|
451
|
+
cursor: action === "list" ? options.cursor : 0,
|
|
452
|
+
suggestionId: action === "list" ? 0 : requestedSuggestionId,
|
|
453
|
+
});
|
|
454
|
+
const items = Array.isArray(result?.suggestions) ? result.suggestions : [];
|
|
455
|
+
|
|
456
|
+
if (action === "list") {
|
|
457
|
+
if (options.json) {
|
|
458
|
+
console.log(
|
|
459
|
+
JSON.stringify({
|
|
460
|
+
buildId: rootBuildId,
|
|
461
|
+
suggestions: items,
|
|
462
|
+
hasMore: Boolean(result?.hasMore),
|
|
463
|
+
nextCursor: Number(result?.nextCursor || 0) || null,
|
|
464
|
+
}),
|
|
465
|
+
);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
printBuildSuggestions({
|
|
469
|
+
rootBuildId,
|
|
470
|
+
suggestions: items,
|
|
471
|
+
hasMore: Boolean(result?.hasMore),
|
|
472
|
+
nextCursor: Number(result?.nextCursor || 0),
|
|
473
|
+
});
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const suggestionId = requestedSuggestionId;
|
|
478
|
+
const suggestion = items.find(
|
|
479
|
+
(item) => Number(item?.id || 0) === suggestionId,
|
|
480
|
+
);
|
|
481
|
+
if (!suggestion) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Open suggestion #${suggestionId} was not found for Build #${rootBuildId}. Run \`lumine suggestions --build ${rootBuildId}\` to refresh the inbox.`,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
487
|
+
const contributionBuildId = Number(suggestion.branchBuildId || 0);
|
|
488
|
+
if (!contributionBuildId) {
|
|
489
|
+
throw new Error(`Suggestion #${suggestionId} has no active branch.`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (action === "merge") {
|
|
493
|
+
if (suggestion.type !== "branch") {
|
|
494
|
+
throw new Error(`Suggestion #${suggestionId} is not a branch suggestion.`);
|
|
495
|
+
}
|
|
496
|
+
const mergeResult = await mergeContributionIntoMain({
|
|
497
|
+
options,
|
|
498
|
+
auth,
|
|
499
|
+
rootBuildId,
|
|
500
|
+
contributionBuildId,
|
|
501
|
+
});
|
|
502
|
+
printContributionActionResult({
|
|
503
|
+
action: "Merged",
|
|
504
|
+
result: mergeResult,
|
|
505
|
+
rootBuildId,
|
|
506
|
+
contributionBuildId,
|
|
507
|
+
});
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (action === "replace-main") {
|
|
512
|
+
if (suggestion.type !== "branch") {
|
|
513
|
+
throw new Error(`Suggestion #${suggestionId} is not a branch suggestion.`);
|
|
514
|
+
}
|
|
515
|
+
const replaceResult = await replaceMainWithContribution({
|
|
516
|
+
options,
|
|
517
|
+
auth,
|
|
518
|
+
rootBuildId,
|
|
519
|
+
contributionBuildId,
|
|
520
|
+
});
|
|
521
|
+
printContributionActionResult({
|
|
522
|
+
action: "Replaced main with",
|
|
523
|
+
result: replaceResult,
|
|
524
|
+
rootBuildId,
|
|
525
|
+
contributionBuildId,
|
|
526
|
+
});
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (action === "adopt-thumbnail") {
|
|
531
|
+
if (suggestion.type !== "thumbnail") {
|
|
532
|
+
throw new Error(
|
|
533
|
+
`Suggestion #${suggestionId} is not a thumbnail suggestion.`,
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
if (suggestion.currentThumbnailUrl && !options.assumeYes) {
|
|
537
|
+
const confirmed = await confirmPrompt(
|
|
538
|
+
`Replace Build #${rootBuildId}'s thumbnail with suggestion #${suggestionId}? [y/N] `,
|
|
539
|
+
);
|
|
540
|
+
if (confirmed === null) {
|
|
541
|
+
console.log("Not a TTY — re-run with --yes to replace the thumbnail.");
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (!confirmed) {
|
|
545
|
+
console.log("Aborted. Thumbnail unchanged.");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const adoptResult = await adoptBuildThumbnailSuggestion({
|
|
550
|
+
options,
|
|
551
|
+
auth,
|
|
552
|
+
rootBuildId,
|
|
553
|
+
contributionBuildId,
|
|
554
|
+
suggestionMessageId: suggestionId,
|
|
555
|
+
thumbnailUrl: suggestion.suggestedThumbnailUrl,
|
|
556
|
+
});
|
|
557
|
+
const canonicalThumbnailUrl = String(
|
|
558
|
+
adoptResult?.build?.thumbnailUrl || "",
|
|
559
|
+
);
|
|
560
|
+
console.log(
|
|
561
|
+
`Applied thumbnail suggestion #${suggestionId} to Build #${rootBuildId}.`,
|
|
562
|
+
);
|
|
563
|
+
if (canonicalThumbnailUrl) console.log(` ${canonicalThumbnailUrl}`);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
throw new Error(
|
|
568
|
+
"Usage: lumine suggestions [build] | lumine suggestions merge|replace-main|adopt-thumbnail <suggestion-id> [--build <id>]",
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async function resolveSuggestionRootBuildId(options, auth) {
|
|
573
|
+
const requestedBuildId = options.buildIdFlag
|
|
574
|
+
? resolveRequiredBuildId(options.buildIdFlag)
|
|
575
|
+
: await resolveRequiredBuildIdOrSelected(options, auth);
|
|
576
|
+
const build = await loadBuildMetadata({
|
|
577
|
+
options,
|
|
578
|
+
auth,
|
|
579
|
+
buildId: requestedBuildId,
|
|
580
|
+
});
|
|
581
|
+
return (
|
|
582
|
+
Number(build?.contributionRootBuildId || 0) || Number(build?.id || 0)
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function printBuildSuggestions({
|
|
587
|
+
rootBuildId,
|
|
588
|
+
suggestions,
|
|
589
|
+
hasMore = false,
|
|
590
|
+
nextCursor = 0,
|
|
591
|
+
}) {
|
|
592
|
+
console.log(`Open suggestions for Build #${rootBuildId}:`);
|
|
593
|
+
if (!suggestions.length) {
|
|
594
|
+
console.log(
|
|
595
|
+
hasMore
|
|
596
|
+
? "No open branch or thumbnail suggestions on this page."
|
|
597
|
+
: "No open branch or thumbnail suggestions.",
|
|
598
|
+
);
|
|
599
|
+
if (hasMore && nextCursor > 0) {
|
|
600
|
+
console.log(
|
|
601
|
+
`Next page: lumine suggestions --build ${rootBuildId} --cursor ${nextCursor}`,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
for (const suggestion of suggestions) {
|
|
607
|
+
const suggestionId = Number(suggestion.id || 0);
|
|
608
|
+
const branchNumber = Number(suggestion.branchNumber || 0);
|
|
609
|
+
const branchLabel = branchNumber
|
|
610
|
+
? `branch ${branchNumber}`
|
|
611
|
+
: `branch #${suggestion.branchBuildId}`;
|
|
612
|
+
const contributor = suggestion.contributorUsername || "a contributor";
|
|
613
|
+
const createdAt = formatVersionTimestamp(suggestion.createdAt);
|
|
614
|
+
if (suggestion.type === "branch") {
|
|
615
|
+
console.log(
|
|
616
|
+
`[#${suggestionId}] ${contributor} submitted ${branchLabel} (${createdAt})`,
|
|
617
|
+
);
|
|
618
|
+
if (suggestion.note) console.log(` ${suggestion.note}`);
|
|
619
|
+
const summary = suggestion.diffSummary || {};
|
|
620
|
+
console.log(
|
|
621
|
+
` files: ${Number(summary.total || suggestion.changedFiles?.length || 0)} changed`,
|
|
622
|
+
);
|
|
623
|
+
if (suggestion.hasNewerWorkSinceSubmission) {
|
|
624
|
+
console.log(" note: the branch changed after this suggestion");
|
|
625
|
+
}
|
|
626
|
+
console.log(
|
|
627
|
+
` merge: lumine suggestions merge ${suggestionId} --build ${rootBuildId}`,
|
|
628
|
+
);
|
|
629
|
+
console.log(
|
|
630
|
+
` replace: lumine suggestions replace-main ${suggestionId} --build ${rootBuildId}`,
|
|
631
|
+
);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
console.log(
|
|
635
|
+
`[#${suggestionId}] ${contributor} suggested a thumbnail from ${branchLabel} (${createdAt})`,
|
|
636
|
+
);
|
|
637
|
+
console.log(` ${suggestion.suggestedThumbnailUrl}`);
|
|
638
|
+
if (suggestion.hasNewerThumbnailSinceSuggestion) {
|
|
639
|
+
console.log(" note: the branch has a newer thumbnail now");
|
|
640
|
+
}
|
|
641
|
+
console.log(
|
|
642
|
+
` apply: lumine suggestions adopt-thumbnail ${suggestionId} --build ${rootBuildId}`,
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
if (hasMore && nextCursor > 0) {
|
|
646
|
+
console.log(
|
|
647
|
+
`Next page: lumine suggestions --build ${rootBuildId} --cursor ${nextCursor}`,
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
340
652
|
export async function explore(options) {
|
|
341
653
|
const auth = await resolveAuth(options);
|
|
342
654
|
const builds = await listOpenSourceBuilds({ options, auth });
|
|
@@ -1557,9 +1869,15 @@ export function printPullResult(result) {
|
|
|
1557
1869
|
);
|
|
1558
1870
|
console.log('Save after edits: lumine save --summary "Describe the change"');
|
|
1559
1871
|
if (isContributionBranch(build) && build.canPublish === false) {
|
|
1560
|
-
console.log(
|
|
1872
|
+
console.log(
|
|
1873
|
+
'Notify the owner when ready: lumine suggest branch "Ready for review"',
|
|
1874
|
+
);
|
|
1875
|
+
console.log(
|
|
1876
|
+
"Offer this branch's thumbnail: lumine suggest thumbnail",
|
|
1877
|
+
);
|
|
1561
1878
|
} else {
|
|
1562
1879
|
console.log("Run `lumine check` or `lumine launch --save` when ready.");
|
|
1880
|
+
console.log("Review team nudges: lumine suggestions");
|
|
1563
1881
|
}
|
|
1564
1882
|
}
|
|
1565
1883
|
|
|
@@ -1616,12 +1934,16 @@ export function printSaveResult({ result, build, dir, files }) {
|
|
|
1616
1934
|
console.log(`Release status: ${releaseState}`);
|
|
1617
1935
|
if (isContributionBranch(build) && build.canPublish === false) {
|
|
1618
1936
|
console.log(
|
|
1619
|
-
|
|
1937
|
+
'Next: notify the project owner with `lumine suggest branch "Ready for review"`.',
|
|
1938
|
+
);
|
|
1939
|
+
console.log(
|
|
1940
|
+
"To offer this branch's thumbnail, run `lumine suggest thumbnail`.",
|
|
1620
1941
|
);
|
|
1621
1942
|
} else {
|
|
1622
1943
|
console.log(
|
|
1623
1944
|
"Next: run `lumine launch` to publish, or `lumine save --publish` next time.",
|
|
1624
1945
|
);
|
|
1946
|
+
console.log("Team suggestions: run `lumine suggestions` to review them.");
|
|
1625
1947
|
}
|
|
1626
1948
|
}
|
|
1627
1949
|
|
|
@@ -1772,6 +2094,8 @@ export function parseArgs(args) {
|
|
|
1772
2094
|
"json",
|
|
1773
2095
|
"keepAssets",
|
|
1774
2096
|
"noBrowser",
|
|
2097
|
+
"includeComments",
|
|
2098
|
+
"anyoneCanReward",
|
|
1775
2099
|
]);
|
|
1776
2100
|
|
|
1777
2101
|
for (let i = 0; i < rest.length; i += 1) {
|
|
@@ -1791,14 +2115,76 @@ export function parseArgs(args) {
|
|
|
1791
2115
|
} else if (booleanFlags.has(camelKey)) {
|
|
1792
2116
|
raw[camelKey] = true;
|
|
1793
2117
|
} else {
|
|
1794
|
-
|
|
2118
|
+
const nextValue = rest[i + 1];
|
|
2119
|
+
if (
|
|
2120
|
+
nextValue === undefined ||
|
|
2121
|
+
nextValue === "-h" ||
|
|
2122
|
+
nextValue.startsWith("--")
|
|
2123
|
+
) {
|
|
2124
|
+
throw new Error(`Missing value for --${key}.`);
|
|
2125
|
+
}
|
|
2126
|
+
raw[camelKey] = nextValue;
|
|
1795
2127
|
i += 1;
|
|
1796
2128
|
}
|
|
1797
2129
|
}
|
|
1798
2130
|
|
|
2131
|
+
const suggestionInboxActions = new Set([
|
|
2132
|
+
"list",
|
|
2133
|
+
"merge",
|
|
2134
|
+
"replace-main",
|
|
2135
|
+
"adopt-thumbnail",
|
|
2136
|
+
]);
|
|
2137
|
+
const suggestionAction =
|
|
2138
|
+
command === "suggest"
|
|
2139
|
+
? String(positional[0] || "")
|
|
2140
|
+
: command === "suggestions" &&
|
|
2141
|
+
suggestionInboxActions.has(String(positional[0] || ""))
|
|
2142
|
+
? String(positional[0])
|
|
2143
|
+
: "list";
|
|
2144
|
+
const suggestionListTarget =
|
|
2145
|
+
command === "suggestions" && suggestionAction === "list"
|
|
2146
|
+
? String(
|
|
2147
|
+
positional[0] === "list" ? positional[1] || "" : positional[0] || "",
|
|
2148
|
+
)
|
|
2149
|
+
: "";
|
|
2150
|
+
|
|
1799
2151
|
return {
|
|
1800
2152
|
command,
|
|
1801
2153
|
positional,
|
|
2154
|
+
suggestionAction,
|
|
2155
|
+
suggestionId:
|
|
2156
|
+
command === "suggestions" && suggestionAction !== "list"
|
|
2157
|
+
? String(positional[1] || "")
|
|
2158
|
+
: "",
|
|
2159
|
+
note:
|
|
2160
|
+
String(
|
|
2161
|
+
raw.note ||
|
|
2162
|
+
(command === "suggest" && suggestionAction === "branch"
|
|
2163
|
+
? positional.slice(1).join(" ")
|
|
2164
|
+
: ""),
|
|
2165
|
+
).trim() || "",
|
|
2166
|
+
cursor: Math.max(0, Math.floor(Number(raw.cursor) || 0)),
|
|
2167
|
+
adminCursor: raw.cursor ? String(raw.cursor) : "",
|
|
2168
|
+
adminAfter: raw.after ? String(raw.after) : "",
|
|
2169
|
+
adminEffort: raw.effort ? String(raw.effort) : "",
|
|
2170
|
+
adminLevel: raw.level ? String(raw.level) : "",
|
|
2171
|
+
adminIds: raw.subjectIds
|
|
2172
|
+
? String(raw.subjectIds)
|
|
2173
|
+
: raw.ids
|
|
2174
|
+
? String(raw.ids)
|
|
2175
|
+
: "",
|
|
2176
|
+
adminType: raw.type ? String(raw.type) : "",
|
|
2177
|
+
adminKind: raw.kind ? String(raw.kind) : "",
|
|
2178
|
+
adminIdentity: raw.identity ? String(raw.identity) : "",
|
|
2179
|
+
commentMode: raw.commentMode ? String(raw.commentMode) : "",
|
|
2180
|
+
runKey: raw.runKey ? String(raw.runKey) : "",
|
|
2181
|
+
idempotencyKey: raw.idempotencyKey ? String(raw.idempotencyKey) : "",
|
|
2182
|
+
draftId: raw.draftId ? String(raw.draftId) : "",
|
|
2183
|
+
twinkles: raw.twinkles ? String(raw.twinkles) : "",
|
|
2184
|
+
adminReason: raw.reason ? String(raw.reason) : "",
|
|
2185
|
+
rewardTwinkles: raw.rewardTwinkles ? String(raw.rewardTwinkles) : "",
|
|
2186
|
+
includeComments: parseBoolean(raw.includeComments, false),
|
|
2187
|
+
anyoneCanReward: parseBoolean(raw.anyoneCanReward, false),
|
|
1802
2188
|
repeat: Math.min(Math.max(Math.floor(Number(raw.repeat) || 1), 1), 20),
|
|
1803
2189
|
allowWrite: parseBoolean(raw.allowWrite, false),
|
|
1804
2190
|
assumeYes: parseBoolean(raw.yes, false),
|
|
@@ -1815,7 +2201,13 @@ export function parseArgs(args) {
|
|
|
1815
2201
|
target:
|
|
1816
2202
|
raw.url ||
|
|
1817
2203
|
raw.target ||
|
|
1818
|
-
(command === "rename"
|
|
2204
|
+
(command === "rename" ||
|
|
2205
|
+
command === "describe" ||
|
|
2206
|
+
command === "suggest"
|
|
2207
|
+
? ""
|
|
2208
|
+
: command === "suggestions"
|
|
2209
|
+
? suggestionListTarget
|
|
2210
|
+
: positional[0] || ""),
|
|
1819
2211
|
title:
|
|
1820
2212
|
String(
|
|
1821
2213
|
raw.title ||
|
|
@@ -1825,11 +2217,12 @@ export function parseArgs(args) {
|
|
|
1825
2217
|
).trim() || "",
|
|
1826
2218
|
description: Object.prototype.hasOwnProperty.call(raw, "description")
|
|
1827
2219
|
? String(raw.description || "").trim()
|
|
1828
|
-
:
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
2220
|
+
: command === "describe" && positional.length > 0
|
|
2221
|
+
? positional.join(" ").trim()
|
|
2222
|
+
: null,
|
|
2223
|
+
descriptionProvided:
|
|
2224
|
+
Object.prototype.hasOwnProperty.call(raw, "description") ||
|
|
2225
|
+
(command === "describe" && positional.length > 0),
|
|
1833
2226
|
noDescription: parseBoolean(raw.noDescription, false),
|
|
1834
2227
|
searchQuery:
|
|
1835
2228
|
String(
|
|
@@ -2009,6 +2402,32 @@ export async function resolveNewBuildDescription(options) {
|
|
|
2009
2402
|
}
|
|
2010
2403
|
}
|
|
2011
2404
|
|
|
2405
|
+
export async function resolveBuildDescriptionUpdate(options) {
|
|
2406
|
+
if (options.noDescription && options.descriptionProvided) {
|
|
2407
|
+
throw new Error(
|
|
2408
|
+
"Pass either a description or `--no-description`, not both.",
|
|
2409
|
+
);
|
|
2410
|
+
}
|
|
2411
|
+
if (options.noDescription) return null;
|
|
2412
|
+
if (options.descriptionProvided) {
|
|
2413
|
+
return String(options.description || "").trim() || null;
|
|
2414
|
+
}
|
|
2415
|
+
if (!input.isTTY || !output.isTTY) {
|
|
2416
|
+
throw new Error(
|
|
2417
|
+
'Pass a description: `lumine describe "What this app does"`, or clear it with `lumine describe --no-description`.',
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
const rl = readline.createInterface({ input, output });
|
|
2421
|
+
try {
|
|
2422
|
+
const answer = await rl.question(
|
|
2423
|
+
"Build description (leave blank to clear): ",
|
|
2424
|
+
);
|
|
2425
|
+
return String(answer || "").trim() || null;
|
|
2426
|
+
} finally {
|
|
2427
|
+
rl.close();
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2012
2431
|
export async function resolveBuildReferenceBuildId({
|
|
2013
2432
|
options,
|
|
2014
2433
|
auth,
|
|
@@ -2042,8 +2461,15 @@ export function printHelp() {
|
|
|
2042
2461
|
lumine logout
|
|
2043
2462
|
lumine new [title]
|
|
2044
2463
|
lumine rename [title] [--target <twinkle-build-url-or-id>]
|
|
2464
|
+
lumine describe [description] [--target <twinkle-build-url-or-id>]
|
|
2045
2465
|
lumine projects
|
|
2046
2466
|
lumine branches [twinkle-build-url-or-id] [--limit <n>]
|
|
2467
|
+
lumine suggest branch [message] [--target <twinkle-branch-url>]
|
|
2468
|
+
lumine suggest thumbnail [--target <twinkle-branch-url>]
|
|
2469
|
+
lumine suggestions [twinkle-build-url-or-id]
|
|
2470
|
+
lumine suggestions merge <suggestion-id> [--build <id>]
|
|
2471
|
+
lumine suggestions replace-main <suggestion-id> [--build <id>]
|
|
2472
|
+
lumine suggestions adopt-thumbnail <suggestion-id> [--build <id>] [--yes]
|
|
2047
2473
|
lumine explore [search terms]
|
|
2048
2474
|
lumine select [twinkle-build-url]
|
|
2049
2475
|
lumine pull [twinkle-build-url]
|
|
@@ -2071,6 +2497,21 @@ export function printHelp() {
|
|
|
2071
2497
|
lumine thumbnail capture [--out <file>]
|
|
2072
2498
|
lumine thumbnail generate ["<prompt>"] --model <gpt-image-2|nano-banana>
|
|
2073
2499
|
lumine doctor runtime-assets
|
|
2500
|
+
lumine admin identity list|status|use <zero|ciel|auto> [--json]
|
|
2501
|
+
lumine admin daily-run start [--identity zero|ciel|auto] [--comment-mode off|draft|post] [--run-key <key>] [--json]
|
|
2502
|
+
lumine admin daily-run status|complete|fail [--reason <text>] [--json]
|
|
2503
|
+
lumine admin recommendations list [--cursor <cursor>] [--json]
|
|
2504
|
+
lumine admin subjects candidates [--after <date>] [--effort unassigned] [--cursor <cursor>] [--json]
|
|
2505
|
+
lumine admin subject get|comments|reveal <subject-url-or-id> [--cursor <cursor>] [--json]
|
|
2506
|
+
lumine admin subject effort set <subject-id> --level <1|2|3> [--json]
|
|
2507
|
+
lumine admin subject creator set-made-by-poster <subject-id> [--json]
|
|
2508
|
+
lumine admin subject feature|unfeature <subject-id> [--json]
|
|
2509
|
+
lumine admin featured list|reorder --subject-ids <id,id,...> [--json]
|
|
2510
|
+
lumine admin post get|comments <target> [--type subject|comment|aiStory|dailyReflection] [--cursor <cursor>] [--json]
|
|
2511
|
+
lumine admin post recommend <target> [--type subject|comment|aiStory|dailyReflection] [--anyone-can-reward] [--reward-twinkles 3] [--json]
|
|
2512
|
+
lumine admin post reward <target> [--type subject|comment|aiStory|dailyReflection] --twinkles 3 [--json]
|
|
2513
|
+
lumine admin comment draft <subject-id> [--identity zero|ciel|auto] [--json]
|
|
2514
|
+
lumine admin comment post --draft-id <id> [--json]
|
|
2074
2515
|
|
|
2075
2516
|
Examples:
|
|
2076
2517
|
npx @stage5/lumine@latest
|
|
@@ -2078,8 +2519,15 @@ Examples:
|
|
|
2078
2519
|
npx @stage5/lumine@latest new "Daily Reflection App"
|
|
2079
2520
|
npx @stage5/lumine@latest rename "My New Build Title"
|
|
2080
2521
|
npx @stage5/lumine@latest rename "New Title" --target 123
|
|
2522
|
+
npx @stage5/lumine@latest describe "A welcoming place to build together"
|
|
2523
|
+
npx @stage5/lumine@latest describe --no-description --target 123
|
|
2081
2524
|
npx @stage5/lumine@latest new --title "Daily Reflection App" --description "Private journal with streaks"
|
|
2082
2525
|
npx @stage5/lumine@latest branches 884
|
|
2526
|
+
npx @stage5/lumine@latest suggest branch "Ready for review"
|
|
2527
|
+
npx @stage5/lumine@latest suggest thumbnail
|
|
2528
|
+
npx @stage5/lumine@latest suggestions 884
|
|
2529
|
+
npx @stage5/lumine@latest suggestions merge 12345 --build 884
|
|
2530
|
+
npx @stage5/lumine@latest suggestions adopt-thumbnail 12346 --build 884 --yes
|
|
2083
2531
|
npx @stage5/lumine@latest explore --sort forks
|
|
2084
2532
|
npx @stage5/lumine@latest reference https://www.twin-kle.com/app/123
|
|
2085
2533
|
npx @stage5/lumine@latest fork https://www.twin-kle.com/app/123
|
|
@@ -2105,6 +2553,11 @@ Examples:
|
|
|
2105
2553
|
npx @stage5/lumine@latest thumbnail capture --out capture.png
|
|
2106
2554
|
npx @stage5/lumine@latest thumbnail generate --model gpt-image-2 --yes
|
|
2107
2555
|
npx @stage5/lumine@latest doctor runtime-assets --build 917 --json
|
|
2556
|
+
npx @stage5/lumine@latest admin daily-run start --identity auto --comment-mode off --json
|
|
2557
|
+
npx @stage5/lumine@latest admin recommendations list --json
|
|
2558
|
+
npx @stage5/lumine@latest admin subjects candidates --effort unassigned --json
|
|
2559
|
+
npx @stage5/lumine@latest admin subject get 123 --include-comments --json
|
|
2560
|
+
npx @stage5/lumine@latest admin post recommend comment:456 --anyone-can-reward --reward-twinkles 3 --json
|
|
2108
2561
|
|
|
2109
2562
|
Options:
|
|
2110
2563
|
--api-url <url> Twinkle API origin
|
|
@@ -2113,13 +2566,30 @@ Options:
|
|
|
2113
2566
|
--auth-file <path> Saved login path
|
|
2114
2567
|
--auth-token <token> Override saved login
|
|
2115
2568
|
--dir <path> Directory for pulled project files
|
|
2116
|
-
--target <build> Explicit Build URL or ID for rename
|
|
2569
|
+
--target <build> Explicit Build URL or ID for rename/describe
|
|
2117
2570
|
--main With pull/versions/restore: target the team project's main
|
|
2118
2571
|
--version <n> With pull: read-only checkout of previous save v<n>
|
|
2119
2572
|
--title <text> Build title for new/rename
|
|
2120
|
-
--description <text>
|
|
2121
|
-
--no-description Skip
|
|
2573
|
+
--description <text> Build description for new/describe
|
|
2574
|
+
--no-description Skip New description or clear with describe
|
|
2122
2575
|
--summary <text> Save summary
|
|
2576
|
+
--note <text> Message attached to a branch suggestion
|
|
2577
|
+
--cursor <id> Continue an owner suggestion inbox listing
|
|
2578
|
+
--after <date> Admin subjects: inclusive Unix/ISO creation boundary
|
|
2579
|
+
--effort unassigned Admin subjects: show only unassigned effort
|
|
2580
|
+
--identity <mode> Admin identity: zero, ciel, or auto
|
|
2581
|
+
--comment-mode <mode> Admin run comments: off, draft, or post
|
|
2582
|
+
--run-key <key> Idempotency key for an admin daily run
|
|
2583
|
+
--idempotency-key <k> Stable retry key for one admin mutation
|
|
2584
|
+
--level <1|2|3> Admin subject effort level
|
|
2585
|
+
--subject-ids <ids> Complete ordered Featured subject IDs
|
|
2586
|
+
--type <type> Admin target: subject, comment, aiStory, or dailyReflection
|
|
2587
|
+
--kind recommend Admin recommendation queue kind
|
|
2588
|
+
--anyone-can-reward Enable canonical reward eligibility
|
|
2589
|
+
--reward-twinkles 3 Pair a recommendation with exactly 3 Twinkles
|
|
2590
|
+
--twinkles 3 Give exactly 3 Twinkles through the normal economy
|
|
2591
|
+
--draft-id <id> Canonical delegated comment draft ID
|
|
2592
|
+
--reason <text> Reason when marking an admin run failed
|
|
2123
2593
|
--force Overwrite server files even if this workspace is stale or missing filesHash
|
|
2124
2594
|
--search <text> Search public open-source Builds
|
|
2125
2595
|
--sort <sort> Sort open-source Builds: forks, popular, recent
|
|
@@ -2128,12 +2598,12 @@ Options:
|
|
|
2128
2598
|
--limit <number> Number of projects to show
|
|
2129
2599
|
--no-update-check Skip the npm latest-version check
|
|
2130
2600
|
--no-open Print the approval URL without opening a browser
|
|
2131
|
-
--build <id> Build id for sdk/assets/thumbnail calls outside a workspace
|
|
2601
|
+
--build <id> Build id for sdk/assets/thumbnail/suggestion calls outside a workspace
|
|
2132
2602
|
--repeat <n> Repeat an sdk call (1-20) and print latency stats
|
|
2133
2603
|
--allow-write Permit sdk methods that mutate app data
|
|
2134
2604
|
--path <api/...> Call an sdk endpoint not in the curated list
|
|
2135
2605
|
--scopes <a,b> Override requested build API token scopes
|
|
2136
|
-
--json Print machine-readable output
|
|
2606
|
+
--json Print machine-readable output where supported
|
|
2137
2607
|
--keep-assets Keep doctor probe assets instead of deleting them
|
|
2138
2608
|
--no-browser Skip doctor browser probes
|
|
2139
2609
|
--model <model> Image model for generate: gpt-image-2 or nano-banana (required, no default)
|