@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.
@@ -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
- ISOLATION_EFFECTIVE=on
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,22 @@ 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
+ # 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
+
36
+
21
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
22
38
  ACTION="${1:-check}"; shift || true
23
39
 
@@ -25,7 +41,7 @@ have() { command -v "$1" >/dev/null 2>&1; }
25
41
  say_ok() { return 0; }
26
42
 
27
43
  # Each surface: `s_<name>` = healthy? (exit 0), `f_<name>` = the repair.
28
- SURFACES=(floors herdr a2abridge hermes sessrumnir well mcp lock migrations hoard)
44
+ SURFACES=(floors herdr a2abridge hermes sessrumnir shells well mcp lock migrations hoard)
29
45
 
30
46
  s_floors() { [ -x "$SCRIPT_DIR/prereq-ensure.sh" ] && "$SCRIPT_DIR/prereq-ensure.sh" status >/dev/null 2>&1; }
31
47
  f_floors() { "$SCRIPT_DIR/prereq-ensure.sh" ensure --install >/dev/null 2>&1; }
@@ -37,6 +53,34 @@ s_hermes() { [ -x "$SCRIPT_DIR/hermes-ensure.sh" ] && "$SCRIPT_DIR/hermes-ens
37
53
  f_hermes() { "$SCRIPT_DIR/hermes-ensure.sh" ensure --install >/dev/null 2>&1; }
38
54
  s_sessrumnir(){ [ -x "$SCRIPT_DIR/sessrumnir-ensure.sh" ] && "$SCRIPT_DIR/sessrumnir-ensure.sh" status >/dev/null 2>&1; }
39
55
  f_sessrumnir(){ "$SCRIPT_DIR/sessrumnir-ensure.sh" ensure --install >/dev/null 2>&1; }
56
+ # The desktop shells: absent is healthy (the web surfaces stand alone), PARTIAL is
57
+ # not — npm gated the Electron postinstall, so the window cannot open while every
58
+ # build still passes (bin/electron-lib.sh).
59
+ shell_dirs() {
60
+ local a d
61
+ for a in hlidskjalf odrerir sessrumnir; do
62
+ for d in "$ROOT/apps/$a" "$ROOT/node_modules/@zerwiz/$a"; do
63
+ [ -d "$d" ] && { printf '%s\n' "$d"; break; }
64
+ done
65
+ done
66
+ }
67
+ s_shells() {
68
+ local d state
69
+ while IFS= read -r d; do
70
+ state="$(electron_runtime_state "$d" 2>/dev/null || true)"
71
+ [ "$state" = partial ] && return 1
72
+ done < <(shell_dirs)
73
+ return 0
74
+ }
75
+ f_shells() {
76
+ printf 'eir: the shells need YOUR hand — npm gated the runtime download:\n' >&2
77
+ electron_remedy >&2
78
+ return 1
79
+ }
80
+ if [ -z "${YMIR_ELECTRON_LIB_LOADED:-}" ] && [ -r "$SCRIPT_DIR/electron-lib.sh" ]; then
81
+ . "$SCRIPT_DIR/electron-lib.sh"; YMIR_ELECTRON_LIB_LOADED=1
82
+ fi
83
+
40
84
  s_well() { [ -x "$SCRIPT_DIR/mimir.sh" ] && "$SCRIPT_DIR/mimir.sh" health >/dev/null 2>&1; }
41
85
  f_well() { "$SCRIPT_DIR/mimir.sh" start >/dev/null 2>&1; }
42
86
  # MCP: both A2A servers wired into opencode + pi.
@@ -121,11 +165,12 @@ detail() { # <name> -> one short fact
121
165
  herdr) "have herdr && herdr --version 2>/dev/null | head -1 || echo 'herdr absent'" ;;
122
166
  a2abridge) "have a2abridge && a2abridge --version 2>/dev/null | head -1 || echo 'engine absent'" ;;
123
167
  hermes) "have hermes && echo present || echo absent" ;;
124
- sessrumnir)"[ -d $ROOT/apps/sessrumnir/out ] && echo built || echo 'not built'" ;;
168
+ sessrumnir)"[ -d $APP_SESSRUMNIR/out ] && echo built || echo 'not built'" ;;
125
169
  well) "echo 'engram :4602'" ;;
