@stage5/lumine 0.1.7 → 0.1.8

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.
Files changed (3) hide show
  1. package/README.md +9 -0
  2. package/bin/lumine.js +102 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -5,6 +5,8 @@ Launch Twinkle Lumine builds from any terminal.
5
5
  ```bash
6
6
  npx @stage5/lumine@latest
7
7
  npx @stage5/lumine@latest login
8
+ npx @stage5/lumine@latest new "Daily Reflection App"
9
+ npx @stage5/lumine@latest new --title "Daily Reflection App" --description "Private journal with streaks"
8
10
  npx @stage5/lumine@latest projects
9
11
  npx @stage5/lumine@latest explore --sort forks
10
12
  npx @stage5/lumine@latest reference https://www.twin-kle.com/app/123
@@ -19,6 +21,13 @@ Run `lumine` with no subcommand for the easiest flow: sign in when needed,
19
21
  choose one of your owned or team projects, and pull the saved project files into
20
22
  a local folder.
21
23
 
24
+ Use `lumine new` to create a new Twinkle Build with the same website create
25
+ route used by Build Studio. The CLI asks for a title when one is not passed and
26
+ asks for an optional description. It creates the Build and pulls an editable
27
+ local workspace, but it does not auto-start a Lumine greeting or prompt run, so
28
+ creating a project from the CLI does not spend AI battery. Add project files
29
+ locally, including `/index.html`, then run `lumine save`.
30
+
22
31
  For team projects, Lumine mirrors the website workspace flow: choosing or
23
32
  pulling the owner's main project creates or reuses your contribution branch and
24
33
  checks out that branch locally. Saves go to your branch, so the project owner
package/bin/lumine.js CHANGED
@@ -119,6 +119,7 @@ const COMMANDS = new Set([
119
119
  "login",
120
120
  "logout",
121
121
  "whoami",
122
+ "new",
122
123
  "projects",
123
124
  "explore",
124
125
  "select",
@@ -167,6 +168,10 @@ async function main() {
167
168
  await whoami(options);
168
169
  return;
169
170
  }
171
+ if (options.command === "new") {
172
+ await newBuild(options);
173
+ return;
174
+ }
170
175
  if (options.command === "projects") {
171
176
  await projects(options);
172
177
  return;
@@ -314,6 +319,26 @@ async function whoami(options) {
314
319
  );
315
320
  }
316
321
 
322
+ async function newBuild(options) {
323
+ const title = await resolveNewBuildTitle(options);
324
+ const description = await resolveNewBuildDescription(options);
325
+ const auth = await ensureAuth(options);
326
+ await assertAuthScope({ options, auth, scope: "build:write" });
327
+ const createResult = await createBuild({
328
+ options,
329
+ auth,
330
+ title,
331
+ description,
332
+ });
333
+ const buildId = Number(createResult.build?.id || 0);
334
+ if (!buildId) {
335
+ throw new Error("Twinkle did not return a created Build.");
336
+ }
337
+ const result = await pullBuildFiles({ options, auth, buildId });
338
+ await saveSelectedBuild({ options, auth, build: result.build });
339
+ printNewBuildResult({ createResult, pullResult: result });
340
+ }
341
+
317
342
  async function workspace(options) {
318
343
  const auth = await ensureAuth(options);
319
344
  const selectedBuild = options.target
@@ -774,6 +799,19 @@ async function forkBuild({ options, auth, buildId }) {
774
799
  });
775
800
  }
776
801
 
802
+ async function createBuild({ options, auth, title, description }) {
803
+ return await requestJson({
804
+ method: "POST",
805
+ url: `${options.apiUrl}/build/create`,
806
+ authToken: auth.token,
807
+ body: {
808
+ title,
809
+ ...(description ? { description } : {}),
810
+ },
811
+ timeoutMs: options.timeoutMs,
812
+ });
813
+ }
814
+
777
815
  async function saveProjectFiles({ options, auth, buildId, files, summary }) {
778
816
  return await requestJson({
779
817
  method: "PUT",
@@ -1517,6 +1555,9 @@ function printPullResult(result) {
1517
1555
  }
1518
1556
  return;
1519
1557
  }
1558
+ if (result.fileCount === 0) {
1559
+ console.log("No project files yet. Create /index.html before your first save.");
1560
+ }
1520
1561
  console.log('Codex: codex "Read AGENTS.md, then make the requested change."');
1521
1562
  console.log(
1522
1563
  'Claude Code: claude "Read CLAUDE.md, then make the requested change."',
@@ -1529,6 +1570,12 @@ function printPullResult(result) {
1529
1570
  }
1530
1571
  }
1531
1572
 
1573
+ function printNewBuildResult({ createResult, pullResult }) {
1574
+ const build = pullResult.build || createResult.build || {};
1575
+ console.log(`Created ${formatBuildTitle(build)}.`);
1576
+ printPullResult(pullResult);
1577
+ }
1578
+
1532
1579
  function printReferenceResult(result) {
1533
1580
  const build = result.build || {};
1534
1581
  const sourceBuildId =
@@ -1868,6 +1915,7 @@ function parseArgs(args) {
1868
1915
  const booleanFlags = new Set([
1869
1916
  "noOpen",
1870
1917
  "open",
1918
+ "noDescription",
1871
1919
  "publish",
1872
1920
  "save",
1873
1921
  "noUpdateCheck",
@@ -1898,6 +1946,20 @@ function parseArgs(args) {
1898
1946
  return {
1899
1947
  command,
1900
1948
  target: raw.url || raw.target || positional[0] || "",
1949
+ title:
1950
+ String(
1951
+ raw.title ||
1952
+ (command === "new" ? positional.join(" ") : ""),
1953
+ ).trim() || "",
1954
+ description:
1955
+ Object.prototype.hasOwnProperty.call(raw, "description")
1956
+ ? String(raw.description || "").trim()
1957
+ : null,
1958
+ descriptionProvided: Object.prototype.hasOwnProperty.call(
1959
+ raw,
1960
+ "description",
1961
+ ),
1962
+ noDescription: parseBoolean(raw.noDescription, false),
1901
1963
  searchQuery:
1902
1964
  String(
1903
1965
  raw.search ||
@@ -1990,6 +2052,40 @@ async function resolveRequiredBuildIdOrSelected(
1990
2052
  );
1991
2053
  }
1992
2054
 
2055
+ async function resolveNewBuildTitle(options) {
2056
+ const title = String(options.title || "").trim();
2057
+ if (title) return title;
2058
+ if (!input.isTTY || !output.isTTY) {
2059
+ throw new Error('Pass a title: `lumine new "My Build"` or `lumine new --title "My Build"`.');
2060
+ }
2061
+ const rl = readline.createInterface({ input, output });
2062
+ try {
2063
+ const answer = await rl.question("Build title: ");
2064
+ const normalized = String(answer || "").trim();
2065
+ if (!normalized) {
2066
+ throw new Error("Title is required.");
2067
+ }
2068
+ return normalized;
2069
+ } finally {
2070
+ rl.close();
2071
+ }
2072
+ }
2073
+
2074
+ async function resolveNewBuildDescription(options) {
2075
+ if (options.noDescription) return null;
2076
+ if (options.descriptionProvided) {
2077
+ return String(options.description || "").trim() || null;
2078
+ }
2079
+ if (!input.isTTY || !output.isTTY) return null;
2080
+ const rl = readline.createInterface({ input, output });
2081
+ try {
2082
+ const answer = await rl.question("Description (optional): ");
2083
+ return String(answer || "").trim() || null;
2084
+ } finally {
2085
+ rl.close();
2086
+ }
2087
+ }
2088
+
1993
2089
  function resolveRequiredBuildId(value) {
1994
2090
  const buildId = resolveBuildReference(value).buildId;
1995
2091
  if (buildId > 0) return buildId;
@@ -2170,6 +2266,7 @@ function printHelp() {
2170
2266
  lumine login
2171
2267
  lumine whoami
2172
2268
  lumine logout
2269
+ lumine new [title]
2173
2270
  lumine projects
2174
2271
  lumine explore [search terms]
2175
2272
  lumine select [twinkle-build-url]
@@ -2186,6 +2283,8 @@ function printHelp() {
2186
2283
  Examples:
2187
2284
  npx @stage5/lumine@latest
2188
2285
  npx @stage5/lumine@latest login
2286
+ npx @stage5/lumine@latest new "Daily Reflection App"
2287
+ npx @stage5/lumine@latest new --title "Daily Reflection App" --description "Private journal with streaks"
2189
2288
  npx @stage5/lumine@latest explore --sort forks
2190
2289
  npx @stage5/lumine@latest reference https://www.twin-kle.com/app/123
2191
2290
  npx @stage5/lumine@latest fork https://www.twin-kle.com/app/123
@@ -2203,6 +2302,9 @@ Options:
2203
2302
  --auth-file <path> Saved login path
2204
2303
  --auth-token <token> Override saved login
2205
2304
  --dir <path> Directory for pulled project files
2305
+ --title <text> New Build title
2306
+ --description <text> Optional New Build description
2307
+ --no-description Skip the New Build description prompt
2206
2308
  --summary <text> Save summary
2207
2309
  --search <text> Search public open-source Builds
2208
2310
  --sort <sort> Sort open-source Builds: forks, popular, recent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {