scenri 0.19.0 → 0.19.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 +11 -0
- package/README.md +2 -2
- package/dist/serve.js +74 -7
- package/package.json +1 -1
- package/studio-dist/assets/{index-BPTXm681.js → index-C8NkERt4.js} +23 -23
- package/studio-dist/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.19.1](https://github.com/tonygorb/Scenri/compare/v0.19.0...v0.19.1) (2026-09-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Codex keeps no picture copies, and the library download is pinned ([e29537f](https://github.com/tonygorb/Scenri/commit/e29537f93860ecc095dad448bfb742c67c49388c))
|
|
9
|
+
* **codex:** keep no copies of a picture, and switch off codex's plugins ([d81d6c2](https://github.com/tonygorb/Scenri/commit/d81d6c21f4ee82990cb26e2f5263bb43b0e4e563))
|
|
10
|
+
* **content:** install only the library archive this version was tested against ([40f7659](https://github.com/tonygorb/Scenri/commit/40f765955475db6b5f51fb8d6bb286b40c7dddc4))
|
|
11
|
+
* **studio:** tell the bell at once when the scene studio starts work ([53ba5e4](https://github.com/tonygorb/Scenri/commit/53ba5e4f46c272d939c1ebaa41b839eb3513843e))
|
|
12
|
+
* **studio:** tell the bell at once when the scene studio starts work ([a3689b7](https://github.com/tonygorb/Scenri/commit/a3689b7b42480b14e702df94a7001d08f5bb6ca5))
|
|
13
|
+
|
|
3
14
|
## [0.19.0](https://github.com/tonygorb/Scenri/compare/v0.18.2...v0.19.0) (2026-09-26)
|
|
4
15
|
|
|
5
16
|
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npx scenri
|
|
|
21
21
|
|
|
22
22
|
<sub>Needs [Node.js](https://nodejs.org) 22 or newer. New to any of this? The [install guide](https://github.com/tonygorb/scenri/blob/main/docs/INSTALL.md) walks through every step.</sub>
|
|
23
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
|
|
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 Ridgeline Trail is chosen, an at sign picks the presenter Paloma, a slash picks the Chalk Steps 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">
|
|
25
25
|
|
|
26
26
|
<sub>Pick a product, pick a presenter, pick a scene, then write the direction. That is the brief.</sub>
|
|
27
27
|
|
|
@@ -39,7 +39,7 @@ It runs as a local server on your own computer. Your brands, your images, and yo
|
|
|
39
39
|
|
|
40
40
|
## What a brief produces
|
|
41
41
|
|
|
42
|
-
The film above ends on the shot its brief made: the
|
|
42
|
+
The film above ends on the shot its brief made: the Ridgeline Trail product, the presenter Paloma, the Chalk Steps scene, and a paragraph of written direction. The chips carry the identity; the prose does the art direction. Here are eight more shots from the home wall, each built exactly that way:
|
|
43
43
|
|
|
44
44
|
<!-- One paragraph, no whitespace between the tags: a newline here renders as
|
|
45
45
|
a fixed word-space, and 4 x 24.5% plus three spaces overflows a phone screen
|
package/dist/serve.js
CHANGED
|
@@ -10,7 +10,7 @@ import { dirname, join, normalize, basename } from 'path';
|
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import fs, { existsSync, readFileSync, mkdirSync, createReadStream, rmSync, readdirSync, statSync, renameSync, lstatSync, unlinkSync } from 'fs';
|
|
12
12
|
import { homedir, tmpdir, networkInterfaces } from 'os';
|
|
13
|
-
import { readFile, stat, copyFile, lstat, readdir, mkdtemp, rm, access, rename, unlink, writeFile } from 'fs/promises';
|
|
13
|
+
import { readFile, stat, copyFile, lstat, readdir, mkdtemp, rm, access, rmdir, rename, unlink, writeFile } from 'fs/promises';
|
|
14
14
|
import { spawn, execFile } from 'child_process';
|
|
15
15
|
import sharp22 from 'sharp';
|
|
16
16
|
import Fastify from 'fastify';
|
|
@@ -829,12 +829,15 @@ function execArgs(dir, effort = "low") {
|
|
|
829
829
|
CODEX_MODEL,
|
|
830
830
|
"-c",
|
|
831
831
|
`model_reasoning_effort="${effort}"`,
|
|
832
|
+
// No session files: codex would otherwise keep every run's transcript, the
|
|
833
|
+
// pictures in it as base64, long after Scenri has let the work go.
|
|
834
|
+
"--ephemeral",
|
|
832
835
|
"-C",
|
|
833
836
|
dir,
|
|
834
837
|
"-"
|
|
835
838
|
];
|
|
836
839
|
}
|
|
837
|
-
function
|
|
840
|
+
function extensionsOffArgs(env) {
|
|
838
841
|
const user = env.HOME || env.USERPROFILE;
|
|
839
842
|
const home = env.CODEX_HOME || (user ? join(user, ".codex") : "");
|
|
840
843
|
if (!home) return [];
|
|
@@ -844,12 +847,12 @@ function mcpOffArgs(env) {
|
|
|
844
847
|
} catch {
|
|
845
848
|
return [];
|
|
846
849
|
}
|
|
847
|
-
const
|
|
850
|
+
const keys = /* @__PURE__ */ new Set();
|
|
848
851
|
for (const line of text.split(/\r?\n/)) {
|
|
849
|
-
const m = /^\s*\[\s*mcp_servers\.(?:"([A-Za-z0-9_
|
|
850
|
-
if (m)
|
|
852
|
+
const m = /^\s*\[\s*(mcp_servers|plugins)\.(?:"([A-Za-z0-9_@-]+)"|([A-Za-z0-9_@-]+))\s*\]\s*(?:#.*)?$/.exec(line);
|
|
853
|
+
if (m) keys.add(`${m[1]}.${m[2] ?? m[3]}`);
|
|
851
854
|
}
|
|
852
|
-
return [...
|
|
855
|
+
return [...keys].flatMap((key) => ["-c", `${key}.enabled=false`]);
|
|
853
856
|
}
|
|
854
857
|
var TAIL_BYTES = 4096;
|
|
855
858
|
var DETAIL_CHARS = 800;
|
|
@@ -939,7 +942,7 @@ function createRunner(opts = {}) {
|
|
|
939
942
|
const spawnCodex = (exe, args, stdinOpen) => {
|
|
940
943
|
const stdio = [stdinOpen ? "pipe" : "ignore", "pipe", "pipe"];
|
|
941
944
|
const env = buildChildEnv(parentEnv, [...ignoreEnvKeys(), ...SIBLING_PROVIDER_KEYS]);
|
|
942
|
-
if (args[0] === "exec") args = ["exec", ...
|
|
945
|
+
if (args[0] === "exec") args = ["exec", ...extensionsOffArgs(parentEnv), ...args.slice(1)];
|
|
943
946
|
return exe.direct ? spawnImpl(exe.command, args, { stdio, env, ...platform !== "win32" ? { detached: true } : {} }) : spawnImpl([exe.command, ...args.map(winArg)].join(" "), [], { stdio, env, shell: true });
|
|
944
947
|
};
|
|
945
948
|
async function run4(args, signal, io) {
|
|
@@ -1716,9 +1719,43 @@ function createCodexEngine(opts) {
|
|
|
1716
1719
|
const buf = await readLeftFile(join(dir, name), name);
|
|
1717
1720
|
if (buf.length === 0) throw new Error(`codex: ${name} is empty`);
|
|
1718
1721
|
hashes.push(saveImage(buf));
|
|
1722
|
+
await forgetGenerated(buf);
|
|
1719
1723
|
}
|
|
1720
1724
|
return hashes;
|
|
1721
1725
|
}
|
|
1726
|
+
async function forgetGenerated(buf) {
|
|
1727
|
+
const home = generatedImagesDir();
|
|
1728
|
+
const recent = Date.now() - 30 * 6e4;
|
|
1729
|
+
let names;
|
|
1730
|
+
try {
|
|
1731
|
+
names = await readdir(home);
|
|
1732
|
+
} catch {
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
const dropIfSame = async (path) => {
|
|
1736
|
+
const st = await lstat(path);
|
|
1737
|
+
if (!st.isFile() || st.size !== buf.length) return false;
|
|
1738
|
+
if (!(await readFile(path)).equals(buf)) return false;
|
|
1739
|
+
await unlink(path);
|
|
1740
|
+
return true;
|
|
1741
|
+
};
|
|
1742
|
+
for (const name of names) {
|
|
1743
|
+
const at = join(home, name);
|
|
1744
|
+
try {
|
|
1745
|
+
const st = await lstat(at);
|
|
1746
|
+
if (st.mtimeMs < recent) continue;
|
|
1747
|
+
if (st.isFile()) {
|
|
1748
|
+
await dropIfSame(at);
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
if (!st.isDirectory()) continue;
|
|
1752
|
+
let dropped = false;
|
|
1753
|
+
for (const file of await readdir(at)) dropped = await dropIfSame(join(at, file)) || dropped;
|
|
1754
|
+
if (dropped && (await readdir(at)).length === 0) await rmdir(at);
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1722
1759
|
async function recoverFromGenerated(before, claimed) {
|
|
1723
1760
|
const home = generatedImagesDir();
|
|
1724
1761
|
let names;
|
|
@@ -6034,6 +6071,10 @@ function shotWordsFor(core, brandId) {
|
|
|
6034
6071
|
}
|
|
6035
6072
|
var CONTENT_VERSION = 3;
|
|
6036
6073
|
var CONTENT_TAG = "content-v3";
|
|
6074
|
+
var CONTENT_SHA256 = "c736f1f9142bc428e19bb80bc139c2d9e60a9dce7449b322760ffe8df333b734";
|
|
6075
|
+
function archiveMatches(bytes, expected = CONTENT_SHA256) {
|
|
6076
|
+
return createHash("sha256").update(bytes).digest("hex") === expected;
|
|
6077
|
+
}
|
|
6037
6078
|
var DEFAULT_CONTENT_URL = `https://github.com/tonygorb/scenri/releases/download/${CONTENT_TAG}/scenri-content.zip`;
|
|
6038
6079
|
var TIMEOUT_MS = 30 * 60 * 1e3;
|
|
6039
6080
|
function resolveContentUrl(env = process.env, override) {
|
|
@@ -6085,6 +6126,7 @@ function createContentFetcher(deps) {
|
|
|
6085
6126
|
const doFetch = deps.fetchImpl ?? fetch;
|
|
6086
6127
|
const url = resolveContentUrl(env, deps.url);
|
|
6087
6128
|
const custom = Boolean(deps.url ?? env.SCENRI_CONTENT_URL);
|
|
6129
|
+
const expected = deps.sha256 !== void 0 ? deps.sha256 : custom ? null : CONTENT_SHA256;
|
|
6088
6130
|
const enabled = () => env.SCENRI_NO_CONTENT_FETCH !== "1" && deps.store.getSetting("content.enabled") !== "false";
|
|
6089
6131
|
let inflight = null;
|
|
6090
6132
|
async function download() {
|
|
@@ -6105,6 +6147,13 @@ function createContentFetcher(deps) {
|
|
|
6105
6147
|
} finally {
|
|
6106
6148
|
clearTimeout(timer);
|
|
6107
6149
|
}
|
|
6150
|
+
if (expected && !archiveMatches(bytes, expected)) {
|
|
6151
|
+
return {
|
|
6152
|
+
ok: false,
|
|
6153
|
+
updated: false,
|
|
6154
|
+
error: "the library download does not match the archive this version expects"
|
|
6155
|
+
};
|
|
6156
|
+
}
|
|
6108
6157
|
const refused = await installContentArchive(bytes, root);
|
|
6109
6158
|
if (refused) return { ok: false, updated: false, error: refused };
|
|
6110
6159
|
try {
|
|
@@ -14843,6 +14892,24 @@ function registerImageRoutes(app, deps) {
|
|
|
14843
14892
|
|
|
14844
14893
|
// src/release/notes.data.ts
|
|
14845
14894
|
var RELEASES = [
|
|
14895
|
+
{
|
|
14896
|
+
version: "0.19.1",
|
|
14897
|
+
date: "2026-09-26",
|
|
14898
|
+
sections: [
|
|
14899
|
+
{
|
|
14900
|
+
heading: "Codex",
|
|
14901
|
+
body: "Codex keeps no copy of a picture once Scenri has it, neither a saved conversation nor a second file, and its plugins stay off while Scenri runs it."
|
|
14902
|
+
},
|
|
14903
|
+
{
|
|
14904
|
+
heading: "Library",
|
|
14905
|
+
body: "The library download is checked against the exact archive this version was released with, and nothing is installed unless it matches."
|
|
14906
|
+
},
|
|
14907
|
+
{
|
|
14908
|
+
heading: "Fixes",
|
|
14909
|
+
body: "A scene draw shows in the bell as soon as it starts, and the Scenes wall no longer calls it not drawn yet while it is drawing."
|
|
14910
|
+
}
|
|
14911
|
+
]
|
|
14912
|
+
},
|
|
14846
14913
|
{
|
|
14847
14914
|
version: "0.19.0",
|
|
14848
14915
|
date: "2026-09-26",
|