126
170
  mcp) "echo 'a2abridge + engram'" ;;
127
171
  lock) "cat $STATE/.lock 2>/dev/null | tr -d '[:space:]' | sed 's/^/pid /' || echo none" ;;
128
172
  migrations)"echo 'structure'" ;;
173
+ shells) 'shell_dirs | while IFS= read -r d; do printf "%s %s; " "$(basename "$d")" "$(electron_runtime_state "$d" 2>/dev/null || true)"; done' ;;
129
174
  hoard) "printf 'hoard %s' \"$(_hoard_root)\" ; [ -d \"$YMIR_HOME/identity\" ] && printf ' +flat-duplicate' ; printf '\\n'" ;;
130
175
  esac
131
176
  }
@@ -151,6 +196,14 @@ done
151
196
  printf 'eir[1]{action,root,broken}:\n "%s","%s",%s\n' "$ACTION" "$ROOT" "$broken"
152
197
  printf 'health[%d]{surface,state,detail}:\n' "$count"
153
198
  printf '%b' "$rows"
199
+ # For the eye, on stderr: marks and colour; for the pipe, the TOON above.
200
+ if [ -t 2 ]; then
201
+ printf '\n' >&2
202
+ while IFS='|' read -r surface state detail; do
203
+ [ -n "$surface" ] || continue
204
+ style_line "$state" "$surface" "$detail"
205
+ done <<<"$(printf '%b' "$rows" | sed 's/^ //; s/"//g; s/,/|/; s/,/|/')"
206
+ fi
154
207
  if [ "$broken" != 0 ]; then
155
208
  printf 'eir: %s surface(s) need mending — run: bin/eir-doctor.sh fix\n' "$broken"
156
209
  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
