scenri 0.8.3 → 0.9.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,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.1](https://github.com/tonygorb/Scenri/compare/v0.9.0...v0.9.1) (2026-09-07)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **cli:** the desktop icon on Windows ends in the studio or a sentence, never in silence ([4a5dacb](https://github.com/tonygorb/Scenri/commit/4a5dacbdc1522fa04e506c92354182d0c3f08309))
9
+ * **cli:** the desktop icon on Windows ends in the studio or a sentence, never in silence ([86bdc1e](https://github.com/tonygorb/Scenri/commit/86bdc1e24728d3a706e690c13c08b0adb27714ec))
10
+ * **cli:** the dialog PowerShell keeps windowsHide, because a detached one shows nothing on Windows ([f140c04](https://github.com/tonygorb/Scenri/commit/f140c04b47008359ea4f2e194a08435b9321842a))
11
+
12
+ ## [0.9.0](https://github.com/tonygorb/Scenri/compare/v0.8.3...v0.9.0) (2026-09-06)
13
+
14
+
15
+ ### Features
16
+
17
+ * **cli:** a desktop icon that starts Scenri without a terminal ([35e8160](https://github.com/tonygorb/Scenri/commit/35e816086b3d2e10b32e454b4f40e93f4c95082a))
18
+ * **cli:** desktop status, install and quit routes for Settings ([c8478e1](https://github.com/tonygorb/Scenri/commit/c8478e1b2059ba40c85779469e97251c5669152b))
19
+ * **cli:** install, repair, remove and report the desktop launcher on macOS and Windows ([ee03601](https://github.com/tonygorb/Scenri/commit/ee036017dd5be738aff40b1adf2b04b7c45548fc))
20
+ * **cli:** launcher support paths, rotating logs, and adopting the running install into app/versions ([abde8cc](https://github.com/tonygorb/Scenri/commit/abde8cc23a0dd7eccb1e55d4aada1603108b68da))
21
+ * **cli:** offer the desktop icon once at a terminal, and keep an installed one current on every start ([5f56bb9](https://github.com/tonygorb/Scenri/commit/5f56bb9a45432df605c295b8893638cd75c0a2a0))
22
+ * **cli:** scenri desktop and the hidden open command, with a console-less supervisor ([fbe3fcf](https://github.com/tonygorb/Scenri/commit/fbe3fcf7ef16573448c22cd222c0cd6a37ca909c))
23
+ * **cli:** scenri open reuses a running Scenri or starts the supervisor detached and waits for it ([713e4f8](https://github.com/tonygorb/Scenri/commit/713e4f8f12014ceeff36bcea23718b2aac19ccec))
24
+ * **cli:** ship the desktop launcher's icons, an icns and an ico cut from the brand mark ([ef104e2](https://github.com/tonygorb/Scenri/commit/ef104e28654c31350dd103850a22dfa34f54b26e))
25
+ * **cli:** the frozen desktop bootstrap and the starting page ([151b2b9](https://github.com/tonygorb/Scenri/commit/151b2b92445a5f6b7602bedb4cc07c7131a85d61))
26
+ * **studio:** after Shut down the tab closes instead of showing a card ([42bdf89](https://github.com/tonygorb/Scenri/commit/42bdf896d9e6ed7a4cfc98e21b201372097e3c8c))
27
+ * **studio:** Desktop shortcut and Quit rows in About, and a verify hop before a build is adopted ([1587567](https://github.com/tonygorb/Scenri/commit/15875670bb5c3810437f19393440ecbffe51e68d))
28
+ * **studio:** Reuse setup is back under the record, beside Try again ([abe6b03](https://github.com/tonygorb/Scenri/commit/abe6b034490733701b6c990eb6688f0147dd81bc))
29
+ * **studio:** Reuse setup is back, and the shot sidebar and colour chips line up ([13345f2](https://github.com/tonygorb/Scenri/commit/13345f2a24d46cf3ae3f7c7d47ecd6c8a24a6665))
30
+ * **studio:** Shut down Scenri lives at the bottom of the brand menu, not in a Settings pane ([d21914b](https://github.com/tonygorb/Scenri/commit/d21914b9dd1b4e712120e1d1c42208e48f8833fa))
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * **cli:** a browser that cannot be opened gets the address in a dialog instead of silence ([ef3d6a1](https://github.com/tonygorb/Scenri/commit/ef3d6a11a963611aa73ff025630bf55fc1a92fa5))
36
+ * **cli:** a double-click answers at once, and every click stops paying for node probing ([25685e7](https://github.com/tonygorb/Scenri/commit/25685e7b8087bb1f794b1b5d30cd1252b4ea9b32))
37
+ * **cli:** scenri open logs the sentence it shows, so support reads the log instead of the screen ([b52350c](https://github.com/tonygorb/Scenri/commit/b52350ce16cd67dc8a0f8931d3f7db71975ece07))
38
+ * **cli:** the boot refresh heals node-path and node-major against the record ([4d09985](https://github.com/tonygorb/Scenri/commit/4d09985accf05b9eacd238249a2c61c697710b07))
39
+ * **cli:** the rendered starting page is not a support file, so the boot refresh leaves it alone ([d3b1396](https://github.com/tonygorb/Scenri/commit/d3b13966b778c4c51a49922984f394ac9d0e146d))
40
+ * **cli:** the starting page carries the studio URL in a meta, because open(1) drops a file URL's fragment ([4fa6c68](https://github.com/tonygorb/Scenri/commit/4fa6c682d09c34de76ef00256fb1c0bdd8f84133))
41
+ * **cli:** when the recorded node is gone the icon prefers a node of the same major, and a native-module death says so ([aa1dba0](https://github.com/tonygorb/Scenri/commit/aa1dba06d0d48c2a6ff3c4adcc47409f31f69311))
42
+ * **studio:** a colour chip wears its colour as the thumbnail's circle, and its menu hugs the chip ([e5970c1](https://github.com/tonygorb/Scenri/commit/e5970c14116f9721e5596167b3e821bcd45908b3))
43
+ * **studio:** the lifecycle overlays cover a Settings dialog instead of hiding behind it ([1e89433](https://github.com/tonygorb/Scenri/commit/1e894337948f1feb4816a67584098e4658d63244))
44
+ * **studio:** the shot record clamps by rows, its release is a verb, and its colour chip is the thumbnail's circle ([20da7a1](https://github.com/tonygorb/Scenri/commit/20da7a154272004db5cd22e0185bc08452e0a132))
45
+ * **studio:** the shot sidebar's composer, hairlines and copy control share one inset ([d5c4a99](https://github.com/tonygorb/Scenri/commit/d5c4a99e9bcc1cc43fd8673547cfdb73923b0d8b))
46
+
3
47
  ## [0.8.3](https://github.com/tonygorb/Scenri/compare/v0.8.2...v0.8.3) (2026-09-06)
4
48
 
5
49
 
package/README.md CHANGED
@@ -65,6 +65,8 @@ npx scenri
65
65
 
66
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.
67
67
 
68
+ On macOS and Windows, Scenri then asks once whether to put a **Scenri icon on your desktop**. Say yes and from then on a double-click starts Scenri and opens it in your browser, no terminal needed; the icon works offline and keeps working across updates. Said no? Settings, then About, then **Add to desktop**, or `npx scenri desktop` in a terminal. Scenri stays what it is, a local server and your browser: the icon is a launcher, not an app.
69
+
68
70
  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).
69
71
 
70
72
  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](https://github.com/tonygorb/scenri/blob/main/docs/INSTALL.md#troubleshooting).
@@ -0,0 +1,61 @@
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync } from 'fs';
2
+ import { join, win32 } from 'path';
3
+
4
+ // src/desktop/paths.ts
5
+ var LAUNCHER_SCHEMA = 1;
6
+ var launcherDir = (homedir) => join(homedir, ".scenri", "launcher");
7
+ var logsDir = (home) => join(home, "logs");
8
+ var launcherRecordPath = (homedir) => join(launcherDir(homedir), "launcher.json");
9
+ function readLauncherRecord(homedir) {
10
+ try {
11
+ const raw = JSON.parse(readFileSync(launcherRecordPath(homedir), "utf8"));
12
+ if (typeof raw?.schema !== "number" || typeof raw.home !== "string" || typeof raw.nodePath !== "string" || typeof raw.artifact?.path !== "string") {
13
+ return null;
14
+ }
15
+ return raw;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+ function writeLauncherRecord(homedir, record) {
21
+ const path = launcherRecordPath(homedir);
22
+ mkdirSync(launcherDir(homedir), { recursive: true });
23
+ const tmp = `${path}.tmp`;
24
+ writeFileSync(tmp, `${JSON.stringify(record, null, 2)}
25
+ `);
26
+ renameSync(tmp, path);
27
+ }
28
+ var REPLAYED = ["SCENRI_PORT", "SCENRI_HOST"];
29
+ function recordedEnv(env) {
30
+ const out = {};
31
+ for (const key of REPLAYED) if (env[key]) out[key] = env[key];
32
+ return out;
33
+ }
34
+ var KNOWN_FOLDER_DESKTOP = "[Environment]::GetFolderPath('Desktop')";
35
+ var PS_UTF8 = "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; ";
36
+ function powershellPath(env, exists = existsSync) {
37
+ const root = env.SystemRoot || env.SYSTEMROOT || env.windir || "C:\\Windows";
38
+ const absolute = win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
39
+ return exists(absolute) ? absolute : "powershell.exe";
40
+ }
41
+ async function desktopDir(deps) {
42
+ if (deps.env.SCENRI_DESKTOP_DIR) return deps.env.SCENRI_DESKTOP_DIR;
43
+ if (deps.platform === "darwin") return join(deps.homedir, "Desktop");
44
+ if (deps.platform === "win32") {
45
+ const out = await deps.runImpl(powershellPath(deps.env), [
46
+ "-NoProfile",
47
+ "-NonInteractive",
48
+ "-Command",
49
+ PS_UTF8 + KNOWN_FOLDER_DESKTOP
50
+ ]);
51
+ const dir = out.trim();
52
+ return dir || null;
53
+ }
54
+ return null;
55
+ }
56
+ var isSupportedPlatform = (platform) => platform === "darwin" || platform === "win32";
57
+ var launcherInstalled = (homedir) => existsSync(launcherRecordPath(homedir));
58
+
59
+ export { LAUNCHER_SCHEMA, PS_UTF8, desktopDir, isSupportedPlatform, launcherDir, launcherInstalled, launcherRecordPath, logsDir, powershellPath, readLauncherRecord, recordedEnv, writeLauncherRecord };
60
+ //# sourceMappingURL=chunk-2IGINCPU.js.map
61
+ //# sourceMappingURL=chunk-2IGINCPU.js.map
@@ -1,3 +1,6 @@
1
+ import { realpathSync } from 'fs';
2
+ import { resolve } from 'path';
3
+
1
4
  // src/bootError.ts
2
5
  var INSTALL_GUIDE = "https://github.com/tonygorb/scenri/blob/main/docs/INSTALL.md";
3
6
  var NATIVE_MARKERS = ["NODE_MODULE_VERSION", "Could not locate the bindings file", "ERR_DLOPEN_FAILED"];
@@ -30,7 +33,26 @@ function bootErrorLines(err) {
30
33
  "Set SCENRI_DEBUG=1 to see the full error."
31
34
  ];
32
35
  }
36
+ function shouldAdoptRunning(input) {
37
+ if (input.installKind !== "dev" || !input.theirHome) return true;
38
+ return realDir(input.theirHome) === realDir(input.ourHome);
39
+ }
40
+ function realDir(p) {
41
+ try {
42
+ return realpathSync(p);
43
+ } catch {
44
+ return resolve(p);
45
+ }
46
+ }
47
+ function anotherScenriLines(port, theirs, ours) {
48
+ return [
49
+ `Port ${port} is held by another Scenri, serving ${theirs}.`,
50
+ `This checkout would serve ${ours}, so it did not start.`,
51
+ "Stop that server, or start this checkout on a different port:",
52
+ ` SCENRI_PORT=${port + 1} pnpm dev`
53
+ ];
54
+ }
33
55
 
34
- export { bootErrorLines, portBusyLines };
35
- //# sourceMappingURL=chunk-WGIZJXNE.js.map
36
- //# sourceMappingURL=chunk-WGIZJXNE.js.map
56
+ export { anotherScenriLines, bootErrorLines, portBusyLines, shouldAdoptRunning };
57
+ //# sourceMappingURL=chunk-CMLJGPYC.js.map
58
+ //# sourceMappingURL=chunk-CMLJGPYC.js.map
@@ -0,0 +1,69 @@
1
+ import { isValidVersionDir, stagingDir, versionsDir, pruneStaged } from './chunk-4MAFHYAD.js';
2
+ import { execFile } from 'child_process';
3
+ import { existsSync, rmSync, mkdirSync, cpSync, renameSync, readFileSync } from 'fs';
4
+ import { dirname, join } from 'path';
5
+
6
+ async function adoptRunningInstall(deps) {
7
+ if (deps.installKind === "dev") return { adopted: false, reason: "dev" };
8
+ if (deps.installKind === "managed") return { adopted: false, reason: "managed" };
9
+ if (!deps.force && isValidVersionDir(deps.home, deps.pkg, deps.version)) return { adopted: false, reason: "present" };
10
+ const pkgRoot = dirname(dirname(deps.ownEntry));
11
+ if (!existsSync(deps.ownEntry) || manifestVersion(pkgRoot) !== deps.version) {
12
+ return { adopted: false, reason: "no-source" };
13
+ }
14
+ const nested = existsSync(join(pkgRoot, "node_modules", "fastify"));
15
+ const source = nested ? pkgRoot : dirname(pkgRoot);
16
+ const work = join(stagingDir(deps.home), `adopt-${deps.version}`);
17
+ rmSync(work, { recursive: true, force: true });
18
+ const dest = nested ? join(work, "node_modules", deps.pkg) : join(work, "node_modules");
19
+ mkdirSync(dirname(dest), { recursive: true });
20
+ try {
21
+ cpSync(source, dest, { recursive: true, verbatimSymlinks: true });
22
+ const copied = join(work, "node_modules", deps.pkg, "dist", "index.js");
23
+ if (!await (deps.verifyImpl ?? verifyEntry)(copied)) return { adopted: false, reason: "no-source" };
24
+ const target = join(versionsDir(deps.home), deps.version);
25
+ mkdirSync(dirname(target), { recursive: true });
26
+ const aside = join(stagingDir(deps.home), `replaced-${deps.version}`);
27
+ rmSync(aside, { recursive: true, force: true });
28
+ try {
29
+ if (existsSync(target)) renameSync(target, aside);
30
+ renameSync(work, target);
31
+ } catch {
32
+ return { adopted: false, reason: "busy" };
33
+ }
34
+ rmSync(aside, { recursive: true, force: true });
35
+ } finally {
36
+ rmSync(work, { recursive: true, force: true });
37
+ }
38
+ pruneStaged(deps.home, deps.pkg, /* @__PURE__ */ new Set([deps.version]));
39
+ return { adopted: true };
40
+ }
41
+ function verifyEntry(entry) {
42
+ return new Promise((resolve) => {
43
+ execFile(
44
+ process.execPath,
45
+ [entry, "verify"],
46
+ { encoding: "utf8", timeout: 12e4, windowsHide: true, maxBuffer: 4 * 1024 * 1024 },
47
+ (err, stdout) => {
48
+ if (err) return resolve(false);
49
+ const line = String(stdout).trim().split("\n").reverse().find((l) => l.startsWith("{"));
50
+ try {
51
+ resolve(Boolean(line && JSON.parse(line).ok === true));
52
+ } catch {
53
+ resolve(false);
54
+ }
55
+ }
56
+ );
57
+ });
58
+ }
59
+ function manifestVersion(pkgRoot) {
60
+ try {
61
+ return JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8")).version ?? null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ export { adoptRunningInstall };
68
+ //# sourceMappingURL=chunk-ECS5LRHB.js.map
69
+ //# sourceMappingURL=chunk-ECS5LRHB.js.map