@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/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,121 @@
|
|
|
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
|
+
# For a long hour: say what is happening and why it takes a while. A user who
|
|
99
|
+
# knows the halls are being set right waits; a user watching a silent cursor
|
|
100
|
+
# wonders whether it has broken.
|
|
101
|
+
style_patience() { # [what is being set right]
|
|
102
|
+
local what="${1:-the halls are being set right}"
|
|
103
|
+
printf '\n' >&2
|
|
104
|
+
style_line DO "much moves" "$what"
|
|
105
|
+
style_hint " this hour is long, and nothing of yours is lost in it —"
|
|
106
|
+
style_hint " roots come home, shapes are re-cut, names are set true again."
|
|
107
|
+
style_hint " Your patience is noted, and it is earned."
|
|
108
|
+
printf '\n' >&2
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# The ending: what stands, then exactly what to type next. Every CLI deserves
|
|
112
|
+
# to leave the operator with the next step and nothing else to guess.
|
|
113
|
+
style_next() { # one command per line, as "verb — what it does"
|
|
114
|
+
printf '\n%s\n' "${C_BOLD}Where to go from here${C_OFF}" >&2
|
|
115
|
+
while [ $# -gt 0 ]; do
|
|
116
|
+
local cmd="${1%% — *}" what="${1#* — }"
|
|
117
|
+
printf ' %s%-22s%s %s\n' "$C_BRONZE" "$cmd" "$C_OFF" "$(printf '%s' "$what" | sed "s/^/$C_FAINT/;s/$/$C_OFF/")" >&2
|
|
118
|
+
shift
|
|
119
|
+
done
|
|
120
|
+
printf '\n' >&2
|
|
121
|
+
}
|
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
|
package/bin/ymir.js
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* ymir — the CLI the npm package publishes.
|
|
3
|
+
* ymir — the CLI the npm package publishes, and the operator's front door.
|
|
4
4
|
*
|
|
5
5
|
* It is a thin, honest wrapper: the real work lives in the shell scripts under
|
|
6
|
-
* bin
|
|
7
|
-
* npm needs a Node entry point (`bin: ymir`) and because
|
|
8
|
-
*
|
|
6
|
+
* bin/ and scripts/ — the same scripts a git clone runs. This file exists
|
|
7
|
+
* because npm needs a Node entry point (`bin: ymir`) and because an operator
|
|
8
|
+
* should never have to know where the package landed.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* ymir
|
|
10
|
+
* The doors are named for the figure whose work they do (the naming law):
|
|
11
|
+
* Eir heals, Gróa renews, Heimdall keeps the way in, Smíðja is the smithy and
|
|
12
|
+
* its board, Hlidskjalf the high seat, Sessrúmnir the seat-hall, Mímir the well.
|
|
13
|
+
*
|
|
14
|
+
* ymir first setup (the same as `ymir install`)
|
|
15
|
+
* ymir install [...] first setup, idempotent and self-healing
|
|
16
|
+
* ymir raise / lower lift the hall, or lay it down
|
|
17
|
+
* ymir eir what stands, and mend what does not
|
|
18
|
+
* ymir groa take the latest, and mend this home forward
|
|
19
|
+
* ymir heimdall the way in: your credential, and invites
|
|
20
|
+
* ymir smidja the smithy's board (:8437) — build · start · stop · status
|
|
21
|
+
* ymir hlidskjalf the high seat's window
|
|
22
|
+
* ymir sessrumnir the seat-hall's window
|
|
23
|
+
* ymir mimir the memory well
|
|
24
|
+
* ymir sense what THIS machine is
|
|
15
25
|
* ymir --version
|
|
16
26
|
*
|
|
27
|
+
* Colour appears only where a human is watching (a TTY, no NO_COLOR); the data
|
|
28
|
+
* on stdout is always plain TOON.
|
|
29
|
+
*
|
|
17
30
|
* Exit codes follow the scripts: 0 ok, 1 error, 2 usage.
|
|
18
31
|
*/
|
|
19
32
|
'use strict';
|
|
@@ -27,32 +40,65 @@ const pkg = (() => {
|
|
|
27
40
|
try { return require(path.join(ROOT, 'package.json')); } catch { return { version: '0.0.0' }; }
|
|
28
41
|
})();
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
// ── the cloth (the same palette as bin/ymir-style.sh, cut from the tokens) ───
|
|
44
|
+
const colour = process.stderr.isTTY && !process.env.NO_COLOR && process.env.TERM !== 'dumb';
|
|
45
|
+
const wrap = (code) => (s) => (colour ? `\u001b[${code}m${s}\u001b[0m` : `${s}`);
|
|
46
|
+
const bone = wrap('38;2;207;195;169');
|
|
47
|
+
const bronze = wrap('38;2;201;151;79');
|
|
48
|
+
const faint = wrap('38;2;107;98;80');
|
|
49
|
+
const blood = wrap('38;2;194;88;74');
|
|
50
|
+
const bold = wrap('1');
|
|
51
|
+
|
|
52
|
+
// ── the doors ────────────────────────────────────────────────────────────────
|
|
53
|
+
// verb → { script, args } — args are prepended to whatever the operator passes,
|
|
54
|
+
// so a verb can be a doorway to a sub-verb of a script that has several.
|
|
55
|
+
const DOORS = {
|
|
56
|
+
install: { script: 'bin/ymir-install.sh', about: 'first setup (idempotent, self-healing)' },
|
|
57
|
+
raise: { script: 'scripts/start.sh', about: 'lift the hall — SPA, gate API, Nornir, bridges, the board' },
|
|
58
|
+
lower: { script: 'scripts/stop.sh', about: 'lay the hall down' },
|
|
59
|
+
eir: { script: 'bin/eir-doctor.sh', about: 'diagnose every surface; mend what is broken' },
|
|
60
|
+
groa: { script: 'bin/groa-update.sh', about: 'take the latest, then mend this home forward' },
|
|
61
|
+
heimdall: { script: 'bin/ymir-setup-auth.sh', about: 'the way in — your credential (status · set · github)' },
|
|
62
|
+
invite: { script: 'bin/ymir-invite.sh', about: 'let someone else in (mint · list · revoke)' },
|
|
63
|
+
smidja: { script: 'bin/smidja-board.sh', about: "the smithy's board on :8437 (build · start · stop · status)" },
|
|
64
|
+
hlidskjalf: { script: 'scripts/electron.sh', about: "the high seat's window", args: ['start', '--view', 'hlidskjalf'] },
|
|
65
|
+
sessrumnir: { script: 'scripts/electron.sh', about: "the seat-hall's window", args: ['start', '--view', 'sessrumnir'] },
|
|
66
|
+
mimir: { script: 'bin/mimir.sh', about: 'the memory well' },
|
|
67
|
+
sense: { script: 'bin/host-sense.sh', about: 'what THIS machine is' },
|
|
68
|
+
plan: { script: 'bin/ymir-plan.sh', about: 'what an install would do here — writes nothing' },
|
|
69
|
+
migrate: { script: 'bin/ymir-migrate.sh', about: "heal this home's structure forward (Gr\u00f3a's mend)" },
|
|
70
|
+
validate: { script: 'bin/ymir-validate.sh', about: 'alias of `ymir eir`' },
|
|
35
71
|
};
|
|
36
72
|
|
|
73
|
+
// Names the law has not given a home are kept for a while, so a muscle memory
|
|
74
|
+
// built yesterday still works — and says what to type instead.
|
|
75
|
+
const RENAMED = { validate: 'eir', auth: 'heimdall', desktop: 'hlidskjalf', doctor: 'eir', update: 'groa' };
|
|
76
|
+
|
|
37
77
|
function usage() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
);
|
|
78
|
+
const lines = [];
|
|
79
|
+
lines.push('');
|
|
80
|
+
lines.push(` ${bronze('\u16c9')} ${bold('Ymir')} ${faint(pkg.version)}`);
|
|
81
|
+
lines.push(` ${faint('the single-tenant agent operating system')}`);
|
|
82
|
+
lines.push('');
|
|
83
|
+
lines.push(bold(' The doors'));
|
|
84
|
+
for (const [verb, d] of Object.entries(DOORS)) {
|
|
85
|
+
lines.push(` ${bronze(verb.padEnd(11))} ${faint(d.about)}`);
|
|
86
|
+
}
|
|
87
|
+
lines.push('');
|
|
88
|
+
lines.push(bold(' Composed by hand'));
|
|
89
|
+
lines.push(` ${bronze('ymir groa migrate'.padEnd(11))} ${faint("heal this home's structure (run by groa / install)")}`);
|
|
90
|
+
lines.push(` ${bronze('ymir heimdall set'.padEnd(11))} ${faint('set the operator password (or `github`)')}`);
|
|
91
|
+
lines.push('');
|
|
92
|
+
lines.push(faint(' A bare `ymir` runs the first setup. Output is TOON; colour only on a TTY.'));
|
|
93
|
+
lines.push('');
|
|
94
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
49
95
|
}
|
|
50
96
|
|
|
51
97
|
function run(script, args) {
|
|
52
|
-
const file = path.join(ROOT,
|
|
98
|
+
const file = path.join(ROOT, script);
|
|
53
99
|
if (!fs.existsSync(file)) {
|
|
54
|
-
process.stderr.write(
|
|
55
|
-
process.stderr.write('help: reinstall — npm i -g @zerwiz/ymir, or git clone the distro\n
|
|
100
|
+
process.stderr.write(`${blood('error:')} ${script} is missing from this install\n`);
|
|
101
|
+
process.stderr.write(`${faint('help: reinstall — npm i -g @zerwiz/ymir, or git clone the distro')}\n`);
|
|
56
102
|
process.exit(1);
|
|
57
103
|
}
|
|
58
104
|
const r = spawnSync('bash', [file, ...args], { stdio: 'inherit' });
|
|
@@ -62,17 +108,19 @@ function run(script, args) {
|
|
|
62
108
|
const argv = process.argv.slice(2);
|
|
63
109
|
const first = argv[0];
|
|
64
110
|
|
|
65
|
-
if (!first) run(
|
|
111
|
+
if (!first) run(DOORS.install.script, []);
|
|
66
112
|
if (first === '--version' || first === '-v' || first === '-V') {
|
|
67
113
|
process.stdout.write(`${pkg.version}\n`);
|
|
68
114
|
process.exit(0);
|
|
69
115
|
}
|
|
70
116
|
if (first === '--help' || first === '-h' || first === 'help') { usage(); process.exit(0); }
|
|
71
117
|
|
|
72
|
-
|
|
73
|
-
if (!
|
|
74
|
-
|
|
75
|
-
process.stderr.write('
|
|
118
|
+
let door = DOORS[first];
|
|
119
|
+
if (!door) {
|
|
120
|
+
const replacement = RENAMED[first];
|
|
121
|
+
process.stderr.write(`${blood('error:')} unknown door ${bold(first)}\n`);
|
|
122
|
+
if (replacement) process.stderr.write(`${faint(`help: the door is named \`ymir ${replacement}\` now (the naming law — a figure does the work)`)}\n`);
|
|
123
|
+
process.stderr.write(`${faint('help: `ymir --help` lists every door')}\n`);
|
|
76
124
|
process.exit(2);
|
|
77
125
|
}
|
|
78
|
-
run(script, argv.slice(1));
|
|
126
|
+
run(door.script, [...(door.args || []), ...argv.slice(1)]);
|
package/docs/design.md
CHANGED
|
@@ -226,7 +226,7 @@ everywhere; never put bronze text on bronze.
|
|
|
226
226
|
- **Runecoded, not emoji.** A minimal, hand-drawn-style rune set replaces emoji
|
|
227
227
|
in all UI: 16×16 and 24×24 grid, 2px stroke, chisel bevel on primary.
|
|
228
228
|
- System subsystems each carry one rune-glyph (reuse Algiz-family geometry:
|
|
229
|
-
Bifrost gate, Ratatoskr squirrel, the Well). Map is in `midgard/design-system/icons.md`
|
|
229
|
+
Bifrost gate, Ratatoskr squirrel, the Well). Map is in `midgard/design-system/icons.md` and the library in `midgard/design-system/runes.md`.
|
|
230
230
|
- Status dot: filled rune ring (ok) / half-ring (warn) / broken ring (danger), never color-only.
|
|
231
231
|
|
|
232
232
|
### 4.5 Surface & texture
|
|
@@ -345,7 +345,7 @@ All components read tokens from `tokens.css`; color-only states are forbidden
|
|
|
345
345
|
|
|
346
346
|
- `docs/design.md` — this document (master).
|
|
347
347
|
- `midgard/design-system/tokens.css` — the consumable design tokens.
|
|
348
|
-
- `midgard/design-system/icons.md` — rune-glyph map (
|
|
348
|
+
- `midgard/design-system/icons.md` — rune-glyph map (21 glyphs, drawn in `icons/*.svg`).
|
|
349
349
|
- `docs/ymir-rut.md` Part 3 — the original Rut design-system specification this
|
|
350
350
|
document implements.
|
|
351
351
|
- `docs/lore.md` §VI — house lineage behind the house accents.
|
package/package.json
CHANGED
package/scripts/electron.sh
CHANGED
|
@@ -26,6 +26,19 @@ fi
|
|
|
26
26
|
VERSION="1.1.0"
|
|
27
27
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
28
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
29
|
+
# Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
|
|
30
|
+
# npm install — both shapes, one resolver (bin/app-lib.sh).
|
|
31
|
+
if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
|
|
32
|
+
_ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
33
|
+
for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
|
|
34
|
+
[ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
|
|
35
|
+
done
|
|
36
|
+
unset _ya _yac
|
|
37
|
+
fi
|
|
38
|
+
app_dir hlidskjalf APP_HLIDSKJALF || APP_HLIDSKJALF=""
|
|
39
|
+
app_dir odrerir APP_ODRERIR || APP_ODRERIR=""
|
|
40
|
+
app_dir sessrumnir APP_SESSRUMNIR || APP_SESSRUMNIR=""
|
|
41
|
+
|
|
29
42
|
|
|
30
43
|
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
31
44
|
# runtime state belong to the home the operator chose at installation, never in
|
|
@@ -39,8 +52,8 @@ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
|
39
52
|
fi
|
|
40
53
|
hoard_state_dir YMIR_STATE_DIR
|
|
41
54
|
hoard_data_dir YMIR_DATA_DIR
|
|
42
|
-
APP="$
|
|
43
|
-
Odrerir_app="$
|
|
55
|
+
APP="$APP_HLIDSKJALF"
|
|
56
|
+
Odrerir_app="$APP_ODRERIR"
|
|
44
57
|
NO_INSTALL=0
|
|
45
58
|
VIEW="${YMIR_DESKTOP_VIEW:-hlidskjalf}"
|
|
46
59
|
VIEWS=(hlidskjalf smidja odrerir)
|
package/scripts/start.sh
CHANGED
|
@@ -17,6 +17,33 @@ if [ -z "${YMIR_PLATFORM_LOADED:-}" ]; then
|
|
|
17
17
|
fi
|
|
18
18
|
|
|
19
19
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
20
|
+
# The cloth (bin/ymir-style.sh) — colour and words for the human watching.
|
|
21
|
+
if [ -z "${YMIR_STYLE_LOADED:-}" ] && [ -r "$ROOT/bin/ymir-style.sh" ]; then
|
|
22
|
+
. "$ROOT/bin/ymir-style.sh"; YMIR_STYLE_LOADED=1; style_init
|
|
23
|
+
fi
|
|
24
|
+
# Where an app lives: apps/<surface> in a clone, node_modules/@zerwiz/<pkg> in an
|
|
25
|
+
# npm install — both shapes, one resolver (bin/app-lib.sh).
|
|
26
|
+
if [ -z "${YMIR_APP_LIB_LOADED:-}" ]; then
|
|
27
|
+
_ya="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
for _yac in "$_ya/app-lib.sh" "$(dirname "$_ya")/bin/app-lib.sh"; do
|
|
29
|
+
[ -r "$_yac" ] && { . "$_yac"; YMIR_APP_LIB_LOADED=1; break; }
|
|
30
|
+
done
|
|
31
|
+
unset _ya _yac
|
|
32
|
+
fi
|
|
33
|
+
app_dir hlidskjalf APP_HLIDSKJALF || APP_HLIDSKJALF=""
|
|
34
|
+
|
|
35
|
+
# Where the smithy's parts live: apps/smidja-factory in a clone, or the
|
|
36
|
+
# @zerwiz/smidja-factory package in an npm install (bin/smidja-lib.sh).
|
|
37
|
+
if [ -z "${YMIR_SMIDJA_LIB_LOADED:-}" ]; then
|
|
38
|
+
_ys="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
39
|
+
for _yc in "$_ys/smidja-lib.sh" "$(dirname "$_ys")/bin/smidja-lib.sh"; do
|
|
40
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_SMIDJA_LIB_LOADED=1; break; }
|
|
41
|
+
done
|
|
42
|
+
unset _ys _yc
|
|
43
|
+
fi
|
|
44
|
+
smidja_visualizer_dir SMIDJA_VIZ
|
|
45
|
+
smidja_factory_dir SMIDJA_FACTORY
|
|
46
|
+
|
|
20
47
|
|
|
21
48
|
# The operator's settings and secrets live in the home they chose, never in the
|
|
22
49
|
# code tree — a packaged install replaces its tree on upgrade, and a credential
|
|
@@ -36,7 +63,7 @@ hoard_data_dir YMIR_DATA_DIR
|
|
|
36
63
|
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
37
64
|
# runtime state belong to the home the operator chose at installation, never in
|
|
38
65
|
# the tree — a packaged install replaces its tree on upgrade (Rule 04).
|
|
39
|
-
APP="$
|
|
66
|
+
APP="$APP_HLIDSKJALF"
|
|
40
67
|
RUN="$ROOT/.run"
|
|
41
68
|
PID_FILE="$RUN/hlidskjalf.pid"
|
|
42
69
|
LOG="$RUN/hlidskjalf.log"
|
|
@@ -68,7 +95,8 @@ else
|
|
|
68
95
|
fi
|
|
69
96
|
|
|
70
97
|
if [[ ! -d "$APP/node_modules" ]]; then
|
|
71
|
-
|
|
98
|
+
style_patience "the seat's own dependencies are being fetched, then the hall is raised"
|
|
99
|
+
echo "Installing dependencies…"
|
|
72
100
|
(cd "$APP" && npm install --no-audit --no-fund)
|
|
73
101
|
fi
|
|
74
102
|
|
|
@@ -116,7 +144,7 @@ fi
|
|
|
116
144
|
# Smíðja's eye — the Vue trace visualizer (API :8437, UI :8438). Reads the repo's
|
|
117
145
|
# own smidja.db and exposes the Sessions/Trace/Decisions/Stats views behind the
|
|
118
146
|
# Hlidskjalf Sessions gate's "Open visualizer" button.
|
|
119
|
-
VIZ_DIR="$
|
|
147
|
+
VIZ_DIR="${SMIDJA_VIZ:-}"
|
|
120
148
|
VIZ_API_PORT="${SMIDJA_VIZ_API_PORT:-8437}"
|
|
121
149
|
VIZ_UI_PORT="${SMIDJA_VIZ_UI_PORT:-8438}"
|
|
122
150
|
VIZ_API_PID_FILE="$RUN/smidja-viz-api.pid"
|