@stage5/lumine 0.1.6 → 0.1.7
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/bin/lumine.js +283 -28
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +5 -5
package/bin/lumine.js
CHANGED
|
@@ -125,6 +125,9 @@ const COMMANDS = new Set([
|
|
|
125
125
|
"pull",
|
|
126
126
|
"reference",
|
|
127
127
|
"fork",
|
|
128
|
+
"diff",
|
|
129
|
+
"merge",
|
|
130
|
+
"replace-main",
|
|
128
131
|
"save",
|
|
129
132
|
"push",
|
|
130
133
|
"check",
|
|
@@ -188,6 +191,18 @@ async function main() {
|
|
|
188
191
|
await fork(options);
|
|
189
192
|
return;
|
|
190
193
|
}
|
|
194
|
+
if (options.command === "diff") {
|
|
195
|
+
await diff(options);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (options.command === "merge") {
|
|
199
|
+
await mergeBranch(options);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (options.command === "replace-main") {
|
|
203
|
+
await replaceMainWithBranch(options);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
191
206
|
if (options.command === "save" || options.command === "push") {
|
|
192
207
|
await save(options);
|
|
193
208
|
return;
|
|
@@ -302,11 +317,7 @@ async function whoami(options) {
|
|
|
302
317
|
async function workspace(options) {
|
|
303
318
|
const auth = await ensureAuth(options);
|
|
304
319
|
const selectedBuild = options.target
|
|
305
|
-
? await
|
|
306
|
-
options,
|
|
307
|
-
auth,
|
|
308
|
-
buildId: resolveRequiredBuildId(options.target),
|
|
309
|
-
})
|
|
320
|
+
? await loadTargetBuildMetadata({ options, auth })
|
|
310
321
|
: await chooseProject({
|
|
311
322
|
builds: await listBuilds({ options, auth }),
|
|
312
323
|
});
|
|
@@ -337,11 +348,7 @@ async function explore(options) {
|
|
|
337
348
|
async function selectProject(options) {
|
|
338
349
|
const auth = await resolveAuth(options);
|
|
339
350
|
const selectedBuild = options.target
|
|
340
|
-
? await
|
|
341
|
-
options,
|
|
342
|
-
auth,
|
|
343
|
-
buildId: resolveRequiredBuildId(options.target),
|
|
344
|
-
})
|
|
351
|
+
? await loadTargetBuildMetadata({ options, auth })
|
|
345
352
|
: await chooseProject({
|
|
346
353
|
builds: await listBuilds({ options, auth }),
|
|
347
354
|
});
|
|
@@ -376,7 +383,7 @@ async function pull(options) {
|
|
|
376
383
|
|
|
377
384
|
async function reference(options) {
|
|
378
385
|
const auth = await resolveAuth(options);
|
|
379
|
-
const buildId =
|
|
386
|
+
const buildId = await resolveRequiredBuildIdOrSelected(options, auth);
|
|
380
387
|
const result = await pullReferenceFiles({ options, auth, buildId });
|
|
381
388
|
printReferenceResult(result);
|
|
382
389
|
}
|
|
@@ -384,7 +391,7 @@ async function reference(options) {
|
|
|
384
391
|
async function fork(options) {
|
|
385
392
|
const auth = await resolveAuth(options);
|
|
386
393
|
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
387
|
-
const buildId =
|
|
394
|
+
const buildId = await resolveRequiredBuildIdOrSelected(options, auth);
|
|
388
395
|
const forkResult = await forkBuild({ options, auth, buildId });
|
|
389
396
|
const forkedBuildId = Number(forkResult.build?.id || 0);
|
|
390
397
|
if (!forkedBuildId) {
|
|
@@ -399,6 +406,60 @@ async function fork(options) {
|
|
|
399
406
|
printForkResult({ forkResult, pullResult: result });
|
|
400
407
|
}
|
|
401
408
|
|
|
409
|
+
async function diff(options) {
|
|
410
|
+
const auth = await resolveAuth(options);
|
|
411
|
+
const build = await loadTargetBuildMetadata({ options, auth });
|
|
412
|
+
const { rootBuildId, contributionBuildId } =
|
|
413
|
+
resolveContributionActionBuildIds(build);
|
|
414
|
+
const result = await loadContributionDiff({
|
|
415
|
+
options,
|
|
416
|
+
auth,
|
|
417
|
+
rootBuildId,
|
|
418
|
+
contributionBuildId,
|
|
419
|
+
});
|
|
420
|
+
printContributionDiff({ result, build });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function mergeBranch(options) {
|
|
424
|
+
const auth = await resolveAuth(options);
|
|
425
|
+
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
426
|
+
const build = await loadTargetBuildMetadata({ options, auth });
|
|
427
|
+
const { rootBuildId, contributionBuildId } =
|
|
428
|
+
resolveContributionActionBuildIds(build);
|
|
429
|
+
const result = await mergeContributionIntoMain({
|
|
430
|
+
options,
|
|
431
|
+
auth,
|
|
432
|
+
rootBuildId,
|
|
433
|
+
contributionBuildId,
|
|
434
|
+
});
|
|
435
|
+
printContributionActionResult({
|
|
436
|
+
action: "Merged",
|
|
437
|
+
result,
|
|
438
|
+
rootBuildId,
|
|
439
|
+
contributionBuildId,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function replaceMainWithBranch(options) {
|
|
444
|
+
const auth = await resolveAuth(options);
|
|
445
|
+
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
446
|
+
const build = await loadTargetBuildMetadata({ options, auth });
|
|
447
|
+
const { rootBuildId, contributionBuildId } =
|
|
448
|
+
resolveContributionActionBuildIds(build);
|
|
449
|
+
const result = await replaceMainWithContribution({
|
|
450
|
+
options,
|
|
451
|
+
auth,
|
|
452
|
+
rootBuildId,
|
|
453
|
+
contributionBuildId,
|
|
454
|
+
});
|
|
455
|
+
printContributionActionResult({
|
|
456
|
+
action: "Replaced main with",
|
|
457
|
+
result,
|
|
458
|
+
rootBuildId,
|
|
459
|
+
contributionBuildId,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
402
463
|
async function save(options) {
|
|
403
464
|
const auth = await resolveAuth(options);
|
|
404
465
|
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
@@ -415,6 +476,11 @@ async function save(options) {
|
|
|
415
476
|
buildId,
|
|
416
477
|
localProject,
|
|
417
478
|
});
|
|
479
|
+
if (build?.canWrite === false) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
"This checkout is read-only for the current CLI login. Pull or diff it for review, then merge or replace main from the project owner workflow.",
|
|
482
|
+
);
|
|
483
|
+
}
|
|
418
484
|
buildId = Number(build?.id || buildId);
|
|
419
485
|
const dir = resolveProjectDirForSave({ options, localProject });
|
|
420
486
|
const files = await collectProjectFiles(dir);
|
|
@@ -566,6 +632,11 @@ async function publishBuild({ options, buildId, auth }) {
|
|
|
566
632
|
function printCheck(result) {
|
|
567
633
|
const checks = result.checks || {};
|
|
568
634
|
console.log(`Launch check: ${result.ok ? "ok" : "fail"}`);
|
|
635
|
+
if (checks.canonicalBuild) {
|
|
636
|
+
console.log(
|
|
637
|
+
`- canonical build: ${checks.canonicalBuild.ok ? "ok" : "fail"}`,
|
|
638
|
+
);
|
|
639
|
+
}
|
|
569
640
|
console.log(
|
|
570
641
|
`- project files: ${checks.projectFiles?.ok ? "ok" : "fail"} ` +
|
|
571
642
|
`files=${checks.projectFiles?.fileCount ?? 0}`,
|
|
@@ -645,6 +716,11 @@ async function loadBuildMetadata({ options, auth, buildId }) {
|
|
|
645
716
|
return result.build;
|
|
646
717
|
}
|
|
647
718
|
|
|
719
|
+
async function loadTargetBuildMetadata({ options, auth }) {
|
|
720
|
+
const buildId = await resolveRequiredBuildIdOrSelected(options, auth);
|
|
721
|
+
return await loadBuildMetadata({ options, auth, buildId });
|
|
722
|
+
}
|
|
723
|
+
|
|
648
724
|
async function loadOpenSourceBuildFiles({
|
|
649
725
|
options,
|
|
650
726
|
auth,
|
|
@@ -670,6 +746,24 @@ async function loadBuildFiles({ options, auth, buildId, includeContent }) {
|
|
|
670
746
|
});
|
|
671
747
|
}
|
|
672
748
|
|
|
749
|
+
async function resolveBranchBuild({
|
|
750
|
+
options,
|
|
751
|
+
auth,
|
|
752
|
+
rootBuildId,
|
|
753
|
+
branchNumber,
|
|
754
|
+
}) {
|
|
755
|
+
const result = await requestJson({
|
|
756
|
+
url: `${options.apiUrl}/cli/build/${rootBuildId}/branches/${branchNumber}`,
|
|
757
|
+
authToken: auth.token,
|
|
758
|
+
timeoutMs: options.timeoutMs,
|
|
759
|
+
});
|
|
760
|
+
const branchBuildId = Number(result.build?.id || 0);
|
|
761
|
+
if (!branchBuildId) {
|
|
762
|
+
throw new Error(`Branch ${branchNumber} for Build ${rootBuildId} could not be loaded.`);
|
|
763
|
+
}
|
|
764
|
+
return result.build;
|
|
765
|
+
}
|
|
766
|
+
|
|
673
767
|
async function forkBuild({ options, auth, buildId }) {
|
|
674
768
|
return await requestJson({
|
|
675
769
|
method: "POST",
|
|
@@ -694,6 +788,49 @@ async function saveProjectFiles({ options, auth, buildId, files, summary }) {
|
|
|
694
788
|
});
|
|
695
789
|
}
|
|
696
790
|
|
|
791
|
+
async function loadContributionDiff({
|
|
792
|
+
options,
|
|
793
|
+
auth,
|
|
794
|
+
rootBuildId,
|
|
795
|
+
contributionBuildId,
|
|
796
|
+
}) {
|
|
797
|
+
return await requestJson({
|
|
798
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}`,
|
|
799
|
+
authToken: auth.token,
|
|
800
|
+
timeoutMs: options.timeoutMs,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async function mergeContributionIntoMain({
|
|
805
|
+
options,
|
|
806
|
+
auth,
|
|
807
|
+
rootBuildId,
|
|
808
|
+
contributionBuildId,
|
|
809
|
+
}) {
|
|
810
|
+
return await requestJson({
|
|
811
|
+
method: "POST",
|
|
812
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}/merge`,
|
|
813
|
+
authToken: auth.token,
|
|
814
|
+
body: {},
|
|
815
|
+
timeoutMs: options.timeoutMs,
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
async function replaceMainWithContribution({
|
|
820
|
+
options,
|
|
821
|
+
auth,
|
|
822
|
+
rootBuildId,
|
|
823
|
+
contributionBuildId,
|
|
824
|
+
}) {
|
|
825
|
+
return await requestJson({
|
|
826
|
+
method: "POST",
|
|
827
|
+
url: `${options.apiUrl}/build/${rootBuildId}/contributions/${contributionBuildId}/replace-main`,
|
|
828
|
+
authToken: auth.token,
|
|
829
|
+
body: {},
|
|
830
|
+
timeoutMs: options.timeoutMs,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
697
834
|
async function resolveBuildForSave({ options, auth, buildId, localProject }) {
|
|
698
835
|
const localBuild = localProject?.metadata?.build;
|
|
699
836
|
const localBuildId =
|
|
@@ -1226,6 +1363,11 @@ function assertLocalProjectCanBeSaved(localProject) {
|
|
|
1226
1363
|
`This is a read-only Lumine reference${sourceBuildId ? ` for Build ${sourceBuildId}` : ""}. Run \`lumine fork${sourceBuildId ? ` ${sourceBuildId}` : ""}\` to create an editable workspace.`,
|
|
1227
1364
|
);
|
|
1228
1365
|
}
|
|
1366
|
+
if (metadata.build?.canWrite === false) {
|
|
1367
|
+
throw new Error(
|
|
1368
|
+
"This Lumine checkout is read-only for the current CLI login. Pull or diff it for review; project-owner branch edits must go through merge or replace-main.",
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1229
1371
|
}
|
|
1230
1372
|
|
|
1231
1373
|
function isReadOnlyReferenceMetadata(metadata) {
|
|
@@ -1344,6 +1486,17 @@ function isContributionBranch(build) {
|
|
|
1344
1486
|
);
|
|
1345
1487
|
}
|
|
1346
1488
|
|
|
1489
|
+
function resolveContributionActionBuildIds(build) {
|
|
1490
|
+
const contributionBuildId = Number(build?.id || 0);
|
|
1491
|
+
const rootBuildId = Number(build?.contributionRootBuildId || 0);
|
|
1492
|
+
if (!contributionBuildId || !rootBuildId || !isContributionBranch(build)) {
|
|
1493
|
+
throw new Error(
|
|
1494
|
+
"Pass a branch URL such as https://www.twin-kle.com/build/884/4, or run this from a pulled branch workspace.",
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
return { rootBuildId, contributionBuildId };
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1347
1500
|
function printPullResult(result) {
|
|
1348
1501
|
const build = result.build || {};
|
|
1349
1502
|
const entryPath = result.manifest?.entryPath || "unknown";
|
|
@@ -1356,6 +1509,12 @@ function printPullResult(result) {
|
|
|
1356
1509
|
console.log(`Next: cd ${shellQuote(result.dir)}`);
|
|
1357
1510
|
if (build.canWrite === false) {
|
|
1358
1511
|
console.log("This checkout is read-only for the current CLI login.");
|
|
1512
|
+
if (isContributionBranch(build)) {
|
|
1513
|
+
console.log("Review changes: lumine diff");
|
|
1514
|
+
if (build.role === "project_owner") {
|
|
1515
|
+
console.log("Owner actions: lumine merge, or lumine replace-main");
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1359
1518
|
return;
|
|
1360
1519
|
}
|
|
1361
1520
|
console.log('Codex: codex "Read AGENTS.md, then make the requested change."');
|
|
@@ -1426,6 +1585,57 @@ function printSaveResult({ result, build, dir, files }) {
|
|
|
1426
1585
|
}
|
|
1427
1586
|
}
|
|
1428
1587
|
|
|
1588
|
+
function printContributionDiff({ result, build }) {
|
|
1589
|
+
const summary = result.diff?.summary || {};
|
|
1590
|
+
const files = Array.isArray(result.diff?.changedFiles)
|
|
1591
|
+
? result.diff.changedFiles
|
|
1592
|
+
: [];
|
|
1593
|
+
const branchNumber = Number(build.contributionBranchNumber || 0) || 0;
|
|
1594
|
+
const branchLabel = branchNumber ? `branch ${branchNumber}` : `branch #${build.id}`;
|
|
1595
|
+
console.log(`Diff for ${branchLabel}:`);
|
|
1596
|
+
console.log(
|
|
1597
|
+
`- total=${summary.total ?? files.length} added=${summary.added ?? 0} ` +
|
|
1598
|
+
`updated=${summary.updated ?? 0} deleted=${summary.deleted ?? 0}`,
|
|
1599
|
+
);
|
|
1600
|
+
if (result.rootDrifted) {
|
|
1601
|
+
console.log("- main changed after this branch was created");
|
|
1602
|
+
}
|
|
1603
|
+
if (!files.length) {
|
|
1604
|
+
console.log("No file changes.");
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
for (const file of files) {
|
|
1608
|
+
const mergeStatus = file.mergeStatus ? ` (${file.mergeStatus})` : "";
|
|
1609
|
+
console.log(`- ${file.status || "changed"} ${file.path}${mergeStatus}`);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function printContributionActionResult({
|
|
1614
|
+
action,
|
|
1615
|
+
result,
|
|
1616
|
+
rootBuildId,
|
|
1617
|
+
contributionBuildId,
|
|
1618
|
+
}) {
|
|
1619
|
+
if (!result?.success) {
|
|
1620
|
+
console.log(result?.error || "Branch action did not complete.");
|
|
1621
|
+
process.exitCode = 1;
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const projectFiles = Array.isArray(result.projectFiles)
|
|
1625
|
+
? result.projectFiles
|
|
1626
|
+
: [];
|
|
1627
|
+
console.log(`${action} branch #${contributionBuildId} for Build #${rootBuildId}.`);
|
|
1628
|
+
if (projectFiles.length > 0) {
|
|
1629
|
+
console.log(
|
|
1630
|
+
`Main now has ${projectFiles.length} project file${projectFiles.length === 1 ? "" : "s"}.`,
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
if (result.mergeConflictsWritten || result.conflicts?.length > 0) {
|
|
1634
|
+
console.log("Merge wrote conflict markers. Resolve them in the Build workspace.");
|
|
1635
|
+
}
|
|
1636
|
+
console.log(`Main workspace: ${rootBuildId}`);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1429
1639
|
async function resolveAuth(options) {
|
|
1430
1640
|
if (options.authToken) {
|
|
1431
1641
|
return { token: options.authToken };
|
|
@@ -1745,8 +1955,14 @@ async function resolveRequiredBuildIdOrSelected(
|
|
|
1745
1955
|
auth,
|
|
1746
1956
|
{ localProject = null } = {},
|
|
1747
1957
|
) {
|
|
1748
|
-
const
|
|
1749
|
-
if (buildId > 0)
|
|
1958
|
+
const targetReference = resolveBuildReference(options.target);
|
|
1959
|
+
if (targetReference.buildId > 0) {
|
|
1960
|
+
return await resolveBuildReferenceBuildId({
|
|
1961
|
+
options,
|
|
1962
|
+
auth,
|
|
1963
|
+
reference: targetReference,
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1750
1966
|
const resolvedLocalProject =
|
|
1751
1967
|
localProject ||
|
|
1752
1968
|
(await findLocalProjectMetadata(
|
|
@@ -1775,7 +1991,7 @@ async function resolveRequiredBuildIdOrSelected(
|
|
|
1775
1991
|
}
|
|
1776
1992
|
|
|
1777
1993
|
function resolveRequiredBuildId(value) {
|
|
1778
|
-
const buildId =
|
|
1994
|
+
const buildId = resolveBuildReference(value).buildId;
|
|
1779
1995
|
if (buildId > 0) return buildId;
|
|
1780
1996
|
throw new Error(
|
|
1781
1997
|
"Pass a Twinkle build URL, app URL, preview URL, or build id.",
|
|
@@ -1783,40 +1999,74 @@ function resolveRequiredBuildId(value) {
|
|
|
1783
1999
|
}
|
|
1784
2000
|
|
|
1785
2001
|
function resolveBuildId(value) {
|
|
2002
|
+
return resolveBuildReference(value).buildId;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
async function resolveBuildReferenceBuildId({ options, auth, reference }) {
|
|
2006
|
+
if (reference.branchNumber > 0) {
|
|
2007
|
+
const build = await resolveBranchBuild({
|
|
2008
|
+
options,
|
|
2009
|
+
auth,
|
|
2010
|
+
rootBuildId: reference.buildId,
|
|
2011
|
+
branchNumber: reference.branchNumber,
|
|
2012
|
+
});
|
|
2013
|
+
return Number(build.id || 0);
|
|
2014
|
+
}
|
|
2015
|
+
return Number(reference.buildId || 0);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
function resolveBuildReference(value) {
|
|
1786
2019
|
const rawValue = String(value || "").trim();
|
|
1787
2020
|
const directId = Number(rawValue);
|
|
1788
|
-
if (Number.isFinite(directId) && directId > 0)
|
|
1789
|
-
|
|
2021
|
+
if (Number.isFinite(directId) && directId > 0) {
|
|
2022
|
+
return { buildId: directId, branchNumber: 0 };
|
|
2023
|
+
}
|
|
2024
|
+
if (!rawValue) return { buildId: 0, branchNumber: 0 };
|
|
1790
2025
|
|
|
1791
2026
|
try {
|
|
1792
2027
|
const parsedUrl = new URL(rawValue);
|
|
1793
2028
|
const host = parsedUrl.hostname.toLowerCase();
|
|
1794
2029
|
const previewHost = host.match(/^b-(\d+)\.preview\.lumine\.app$/);
|
|
1795
|
-
if (previewHost)
|
|
2030
|
+
if (previewHost) {
|
|
2031
|
+
return { buildId: Number(previewHost[1]) || 0, branchNumber: 0 };
|
|
2032
|
+
}
|
|
1796
2033
|
|
|
1797
2034
|
const parts = parsedUrl.pathname.split("/").filter(Boolean);
|
|
1798
2035
|
const appIndex = parts.indexOf("app");
|
|
1799
|
-
if (appIndex >= 0)
|
|
2036
|
+
if (appIndex >= 0) {
|
|
2037
|
+
return { buildId: Number(parts[appIndex + 1]) || 0, branchNumber: 0 };
|
|
2038
|
+
}
|
|
1800
2039
|
|
|
1801
2040
|
const buildIndex = parts.indexOf("build");
|
|
1802
2041
|
if (buildIndex >= 0) {
|
|
1803
2042
|
if (parts[buildIndex + 1] === "preview") {
|
|
1804
2043
|
const nestedBuildIndex = parts.indexOf("build", buildIndex + 2);
|
|
1805
|
-
return
|
|
2044
|
+
return {
|
|
2045
|
+
buildId: Number(parts[nestedBuildIndex + 1]) || 0,
|
|
2046
|
+
branchNumber: 0,
|
|
2047
|
+
};
|
|
1806
2048
|
}
|
|
1807
|
-
return
|
|
2049
|
+
return {
|
|
2050
|
+
buildId: Number(parts[buildIndex + 1]) || 0,
|
|
2051
|
+
branchNumber: Number(parts[buildIndex + 2]) || 0,
|
|
2052
|
+
};
|
|
1808
2053
|
}
|
|
1809
2054
|
|
|
1810
|
-
return
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2055
|
+
return {
|
|
2056
|
+
buildId:
|
|
2057
|
+
Number(parsedUrl.searchParams.get("buildId")) ||
|
|
2058
|
+
Number(parsedUrl.searchParams.get("build")) ||
|
|
2059
|
+
0,
|
|
2060
|
+
branchNumber: 0,
|
|
2061
|
+
};
|
|
1815
2062
|
} catch {
|
|
1816
2063
|
const match = rawValue.match(
|
|
1817
|
-
/(?:^|\/)(?:app|build)\/(?:preview\/build\/)?(\d+)(?:\/|$)/,
|
|
2064
|
+
/(?:^|\/)(?:app|build)\/(?:preview\/build\/)?(\d+)(?:\/(\d+))?(?:\/|$)/,
|
|
1818
2065
|
);
|
|
1819
|
-
return
|
|
2066
|
+
return {
|
|
2067
|
+
buildId: Number(match?.[1] || 0) || 0,
|
|
2068
|
+
branchNumber: Number(match?.[2] || 0) || 0,
|
|
2069
|
+
};
|
|
1820
2070
|
}
|
|
1821
2071
|
}
|
|
1822
2072
|
|
|
@@ -1926,6 +2176,9 @@ function printHelp() {
|
|
|
1926
2176
|
lumine pull [twinkle-build-url]
|
|
1927
2177
|
lumine reference <twinkle-build-url>
|
|
1928
2178
|
lumine fork <twinkle-build-url>
|
|
2179
|
+
lumine diff <twinkle-branch-url>
|
|
2180
|
+
lumine merge <twinkle-branch-url>
|
|
2181
|
+
lumine replace-main <twinkle-branch-url>
|
|
1929
2182
|
lumine save
|
|
1930
2183
|
lumine check [twinkle-build-url]
|
|
1931
2184
|
lumine launch [twinkle-build-url]
|
|
@@ -1936,6 +2189,8 @@ Examples:
|
|
|
1936
2189
|
npx @stage5/lumine@latest explore --sort forks
|
|
1937
2190
|
npx @stage5/lumine@latest reference https://www.twin-kle.com/app/123
|
|
1938
2191
|
npx @stage5/lumine@latest fork https://www.twin-kle.com/app/123
|
|
2192
|
+
npx @stage5/lumine@latest diff https://www.twin-kle.com/build/884/4
|
|
2193
|
+
npx @stage5/lumine@latest merge https://www.twin-kle.com/build/884/4
|
|
1939
2194
|
npx @stage5/lumine@latest pull
|
|
1940
2195
|
npx @stage5/lumine@latest save
|
|
1941
2196
|
npx @stage5/lumine@latest save --publish
|
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.25.
|
|
3
|
+
Version: 1.25.1
|
|
4
4
|
Updated: 2026-05-28
|
|
5
|
-
Generated: 2026-05-
|
|
5
|
+
Generated: 2026-05-28T10:24:18.784Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -203,15 +203,15 @@ const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode:
|
|
|
203
203
|
|
|
204
204
|
### Twinkle.aiCards
|
|
205
205
|
- async list({ limit, cursor, level, minLevel, maxLevel, quality, userId, hasImage, hasExample } = {}) | scopes: content:read
|
|
206
|
-
- Returns: { cards: [{ id, contentType, contentId, word, text, exampleText, prompt, level, wordLevel, quality, style, imagePath, imageUrl, creatorId, ownerId, username, profilePicUrl, timeStamp, lastInteraction }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
|
|
206
|
+
- Returns: { cards: [{ id, contentType, contentId, word, text, exampleText, prompt, level, wordLevel, quality, style, imagePath, imageUrl, isMysteryCard, isImageGenerating, creatorId, ownerId, username, profilePicUrl, timeStamp, lastInteraction }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
|
|
207
207
|
- List existing public AI Cards newest first, including each card word, example sentence text, word level, and quality.
|
|
208
208
|
- Example: const { cards } = await Twinkle.aiCards.list({ level: 2, hasExample: true, limit: 20 });
|
|
209
209
|
- async search({ query, limit, cursor, level, minLevel, maxLevel, quality, userId, hasImage, hasExample } = {}) | scopes: content:read
|
|
210
|
-
- Returns: { cards: [{ id, contentType, contentId, word, text, exampleText, prompt, level, wordLevel, quality, style, imagePath, imageUrl, creatorId, ownerId, username, profilePicUrl, timeStamp, lastInteraction }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
|
|
210
|
+
- Returns: { cards: [{ id, contentType, contentId, word, text, exampleText, prompt, level, wordLevel, quality, style, imagePath, imageUrl, isMysteryCard, isImageGenerating, creatorId, ownerId, username, profilePicUrl, timeStamp, lastInteraction }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
|
|
211
211
|
- Search existing public AI Cards by word, with optional level and quality filters.
|
|
212
212
|
- Example: const { cards } = await Twinkle.aiCards.search({ query: searchText, minLevel: 1, maxLevel: 3, hasExample: true, limit: 12 });
|
|
213
213
|
- async get(cardId) | scopes: content:read
|
|
214
|
-
- Returns: { card: { id, contentType, contentId, word, text, exampleText, prompt, level, wordLevel, quality, style, imagePath, imageUrl, creatorId, ownerId, username, profilePicUrl, timeStamp, lastInteraction } }
|
|
214
|
+
- Returns: { card: { id, contentType, contentId, word, text, exampleText, prompt, level, wordLevel, quality, style, imagePath, imageUrl, isMysteryCard, isImageGenerating, creatorId, ownerId, username, profilePicUrl, timeStamp, lastInteraction } }
|
|
215
215
|
- Fetch one existing public AI Card by id, including word, example text, and level metadata.
|
|
216
216
|
- Example: const { card } = await Twinkle.aiCards.get(cardId);
|
|
217
217
|
|