@zerwiz/ymir 0.1.7 → 0.1.8
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/.agents/skills/galdr-ymirsystem/assets/installation.md +74 -0
- package/CHANGELOG.md +330 -1
- package/bin/design-icon.sh +7 -4
- package/bin/einherjar-spawn.sh +9 -3
- package/bin/eir-doctor.sh +43 -1
- package/bin/electron-lib.sh +34 -0
- package/bin/smidja-board.sh +136 -0
- package/bin/smidja-lib.sh +49 -0
- package/bin/ymir-install.sh +41 -6
- package/bin/ymir-plan.sh +48 -14
- package/bin/ymir-style.sh +108 -0
- package/bin/ymir-validate.sh +36 -3
- package/bin/ymir.js +82 -34
- package/docs/design.md +2 -2
- package/package.json +1 -1
- package/scripts/start.sh +13 -1
|
@@ -142,6 +142,75 @@ bin/ymir-migrate.sh apply # carry it
|
|
|
142
142
|
|
|
143
143
|
Stop the runtime first if you want no stale pid files; nothing is lost either way.
|
|
144
144
|
|
|
145
|
+
## Operating it afterwards — the doors, the cloth, and the shapes
|
|
146
|
+
|
|
147
|
+
### The doors (the CLI), named for the figure who does the work
|
|
148
|
+
|
|
149
|
+
`npm install -g @zerwiz/ymir` puts **two** commands on PATH: `ymir` (the front
|
|
150
|
+
door) and `ymir-install` (the bare installer). Everything else the operator
|
|
151
|
+
needs is a door on `ymir` — never a script path inside `node_modules`.
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
doors[14]{verb,door,what}:
|
|
155
|
+
"ymir","first setup (a bare call installs)","the plan, then consent, then the work"
|
|
156
|
+
"ymir install [...]","bin/ymir-install.sh","the same, said out loud"
|
|
157
|
+
"ymir plan","bin/ymir-plan.sh","what an install would do here — writes nothing"
|
|
158
|
+
"ymir raise / lower","scripts/start.sh · stop.sh","lift the hall, or lay it down"
|
|
159
|
+
"ymir eir","bin/eir-doctor.sh","what stands, and mend what does not (the healer)"
|
|
160
|
+
"ymir groa","bin/groa-update.sh","take the latest, then mend this home forward"
|
|
161
|
+
"ymir groa migrate","bin/ymir-migrate.sh","heal this home's structure"
|
|
162
|
+
"ymir heimdall","bin/ymir-setup-auth.sh","the way in — status · set · github (the guardian)"
|
|
163
|
+
"ymir invite","bin/ymir-invite.sh","let someone else in — mint · list · revoke"
|
|
164
|
+
"ymir smidja","bin/smidja-board.sh","the smithy's board on :8437 — build · start · stop · status"
|
|
165
|
+
"ymir hlidskjalf","scripts/electron.sh start --view hlidskjalf","the high seat's window"
|
|
166
|
+
"ymir sessrumnir","scripts/electron.sh start --view sessrumnir","the seat-hall's window"
|
|
167
|
+
"ymir mimir","bin/mimir.sh","the memory well"
|
|
168
|
+
"ymir sense","bin/host-sense.sh","what THIS machine is"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
A name the law has not given a home is still answered, once, with the name that
|
|
172
|
+
has it: `ymir doctor` → *the door is named `ymir eir` now*. Colour appears only
|
|
173
|
+
where a human watches.
|
|
174
|
+
|
|
175
|
+
### The cloth — `bin/ymir-style.sh`
|
|
176
|
+
|
|
177
|
+
Ymir had correct output and no design. The cloth is cut from the same stone as
|
|
178
|
+
the halls (`midgard/design-system/tokens.css`): **bone** `#cfc3a9` for words,
|
|
179
|
+
**bronze** `#c9973f` for what acts, **steel** `#96a0a8` for what stands, **blood**
|
|
180
|
+
`#c2584a` for what is wrong, and a mark per state (`◆` do · `·` already · `—`
|
|
181
|
+
fact · `✕` blocked · `?` needs your word · `✓` proved).
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
cloth_rules[4]{rule,why}:
|
|
185
|
+
"colour and marks only when stderr is a terminal and NO_COLOR is unset","a pipeline never parses a decoration"
|
|
186
|
+
"data on stdout, the human rendering on stderr","the TOON row is the data; the coloured line is for the eye"
|
|
187
|
+
"the words are never conditional — only the colour is","hiding information to save colour is the wrong trade"
|
|
188
|
+
"no banner over four lines, no rule longer than its text","density first (Monoline TUI · cli-guidelines)"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`bin/ymir-plan.sh --colour` renders the plan in the cloth on stderr while the
|
|
192
|
+
TOON stays on stdout. The installer prints it at the consent, and ends with the
|
|
193
|
+
next steps — *a reaction for every action, a next step for every ending*.
|
|
194
|
+
|
|
195
|
+
### Two libs the packaged tree needs
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
libs[2]{path,resolves}:
|
|
199
|
+
"bin/smidja-lib.sh","the smithy: apps/smidja-factory in a clone, node_modules/@zerwiz/smidja-factory in a package — `smidja_factory_dir`, `smidja_visualizer_dir`"
|
|
200
|
+
"bin/electron-lib.sh","whether a shell's runtime VERIFIES: `electron_runtime_state` (ok · partial · absent) and the exact remedy"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Why they exist.** A packaged tree has no `apps/`: the smithy arrives as a
|
|
204
|
+
dependency and the skill symlink dangles. Four call sites assumed the clone's
|
|
205
|
+
layout, so the visualizer read as unbuildable in a package. They resolve through
|
|
206
|
+
`bin/smidja-lib.sh` now.
|
|
207
|
+
|
|
208
|
+
**And a shell is never declared ready on a directory's presence.** npm gates
|
|
209
|
+
install scripts; a skipped Electron postinstall leaves `dist/` partial and
|
|
210
|
+
`path.txt` unwritten, so the web app builds, every check passes, and the window
|
|
211
|
+
never opens. `electron_runtime_state` verifies, and the plan's `electron` row
|
|
212
|
+
says *the runtime is PARTIAL* with the command that mends it.
|
|
213
|
+
|
|
145
214
|
## The app packages — how the four surfaces arrive
|
|
146
215
|
|
|
147
216
|
The distro depends on the surfaces as their own npm packages, so one
|
|
@@ -354,6 +423,11 @@ apply` **moves private data**, so the step chain runs it only on a real run;
|
|
|
354
423
|
a preview leaves the home exactly as it found it. (It used to apply them even
|
|
355
424
|
under `--check`, which moved a home during a "report only" pass.)
|
|
356
425
|
|
|
426
|
+
`--check` writes nothing — and that includes the migrations. `bin/ymir-migrate.sh
|
|
427
|
+
apply` **moves private data**, so the step chain runs it only on a real run;
|
|
428
|
+
a preview leaves the home exactly as it found it. (It used to apply them even
|
|
429
|
+
under `--check`, which moved a home during a "report only" pass.)
|
|
430
|
+
|
|
357
431
|
## The operator's way in (auth) — `bin/ymir-setup-auth.sh`
|
|
358
432
|
|
|
359
433
|
A fresh checkout seeds **no credential**, so the gate has no way in until the
|
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,218 @@
|
|
|
1
1
|
|
|
2
|
+
## 2026-09-17 — 0.1.8: the day's work reaches npm
|
|
3
|
+
|
|
4
|
+
- **The doors.** `ymir` now carries the operator's verbs, each named for the
|
|
5
|
+
figure who does the work: `raise` · `lower` · `eir` (heal) · `groa` (renew) ·
|
|
6
|
+
`heimdall` (the way in) · `invite` · `smidja` (the board) · `hlidskjalf` ·
|
|
7
|
+
`sessrumnir` · `mimir` · `sense` · `plan`. Before this, the package put two
|
|
8
|
+
commands on PATH and neither could raise the app or the board.
|
|
9
|
+
- **The cloth.** `bin/ymir-style.sh` — colour and marks cut from the halls' own
|
|
10
|
+
tokens (bone · bronze · steel · blood), shown only where a human watches, with
|
|
11
|
+
the data left as TOON on stdout. The plan, the installer, the validate report
|
|
12
|
+
and Eir all wear it.
|
|
13
|
+
- **The shapes.** `bin/smidja-lib.sh` tells a clone's smithy from a packaged one;
|
|
14
|
+
`bin/smidja-board.sh` is the board's own door (`ymir smidja`), built and served
|
|
15
|
+
from an npm install; `bin/electron-lib.sh` verifies a shell's runtime so a
|
|
16
|
+
skipped npm install script cannot pass as a launch.
|
|
17
|
+
- **The icons.** One truth where three maps disagreed, and no claim of a glyph
|
|
18
|
+
nobody drew: Óðrerir wears ansuz, Sessrúmnir othala, the smithy's icon is named
|
|
19
|
+
for the smithy.
|
|
20
|
+
- **The repair.** `4dd7273`'s hand-merge left `bin/ymir-install.sh` and
|
|
21
|
+
`scripts/start.sh` unparseable on main; both restored. Every runtime script
|
|
22
|
+
parses again.
|
|
23
|
+
|
|
24
|
+
## 2026-09-17 — main is repaired, and the report wears the cloth
|
|
25
|
+
|
|
26
|
+
- **Main was broken and nobody had noticed.** A merge resolved by hand
|
|
27
|
+
(`4dd7273`, from a parallel branch) kept *both* sides of a conflict in
|
|
28
|
+
`bin/ymir-install.sh` and `scripts/start.sh`: a stray `<`, a duplicated step
|
|
29
|
+
line, orphaned comments, an `if` with no `fi`. The installer could not parse at
|
|
30
|
+
all — `bash -n` failed on main — and PR #53's merge carried the breakage
|
|
31
|
+
forward. Both files are restored from the last revision that parses (`5b7fc66`),
|
|
32
|
+
with the two fixes that revision predated re-applied: the `--phase` value is
|
|
33
|
+
consumed, and `--yes | --non-interactive | --accept-all-defaults` are one thing.
|
|
34
|
+
Every runtime script parses again; the installer's plan runs.
|
|
35
|
+
- **The lesson worth keeping:** a merge is not a place to guess. When both sides
|
|
36
|
+
of a conflict are real, the resolution is a decision — and `bash -n` on every
|
|
37
|
+
runtime script is cheap enough to be part of it.
|
|
38
|
+
- **The validate report wears the cloth** — the same rows rendered for the eye on
|
|
39
|
+
stderr (marks, colour, and a verdict with the next step) while the TOON stays
|
|
40
|
+
the data on stdout. It found real drift while it was being written: the SPA down
|
|
41
|
+
on `:3888`, Nornir cron stopped, `sessrumnir`, `mcp` and `hoard` broken.
|
|
42
|
+
- **Eir watches the shells.** A new surface reports the desktop shells' runtime
|
|
43
|
+
through `bin/electron-lib.sh` — a *partial* Electron runtime is exactly the
|
|
44
|
+
quiet failure Eir exists for — and her rows render in the cloth too.
|
|
45
|
+
|
|
46
|
+
## 2026-09-17 — the house gains an unpublish door
|
|
47
|
+
|
|
48
|
+
- **`bin/npm-publish.sh --unpublish @scope/name@<version>`.** Publishing had a
|
|
49
|
+
door in this house and unpublishing had none, so the act was done by hand on a
|
|
50
|
+
machine whose `~/.npmrc` holds a stale token — the very reason the door exists.
|
|
51
|
+
The new mode resolves the token from the hoard exactly as publishing does,
|
|
52
|
+
demands a spec that names the version (a whole package is not a one-word act),
|
|
53
|
+
supports `--dry-run`, and says plainly that the registry's CDN may serve the
|
|
54
|
+
tarball for a while after the removal.
|
|
55
|
+
- **The window is 72 hours.** npm allows one version back within 72 hours of its
|
|
56
|
+
publication; past that only npm support can remove it. The help text says so,
|
|
57
|
+
because a door that does not name its own clock invites a late knock.
|
|
58
|
+
- **`@zerwiz/ymir@0.1.5` was unpublished** — the version that carried the memory
|
|
59
|
+
well. Publishing the clean 0.1.7 moves the `latest` tag off the tainted build at
|
|
60
|
+
once, which is the half of the repair that does not wait for propagation.
|
|
61
|
+
|
|
62
|
+
## 2026-09-17 — the plan learns the smithy's package name, and the install forwards its flags
|
|
63
|
+
|
|
64
|
+
Two defects the first real end-to-end npm install surfaced — which is what an
|
|
65
|
+
end-to-end test is for.
|
|
66
|
+
|
|
67
|
+
- **A surface's name is not its package's name.** The plan looked for
|
|
68
|
+
`apps/smidja` or `@zerwiz/smidja`, but npm serves the smithy as
|
|
69
|
+
`@zerwiz/smidja-factory` — so the row read `BLOCKED` on a machine where the
|
|
70
|
+
smithy was installed and standing. `app_dir`/`app_row` now take the surface
|
|
71
|
+
name and the package name separately, and the row tells the truth: installed,
|
|
72
|
+
with the visualizer's UI still to build (the tarball ships its source, never a
|
|
73
|
+
stale build).
|
|
74
|
+
- **`ymir install --plan` forwarded only `--json`.** `--phase` and `--blocked`
|
|
75
|
+
were dropped on the way through the installer's door, so `--blocked` answered
|
|
76
|
+
*unknown flag*. Every plan flag is forwarded now.
|
|
77
|
+
|
|
78
|
+
**The install this came out of:** `npm install -g @zerwiz/ymir` from the registry
|
|
79
|
+
— `@zerwiz/ymir@0.1.7`, 331 packages, and all four surfaces
|
|
80
|
+
(`hlidskjalf` · `odrerir` · `sessrumnir` · `smidja-factory`) arrived inside the
|
|
81
|
+
distro. The tarball carries no memory store — the leak is closed in the artefact
|
|
82
|
+
as well as in the repo.
|
|
83
|
+
|
|
84
|
+
## 2026-09-17 — the package shipped the memory well (one version, now excluded)
|
|
85
|
+
|
|
86
|
+
- **The exposure.** `@zerwiz/ymir@0.1.5` carries five files that are nobody's but
|
|
87
|
+
the operator's: `.agents/memory/kaia.engram`, its `-shm` and `-wal` sidecars, and
|
|
88
|
+
`.agents/memory/well/{episodes,workspace}.jsonl`. A public npm tarball contained
|
|
89
|
+
the live memory well — the store and the episodes. Verified by fetching the
|
|
90
|
+
published artefact and listing it; `0.1.0`–`0.1.4` are clean, and this branch's
|
|
91
|
+
build is clean.
|
|
92
|
+
- **The cause, and it is a trap worth naming.** `.gitignore` excludes
|
|
93
|
+
`.agents/memory/kaia.engram*` and `.agents/memory/well/*.jsonl`, and the repo is
|
|
94
|
+
clean — but **npm does not consult `.gitignore` when `files[]` names a whole
|
|
95
|
+
directory.** `files: [".agents/"]` packs that subtree *including* the ignored
|
|
96
|
+
files. Nothing warned: the leak travelled in the artefact, not the repo.
|
|
97
|
+
- **The fix.** The manifest excludes them explicitly, because a `files[]` list
|
|
98
|
+
cannot rely on the ignore file it overrides:
|
|
99
|
+
`!.agents/memory/kaia.engram*`, `!.agents/memory/well/*.jsonl`,
|
|
100
|
+
`!.agents/memory/*.db`, plus `!**/__pycache__/` and `!**/*.pyc` for the compiled
|
|
101
|
+
junk that was travelling the same way. Verified: `npm pack --dry-run` carries
|
|
102
|
+
864 files and **no** memory store — the memory README and the ledger's scaffold
|
|
103
|
+
header are all that remain, as they should be.
|
|
104
|
+
- **The rule for every future package:** a `files[]` entry that names a directory
|
|
105
|
+
overrides `.gitignore` for everything beneath it. Name what ships, or exclude
|
|
106
|
+
what must not — and verify with a dry-run pack, never by assumption.
|
|
107
|
+
- **Remediation.** `@zerwiz/ymir@0.1.5` should be unpublished (it is inside npm's
|
|
108
|
+
window); the clean build publishes as 0.1.7 and supersedes it.
|
|
109
|
+
|
|
110
|
+
## 2026-09-17 — the doors, the cloth, and the icons that were not there
|
|
111
|
+
|
|
112
|
+
Four things the operator met and could not use: doors that never opened, output
|
|
113
|
+
with no design, a board that could not be built from a package, and an icon map
|
|
114
|
+
that named a glyph nobody had drawn.
|
|
115
|
+
|
|
116
|
+
- **The doors are named for the figure who does the work.** `ymir` put two
|
|
117
|
+
commands on PATH and could neither start the app nor raise the board. It now
|
|
118
|
+
carries the doors: **raise · lower · eir** (heal) · **groa** (renew) ·
|
|
119
|
+
**heimdall** (the way in) · **invite** · **smidja** (the board) ·
|
|
120
|
+
**hlidskjalf** · **sessrumnir** · **mimir** · **sense** · **plan**. A name the
|
|
121
|
+
law has not given a home — `doctor`, `validate`, `auth` — still answers, once,
|
|
122
|
+
with the name that has it.
|
|
123
|
+
- **The cloth: `bin/ymir-style.sh`.** Ymir had correct output and no design. The
|
|
124
|
+
palette is cut from the halls' own tokens — bone for words, bronze for what
|
|
125
|
+
acts, steel for what stands, blood for what is wrong — with a mark per state
|
|
126
|
+
(`◆ · — ✕ ? ✓`). Colour and marks appear only where a human is watching
|
|
127
|
+
(stderr a TTY, `NO_COLOR` unset); the data on stdout stays TOON, and **the
|
|
128
|
+
words are never conditional, only the colour is**. The plan renders in the
|
|
129
|
+
cloth on stderr while the TOON stays pipeable; the installer opens with it and
|
|
130
|
+
ends with the next steps.
|
|
131
|
+
- **The packaged tree is told apart from a clone.** `bin/smidja-lib.sh` resolves
|
|
132
|
+
the smithy — `apps/smidja-factory` in a clone, `node_modules/@zerwiz/smidja-factory`
|
|
133
|
+
in a package — and four call sites that assumed the clone now resolve through
|
|
134
|
+
it. `bin/smidja-board.sh` is the board's own door (`build · start · stop ·
|
|
135
|
+
status`), which `ymir smidja` runs; built and served end to end from an npm
|
|
136
|
+
install: `{"ok":true,"sessions":1}`, `GET / → 200`.
|
|
137
|
+
- **A shell's runtime is verified, never assumed.** npm gates install scripts, so
|
|
138
|
+
a skipped Electron postinstall leaves a partial runtime that still builds the
|
|
139
|
+
web app and still reports success. `bin/electron-lib.sh` answers `ok · partial
|
|
140
|
+
· absent`, the plan's `electron` row says **PARTIAL** with the exact remedy, and
|
|
141
|
+
`step_desktop` refuses to claim a launch it cannot make.
|
|
142
|
+
- **The icons now name glyphs that exist.** Three maps disagreed: `runes.md` gave
|
|
143
|
+
Óðrerir the rune `wunjo` — **a glyph nobody had drawn** — and tinted Sessrúmnir
|
|
144
|
+
with a violet that is not in the tokens; `icons.md` gave `othala` to Óðrerir
|
|
145
|
+
*and* Sessrúmnir; `design-icon.sh` minted `valhalla` for Óðrerir. One truth now:
|
|
146
|
+
**Óðrerir → ansuz** (Odin's breath, the mead of poetry — what Óðrerir *is*),
|
|
147
|
+
Sessrúmnir → othala, Valhalla keeps `ᚹ` (drawn in `valhalla.svg`, named Wunjo),
|
|
148
|
+
Sowilo no longer claimed twice, every tint a house accent from the tokens, and
|
|
149
|
+
the smithy's icon named `ymir-smidja` rather than `ymir-visualizer`. All 22
|
|
150
|
+
glyphs parse; every claim resolves to a file. `docs/design.md` stopped calling
|
|
151
|
+
a living map *"to create"*.
|
|
152
|
+
|
|
153
|
+
## 2026-09-17 — the house gains an unpublish door
|
|
154
|
+
|
|
155
|
+
- **`bin/npm-publish.sh --unpublish @scope/name@<version>`.** Publishing had a
|
|
156
|
+
door in this house and unpublishing had none, so the act was done by hand on a
|
|
157
|
+
machine whose `~/.npmrc` holds a stale token — the very reason the door exists.
|
|
158
|
+
The new mode resolves the token from the hoard exactly as publishing does,
|
|
159
|
+
demands a spec that names the version (a whole package is not a one-word act),
|
|
160
|
+
supports `--dry-run`, and says plainly that the registry's CDN may serve the
|
|
161
|
+
tarball for a while after the removal.
|
|
162
|
+
- **The window is 72 hours.** npm allows one version back within 72 hours of its
|
|
163
|
+
publication; past that only npm support can remove it. The help text says so,
|
|
164
|
+
because a door that does not name its own clock invites a late knock.
|
|
165
|
+
- **`@zerwiz/ymir@0.1.5` was unpublished** — the version that carried the memory
|
|
166
|
+
well. Publishing the clean 0.1.7 moves the `latest` tag off the tainted build at
|
|
167
|
+
once, which is the half of the repair that does not wait for propagation.
|
|
168
|
+
|
|
169
|
+
## 2026-09-17 — the plan learns the smithy's package name, and the install forwards its flags
|
|
170
|
+
|
|
171
|
+
Two defects the first real end-to-end npm install surfaced — which is what an
|
|
172
|
+
end-to-end test is for.
|
|
173
|
+
|
|
174
|
+
- **A surface's name is not its package's name.** The plan looked for
|
|
175
|
+
`apps/smidja` or `@zerwiz/smidja`, but npm serves the smithy as
|
|
176
|
+
`@zerwiz/smidja-factory` — so the row read `BLOCKED` on a machine where the
|
|
177
|
+
smithy was installed and standing. `app_dir`/`app_row` now take the surface
|
|
178
|
+
name and the package name separately, and the row tells the truth: installed,
|
|
179
|
+
with the visualizer's UI still to build (the tarball ships its source, never a
|
|
180
|
+
stale build).
|
|
181
|
+
- **`ymir install --plan` forwarded only `--json`.** `--phase` and `--blocked`
|
|
182
|
+
were dropped on the way through the installer's door, so `--blocked` answered
|
|
183
|
+
*unknown flag*. Every plan flag is forwarded now.
|
|
184
|
+
|
|
185
|
+
**The install this came out of:** `npm install -g @zerwiz/ymir` from the registry
|
|
186
|
+
— `@zerwiz/ymir@0.1.7`, 331 packages, and all four surfaces
|
|
187
|
+
(`hlidskjalf` · `odrerir` · `sessrumnir` · `smidja-factory`) arrived inside the
|
|
188
|
+
distro. The tarball carries no memory store — the leak is closed in the artefact
|
|
189
|
+
as well as in the repo.
|
|
190
|
+
|
|
191
|
+
## 2026-09-17 — the package shipped the memory well (one version, now excluded)
|
|
192
|
+
|
|
193
|
+
- **The exposure.** `@zerwiz/ymir@0.1.5` carries five files that are nobody's but
|
|
194
|
+
the operator's: `.agents/memory/kaia.engram`, its `-shm` and `-wal` sidecars, and
|
|
195
|
+
`.agents/memory/well/{episodes,workspace}.jsonl`. A public npm tarball contained
|
|
196
|
+
the live memory well — the store and the episodes. Verified by fetching the
|
|
197
|
+
published artefact and listing it; `0.1.0`–`0.1.4` are clean, and this branch's
|
|
198
|
+
build is clean.
|
|
199
|
+
- **The cause, and it is a trap worth naming.** `.gitignore` excludes
|
|
200
|
+
`.agents/memory/kaia.engram*` and `.agents/memory/well/*.jsonl`, and the repo is
|
|
201
|
+
clean — but **npm does not consult `.gitignore` when `files[]` names a whole
|
|
202
|
+
directory.** `files: [".agents/"]` packs that subtree *including* the ignored
|
|
203
|
+
files. Nothing warned: the leak travelled in the artefact, not the repo.
|
|
204
|
+
- **The fix.** The manifest excludes them explicitly, because a `files[]` list
|
|
205
|
+
cannot rely on the ignore file it overrides:
|
|
206
|
+
`!.agents/memory/kaia.engram*`, `!.agents/memory/well/*.jsonl`,
|
|
207
|
+
`!.agents/memory/*.db`, plus `!**/__pycache__/` and `!**/*.pyc` for the compiled
|
|
208
|
+
junk that was travelling the same way. Verified: `npm pack --dry-run` carries
|
|
209
|
+
864 files and **no** memory store — the memory README and the ledger's scaffold
|
|
210
|
+
header are all that remain, as they should be.
|
|
211
|
+
- **The rule for every future package:** a `files[]` entry that names a directory
|
|
212
|
+
overrides `.gitignore` for everything beneath it. Name what ships, or exclude
|
|
213
|
+
what must not — and verify with a dry-run pack, never by assumption.
|
|
214
|
+
- **Remediation.** `@zerwiz/ymir@0.1.5` should be unpublished (it is inside npm's
|
|
215
|
+
window); the clean build publishes as 0.1.7 and supersedes it.
|
|
2
216
|
|
|
3
217
|
## 2026-09-17 — the house gains an unpublish door
|
|
4
218
|
|
|
@@ -59,7 +273,6 @@
|
|
|
59
273
|
plan's phase-5 row is what says so out loud.
|
|
60
274
|
- **`@zerwiz/ymir` 0.1.7** — the version published once this lands.
|
|
61
275
|
|
|
62
|
-
|
|
63
276
|
## 2026-09-17 — the four surfaces arrive by npm, and an old tree gives up what it holds
|
|
64
277
|
|
|
65
278
|
- **The app packages arrive with the distro.** `@zerwiz/ymir` now depends on the
|
|
@@ -530,6 +743,122 @@ EmberBackground hearth section (shared module + ResizeObserver mend).
|
|
|
530
743
|
|
|
531
744
|
# CHANGELOG
|
|
532
745
|
|
|
746
|
+
## 2026-09-16 — a sandboxed worker cannot think: `auto` stops killing agents
|
|
747
|
+
|
|
748
|
+
Spawning an Eindri with the default `--isolation auto` produced an **empty pane
|
|
749
|
+
and no status line**. The cause: `auto` chose Utgard whenever the image existed,
|
|
750
|
+
and Utgard runs with `--network none` — so the worker could reach neither its
|
|
751
|
+
cloud model (OpenCode Go) nor a local one (llama.cpp) and died at launch, silently.
|
|
752
|
+
|
|
753
|
+
- **`auto` now keeps the worker in its worktree.** A spawned Eindri is a
|
|
754
|
+
model-driven worker: it must reach a model endpoint to think. Utgard is for
|
|
755
|
+
untrusted *code*, not for the agent's own brain, so the automatic choice is
|
|
756
|
+
`off`, and it says why.
|
|
757
|
+
- **`--isolation on` warns loudly.** It stays available for a sandbox that can
|
|
758
|
+
actually reach a model, but it now prints that Utgard has no network and the
|
|
759
|
+
agent will die silently without one.
|
|
760
|
+
|
|
761
|
+
## 2026-09-16 — the roster's effect lands, and the backend pin stops being committable
|
|
762
|
+
|
|
763
|
+
Applying the private roster (`bin/agents-config.sh apply`) writes the resolved
|
|
764
|
+
model into each agent's canonical profile. Flipping the two local agents to Pi
|
|
765
|
+
made that visible — and surfaced a small ignore hole.
|
|
766
|
+
|
|
767
|
+
- **Profiles follow the roster.** `sindri-developer.md` and `kvasir-scout.md`
|
|
768
|
+
now carry their **Pi** ids (`llamacpp-coder/qwen3-coder-30b`,
|
|
769
|
+
`llamacpp/qwen3.5-9b`), and `huginn-researcher.md` its declared model — the
|
|
770
|
+
output of `apply` against the operator's roster. These profiles are what the
|
|
771
|
+
harnesses load, so the local agents now run on Pi rather than through OpenCode.
|
|
772
|
+
- **`.agents/config/backend` is ignored.** The pinned terminal backend is
|
|
773
|
+
machine-local, but `config` is a **symlink** to `.agents/config`, so the
|
|
774
|
+
`config/*` rule never matched it and the pin was committable. The file is named
|
|
775
|
+
in `.gitignore` instead.
|
|
776
|
+
|
|
777
|
+
## 2026-09-16 — the roster can finally run an agent on Pi
|
|
778
|
+
|
|
779
|
+
`bin/agents-config.sh apply` wrote **every** agent's model into `opencode.json`'s
|
|
780
|
+
agent block, whatever harness that agent used. So an agent set to run on **Pi**
|
|
781
|
+
(native local models) would have had its Pi model id —
|
|
782
|
+
`llamacpp/qwen3.5-9b` — written into OpenCode's config, which cannot resolve it.
|
|
783
|
+
That is why the roster's two local agents were pinned to `harness: opencode` with
|
|
784
|
+
opencode-style ids: there was no working way to put an agent on Pi.
|
|
785
|
+
|
|
786
|
+
- **`apply` is now harness-aware.** Only `opencode`-harness agents are written
|
|
787
|
+
into `opencode.json`; a `pi` (or `hermes`) agent is left out, its model id going
|
|
788
|
+
to the resolve cache that `bin/agent-run.sh` reads. The providers block is
|
|
789
|
+
unchanged — a provider's endpoint is a real fact OpenCode may still want.
|
|
790
|
+
- The way this is meant to be used: declare a local agent's model as its **Pi**
|
|
791
|
+
id (`llamacpp/qwen3.5-9b`, `llamacpp-coder/qwen3-coder-30b` — the ids `pi
|
|
792
|
+
--list-models` reports) with `harness: pi`; the roster is then Pi-driven with no
|
|
793
|
+
per-machine hand-editing.
|
|
794
|
+
- `galdr-reread`: `assets/harness-integration/README.md` — the two writers of
|
|
795
|
+
`opencode.json`, and the rule that only OpenCode agents belong in it.
|
|
796
|
+
|
|
797
|
+
## 2026-09-16 — the visualizer finds its DB, and the seat-hall actually builds
|
|
798
|
+
|
|
799
|
+
Two more readers left behind — both discovered by *starting the apps*, not by any
|
|
800
|
+
gate. Each failed silently in its own way.
|
|
801
|
+
|
|
802
|
+
- **`scripts/start.sh` pointed the smithy at a repo path that no longer holds the
|
|
803
|
+
DB.** `0003-private-data-separation` moved it to `$YMIR_HOME/smidja/smidja.db`,
|
|
804
|
+
but the starter still passed `CMD_DB=<repo>/apps/smidja/smidja_data/smidja.db`
|
|
805
|
+
— so the visualizer API died on boot with `smidja.db not found` and `:8437`
|
|
806
|
+
answered nothing. It now resolves the same pair `bin/smidja-bootstrap.sh` does
|
|
807
|
+
(home first, in-repo fallback, `SMIDJA_DB` override). The asset already
|
|
808
|
+
*described* this resolution; the code now implements it.
|
|
809
|
+
- **`bin/sessrumnir-ensure.sh` never built the app.** `[ "$built_present" ]` tests
|
|
810
|
+
a **literal, non-empty string** — always true — so `ensure --install` skipped
|
|
811
|
+
`build_app` and reported `built: no` with exit 0. Sessrúmnir only built when the
|
|
812
|
+
build was run by hand. Now `built_present` (the function) is called.
|
|
813
|
+
- `galdr-reread`: `.agents/skills/galdr-ymirsystem/assets/smidja.md` — the DB
|
|
814
|
+
path, the `scripts/start.sh` DB resolution, and the observer's read list.
|
|
815
|
+
|
|
816
|
+
## 2026-09-16 — the credential reaches the gate, and the hall stops leaking an IP
|
|
817
|
+
|
|
818
|
+
Setting the operator password had **no effect**. This is the bug that made that
|
|
819
|
+
true, and the runtime state that leaked a private address.
|
|
820
|
+
|
|
821
|
+
- **`scripts/start.sh` never loaded the platform env for the gate.** The gate is
|
|
822
|
+
`bun run apps/hlidskjalf/server/index.ts`, and the server reads
|
|
823
|
+
`process.env.HLIDSKJALF_AUTH` — but the launcher passed only `PORT`, and nothing
|
|
824
|
+
sourced `.env.local`. So a credential written by `bin/ymir-setup-auth.sh`
|
|
825
|
+
(correctly: `0600`, gitignored) never arrived, and because the gate treats an
|
|
826
|
+
empty `GATE_AUTH` as authenticated (`authed: GATE_AUTH ? … : true`) the gate
|
|
827
|
+
stayed **open**. `start.sh` now loads `.env.local` once, before the ports, for
|
|
828
|
+
every service it raises — the gate, Bifrost and Mimir.
|
|
829
|
+
- **`apps/odrerir/.astro/dev.json` was tracked.** Astro rewrites it on every
|
|
830
|
+
start with the live `pid` and the host's own addresses (LAN + tailnet) — a
|
|
831
|
+
private-IP leak into a public tree. It is now untracked and gitignored; the
|
|
832
|
+
generated types beside it stay tracked.
|
|
833
|
+
- `galdr-reread`: `assets/hlidskjalf-ui.md` (the gate must receive the credential)
|
|
834
|
+
and `assets/odrerir-hall.md` (`.astro/dev.json` is runtime state).
|
|
835
|
+
|
|
836
|
+
## 2026-09-16 — four readers that still pointed at the pre-split world
|
|
837
|
+
|
|
838
|
+
The app split and the hoard migration moved things; four readers never followed.
|
|
839
|
+
None failed loudly — each reported a clean PASS, a wrong SKIP, or a silent loss.
|
|
840
|
+
|
|
841
|
+
- **`bin/ymir-install.sh` wrote during `--check`.** The step chain ran
|
|
842
|
+
`bin/ymir-migrate.sh apply` unconditionally, so a preview that promises *"report
|
|
843
|
+
only, no writes"* actually **moved private data**. Migrations now apply only on
|
|
844
|
+
a real run: `if [ "$CHECK" = 0 ]; then ... apply; fi`.
|
|
845
|
+
- **`.agents/migrations/0003-private-data-separation.sh` skipped every directory.**
|
|
846
|
+
It pre-creates its target dirs, then its `copy` refused any target that already
|
|
847
|
+
existed — so `data/` (and every other directory source) was silently dropped.
|
|
848
|
+
The realm declaration never arrived and `0004` fell back to a neutral realm.
|
|
849
|
+
`copy` now **merges** a directory into its target (never overwriting a file) and
|
|
850
|
+
copies a single file only when it is absent.
|
|
851
|
+
- **`bin/smidja-bootstrap.sh` looked for the smithy at the old root.** `sys.path`
|
|
852
|
+
pointed at `smidja/`, but the split moved it to `apps/smidja/`, so the DB seed
|
|
853
|
+
died with `ModuleNotFoundError: No module named 'smidja_modules'`. It now
|
|
854
|
+
searches `apps/smidja` then `smidja`, so either layout works.
|
|
855
|
+
- **`bin/ymir-validate.sh` read the ledger from the pre-move path.** It looked at
|
|
856
|
+
`$YMIR_HOME/memory/`, but `0004-hoard-and-realms` put the ledger at
|
|
857
|
+
`$YMIR_HOME/hodd/memory/` — a false `runes FAIL` on a healthy home. It now tries
|
|
858
|
+
the hoard first, then the pre-move locations.
|
|
859
|
+
- `galdr-reread`: `.agents/skills/galdr-ymirsystem/assets/smidja.md` — the
|
|
860
|
+
protected-paths gotcha and the `smidja/` → `apps/smidja/` move.
|
|
861
|
+
|
|
533
862
|
## 2026-09-17 — Pi gets its agents, and a dead extension comes back
|
|
534
863
|
|
|
535
864
|
- **Pi has no agent loader.** Agent loading in Pi is a *package* (`pi-agents`,
|
package/bin/design-icon.sh
CHANGED
|
@@ -30,16 +30,19 @@ ACTION="${1:-list}"; shift || true
|
|
|
30
30
|
APPS=(
|
|
31
31
|
"apps/hlidskjalf|ehwaz|#c9973f|the seat — Hlidskjalf, the high seat of the control plane|ymir-hlidskjalf|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf|Ymir · Hlidskjalf"
|
|
32
32
|
"apps/hlidskjalf-mobile|raidho|#c9973f|the road — the seat carried, Hlidskjalf on a phone|ymir-hlidskjalf-mobile|scripts/electron.sh start --view hlidskjalf|ymir-hlidskjalf-mobile|Ymir · Hlidskjalf Mobile"
|
|
33
|
-
"apps/odrerir|
|
|
33
|
+
"apps/odrerir|ansuz|#c9973f|the mead — Óðrerir, the Live Hall (Odin's breath, inspiration)|ymir-odrerir|scripts/electron.sh start --view odrerir|ymir-odrerir|Ymir · Óðrerir"
|
|
34
34
|
"apps/sessrumnir|othala|#c9973f|the hall — Sessrúmnir, the seat that holds the cloth|ymir-sessrumnir|bin/sessrumnir.sh start|ymir-sessrumnir|Ymir · Sessrúmnir"
|
|
35
|
-
"apps/smidja-factory/apps/visualizer|kaunan|#f59e0b|the torch — the forge's eye, Smíðja's trace|ymir-
|
|
35
|
+
"apps/smidja-factory/apps/visualizer|kaunan|#f59e0b|the torch — the forge's eye, Smíðja's trace|ymir-smidja|scripts/electron.sh start --view smidja|ymir-smidja|Ymir · Smíðja"
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
list() {
|
|
39
39
|
printf 'app_icons[%d]{app,rune,tint,says}:\n' "${#APPS[@]}"
|
|
40
40
|
for row in "${APPS[@]}"; do
|
|
41
|
-
IFS='|' read -r dir glyph tint says
|
|
42
|
-
|
|
41
|
+
IFS='|' read -r dir glyph tint says icon _exec _klass _name <<<"$row"
|
|
42
|
+
# The surface's name is the one the desktop knows it by (ymir-smidja →
|
|
43
|
+
# smidja), never the directory's leaf — a path is not a name (the naming law).
|
|
44
|
+
surface="${icon#ymir-}"
|
|
45
|
+
printf ' "%s","%s","%s","%s"\n' "$surface" "$glyph" "$tint" "$says"
|
|
43
46
|
done
|
|
44
47
|
printf 'runes[6]{glyph,name,meaning}:\n'
|
|
45
48
|
printf ' "algiz","ᛉ","the Ymir emblem — the platform itself"\n'
|
package/bin/einherjar-spawn.sh
CHANGED
|
@@ -342,6 +342,12 @@ else
|
|
|
342
342
|
fi
|
|
343
343
|
|
|
344
344
|
# --- isolation resolution ----------------------------------------------------
|
|
345
|
+
# A spawned Eindri is a MODEL-DRIVEN worker: it must reach a model endpoint to
|
|
346
|
+
# think at all. Utgard runs with `--network none`, so a sandboxed agent cannot
|
|
347
|
+
# reach either a cloud model (OpenCode Go) or a local one (llama.cpp) — it dies
|
|
348
|
+
# silently at launch, leaving an empty pane and no status line. Utgard is for
|
|
349
|
+
# untrusted CODE, not for the agent's own brain. So `auto` keeps the worker in
|
|
350
|
+
# its worktree, and `on` is an explicit choice that warns.
|
|
345
351
|
ISOLATION_EFFECTIVE=off
|
|
346
352
|
case "$ISOLATION" in
|
|
347
353
|
on)
|
|
@@ -351,14 +357,14 @@ case "$ISOLATION" in
|
|
|
351
357
|
exit 1
|
|
352
358
|
}
|
|
353
359
|
ISOLATION_EFFECTIVE=on
|
|
360
|
+
echo "warning: Utgard runs with --network none — the worker's model endpoint must be reachable INSIDE the sandbox, or the agent dies silently. Pass --isolation off to run in the worktree." >&2
|
|
354
361
|
;;
|
|
355
362
|
off) ISOLATION_EFFECTIVE=off ;;
|
|
356
363
|
auto)
|
|
364
|
+
ISOLATION_EFFECTIVE=off
|
|
357
365
|
if [ -n "$ENGINE" ] && "$ENGINE" image inspect utgard-runner:latest >/dev/null 2>&1; then
|
|
358
|
-
|
|
359
|
-
echo "isolation: on (Utgard image present; pass --isolation off to run in the worktree)" >&2
|
|
366
|
+
echo "isolation: off (Utgard image present, but a model-driven worker needs its endpoint and Utgard has no network — pass --isolation on only for a sandbox that can reach a model)" >&2
|
|
360
367
|
else
|
|
361
|
-
ISOLATION_EFFECTIVE=off
|
|
362
368
|
echo "isolation: off (no Utgard image; pass --isolation on after building it)" >&2
|
|
363
369
|
fi
|
|
364
370
|
;;
|
package/bin/eir-doctor.sh
CHANGED
|
@@ -18,6 +18,11 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
18
18
|
YMIR_HOME="${YMIR_HOME:-$HOME/Documents/Ymir}"
|
|
19
19
|
STATE="${BROKK_STATE_OVERRIDE:-$YMIR_HOME/state}"
|
|
20
20
|
|
|
21
|
+
# The cloth: colour and marks for the human reading this report; the TOON rows on
|
|
22
|
+
# stdout stay the data (bin/ymir-style.sh).
|
|
23
|
+
if [ -z "${YMIR_STYLE_LOADED:-}" ]; then . "$SCRIPT_DIR/ymir-style.sh"; YMIR_STYLE_LOADED=1; fi
|
|
24
|
+
style_init
|
|
25
|
+
|
|
21
26
|
case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help|"") sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
|
|
22
27
|
ACTION="${1:-check}"; shift || true
|
|
23
28
|
|
|
@@ -25,7 +30,7 @@ have() { command -v "$1" >/dev/null 2>&1; }
|
|
|
25
30
|
say_ok() { return 0; }
|
|
26
31
|
|
|
27
32
|
# Each surface: `s_<name>` = healthy? (exit 0), `f_<name>` = the repair.
|
|
28
|
-
SURFACES=(floors herdr a2abridge hermes sessrumnir well mcp lock migrations hoard)
|
|
33
|
+
SURFACES=(floors herdr a2abridge hermes sessrumnir shells well mcp lock migrations hoard)
|
|
29
34
|
|
|
30
35
|
s_floors() { [ -x "$SCRIPT_DIR/prereq-ensure.sh" ] && "$SCRIPT_DIR/prereq-ensure.sh" status >/dev/null 2>&1; }
|
|
31
36
|
f_floors() { "$SCRIPT_DIR/prereq-ensure.sh" ensure --install >/dev/null 2>&1; }
|
|
@@ -37,6 +42,34 @@ s_hermes() { [ -x "$SCRIPT_DIR/hermes-ensure.sh" ] && "$SCRIPT_DIR/hermes-ens
|
|
|
37
42
|
f_hermes() { "$SCRIPT_DIR/hermes-ensure.sh" ensure --install >/dev/null 2>&1; }
|
|
38
43
|
s_sessrumnir(){ [ -x "$SCRIPT_DIR/sessrumnir-ensure.sh" ] && "$SCRIPT_DIR/sessrumnir-ensure.sh" status >/dev/null 2>&1; }
|
|
39
44
|
f_sessrumnir(){ "$SCRIPT_DIR/sessrumnir-ensure.sh" ensure --install >/dev/null 2>&1; }
|
|
45
|
+
# The desktop shells: absent is healthy (the web surfaces stand alone), PARTIAL is
|
|
46
|
+
# not — npm gated the Electron postinstall, so the window cannot open while every
|
|
47
|
+
# build still passes (bin/electron-lib.sh).
|
|
48
|
+
shell_dirs() {
|
|
49
|
+
local a d
|
|
50
|
+
for a in hlidskjalf odrerir sessrumnir; do
|
|
51
|
+
for d in "$ROOT/apps/$a" "$ROOT/node_modules/@zerwiz/$a"; do
|
|
52
|
+
[ -d "$d" ] && { printf '%s\n' "$d"; break; }
|
|
53
|
+
done
|
|
54
|
+
done
|
|
55
|
+
}
|
|
56
|
+
s_shells() {
|
|
57
|
+
local d state
|
|
58
|
+
while IFS= read -r d; do
|
|
59
|
+
state="$(electron_runtime_state "$d" 2>/dev/null || true)"
|
|
60
|
+
[ "$state" = partial ] && return 1
|
|
61
|
+
done < <(shell_dirs)
|
|
62
|
+
return 0
|
|
63
|
+
}
|
|
64
|
+
f_shells() {
|
|
65
|
+
printf 'eir: the shells need YOUR hand — npm gated the runtime download:\n' >&2
|
|
66
|
+
electron_remedy >&2
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
if [ -z "${YMIR_ELECTRON_LIB_LOADED:-}" ] && [ -r "$SCRIPT_DIR/electron-lib.sh" ]; then
|
|
70
|
+
. "$SCRIPT_DIR/electron-lib.sh"; YMIR_ELECTRON_LIB_LOADED=1
|
|
71
|
+
fi
|
|
72
|
+
|
|
40
73
|
s_well() { [ -x "$SCRIPT_DIR/mimir.sh" ] && "$SCRIPT_DIR/mimir.sh" health >/dev/null 2>&1; }
|
|
41
74
|
f_well() { "$SCRIPT_DIR/mimir.sh" start >/dev/null 2>&1; }
|
|
42
75
|
# MCP: both A2A servers wired into opencode + pi.
|
|
@@ -126,6 +159,7 @@ detail() { # <name> -> one short fact
|
|
|
126
159
|
mcp) "echo 'a2abridge + engram'" ;;
|
|
127
160
|
lock) "cat $STATE/.lock 2>/dev/null | tr -d '[:space:]' | sed 's/^/pid /' || echo none" ;;
|
|
128
161
|
migrations)"echo 'structure'" ;;
|
|
162
|
+
shells) 'shell_dirs | while IFS= read -r d; do printf "%s %s; " "$(basename "$d")" "$(electron_runtime_state "$d" 2>/dev/null || true)"; done' ;;
|
|
129
163
|
hoard) "printf 'hoard %s' \"$(_hoard_root)\" ; [ -d \"$YMIR_HOME/identity\" ] && printf ' +flat-duplicate' ; printf '\\n'" ;;
|
|
130
164
|
esac
|
|
131
165
|
}
|
|
@@ -151,6 +185,14 @@ done
|
|
|
151
185
|
printf 'eir[1]{action,root,broken}:\n "%s","%s",%s\n' "$ACTION" "$ROOT" "$broken"
|
|
152
186
|
printf 'health[%d]{surface,state,detail}:\n' "$count"
|
|
153
187
|
printf '%b' "$rows"
|
|
188
|
+
# For the eye, on stderr: marks and colour; for the pipe, the TOON above.
|
|
189
|
+
if [ -t 2 ]; then
|
|
190
|
+
printf '\n' >&2
|
|
191
|
+
while IFS='|' read -r surface state detail; do
|
|
192
|
+
[ -n "$surface" ] || continue
|
|
193
|
+
style_line "$state" "$surface" "$detail"
|
|
194
|
+
done <<<"$(printf '%b' "$rows" | sed 's/^ //; s/"//g; s/,/|/; s/,/|/')"
|
|
195
|
+
fi
|
|
154
196
|
if [ "$broken" != 0 ]; then
|
|
155
197
|
printf 'eir: %s surface(s) need mending — run: bin/eir-doctor.sh fix\n' "$broken"
|
|
156
198
|
exit 1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# electron-lib.sh — is a desktop shell's runtime actually there?
|
|
3
|
+
#
|
|
4
|
+
# The trap this exists for: npm blocks a package's install scripts by default
|
|
5
|
+
# (npm 11.16+ warns, npm 12 refuses), and Electron's postinstall is what downloads
|
|
6
|
+
# its ~100 MB runtime. Without it the install still reports SUCCESS and the web app
|
|
7
|
+
# still builds — `node_modules/electron/dist/` is simply partial and `path.txt` is
|
|
8
|
+
# never written. So the shell fails to launch while every check says healthy, and
|
|
9
|
+
# nobody is told.
|
|
10
|
+
#
|
|
11
|
+
# Ymir refuses that silence: a shell is only "ready" when the runtime verifies.
|
|
12
|
+
# Source-safe, functions only.
|
|
13
|
+
#
|
|
14
|
+
# electron_runtime_state <app-dir> ok | partial | absent (exit 0 only for ok)
|
|
15
|
+
# electron_remedy the exact command that mends a partial one
|
|
16
|
+
set -u
|
|
17
|
+
|
|
18
|
+
electron_runtime_state() { # <app-dir>
|
|
19
|
+
local dir="${1-}"
|
|
20
|
+
[ -n "$dir" ] && [ -d "$dir" ] || { printf 'absent'; return 1; }
|
|
21
|
+
[ -d "$dir/node_modules/electron" ] || { printf 'absent'; return 1; }
|
|
22
|
+
# path.txt is written by the postinstall; dist/electron is what it points at.
|
|
23
|
+
if [ -f "$dir/node_modules/electron/path.txt" ] \
|
|
24
|
+
&& [ -x "$dir/node_modules/electron/dist/electron" ]; then
|
|
25
|
+
printf 'ok'; return 0
|
|
26
|
+
fi
|
|
27
|
+
printf 'partial'; return 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
electron_remedy() {
|
|
31
|
+
printf 'the runtime download was skipped by npm — approve and rebuild:\n'
|
|
32
|
+
printf ' npm install-scripts approve electron && npm rebuild electron\n'
|
|
33
|
+
printf 'yonder: bin/ymir-install.sh --no-desktop if the web surfaces are enough'
|
|
34
|
+
}
|