@zerwiz/ymir 0.1.8 → 0.1.10

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.
@@ -192,6 +192,99 @@ cloth_rules[4]{rule,why}:
192
192
  TOON stays on stdout. The installer prints it at the consent, and ends with the
193
193
  next steps — *a reaction for every action, a next step for every ending*.
194
194
 
195
+ ### A clone and a package are one tree — `bin/app-lib.sh`
196
+
197
+ The apps are the only thing that differs between the two shapes, and only in
198
+ where they live:
199
+
200
+ ```
201
+ surface a clone an npm install
202
+ --------------- ---------------------- ------------------------------------
203
+ hlidskjalf apps/hlidskjalf node_modules/@zerwiz/hlidskjalf
204
+ hlidskjalf-mobile apps/hlidskjalf-mobile node_modules/@zerwiz/hlidskjalf-mobile
205
+ odrerir apps/odrerir node_modules/@zerwiz/odrerir
206
+ sessrumnir apps/sessrumnir node_modules/@zerwiz/sessrumnir
207
+ smidja apps/smidja-factory node_modules/@zerwiz/smidja-factory
208
+ ```
209
+
210
+ `bin/app-lib.sh` answers for both — `app_dir <surface> <var>`, `app_pkg
211
+ <surface>` — and **eighteen files** were converted to it: the raise path
212
+ (`scripts/start.sh`), the windows (`scripts/electron.sh`), the invite door, the
213
+ seat-hall trio, Eir, the icon mint, the desktop placement, the hall snapshot, and
214
+ the installer's own SPA and shell steps. `bin/smidja-lib.sh` delegates to it, so
215
+ there is **one** truth about where things live.
216
+
217
+ **The trap that named itself.** `printf -v <name>` writes to the *function's*
218
+ scope. A helper whose scratch variable shares the caller's requested name
219
+ swallows the answer — `app_dir hlidskjalf c` returned nothing because the
220
+ helper's own `local c` held the path. Every scratch name in the resolvers is
221
+ function-prefixed (`_apd_c`, `_smd_c`, `_apr_root`) for exactly that reason, and a
222
+ new resolver must follow the rule.
223
+
224
+ **Why it matters beyond tidiness:** a packaged install shipped with eighteen
225
+ scripts looking in `apps/`, so `ymir raise` died on
226
+ `cd …/apps/hlidskjalf: No such file or directory` while every package had in fact
227
+ arrived. A shape assumption is a bug that only shows itself on the *other* shape.
228
+
229
+ ### The hall answers on the port it was given
230
+
231
+ `scripts/start.sh` raises the SPA, and the SPA must listen on
232
+ `$HLIDSKJALF_PORT` (3888 by default) — every other door looks for it there. Two
233
+ rules came out of a packaged install where it listened nowhere:
234
+
235
+ ```
236
+ spa_serving[2]{shape,how}:
237
+ "a packaged install","serves its built ./dist with `vite preview --port $PORT --strictPort`"
238
+ "a clone","serves the dev server, but STILL on $PORT — `npm run dev -- --port $PORT --strictPort`"
239
+ ```
240
+
241
+ A bare `npm run dev` lets Vite take 5173 and the hall silently answers on the
242
+ wrong port; the raise now prints the log's last lines when the port stays silent
243
+ rather than claiming success. Óðrerir resolves through `bin/app-lib.sh` like every
244
+ other surface (it read as `missing apps/odrerir` on a packaged install).
245
+
246
+ ### The marks land on any desktop — not only Omarchy's
247
+
248
+ `bin/desktop-place.sh` holds **two kinds of thing**, and they were behind one gate:
249
+
250
+ ```
251
+ desktop_halves[2]{half,who_reads_it}:
252
+ "the window rules (numbered desktops, the Lua rule)","Hyprland / Omarchy only"
253
+ "the launcher entries and the rune icons","the freedesktop standard — GNOME, KDE, Hyprland alike"
254
+ ```
255
+
256
+ A GNOME operator gets entries and icons and no window rules, which is the correct
257
+ answer — and the reason a packaged install on GNOME placed *nothing* while the
258
+ clone's marks already existed. `bin/desktop-place.sh entries` is the launcher half
259
+ alone (any Linux desktop), the installer's `marks` step calls it, and both the
260
+ desktop database and the icon cache are refreshed after.
261
+
262
+ ### What a user went from, and to
263
+
264
+ `npm install -g` prints *"changed 266 packages"* and no versions. The CLI records
265
+ the version it last ran in the home's state (`state/version`) and says the
266
+ transition **once**, on the first run after an update:
267
+
268
+ ```
269
+ the tree moved 0.1.7 → 0.1.9
270
+ run `ymir eir` to see what stands, `ymir raise` to lift the hall
271
+ ```
272
+
273
+ ### The patience words — `style_patience`
274
+
275
+ A long hour must say what it is doing. `bin/ymir-style.sh` carries the line, and
276
+ the installer's long chain and the raise path's build open with it:
277
+
278
+ ```
279
+ ◆ much moves the halls are being stood up for the first time
280
+ this hour is long, and nothing of yours is lost in it —
281
+ roots come home, shapes are re-cut, names are set true again.
282
+ Your patience is noted, and it is earned.
283
+ ```
284
+
285
+ Written in the house voice, kept under four lines (density), and shown on stderr
286
+ with everything else the cloth renders.
287
+
195
288
  ### Two libs the packaged tree needs
196
289
 
197
290
  ```
@@ -62,6 +62,15 @@ This touches only the Brokk repo and its own worktrees, never anything under `pr
62
62
  Summarize what landed without internal vocabulary: which parts of the fleet are now on the latest, and which were left as-is and why.
