@zerwiz/ymir 0.1.7 → 0.1.9
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 +372 -1
- package/bin/app-lib.sh +58 -0
- package/bin/design-check.sh +12 -1
- package/bin/design-icon.sh +22 -4
- package/bin/desktop-place.sh +14 -1
- package/bin/einherjar-spawn.sh +9 -3
- package/bin/eir-doctor.sh +55 -2
- package/bin/electron-lib.sh +34 -0
- package/bin/hall-snapshot.sh +12 -1
- package/bin/sessrumnir-ensure.sh +12 -1
- package/bin/sessrumnir-sync.sh +12 -1
- package/bin/sessrumnir.sh +12 -1
- package/bin/smidja-board.sh +136 -0
- package/bin/smidja-lib.sh +50 -0
- package/bin/ymir-install.sh +46 -7
- package/bin/ymir-invite.sh +14 -3
- package/bin/ymir-plan.sh +48 -14
- package/bin/ymir-style.sh +121 -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/electron.sh +15 -2
- package/scripts/start.sh +31 -3
|
@@ -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,260 @@
|
|
|
1
1
|
|
|
2
|
+
## 2026-09-17 — 0.1.9: both shapes reach the registry
|
|
3
|
+
|
|
4
|
+
- **`ymir raise` works on a packaged install.** 0.1.8 shipped before the resolver,
|
|
5
|
+
so a user who installed from npm hit `cd …/apps/hlidskjalf: No such file or
|
|
6
|
+
directory` — every package had arrived, and eighteen scripts were looking in the
|
|
7
|
+
clone's `apps/`. `bin/app-lib.sh` resolves a surface in either shape now.
|
|
8
|
+
- **A clone and a package are one tree to the scripts.** Proven against both: the
|
|
9
|
+
five surfaces resolve from `/home/zerwiz/Ymir` and from an npm-installed
|
|
10
|
+
package, and `ymir raise` against a packaged tree fetches the app's
|
|
11
|
+
dependencies, raises the gate API, starts Nornir cron, and brings up Bifrost and
|
|
12
|
+
the well.
|
|
13
|
+
- **The patience words** — `style_patience` — open the installer's long chain and
|
|
14
|
+
the raise path's build, so a long hour says what it is doing.
|
|
15
|
+
|
|
16
|
+
## 2026-09-17 — both shapes: a clone and an npm install, and the patience words
|
|
17
|
+
|
|
18
|
+
- **`ymir raise` failed on a packaged install** — and the reason was ours, not
|
|
19
|
+
npm's. Every package had arrived; the scripts looked for the apps in the clone's
|
|
20
|
+
`apps/`, where a package keeps nothing. `scripts/start.sh` died on
|
|
21
|
+
`cd …/apps/hlidskjalf: No such file or directory`. **Eighteen files assumed the
|
|
22
|
+
clone's layout.**
|
|
23
|
+
- **One resolver, both shapes:** `bin/app-lib.sh` — `apps/<surface>` in a clone,
|
|
24
|
+
`node_modules/@zerwiz/<package>` in a package, with the surface→package map
|
|
25
|
+
(`smidja` → `@zerwiz/smidja-factory`). Wired into the raise path, the windows,
|
|
26
|
+
the invite door, the seat-hall, Eir, the icons, the placement, the hall snapshot
|
|
27
|
+
and the installer's own SPA and shell steps. `bin/smidja-lib.sh` delegates to it
|
|
28
|
+
now, so there is one truth about where things live.
|
|
29
|
+
- **Proven in both trees**: all five surfaces resolve from `/home/zerwiz/Ymir`
|
|
30
|
+
(a clone) and from an npm-installed package. And `ymir raise` run against the
|
|
31
|
+
packaged tree no longer dies — it fetches the app's dependencies, raises the
|
|
32
|
+
gate API, **starts Nornir cron**, and brings up Bifrost and the well.
|
|
33
|
+
- **A trap worth naming:** `printf -v <name>` writes to the *function's* scope, so
|
|
34
|
+
a helper whose scratch variable shares the caller's requested name swallows the
|
|
35
|
+
answer. Every scratch name in the resolvers is function-prefixed for that
|
|
36
|
+
reason.
|
|
37
|
+
- **The patience words.** A long install should say what it is doing and why it
|
|
38
|
+
takes a while. `style_patience` — the cloth's own line — now opens the
|
|
39
|
+
installer's long chain and the raise path's build:
|
|
40
|
+
*much moves · the halls are being set right · this hour is long, and nothing of
|
|
41
|
+
yours is lost in it — roots come home, shapes are re-cut, names are set true
|
|
42
|
+
again. Your patience is noted, and it is earned.*
|
|
43
|
+
|
|
44
|
+
## 2026-09-17 — 0.1.8: the day's work reaches npm
|
|
45
|
+
|
|
46
|
+
- **The doors.** `ymir` now carries the operator's verbs, each named for the
|
|
47
|
+
figure who does the work: `raise` · `lower` · `eir` (heal) · `groa` (renew) ·
|
|
48
|
+
`heimdall` (the way in) · `invite` · `smidja` (the board) · `hlidskjalf` ·
|
|
49
|
+
`sessrumnir` · `mimir` · `sense` · `plan`. Before this, the package put two
|
|
50
|
+
commands on PATH and neither could raise the app or the board.
|
|
51
|
+
- **The cloth.** `bin/ymir-style.sh` — colour and marks cut from the halls' own
|
|
52
|
+
tokens (bone · bronze · steel · blood), shown only where a human watches, with
|
|
53
|
+
the data left as TOON on stdout. The plan, the installer, the validate report
|
|
54
|
+
and Eir all wear it.
|
|
55
|
+
- **The shapes.** `bin/smidja-lib.sh` tells a clone's smithy from a packaged one;
|
|
56
|
+
`bin/smidja-board.sh` is the board's own door (`ymir smidja`), built and served
|
|
57
|
+
from an npm install; `bin/electron-lib.sh` verifies a shell's runtime so a
|
|
58
|
+
skipped npm install script cannot pass as a launch.
|
|
59
|
+
- **The icons.** One truth where three maps disagreed, and no claim of a glyph
|
|
60
|
+
nobody drew: Óðrerir wears ansuz, Sessrúmnir othala, the smithy's icon is named
|
|
61
|
+
for the smithy.
|
|
62
|
+
- **The repair.** `4dd7273`'s hand-merge left `bin/ymir-install.sh` and
|
|
63
|
+
`scripts/start.sh` unparseable on main; both restored. Every runtime script
|
|
64
|
+
parses again.
|
|
65
|
+
|
|
66
|
+
## 2026-09-17 — main is repaired, and the report wears the cloth
|
|
67
|
+
|
|
68
|
+
- **Main was broken and nobody had noticed.** A merge resolved by hand
|
|
69
|
+
(`4dd7273`, from a parallel branch) kept *both* sides of a conflict in
|
|
70
|
+
`bin/ymir-install.sh` and `scripts/start.sh`: a stray `<`, a duplicated step
|
|
71
|
+
line, orphaned comments, an `if` with no `fi`. The installer could not parse at
|
|
72
|
+
all — `bash -n` failed on main — and PR #53's merge carried the breakage
|
|
73
|
+
forward. Both files are restored from the last revision that parses (`5b7fc66`),
|
|
74
|
+
with the two fixes that revision predated re-applied: the `--phase` value is
|
|
75
|
+
consumed, and `--yes | --non-interactive | --accept-all-defaults` are one thing.
|
|
76
|
+
Every runtime script parses again; the installer's plan runs.
|
|
77
|
+
- **The lesson worth keeping:** a merge is not a place to guess. When both sides
|
|
78
|
+
of a conflict are real, the resolution is a decision — and `bash -n` on every
|
|
79
|
+
runtime script is cheap enough to be part of it.
|
|
80
|
+
- **The validate report wears the cloth** — the same rows rendered for the eye on
|
|
81
|
+
stderr (marks, colour, and a verdict with the next step) while the TOON stays
|
|
82
|
+
the data on stdout. It found real drift while it was being written: the SPA down
|
|
83
|
+
on `:3888`, Nornir cron stopped, `sessrumnir`, `mcp` and `hoard` broken.
|
|
84
|
+
- **Eir watches the shells.** A new surface reports the desktop shells' runtime
|
|
85
|
+
through `bin/electron-lib.sh` — a *partial* Electron runtime is exactly the
|
|
86
|
+
quiet failure Eir exists for — and her rows render in the cloth too.
|
|
87
|
+
|
|
88
|
+
## 2026-09-17 — the house gains an unpublish door
|
|
89
|
+
|
|
90
|
+
- **`bin/npm-publish.sh --unpublish @scope/name@<version>`.** Publishing had a
|
|
91
|
+
door in this house and unpublishing had none, so the act was done by hand on a
|
|
92
|
+
machine whose `~/.npmrc` holds a stale token — the very reason the door exists.
|
|
93
|
+
The new mode resolves the token from the hoard exactly as publishing does,
|
|
94
|
+
demands a spec that names the version (a whole package is not a one-word act),
|
|
95
|
+
supports `--dry-run`, and says plainly that the registry's CDN may serve the
|
|
96
|
+
tarball for a while after the removal.
|
|
97
|
+
- **The window is 72 hours.** npm allows one version back within 72 hours of its
|
|
98
|
+
publication; past that only npm support can remove it. The help text says so,
|
|
99
|
+
because a door that does not name its own clock invites a late knock.
|
|
100
|
+
- **`@zerwiz/ymir@0.1.5` was unpublished** — the version that carried the memory
|
|
101
|
+
well. Publishing the clean 0.1.7 moves the `latest` tag off the tainted build at
|
|
102
|
+
once, which is the half of the repair that does not wait for propagation.
|
|
103
|
+
|
|
104
|
+
## 2026-09-17 — the plan learns the smithy's package name, and the install forwards its flags
|
|
105
|
+
|
|
106
|
+
Two defects the first real end-to-end npm install surfaced — which is what an
|
|
107
|
+
end-to-end test is for.
|
|
108
|
+
|
|
109
|
+
- **A surface's name is not its package's name.** The plan looked for
|
|
110
|
+
`apps/smidja` or `@zerwiz/smidja`, but npm serves the smithy as
|
|
111
|
+
`@zerwiz/smidja-factory` — so the row read `BLOCKED` on a machine where the
|
|
112
|
+
smithy was installed and standing. `app_dir`/`app_row` now take the surface
|
|
113
|
+
name and the package name separately, and the row tells the truth: installed,
|
|
114
|
+
with the visualizer's UI still to build (the tarball ships its source, never a
|
|
115
|
+
stale build).
|
|
116
|
+
- **`ymir install --plan` forwarded only `--json`.** `--phase` and `--blocked`
|
|
117
|
+
were dropped on the way through the installer's door, so `--blocked` answered
|
|
118
|
+
*unknown flag*. Every plan flag is forwarded now.
|
|
119
|
+
|
|
120
|
+
**The install this came out of:** `npm install -g @zerwiz/ymir` from the registry
|
|
121
|
+
— `@zerwiz/ymir@0.1.7`, 331 packages, and all four surfaces
|
|
122
|
+
(`hlidskjalf` · `odrerir` · `sessrumnir` · `smidja-factory`) arrived inside the
|
|
123
|
+
distro. The tarball carries no memory store — the leak is closed in the artefact
|
|
124
|
+
as well as in the repo.
|
|
125
|
+
|
|
126
|
+
## 2026-09-17 — the package shipped the memory well (one version, now excluded)
|
|
127
|
+
|
|
128
|
+
- **The exposure.** `@zerwiz/ymir@0.1.5` carries five files that are nobody's but
|
|
129
|
+
the operator's: `.agents/memory/kaia.engram`, its `-shm` and `-wal` sidecars, and
|
|
130
|
+
`.agents/memory/well/{episodes,workspace}.jsonl`. A public npm tarball contained
|
|
131
|
+
the live memory well — the store and the episodes. Verified by fetching the
|
|
132
|
+
published artefact and listing it; `0.1.0`–`0.1.4` are clean, and this branch's
|
|
133
|
+
build is clean.
|
|
134
|
+
- **The cause, and it is a trap worth naming.** `.gitignore` excludes
|
|
135
|
+
`.agents/memory/kaia.engram*` and `.agents/memory/well/*.jsonl`, and the repo is
|
|
136
|
+
clean — but **npm does not consult `.gitignore` when `files[]` names a whole
|
|
137
|
+
directory.** `files: [".agents/"]` packs that subtree *including* the ignored
|
|
138
|
+
files. Nothing warned: the leak travelled in the artefact, not the repo.
|
|
139
|
+
- **The fix.** The manifest excludes them explicitly, because a `files[]` list
|
|
140
|
+
cannot rely on the ignore file it overrides:
|
|
141
|
+
`!.agents/memory/kaia.engram*`, `!.agents/memory/well/*.jsonl`,
|
|
142
|
+
`!.agents/memory/*.db`, plus `!**/__pycache__/` and `!**/*.pyc` for the compiled
|
|
143
|
+
junk that was travelling the same way. Verified: `npm pack --dry-run` carries
|
|
144
|
+
864 files and **no** memory store — the memory README and the ledger's scaffold
|
|
145
|
+
header are all that remain, as they should be.
|
|
146
|
+
- **The rule for every future package:** a `files[]` entry that names a directory
|
|
147
|
+
overrides `.gitignore` for everything beneath it. Name what ships, or exclude
|
|
148
|
+
what must not — and verify with a dry-run pack, never by assumption.
|
|
149
|
+
- **Remediation.** `@zerwiz/ymir@0.1.5` should be unpublished (it is inside npm's
|
|
150
|
+
window); the clean build publishes as 0.1.7 and supersedes it.
|
|
151
|
+
|
|
152
|
+
## 2026-09-17 — the doors, the cloth, and the icons that were not there
|
|
153
|
+
|
|
154
|
+
Four things the operator met and could not use: doors that never opened, output
|
|
155
|
+
with no design, a board that could not be built from a package, and an icon map
|
|
156
|
+
that named a glyph nobody had drawn.
|
|
157
|
+
|
|
158
|
+
- **The doors are named for the figure who does the work.** `ymir` put two
|
|
159
|
+
commands on PATH and could neither start the app nor raise the board. It now
|
|
160
|
+
carries the doors: **raise · lower · eir** (heal) · **groa** (renew) ·
|
|
161
|
+
**heimdall** (the way in) · **invite** · **smidja** (the board) ·
|
|
162
|
+
**hlidskjalf** · **sessrumnir** · **mimir** · **sense** · **plan**. A name the
|
|
163
|
+
law has not given a home — `doctor`, `validate`, `auth` — still answers, once,
|
|
164
|
+
with the name that has it.
|
|
165
|
+
- **The cloth: `bin/ymir-style.sh`.** Ymir had correct output and no design. The
|
|
166
|
+
palette is cut from the halls' own tokens — bone for words, bronze for what
|
|
167
|
+
acts, steel for what stands, blood for what is wrong — with a mark per state
|
|
168
|
+
(`◆ · — ✕ ? ✓`). Colour and marks appear only where a human is watching
|
|
169
|
+
(stderr a TTY, `NO_COLOR` unset); the data on stdout stays TOON, and **the
|
|
170
|
+
words are never conditional, only the colour is**. The plan renders in the
|
|
171
|
+
cloth on stderr while the TOON stays pipeable; the installer opens with it and
|
|
172
|
+
ends with the next steps.
|
|
173
|
+
- **The packaged tree is told apart from a clone.** `bin/smidja-lib.sh` resolves
|
|
174
|
+
the smithy — `apps/smidja-factory` in a clone, `node_modules/@zerwiz/smidja-factory`
|
|
175
|
+
in a package — and four call sites that assumed the clone now resolve through
|
|
176
|
+
it. `bin/smidja-board.sh` is the board's own door (`build · start · stop ·
|
|
177
|
+
status`), which `ymir smidja` runs; built and served end to end from an npm
|
|
178
|
+
install: `{"ok":true,"sessions":1}`, `GET / → 200`.
|
|
179
|
+
- **A shell's runtime is verified, never assumed.** npm gates install scripts, so
|
|
180
|
+
a skipped Electron postinstall leaves a partial runtime that still builds the
|
|
181
|
+
web app and still reports success. `bin/electron-lib.sh` answers `ok · partial
|
|
182
|
+
· absent`, the plan's `electron` row says **PARTIAL** with the exact remedy, and
|
|
183
|
+
`step_desktop` refuses to claim a launch it cannot make.
|
|
184
|
+
- **The icons now name glyphs that exist.** Three maps disagreed: `runes.md` gave
|
|
185
|
+
Óðrerir the rune `wunjo` — **a glyph nobody had drawn** — and tinted Sessrúmnir
|
|
186
|
+
with a violet that is not in the tokens; `icons.md` gave `othala` to Óðrerir
|
|
187
|
+
*and* Sessrúmnir; `design-icon.sh` minted `valhalla` for Óðrerir. One truth now:
|
|
188
|
+
**Óðrerir → ansuz** (Odin's breath, the mead of poetry — what Óðrerir *is*),
|
|
189
|
+
Sessrúmnir → othala, Valhalla keeps `ᚹ` (drawn in `valhalla.svg`, named Wunjo),
|
|
190
|
+
Sowilo no longer claimed twice, every tint a house accent from the tokens, and
|
|
191
|
+
the smithy's icon named `ymir-smidja` rather than `ymir-visualizer`. All 22
|
|
192
|
+
glyphs parse; every claim resolves to a file. `docs/design.md` stopped calling
|
|
193
|
+
a living map *"to create"*.
|
|
194
|
+
|
|
195
|
+
## 2026-09-17 — the house gains an unpublish door
|
|
196
|
+
|
|
197
|
+
- **`bin/npm-publish.sh --unpublish @scope/name@<version>`.** Publishing had a
|
|
198
|
+
door in this house and unpublishing had none, so the act was done by hand on a
|
|
199
|
+
machine whose `~/.npmrc` holds a stale token — the very reason the door exists.
|
|
200
|
+
The new mode resolves the token from the hoard exactly as publishing does,
|
|
201
|
+
demands a spec that names the version (a whole package is not a one-word act),
|
|
202
|
+
supports `--dry-run`, and says plainly that the registry's CDN may serve the
|
|
203
|
+
tarball for a while after the removal.
|
|
204
|
+
- **The window is 72 hours.** npm allows one version back within 72 hours of its
|
|
205
|
+
publication; past that only npm support can remove it. The help text says so,
|
|
206
|
+
because a door that does not name its own clock invites a late knock.
|
|
207
|
+
- **`@zerwiz/ymir@0.1.5` was unpublished** — the version that carried the memory
|
|
208
|
+
well. Publishing the clean 0.1.7 moves the `latest` tag off the tainted build at
|
|
209
|
+
once, which is the half of the repair that does not wait for propagation.
|
|
210
|
+
|
|
211
|
+
## 2026-09-17 — the plan learns the smithy's package name, and the install forwards its flags
|
|
212
|
+
|
|
213
|
+
Two defects the first real end-to-end npm install surfaced — which is what an
|
|
214
|
+
end-to-end test is for.
|
|
215
|
+
|
|
216
|
+
- **A surface's name is not its package's name.** The plan looked for
|
|
217
|
+
`apps/smidja` or `@zerwiz/smidja`, but npm serves the smithy as
|
|
218
|
+
`@zerwiz/smidja-factory` — so the row read `BLOCKED` on a machine where the
|
|
219
|
+
smithy was installed and standing. `app_dir`/`app_row` now take the surface
|
|
220
|
+
name and the package name separately, and the row tells the truth: installed,
|
|
221
|
+
with the visualizer's UI still to build (the tarball ships its source, never a
|
|
222
|
+
stale build).
|
|
223
|
+
- **`ymir install --plan` forwarded only `--json`.** `--phase` and `--blocked`
|
|
224
|
+
were dropped on the way through the installer's door, so `--blocked` answered
|
|
225
|
+
*unknown flag*. Every plan flag is forwarded now.
|
|
226
|
+
|
|
227
|
+
**The install this came out of:** `npm install -g @zerwiz/ymir` from the registry
|
|
228
|
+
— `@zerwiz/ymir@0.1.7`, 331 packages, and all four surfaces
|
|
229
|
+
(`hlidskjalf` · `odrerir` · `sessrumnir` · `smidja-factory`) arrived inside the
|
|
230
|
+
distro. The tarball carries no memory store — the leak is closed in the artefact
|
|
231
|
+
as well as in the repo.
|
|
232
|
+
|
|
233
|
+
## 2026-09-17 — the package shipped the memory well (one version, now excluded)
|
|
234
|
+
|
|
235
|
+
- **The exposure.** `@zerwiz/ymir@0.1.5` carries five files that are nobody's but
|
|
236
|
+
the operator's: `.agents/memory/kaia.engram`, its `-shm` and `-wal` sidecars, and
|
|
237
|
+
`.agents/memory/well/{episodes,workspace}.jsonl`. A public npm tarball contained
|
|
238
|
+
the live memory well — the store and the episodes. Verified by fetching the
|
|
239
|
+
published artefact and listing it; `0.1.0`–`0.1.4` are clean, and this branch's
|
|
240
|
+
build is clean.
|
|
241
|
+
- **The cause, and it is a trap worth naming.** `.gitignore` excludes
|
|
242
|
+
`.agents/memory/kaia.engram*` and `.agents/memory/well/*.jsonl`, and the repo is
|
|
243
|
+
clean — but **npm does not consult `.gitignore` when `files[]` names a whole
|
|
244
|
+
directory.** `files: [".agents/"]` packs that subtree *including* the ignored
|
|
245
|
+
files. Nothing warned: the leak travelled in the artefact, not the repo.
|
|
246
|
+
- **The fix.** The manifest excludes them explicitly, because a `files[]` list
|
|
247
|
+
cannot rely on the ignore file it overrides:
|
|
248
|
+
`!.agents/memory/kaia.engram*`, `!.agents/memory/well/*.jsonl`,
|
|
249
|
+
`!.agents/memory/*.db`, plus `!**/__pycache__/` and `!**/*.pyc` for the compiled
|
|
250
|
+
junk that was travelling the same way. Verified: `npm pack --dry-run` carries
|
|
251
|
+
864 files and **no** memory store — the memory README and the ledger's scaffold
|
|
252
|
+
header are all that remain, as they should be.
|
|
253
|
+
- **The rule for every future package:** a `files[]` entry that names a directory
|
|
254
|
+
overrides `.gitignore` for everything beneath it. Name what ships, or exclude
|
|
255
|
+
what must not — and verify with a dry-run pack, never by assumption.
|
|
256
|
+
- **Remediation.** `@zerwiz/ymir@0.1.5` should be unpublished (it is inside npm's
|
|
257
|
+
window); the clean build publishes as 0.1.7 and supersedes it.
|
|
2
258
|
|
|
3
259
|
## 2026-09-17 — the house gains an unpublish door
|
|
4
260
|
|
|
@@ -59,7 +315,6 @@
|
|
|
59
315
|
plan's phase-5 row is what says so out loud.
|
|
60
316
|
- **`@zerwiz/ymir` 0.1.7** — the version published once this lands.
|
|
61
317
|
|
|
62
|
-
|
|
63
318
|
## 2026-09-17 — the four surfaces arrive by npm, and an old tree gives up what it holds
|
|
64
319
|
|
|
65
320
|
- **The app packages arrive with the distro.** `@zerwiz/ymir` now depends on the
|
|
@@ -530,6 +785,122 @@ EmberBackground hearth section (shared module + ResizeObserver mend).
|
|
|
530
785
|
|
|
531
786
|
# CHANGELOG
|
|
532
787
|
|
|
788
|
+
## 2026-09-16 — a sandboxed worker cannot think: `auto` stops killing agents
|
|
789
|
+
|
|
790
|
+
Spawning an Eindri with the default `--isolation auto` produced an **empty pane
|
|
791
|
+
and no status line**. The cause: `auto` chose Utgard whenever the image existed,
|
|
792
|
+
and Utgard runs with `--network none` — so the worker could reach neither its
|
|
793
|
+
cloud model (OpenCode Go) nor a local one (llama.cpp) and died at launch, silently.
|
|
794
|
+
|
|
795
|
+
- **`auto` now keeps the worker in its worktree.** A spawned Eindri is a
|
|
796
|
+
model-driven worker: it must reach a model endpoint to think. Utgard is for
|
|
797
|
+
untrusted *code*, not for the agent's own brain, so the automatic choice is
|
|
798
|
+
`off`, and it says why.
|
|
799
|
+
- **`--isolation on` warns loudly.** It stays available for a sandbox that can
|
|
800
|
+
actually reach a model, but it now prints that Utgard has no network and the
|
|
801
|
+
agent will die silently without one.
|
|
802
|
+
|
|
803
|
+
## 2026-09-16 — the roster's effect lands, and the backend pin stops being committable
|
|
804
|
+
|
|
805
|
+
Applying the private roster (`bin/agents-config.sh apply`) writes the resolved
|
|
806
|
+
model into each agent's canonical profile. Flipping the two local agents to Pi
|
|
807
|
+
made that visible — and surfaced a small ignore hole.
|
|
808
|
+
|
|
809
|
+
- **Profiles follow the roster.** `sindri-developer.md` and `kvasir-scout.md`
|
|
810
|
+
now carry their **Pi** ids (`llamacpp-coder/qwen3-coder-30b`,
|
|
811
|
+
`llamacpp/qwen3.5-9b`), and `huginn-researcher.md` its declared model — the
|
|
812
|
+
output of `apply` against the operator's roster. These profiles are what the
|
|
813
|
+
harnesses load, so the local agents now run on Pi rather than through OpenCode.
|
|
814
|
+
- **`.agents/config/backend` is ignored.** The pinned terminal backend is
|
|
815
|
+
machine-local, but `config` is a **symlink** to `.agents/config`, so the
|
|
816
|
+
`config/*` rule never matched it and the pin was committable. The file is named
|
|
817
|
+
in `.gitignore` instead.
|
|
818
|
+
|
|
819
|
+
## 2026-09-16 — the roster can finally run an agent on Pi
|
|
820
|
+
|
|
821
|
+
`bin/agents-config.sh apply` wrote **every** agent's model into `opencode.json`'s
|
|
822
|
+
agent block, whatever harness that agent used. So an agent set to run on **Pi**
|
|
823
|
+
(native local models) would have had its Pi model id —
|
|
824
|
+
`llamacpp/qwen3.5-9b` — written into OpenCode's config, which cannot resolve it.
|
|
825
|
+
That is why the roster's two local agents were pinned to `harness: opencode` with
|
|
826
|
+
opencode-style ids: there was no working way to put an agent on Pi.
|
|
827
|
+
|
|
828
|
+
- **`apply` is now harness-aware.** Only `opencode`-harness agents are written
|
|
829
|
+
into `opencode.json`; a `pi` (or `hermes`) agent is left out, its model id going
|
|
830
|
+
to the resolve cache that `bin/agent-run.sh` reads. The providers block is
|
|
831
|
+
unchanged — a provider's endpoint is a real fact OpenCode may still want.
|
|
832
|
+
- The way this is meant to be used: declare a local agent's model as its **Pi**
|
|
833
|
+
id (`llamacpp/qwen3.5-9b`, `llamacpp-coder/qwen3-coder-30b` — the ids `pi
|
|
834
|
+
--list-models` reports) with `harness: pi`; the roster is then Pi-driven with no
|
|
835
|
+
per-machine hand-editing.
|
|
836
|
+
- `galdr-reread`: `assets/harness-integration/README.md` — the two writers of
|
|
837
|
+
`opencode.json`, and the rule that only OpenCode agents belong in it.
|
|
838
|
+
|
|
839
|
+
## 2026-09-16 — the visualizer finds its DB, and the seat-hall actually builds
|
|
840
|
+
|
|
841
|
+
Two more readers left behind — both discovered by *starting the apps*, not by any
|
|
842
|
+
gate. Each failed silently in its own way.
|
|
843
|
+
|
|
844
|
+
- **`scripts/start.sh` pointed the smithy at a repo path that no longer holds the
|
|
845
|
+
DB.** `0003-private-data-separation` moved it to `$YMIR_HOME/smidja/smidja.db`,
|
|
846
|
+
but the starter still passed `CMD_DB=<repo>/apps/smidja/smidja_data/smidja.db`
|
|
847
|
+
— so the visualizer API died on boot with `smidja.db not found` and `:8437`
|
|
848
|
+
answered nothing. It now resolves the same pair `bin/smidja-bootstrap.sh` does
|
|
849
|
+
(home first, in-repo fallback, `SMIDJA_DB` override). The asset already
|
|
850
|
+
*described* this resolution; the code now implements it.
|
|
851
|
+
- **`bin/sessrumnir-ensure.sh` never built the app.** `[ "$built_present" ]` tests
|
|
852
|
+
a **literal, non-empty string** — always true — so `ensure --install` skipped
|
|
853
|
+
`build_app` and reported `built: no` with exit 0. Sessrúmnir only built when the
|
|
854
|
+
build was run by hand. Now `built_present` (the function) is called.
|
|
855
|
+
- `galdr-reread`: `.agents/skills/galdr-ymirsystem/assets/smidja.md` — the DB
|
|
856
|
+
path, the `scripts/start.sh` DB resolution, and the observer's read list.
|
|
857
|
+
|
|
858
|
+
## 2026-09-16 — the credential reaches the gate, and the hall stops leaking an IP
|
|
859
|
+
|
|
860
|
+
Setting the operator password had **no effect**. This is the bug that made that
|
|
861
|
+
true, and the runtime state that leaked a private address.
|
|
862
|
+
|
|
863
|
+
- **`scripts/start.sh` never loaded the platform env for the gate.** The gate is
|
|
864
|
+
`bun run apps/hlidskjalf/server/index.ts`, and the server reads
|
|
865
|
+
`process.env.HLIDSKJALF_AUTH` — but the launcher passed only `PORT`, and nothing
|
|
866
|
+
sourced `.env.local`. So a credential written by `bin/ymir-setup-auth.sh`
|
|
867
|
+
(correctly: `0600`, gitignored) never arrived, and because the gate treats an
|
|
868
|
+
empty `GATE_AUTH` as authenticated (`authed: GATE_AUTH ? … : true`) the gate
|
|
869
|
+
stayed **open**. `start.sh` now loads `.env.local` once, before the ports, for
|
|
870
|
+
every service it raises — the gate, Bifrost and Mimir.
|
|
871
|
+
- **`apps/odrerir/.astro/dev.json` was tracked.** Astro rewrites it on every
|
|
872
|
+
start with the live `pid` and the host's own addresses (LAN + tailnet) — a
|
|
873
|
+
private-IP leak into a public tree. It is now untracked and gitignored; the
|
|
874
|
+
generated types beside it stay tracked.
|
|
875
|
+
- `galdr-reread`: `assets/hlidskjalf-ui.md` (the gate must receive the credential)
|
|
876
|
+
and `assets/odrerir-hall.md` (`.astro/dev.json` is runtime state).
|
|
877
|
+
|
|
878
|
+
## 2026-09-16 — four readers that still pointed at the pre-split world
|
|
879
|
+
|
|
880
|
+
The app split and the hoard migration moved things; four readers never followed.
|
|
881
|
+
None failed loudly — each reported a clean PASS, a wrong SKIP, or a silent loss.
|
|
882
|
+
|
|
883
|
+
- **`bin/ymir-install.sh` wrote during `--check`.** The step chain ran
|
|
884
|
+
`bin/ymir-migrate.sh apply` unconditionally, so a preview that promises *"report
|
|
885
|
+
only, no writes"* actually **moved private data**. Migrations now apply only on
|
|
886
|
+
a real run: `if [ "$CHECK" = 0 ]; then ... apply; fi`.
|
|
887
|
+
- **`.agents/migrations/0003-private-data-separation.sh` skipped every directory.**
|
|
888
|
+
It pre-creates its target dirs, then its `copy` refused any target that already
|
|
889
|
+
existed — so `data/` (and every other directory source) was silently dropped.
|
|
890
|
+
The realm declaration never arrived and `0004` fell back to a neutral realm.
|
|
891
|
+
`copy` now **merges** a directory into its target (never overwriting a file) and
|
|
892
|
+
copies a single file only when it is absent.
|
|
893
|
+
- **`bin/smidja-bootstrap.sh` looked for the smithy at the old root.** `sys.path`
|
|
894
|
+
pointed at `smidja/`, but the split moved it to `apps/smidja/`, so the DB seed
|
|
895
|
+
died with `ModuleNotFoundError: No module named 'smidja_modules'`. It now
|
|
896
|
+
searches `apps/smidja` then `smidja`, so either layout works.
|
|
897
|
+
- **`bin/ymir-validate.sh` read the ledger from the pre-move path.** It looked at
|
|
898
|
+
`$YMIR_HOME/memory/`, but `0004-hoard-and-realms` put the ledger at
|
|
899
|
+
`$YMIR_HOME/hodd/memory/` — a false `runes FAIL` on a healthy home. It now tries
|
|
900
|
+
the hoard first, then the pre-move locations.
|
|
901
|
+
- `galdr-reread`: `.agents/skills/galdr-ymirsystem/assets/smidja.md` — the
|
|
902
|
+
protected-paths gotcha and the `smidja/` → `apps/smidja/` move.
|
|
903
|
+
|
|
533
904
|
## 2026-09-17 — Pi gets its agents, and a dead extension comes back
|
|
534
905
|
|
|
535
906
|
- **Pi has no agent loader.** Agent loading in Pi is a *package* (`pi-agents`,
|
package/bin/app-lib.sh
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# app-lib.sh — where an app actually lives.
|
|
3
|
+
#
|
|
4
|
+
# Two installs, one name. In a clone the apps sit at `apps/<surface>` (the
|
|
5
|
+
# registry's `repo: apps/<path>` block clones them there). In a packaged install
|
|
6
|
+
# the tree has no `apps/` at all: each surface arrives as a dependency, at
|
|
7
|
+
# `node_modules/@zerwiz/<package>`.
|
|
8
|
+
#
|
|
9
|
+
# surface package
|
|
10
|
+
# ---------------- ---------------------
|
|
11
|
+
# hlidskjalf hlidskjalf
|
|
12
|
+
# hlidskjalf-mobile hlidskjalf-mobile
|
|
13
|
+
# odrerir odrerir
|
|
14
|
+
# sessrumnir sessrumnir
|
|
15
|
+
# smidja smidja-factory
|
|
16
|
+
#
|
|
17
|
+
# A surface's name is not always its package's name — and a path is not a name at
|
|
18
|
+
# all. Every script that needs an app resolves it HERE, so the two shapes can
|
|
19
|
+
# never disagree (Rule 07). Source-safe; functions only.
|
|
20
|
+
#
|
|
21
|
+
# app_dir <surface> <result-var> the app's directory (empty + exit 1 if absent)
|
|
22
|
+
# app_pkg <surface> the npm package name for that surface
|
|
23
|
+
set -u
|
|
24
|
+
|
|
25
|
+
app_pkg() { # <surface> → the package name npm serves
|
|
26
|
+
case "${1-}" in
|
|
27
|
+
smidja) printf '%s' 'smidja-factory' ;;
|
|
28
|
+
*) printf '%s' "${1-}" ;;
|
|
29
|
+
esac
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
app_dir() { # <surface> <result-var> — a clone's apps/<x>, else the package
|
|
33
|
+
# Scratch names are function-prefixed on purpose: `printf -v <name>` writes to THIS
|
|
34
|
+
# function's scope, so a scratch variable sharing the caller's requested name
|
|
35
|
+
# would swallow the answer (a caller asking for "c" or "root" would get nothing).
|
|
36
|
+
local _apd_surface="${1-}" _apd_rv="${2-}" _apd_root _apd_pkg _apd_c
|
|
37
|
+
[ -n "$_apd_surface" ] && [ -n "$_apd_rv" ] || return 2
|
|
38
|
+
_apd_root="${YMIR_ROOT_DIR:-}"
|
|
39
|
+
if [ -z "$_apd_root" ]; then _apd_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; fi
|
|
40
|
+
_apd_pkg="$(app_pkg "$_apd_surface")"
|
|
41
|
+
for _apd_c in "$_apd_root/apps/$_apd_surface" "$_apd_root/node_modules/@zerwiz/$_apd_pkg"; do
|
|
42
|
+
[ -d "$_apd_c" ] && { printf -v "$_apd_rv" '%s' "$_apd_c"; return 0; }
|
|
43
|
+
done
|
|
44
|
+
printf -v "$_apd_rv" '%s' ""
|
|
45
|
+
return 1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Some callers need the directory a mint writes INTO (an app's public/, electron/,
|
|
49
|
+
# dist/…). A packaged app is read-only in spirit, so the clone's apps/ is preferred
|
|
50
|
+
# when it exists; otherwise the package is the only truth there is.
|
|
51
|
+
app_root() { # <result-var> — where apps live in THIS tree
|
|
52
|
+
local _apr_rv="${1-}" _apr_root
|
|
53
|
+
[ -n "$_apr_rv" ] || return 2
|
|
54
|
+
_apr_root="${YMIR_ROOT_DIR:-}"
|
|
55
|
+
if [ -z "$_apr_root" ]; then _apr_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; fi
|
|
56
|
+
if [ -d "$_apr_root/apps" ]; then printf -v "$_apr_rv" '%s' "$_apr_root/apps"; return 0; fi
|
|
57
|
+
printf -v "$_apr_rv" '%s' "$_apr_root/node_modules/@zerwiz"
|
|
58
|
+
}
|
package/bin/design-check.sh
CHANGED
|
@@ -19,8 +19,19 @@ set -u
|
|
|
19
19
|
VERSION="1.0.0"
|
|
20
20
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
21
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
22
|
+
# Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
|
|
23
|
+
# npm install — both shapes, one resolver (bin/app-lib.sh).
|
|
24
|
+
if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
|
|
25
|
+
_ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
|
|
27
|
+
[ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
|
|
28
|
+
done
|
|
29
|
+
unset _ya _yac
|
|
30
|
+
fi
|
|
31
|
+
app_dir sessrumnir APP_SESSRUMNIR || APP_SESSRUMNIR=""
|
|
32
|
+
|
|
22
33
|
TOKENS="$ROOT/midgard/design-system/tokens.css"
|
|
23
|
-
SEEDS="$
|
|
34
|
+
SEEDS="$APP_SESSRUMNIR/src/renderer/src/themes/fensalir.json"
|
|
24
35
|
|
|
25
36
|
case "${1-}" in
|
|
26
37
|
-v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
|
package/bin/design-icon.sh
CHANGED
|
@@ -16,6 +16,21 @@ set -u
|
|
|
16
16
|
VERSION="1.0.0"
|
|
17
17
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
18
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
19
|
+
# Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
|
|
20
|
+
# npm install — both shapes, one resolver (bin/app-lib.sh).
|
|
21
|
+
if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
|
|
22
|
+
_ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
|
|
24
|
+
[ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
|
|
25
|
+
done
|
|
26
|
+
unset _ya _yac
|
|
27
|
+
fi
|
|
28
|
+
app_dir hlidskjalf APP_HLIDSKJALF || APP_HLIDSKJALF=""
|
|
29
|
+
app_dir hlidskjalf-mobile APP_HLIDSKJALF_MOBILE || APP_HLIDSKJALF_MOBILE=""
|
|
30
|
+
app_dir odrerir APP_ODRERIR || APP_ODRERIR=""
|
|
31
|
+
app_dir sessrumnir APP_SESSRUMNIR || APP_SESSRUMNIR=""
|
|
32
|
+
app_dir smidja-factory APP_SMIDJA_FACTORY || APP_SMIDJA_FACTORY=""
|
|
33
|
+
|
|
19
34
|
ICONS="$ROOT/midgard/design-system/icons"
|
|
20
35
|
STONE="#0e0c09"
|
|
21
36
|
|
|
@@ -30,16 +45,19 @@ ACTION="${1:-list}"; shift || true
|
|
|
30
45
|
APPS=(
|
|
31
46
|
"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
47
|
"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|
|
|
48
|
+
"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
49
|
"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-
|
|
50
|
+
"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
51
|
)
|
|
37
52
|
|
|
38
53
|
list() {
|
|
39
54
|
printf 'app_icons[%d]{app,rune,tint,says}:\n' "${#APPS[@]}"
|
|
40
55
|
for row in "${APPS[@]}"; do
|
|
41
|
-
IFS='|' read -r dir glyph tint says
|
|
42
|
-
|
|
56
|
+
IFS='|' read -r dir glyph tint says icon _exec _klass _name <<<"$row"
|
|
57
|
+
# The surface's name is the one the desktop knows it by (ymir-smidja →
|
|
58
|
+
# smidja), never the directory's leaf — a path is not a name (the naming law).
|
|
59
|
+
surface="${icon#ymir-}"
|
|
60
|
+
printf ' "%s","%s","%s","%s"\n' "$surface" "$glyph" "$tint" "$says"
|
|
43
61
|
done
|
|
44
62
|
printf 'runes[6]{glyph,name,meaning}:\n'
|
|
45
63
|
printf ' "algiz","ᛉ","the Ymir emblem — the platform itself"\n'
|
package/bin/desktop-place.sh
CHANGED
|
@@ -29,6 +29,19 @@ fi
|
|
|
29
29
|
VERSION="1.0.0"
|
|
30
30
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
31
31
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
32
|
+
# Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
|
|
33
|
+
# npm install — both shapes, one resolver (bin/app-lib.sh).
|
|
34
|
+
if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
|
|
35
|
+
_ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
+
for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
|
|
37
|
+
[ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
|
|
38
|
+
done
|
|
39
|
+
unset _ya _yac
|
|
40
|
+
fi
|
|
41
|
+
app_dir hlidskjalf APP_HLIDSKJALF || APP_HLIDSKJALF=""
|
|
42
|
+
app_dir sessrumnir APP_SESSRUMNIR || APP_SESSRUMNIR=""
|
|
43
|
+
app_dir odrerir APP_ODRERIR || APP_ODRERIR=""
|
|
44
|
+
|
|
32
45
|
HYPR_DIR="$HOME/.config/hypr"
|
|
33
46
|
RULE_FILE="$HYPR_DIR/ymir-desktops.lua"
|
|
34
47
|
# The three Ymir surfaces, in the order they should claim desktops.
|
|
@@ -46,7 +59,7 @@ CLASS_odrerir="ymir-odrerir"
|
|
|
46
59
|
# not a branch of this one.
|
|
47
60
|
install_entries() {
|
|
48
61
|
local dst="$HOME/.local/share/applications" n=0
|
|
49
|
-
local dirs=("$
|
|
62
|
+
local dirs=("$APP_HLIDSKJALF/electron" "$APP_SESSRUMNIR/resources" "$APP_ODRERIR/electron")
|
|
50
63
|
if [ "$(ymir_os)" != linux ] && [ "$(ymir_os)" != wsl ]; then
|
|
51
64
|
printf 'skip: desktop entries are Omarchy/Linux-shaped; this host is %s\n' "$(ymir_os)" >&2
|
|
52
65
|
return 0
|