@zerwiz/ymir 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +74 -0
- package/CHANGELOG.md +330 -1
- package/bin/design-icon.sh +7 -4
- package/bin/einherjar-spawn.sh +9 -3
- package/bin/eir-doctor.sh +43 -1
- package/bin/electron-lib.sh +34 -0
- package/bin/smidja-board.sh +136 -0
- package/bin/smidja-lib.sh +49 -0
- package/bin/ymir-install.sh +41 -6
- package/bin/ymir-plan.sh +48 -14
- package/bin/ymir-style.sh +108 -0
- package/bin/ymir-validate.sh +36 -3
- package/bin/ymir.js +82 -34
- package/docs/design.md +2 -2
- package/package.json +1 -1
- package/scripts/start.sh +13 -1
|
@@ -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,49 @@
|
|
|
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
|
+
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)"
|
|
27
|
+
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' ""
|
|
37
|
+
return 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
smidja_visualizer_dir() { # <result-var> — the visualizer (server · UI source · dist)
|
|
41
|
+
local result_var=${1-} factory
|
|
42
|
+
[ -n "$result_var" ] || return 2
|
|
43
|
+
if smidja_factory_dir factory; then
|
|
44
|
+
printf -v "$result_var" '%s' "$factory/apps/visualizer"
|
|
45
|
+
return 0
|
|
46
|
+
fi
|
|
47
|
+
printf -v "$result_var" '%s' ""
|
|
48
|
+
return 1
|
|
49
|
+
}
|
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
|
#
|
|
@@ -53,6 +54,18 @@ hoard_local_env YMIR_ENV_FILE
|
|
|
53
54
|
# shellcheck source=bin/hoard-lib.sh
|
|
54
55
|
. "$SCRIPT_DIR/hoard-lib.sh"
|
|
55
56
|
ymir_home_root YMIR_HOME
|
|
57
|
+
# Where the smithy's parts live: apps/smidja-factory in a clone, or the
|
|
58
|
+
# @zerwiz/smidja-factory package in an npm install (bin/smidja-lib.sh).
|
|
59
|
+
if [ -z "${YMIR_SMIDJA_LIB_LOADED:-}" ]; then
|
|
60
|
+
_ys="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
61
|
+
for _yc in "$_ys/smidja-lib.sh" "$(dirname "$_ys")/bin/smidja-lib.sh"; do
|
|
62
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_SMIDJA_LIB_LOADED=1; break; }
|
|
63
|
+
done
|
|
64
|
+
unset _ys _yc
|
|
65
|
+
fi
|
|
66
|
+
smidja_visualizer_dir SMIDJA_VIZ
|
|
67
|
+
smidja_factory_dir SMIDJA_FACTORY
|
|
68
|
+
|
|
56
69
|
WORKSPACE="${YMIR_WORKSPACE:-$YMIR_HOME/workspaces}"
|
|
57
70
|
hoard_root HOARD
|
|
58
71
|
DOMAINS="company marketing development life me"
|
|
@@ -63,11 +76,12 @@ while [ $# -gt 0 ]; do
|
|
|
63
76
|
case "$1" in
|
|
64
77
|
--check) CHECK=1; shift ;;
|
|
65
78
|
--plan|--dry-run) PLAN_ONLY=1; shift ;;
|
|
66
|
-
--json) PLAN_ARGS+=(
|
|
79
|
+
--json|--blocked) PLAN_ARGS+=("$1"); shift ;;
|
|
80
|
+
--phase) PLAN_ARGS+=("$1" "${2-}"); shift 2 ;; # --phase carries its number
|
|
67
81
|
--skip-engines) SKIP_ENGINES=1; shift ;;
|
|
68
82
|
--skip-services) SKIP_SERVICES=1; shift ;;
|
|
69
83
|
--no-desktop) NO_DESKTOP=1; shift ;;
|
|
70
|
-
--yes|-y) ASSUME_YES=1; shift ;;
|
|
84
|
+
--yes|-y|--non-interactive|--accept-all-defaults) ASSUME_YES=1; shift ;;
|
|
71
85
|
--status) exec "$SCRIPT_DIR/ymir-install.sh" --check ;;
|
|
72
86
|
*) 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
87
|
esac
|
|
@@ -78,6 +92,11 @@ if [ "$PLAN_ONLY" = 1 ]; then
|
|
|
78
92
|
exec "$SCRIPT_DIR/ymir-plan.sh" ${PLAN_ARGS[@]+"${PLAN_ARGS[@]}"}
|
|
79
93
|
fi
|
|
80
94
|
|
|
95
|
+
# The cloth (bin/ymir-style.sh): colour, marks and spacing for the human, while
|
|
96
|
+
# every row of data stays TOON on stdout.
|
|
97
|
+
. "$SCRIPT_DIR/ymir-style.sh"
|
|
98
|
+
style_init
|
|
99
|
+
|
|
81
100
|
declare -a IDS STATUS DETAIL
|
|
82
101
|
# The code minted for this install, reported at the end so it is not lost in the
|
|
83
102
|
# step table. Empty on --check, and when no code could be minted.
|
|
@@ -97,8 +116,8 @@ confirm_install() {
|
|
|
97
116
|
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
117
|
exit 3
|
|
99
118
|
fi
|
|
119
|
+
style_title "Ymir first setup" "the plan below is probed on this machine, not recited"
|
|
100
120
|
cat <<'PLAN'
|
|
101
|
-
Ymir first setup — the plan below is probed on THIS machine, not recited.
|
|
102
121
|
Each row is a phase, a step, its state, and why.
|
|
103
122
|
|
|
104
123
|
DO a change will be made
|
|
@@ -110,7 +129,7 @@ Each row is a phase, a step, its state, and why.
|
|
|
110
129
|
Nothing is deleted. Every step is idempotent.
|
|
111
130
|
PLAN
|
|
112
131
|
printf '\n'
|
|
113
|
-
bash "$SCRIPT_DIR/ymir-plan.sh" 2>&1 || true
|
|
132
|
+
bash "$SCRIPT_DIR/ymir-plan.sh" --colour >/dev/null 2>&1 || true
|
|
114
133
|
printf '\nProceed with the install? [y/N] '
|
|
115
134
|
read -r reply || reply=""
|
|
116
135
|
case "$reply" in
|
|
@@ -527,7 +546,7 @@ step_smidja() {
|
|
|
527
546
|
if [ "$CHECK" = 1 ]; then
|
|
528
547
|
local dbok vizok
|
|
529
548
|
[ -f "$YMIR_HOME/smidja/smidja.db" ] && dbok=present || dbok=missing
|
|
530
|
-
[ -d "$
|
|
549
|
+
[ -n "${SMIDJA_VIZ:-}" ] && [ -d "$SMIDJA_VIZ/dist" ] && vizok=built || vizok=unbuilt
|
|
531
550
|
if [ "$dbok" = missing ]; then
|
|
532
551
|
add smidja WARN "smidja.db missing — a full run (without --check) creates it and seeds one bootstrap session"
|
|
533
552
|
else
|
|
@@ -543,7 +562,7 @@ step_smidja() {
|
|
|
543
562
|
else add smidja SKIP "no smidja-bootstrap.sh"; fi
|
|
544
563
|
# The visualizer API serves its UI from ./dist — without a build it answers
|
|
545
564
|
# the API but shows "No ./dist build found". Build it once when absent.
|
|
546
|
-
local viz="$
|
|
565
|
+
local viz="${SMIDJA_VIZ:-}"; [ -n "$viz" ] || return 0
|
|
547
566
|
[ -d "$viz" ] || return 0
|
|
548
567
|
if [ -d "$viz/dist" ]; then
|
|
549
568
|
add visualizer OK "UI built (served on :8437)"
|
|
@@ -682,6 +701,22 @@ step_desktop() {
|
|
|
682
701
|
fi
|
|
683
702
|
# Placement is the Omarchy layer's job and has already run (step_omarchy runs
|
|
684
703
|
# before this step), so here we only launch.
|
|
704
|
+
# Verify the runtime before claiming anything: a skipped Electron postinstall
|
|
705
|
+
# leaves a partial runtime that fails to launch while every build still passes.
|
|
706
|
+
if [ -z "${YMIR_ELECTRON_LIB_LOADED:-}" ] && [ -r "$SCRIPT_DIR/electron-lib.sh" ]; then
|
|
707
|
+
. "$SCRIPT_DIR/electron-lib.sh"; YMIR_ELECTRON_LIB_LOADED=1
|
|
708
|
+
fi
|
|
709
|
+
local shell partial=""
|
|
710
|
+
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"
|
|
713
|
+
d="$(electron_runtime_state "$dir" 2>/dev/null || true)"
|
|
714
|
+
[ "$d" = partial ] && partial="$partial $shell"
|
|
715
|
+
done
|
|
716
|
+
if [ -n "$partial" ]; then
|
|
717
|
+
add desktop WARN "the Electron runtime is PARTIAL for:$partial — the web surfaces stand; approve and rebuild to launch the shells"
|
|
718
|
+
return 0
|
|
719
|
+
fi
|
|
685
720
|
if "$ROOT/scripts/electron.sh" start --both >/dev/null 2>&1; then
|
|
686
721
|
add desktop OK "raised Hlidskjalf + Smíðja"
|
|
687
722
|
else
|
package/bin/ymir-plan.sh
CHANGED
|
@@ -46,7 +46,7 @@ YMIR_HOME_WAS_SET=0; [ -n "${YMIR_HOME:-}" ] && YMIR_HOME_WAS_SET=1
|
|
|
46
46
|
ymir_home_root YMIR_HOME
|
|
47
47
|
hoard_root HOARD
|
|
48
48
|
|
|
49
|
-
JSON=0; ONLY_PHASE=""; ONLY_BLOCKED=0
|
|
49
|
+
JSON=0; COLOUR=0; ONLY_PHASE=""; ONLY_BLOCKED=0
|
|
50
50
|
case "${1-}" in
|
|
51
51
|
-v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
|
|
52
52
|
-h|--help) sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
@@ -54,9 +54,10 @@ esac
|
|
|
54
54
|
while [ $# -gt 0 ]; do
|
|
55
55
|
case "$1" in
|
|
56
56
|
--json) JSON=1; shift ;;
|
|
57
|
+
--colour|--color) COLOUR=1; shift ;;
|
|
57
58
|
--phase) ONLY_PHASE="${2-}"; shift 2 ;;
|
|
58
59
|
--blocked) ONLY_BLOCKED=1; shift ;;
|
|
59
|
-
*) printf 'error: unknown flag %s\nhelp: bin/ymir-plan.sh [--json] [--phase N] [--blocked]\n' "$1" >&2; exit 2 ;;
|
|
60
|
+
*) printf 'error: unknown flag %s\nhelp: bin/ymir-plan.sh [--json|--colour] [--phase N] [--blocked]\n' "$1" >&2; exit 2 ;;
|
|
60
61
|
esac
|
|
61
62
|
done
|
|
62
63
|
|
|
@@ -71,9 +72,12 @@ port_up() { # <port>
|
|
|
71
72
|
}
|
|
72
73
|
# An app is ours by one of two shapes: a source checkout under apps/, or a
|
|
73
74
|
# published package under node_modules/@zerwiz/. Neither is guessed.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
# A surface has a name the operator knows and a PACKAGE has a name npm serves;
|
|
76
|
+
# they are not always the same (the smithy is `smidja` to us and
|
|
77
|
+
# `@zerwiz/smidja-factory` on the registry). <surface> [<package>]
|
|
78
|
+
app_dir() {
|
|
79
|
+
local n="$1" pkg="${2:-$1}" c
|
|
80
|
+
for c in "$ROOT/apps/$n" "$ROOT/node_modules/@zerwiz/$pkg"; do
|
|
77
81
|
[ -d "$c" ] && { printf '%s' "$c"; return 0; }
|
|
78
82
|
done
|
|
79
83
|
return 1
|
|
@@ -234,35 +238,48 @@ engines_phase() {
|
|
|
234
238
|
# ── phase 5 · apps — the surfaces the operator actually sees ────────────────
|
|
235
239
|
# Four surfaces, one shape each: web build required, Electron shell gated.
|
|
236
240
|
# The apps are their own packages (@zerwiz/<app>) — the distro depends on them.
|
|
237
|
-
app_row() { # <
|
|
238
|
-
local name="$1" about="$2" dir
|
|
239
|
-
if dir="$(app_dir "$name")"; then
|
|
241
|
+
app_row() { # <surface> <about> [<package>]
|
|
242
|
+
local name="$1" about="$2" pkg="${3:-$1}" dir
|
|
243
|
+
if dir="$(app_dir "$name" "$pkg")"; then
|
|
240
244
|
if [ -d "$dir/dist" ] || [ -d "$dir/out" ]; then
|
|
241
245
|
emit 5 apps "$name" SKIP "$about — installed, and its build shipped with it"
|
|
242
246
|
else
|
|
243
247
|
emit 5 apps "$name" DO "$about — installed from source; the web build is still to make"
|
|
244
248
|
fi
|
|
245
|
-
elif grep -q "\"@zerwiz/$
|
|
249
|
+
elif grep -q "\"@zerwiz/$pkg\"" "$ROOT/package.json" 2>/dev/null; then
|
|
246
250
|
# Declared as a dependency of the distro but not present: the package exists,
|
|
247
251
|
# the tree simply has not fetched it. That is a DO, not a dead end.
|
|
248
252
|
emit 5 apps "$name" DO "$about — declared as a dependency but not fetched (npm i -g @zerwiz/ymir fetches it)"
|
|
249
253
|
else
|
|
250
|
-
emit 5 apps "$name" BLOCKED "$about — no apps/$name, no @zerwiz/$
|
|
254
|
+
emit 5 apps "$name" BLOCKED "$about — no apps/$name, no @zerwiz/$pkg package, and the distro does not depend on it"
|
|
251
255
|
fi
|
|
252
256
|
}
|
|
253
257
|
apps_phase() {
|
|
254
258
|
app_row hlidskjalf "the control plane (the high seat), served on :3888"
|
|
255
259
|
app_row odrerir "the live hall (chat + council)"
|
|
256
260
|
app_row sessrumnir "the seat-hall desktop"
|
|
257
|
-
app_row smidja "the smithy and its visualizer (:8437)"
|
|
261
|
+
app_row smidja "the smithy and its visualizer (:8437)" smidja-factory
|
|
258
262
|
|
|
259
|
-
|
|
263
|
+
# A shell is ready only when its runtime VERIFIES: npm gates install scripts,
|
|
264
|
+
# and a skipped Electron postinstall leaves a partial runtime that still builds
|
|
265
|
+
# the web app and still reports success (bin/electron-lib.sh).
|
|
266
|
+
if [ -z "${YMIR_ELECTRON_LIB_LOADED:-}" ]; then
|
|
267
|
+
for c in "$SCRIPT_DIR/electron-lib.sh"; do [ -r "$c" ] && { . "$c"; YMIR_ELECTRON_LIB_LOADED=1; }; done
|
|
268
|
+
fi
|
|
269
|
+
local shells=0 partial="" missing=0 d
|
|
260
270
|
for d in hlidskjalf odrerir sessrumnir; do
|
|
261
271
|
dir="$(app_dir "$d" 2>/dev/null || true)"
|
|
262
|
-
if [ -
|
|
272
|
+
if [ -z "$dir" ]; then missing=$((missing+1)); continue; fi
|
|
273
|
+
case "$(electron_runtime_state "$dir" 2>/dev/null || true)" in
|
|
274
|
+
ok) shells=$((shells+1)) ;;
|
|
275
|
+
partial) partial="$partial $d" ;;
|
|
276
|
+
*) missing=$((missing+1)) ;;
|
|
277
|
+
esac
|
|
263
278
|
done
|
|
264
279
|
if [ "$shells" -ge 3 ]; then
|
|
265
|
-
emit 5 apps electron SKIP "the three desktop shells
|
|
280
|
+
emit 5 apps electron SKIP "the three desktop shells' runtimes verify"
|
|
281
|
+
elif [ -n "$partial" ]; then
|
|
282
|
+
emit 5 apps electron DO "the runtime is PARTIAL for:$partial — npm skipped the Electron postinstall; the web surfaces stand, the shells will not launch"
|
|
266
283
|
elif [ "${YMIR_NO_DESKTOP:-0}" = 1 ]; then
|
|
267
284
|
emit 5 apps electron SKIP "declined (--no-desktop) — the web surfaces stand without the shells"
|
|
268
285
|
else
|
|
@@ -347,6 +364,23 @@ if [ "$shown" -eq 0 ]; then
|
|
|
347
364
|
exit 0
|
|
348
365
|
fi
|
|
349
366
|
|
|
367
|
+
# A human sees the same rows, rendered in the cloth, on stderr — the TOON below
|
|
368
|
+
# stays the data on stdout, so a pipeline never parses a decoration.
|
|
369
|
+
if [ "$COLOUR" = 1 ]; then
|
|
370
|
+
. "$SCRIPT_DIR/ymir-style.sh"
|
|
371
|
+
style_init
|
|
372
|
+
last_phase=""
|
|
373
|
+
for i in "${!P_STEP[@]}"; do
|
|
374
|
+
[ -n "$ONLY_PHASE" ] && [ "${P_N[$i]}" != "$ONLY_PHASE" ] && continue
|
|
375
|
+
[ "$ONLY_BLOCKED" = 1 ] && [ "${P_STATE[$i]}" != BLOCKED ] && continue
|
|
376
|
+
if [ "${P_N[$i]}" != "$last_phase" ]; then
|
|
377
|
+
style_heading "${P_NAME[$i]}"
|
|
378
|
+
last_phase="${P_N[$i]}"
|
|
379
|
+
fi
|
|
380
|
+
style_line "${P_STATE[$i]}" "${P_STEP[$i]}" "${P_WHY[$i]}"
|
|
381
|
+
done
|
|
382
|
+
fi
|
|
383
|
+
|
|
350
384
|
printf 'plan[%d]{phase,name,step,state,why}:\n' "$shown"
|
|
351
385
|
for i in "${!P_STEP[@]}"; do
|
|
352
386
|
[ -n "$ONLY_PHASE" ] && [ "${P_N[$i]}" != "$ONLY_PHASE" ] && continue
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ymir-style.sh — the cloth of the terminal halls.
|
|
3
|
+
#
|
|
4
|
+
# Ymir had no design for its own command line: correct output, no experience.
|
|
5
|
+
# This is the cloth — cut from the same stone as the halls
|
|
6
|
+
# (midgard/design-system/tokens.css): bone for words, bronze for what acts,
|
|
7
|
+
# steel for what stands, blood for what is wrong, and the forge's marks for the
|
|
8
|
+
# states a plan can be in.
|
|
9
|
+
#
|
|
10
|
+
# The rules it obeys (cli-guidelines, clig.dev, Monospace TUI):
|
|
11
|
+
# · colour, marks and motion only when a human is present — never in a pipe,
|
|
12
|
+
# never when NO_COLOR is set, never on a dumb terminal, never for --no-color
|
|
13
|
+
# · data goes to stdout, the human's rendering goes to stderr, so a pipeline
|
|
14
|
+
# never has to parse a decoration
|
|
15
|
+
# · a reaction for every action, and a next step for every ending
|
|
16
|
+
# · density first: no banner bigger than four lines, no rule longer than the text
|
|
17
|
+
#
|
|
18
|
+
# Source-safe; functions only. Nothing here writes a byte of data.
|
|
19
|
+
set -u
|
|
20
|
+
|
|
21
|
+
# --- the palette, from the tokens (truecolor → 256 → none) ------------------
|
|
22
|
+
style_init() {
|
|
23
|
+
STYLE_ON=0
|
|
24
|
+
# A human must be present: stderr is a terminal and nobody said no.
|
|
25
|
+
if [ -t 2 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-dumb}" != dumb ] && [ "${YMIR_NO_COLOR:-0}" != 1 ]; then
|
|
26
|
+
STYLE_ON=1
|
|
27
|
+
fi
|
|
28
|
+
if [ "$STYLE_ON" = 1 ] && { [ "${COLORTERM:-}" = truecolor ] || [ "${COLORTERM:-}" = 24bit ]; }; then
|
|
29
|
+
C_BONE=$'\033[38;2;207;195;169m'; C_BRONZE=$'\033[38;2;201;151;79m'
|
|
30
|
+
C_STEEL=$'\033[38;2;150;160;168m'; C_BLOOD=$'\033[38;2;194;88;74m'
|
|
31
|
+
C_FAINT=$'\033[38;2;107;98;80m'; C_BOLD=$'\033[1m'
|
|
32
|
+
elif [ "$STYLE_ON" = 1 ]; then
|
|
33
|
+
C_BONE=$'\033[38;5;187m'; C_BRONZE=$'\033[38;5;179m'
|
|
34
|
+
C_STEEL=$'\033[38;5;109m'; C_BLOOD=$'\033[38;5;167m'
|
|
35
|
+
C_FAINT=$'\033[38;5;101m'; C_BOLD=$'\033[1m'
|
|
36
|
+
else
|
|
37
|
+
C_BONE=""; C_BRONZE=""; C_STEEL=""; C_BLOOD=""; C_FAINT=""; C_BOLD=""
|
|
38
|
+
fi
|
|
39
|
+
C_OFF=""
|
|
40
|
+
[ "$STYLE_ON" = 1 ] && C_OFF=$'\033[0m'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# The forge's marks, one per state a thing can be in.
|
|
44
|
+
style_mark() { # <state> → a glyph
|
|
45
|
+
case "${1:-}" in
|
|
46
|
+
DO|do) printf '%s' '◆' ;;
|
|
47
|
+
SKIP|skip|already) printf '%s' '·' ;;
|
|
48
|
+
INFO|info) printf '%s' '—' ;;
|
|
49
|
+
BLOCKED|blocked) printf '%s' '✕' ;;
|
|
50
|
+
CONSENT|consent) printf '%s' '?' ;;
|
|
51
|
+
OK|ok) printf '%s' '✓' ;;
|
|
52
|
+
WARN|warn) printf '%s' '·' ;;
|
|
53
|
+
FAIL|fail) printf '%s' '✕' ;;
|
|
54
|
+
*) printf '%s' '·' ;;
|
|
55
|
+
esac
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
style_colour() { # <state> → the colour for it
|
|
59
|
+
case "${1:-}" in
|
|
60
|
+
DO|CONSENT|WARN|warn|do|consent) printf '%s' "$C_BRONZE" ;;
|
|
61
|
+
OK|SKIP|INFO|ok|skip|info) printf '%s' "$C_STEEL" ;;
|
|
62
|
+
FAIL|BLOCKED|fail|blocked) printf '%s' "$C_BLOOD" ;;
|
|
63
|
+
*) printf '%s' "$C_FAINT" ;;
|
|
64
|
+
esac
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# --- the pieces -------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
# The mark: four lines at most. The WORDS show even in a pipe — only the colour
|
|
70
|
+
# is conditional, because colour is decoration and the name is information.
|
|
71
|
+
style_title() { # <name> <claim>
|
|
72
|
+
printf '%s\n' "${C_BRONZE} ᛉ ${C_BOLD}${1}${C_OFF}" >&2
|
|
73
|
+
printf '%s\n' "${C_FAINT} ${2}${C_OFF}" >&2
|
|
74
|
+
printf '\n' >&2
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
style_rule() { # a hairline, no wider than the words it separates
|
|
78
|
+
local width="${1:-52}"
|
|
79
|
+
[ "$STYLE_ON" = 1 ] || return 0
|
|
80
|
+
local line=""; local i=0
|
|
81
|
+
while [ "$i" -lt "$width" ]; do line="$line─"; i=$((i+1)); done
|
|
82
|
+
printf '%s\n' "${C_FAINT}${line}${C_OFF}" >&2
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
style_say() { printf '%s\n' "${C_BONE}$*${C_OFF}" >&2; }
|
|
86
|
+
style_hint() { printf '%s\n' "${C_FAINT}$*${C_OFF}" >&2; }
|
|
87
|
+
|
|
88
|
+
style_line() { # <state> <name> <detail> — the one line per thing
|
|
89
|
+
local state="$1" name="$2" detail="${3:-}"
|
|
90
|
+
printf ' %s%s%s %-12s %s%s\n' \
|
|
91
|
+
"$(style_colour "$state")" "$(style_mark "$state")" "$C_OFF" \
|
|
92
|
+
"$name" "$(style_colour "$state")" "$detail" >&2
|
|
93
|
+
printf '%s' "$C_OFF" >/dev/null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
style_heading() { printf '\n%s%s%s\n' "$C_BRONZE" "$1" "$C_OFF" >&2; }
|
|
97
|
+
|
|
98
|
+
# The ending: what stands, then exactly what to type next. Every CLI deserves
|
|
99
|
+
# to leave the operator with the next step and nothing else to guess.
|
|
100
|
+
style_next() { # one command per line, as "verb — what it does"
|
|
101
|
+
printf '\n%s\n' "${C_BOLD}Where to go from here${C_OFF}" >&2
|
|
102
|
+
while [ $# -gt 0 ]; do
|
|
103
|
+
local cmd="${1%% — *}" what="${1#* — }"
|
|
104
|
+
printf ' %s%-22s%s %s\n' "$C_BRONZE" "$cmd" "$C_OFF" "$(printf '%s' "$what" | sed "s/^/$C_FAINT/;s/$/$C_OFF/")" >&2
|
|
105
|
+
shift
|
|
106
|
+
done
|
|
107
|
+
printf '\n' >&2
|
|
108
|
+
}
|
package/bin/ymir-validate.sh
CHANGED
|
@@ -16,6 +16,24 @@ 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
|
+
# The cloth: colour and marks for the human reading this report; the TOON rows on
|
|
20
|
+
# stdout stay the data (bin/ymir-style.sh).
|
|
21
|
+
if [ -z "${YMIR_STYLE_LOADED:-}" ]; then
|
|
22
|
+
. "$SCRIPT_DIR/ymir-style.sh"; YMIR_STYLE_LOADED=1
|
|
23
|
+
fi
|
|
24
|
+
style_init
|
|
25
|
+
# Where the smithy's parts live: apps/smidja-factory in a clone, or the
|
|
26
|
+
# @zerwiz/smidja-factory package in an npm install (bin/smidja-lib.sh).
|
|
27
|
+
if [ -z "${YMIR_SMIDJA_LIB_LOADED:-}" ]; then
|
|
28
|
+
_ys="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
for _yc in "$_ys/smidja-lib.sh" "$(dirname "$_ys")/bin/smidja-lib.sh"; do
|
|
30
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_SMIDJA_LIB_LOADED=1; break; }
|
|
31
|
+
done
|
|
32
|
+
unset _ys _yc
|
|
33
|
+
fi
|
|
34
|
+
smidja_visualizer_dir SMIDJA_VIZ
|
|
35
|
+
smidja_factory_dir SMIDJA_FACTORY
|
|
36
|
+
|
|
19
37
|
# shellcheck source=bin/ymir-platform.sh
|
|
20
38
|
. "$SCRIPT_DIR/ymir-platform.sh"
|
|
21
39
|
# Docker or rootless Podman (Fedora), whichever is present.
|
|
@@ -118,7 +136,7 @@ fi
|
|
|
118
136
|
# ── 7b. visualizer (built ./dist AND its API actually listening) ────────────
|
|
119
137
|
# A PASS must mean the thing is UP. Probe the port, not just the build: a
|
|
120
138
|
# built-but-dead visualizer (a bad CMD_DB, a crashed API) is a FAIL, not green.
|
|
121
|
-
VIZ="$
|
|
139
|
+
VIZ="${SMIDJA_VIZ:-}"
|
|
122
140
|
VIZ_PORT="${SMIDJA_VIZ_API_PORT:-8437}"
|
|
123
141
|
if [ ! -d "$VIZ" ]; then
|
|
124
142
|
add visualizer SKIP "no visualizer tree at $VIZ"
|
|
@@ -182,12 +200,27 @@ else
|
|
|
182
200
|
for i in "${!IDS[@]}"; do
|
|
183
201
|
say " \"${IDS[$i]}\",\"${STATES[$i]}\",\"${DETAILS[$i]}\""
|
|
184
202
|
done
|
|
203
|
+
# The same rows, rendered for the eye, on stderr: a human reads marks and
|
|
204
|
+
# colour, a pipeline reads the TOON above, and neither parses the other.
|
|
205
|
+
if [ "$QUIET" != 1 ]; then
|
|
206
|
+
printf '\n' >&2
|
|
207
|
+
for i in "${!IDS[@]}"; do
|
|
208
|
+
style_line "${STATES[$i]}" "${IDS[$i]}" "${DETAILS[$i]}"
|
|
209
|
+
done
|
|
210
|
+
fi
|
|
185
211
|
fi
|
|
186
212
|
|
|
187
213
|
if [ "$fails" -gt 0 ]; then
|
|
188
|
-
[ "$QUIET" = 1 ] ||
|
|
214
|
+
[ "$QUIET" = 1 ] || {
|
|
215
|
+
style_rule 52
|
|
216
|
+
style_line FAIL "not usable" "$fails required check(s) failed, $warns warning(s)"
|
|
217
|
+
style_hint "mend it: ymir eir (diagnose every surface, then mend what is broken)"
|
|
218
|
+
}
|
|
189
219
|
printf 'help: %s required check(s) failed, %s warning(s)\n' "$fails" "$warns" >&2
|
|
190
220
|
exit 1
|
|
191
221
|
fi
|
|
192
|
-
[ "$QUIET" = 1 ] ||
|
|
222
|
+
[ "$QUIET" = 1 ] || {
|
|
223
|
+
style_rule 52
|
|
224
|
+
style_line OK "stands" "every required check passes ($warns warning(s))"
|
|
225
|
+
}
|
|
193
226
|
exit 0
|