63
63
  Surface any skipped target whose reason needs the Allfather's attention (a home with un-landed changes, local edits).
64
64
 
65
+ 5. **Re-check where the apps live — an update can move the shape.**
66
+ `bin/app-lib.sh` resolves a surface in a clone (`apps/<surface>`) or in a
67
+ package (`node_modules/@zerwiz/<package>`); `bin/smidja-lib.sh` and
68
+ `bin/electron-lib.sh` do the same for the smithy and a shell's runtime. After a
69
+ fast-forward, run `bin/ymir-plan.sh --phase 5` — if a surface that stood
70
+ installed now reads `BLOCKED`, the layout assumption moved and the resolver
71
+ needs the new shape. A long update should also say so: `style_patience` from
72
+ `bin/ymir-style.sh` opens the slow part in the house's own voice.
73
+
65
74
  5. **Read the plan after an update — the new version may expect more of this host.**
66
75
  A tracked change can add a step, a root, or a setting the running machine has not
67
76
  met yet. After a fast-forward that moved the instruction surface, ask what the
@@ -48,6 +48,35 @@ To move an existing home to a new place, re-run the install with the new value:
48
48
  YMIR_HOME=/path/to/new-home bin/ymir-install.sh
49
49
  ```
50
50
 
51
+ ## The doors — what the operator types afterwards
52
+
53
+ Two commands land on PATH (`ymir`, `ymir-install`); everything else is a door on
54
+ `ymir`, named for the figure who does the work. `ymir --help` lists them all.
55
+
56
+ ```
57
+ ymir raise | lower lift the hall, or lay it down
58
+ ymir hlidskjalf | sessrumnir the two windows
59
+ ymir smidja the board on :8437 — build · start · stop · status
60
+ ymir heimdall [invite] the way in, and letting someone else in
61
+ ymir eir what stands, and mend what does not
62
+ ymir groa [migrate] take the latest, and mend this home forward
63
+ ymir mimir · sense · plan
64
+ ```
65
+
66
+ ## Both shapes — a clone and an npm install
67
+
68
+ The operator may have cloned the tree or installed the package; the scripts must
69
+ not care. `bin/app-lib.sh` resolves a surface either way (`apps/<surface>`, else
70
+ `node_modules/@zerwiz/<package>`), `bin/smidja-lib.sh` resolves the smithy, and
71
+ `bin/electron-lib.sh` verifies a shell's runtime. To exercise the *other* shape
72
+ without a second machine:
73
+
74
+ ```bash
75
+ YMIR_ROOT_DIR=/path/to/an/npm/@zerwiz/ymir bash bin/app-lib.sh # resolve against it
76
+ ```
77
+
78
+ That variable is how a resolver is tested against a package from a clone.
79
+
51
80
  ## Which root holds what
52
81
 
53
82
  ```
package/CHANGELOG.md CHANGED
@@ -1,4 +1,100 @@
1
1
 