+ }
@@ -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
 
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bash
2
+ # smidja-board.sh — Smíðja's board, by one name.
3
+ #
4
+ # The visualizer can live in two very different trees: a clone's
5
+ # `apps/smidja-factory/apps/visualizer`, or the `@zerwiz/smidja-factory` package
6
+ # a user got from npm. Its database lives in the HOME (`$YMIR_HOME/smidja/smidja.db`),
7
+ # not in either tree. This door resolves all three so the operator never has to.
8
+ #
9
+ # smidja-board.sh build # install deps and build the UI (./dist)
10
+ # smidja-board.sh start # raise the API + UI, detached, ready to answer
11
+ # smidja-board.sh stop
12
+ # smidja-board.sh status
13
+ # smidja-board.sh --version | --help
14
+ #
15
+ # The operator's door is `ymir smidja [build|start|stop|status]` — this is what
16
+ # that door runs.
17
+ #
18
+ # Exit: 0 ok, 1 error, 2 usage.
19
+ set -u
20
+
21
+ VERSION="1.0.0"
22
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
24
+ YMIR_HOME="${YMIR_HOME:-$HOME/Documents/Ymir}"
25
+
26
+ # shellcheck source=bin/hoard-lib.sh
27
+ . "$SCRIPT_DIR/hoard-lib.sh"
28
+ ymir_home_root YMIR_HOME
29
+ hoard_state_dir STATE
30
+ # shellcheck source=bin/smidja-lib.sh
31
+ . "$SCRIPT_DIR/smidja-lib.sh"
32
+
33
+ PORT="${SMIDJA_VIZ_API_PORT:-8437}"
34
+ PID_FILE="$STATE/smidja-viz-api.pid"
35
+ LOG_FILE="$STATE/smidja-viz-api.log"
36
+
37
+ # The database follows the HOME; SMIDJA_DB overrides. A tree-local copy is the
38
+ # last resort, for a checkout that keeps its own.
39
+ SMIDJA_DB_PATH="${SMIDJA_DB:-}"
40
+ if [ -z "$SMIDJA_DB_PATH" ]; then
41
+ for _db in "$YMIR_HOME/smidja/smidja.db" "$ROOT/apps/smidja/smidja_data/smidja.db"; do
42
+ [ -f "$_db" ] && { SMIDJA_DB_PATH="$_db"; break; }
43
+ done
44
+ fi
45
+
46
+ viz_dir() { # <result-var>
47
+ local result_var=${1-} v
48
+ [ -n "$result_var" ] || return 2
49
+ if smidja_visualizer_dir v; then printf -v "$result_var" '%s' "$v"; return 0; fi
50
+ printf -v "$result_var" '%s' ""; return 1
51
+ }
52
+
53
+ alive() { [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null; }
54
+
55
+ case "${1-}" in
56
+ -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
57
+ -h|--help|"") sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
58
+ esac
59
+ ACTION="$1"
60
+
61
+ VIZ=""
62
+ if ! viz_dir VIZ; then
63
+ printf 'error: the visualizer is not installed in this tree\n'
64
+ printf 'help: npm i -g @zerwiz/ymir (the smithy arrives as @zerwiz/smidja-factory)\n'
65
+ exit 1
66
+ fi
67
+
68
+ case "$ACTION" in
69
+ build)
70
+ if ! command -v bun >/dev/null 2>&1; then
71
+ printf 'error: bun is needed to build the UI\nhelp: bin/prereq-ensure.sh bun\n' >&2; exit 1
72
+ fi
73
+ printf 'visualizer_build[1]{step,state}:\n'
74
+ if [ ! -d "$VIZ/node_modules" ]; then
75
+ if (cd "$VIZ" && bun install >/dev/null 2>&1); then printf ' "deps","installed"\n'
76
+ else printf ' "deps","FAILED — (cd %s && bun install)"\n' "$VIZ" >&2; exit 1; fi
77
+ else printf ' "deps","present"\n'; fi
78
+ if (cd "$VIZ" && bun run build >/dev/null 2>&1) || (cd "$VIZ" && bunx vite build >/dev/null 2>&1); then
79
+ printf ' "ui","%s/dist"\n' "$VIZ"
80
+ else
81
+ printf 'error: the UI build failed\nhelp: (cd %s && bun run build)\n' "$VIZ" >&2; exit 1
82
+ fi
83
+ ;;
84
+ start)
85
+ command -v bun >/dev/null 2>&1 || { printf 'error: bun is needed to run the visualizer\nhelp: bin/prereq-ensure.sh bun\n' >&2; exit 1; }
86
+ [ -n "$SMIDJA_DB_PATH" ] && [ -f "$SMIDJA_DB_PATH" ] || {
87
+ printf 'error: no smidja.db found\nhelp: bin/smidja-bootstrap.sh (creates $YMIR_HOME/smidja/smidja.db)\n' >&2; exit 1; }
88
+ [ -d "$VIZ/dist" ] || printf 'note: the UI is unbuilt — the API will answer and show no interface\nnote: mend it with: bin/ymir-visualizer.sh build\n' >&2
89
+ if alive; then
90
+ printf 'visualizer[1]{state,pid,url}:\n "already running","%s","http://127.0.0.1:%s/"\n' "$(cat "$PID_FILE")" "$PORT"
91
+ exit 0
92
+ fi
93
+ mkdir -p "$STATE"
94
+ # ymir_detach PRINTS the pid it spawned (setsid/nohup is its business, not
95
+ # ours); reading $! here would read a variable the helper already consumed.
96
+ pid=""
97
+ if command -v ymir_detach >/dev/null 2>&1; then
98
+ pid="$(ymir_detach env CMD_DB="$SMIDJA_DB_PATH" PORT="$PORT" bun run "$VIZ/server/index.ts" 2>>"$LOG_FILE")"
99
+ fi
100
+ if [ -z "$pid" ]; then
101
+ ( cd "$VIZ" && exec env CMD_DB="$SMIDJA_DB_PATH" PORT="$PORT" bun run server/index.ts ) >>"$LOG_FILE" 2>&1 &
102
+ pid="$!"
103
+ fi
104
+ [ -n "$pid" ] && printf '%s\n' "$pid" >"$PID_FILE"
105
+ for _i in $(seq 1 20); do
106
+ curl -s -o /dev/null --max-time 1 "http://127.0.0.1:$PORT/api/health" 2>/dev/null && break
107
+ sleep 0.5
108
+ done
109
+ if curl -s --max-time 2 "http://127.0.0.1:$PORT/api/health" >/dev/null 2>&1; then
110
+ printf 'visualizer[1]{state,db,url}:\n "up","%s","http://127.0.0.1:%s/"\n' "$SMIDJA_DB_PATH" "$PORT"
111
+ else
112
+ printf 'error: the visualizer did not answer on :%s\nhelp: see %s\n' "$PORT" "$LOG_FILE" >&2; exit 1
113
+ fi
114
+ ;;
115
+ stop)
116
+ if alive; then
117
+ kill "$(cat "$PID_FILE")" 2>/dev/null; rm -f "$PID_FILE"
118
+ printf 'visualizer[1]{state}:\n "stopped"\n'
119
+ else
120
+ rm -f "$PID_FILE"
121
+ printf 'visualizer[1]{state}:\n "not running"\n'
122
+ fi
123
+ ;;
124
+ status)
125
+ local_db="${SMIDJA_DB_PATH:-none}"
126
+ if alive && curl -s --max-time 2 "http://127.0.0.1:$PORT/api/health" >/dev/null 2>&1; then
127
+ printf 'visualizer[3]{state,pid,db,url}:\n "up","%s","%s","http://127.0.0.1:%s/"\n' "$(cat "$PID_FILE")" "$local_db" "$PORT"
128
+ elif alive; then
129
+ printf 'visualizer[2]{state,pid,note}:\n "starting or wedged","%s","answers nothing on :%s — see %s"\n' "$(cat "$PID_FILE")" "$PORT" "$LOG_FILE"
130
+ exit 1
131
+ else
132
+ printf 'visualizer[3]{state,tree,db,ui}:\n "down","%s","%s","%s"\n' "$VIZ" "$local_db" "$([ -d "$VIZ/dist" ] && echo built || echo unbuilt)"
133
+ fi
134
+ ;;
135
+ *) printf 'error: unknown action %s\nhelp: bin/smidja-board.sh [build|start|stop|status]\n' "$ACTION" >&2; exit 2 ;;
136
+ esac
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # smidja-lib.sh — where the smithy's parts actually live.
3
+ #
4
+ # Two installs, one name. In a clone the smithy sits at `apps/smidja-factory`
5
+ # (the registry's `repo: apps/<path>` block clones it there), and
6
+ # `.agents/skills/smidja-factory` is a symlink to it. In a packaged install the
7
+ # tree has no `apps/` at all: the smithy arrives as a dependency, at
8
+ # `node_modules/@zerwiz/smidja-factory`, and that symlink dangles.
9
+ #
10
+ # Every script that needs the visualizer resolves it HERE, so the two shapes can
11
+ # never disagree (Rule 07). Source-safe; defines functions only.
12
+ #
13
+ # smidja_factory_dir <result-var> the smithy: skill, skills/, templates/, apps/
14
+ # smidja_visualizer_dir <result-var> the board: its server, its UI source, its dist
15
+ set -u
16
+
17
+ # A surface has a name the operator knows and a package has a name npm serves;
18
+ # they are not always the same, and the smithy is the pair that proves it.
19
+ SMIDJA_SURFACE="smidja"
20
+ SMIDJA_PACKAGE="smidja-factory"
21
+
22
+ smidja_factory_dir() { # <result-var> — the smithy's directory, or empty
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; }
31
+ fi
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' ""
38
+ return 1
39
+ }
40
+
41
+ smidja_visualizer_dir() { # <result-var> — the visualizer (server · UI source · dist)
42
+ local result_var=${1-} factory
43
+ [ -n "$result_var" ] || return 2
44
+ if smidja_factory_dir factory; then
45
+ printf -v "$result_var" '%s' "$factory/apps/visualizer"
46
+ return 0
47
+ fi
48
+ printf -v "$result_var" '%s' ""
49
+ return 1
50
+ }
@@ -7,6 +7,7 @@
7
7
  # Usage:
