scenri 0.4.0 → 0.4.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 +22 -0
- package/README.md +1 -1
- package/dist/{chunk-3W2RHBZC.js → chunk-WIIQZN3K.js} +15 -5
- package/dist/{cli-HF5KPURA.js → cli-PBDPZQ4G.js} +3 -3
- package/dist/index.js +9 -3
- package/dist/serve.js +68 -14
- package/package.json +1 -1
- package/studio-dist/assets/{index-CC0sP0Ws.css → index-CHiI290n.css} +1 -1
- package/studio-dist/assets/index-DZ_dkMVl.js +102 -0
- package/studio-dist/index.html +2 -2
- package/studio-dist/assets/index-CwJxyJAh.js +0 -102
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.2](https://github.com/tonygorb/Scenri/compare/v0.4.1...v0.4.2) (2026-08-23)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* one Updates row, one Update verb, and a gift instead of a dot ([1ecf017](https://github.com/tonygorb/Scenri/commit/1ecf01711202419a06fb9f2e5e1c998c53c7d7eb))
|
|
9
|
+
* the boot look dies with the server ([8f5c575](https://github.com/tonygorb/Scenri/commit/8f5c575c0363a9dbf59efbeda2f5c6ab19a35ceb))
|
|
10
|
+
* the boot look dies with the server ([3c7c018](https://github.com/tonygorb/Scenri/commit/3c7c018ff177a426bc327c32704ca15ebec2c107))
|
|
11
|
+
* the check button steps aside once an update is found ([2fb0400](https://github.com/tonygorb/Scenri/commit/2fb040098b31a32df617d2453f8c6756e5fb32fe))
|
|
12
|
+
* the check button steps aside once an update is found ([ba37979](https://github.com/tonygorb/Scenri/commit/ba37979a2023315c94bb45e1d422c1762c574267))
|
|
13
|
+
|
|
14
|
+
## [0.4.1](https://github.com/tonygorb/Scenri/compare/v0.4.0...v0.4.1) (2026-08-23)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* a clicked update check answers even when automatic checks are off ([a17b8e8](https://github.com/tonygorb/Scenri/commit/a17b8e83c3f006ff518a34412e918099271f27cf))
|
|
20
|
+
* dispatch launch from the module's own path, not argv ([46cd5ea](https://github.com/tonygorb/Scenri/commit/46cd5ea9679ee1261eea25b215590bcdf6027a67))
|
|
21
|
+
* make the local update experience actually deliver updates ([8c5aa0a](https://github.com/tonygorb/Scenri/commit/8c5aa0aa454c4713eb90e1f27bc70867ef93129f))
|
|
22
|
+
* stage a discovered update in the background, ready to restart ([449b9ad](https://github.com/tonygorb/Scenri/commit/449b9ad7c775dd4a4269d1cb41a50be9d83adbe2))
|
|
23
|
+
* update UI narrates the whole arc and never answers with silence ([f964213](https://github.com/tonygorb/Scenri/commit/f964213e03668c200c55dd4bc73350021165bbbe))
|
|
24
|
+
|
|
3
25
|
## [0.4.0](https://github.com/tonygorb/Scenri/compare/v0.3.5...v0.4.0) (2026-08-23)
|
|
4
26
|
|
|
5
27
|
|
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ pnpm dev # starts the server on 127.0.0.1:4747
|
|
|
95
95
|
- **Iteration is the product.** A version tree, not a prompt box. Branch, compare, keep the winners.
|
|
96
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.
|
|
97
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.
|
|
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](https://github.com/tonygorb/scenri/blob/main/docs/updates.md)).
|
|
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, when one is found, the download of that release from npm, staged locally until you choose to restart), 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](https://github.com/tonygorb/scenri/blob/main/docs/updates.md)).
|
|
99
99
|
|
|
100
100
|
## Engines
|
|
101
101
|
|
|
@@ -61,6 +61,7 @@ function createUpdateChecker(deps) {
|
|
|
61
61
|
};
|
|
62
62
|
let inflight = null;
|
|
63
63
|
let lastForceAt = 0;
|
|
64
|
+
let onResultFn = null;
|
|
64
65
|
const enabled = () => env.SCENRI_NO_UPDATE_CHECK !== "1" && deps.store.getSetting("update.enabled") !== "false";
|
|
65
66
|
async function fetchLatest() {
|
|
66
67
|
if (!deps.store.getSetting("update.disclosed")) {
|
|
@@ -71,10 +72,12 @@ function createUpdateChecker(deps) {
|
|
|
71
72
|
if (res.error) return { ...cached(), error: res.error };
|
|
72
73
|
deps.store.setSetting("update.latest", res.latest ?? "");
|
|
73
74
|
deps.store.setSetting("update.checkedAt", String(now()));
|
|
74
|
-
|
|
75
|
+
const result = cached();
|
|
76
|
+
onResultFn?.(result);
|
|
77
|
+
return result;
|
|
75
78
|
}
|
|
76
79
|
async function check(force = false) {
|
|
77
|
-
if (!enabled()) return cached();
|
|
80
|
+
if (!enabled() && !force) return cached();
|
|
78
81
|
if (inflight) return inflight;
|
|
79
82
|
const prior = cached();
|
|
80
83
|
const fresh = prior.checkedAt !== null && now() - prior.checkedAt < CACHE_MS;
|
|
@@ -93,7 +96,14 @@ function createUpdateChecker(deps) {
|
|
|
93
96
|
setTimeout(() => void check(), 1e4).unref();
|
|
94
97
|
setInterval(() => void check(), CACHE_MS).unref();
|
|
95
98
|
}
|
|
96
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
enabled,
|
|
101
|
+
check,
|
|
102
|
+
schedule,
|
|
103
|
+
onResult: (fn) => {
|
|
104
|
+
onResultFn = fn;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
97
107
|
}
|
|
98
108
|
function findNpm(opts = {}) {
|
|
99
109
|
const env = opts.env ?? process.env;
|
|
@@ -220,5 +230,5 @@ async function stageVersion(deps) {
|
|
|
220
230
|
}
|
|
221
231
|
|
|
222
232
|
export { classify, createUpdateChecker, fetchDistTagLatest, findNpm, stageVersion };
|
|
223
|
-
//# sourceMappingURL=chunk-
|
|
224
|
-
//# sourceMappingURL=chunk-
|
|
233
|
+
//# sourceMappingURL=chunk-WIIQZN3K.js.map
|
|
234
|
+
//# sourceMappingURL=chunk-WIIQZN3K.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stageVersion, fetchDistTagLatest, findNpm } from './chunk-
|
|
1
|
+
import { stageVersion, fetchDistTagLatest, findNpm } from './chunk-WIIQZN3K.js';
|
|
2
2
|
import { readMeta } from './chunk-Y3ZPBPLP.js';
|
|
3
3
|
import { defaultHome, newestStaged, compareSemver } from './chunk-4MAFHYAD.js';
|
|
4
4
|
|
|
@@ -63,5 +63,5 @@ async function runUpdateCommand(opts) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
export { runUpdateCommand };
|
|
66
|
-
//# sourceMappingURL=cli-
|
|
67
|
-
//# sourceMappingURL=cli-
|
|
66
|
+
//# sourceMappingURL=cli-PBDPZQ4G.js.map
|
|
67
|
+
//# sourceMappingURL=cli-PBDPZQ4G.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { bootErrorLines } from './chunk-WGIZJXNE.js';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
3
4
|
|
|
4
5
|
// src/args.ts
|
|
5
6
|
function parseArgs(argv) {
|
|
@@ -41,6 +42,11 @@ SCENRI_CONTENT_URL.
|
|
|
41
42
|
`;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
// src/builtEntry.ts
|
|
46
|
+
function isBuiltEntry(entryPath) {
|
|
47
|
+
return /[\\/]dist[\\/]/.test(entryPath);
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
// src/index.ts
|
|
45
51
|
var command = parseArgs(process.argv.slice(2));
|
|
46
52
|
try {
|
|
@@ -64,13 +70,13 @@ try {
|
|
|
64
70
|
await (await import('./serve.js')).serve();
|
|
65
71
|
break;
|
|
66
72
|
case "update": {
|
|
67
|
-
const { runUpdateCommand } = await import('./cli-
|
|
73
|
+
const { runUpdateCommand } = await import('./cli-PBDPZQ4G.js');
|
|
68
74
|
process.exit(await runUpdateCommand(command));
|
|
69
75
|
break;
|
|
70
76
|
}
|
|
71
77
|
case "launch": {
|
|
72
|
-
const ownEntry =
|
|
73
|
-
if (
|
|
78
|
+
const ownEntry = fileURLToPath(import.meta.url);
|
|
79
|
+
if (!isBuiltEntry(ownEntry)) {
|
|
74
80
|
await (await import('./serve.js')).serve();
|
|
75
81
|
break;
|
|
76
82
|
}
|
package/dist/serve.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { portBusyLines } from './chunk-WGIZJXNE.js';
|
|
2
|
-
import { createUpdateChecker, classify,
|
|
2
|
+
import { createUpdateChecker, classify, findNpm, stageVersion } from './chunk-WIIQZN3K.js';
|
|
3
3
|
import { readMeta, repoSlug } from './chunk-Y3ZPBPLP.js';
|
|
4
|
-
import {
|
|
4
|
+
import { compareSemver, newestStaged } from './chunk-4MAFHYAD.js';
|
|
5
5
|
import { networkInterfaces, homedir, tmpdir } from 'os';
|
|
6
6
|
import { sep, join, dirname, normalize } from 'path';
|
|
7
7
|
import Database from 'better-sqlite3';
|
|
@@ -6781,6 +6781,31 @@ function registerImageRoutes(app, deps) {
|
|
|
6781
6781
|
|
|
6782
6782
|
// src/release/notes.data.ts
|
|
6783
6783
|
var RELEASES = [
|
|
6784
|
+
{
|
|
6785
|
+
version: "0.4.2",
|
|
6786
|
+
date: "2026-08-23",
|
|
6787
|
+
sections: [
|
|
6788
|
+
{
|
|
6789
|
+
heading: "Updates",
|
|
6790
|
+
body: "Settings now tells the update story in one row: what you are on, what arrived, and a single Update button for whichever step remains. The floating notice carries a small gift where the dot was."
|
|
6791
|
+
}
|
|
6792
|
+
]
|
|
6793
|
+
},
|
|
6794
|
+
{
|
|
6795
|
+
version: "0.4.1",
|
|
6796
|
+
date: "2026-08-23",
|
|
6797
|
+
title: "Updates now arrive by themselves.",
|
|
6798
|
+
sections: [
|
|
6799
|
+
{
|
|
6800
|
+
heading: "Updates",
|
|
6801
|
+
body: "A new version now downloads quietly in the background, verifies itself next to the running copy, and asks for a single click to restart. This is also the release that makes in-app updating truly work on macOS and Linux, where the one-click path silently never engaged. Checking by hand always answers, even when automatic checks are off, and a restart never interrupts running work."
|
|
6802
|
+
},
|
|
6803
|
+
{
|
|
6804
|
+
heading: "Existing installs",
|
|
6805
|
+
body: "Copies first started with npx before this release cannot pick the fix up on their own. Run npx scenri@latest once in a terminal; every update after that arrives without it."
|
|
6806
|
+
}
|
|
6807
|
+
]
|
|
6808
|
+
},
|
|
6784
6809
|
{
|
|
6785
6810
|
version: "0.4.0",
|
|
6786
6811
|
date: "2026-08-23",
|
|
@@ -7002,6 +7027,37 @@ function registerUpdateRoutes(app, deps) {
|
|
|
7002
7027
|
npmProbe ??= findNpm() !== null;
|
|
7003
7028
|
return npmProbe ? null : "no-npm";
|
|
7004
7029
|
};
|
|
7030
|
+
const startStaging = (target) => {
|
|
7031
|
+
applyState = { phase: "staging", version: target, error: null };
|
|
7032
|
+
void (deps.stageImpl ?? stageVersion)({
|
|
7033
|
+
home: core.home,
|
|
7034
|
+
pkg: meta.name,
|
|
7035
|
+
source: { version: target },
|
|
7036
|
+
keep: /* @__PURE__ */ new Set([meta.version])
|
|
7037
|
+
}).then((res) => {
|
|
7038
|
+
applyState = res.ok ? { phase: "ready", version: res.version, error: null } : { phase: "error", version: null, error: `${res.reason}: ${res.detail}` };
|
|
7039
|
+
}).catch((err) => {
|
|
7040
|
+
applyState = { phase: "error", version: null, error: String(err?.message ?? err) };
|
|
7041
|
+
});
|
|
7042
|
+
};
|
|
7043
|
+
const maybeAutoStage = (r) => {
|
|
7044
|
+
if (applyState.phase === "error") applyState = { phase: "idle", version: null, error: null };
|
|
7045
|
+
if (!updates.enabled()) return;
|
|
7046
|
+
if (!r.latest || classify(meta.version, r.latest) === null) return;
|
|
7047
|
+
if (blockReason() !== null) return;
|
|
7048
|
+
if (deps.busyCount() > 0) return;
|
|
7049
|
+
const apply = effectiveApply();
|
|
7050
|
+
if (apply.phase === "staging") return;
|
|
7051
|
+
if (apply.phase === "ready" && compareSemver(r.latest, apply.version ?? "") <= 0) return;
|
|
7052
|
+
startStaging(r.latest);
|
|
7053
|
+
};
|
|
7054
|
+
updates.onResult(maybeAutoStage);
|
|
7055
|
+
const bootLook = setTimeout(() => {
|
|
7056
|
+
updates.check().then(maybeAutoStage).catch(() => {
|
|
7057
|
+
});
|
|
7058
|
+
}, deps.bootLookMs ?? 15e3);
|
|
7059
|
+
bootLook.unref();
|
|
7060
|
+
app.addHook("onClose", async () => clearTimeout(bootLook));
|
|
7005
7061
|
const updateStatus = async (force = false) => {
|
|
7006
7062
|
const r = await updates.check(force);
|
|
7007
7063
|
const kind = r.latest ? classify(meta.version, r.latest) : null;
|
|
@@ -7034,27 +7090,25 @@ function registerUpdateRoutes(app, deps) {
|
|
|
7034
7090
|
const block = blockReason();
|
|
7035
7091
|
if (block)
|
|
7036
7092
|
return reply.status(409).send({ error: `one-click update cannot run here (${block})`, blockReason: block });
|
|
7037
|
-
if (applyState.phase === "staging") return reply.status(409).send({ error: "an update is already staging" });
|
|
7038
7093
|
const r = await updates.check();
|
|
7039
7094
|
const kind = r.latest ? classify(meta.version, r.latest) : null;
|
|
7040
7095
|
if (!r.latest || kind === null) return reply.status(409).send({ error: "nothing newer to install" });
|
|
7041
7096
|
const target = r.latest;
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
}).then((res) => {
|
|
7049
|
-
applyState = res.ok ? { phase: "ready", version: res.version, error: null } : { phase: "error", version: null, error: `${res.reason}: ${res.detail}` };
|
|
7050
|
-
}).catch((err) => {
|
|
7051
|
-
applyState = { phase: "error", version: null, error: String(err?.message ?? err) };
|
|
7052
|
-
});
|
|
7097
|
+
const current = effectiveApply();
|
|
7098
|
+
if ((current.phase === "staging" || current.phase === "ready") && current.version === target) {
|
|
7099
|
+
return { ok: true, staging: target };
|
|
7100
|
+
}
|
|
7101
|
+
if (current.phase === "staging") return reply.status(409).send({ error: "an update is already staging" });
|
|
7102
|
+
startStaging(target);
|
|
7053
7103
|
return { ok: true, staging: target };
|
|
7054
7104
|
});
|
|
7055
7105
|
app.post("/api/update/restart", async (_req, reply) => {
|
|
7056
7106
|
const apply = effectiveApply();
|
|
7057
7107
|
if (apply.phase !== "ready") return reply.status(409).send({ error: "no staged update to restart into" });
|
|
7108
|
+
const busy = deps.busyCount();
|
|
7109
|
+
if (busy > 0) {
|
|
7110
|
+
return reply.status(409).send({ error: `work is still running (${busy} task${busy === 1 ? "" : "s"})`, blockReason: "busy" });
|
|
7111
|
+
}
|
|
7058
7112
|
if (!runtime.supervised) {
|
|
7059
7113
|
return reply.status(409).send({ error: "not supervised; restart Scenri yourself", blockReason: "unsupervised" });
|
|
7060
7114
|
}
|