2
+ ## 2026-09-17 — 0.1.10: the hall answers on its port, and the marks land
3
+
4
+ - **The SPA listens where it is asked.** A bare `npm run dev` let Vite take 5173,
5
+ so `:3888` answered nothing in either shape; a packaged tree now serves its built
6
+ `./dist` and every shape passes `--port "$HLIDSKJALF_PORT" --strictPort`.
7
+ - **Óðrerir resolves** through `bin/app-lib.sh` instead of the clone's `apps/`.
8
+ - **The launcher entries and rune icons land on any Linux desktop** — the
9
+ freedesktop half of desktop integration is no longer behind Omarchy's gate, so a
10
+ GNOME operator gets marks at all.
11
+ - **The version a user went from and to** is said once, on the first run after an
12
+ update: *the tree moved 0.1.9 → 0.1.10*.
13
+
14
+ ## 2026-09-17 — npm-only: the hall answers on its own port, and the marks land on any desktop
15
+
16
+ The Allfather turned off every service running from the clone and asked for the
17
+ npm path alone to be true.
18
+
19
+ - **The SPA never listened on `:3888`.** `scripts/start.sh` raised it with a bare
20
+ `npm run dev`, so Vite took 5173 — then 5174 — while `ymir-validate.sh` and
21
+ every door looked for `:3888`. The port is now our decision
22
+ (`--port "$HLIDSKJALF_PORT" --strictPort`), a packaged install serves its built
23
+ `./dist` through `vite preview` instead of a dev server, and the raise prints
24
+ the last lines of the log when the port does not answer instead of claiming
25
+ success.
26
+ - **Óðrerir was skipped on a packaged install** — `HALL_DIR="$ROOT/apps/odrerir"`,
27
+ the clone's layout again. It resolves through `bin/app-lib.sh` now
28
+ (`app_dir odrerir`), like every other surface.
29
+ - **The launcher entries and the icons are the freedesktop half, not Omarchy's.**
30
+ Both halves of `bin/desktop-place.sh` sat behind the Omarchy gate, so a GNOME
31
+ operator got *nothing* — no entries, no rune icons — which is why the desktop
32
+ marks from the clone existed and the ones from npm never appeared. `entries` is
33
+ its own verb now (any Linux desktop), the installer's `marks` step calls it, and
34
+ the desktop database and icon cache are refreshed afterwards.
35
+ - **A user can see what moved.** `npm install -g` says *"changed 266 packages"* and
36
+ no versions. The CLI records the version it last ran and says the transition
37
+ once: *the tree moved 0.1.7 → 0.1.9 · run `ymir eir` to see what stands*.
38
+
39
+ ## 2026-09-17 — the doors, written down where a user will find them
40
+
41
+ - **`README.md` gains "After it installs — the doors"**: the two commands on PATH,
42
+ the twelve doors on `ymir`, the lawless name that answers once and points at the
43
+ lawful one, the patience words in full, and a section on **both shapes** — a
44
+ clone and a package, with one resolver and one rule (colour for the eye, TOON
45
+ for the pipe).
46
+ - **The skills that must know are taught.** Galdr's `assets/installation.md` gains
47
+ the shape table, the eighteen converted call sites, and the trap that named
48
+ itself (`printf -v` writes to the function's scope — scratch names are
49
+ function-prefixed for that reason). The `ymir-host` skill's `assets/install.md`
50
+ gains the doors and how to exercise the *other* shape without a second machine
51
+ (`YMIR_ROOT_DIR`). The `groa-update` skill gains the step that matters after
52
+ every update: **re-check where the apps live** — a shape assumption is a bug
53
+ that only shows itself on the other shape — and say the long hour in the house's
54
+ voice.
55
+
56
+ ## 2026-09-17 — 0.1.9: both shapes reach the registry
57
+
58
+ - **`ymir raise` works on a packaged install.** 0.1.8 shipped before the resolver,
59
+ so a user who installed from npm hit `cd …/apps/hlidskjalf: No such file or
60
+ directory` — every package had arrived, and eighteen scripts were looking in the
61
+ clone's `apps/`. `bin/app-lib.sh` resolves a surface in either shape now.
62
+ - **A clone and a package are one tree to the scripts.** Proven against both: the
63
+ five surfaces resolve from `/home/zerwiz/Ymir` and from an npm-installed
64
+ package, and `ymir raise` against a packaged tree fetches the app's
65
+ dependencies, raises the gate API, starts Nornir cron, and brings up Bifrost and
66
+ the well.
67
+ - **The patience words** — `style_patience` — open the installer's long chain and
68
+ the raise path's build, so a long hour says what it is doing.
69
+
70
+ ## 2026-09-17 — both shapes: a clone and an npm install, and the patience words
71
+
72
+ - **`ymir raise` failed on a packaged install** — and the reason was ours, not
73
+ npm's. Every package had arrived; the scripts looked for the apps in the clone's
74
+ `apps/`, where a package keeps nothing. `scripts/start.sh` died on
75
+ `cd …/apps/hlidskjalf: No such file or directory`. **Eighteen files assumed the
76
+ clone's layout.**
77
+ - **One resolver, both shapes:** `bin/app-lib.sh` — `apps/<surface>` in a clone,
78
+ `node_modules/@zerwiz/<package>` in a package, with the surface→package map
79
+ (`smidja` → `@zerwiz/smidja-factory`). Wired into the raise path, the windows,
80
+ the invite door, the seat-hall, Eir, the icons, the placement, the hall snapshot
81
+ and the installer's own SPA and shell steps. `bin/smidja-lib.sh` delegates to it
82
+ now, so there is one truth about where things live.
83
+ - **Proven in both trees**: all five surfaces resolve from `/home/zerwiz/Ymir`
84
+ (a clone) and from an npm-installed package. And `ymir raise` run against the
85
+ packaged tree no longer dies — it fetches the app's dependencies, raises the
86
+ gate API, **starts Nornir cron**, and brings up Bifrost and the well.
87
+ - **A trap worth naming:** `printf -v <name>` writes to the *function's* scope, so
88
+ a helper whose scratch variable shares the caller's requested name swallows the
89
+ answer. Every scratch name in the resolvers is function-prefixed for that
90
+ reason.
91
+ - **The patience words.** A long install should say what it is doing and why it
92
+ takes a while. `style_patience` — the cloth's own line — now opens the
93
+ installer's long chain and the raise path's build:
94
+ *much moves · the halls are being set right · this hour is long, and nothing of
95
+ yours is lost in it — roots come home, shapes are re-cut, names are set true
96
+ again. Your patience is noted, and it is earned.*
97
+
2
98
  ## 2026-09-17 — 0.1.8: the day's work reaches npm
3
99
 
4
100
  - **The doors.** `ymir` now carries the operator's verbs, each named for the
package/README.md CHANGED
@@ -666,3 +666,57 @@ packages (Hlidskjalf, Óðrerir, Sessrúmnir, Smíðja) — one per app.
666
666
  Private data never touches npm — all user data lives at `$YMIR_HOME` (the hoard
667
667
  at `hodd/` and the realms at `svartalfaheim/`) and syncs via the user's
668
668
  **private** GitHub repo, never a public one.
