@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
package/bin/einherjar-spawn.sh
CHANGED
|
@@ -342,6 +342,12 @@ else
|
|
|
342
342
|
fi
|
|
343
343
|
|
|
344
344
|
# --- isolation resolution ----------------------------------------------------
|
|
345
|
+
# A spawned Eindri is a MODEL-DRIVEN worker: it must reach a model endpoint to
|
|
346
|
+
# think at all. Utgard runs with `--network none`, so a sandboxed agent cannot
|
|
347
|
+
# reach either a cloud model (OpenCode Go) or a local one (llama.cpp) — it dies
|
|
348
|
+
# silently at launch, leaving an empty pane and no status line. Utgard is for
|
|
349
|
+
# untrusted CODE, not for the agent's own brain. So `auto` keeps the worker in
|
|
350
|
+
# its worktree, and `on` is an explicit choice that warns.
|
|
345
351
|
ISOLATION_EFFECTIVE=off
|
|
346
352
|
case "$ISOLATION" in
|
|
347
353
|
on)
|
|
@@ -351,14 +357,14 @@ case "$ISOLATION" in
|
|
|
351
357
|
exit 1
|
|
352
358
|
}
|
|
353
359
|
ISOLATION_EFFECTIVE=on
|
|
360
|
+
echo "warning: Utgard runs with --network none — the worker's model endpoint must be reachable INSIDE the sandbox, or the agent dies silently. Pass --isolation off to run in the worktree." >&2
|
|
354
361
|
;;
|
|
355
362
|
off) ISOLATION_EFFECTIVE=off ;;
|
|
356
363
|
auto)
|
|
364
|
+
ISOLATION_EFFECTIVE=off
|
|
357
365
|
if [ -n "$ENGINE" ] && "$ENGINE" image inspect utgard-runner:latest >/dev/null 2>&1; then
|
|
358
|
-
|
|
359
|
-
echo "isolation: on (Utgard image present; pass --isolation off to run in the worktree)" >&2
|
|
366
|
+
echo "isolation: off (Utgard image present, but a model-driven worker needs its endpoint and Utgard has no network — pass --isolation on only for a sandbox that can reach a model)" >&2
|
|
360
367
|
else
|
|
361
|
-
ISOLATION_EFFECTIVE=off
|
|
362
368
|
echo "isolation: off (no Utgard image; pass --isolation on after building it)" >&2
|
|
363
369
|
fi
|
|
364
370
|
;;
|
package/bin/eir-doctor.sh
CHANGED
|
@@ -18,6 +18,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 $
|
|
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
|
+
}
|
package/bin/hall-snapshot.sh
CHANGED
|
@@ -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:-$
|
|
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"
|
package/bin/sessrumnir-ensure.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
package/bin/sessrumnir-sync.sh
CHANGED
|
@@ -24,7 +24,18 @@
|
|
|
24
24
|
set -euo pipefail
|
|
25
25
|
|
|
26
26
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
27
|
-
|
|
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="$
|
|
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
|
+
}
|
package/bin/ymir-install.sh
CHANGED
|
@@ -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+=(
|
|
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 "$
|
|
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="$
|
|
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="
|
|
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.
|
package/bin/ymir-invite.sh
CHANGED
|
@@ -16,8 +16,19 @@
|
|
|
16
16
|
set -euo pipefail
|
|
17
17
|
|
|
18
18
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
19
|
-
|
|
20
|
-
|
|
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 "$
|
|
39
|
+
( cd "$APP_HLIDSKJALF" && bun -e "$1" )
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
[ -f "$MODULE" ] || die "the account store module is missing: $MODULE"
|