scenri 0.3.0 → 0.3.2

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.2](https://github.com/tonygorb/scenri/compare/v0.3.1...v0.3.2) (2026-08-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * suggest sudo and drop stale errors in the codex setup dialog ([da88610](https://github.com/tonygorb/scenri/commit/da886109666e9613b954a6f3d6c8299de0fdf944))
9
+ * suggest sudo and drop stale errors in the codex setup dialog ([1283f3c](https://github.com/tonygorb/scenri/commit/1283f3c5f93a3204bff6ba9e33ebc670b6004034))
10
+
11
+ ## [0.3.1](https://github.com/tonygorb/scenri/compare/v0.3.0...v0.3.1) (2026-08-20)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * calm boot failures and stop the silent hang on a busy port ([d04946f](https://github.com/tonygorb/scenri/commit/d04946f011b204c230473f5338f0a32f8870a1d0))
17
+ * friendly first-run errors and the install guide ([b1782ad](https://github.com/tonygorb/scenri/commit/b1782adb4fc48807e384a208ee8894022d62d615))
18
+
3
19
  ## [0.3.0](https://github.com/tonygorb/scenri/compare/v0.2.3...v0.3.0) (2026-08-20)
4
20
 
5
21
 
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';
@@ -2096,6 +2097,7 @@ function pick(v, allowed, max) {
2096
2097
  }
2097
2098
  var INSTALL_COMMAND = "npm install -g @openai/codex";
2098
2099
  var INSTALL_DOCS_URL = "https://developers.openai.com/codex/cli";
2100
+ var INSTALL_COMMAND_SUDO = "sudo npm install -g @openai/codex";
2099
2101
  var DEFAULT_INSTALL_TIMEOUT_MS = 18e4;
2100
2102
  function stateFrom(avail) {
2101
2103
  if (avail.ok) return "ready";
@@ -2154,6 +2156,14 @@ function createCodexSetup(opts = {}) {
2154
2156
  }
2155
2157
  return { ok: true };
2156
2158
  }
2159
+ if (process.platform !== "win32" && /EACCES|permission denied/i.test(res.stderr)) {
2160
+ return {
2161
+ ok: false,
2162
+ fallbackCommand: INSTALL_COMMAND_SUDO,
2163
+ docsUrl: INSTALL_DOCS_URL,
2164
+ detail: "npm needs your password to install into its system folder. Run the command below and type your password when asked; it stays invisible while you type."
2165
+ };
2166
+ }
2157
2167
  const detail = (res.spawnError ?? res.stderr).trim().slice(0, 400) || void 0;
2158
2168
  return { ok: false, fallbackCommand: INSTALL_COMMAND, docsUrl: INSTALL_DOCS_URL, detail };
2159
2169
  },
@@ -6514,6 +6524,26 @@ function registerImageRoutes(app, deps) {
6514
6524
 
6515
6525
  // src/release/notes.data.ts
6516
6526
  var RELEASES = [
6527
+ {
6528
+ version: "0.3.2",
6529
+ date: "2026-08-20",
6530
+ sections: [
6531
+ {
6532
+ heading: "Fixes",
6533
+ body: "When your computer refuses the Codex CLI install, setup now offers the command that gets past it instead of repeating the one that just failed. And once a step has succeeded, an old error clears instead of lingering under the green check."
6534
+ }
6535
+ ]
6536
+ },
6537
+ {
6538
+ version: "0.3.1",
6539
+ date: "2026-08-20",
6540
+ sections: [
6541
+ {
6542
+ heading: "Fixes",
6543
+ 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."
6544
+ }
6545
+ ]
6546
+ },
6517
6547
  {
6518
6548
  version: "0.3.0",
6519
6549
  date: "2026-08-20",
@@ -7515,7 +7545,9 @@ async function run() {
7515
7545
  continue;
7516
7546
  }
7517
7547
  try {
7518
- const res = await fetch(`http://127.0.0.1:${PORT}/api/version`);
7548
+ const res = await fetch(`http://127.0.0.1:${PORT}/api/version`, {
7549
+ signal: AbortSignal.timeout(2e3)
7550
+ });
7519
7551
  const info = await res.json();
7520
7552
  if (info.name === readMeta().name) {
7521
7553
  const url = `http://127.0.0.1:${PORT}`;
@@ -7533,6 +7565,10 @@ async function run() {
7533
7565
  }
7534
7566
  } catch {
7535
7567
  }
7568
+ console.error("");
7569
+ for (const line of portBusyLines(PORT)) console.error(` ${line}`);
7570
+ console.error("");
7571
+ process.exit(1);
7536
7572
  }
7537
7573
  throw err;
7538
7574
  }
@@ -7555,8 +7591,8 @@ async function run() {
7555
7591
  console.log(`
7556
7592
  Scenri Studio \u2192 ${localUrl}`);
7557
7593
  for (const ip of reachableAt) console.log(` on your network \u2192 http://${ip}:${PORT}${query}`);
7558
- console.log(` data dir \u2192 ${core.home}
7559
- `);
7594
+ console.log(` data dir \u2192 ${core.home}`);
7595
+ console.log(" Keep this window open while Scenri is running.\n");
7560
7596
  if (!studioDist) console.log(" (studio UI not built, API only. Run: pnpm build)\n");
7561
7597
  if (token) {
7562
7598
  console.log(" Warning: the studio is reachable from your network.");
@@ -7566,10 +7602,22 @@ async function run() {
7566
7602
  console.log(" For this machine only, unset SCENRI_HOST.\n");
7567
7603
  }
7568
7604
  if (process.env.SCENRI_NO_OPEN !== "1") {
7605
+ let told = false;
7606
+ const tellUrl = () => {
7607
+ if (told) return;
7608
+ told = true;
7609
+ console.log(` Open ${localUrl} in your browser.
7610
+ `);
7611
+ };
7569
7612
  try {
7570
7613
  const { default: open } = await import('open');
7571
- await open(localUrl);
7614
+ const child = await open(localUrl);
7615
+ child.once("error", tellUrl);
7616
+ child.once("exit", (openExit) => {
7617
+ if (openExit !== null && openExit !== 0) tellUrl();
7618
+ });
7572
7619
  } catch {
7620
+ tellUrl();
7573
7621
  }
7574
7622
  }
7575
7623
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scenri",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "author": "Tony Gorb <hello@scenri.co>",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",