669
+
670
+ ### After it installs — the doors
671
+
672
+ `npm install -g @zerwiz/ymir` puts **two** commands on your PATH: `ymir` and
673
+ `ymir-install`. Everything else is a door on `ymir`, named for the figure whose
674
+ work it does — because a great many doors either open or they do not, and a name
675
+ that says whose hand is on it is the difference between guessing and knowing.
676
+
677
+ ```
678
+ ymir first setup: the plan, then your word
679
+ ymir raise | lower lift the hall, or lay it down
680
+ ymir hlidskjalf the high seat's window
681
+ ymir smidja the smithy's board (:8437) — build · start · stop · status
682
+ ymir sessrumnir the seat-hall's window
683
+ ymir heimdall the way in — your credential (status · set · github)
684
+ ymir invite let someone else in — mint · list · revoke
685
+ ymir eir what stands, and mend what does not
686
+ ymir groa [migrate] take the latest, and mend this home forward
687
+ ymir mimir · sense · plan
688
+ ```
689
+
690
+ A name the law has not given a home still answers, once, and tells you the name
691
+ that has it: `ymir doctor` replies *the door is named `ymir eir` now*.
692
+
693
+ **A long hour says so, in its own voice.** The installer opens with the plan and,
694
+ when the work will take a while, tells you plainly:
695
+
696
+ ```
697
+ ◆ much moves the halls are being stood up for the first time
698
+ this hour is long, and nothing of yours is lost in it —
699
+ roots come home, shapes are re-cut, names are set true again.
700
+ Your patience is noted, and it is earned.
701
+ ```
702
+
703
+ ### Both shapes — a clone and a package are one tree
704
+
705
+ Ymir runs the same whether you **cloned** it or **installed** it from npm. The
706
+ apps are the difference, and only in where they live:
707
+
708
+ | | a clone | an npm install |
709
+ |---|---|---|
710
+ | the surfaces | `apps/hlidskjalf` · `odrerir` · `sessrumnir` · `smidja-factory` | `node_modules/@zerwiz/hlidskjalf` · `odrerir` · `sessrumnir` · `smidja-factory` |
711
+ | how they got there | the registry's `repo: apps/<path>` block clones each | declared as dependencies of the distro |
712
+
713
+ One resolver (`bin/app-lib.sh`) answers for both, so a script never has to care
714
+ which shape it woke in — and the same is true of the smithy (`bin/smidja-lib.sh`)
715
+ and of a desktop shell's runtime (`bin/electron-lib.sh`). The plan tells you which
716
+ surfaces are present, which are declared but not yet fetched, and which have no
717
+ package at all.
718
+
719
+ **Colour is for the eye, never for the pipe.** `bin/ymir-style.sh` renders in the
720
+ halls' own cloth — bone for words, bronze for what acts, steel for what stands,
721
+ blood for what is wrong — and only when a human is watching (a terminal, no
722
+ `NO_COLOR`). What a pipeline reads on stdout stays plain TOON.
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
+ }
@@ -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="$ROOT/apps/sessrumnir/src/renderer/src/themes/fensalir.json"
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 ;;
@@ -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
 
@@ -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.
@@ -38,15 +51,20 @@ CLASS_smidja="ymir-smidja"
38
51
  CLASS_sessrumnir="sessrumnir"
39
52
  CLASS_odrerir="ymir-odrerir"
40
53
 
41
- # ── launcher entries (Omarchy / Linux desktop only) ──────────────────────────
54
+ # ── launcher entries (ANY Linux desktop — GNOME, KDE, Hyprland) ──────────────
55
+ # Two halves live in this file, and they are not the same kind of thing:
56
+ # · the WINDOW RULES are Hyprland's (Omarchy) — numbered desktops, a Lua rule;
57
+ # · the LAUNCHER ENTRIES and the app icons are the freedesktop standard, which
58
+ # GNOME, KDE and Hyprland all read from the same place.
59
+ # A GNOME operator gets entries and icons and no window rules, which is the
60
+ # correct answer — and the reason a packaged install on GNOME had no icons at
61
+ # all, because both halves lived behind the Omarchy gate.
62
+ #
42
63
  # The .desktop files are templates (__YMIR_ROOT__, not an absolute path), because
43
- # where this checkout lives is a fact about the machine, not about Ymir. They are
44
- # rendered into the user's applications directory. This is DESKTOP INTEGRATION —
45
- # Omarchy/Hyprland is the supported environment; another OS needs its own launcher,
46
- # not a branch of this one.
64
+ # where this checkout lives is a fact about the machine, not about Ymir.
47
65
  install_entries() {
48
66
  local dst="$HOME/.local/share/applications" n=0
49
- local dirs=("$ROOT/apps/hlidskjalf/electron" "$ROOT/apps/sessrumnir/resources" "$ROOT/apps/odrerir/electron")
67
+ local dirs=("$APP_HLIDSKJALF/electron" "$APP_SESSRUMNIR/resources" "$APP_ODRERIR/electron")
50
68
  if [ "$(ymir_os)" != linux ] && [ "$(ymir_os)" != wsl ]; then
51
69
  printf 'skip: desktop entries are Omarchy/Linux-shaped; this host is %s\n' "$(ymir_os)" >&2
52
70
  return 0
@@ -182,6 +200,16 @@ include_launchers_file() {
182
200
  }
183
201
 
184
202
  case "$ACTION" in
203
+ entries)
204
+ # The launcher half on its own: any Linux desktop, no Hyprland required.
205
+ install_entries
206
+ if command -v update-desktop-database >/dev/null 2>&1; then
207
+ update-desktop-database "$HOME/.local/share/applications" >/dev/null 2>&1 || true
208
+ fi
209
+ if command -v gtk-update-icon-cache >/dev/null 2>&1 && [ -d "$HOME/.local/share/icons/hicolor" ]; then
210
+ gtk-update-icon-cache -q -t -f "$HOME/.local/share/icons/hicolor" >/dev/null 2>&1 || true
211
+ fi
212
+ ;;
185
213
  plan)
186
214
  plan="$(plan_desktops)"
187
215
  printf 'desktop-place[%d]{app,desktop}:\n' "$(printf '%s\n' "$plan" | grep -c .)"
package/bin/eir-doctor.sh CHANGED
@@ -22,6 +22,17 @@ STATE="${BROKK_STATE_OVERRIDE:-$YMIR_HOME/state}"
22
22
  # stdout stay the data (bin/ymir-style.sh).
23
23
  if [ -z "${YMIR_STYLE_LOADED:-}" ]; then . "$SCRIPT_DIR/ymir-style.sh"; YMIR_STYLE_LOADED=1; fi
24
24
  style_init
