scenri 0.2.3 → 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/ASSETS-LICENSE.md +4 -4
- package/CHANGELOG.md +16 -0
- package/LICENSE +1 -1
- package/NOTICE +4 -4
- package/README.md +18 -14
- package/dist/{chunk-VE675PSG.js → chunk-GA7P7K2M.js} +2 -2
- package/dist/chunk-WGIZJXNE.js +36 -0
- package/dist/{cli-JVN6MY72.js → cli-GQLRYYGU.js} +4 -4
- package/dist/index.js +45 -35
- package/dist/{launcher-YT5KSSTN.js → launcher-RETYUGSR.js} +4 -4
- package/dist/serve.js +60 -16
- package/package.json +1 -1
- package/studio-dist/assets/index-BaASA7BS.js +102 -0
- package/studio-dist/favicon.svg +1 -1
- package/studio-dist/index.html +2 -2
- package/studio-dist/site.webmanifest +2 -2
- package/studio-dist/assets/index-CZX6N9rq.js +0 -102
package/ASSETS-LICENSE.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Scenri assets license
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scenri's code is AGPL-3.0-only (see LICENSE). The imagery and curated content are
|
|
4
4
|
licensed under these terms instead.
|
|
5
5
|
|
|
6
6
|
This covers the starter content pack, everything under `templates/`, the library
|
|
7
|
-
content
|
|
7
|
+
content Scenri downloads at runtime, and the project's marketing imagery.
|
|
8
8
|
|
|
9
|
-
You may use this content freely within
|
|
9
|
+
You may use this content freely within Scenri, including for commercial work.
|
|
10
10
|
Everything you generate with it is yours.
|
|
11
11
|
|
|
12
12
|
You may not redistribute, rebundle, resell, or republish the content itself, and
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
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
|
+
|
|
11
|
+
## [0.3.0](https://github.com/tonygorb/scenri/compare/v0.2.3...v0.3.0) (2026-08-20)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* give every browser tab an intentional title ([e518396](https://github.com/tonygorb/scenri/commit/e5183961dae9f52ecd5801ae4c8cfce5fb397f3a))
|
|
17
|
+
* name the product Scenri across the interface, docs and CLI ([cce242b](https://github.com/tonygorb/scenri/commit/cce242be955520569fdffb170f14f91f752d1801))
|
|
18
|
+
|
|
3
19
|
## [0.2.3](https://github.com/tonygorb/scenri/compare/v0.2.2...v0.2.3) (2026-08-20)
|
|
4
20
|
|
|
5
21
|
|
package/LICENSE
CHANGED
|
@@ -629,7 +629,7 @@ to attach them to the start of each source file to most effectively
|
|
|
629
629
|
state the exclusion of warranty; and each file should have at least
|
|
630
630
|
the "copyright" line and a pointer to where the full notice is found.
|
|
631
631
|
|
|
632
|
-
|
|
632
|
+
Scenri - the open studio for brand-consistent AI visuals
|
|
633
633
|
Copyright (C) 2026 Tony Gorb
|
|
634
634
|
|
|
635
635
|
This program is free software: you can redistribute it and/or modify
|
package/NOTICE
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
Scenri
|
|
2
2
|
Copyright 2026 Tony Gorb
|
|
3
3
|
|
|
4
4
|
This product includes software and assets developed by third parties.
|
|
@@ -46,11 +46,11 @@ Phosphor Icons (`@phosphor-icons/react`), MIT, Copyright (c) 2023 Phosphor Icons
|
|
|
46
46
|
|
|
47
47
|
## Third-party marks
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Scenri shows each generation provider's own mark beside its name, so that the
|
|
50
50
|
row asking for your key names the service you recognise rather than a generic
|
|
51
51
|
glyph. Every mark is reproduced unmodified, in a variant its owner publishes,
|
|
52
52
|
and is used referentially to identify which service a key or a sign-in belongs
|
|
53
|
-
to.
|
|
53
|
+
to. Scenri is not built, supported, certified, endorsed by, or affiliated with
|
|
54
54
|
any of them, and makes no such claim.
|
|
55
55
|
|
|
56
56
|
| Mark | File | Artwork source | Artwork licence |
|
|
@@ -63,7 +63,7 @@ Each mark sits on the plate its owner puts it on, in the colours its owner
|
|
|
63
63
|
publishes, sampled from that owner's own icon: OpenAI's 180px favicon (white
|
|
64
64
|
plate, black mark), OpenRouter's app icon (white plate, violet mark) and
|
|
65
65
|
Replicate's favicon (red plate, white mark). The pairs are recorded in `BRAND` in
|
|
66
|
-
`apps/studio/src/engines/providers.tsx` and are identical in both of
|
|
66
|
+
`apps/studio/src/engines/providers.tsx` and are identical in both of Scenri's
|
|
67
67
|
themes, because none of these owners publishes a dark variant and inventing one
|
|
68
68
|
would be redrawing their logo. No geometry is redrawn, distorted, recomposed,
|
|
69
69
|
recoloured or tinted, and no mark is ever separated from its own plate.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<picture>
|
|
4
4
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/tonygorb/scenri/main/docs/media/logo-on-dark.svg">
|
|
5
|
-
<img src="https://raw.githubusercontent.com/tonygorb/scenri/main/docs/media/logo-on-light.svg" alt="
|
|
5
|
+
<img src="https://raw.githubusercontent.com/tonygorb/scenri/main/docs/media/logo-on-light.svg" alt="Scenri" width="200">
|
|
6
6
|
</picture>
|
|
7
7
|
<br><br>
|
|
8
8
|
|
|
@@ -19,7 +19,9 @@ Define a client's brand once. Then generate, branch, and art-direct on-brand ima
|
|
|
19
19
|
npx scenri
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
<
|
|
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
|
+
|
|
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>
|
|
25
27
|
|
|
@@ -29,7 +31,7 @@ npx scenri
|
|
|
29
31
|
|
|
30
32
|
## What it is
|
|
31
33
|
|
|
32
|
-
Most AI image tools give you a prompt box and a slot machine.
|
|
34
|
+
Most AI image tools give you a prompt box and a slot machine. Scenri gives you the part that actually takes the time: **art direction**.
|
|
33
35
|
|
|
34
36
|
Every shot keeps its lineage. Branch an edit from any shot, put the two side by side, and the drift heatmap shows you exactly where the model changed your product when it should not have.
|
|
35
37
|
|
|
@@ -47,7 +49,7 @@ All 97 examples on the home wall work the same way. Open one and it loads back i
|
|
|
47
49
|
|
|
48
50
|
## What you get out of the box
|
|
49
51
|
|
|
50
|
-
<img src="https://raw.githubusercontent.com/tonygorb/scenri/main/docs/media/library.gif" alt="A slow scroll down the
|
|
52
|
+
<img src="https://raw.githubusercontent.com/tonygorb/scenri/main/docs/media/library.gif" alt="A slow scroll down the Scenri home wall: rows of finished example shots across product categories, with the composer docked at the bottom of the screen" width="820">
|
|
51
53
|
|
|
52
54
|
A library of scenes, presenters and demo products, so the app is useful before you have uploaded anything of your own. Filter by category, bookmark what fits the brand, or build your own from a few reference photos.
|
|
53
55
|
|
|
@@ -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.
|
|
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
|
-
Generation runs on **Codex CLI**, an official helper from OpenAI that draws on your own ChatGPT plan. No API key to paste, and
|
|
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
|
-
|
|
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>
|
|
@@ -80,7 +84,7 @@ pnpm dev # starts the server on 127.0.0.1:4747
|
|
|
80
84
|
|
|
81
85
|
## First five minutes
|
|
82
86
|
|
|
83
|
-
1. **Paste a website URL.**
|
|
87
|
+
1. **Paste a website URL.** Scenri reads the public page and drafts the kit: name, palette, logo, tone.
|
|
84
88
|
2. **Describe a shot.** In the composer, `$` reaches for a product, `@` for a presenter, `/` for a scene, and `#` for a colour. Everything between them is your own words. The composer compiles a brief you can inspect before it runs.
|
|
85
89
|
3. **Generate on Codex CLI.** Runs on your own ChatGPT plan, so there is no key and no per-image charge from us.
|
|
86
90
|
4. **Branch an edit** from any shot, then hit compare. The heatmap shows what moved.
|
|
@@ -91,7 +95,7 @@ pnpm dev # starts the server on 127.0.0.1:4747
|
|
|
91
95
|
- **Iteration is the product.** A version tree, not a prompt box. Branch, compare, keep the winners.
|
|
92
96
|
- **Your brands are files, not hostages.** `.brand` is an open, documented format under a permissive license. Email one to a client. Any tool can adopt it.
|
|
93
97
|
- **Your AI, your cost.** Bring your own Codex CLI session or an API key. Experiments cost raw API price, or nothing at all on a local session. No credits that burn on a miss.
|
|
94
|
-
- **Local first, and it means it.** No account, no telemetry, no upload. The server binds to your machine only.
|
|
98
|
+
- **Local first, and it means it.** No account, no telemetry, no upload. The server binds to your machine only. Scenri makes exactly two requests on its own behalf: a daily version-number check against npm so updates can announce themselves, and a one-time download of the library imagery archive, cached locally forever after. Nothing about you or your work is ever sent, and both turn off: in Settings or `SCENRI_NO_UPDATE_CHECK=1` for the first, `SCENRI_NO_CONTENT_FETCH=1` for the second ([how updates work](docs/updates.md)).
|
|
95
99
|
|
|
96
100
|
## Engines
|
|
97
101
|
|
|
@@ -104,13 +108,13 @@ pnpm dev # starts the server on 127.0.0.1:4747
|
|
|
104
108
|
|
|
105
109
|
Codex CLI is the default because it needs no key and because it carries the most reference images: a shot that has to keep both a product and a person accurate needs the room.
|
|
106
110
|
|
|
107
|
-
It is not free. Every ChatGPT plan comes with some Codex usage and each image spends a little of it. OpenAI meters that, not
|
|
111
|
+
It is not free. Every ChatGPT plan comes with some Codex usage and each image spends a little of it. OpenAI meters that, not Scenri, so spend caps do not apply to this engine.
|
|
108
112
|
|
|
109
|
-
**Without a ChatGPT plan**, use your own provider key. OpenRouter is the one to pick if your shots name a Product or a Presenter. Replicate and fal take no reference images, so
|
|
113
|
+
**Without a ChatGPT plan**, use your own provider key. OpenRouter is the one to pick if your shots name a Product or a Presenter. Replicate and fal take no reference images, so Scenri refuses those briefs on them rather than generating something that only looks right.
|
|
110
114
|
|
|
111
115
|
Keys are stored in your local library folder, sent only to that provider, and never returned by the API. Set a monthly spend cap per engine in Settings.
|
|
112
116
|
|
|
113
|
-
Your Codex session is yours:
|
|
117
|
+
Your Codex session is yours: Scenri runs the official `codex` commands on your machine and never reads, copies or stores the credential. That is also why Scenri never pools user plans: a plan is licensed to the person who pays for it, not to a service reselling it to other people. Any hosted version of Scenri, if one ever exists, would run API-priced engines only.
|
|
114
118
|
|
|
115
119
|
## Configuration
|
|
116
120
|
|
|
@@ -127,7 +131,7 @@ Your Codex session is yours: scenri runs the official `codex` commands on your m
|
|
|
127
131
|
|
|
128
132
|
`OPENROUTER_API_KEY`, `REPLICATE_API_TOKEN` and `FAL_KEY` are read from the environment as an alternative to entering them in Settings.
|
|
129
133
|
|
|
130
|
-
**Before you change `SCENRI_HOST`:**
|
|
134
|
+
**Before you change `SCENRI_HOST`:** Scenri has no accounts, so anyone who can reach the port can spend your API keys and delete your library. Setting `SCENRI_HOST=0.0.0.0` opens it to your local network so you can use it from a phone. That path prints a URL carrying a one-time access token, refuses every request without it, and rejects unfamiliar `Host` headers. Treat it as convenience on a network you trust, not as a security boundary.
|
|
131
135
|
|
|
132
136
|
## Layout
|
|
133
137
|
|
|
@@ -158,7 +162,7 @@ Early. The version is `0.x` and interfaces can still move. Everything documented
|
|
|
158
162
|
|
|
159
163
|
The application is [AGPL-3.0-only](LICENSE). The `.brand` format, its schema, and its validator live in [packages/brand-spec](https://github.com/tonygorb/scenri/tree/main/packages/brand-spec) under Apache-2.0, so any tool may implement or reuse them without taking on copyleft; the npm package carries that license text as `LICENSE-APACHE-2.0-brand-spec`. Contributions require a [CLA](CLA.md).
|
|
160
164
|
|
|
161
|
-
The bundled imagery and curated content are licensed separately under [ASSETS-LICENSE.md](docs/ASSETS-LICENSE.md): free to use within
|
|
165
|
+
The bundled imagery and curated content are licensed separately under [ASSETS-LICENSE.md](docs/ASSETS-LICENSE.md): free to use within Scenri, commercial work included; no redistribution or rebundling. The npm package's `license` field describes the code; the starter imagery inside it stays under those asset terms.
|
|
162
166
|
|
|
163
167
|
The name and the mark are not part of the code license: [TRADEMARKS.md](TRADEMARKS.md) says what a fork may and may not call itself. What the app does and does not send anywhere is written down in [PRIVACY.md](PRIVACY.md).
|
|
164
168
|
|
|
@@ -202,5 +202,5 @@ async function stageVersion(deps) {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
export { classify, createUpdateChecker, fetchDistTagLatest, findNpm, stageVersion };
|
|
205
|
-
//# sourceMappingURL=chunk-
|
|
206
|
-
//# sourceMappingURL=chunk-
|
|
205
|
+
//# sourceMappingURL=chunk-GA7P7K2M.js.map
|
|
206
|
+
//# sourceMappingURL=chunk-GA7P7K2M.js.map
|
|
@@ -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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stageVersion, fetchDistTagLatest, findNpm } from './chunk-
|
|
1
|
+
import { stageVersion, fetchDistTagLatest, findNpm } from './chunk-GA7P7K2M.js';
|
|
2
2
|
import { readMeta } from './chunk-Y3ZPBPLP.js';
|
|
3
3
|
import { defaultHome, newestStaged, compareSemver } from './chunk-4MAFHYAD.js';
|
|
4
4
|
|
|
@@ -34,7 +34,7 @@ async function runUpdateCommand(opts) {
|
|
|
34
34
|
const effective = staged && compareSemver(staged, meta.version) > 0 ? staged : meta.version;
|
|
35
35
|
console.log(` running ${meta.version}${staged ? ` \xB7 staged ${staged}` : ""} \xB7 latest ${latest}`);
|
|
36
36
|
if (compareSemver(latest, effective) <= 0) {
|
|
37
|
-
console.log(staged ? " the newest version is already staged. Just start
|
|
37
|
+
console.log(staged ? " the newest version is already staged. Just start Scenri." : " already the newest.");
|
|
38
38
|
return 0;
|
|
39
39
|
}
|
|
40
40
|
if (opts.check) {
|
|
@@ -62,5 +62,5 @@ async function runUpdateCommand(opts) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export { runUpdateCommand };
|
|
65
|
-
//# sourceMappingURL=cli-
|
|
66
|
-
//# sourceMappingURL=cli-
|
|
65
|
+
//# sourceMappingURL=cli-GQLRYYGU.js.map
|
|
66
|
+
//# sourceMappingURL=cli-GQLRYYGU.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;
|
|
@@ -23,10 +25,10 @@ function parseArgs(argv) {
|
|
|
23
25
|
return { cmd: "error", message: `unknown command '${first}' (try --help)` };
|
|
24
26
|
}
|
|
25
27
|
function helpText() {
|
|
26
|
-
return `
|
|
28
|
+
return `Scenri: local brand studio
|
|
27
29
|
|
|
28
30
|
Usage
|
|
29
|
-
scenri start
|
|
31
|
+
scenri start Scenri (auto-restarts after updates)
|
|
30
32
|
scenri serve start this exact build, no supervision
|
|
31
33
|
scenri update download and stage the newest version
|
|
32
34
|
--check only check, do not download
|
|
@@ -41,43 +43,51 @@ SCENRI_CONTENT_URL.
|
|
|
41
43
|
|
|
42
44
|
// src/index.ts
|
|
43
45
|
var command = parseArgs(process.argv.slice(2));
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
break;
|
|
63
|
-
case "update": {
|
|
64
|
-
const { runUpdateCommand } = await import('./cli-JVN6MY72.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-YT5KSSTN.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
|
|
@@ -31,7 +31,7 @@ async function runLauncher(deps) {
|
|
|
31
31
|
starts.push(startedAt);
|
|
32
32
|
while (starts.length > 0 && startedAt - starts[0] > LOOP_WINDOW_MS) starts.shift();
|
|
33
33
|
if (starts.length > LOOP_MAX_STARTS) {
|
|
34
|
-
log("
|
|
34
|
+
log(" Scenri is restarting in a loop and has stopped trying.");
|
|
35
35
|
log(" Recover with: rm -rf ~/.scenri/app (app versions only, your library is untouched)");
|
|
36
36
|
log(" then start fresh: npx scenri@latest");
|
|
37
37
|
return 1;
|
|
@@ -51,7 +51,7 @@ async function runLauncher(deps) {
|
|
|
51
51
|
if (signalled) return code ?? 0;
|
|
52
52
|
if (code === RESTART_EXIT_CODE) continue;
|
|
53
53
|
if (code !== 0 && useStaged && now() - startedAt < QUICK_DEATH_MS) {
|
|
54
|
-
log(`
|
|
54
|
+
log(` Scenri ${staged} failed to start. Falling back to ${deps.ownVersion}.`);
|
|
55
55
|
log(" If this keeps happening: rm -rf ~/.scenri/app");
|
|
56
56
|
distrustStaged = true;
|
|
57
57
|
continue;
|
|
@@ -61,5 +61,5 @@ async function runLauncher(deps) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export { LAUNCHER_PROTOCOL, RESTART_EXIT_CODE, runLauncher };
|
|
64
|
-
//# sourceMappingURL=launcher-
|
|
65
|
-
//# sourceMappingURL=launcher-
|
|
64
|
+
//# sourceMappingURL=launcher-RETYUGSR.js.map
|
|
65
|
+
//# sourceMappingURL=launcher-RETYUGSR.js.map
|
package/dist/serve.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { portBusyLines } from './chunk-WGIZJXNE.js';
|
|
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';
|
|
4
5
|
import { networkInterfaces, homedir, tmpdir } from 'os';
|
|
@@ -297,7 +298,7 @@ var SCHEMA_VERSION = 1;
|
|
|
297
298
|
var SchemaTooNewError = class extends Error {
|
|
298
299
|
constructor(found, supported) {
|
|
299
300
|
super(
|
|
300
|
-
`This library was written by a newer
|
|
301
|
+
`This library was written by a newer Scenri (schema ${found}; this build understands ${supported}). Update and retry: npx scenri@latest`
|
|
301
302
|
);
|
|
302
303
|
this.name = "SchemaTooNewError";
|
|
303
304
|
}
|
|
@@ -2149,7 +2150,7 @@ function createCodexSetup(opts = {}) {
|
|
|
2149
2150
|
ok: false,
|
|
2150
2151
|
fallbackCommand: INSTALL_COMMAND,
|
|
2151
2152
|
docsUrl: INSTALL_DOCS_URL,
|
|
2152
|
-
detail: "Codex installed but is not on this app\u2019s PATH yet. Restart
|
|
2153
|
+
detail: "Codex installed but is not on this app\u2019s PATH yet. Restart Scenri, or install it yourself."
|
|
2153
2154
|
};
|
|
2154
2155
|
}
|
|
2155
2156
|
return { ok: true };
|
|
@@ -2308,7 +2309,7 @@ function createDemoEngine(saveImage) {
|
|
|
2308
2309
|
<rect width="100%" height="100%" fill="url(#g)"/>
|
|
2309
2310
|
<circle cx="${w * (0.25 + 0.5 * (seed * 37 % 100) / 100)}" cy="${h * 0.38}" r="${Math.min(w, h) * 0.18}" fill="#ffffff" opacity="0.25"/>
|
|
2310
2311
|
<text x="24" y="${h - 48}" font-family="Helvetica, Arial" font-size="${Math.max(14, Math.round(w / 42))}" fill="#ffffff" opacity="0.92">${esc(label)}</text>
|
|
2311
|
-
<text x="24" y="${h - 22}" font-family="Helvetica, Arial" font-size="12" fill="#ffffff" opacity="0.6">
|
|
2312
|
+
<text x="24" y="${h - 22}" font-family="Helvetica, Arial" font-size="12" fill="#ffffff" opacity="0.6">Scenri demo engine</text>
|
|
2312
2313
|
</svg>`;
|
|
2313
2314
|
return sharp5(Buffer.from(svg)).png().toBuffer();
|
|
2314
2315
|
}
|
|
@@ -2946,7 +2947,7 @@ function createContentFetcher(deps) {
|
|
|
2946
2947
|
const root = contentCacheRoot(env);
|
|
2947
2948
|
const staging = `${root}.staging`;
|
|
2948
2949
|
if (!deps.store.getSetting("content.disclosed")) {
|
|
2949
|
-
log(" fetching the
|
|
2950
|
+
log(" fetching the Scenri library (~95 MB, once, cached; set SCENRI_NO_CONTENT_FETCH=1 to disable)");
|
|
2950
2951
|
deps.store.setSetting("content.disclosed", "1");
|
|
2951
2952
|
}
|
|
2952
2953
|
try {
|
|
@@ -2982,7 +2983,7 @@ function createContentFetcher(deps) {
|
|
|
2982
2983
|
deps.store.setSetting("content.version", String(meta.version ?? ""));
|
|
2983
2984
|
} catch {
|
|
2984
2985
|
}
|
|
2985
|
-
log("
|
|
2986
|
+
log(" Scenri library ready");
|
|
2986
2987
|
return { ok: true, updated: true, error: null };
|
|
2987
2988
|
} catch (err) {
|
|
2988
2989
|
rmSync(staging, { recursive: true, force: true });
|
|
@@ -5900,11 +5901,11 @@ function registerAssetBuildRoutes(app, deps) {
|
|
|
5900
5901
|
engineId: engine?.capabilities().id ?? null,
|
|
5901
5902
|
engineName: engine?.capabilities().displayName ?? null,
|
|
5902
5903
|
/**
|
|
5903
|
-
* True when
|
|
5904
|
+
* True when Scenri cannot price this per image, because it is not billed
|
|
5904
5905
|
* through a key we hold. NOT the same as costing the user nothing: the
|
|
5905
5906
|
* local Codex engine spends the Codex allowance on their ChatGPT plan,
|
|
5906
5907
|
* which only OpenAI can meter. Copy built on this flag must say "nothing
|
|
5907
|
-
* billed through
|
|
5908
|
+
* billed through Scenri", never "free".
|
|
5908
5909
|
*/
|
|
5909
5910
|
free: engine ? await engine.costEstimate(COST_PROBE).catch(() => 0) <= 0 : true
|
|
5910
5911
|
};
|
|
@@ -6514,6 +6515,31 @@ 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
|
+
},
|
|
6528
|
+
{
|
|
6529
|
+
version: "0.3.0",
|
|
6530
|
+
date: "2026-08-20",
|
|
6531
|
+
title: "The name is Scenri.",
|
|
6532
|
+
sections: [
|
|
6533
|
+
{
|
|
6534
|
+
heading: "The name",
|
|
6535
|
+
body: "Written out, the product is Scenri. Every screen, every page of the documentation and every line the terminal prints now spells it that way. Lowercase stays where a machine reads it: the command you type, the folder your library lives in, and the settings that configure it are all unchanged."
|
|
6536
|
+
},
|
|
6537
|
+
{
|
|
6538
|
+
heading: "Browser tabs",
|
|
6539
|
+
body: "A tab now names what is open in it rather than the section it came from, so eight products in eight tabs read as eight product names. Settings and What's new say so, an open shot says Shot, and a tab whose page is still loading names its section instead of going blank."
|
|
6540
|
+
}
|
|
6541
|
+
]
|
|
6542
|
+
},
|
|
6517
6543
|
{
|
|
6518
6544
|
version: "0.2.3",
|
|
6519
6545
|
date: "2026-08-20",
|
|
@@ -6700,7 +6726,7 @@ function registerUpdateRoutes(app, deps) {
|
|
|
6700
6726
|
const apply = effectiveApply();
|
|
6701
6727
|
if (apply.phase !== "ready") return reply.status(409).send({ error: "no staged update to restart into" });
|
|
6702
6728
|
if (!runtime.supervised) {
|
|
6703
|
-
return reply.status(409).send({ error: "not supervised; restart
|
|
6729
|
+
return reply.status(409).send({ error: "not supervised; restart Scenri yourself", blockReason: "unsupervised" });
|
|
6704
6730
|
}
|
|
6705
6731
|
reply.send({ ok: true });
|
|
6706
6732
|
const exit = deps.exitImpl ?? ((code) => process.exit(code));
|
|
@@ -7500,12 +7526,14 @@ async function run() {
|
|
|
7500
7526
|
continue;
|
|
7501
7527
|
}
|
|
7502
7528
|
try {
|
|
7503
|
-
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
|
+
});
|
|
7504
7532
|
const info = await res.json();
|
|
7505
7533
|
if (info.name === readMeta().name) {
|
|
7506
7534
|
const url = `http://127.0.0.1:${PORT}`;
|
|
7507
7535
|
console.log(`
|
|
7508
|
-
|
|
7536
|
+
Scenri is already running \u2192 ${url}
|
|
7509
7537
|
`);
|
|
7510
7538
|
if (process.env.SCENRI_NO_OPEN !== "1") {
|
|
7511
7539
|
try {
|
|
@@ -7518,6 +7546,10 @@ async function run() {
|
|
|
7518
7546
|
}
|
|
7519
7547
|
} catch {
|
|
7520
7548
|
}
|
|
7549
|
+
console.error("");
|
|
7550
|
+
for (const line of portBusyLines(PORT)) console.error(` ${line}`);
|
|
7551
|
+
console.error("");
|
|
7552
|
+
process.exit(1);
|
|
7521
7553
|
}
|
|
7522
7554
|
throw err;
|
|
7523
7555
|
}
|
|
@@ -7527,7 +7559,7 @@ async function run() {
|
|
|
7527
7559
|
if (closing) return;
|
|
7528
7560
|
closing = true;
|
|
7529
7561
|
console.log(`
|
|
7530
|
-
${signal}: closing
|
|
7562
|
+
${signal}: closing Scenri\u2026`);
|
|
7531
7563
|
setTimeout(() => process.exit(1), 5e3).unref();
|
|
7532
7564
|
app.drain().then(() => process.exit(0)).catch(() => process.exit(1));
|
|
7533
7565
|
};
|
|
@@ -7538,10 +7570,10 @@ async function run() {
|
|
|
7538
7570
|
const query = token ? `/?t=${token}` : "";
|
|
7539
7571
|
const localUrl = `http://127.0.0.1:${PORT}${query}`;
|
|
7540
7572
|
console.log(`
|
|
7541
|
-
|
|
7573
|
+
Scenri Studio \u2192 ${localUrl}`);
|
|
7542
7574
|
for (const ip of reachableAt) console.log(` on your network \u2192 http://${ip}:${PORT}${query}`);
|
|
7543
|
-
console.log(` data dir \u2192 ${core.home}
|
|
7544
|
-
|
|
7575
|
+
console.log(` data dir \u2192 ${core.home}`);
|
|
7576
|
+
console.log(" Keep this window open while Scenri is running.\n");
|
|
7545
7577
|
if (!studioDist) console.log(" (studio UI not built, API only. Run: pnpm build)\n");
|
|
7546
7578
|
if (token) {
|
|
7547
7579
|
console.log(" Warning: the studio is reachable from your network.");
|
|
@@ -7551,10 +7583,22 @@ async function run() {
|
|
|
7551
7583
|
console.log(" For this machine only, unset SCENRI_HOST.\n");
|
|
7552
7584
|
}
|
|
7553
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
|
+
};
|
|
7554
7593
|
try {
|
|
7555
7594
|
const { default: open } = await import('open');
|
|
7556
|
-
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
|
+
});
|
|
7557
7600
|
} catch {
|
|
7601
|
+
tellUrl();
|
|
7558
7602
|
}
|
|
7559
7603
|
}
|
|
7560
7604
|
}
|