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 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 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">
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 Selvedge Trucker product, the presenter Maren, the Editorial Walk 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:
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 mcpOffArgs(env) {
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 names = /* @__PURE__ */ new Set();
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_-]+)"|([A-Za-z0-9_-]+))\s*\]\s*(?:#.*)?$/.exec(line);
850
- if (m) names.add(m[1] ?? m[2]);
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 [...names].flatMap((name) => ["-c", `mcp_servers.${name}.enabled=false`]);
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", ...mcpOffArgs(parentEnv), ...args.slice(1)];
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scenri",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "author": "Tony Gorb <hello@scenri.co>",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",