25
+ # Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
26
+ # npm install — both shapes, one resolver (bin/app-lib.sh).
27
+ if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
28
+ _ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29
+ for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
30
+ [ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
31
+ done
32
+ unset _ya _yac
33
+ fi
34
+ app_dir sessrumnir APP_SESSRUMNIR || APP_SESSRUMNIR=""
35
+
25
36
 
26
37
  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
27
38
  ACTION="${1:-check}"; shift || true
@@ -154,7 +165,7 @@ detail() { # <name> -> one short fact
154
165
  herdr) "have herdr && herdr --version 2>/dev/null | head -1 || echo 'herdr absent'" ;;
155
166
  a2abridge) "have a2abridge && a2abridge --version 2>/dev/null | head -1 || echo 'engine absent'" ;;
156
167
  hermes) "have hermes && echo present || echo absent" ;;
157
- sessrumnir)"[ -d $ROOT/apps/sessrumnir/out ] && echo built || echo 'not built'" ;;
168
+ sessrumnir)"[ -d $APP_SESSRUMNIR/out ] && echo built || echo 'not built'" ;;
158
169
  well) "echo 'engram :4602'" ;;
159
170
  mcp) "echo 'a2abridge + engram'" ;;
160
171
  lock) "cat $STATE/.lock 2>/dev/null | tr -d '[:space:]' | sed 's/^/pid /' || echo none" ;;
@@ -16,6 +16,17 @@
16
16
  # (the Live Hall's own deck; the merged public site takes it from its build).
17
17
  set -u
18
18
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && 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 odrerir APP_ODRERIR || APP_ODRERIR=""
29
+
19
30
  ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
20
31
 
21
32
  # The operator's settings and secrets live in the home they chose, never in the
@@ -37,7 +48,7 @@ hoard_data_dir YMIR_DATA_DIR
37
48
  # runtime state belong to the home the operator chose at installation, never in
38
49
  # the tree — a packaged install replaces its tree on upgrade (Rule 04).
39
50
  STATE="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
40
- OUT="${1:-$ROOT/apps/odrerir/public/livehall.json}"
51
+ OUT="${1:-$APP_ODRERIR/public/livehall.json}"
41
52
 
42
53
  runes_file="${BROKK_RUNES_FILE:-${YMIR_HOME:-$HOME/Documents/Ymir}/hodd/memory/runes_audit.md}"
43
54
  PROJECTS_FILE="${YMIR_HOME:-$HOME/Documents/Ymir}/hodd/identity/projects.yaml"
@@ -20,7 +20,18 @@ set -u
20
20
  VERSION="1.0.0"
21
21
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
22
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
23
- APP="$ROOT/apps/sessrumnir"
23
+ # Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
24
+ # npm install — both shapes, one resolver (bin/app-lib.sh).
25
+ if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
26
+ _ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
28
+ [ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
29
+ done
30
+ unset _ya _yac
31
+ fi
32
+ app_dir sessrumnir APP_SESSRUMNIR || APP_SESSRUMNIR=""
33
+
34
+ APP="$APP_SESSRUMNIR"
24
35
 
25
36
  case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help|"") sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
26
37
  CMD="${1-}"; shift || true
@@ -24,7 +24,18 @@
24
24
  set -euo pipefail
25
25
 