8
8
  # bin/ymir-install.sh [--check] [--skip-engines] [--skip-services] [--no-desktop] [--yes]
9
9
  # bin/ymir-install.sh --plan [--json] # the plan, computed — changes nothing
10
+ # bin/ymir-install.sh --yes | --non-interactive | --accept-all-defaults
10
11
  # bin/ymir-install.sh --status
11
12
  # bin/ymir-install.sh --version
12
13
  #
@@ -52,7 +53,21 @@ hoard_local_env YMIR_ENV_FILE
52
53
  # repo (Rule 04).
53
54
  # shellcheck source=bin/hoard-lib.sh
54
55
  . "$SCRIPT_DIR/hoard-lib.sh"
56
+ # shellcheck source=bin/app-lib.sh
57
+ . "$SCRIPT_DIR/app-lib.sh"
55
58
  ymir_home_root YMIR_HOME
59
+ # Where the smithy's parts live: apps/smidja-factory in a clone, or the
60
+ # @zerwiz/smidja-factory package in an npm install (bin/smidja-lib.sh).
61
+ if [ -z "${YMIR_SMIDJA_LIB_LOADED:-}" ]; then
62
+ _ys="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
63
+ for _yc in "$_ys/smidja-lib.sh" "$(dirname "$_ys")/bin/smidja-lib.sh"; do
64
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_SMIDJA_LIB_LOADED=1; break; }
65
+ done
66
+ unset _ys _yc
67
+ fi
68
+ smidja_visualizer_dir SMIDJA_VIZ
69
+ smidja_factory_dir SMIDJA_FACTORY
70
+
56
71
  WORKSPACE="${YMIR_WORKSPACE:-$YMIR_HOME/workspaces}"
