scenri 0.3.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1](https://github.com/tonygorb/scenri/compare/v0.3.0...v0.3.1) (2026-08-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * calm boot failures and stop the silent hang on a busy port ([d04946f](https://github.com/tonygorb/scenri/commit/d04946f011b204c230473f5338f0a32f8870a1d0))
9
+ * friendly first-run errors and the install guide ([b1782ad](https://github.com/tonygorb/scenri/commit/b1782adb4fc48807e384a208ee8894022d62d615))
10
+
3
11
  ## [0.3.0](https://github.com/tonygorb/scenri/compare/v0.2.3...v0.3.0) (2026-08-20)
4
12
 
5
13
 
package/README.md CHANGED
@@ -19,6 +19,8 @@ Define a client's brand once. Then generate, branch, and art-direct on-brand ima
19
19
  npx scenri
20
20
  ```
21
21
 
22
+ <sub>Needs [Node.js](https://nodejs.org) 22 or newer. New to any of this? The [install guide](docs/INSTALL.md) walks through every step.</sub>
23
+
22
24
  <img src="https://raw.githubusercontent.com/tonygorb/scenri/main/docs/media/demo.gif" alt="The Scenri composer: a dollar sign opens a product picker and Selvedge Trucker is chosen, an at sign picks the presenter Maren, a slash picks the Editorial Walk scene, a line of written direction is typed after the three chips, the shot renders as a card in the wall, and it opens to show the picture beside the brief that made it" width="820">
23
25
 
24
26
  <sub>Pick a product, pick a presenter, pick a scene, then write the direction. That is the brief.</sub>
@@ -55,15 +57,17 @@ The brand kit sits one gesture away in Settings and is the part that keeps outpu
55
57
 
56
58
  ## Run it
57
59
 
60
+ Scenri runs on [Node.js](https://nodejs.org), version 22 or newer. If you are not sure you have it, or terminals are not part of your day, the **[install guide](docs/INSTALL.md)** covers every step for macOS, Windows and Linux. With Node in place:
61
+
58
62
  ```bash
59
63
  npx scenri
60
64
  ```
61
65
 
62
- That is the whole install. It opens `http://127.0.0.1:4747`.
66
+ That is the whole install. npm asks once whether to proceed, downloads the current release, and opens `http://127.0.0.1:4747`. Keep the terminal window open while you work; closing it stops Scenri. Tomorrow the same command opens it again, with everything where you left it.
63
67
 
64
68
  Generation runs on **Codex CLI**, an official helper from OpenAI that draws on your own ChatGPT plan. No API key to paste, and Scenri never charges you. Each image draws on your plan's Codex usage. You do not have to set it up by hand: if it is missing, Scenri offers to install it and to sign you in, both from the app. No ChatGPT plan? Add your own key from an image provider in Settings instead, see [Engines](#engines).
65
69
 
66
- Requires **Node 22 or newer**. Two dependencies (`better-sqlite3` and `sharp`) ship native binaries, so on recent npm you may be asked to approve their install scripts once.
70
+ Two dependencies (`better-sqlite3` and `sharp`) ship native binaries, so on recent npm you may be asked to approve their install scripts once. Something not starting? See [troubleshooting](docs/INSTALL.md#troubleshooting).
67
71
 
68
72
  <details>
69
73
  <summary>Run from source</summary>
@@ -0,0 +1,36 @@
1
+ // src/bootError.ts
2
+ var INSTALL_GUIDE = "https://github.com/tonygorb/scenri/blob/main/docs/INSTALL.md";
3
+ var NATIVE_MARKERS = ["NODE_MODULE_VERSION", "Could not locate the bindings file", "ERR_DLOPEN_FAILED"];
4
+ function portBusyLines(port) {
5
+ const next = port + 1;
6
+ return [
7
+ `Port ${port} is in use by another app.`,
8
+ "Start Scenri on a different port:",
9
+ ` macOS or Linux: SCENRI_PORT=${next} npx scenri`,
10
+ ` Windows PowerShell: $env:SCENRI_PORT=${next}; npx scenri`
11
+ ];
12
+ }
13
+ function bootErrorLines(err) {
14
+ const raw = err instanceof Error ? err.message : String(err);
15
+ const first = raw.split("\n")[0];
16
+ const code = err?.code;
17
+ const haystack = `${typeof code === "string" ? code : ""} ${raw}`;
18
+ if (NATIVE_MARKERS.some((marker) => haystack.includes(marker))) {
19
+ return [
20
+ "Scenri could not start: a native component failed to load.",
21
+ `(${first})`,
22
+ "This usually means Node changed since this copy of Scenri was installed.",
23
+ "Fix: install the current Node LTS from https://nodejs.org, then run npx scenri@latest."
24
+ ];
25
+ }
26
+ return [
27
+ `Scenri could not start: ${first}`,
28
+ "If this keeps happening, run npx scenri@latest, or see the install guide:",
29
+ INSTALL_GUIDE,
30
+ "Set SCENRI_DEBUG=1 to see the full error."
31
+ ];
32
+ }
33
+
34
+ export { bootErrorLines, portBusyLines };
35
+ //# sourceMappingURL=chunk-WGIZJXNE.js.map
36
+ //# sourceMappingURL=chunk-WGIZJXNE.js.map
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { bootErrorLines } from './chunk-WGIZJXNE.js';
3
+
2
4
  // src/args.ts
3
5
  function parseArgs(argv) {
4
6
  const [first, ...rest] = argv;
@@ -41,43 +43,51 @@ SCENRI_CONTENT_URL.
41
43
 
42
44
  // src/index.ts
43
45
  var command = parseArgs(process.argv.slice(2));
44
- switch (command.cmd) {
45
- case "version": {
46
- const { readMeta } = await import('./meta-MPQPBPBK.js');
47
- console.log(readMeta().version);
48
- break;
49
- }
50
- case "help":
51
- console.log(helpText());
52
- break;
53
- case "error":
54
- console.error(command.message);
55
- process.exit(2);
56
- break;
57
- case "verify":
58
- await (await import('./serve.js')).verify();
59
- break;
60
- case "serve":
61
- await (await import('./serve.js')).serve();
62
- break;
63
- case "update": {
64
- const { runUpdateCommand } = await import('./cli-GQLRYYGU.js');
65
- process.exit(await runUpdateCommand(command));
66
- break;
67
- }
68
- case "launch": {
69
- const ownEntry = process.argv[1] ?? "";
70
- if (!/[\\/]dist[\\/]/.test(ownEntry)) {
46
+ try {
47
+ switch (command.cmd) {
48
+ case "version": {
49
+ const { readMeta } = await import('./meta-MPQPBPBK.js');
50
+ console.log(readMeta().version);
51
+ break;
52
+ }
53
+ case "help":
54
+ console.log(helpText());
55
+ break;
56
+ case "error":
57
+ console.error(command.message);
58
+ process.exit(2);
59
+ break;
60
+ case "verify":
61
+ await (await import('./serve.js')).verify();
62
+ break;
63
+ case "serve":
71
64
  await (await import('./serve.js')).serve();
72
65
  break;
66
+ case "update": {
67
+ const { runUpdateCommand } = await import('./cli-GQLRYYGU.js');
68
+ process.exit(await runUpdateCommand(command));
69
+ break;
70
+ }
71
+ case "launch": {
72
+ const ownEntry = process.argv[1] ?? "";
73
+ if (!/[\\/]dist[\\/]/.test(ownEntry)) {
74
+ await (await import('./serve.js')).serve();
75
+ break;
76
+ }
77
+ const { runLauncher } = await import('./launcher-RETYUGSR.js');
78
+ const { readMeta } = await import('./meta-MPQPBPBK.js');
79
+ const { defaultHome } = await import('./versionsDir-ILQ4CI7B.js');
80
+ const meta = readMeta();
81
+ process.exit(await runLauncher({ home: defaultHome(), pkg: meta.name, ownVersion: meta.version, ownEntry }));
82
+ break;
73
83
  }
74
- const { runLauncher } = await import('./launcher-RETYUGSR.js');
75
- const { readMeta } = await import('./meta-MPQPBPBK.js');
76
- const { defaultHome } = await import('./versionsDir-ILQ4CI7B.js');
77
- const meta = readMeta();
78
- process.exit(await runLauncher({ home: defaultHome(), pkg: meta.name, ownVersion: meta.version, ownEntry }));
79
- break;
80
84
  }
85
+ } catch (err) {
86
+ if (process.env.SCENRI_DEBUG === "1") throw err;
87
+ console.error("");
88
+ for (const line of bootErrorLines(err)) console.error(` ${line}`);
89
+ console.error("");
90
+ process.exit(1);
81
91
  }
82
92
  //# sourceMappingURL=index.js.map
83
93
  //# sourceMappingURL=index.js.map
package/dist/serve.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { portBusyLines } from './chunk-WGIZJXNE.js';
1
2
  import { createUpdateChecker, classify, stageVersion, findNpm } from './chunk-GA7P7K2M.js';
2
3
  import { readMeta, repoSlug } from './chunk-Y3ZPBPLP.js';
3
4
  import { newestStaged, compareSemver } from './chunk-4MAFHYAD.js';
@@ -6514,6 +6515,16 @@ function registerImageRoutes(app, deps) {
6514
6515
 
6515
6516
  // src/release/notes.data.ts
6516
6517
  var RELEASES = [
6518
+ {
6519
+ version: "0.3.1",
6520
+ date: "2026-08-20",
6521
+ sections: [
6522
+ {
6523
+ heading: "Fixes",
6524
+ body: "Starting Scenri is calmer when something is wrong. A busy port or a failed start now explains itself in plain words instead of a stack trace, and the terminal says to keep its window open while Scenri runs."
6525
+ }
6526
+ ]
6527
+ },
6517
6528
  {
6518
6529
  version: "0.3.0",
6519
6530
  date: "2026-08-20",
@@ -7515,7 +7526,9 @@ async function run() {
7515
7526
  continue;
7516
7527
  }
7517
7528
  try {
7518
- const res = await fetch(`http://127.0.0.1:${PORT}/api/version`);
7529
+ const res = await fetch(`http://127.0.0.1:${PORT}/api/version`, {
7530
+ signal: AbortSignal.timeout(2e3)
7531
+ });
7519
7532
  const info = await res.json();
7520
7533
  if (info.name === readMeta().name) {
7521
7534
  const url = `http://127.0.0.1:${PORT}`;
@@ -7533,6 +7546,10 @@ async function run() {
7533
7546
  }
7534
7547
  } catch {
7535
7548
  }
7549
+ console.error("");
7550
+ for (const line of portBusyLines(PORT)) console.error(` ${line}`);
7551
+ console.error("");
7552
+ process.exit(1);
7536
7553
  }
7537
7554
  throw err;
7538
7555
  }
@@ -7555,8 +7572,8 @@ async function run() {
7555
7572
  console.log(`
7556
7573
  Scenri Studio \u2192 ${localUrl}`);
7557
7574
  for (const ip of reachableAt) console.log(` on your network \u2192 http://${ip}:${PORT}${query}`);
7558
- console.log(` data dir \u2192 ${core.home}
7559
- `);
7575
+ console.log(` data dir \u2192 ${core.home}`);
7576
+ console.log(" Keep this window open while Scenri is running.\n");
7560
7577
  if (!studioDist) console.log(" (studio UI not built, API only. Run: pnpm build)\n");
7561
7578
  if (token) {
7562
7579
  console.log(" Warning: the studio is reachable from your network.");
@@ -7566,10 +7583,22 @@ async function run() {
7566
7583
  console.log(" For this machine only, unset SCENRI_HOST.\n");
7567
7584
  }
7568
7585
  if (process.env.SCENRI_NO_OPEN !== "1") {
7586
+ let told = false;
7587
+ const tellUrl = () => {
7588
+ if (told) return;
7589
+ told = true;
7590
+ console.log(` Open ${localUrl} in your browser.
7591
+ `);
7592
+ };
7569
7593
  try {
7570
7594
  const { default: open } = await import('open');
7571
- await open(localUrl);
7595
+ const child = await open(localUrl);
7596
+ child.once("error", tellUrl);
7597
+ child.once("exit", (openExit) => {
7598
+ if (openExit !== null && openExit !== 0) tellUrl();
7599
+ });
7572
7600
  } catch {
7601
+ tellUrl();
7573
7602
  }
7574
7603
  }
7575
7604
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scenri",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "author": "Tony Gorb <hello@scenri.co>",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",