26
26
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
27
- APP="$ROOT/apps/sessrumnir"
27
+ # Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
28
+ # npm install — both shapes, one resolver (bin/app-lib.sh).
29
+ if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
30
+ _ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
32
+ [ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
33
+ done
34
+ unset _ya _yac
35
+ fi
36
+ app_dir sessrumnir APP_SESSRUMNIR || APP_SESSRUMNIR=""
37
+
38
+ APP="$APP_SESSRUMNIR"
28
39
  FORK="$APP/fork"
29
40
  UPSTREAM_REPO="${SESSRUMNIR_UPSTREAM:-https://github.com/zerwiz/pi-desktop.git}"
30
41
  OWNED_JSON="$FORK/owned.json"
package/bin/sessrumnir.sh CHANGED
@@ -14,6 +14,17 @@ set -u
14
14
  VERSION="1.0.0"
15
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
16
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
17
+ # Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
18
+ # npm install — both shapes, one resolver (bin/app-lib.sh).
19
+ if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
20
+ _ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
22
+ [ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
23
+ done
24
+ unset _ya _yac
25
+ fi
26
+ app_dir sessrumnir APP_SESSRUMNIR || APP_SESSRUMNIR=""
27
+
17
28
 
18
29
  # The roots that live OUTSIDE the code tree: this machine's records and the
19
30
  # runtime state belong to the home the operator chose at installation, never in
@@ -27,7 +38,7 @@ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
27
38
  fi
28
39
  hoard_state_dir YMIR_STATE_DIR
29
40
  hoard_data_dir YMIR_DATA_DIR
30
- APP="$ROOT/apps/sessrumnir"
41
+ APP="$APP_SESSRUMNIR"
31
42
  PID_FILE="$YMIR_STATE_DIR/sessrumnir.pid"
32
43
  LOG_FILE="$YMIR_STATE_DIR/sessrumnir.log"
33
44
 
package/bin/smidja-lib.sh CHANGED
@@ -20,20 +20,21 @@ SMIDJA_SURFACE="smidja"
20
20
  SMIDJA_PACKAGE="smidja-factory"
21
21
 
22
22
  smidja_factory_dir() { # <result-var> — the smithy's directory, or empty
23
- local result_var=${1-} root="${YMIR_ROOT_DIR:-}" c
24
- [ -n "$result_var" ] || return 2
25
- if [ -z "$root" ]; then
26
- root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23
+ # The general resolver (bin/app-lib.sh) knows both shapes; the smithy is one
24
+ # surface among them. The old skill symlink is the last resort, and a dangling
25
+ # one is not a home — the visualizer must be readable there.
26
+ local _smd_rv=${1-} _smd_c
27
+ [ -n "$_smd_rv" ] || return 2
28
+ if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
29
+ local _sl; _sl="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
+ [ -r "$_sl/app-lib.sh" ] && { . "$_sl/app-lib.sh"; YMIR_APP_LIB_LOADED=1; }
27
31
  fi
28
- for c in \
29
- "$root/apps/$SMIDJA_PACKAGE" \
30
- "$root/node_modules/@zerwiz/$SMIDJA_PACKAGE" \
31
- "$root/.agents/skills/$SMIDJA_PACKAGE"
32
- do
33
- # A dangling symlink is not a home: the visualizer must be readable there.
34
- [ -d "$c/apps/visualizer" ] && { printf -v "$result_var" '%s' "$c"; return 0; }
35
- done
36
- printf -v "$result_var" '%s' ""
32
+ if app_dir smidja _smd_c 2>/dev/null && [ -d "$_smd_c/apps/visualizer" ]; then
33
+ printf -v "$_smd_rv" '%s' "$_smd_c"; return 0
34
+ fi
35
+ _smd_c="${YMIR_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/.agents/skills/$SMIDJA_PACKAGE"
36
+ [ -d "$_smd_c/apps/visualizer" ] && { printf -v "$_smd_rv" '%s' "$_smd_c"; return 0; }
37
+ printf -v "$_smd_rv" '%s' ""
37
38
  return 1
38
39
  }
39
40
 
@@ -53,6 +53,8 @@ hoard_local_env YMIR_ENV_FILE
53
53
  # repo (Rule 04).
54
54
  # shellcheck source=bin/hoard-lib.sh
55
55
  . "$SCRIPT_DIR/hoard-lib.sh"
56
+ # shellcheck source=bin/app-lib.sh
57
+ . "$SCRIPT_DIR/app-lib.sh"
56
58
  ymir_home_root YMIR_HOME
57
59
  # Where the smithy's parts live: apps/smidja-factory in a clone, or the
58
60
  # @zerwiz/smidja-factory package in an npm install (bin/smidja-lib.sh).
@@ -585,7 +587,8 @@ step_smidja() {
585
587
  # Hlidskjalf installs with npm (its own lockfile), not bun, and its API serves the
586
588
  # UI from ./dist. Without this step a fresh clone has no SPA and no build.
587
589
  step_spa() {
588
- local app="$ROOT/apps/hlidskjalf"
590
+ local app; app_dir hlidskjalf app || app=""
591
+ local app_ok="$app"
589
592
  [ -d "$app" ] || { add hlidskjalf SKIP "no apps/hlidskjalf"; return 0; }
590
593
  if [ "$CHECK" = 0 ]; then
591
594
  if ! have npm; then
@@ -669,6 +672,13 @@ step_marks() {
669
672
  fi
670
673
  "$SCRIPT_DIR/design-icon.sh" mint --all >/dev/null 2>&1 || true
671
674
  n=$("$SCRIPT_DIR/design-icon.sh" install 2>/dev/null | grep -c '"ymir-') || n=0
675
+ # The launcher entries are the freedesktop half of desktop integration, and they
676
+ # belong to ANY Linux desktop — not only to Omarchy's window rules. A GNOME
677
+ # operator gets entries and icons and no window rules; before this, both halves
678
+ # sat behind the Omarchy gate and a packaged install placed nothing at all.
679
+ if [ -x "$SCRIPT_DIR/desktop-place.sh" ]; then
680
+ "$SCRIPT_DIR/desktop-place.sh" entries >/dev/null 2>&1 || true
681
+ fi
672
682
  [ -r "$HOME/.pi/agent/AGENTS.md" ] || [ -d "$HOME/.pi/agent" ] && {
673
683
  ln -sfn "$ROOT/AGENTS.md" "$HOME/.pi/agent/AGENTS.md" 2>/dev/null || true
674
684
  }
@@ -708,8 +718,8 @@ step_desktop() {
708
718
  fi
709
719
  local shell partial=""
710
720
  for shell in hlidskjalf odrerir sessrumnir; do
711
- local dir="$ROOT/apps/$shell" d
712
- [ -d "$dir" ] || dir="$(cd "$ROOT" && npm root 2>/dev/null)/@zerwiz/$shell"
721
+ local dir d
722
+ app_dir "$shell" dir || continue
713
723
  d="$(electron_runtime_state "$dir" 2>/dev/null || true)"
714
724
  [ "$d" = partial ] && partial="$partial $shell"
715
725
  done
@@ -819,6 +829,7 @@ step_panes() {
819
829
 
820
830
  # Ask before touching the machine; --check only previews and never asks.
821
831
  [ "$CHECK" = 0 ] && confirm_install
832
+ [ "$CHECK" = 0 ] && style_patience "the halls are being stood up for the first time"
822
833
 
823
834
  step_panes; step_prereqs; step_home; step_tree; step_apps; step_engines; step_models; step_hermes; step_sessrumnir; step_backend; step_host; step_sandbox; step_memory; step_smidja; step_spa; step_omarchy; step_loaders; step_gates; step_marks
824
835
  # Migrations MOVE private data — that is a write, and `--check` promises none.
@@ -16,8 +16,19 @@
16
16
  set -euo pipefail
17
17
 
18
18
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
19
- MODULE="$ROOT/apps/hlidskjalf/server/accounts.ts"
20
- STORE="$ROOT/apps/hlidskjalf/server/accounts.ts" # absolute: one definition of an account, wherever this is run from
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
+
30
+ MODULE="$APP_HLIDSKJALF/server/accounts.ts"
31
+ STORE="$APP_HLIDSKJALF/server/accounts.ts" # absolute: one definition of an account, wherever this is run from
21
32
 
22
33
  die() { printf 'ymir-invite: %s\n' "$1" >&2; exit 1; }
23
34
 
@@ -25,7 +36,7 @@ command -v bun >/dev/null 2>&1 || die "bun is required (bin/ymir-install.sh inst
25
36
 
26
37
  # A tiny bun program per action — the module is the single source of truth.
27
38
  accounts() {
28
- ( cd "$ROOT/apps/hlidskjalf" && bun -e "$1" )
39
+ ( cd "$APP_HLIDSKJALF" && bun -e "$1" )
29
40
  }
30
41
 
31
42
  [ -f "$MODULE" ] || die "the account store module is missing: $MODULE"
package/bin/ymir-style.sh CHANGED
@@ -95,6 +95,19 @@ style_line() { # <state> <name> <detail> — the one line per thing
95
95
 
96
96
  style_heading() { printf '\n%s%s%s\n' "$C_BRONZE" "$1" "$C_OFF" >&2; }
97
97
 
98
+ # For a long hour: say what is happening and why it takes a while. A user who
99
+ # knows the halls are being set right waits; a user watching a silent cursor
100
+ # wonders whether it has broken.
101
+ style_patience() { # [what is being set right]
102
+ local what="${1:-the halls are being set right}"
103
+ printf '\n' >&2
104
+ style_line DO "much moves" "$what"
105
+ style_hint " this hour is long, and nothing of yours is lost in it —"
106
+ style_hint " roots come home, shapes are re-cut, names are set true again."
107
+ style_hint " Your patience is noted, and it is earned."
108
+ printf '\n' >&2
109
+ }
110
+
98
111
  # The ending: what stands, then exactly what to type next. Every CLI deserves
99
112
  # to leave the operator with the next step and nothing else to guess.
100
113
  style_next() { # one command per line, as "verb — what it does"
package/bin/ymir.js CHANGED
@@ -49,6 +49,25 @@ const faint = wrap('38;2;107;98;80');
49
49
  const blood = wrap('38;2;194;88;74');
50
50
  const bold = wrap('1');
51
51
 
52
+ // ── what a user went from, and to ────────────────────────────────────────────
53
+ // `npm install -g` prints "changed 266 packages" and no versions, so an operator
54
+ // cannot see what moved. We can: record the version we last ran, and say the
55
+ // transition once, on the first run after an update.
56
+ const STATE_DIR = process.env.YMIR_STATE_DIR || path.join(process.env.YMIR_HOME || path.join(require('node:os').homedir(), 'Documents', 'Ymir'), 'state');
57
+ function versionNotice() {
58
+ try {
59
+ const f = path.join(STATE_DIR, 'version');
60
+ const seen = fs.existsSync(f) ? fs.readFileSync(f, 'utf8').trim() : '';
61
+ if (seen === pkg.version) return;
62
+ fs.mkdirSync(STATE_DIR, { recursive: true });
63
+ fs.writeFileSync(f, pkg.version + '\n');
64
+ if (!seen) return; // a first install has nothing to compare
65
+ process.stderr.write(
66
+ bone(` the tree moved `) + faint(`${seen} → `) + bronze(`${pkg.version}`) + '\n' +
67
+ faint(` run \`ymir eir\` to see what stands, \`ymir raise\` to lift the hall\n\n`));
68
+ } catch { /* a version notice must never break a door */ }
69
+ }
70
+
52
71
  // ── the doors ────────────────────────────────────────────────────────────────
53
72
  // verb → { script, args } — args are prepended to whatever the operator passes,
54
73
  // so a verb can be a doorway to a sub-verb of a script that has several.
@@ -108,6 +127,7 @@ function run(script, args) {
108
127
  const argv = process.argv.slice(2);
109
128
  const first = argv[0];
110
129
 
130
+ versionNotice();
111
131
  if (!first) run(DOORS.install.script, []);
112
132
  if (first === '--version' || first === '-v' || first === '-V') {
113
133
  process.stdout.write(`${pkg.version}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerwiz/ymir",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Ymir \u2014 the single-tenant agent operating system. Omarchy-first, host-aware, self-healing.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/zerwiz/ymir",
@@ -26,6 +26,19 @@ fi
26
26
  VERSION="1.1.0"
27
27
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
28
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
29
+ # Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
30
+ # npm install — both shapes, one resolver (bin/app-lib.sh).
31
+ if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
32
+ _ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33
+ for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
34
+ [ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
35
+ done
36
+ unset _ya _yac
37
+ fi
38
+ app_dir hlidskjalf APP_HLIDSKJALF || APP_HLIDSKJALF=""
39
+ app_dir odrerir APP_ODRERIR || APP_ODRERIR=""
40
+ app_dir sessrumnir APP_SESSRUMNIR || APP_SESSRUMNIR=""
41
+
29
42
 
30
43
  # The roots that live OUTSIDE the code tree: this machine's records and the
31
44
  # runtime state belong to the home the operator chose at installation, never in
@@ -39,8 +52,8 @@ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
39
52
  fi
40
53
  hoard_state_dir YMIR_STATE_DIR
41
54
  hoard_data_dir YMIR_DATA_DIR
42
- APP="$ROOT/apps/hlidskjalf"
43
- Odrerir_app="$ROOT/apps/odrerir"
55
+ APP="$APP_HLIDSKJALF"
56
+ Odrerir_app="$APP_ODRERIR"
44
57
  NO_INSTALL=0
45
58
  VIEW="${YMIR_DESKTOP_VIEW:-hlidskjalf}"
46
59
  VIEWS=(hlidskjalf smidja odrerir)
package/scripts/start.sh CHANGED
@@ -17,6 +17,22 @@ if [ -z "${YMIR_PLATFORM_LOADED:-}" ]; then
17
17
  fi
18
18
 
19
19
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
20
+ # The cloth (bin/ymir-style.sh) — colour and words for the human watching.
21
+ if [ -z "${YMIR_STYLE_LOADED:-}" ] && [ -r "$ROOT/bin/ymir-style.sh" ]; then
22
+ . "$ROOT/bin/ymir-style.sh"; YMIR_STYLE_LOADED=1; style_init
23
+ fi
24
+ # Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
25
+ # npm install — both shapes, one resolver (bin/app-lib.sh).
26
+ if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
27
+ _ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
29
+ [ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
30
+ done
31
+ unset _ya _yac
32
+ fi
33
+ app_dir hlidskjalf APP_HLIDSKJALF || APP_HLIDSKJALF=""
34
+ app_dir odrerir HALL_DIR || HALL_DIR=""
35
+
20
36
  # Where the smithy's parts live: apps/smidja-factory in a clone, or the
21
37
  # @zerwiz/smidja-factory package in an npm install (bin/smidja-lib.sh).
22
38
  if [ -z "${YMIR_SMIDJA_LIB_LOADED:-}" ]; then
@@ -48,7 +64,7 @@ hoard_data_dir YMIR_DATA_DIR
48
64
  # The roots that live OUTSIDE the code tree: this machine's records and the
49
65
  # runtime state belong to the home the operator chose at installation, never in
50
66
  # the tree — a packaged install replaces its tree on upgrade (Rule 04).
51
- APP="$ROOT/apps/hlidskjalf"
67
+ APP="$APP_HLIDSKJALF"
52
68
  RUN="$ROOT/.run"
53
69
  PID_FILE="$RUN/hlidskjalf.pid"
54
70
  LOG="$RUN/hlidskjalf.log"
@@ -80,7 +96,8 @@ else
80
96
  fi
81
97
 
82
98
  if [[ ! -d "$APP/node_modules" ]]; then
83
- echo "Installing dependencies"
99
+ style_patience "the seat's own dependencies are being fetched, then the hall is raised"
100
+ echo "Installing dependencies…"
84
101
  (cd "$APP" && npm install --no-audit --no-fund)
85
102
  fi
86
103
 
@@ -180,7 +197,7 @@ fi
180
197
 
181
198
  # Óðrerir — the Live Hall. Its own Astro board on :4322, the carved planning
182
199
  # glass every hall door opens. Raised before the SPA so a window never waits.
183
- HALL_DIR="$ROOT/apps/odrerir"
200
+ app_dir odrerir HALL_DIR || HALL_DIR=""
184
201
  HALL_PORT="${ODRERIR_PORT:-4322}"
185
202
  HALL_PID_FILE="$RUN/odrerir.pid"
186
203
  HALL_LOG="$RUN/odrerir.log"
@@ -195,13 +212,14 @@ if [ -d "$HALL_DIR" ]; then
195
212
  echo "Óðrerir — Live Hall raised (pid $(cat "$HALL_PID_FILE")) → http://127.0.0.1:${HALL_PORT}/"
196
213
  fi
197
214
  else
198
- echo "Óðrerir — Live Hall skipped (missing apps/odrerir)." >&2
215
+ echo "Óðrerir — Live Hall skipped (the odrerir surface is not present)." >&2
199
216
  fi
200
217
 
201
218
  cd "$APP"
202
219
 
203
220
  if [[ "${1:-}" == "--foreground" ]]; then
204
- exec npm run dev
221
+ if [ -d "$APP/dist" ]; then exec npx --no-install vite preview --port "$PORT" --strictPort --host
222
+ else exec npm run dev -- --port "$PORT" --strictPort --host; fi
205
223
  fi
206
224
 
207
225
  # The SPA is already serving — nothing more for us to raise here.
@@ -211,11 +229,18 @@ if [[ "$SPA_UP" == "1" ]]; then
211
229
  fi
212
230
 
213
231
  # New session so we can signal the whole process group on stop.
214
- ymir_detach npm run dev >"$LOG" 2>&1
232
+ # The port is OUR decision, not Vite's: a bare `npm run dev` takes 5173 and the
233
+ # hall never answers on the port every other door expects. A packaged install
234
+ # ships ./dist, so it is served; a clone gets the dev server — both on $PORT.
235
+ if [ -d "$APP/dist" ]; then
236
+ ymir_detach bash -c "cd '$APP' && exec npx --no-install vite preview --port '$PORT' --strictPort --host" >"$LOG" 2>&1
237
+ else
238
+ ymir_detach bash -c "cd '$APP' && exec npm run dev -- --port '$PORT' --strictPort --host" >"$LOG" 2>&1
239
+ fi
215
240
  PID=$!
216
241
  echo "$PID" > "$PID_FILE"
217
242
 
218
- # Wait for the port to answer (max ~15s).
243
+ # Wait for the port to answer (max ~15s), then say what is true.
219
244
  for _ in $(seq 1 30); do
220
245
  if curl -s -o /dev/null "http://127.0.0.1:${PORT}/"; then
221
246
  echo "Hlidskjalf raised (pid $PID) → http://127.0.0.1:${PORT}/"
@@ -225,5 +250,6 @@ for _ in $(seq 1 30); do
225
250
  sleep 0.5
226
251
  done
227
252
 
228
- echo "Hlidskjalf started (pid $PID) but the port did not answer yet check $LOG" >&2
253
+ echo "Hlidskjalf started (pid $PID) but :${PORT} did not answer — see $LOG" >&2
254
+ tail -5 "$LOG" >&2 2>/dev/null || true
229
255
  exit 1