57
72
  hoard_root HOARD
58
73
  DOMAINS="company marketing development life me"
@@ -63,11 +78,12 @@ while [ $# -gt 0 ]; do
63
78
  case "$1" in
64
79
  --check) CHECK=1; shift ;;
65
80
  --plan|--dry-run) PLAN_ONLY=1; shift ;;
66
- --json) PLAN_ARGS+=(--json); shift ;;
81
+ --json|--blocked) PLAN_ARGS+=("$1"); shift ;;
82
+ --phase) PLAN_ARGS+=("$1" "${2-}"); shift 2 ;; # --phase carries its number
67
83
  --skip-engines) SKIP_ENGINES=1; shift ;;
68
84
  --skip-services) SKIP_SERVICES=1; shift ;;
69
85
  --no-desktop) NO_DESKTOP=1; shift ;;
70
- --yes|-y) ASSUME_YES=1; shift ;;
86
+ --yes|-y|--non-interactive|--accept-all-defaults) ASSUME_YES=1; shift ;;
71
87
  --status) exec "$SCRIPT_DIR/ymir-install.sh" --check ;;
72
88
  *) printf 'error: unknown flag %s\nhelp: bin/ymir-install.sh [--check|--plan|--skip-engines|--skip-services|--no-desktop|--yes]\n' "$1" >&2; exit 2 ;;
73
89
  esac
@@ -78,6 +94,11 @@ if [ "$PLAN_ONLY" = 1 ]; then
78
94
  exec "$SCRIPT_DIR/ymir-plan.sh" ${PLAN_ARGS[@]+"${PLAN_ARGS[@]}"}
79
95
  fi
80
96
 
97
+ # The cloth (bin/ymir-style.sh): colour, marks and spacing for the human, while
98
+ # every row of data stays TOON on stdout.
99
+ . "$SCRIPT_DIR/ymir-style.sh"
100
+ style_init
101
+
81
102
  declare -a IDS STATUS DETAIL
82
103
  # The code minted for this install, reported at the end so it is not lost in the
83
104
  # step table. Empty on --check, and when no code could be minted.
@@ -97,8 +118,8 @@ confirm_install() {
97
118
  printf 'error: refusing a non-interactive install without --yes\nhelp: re-run with --yes to accept non-interactively, or --plan / --check to preview\n' >&2
98
119
  exit 3
99
120
  fi
121
+ style_title "Ymir first setup" "the plan below is probed on this machine, not recited"
100
122
  cat <<'PLAN'
101
- Ymir first setup — the plan below is probed on THIS machine, not recited.
102
123
  Each row is a phase, a step, its state, and why.
103
124
 
104
125
  DO a change will be made
@@ -110,7 +131,7 @@ Each row is a phase, a step, its state, and why.
110
131
  Nothing is deleted. Every step is idempotent.
111
132
  PLAN
112
133
  printf '\n'
113
- bash "$SCRIPT_DIR/ymir-plan.sh" 2>&1 || true
134
+ bash "$SCRIPT_DIR/ymir-plan.sh" --colour >/dev/null 2>&1 || true
114
135
  printf '\nProceed with the install? [y/N] '
115
136
  read -r reply || reply=""
116
137
  case "$reply" in
@@ -527,7 +548,7 @@ step_smidja() {
527
548
  if [ "$CHECK" = 1 ]; then
528
549
  local dbok vizok
529
550
  [ -f "$YMIR_HOME/smidja/smidja.db" ] && dbok=present || dbok=missing
530
- [ -d "$ROOT/.agents/skills/smidja-factory/apps/visualizer/dist" ] && vizok=built || vizok=unbuilt
551
+ [ -n "${SMIDJA_VIZ:-}" ] && [ -d "$SMIDJA_VIZ/dist" ] && vizok=built || vizok=unbuilt
531
552
  if [ "$dbok" = missing ]; then
532
553
  add smidja WARN "smidja.db missing — a full run (without --check) creates it and seeds one bootstrap session"
533
554
  else
@@ -543,7 +564,7 @@ step_smidja() {
543
564
  else add smidja SKIP "no smidja-bootstrap.sh"; fi
544
565
  # The visualizer API serves its UI from ./dist — without a build it answers
545
566
  # the API but shows "No ./dist build found". Build it once when absent.
546
- local viz="$ROOT/.agents/skills/smidja-factory/apps/visualizer"
567
+ local viz="${SMIDJA_VIZ:-}"; [ -n "$viz" ] || return 0
547
568
  [ -d "$viz" ] || return 0
548
569
  if [ -d "$viz/dist" ]; then
549
570
  add visualizer OK "UI built (served on :8437)"
@@ -566,7 +587,8 @@ step_smidja() {
566
587
  # Hlidskjalf installs with npm (its own lockfile), not bun, and its API serves the
567
588
  # UI from ./dist. Without this step a fresh clone has no SPA and no build.
568
589
  step_spa() {
569
- local app="$ROOT/apps/hlidskjalf"
590
+ local app; app_dir hlidskjalf app || app=""
591
+ local app_ok="$app"
570
592
  [ -d "$app" ] || { add hlidskjalf SKIP "no apps/hlidskjalf"; return 0; }
571
593
  if [ "$CHECK" = 0 ]; then
572
594
  if ! have npm; then
@@ -682,6 +704,22 @@ step_desktop() {
682
704
  fi
683
705
  # Placement is the Omarchy layer's job and has already run (step_omarchy runs
684
706
  # before this step), so here we only launch.
707
+ # Verify the runtime before claiming anything: a skipped Electron postinstall
708
+ # leaves a partial runtime that fails to launch while every build still passes.
709
+ if [ -z "${YMIR_ELECTRON_LIB_LOADED:-}" ] && [ -r "$SCRIPT_DIR/electron-lib.sh" ]; then
710
+ . "$SCRIPT_DIR/electron-lib.sh"; YMIR_ELECTRON_LIB_LOADED=1
711
+ fi
712
+ local shell partial=""
713
+ for shell in hlidskjalf odrerir sessrumnir; do
714
+ local dir d
715
+ app_dir "$shell" dir || continue
716
+ d="$(electron_runtime_state "$dir" 2>/dev/null || true)"
717
+ [ "$d" = partial ] && partial="$partial $shell"
718
+ done
719
+ if [ -n "$partial" ]; then
720
+ add desktop WARN "the Electron runtime is PARTIAL for:$partial — the web surfaces stand; approve and rebuild to launch the shells"
721
+ return 0
722
+ fi
685
723
  if "$ROOT/scripts/electron.sh" start --both >/dev/null 2>&1; then
686
724
  add desktop OK "raised Hlidskjalf + Smíðja"
687
725
  else
@@ -784,6 +822,7 @@ step_panes() {
784
822
 
785
823
  # Ask before touching the machine; --check only previews and never asks.
786
824
  [ "$CHECK" = 0 ] && confirm_install
825
+ [ "$CHECK" = 0 ] && style_patience "the halls are being stood up for the first time"
787
826
 
788
827
  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
789
828
  # 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"