@zerwiz/ymir 0.1.5 → 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/agents/huginn-researcher.md +1 -1
- package/.agents/agents/kvasir-scout.md +1 -1
- package/.agents/agents/sindri-developer.md +1 -1
- package/.agents/backend/README.md +74 -0
- package/.agents/backend/model-bridge.py +74 -4
- package/.agents/config/cron.yaml +2 -0
- package/.agents/config/eindri-dispatch.json +6 -6
- package/.agents/migrations/0003-private-data-separation.sh +11 -1
- package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
- package/.agents/skills/README.md +1 -1
- package/.agents/skills/galdr-ymirsystem/SKILL.md +2 -1
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +73 -10
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +44 -1
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +290 -24
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +3 -1
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +39 -1
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +4 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +7 -2
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +1 -1
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +2 -2
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +16 -11
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +147 -4
- package/.agents/skills/groa-update/SKILL.md +20 -0
- package/.agents/skills/hamr-adapters/SKILL.md +2 -2
- package/.agents/skills/lifecycle/smoke_test.sh +7 -2
- package/.agents/skills/ymir-host/SKILL.md +2 -1
- package/.agents/skills/ymir-host/assets/install.md +69 -0
- package/AGENTS.md +78 -34
- package/CHANGELOG.md +1362 -1
- package/README.md +1 -1
- package/RULES/02-agents.md +2 -2
- package/RULES/04-hoard.md +32 -0
- package/RULES/06-append-only.md +50 -0
- package/RULES/07-config.md +56 -0
- package/RULES/09-electron.md +58 -0
- package/RULES/README.md +2 -0
- package/bin/a2a-mcp.sh +42 -7
- package/bin/agents-config.sh +30 -7
- package/bin/apodex-smoke-test.sh +14 -1
- package/bin/bifrost-bridge.sh +25 -6
- package/bin/bootstrap-macos.sh +6 -1
- package/bin/brokk-send.sh +14 -1
- package/bin/changelog-assemble.sh +154 -0
- package/bin/changelog-guard.sh +39 -7
- package/bin/crash-sense.sh +14 -1
- package/bin/design-check.sh +80 -0
- package/bin/design-icon.sh +156 -0
- package/bin/docs-guard.sh +2 -2
- package/bin/editor-place.sh +33 -15
- package/bin/eindri-acclaim.sh +14 -1
- package/bin/eindri-seat.sh +83 -0
- package/bin/eindri-seen.sh +14 -1
- package/bin/eindri-start.sh +14 -1
- package/bin/einherjar-spawn.sh +9 -3
- package/bin/eir-doctor.sh +89 -1
- package/bin/electron-lib.sh +34 -0
- package/bin/feature-inventory.sh +73 -0
- package/bin/fleet-apply.sh +15 -2
- package/bin/github-deploy.sh +14 -1
- package/bin/gjallarhorn-expose.sh +43 -9
- package/bin/gjallarhorn-notify.sh +14 -1
- package/bin/gjallarhorn-purge.sh +14 -1
- package/bin/gjallarhorn-tunnel.sh +16 -3
- package/bin/groa-update.sh +9 -1
- package/bin/hall-snapshot.sh +21 -2
- package/bin/herdr-run.sh +23 -2
- package/bin/hlidskjalf-agents.sh +172 -0
- package/bin/hlidskjalf-usage.sh +196 -0
- package/bin/hoard-guard.sh +135 -0
- package/bin/hoard-lib.sh +91 -11
- package/bin/hodd.sh +28 -4
- package/bin/huginn-research-worker.sh +14 -1
- package/bin/local-model-lock.sh +22 -3
- package/bin/mimir-bridge.py +33 -0
- package/bin/mimir-bridge.sh +14 -1
- package/bin/mimir-reflect.sh +27 -0
- package/bin/mjollnir-webhook.sh +14 -1
- package/bin/mjollnir.sh +22 -3
- package/bin/model-hardware.sh +17 -3
- package/bin/model-resolve.sh +14 -1
- package/bin/nornir-job-hall-snapshot.sh +28 -0
- package/bin/nornir-job-nsr-compliance.sh +54 -0
- package/bin/npm-publish.sh +136 -0
- package/bin/omarchy-install.sh +18 -5
- package/bin/omarchy-plugins.sh +15 -2
- package/bin/omarchy-sense.sh +14 -1
- package/bin/pi-model-agnostic.sh +71 -0
- package/bin/private-guard.sh +20 -0
- package/bin/project-git.sh +18 -4
- package/bin/public-guard.sh +3 -2
- package/bin/sessrumnir-ensure.sh +1 -1
- package/bin/sessrumnir.sh +16 -3
- package/bin/smidja-board.sh +136 -0
- package/bin/smidja-bootstrap.sh +5 -1
- package/bin/smidja-lib.sh +49 -0
- package/bin/syn-asset-pretool-check.sh +14 -1
- package/bin/syn-spawn-pretool-check.sh +58 -0
- package/bin/tailscale-sync.sh +15 -2
- package/bin/telegram-bot.sh +14 -1
- package/bin/ui-truth-check.sh +75 -0
- package/bin/valknut-load.sh +42 -4
- package/bin/wedge-notify.sh +14 -1
- package/bin/workspace-rag.sh +14 -1
- package/bin/ymir-install.sh +257 -57
- package/bin/ymir-plan.sh +390 -0
- package/bin/ymir-say.sh +14 -1
- package/bin/ymir-setup-auth.sh +12 -1
- package/bin/ymir-style.sh +108 -0
- package/bin/ymir-validate.sh +64 -11
- package/bin/ymir.js +82 -34
- package/docs/design.md +2 -2
- package/docs/lore.md +5 -1
- package/docs/runbooks/agents.md +1 -1
- package/package.json +13 -3
- package/scripts/electron.sh +74 -15
- package/scripts/lower.sh +34 -0
- package/scripts/raise.sh +42 -0
- package/scripts/start.sh +65 -3
- package/.agents/config/agents.yaml +0 -50
- package/.agents/config/tailscale-sync.yaml +0 -27
- package/.agents/memory/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/well/episodes.jsonl +0 -367
- package/.agents/memory/well/workspace.jsonl +0 -27
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ui-truth-check.sh — the panel's number must equal the machine's number.
|
|
3
|
+
#
|
|
4
|
+
# The wiring order's last row, and the one that makes the rest trustworthy: a
|
|
5
|
+
# board can render beautifully and still lie. This compares what the UI is SERVED
|
|
6
|
+
# against what the machine actually holds, per source, and FAILS on a mismatch.
|
|
7
|
+
#
|
|
8
|
+
# bin/ui-truth-check.sh # compare, TOON, exit 0/1
|
|
9
|
+
# bin/ui-truth-check.sh --help
|
|
10
|
+
#
|
|
11
|
+
# It reads endpoints as the DESKTOP SEAT (loopback + the marker), which is the
|
|
12
|
+
# trusted seat rule 09 allows — the web door would answer 401 and prove nothing
|
|
13
|
+
# about the data.
|
|
14
|
+
set -u
|
|
15
|
+
|
|
16
|
+
VERSION="1.0.0"
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
19
|
+
GATE="${HLIDSKJALF_API_PORT:-3889}"
|
|
20
|
+
# shellcheck source=bin/hoard-lib.sh
|
|
21
|
+
. "$SCRIPT_DIR/hoard-lib.sh"
|
|
22
|
+
|
|
23
|
+
case "${1-}" in
|
|
24
|
+
-v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
|
|
25
|
+
-h|--help) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
have() { command -v "$1" >/dev/null 2>&1; }
|
|
29
|
+
have curl || { printf 'error: curl is required\nhelp: install curl\n' >&2; exit 1; }
|
|
30
|
+
|
|
31
|
+
json_len() { python3 -c 'import json,sys
|
|
32
|
+
try: print(len(json.load(sys.stdin)))
|
|
33
|
+
except Exception: print(0)' 2>/dev/null || echo 0; }
|
|
34
|
+
|
|
35
|
+
verdict="PASS"; details=""
|
|
36
|
+
|
|
37
|
+
# ── the fleet: herdr vs the connector vs the endpoint ─────────────────────────
|
|
38
|
+
SRC_JSON="$("$SCRIPT_DIR/hlidskjalf-agents.sh" 2>/dev/null || printf '[]')"
|
|
39
|
+
src_n=$(printf '%s' "$SRC_JSON" | json_len)
|
|
40
|
+
UI_JSON="$(curl -s --max-time 8 -H 'x-ymir-surface: desktop' "http://127.0.0.1:${GATE}/api/agents" 2>/dev/null || printf '[]')"
|
|
41
|
+
ui_n=$(printf '%s' "$UI_JSON" | json_len)
|
|
42
|
+
if have "${HERDR_BIN_PATH:-herdr}"; then
|
|
43
|
+
herd_n="$("${HERDR_BIN_PATH:-herdr}" pane list 2>/dev/null | python3 -c 'import json,sys
|
|
44
|
+
try: d=json.load(sys.stdin)
|
|
45
|
+
except Exception: print(0); raise SystemExit
|
|
46
|
+
panes=(d.get("result") or {}).get("panes") or d.get("panes") or []
|
|
47
|
+
print(sum(1 for p in panes if p.get("agent")))' 2>/dev/null || echo 0)"
|
|
48
|
+
else
|
|
49
|
+
herd_n="unknown"
|
|
50
|
+
fi
|
|
51
|
+
[ "$src_n" = "$ui_n" ] || { verdict="FAIL"; details="$details fleet:connector=$src_n,served=$ui_n"; }
|
|
52
|
+
# The connector is ROSTER-FIRST: it reports the agents (the who) and the seats (the
|
|
53
|
+
# where), so its total legitimately exceeds the pane count. What must not happen is
|
|
54
|
+
# FEWER entries than panes - a standing seat hidden from the board. Panes are a subset.
|
|
55
|
+
[ "$herd_n" = "unknown" ] || [ "$src_n" -ge "$herd_n" ] || { verdict="FAIL"; details="$details fleet:connector=$src_n,herdr=$herd_n"; }
|
|
56
|
+
|
|
57
|
+
# ── the ledger: the hoard's, against what the gate serves ─────────────────────
|
|
58
|
+
hoard_root _hoard
|
|
59
|
+
LEDGER="$_hoard/memory/runes_audit.md"
|
|
60
|
+
ledger_n=0
|
|
61
|
+
[ -r "$LEDGER" ] && ledger_n=$(grep -c '^[[:space:]]*{' "$LEDGER" 2>/dev/null || echo 0)
|
|
62
|
+
UI_RUNES="$(curl -s --max-time 8 -H 'x-ymir-surface: desktop' "http://127.0.0.1:${GATE}/api/runes" 2>/dev/null || printf '[]')"
|
|
63
|
+
ui_runes_n=$(printf '%s' "$UI_RUNES" | json_len)
|
|
64
|
+
[ "$ledger_n" = "$ui_runes_n" ] || { verdict="FAIL"; details="$details ledger=$ledger_n,served=$ui_runes_n"; }
|
|
65
|
+
|
|
66
|
+
printf 'ui_truth[6]{source,count,note}:\n'
|
|
67
|
+
printf ' "herdr panes","%s","what the machine holds"\n' "$herd_n"
|
|
68
|
+
printf ' "the connector","%s","bin/hlidskjalf-agents.sh"\n' "$src_n"
|
|
69
|
+
printf ' "the endpoint","%s","/api/agents as the desktop seat"\n' "$ui_n"
|
|
70
|
+
printf ' "ledger entries","%s","the hoard'"'"'s ledger"\n' "$ledger_n"
|
|
71
|
+
printf ' "ledger served","%s","/api/runes as the desktop seat"\n' "$ui_runes_n"
|
|
72
|
+
printf ' "verdict","%s","%s"\n' "$verdict" "$([ -n "$details" ] && printf 'mismatch:%s' "$details" || printf 'the board may be believed')"
|
|
73
|
+
|
|
74
|
+
[ "$verdict" = "PASS" ] || { printf 'error: the UI and the machine disagree\nhelp: %s\n' "${details:-see the rows above}" >&2; exit 1; }
|
|
75
|
+
exit 0
|
package/bin/valknut-load.sh
CHANGED
|
@@ -26,7 +26,7 @@ PI_GLOBAL="${HOME}/.pi/agent/agents"
|
|
|
26
26
|
# seated. This loader DEPLOYS that source into the single home.
|
|
27
27
|
PI_EXT_SRC="$ROOT/.pi/shared/extensions"
|
|
28
28
|
PI_EXT_HOME="${HOME}/.pi/agent/extensions"
|
|
29
|
-
OC_LOCAL="$ROOT/.opencode/
|
|
29
|
+
OC_LOCAL="$ROOT/.opencode/agents"
|
|
30
30
|
SKILLS="$ROOT/.agents/skills"
|
|
31
31
|
|
|
32
32
|
usage() {
|
|
@@ -167,6 +167,18 @@ fi
|
|
|
167
167
|
|
|
168
168
|
if [ "$MODE_OPENCODE" = 1 ]; then
|
|
169
169
|
add opencode-config "$ROOT/opencode.json" "$(config_out "$ROOT/opencode.json.example" "$ROOT/opencode.json")"
|
|
170
|
+
# OpenCode reads `.opencode/agents/` (PLURAL). The singular `.opencode/agent/`
|
|
171
|
+
# was never read by the harness at all — twenty correct symlinks in a directory
|
|
172
|
+
# no loader opens, which is why only the agents declared by hand in
|
|
173
|
+
# opencode.json ever appeared. Migrate a legacy singular dir forward so an old
|
|
174
|
+
# home heals instead of silently keeping its agents invisible.
|
|
175
|
+
if [ -d "$ROOT/.opencode/agent" ] && [ ! -e "$ROOT/.opencode/agents" ]; then
|
|
176
|
+
mv "$ROOT/.opencode/agent" "$ROOT/.opencode/agents" 2>/dev/null \
|
|
177
|
+
&& add opencode-migrate "$ROOT/.opencode/agents" "legacy .opencode/agent/ moved to .opencode/agents/" \
|
|
178
|
+
|| add opencode-migrate "$ROOT/.opencode/agents" "ERROR could not migrate"
|
|
179
|
+
fi
|
|
180
|
+
# Count what is bound here; the binding itself happens further down, after
|
|
181
|
+
# `link_agents` is defined (this block runs before that definition).
|
|
170
182
|
if [ -d "$OC_LOCAL" ]; then
|
|
171
183
|
n=$(ls "$OC_LOCAL"/*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
172
184
|
add opencode "$OC_LOCAL" "native ($n agents)"
|
|
@@ -190,6 +202,17 @@ if [ "$MODE_PI" = 1 ]; then
|
|
|
190
202
|
# Deploy the shared extensions into their single home. Copy, not link: a
|
|
191
203
|
# broken link would silently disable a tool, and pi reads the file directly.
|
|
192
204
|
# Idempotent — identical files are left untouched.
|
|
205
|
+
# The CONTRACT, deployed globally: pi reads an AGENTS.md from its agent home in
|
|
206
|
+
# every session, whatever the workspace. Without this, Sessrumnir (or any pi
|
|
207
|
+
# session) started in another project loads THAT project's rules and knows
|
|
208
|
+
# nothing of Ymir - no Brokk, no laws, no lore. Symlink, so the tree stays the
|
|
209
|
+
# single source and an update is picked up with no re-install.
|
|
210
|
+
if [ -d "$HOME/.pi/agent" ]; then
|
|
211
|
+
ln -sfn "$ROOT/AGENTS.md" "$HOME/.pi/agent/AGENTS.md" 2>/dev/null \
|
|
212
|
+
&& add pi-global-contract "$HOME/.pi/agent/AGENTS.md" "the Ymir contract, loaded in every pi session" \
|
|
213
|
+
|| add pi-global-contract "$HOME/.pi/agent/AGENTS.md" "ERROR"
|
|
214
|
+
fi
|
|
215
|
+
|
|
193
216
|
if [ -d "$PI_EXT_SRC" ]; then
|
|
194
217
|
mkdir -p "$PI_EXT_HOME" 2>/dev/null
|
|
195
218
|
dep_n=0
|
|
@@ -199,7 +222,22 @@ if [ "$MODE_PI" = 1 ]; then
|
|
|
199
222
|
if [ -f "$PI_EXT_HOME/$b" ] && cmp -s "$f" "$PI_EXT_HOME/$b"; then continue; fi
|
|
200
223
|
cp -f "$f" "$PI_EXT_HOME/$b" && dep_n=$((dep_n+1))
|
|
201
224
|
done
|
|
202
|
-
|
|
225
|
+
# Their supporting modules are one level away, under the extensions' own lib:
|
|
226
|
+
# the shared extensions require sibling lib modules, and deploying the
|
|
227
|
+
# top-level files ALONE ships extensions that cannot load. That is exactly
|
|
228
|
+
# what pi reported: Failed to load extension, Cannot find module ./lib/....
|
|
229
|
+
# A deploy that copies a file but not the module it imports is not a deploy.
|
|
230
|
+
PI_EXT_LIB="$ROOT/.pi/extensions/lib"
|
|
231
|
+
if [ -d "$PI_EXT_LIB" ]; then
|
|
232
|
+
mkdir -p "$PI_EXT_HOME/lib" 2>/dev/null
|
|
233
|
+
for f in "$PI_EXT_LIB"/*; do
|
|
234
|
+
[ -e "$f" ] || continue
|
|
235
|
+
b=$(basename "$f")
|
|
236
|
+
if [ -f "$PI_EXT_HOME/lib/$b" ] && cmp -s "$f" "$PI_EXT_HOME/lib/$b"; then continue; fi
|
|
237
|
+
cp -f "$f" "$PI_EXT_HOME/lib/$b" && dep_n=$((dep_n+1))
|
|
238
|
+
done
|
|
239
|
+
fi
|
|
240
|
+
add pi-extensions "$PI_EXT_HOME" "$dep_n deployed (shared single home, lib included)"
|
|
203
241
|
fi
|
|
204
242
|
fi
|
|
205
243
|
|
|
@@ -248,8 +286,8 @@ if [ "$MODE_STATUS" = 0 ]; then
|
|
|
248
286
|
add "${hd#.}-agents" "$ROOT/$hd/agents" "bound ($n)"
|
|
249
287
|
done
|
|
250
288
|
if [ -d "$ROOT/.opencode" ]; then
|
|
251
|
-
n=$(link_agents "$
|
|
252
|
-
add opencode-agents "$
|
|
289
|
+
n=$(link_agents "$OC_LOCAL" short) || n=0
|
|
290
|
+
add opencode-agents "$OC_LOCAL" "bound ($n)"
|
|
253
291
|
fi
|
|
254
292
|
fi
|
|
255
293
|
|
package/bin/wedge-notify.sh
CHANGED
|
@@ -20,7 +20,20 @@ CHANNEL="${1:-unknown}"
|
|
|
20
20
|
SUMMARY="${2:-}"
|
|
21
21
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
22
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
25
|
+
# runtime state belong to the home the operator chose at installation, never in
|
|
26
|
+
# the tree — a packaged install replaces its tree on upgrade (Rule 04).
|
|
27
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
28
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
30
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
31
|
+
done
|
|
32
|
+
unset _yr _yc
|
|
33
|
+
fi
|
|
34
|
+
hoard_state_dir YMIR_STATE_DIR
|
|
35
|
+
hoard_data_dir YMIR_DATA_DIR
|
|
36
|
+
STATE_DIR="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
|
|
24
37
|
LOG="$STATE_DIR/wedge-alarm.log"
|
|
25
38
|
|
|
26
39
|
# "discard" is the library-mode default so no test posts a real notification.
|
package/bin/workspace-rag.sh
CHANGED
|
@@ -16,8 +16,21 @@ set -u
|
|
|
16
16
|
VERSION="1.0.0"
|
|
17
17
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
18
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
19
|
+
|
|
20
|
+
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
21
|
+
# runtime state belong to the home the operator chose at installation, never in
|
|
22
|
+
# the tree — a packaged install replaces its tree on upgrade (Rule 04).
|
|
23
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
24
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
26
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
27
|
+
done
|
|
28
|
+
unset _yr _yc
|
|
29
|
+
fi
|
|
30
|
+
hoard_state_dir YMIR_STATE_DIR
|
|
31
|
+
hoard_data_dir YMIR_DATA_DIR
|
|
19
32
|
STORE="$ROOT/.agents/memory/well/workspace.jsonl"
|
|
20
|
-
REALM_FILE="$
|
|
33
|
+
REALM_FILE="$YMIR_DATA_DIR/realm.md"
|
|
21
34
|
DEFAULT_REALM="$(head -n1 "$REALM_FILE" 2>/dev/null | tr -d '[:space:]')"
|
|
22
35
|
# Never assume the company's slug: resolve the operator's realm neutrally.
|
|
23
36
|
if [ -z "$DEFAULT_REALM" ]; then . "$SCRIPT_DIR/realm-lib.sh"; ymir_active_realm "$ROOT" DEFAULT_REALM; fi
|
package/bin/ymir-install.sh
CHANGED
|
@@ -6,13 +6,17 @@
|
|
|
6
6
|
#
|
|
7
7
|
# Usage:
|
|
8
8
|
# bin/ymir-install.sh [--check] [--skip-engines] [--skip-services] [--no-desktop] [--yes]
|
|
9
|
+
# bin/ymir-install.sh --plan [--json] # the plan, computed — changes nothing
|
|
10
|
+
# bin/ymir-install.sh --yes | --non-interactive | --accept-all-defaults
|
|
9
11
|
# bin/ymir-install.sh --status
|
|
10
12
|
# bin/ymir-install.sh --version
|
|
11
13
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
+
# The consent a real install asks for is not a recited paragraph: it is the PLAN
|
|
15
|
+
# printed by bin/ymir-plan.sh — probed on THIS host, one row per step, each with
|
|
16
|
+
# its state and the reason for it. A paragraph drifts behind the code; a plan is
|
|
17
|
+
# computed from it every run.
|
|
14
18
|
#
|
|
15
|
-
# Exit: 0 all good (or --check), 1 a step failed, 2 usage, 3 declined.
|
|
19
|
+
# Exit: 0 all good (or --check/--plan), 1 a step failed, 2 usage, 3 declined.
|
|
16
20
|
set -u
|
|
17
21
|
|
|
18
22
|
# --- portability shim: bin/ymir-platform.sh --------------------------------
|
|
@@ -27,30 +31,72 @@ fi
|
|
|
27
31
|
VERSION="1.0.0"
|
|
28
32
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
33
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
|
|
35
|
+
# The operator's settings and secrets live in the home they chose, never in the
|
|
36
|
+
# code tree — a packaged install replaces its tree on upgrade, and a credential
|
|
37
|
+
# must never sit in a tree that ships (Rule 04).
|
|
38
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
39
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
40
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
41
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
42
|
+
done
|
|
43
|
+
unset _yr _yc
|
|
44
|
+
fi
|
|
45
|
+
hoard_settings_dir YMIR_SETTINGS_DIR
|
|
46
|
+
hoard_local_env YMIR_ENV_FILE
|
|
47
|
+
# The home is the OPERATOR's to choose, never ours to assume. Resolution:
|
|
48
|
+
# $YMIR_HOME (explicit) → the choice recorded at a previous installation →
|
|
49
|
+
# the one documented default. An interactive run ASKS and RECORDS the answer
|
|
50
|
+
# (step_home); --check never writes, --yes takes what is recorded.
|
|
32
51
|
# The hoard resolves through the shared lib, so the installer can never disagree
|
|
33
52
|
# with bin/hodd.sh about where private data lives — and never points inside the
|
|
34
53
|
# repo (Rule 04).
|
|
35
54
|
# shellcheck source=bin/hoard-lib.sh
|
|
36
55
|
. "$SCRIPT_DIR/hoard-lib.sh"
|
|
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
|
+
|
|
69
|
+
WORKSPACE="${YMIR_WORKSPACE:-$YMIR_HOME/workspaces}"
|
|
37
70
|
hoard_root HOARD
|
|
38
71
|
DOMAINS="company marketing development life me"
|
|
39
72
|
|
|
40
|
-
CHECK=0; SKIP_ENGINES=0; SKIP_SERVICES=0; ASSUME_YES=0; NO_DESKTOP=0
|
|
41
|
-
case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help) sed -n '2,
|
|
73
|
+
CHECK=0; SKIP_ENGINES=0; SKIP_SERVICES=0; ASSUME_YES=0; NO_DESKTOP=0; PLAN_ONLY=0; PLAN_ARGS=()
|
|
74
|
+
case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help) sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
|
|
42
75
|
while [ $# -gt 0 ]; do
|
|
43
76
|
case "$1" in
|
|
44
77
|
--check) CHECK=1; shift ;;
|
|
78
|
+
--plan|--dry-run) PLAN_ONLY=1; shift ;;
|
|
79
|
+
--json|--blocked) PLAN_ARGS+=("$1"); shift ;;
|
|
80
|
+
--phase) PLAN_ARGS+=("$1" "${2-}"); shift 2 ;; # --phase carries its number
|
|
45
81
|
--skip-engines) SKIP_ENGINES=1; shift ;;
|
|
46
82
|
--skip-services) SKIP_SERVICES=1; shift ;;
|
|
47
83
|
--no-desktop) NO_DESKTOP=1; shift ;;
|
|
48
|
-
--yes|-y) ASSUME_YES=1; shift ;;
|
|
84
|
+
--yes|-y|--non-interactive|--accept-all-defaults) ASSUME_YES=1; shift ;;
|
|
49
85
|
--status) exec "$SCRIPT_DIR/ymir-install.sh" --check ;;
|
|
50
|
-
*) printf 'error: unknown flag %s\nhelp: bin/ymir-install.sh [--check|--skip-engines|--skip-services|--no-desktop|--yes]\n' "$1" >&2; exit 2 ;;
|
|
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 ;;
|
|
51
87
|
esac
|
|
52
88
|
done
|
|
53
89
|
|
|
90
|
+
# The plan is a question, not a change: it prints and exits without writing.
|
|
91
|
+
if [ "$PLAN_ONLY" = 1 ]; then
|
|
92
|
+
exec "$SCRIPT_DIR/ymir-plan.sh" ${PLAN_ARGS[@]+"${PLAN_ARGS[@]}"}
|
|
93
|
+
fi
|
|
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
|
+
|
|
54
100
|
declare -a IDS STATUS DETAIL
|
|
55
101
|
# The code minted for this install, reported at the end so it is not lost in the
|
|
56
102
|
# step table. Empty on --check, and when no code could be minted.
|
|
@@ -62,33 +108,28 @@ TOON="install[0]{step,status,detail}:"
|
|
|
62
108
|
# ── consent ──────────────────────────────────────────────────────────────
|
|
63
109
|
# Show exactly what will change and require explicit acceptance. A real
|
|
64
110
|
# install touches the machine (packages, a docker image, raised services),
|
|
65
|
-
# so it never proceeds on silence.
|
|
111
|
+
# so it never proceeds on silence. The plan is COMPUTED here (bin/ymir-plan.sh
|
|
112
|
+
# probes this host) — never a paragraph that can drift behind the code.
|
|
66
113
|
confirm_install() {
|
|
67
114
|
[ "$ASSUME_YES" = 1 ] && return 0
|
|
68
115
|
if [ ! -t 0 ]; then
|
|
69
|
-
printf 'error: refusing a non-interactive install without --yes\nhelp: re-run with --yes to accept non-interactively, or --check to preview\n' >&2
|
|
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
|
|
70
117
|
exit 3
|
|
71
118
|
fi
|
|
119
|
+
style_title "Ymir first setup" "the plan below is probed on this machine, not recited"
|
|
72
120
|
cat <<'PLAN'
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
• learn this machine (Omarchy version, packages, configs, monitors, scale)
|
|
81
|
-
• place the desktop apps on their own numbered desktops
|
|
82
|
-
• build the Utgard sandbox image 'utgard-runner:latest' (needs docker access)
|
|
83
|
-
• create the Smiðja database and build the visualizer UI
|
|
84
|
-
• load agents/skills and write workspace/INSTALL.md
|
|
85
|
-
• install the git delivery gates (secret · branch · changelog)
|
|
86
|
-
• raise the runtime services (Hlidskjalf SPA + gate API + bridges)
|
|
87
|
-
• open BOTH desktop apps so you see them: Hlidskjalf + Smíðja
|
|
88
|
-
• verify the running system and report what stands
|
|
121
|
+
Each row is a phase, a step, its state, and why.
|
|
122
|
+
|
|
123
|
+
DO a change will be made
|
|
124
|
+
SKIP already satisfied — nothing to do
|
|
125
|
+
INFO a fact about this host; no change implied
|
|
126
|
+
BLOCKED cannot run — the reason names what is missing
|
|
127
|
+
CONSENT needs your word (a credential, an invite, the desktop shells)
|
|
89
128
|
|
|
90
129
|
Nothing is deleted. Every step is idempotent.
|
|
91
130
|
PLAN
|
|
131
|
+
printf '\n'
|
|
132
|
+
bash "$SCRIPT_DIR/ymir-plan.sh" --colour >/dev/null 2>&1 || true
|
|
92
133
|
printf '\nProceed with the install? [y/N] '
|
|
93
134
|
read -r reply || reply=""
|
|
94
135
|
case "$reply" in
|
|
@@ -197,6 +238,112 @@ YAML
|
|
|
197
238
|
add tree OK "workspace/{personal} · companies · registries · hoard (outside the repo) (created $created)"
|
|
198
239
|
}
|
|
199
240
|
|
|
241
|
+
# ── 2a. the home the operator chooses ────────────────────────────────────────
|
|
242
|
+
# Everything private — docs, secrets, identity, projects, workspaces, memory —
|
|
243
|
+
# lives in ONE place the operator owns, and it is the operator's to name. A real
|
|
244
|
+
# interactive run asks once and records the answer under ~/.config/ymir/home, so
|
|
245
|
+
# every later script resolves the same home without being told again. --check
|
|
246
|
+
# never writes; --yes takes what is already recorded (or the documented default).
|
|
247
|
+
step_home() {
|
|
248
|
+
local rec="" chosen=""
|
|
249
|
+
ymir_home_record rec
|
|
250
|
+
if [ -n "${YMIR_HOME:-}" ] && [ "$CHECK" = 1 ]; then add home OK "home: $YMIR_HOME"; return; fi
|
|
251
|
+
if [ -n "$rec" ]; then
|
|
252
|
+
add home OK "home: $YMIR_HOME (the choice recorded at installation)"
|
|
253
|
+
return
|
|
254
|
+
fi
|
|
255
|
+
if [ "$ASSUME_YES" = 1 ] || [ ! -t 0 ]; then
|
|
256
|
+
if ymir_home_record_set "$YMIR_HOME" 2>/dev/null; then
|
|
257
|
+
add home OK "home: $YMIR_HOME (default recorded — change it any time with YMIR_HOME=<path> bin/ymir-install.sh)"
|
|
258
|
+
else
|
|
259
|
+
add home WARN "home: $YMIR_HOME (could not record the choice under $YMIR_CONFIG_DIR — pass YMIR_HOME=<path> to every run)"
|
|
260
|
+
fi
|
|
261
|
+
return
|
|
262
|
+
fi
|
|
263
|
+
printf '\nWhere shall your home live?\n'
|
|
264
|
+
printf 'Everything private is kept there — docs, secrets, identity, projects,\n'
|
|
265
|
+
printf 'workspaces, memory — never in the code tree, never in this package.\n'
|
|
266
|
+
printf 'Home [%s]: ' "$YMIR_HOME"
|
|
267
|
+
IFS= read -r chosen || true
|
|
268
|
+
[ -n "$chosen" ] && YMIR_HOME="${chosen/#\~/$HOME}"
|
|
269
|
+
if [ -e "$YMIR_HOME" ]; then
|
|
270
|
+
printf 'using %s — what is already there is kept; nothing is deleted.\n' "$YMIR_HOME"
|
|
271
|
+
fi
|
|
272
|
+
# Re-resolve the roots the choice moved: the hoard within the home, and the
|
|
273
|
+
# workspace tree beneath it. A second run finds the record and never asks again.
|
|
274
|
+
hoard_root HOARD
|
|
275
|
+
WORKSPACE="${YMIR_WORKSPACE:-$YMIR_HOME/workspaces}"
|
|
276
|
+
if ymir_home_record_set "$YMIR_HOME"; then
|
|
277
|
+
add home OK "home: $YMIR_HOME (recorded — later runs need not ask)"
|
|
278
|
+
else
|
|
279
|
+
add home WARN "home: $YMIR_HOME (could not record the choice; pass YMIR_HOME=<path> to every run)"
|
|
280
|
+
fi
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# ── 2b. app repos (the app split) ────────────────────────────────────────────
|
|
284
|
+
# The apps live in their OWN repos (zerwiz/hlidskjalf · hlidskjalf-mobile ·
|
|
285
|
+
# odrerir · sessrumnir · smidja), registered in the home registry with a
|
|
286
|
+
# `repo: apps/<path>` block. The monorepo never tracks them; installation
|
|
287
|
+
# clones each into the tree — never guessing a remote, always reading the
|
|
288
|
+
# registry. A present repo is fast-forwarded; a missing one cloned. The smithy
|
|
289
|
+
# engine (apps/smidja) is stamped from the cloned factory's templates, exactly
|
|
290
|
+
# as install.py does for a target repo.
|
|
291
|
+
step_apps() {
|
|
292
|
+
local reg="$HOARD/identity/projects.yaml"
|
|
293
|
+
if [ ! -r "$reg" ]; then
|
|
294
|
+
add apps SKIP "no registry — no app repos to pull ($reg)"
|
|
295
|
+
return 0
|
|
296
|
+
fi
|
|
297
|
+
# Parse blocks that carry `repo: apps/<path>` and a `git:` inline dict.
|
|
298
|
+
local entries
|
|
299
|
+
entries=$(awk '
|
|
300
|
+
/^ - id:/ { repo="" }
|
|
301
|
+
/^ repo: apps\// { repo=substr($2, 6) }
|
|
302
|
+
/^ git: / {
|
|
303
|
+
line=$0; host=""; owner=""; grepo=""
|
|
304
|
+
if (match(line, /host: [A-Za-z0-9._-]+/)) host=substr(line, RSTART+6, RLENGTH-6)
|
|
305
|
+
if (match(line, /owner: [A-Za-z0-9_-]+/)) owner=substr(line, RSTART+7, RLENGTH-7)
|
|
306
|
+
if (match(line, /repo: [A-Za-z0-9._-]+/)) grepo=substr(line, RSTART+6, RLENGTH-6)
|
|
307
|
+
if (repo != "" && owner != "" && grepo != "") print repo "|" host "|" owner "|" grepo
|
|
308
|
+
repo=""
|
|
309
|
+
}' "$reg")
|
|
310
|
+
if [ -z "$entries" ]; then
|
|
311
|
+
add apps OK "no apps/ projects registered — nothing to pull"
|
|
312
|
+
return 0
|
|
313
|
+
fi
|
|
314
|
+
local ok=0 skip=0 fail=0 missing=0
|
|
315
|
+
while IFS='|' read -r rel host owner grepo; do
|
|
316
|
+
[ -n "$rel" ] || continue
|
|
317
|
+
local path="$ROOT/apps/$rel"
|
|
318
|
+
if [ "$CHECK" = 1 ]; then
|
|
319
|
+
if [ -d "$path/.git" ]; then add apps OK "$rel present ($owner/$grepo)"; ok=$((ok+1));
|
|
320
|
+
elif [ -d "$path" ]; then add apps WARN "$rel present but not a git clone — re-install to replace"; skip=$((skip+1));
|
|
321
|
+
else add apps WARN "$rel absent — a full run clones $owner/$grepo"; missing=$((missing+1)); fi
|
|
322
|
+
continue
|
|
323
|
+
fi
|
|
324
|
+
if [ -d "$path/.git" ]; then
|
|
325
|
+
if (cd "$path" && git fetch origin 2>/dev/null && git merge --ff-only origin/main >/dev/null 2>&1); then add apps OK "$rel up to date ($owner/$grepo)"; ok=$((ok+1));
|
|
326
|
+
else add apps OK "$rel present ($owner/$grepo; pull refused — local changes?)"; skip=$((skip+1)); fi
|
|
327
|
+
elif [ -d "$path" ]; then
|
|
328
|
+
add apps WARN "$rel present but not a git clone — move $path aside and re-run"; skip=$((skip+1))
|
|
329
|
+
else
|
|
330
|
+
local url="https://github.com/$owner/$grepo.git"
|
|
331
|
+
if git clone -q "$url" "$path" 2>/dev/null; then add apps OK "$rel cloned ($owner/$grepo)"; ok=$((ok+1));
|
|
332
|
+
else add apps WARN "$rel clone failed — run: git clone $url $path"; fail=$((fail+1)); fi
|
|
333
|
+
fi
|
|
334
|
+
done <<< "$entries"
|
|
335
|
+
# The smithy engine: apps/smidja is stamped from the cloned factory's
|
|
336
|
+
# templates (the monorepo tracks neither). The config template follows.
|
|
337
|
+
local factory="$ROOT/apps/smidja-factory" eng="$ROOT/apps/smidja"
|
|
338
|
+
if [ "$CHECK" = 0 ] && [ -d "$factory/templates/smidja" ] && [ ! -e "$eng/smidja_modules" ]; then
|
|
339
|
+
mkdir -p "$eng"
|
|
340
|
+
cp -rn "$factory/templates/smidja/." "$eng/" 2>/dev/null
|
|
341
|
+
[ -f "$factory/templates/smidja.config.yaml" ] && { mkdir -p "$eng/smidja_smidja_config"; cp -n "$factory/templates/smidja.config.yaml" "$eng/smidja_smidja_config/smidja.config.yaml" 2>/dev/null; }
|
|
342
|
+
add apps OK "smidja engine stamped into apps/smidja"
|
|
343
|
+
fi
|
|
344
|
+
if [ "$fail" -gt 0 ]; then add apps WARN "$fail app clone(s) failed (offline?)"; fi
|
|
345
|
+
}
|
|
346
|
+
|
|
200
347
|
# ── 3. engines ───────────────────────────────────────────────────────────────
|
|
201
348
|
step_engines() {
|
|
202
349
|
[ "$SKIP_ENGINES" = 1 ] && { add engines SKIP "--skip-engines"; return; }
|
|
@@ -240,7 +387,6 @@ step_models() {
|
|
|
240
387
|
if [ ! -f "$HOME/.pi/agent/models.json" ]; then
|
|
241
388
|
"$SCRIPT_DIR/models-detect.sh" --write >/dev/null 2>&1 && add models OK "seeded ~/.pi/agent/models.json (was absent)"
|
|
242
389
|
fi
|
|
243
|
-
printf 'models: local-model testing is first-class — load the modeltesting skill; llama.cpp (llama-server/llama-swap) is fastest on the GPU; research your hardware best settings online; for coding use >= 80000 context.\n'
|
|
244
390
|
fi
|
|
245
391
|
if [ -x "$SCRIPT_DIR/model-hardware.sh" ]; then
|
|
246
392
|
"$SCRIPT_DIR/model-hardware.sh" >/dev/null 2>&1 && add models OK "profiled this machine (data/local-models.md; online research left for Brokk)"
|
|
@@ -323,35 +469,37 @@ step_omarchy() {
|
|
|
323
469
|
}
|
|
324
470
|
|
|
325
471
|
step_host() {
|
|
326
|
-
# Core host learning:
|
|
327
|
-
# The Omarchy-specific
|
|
328
|
-
#
|
|
329
|
-
# applied by step_omarchy; nothing
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
add host OK "host snapshot: ${learned}"
|
|
337
|
-
else
|
|
338
|
-
if [ -x "$SCRIPT_DIR/omarchy-sense.sh" ]; then
|
|
339
|
-
"$SCRIPT_DIR/omarchy-sense.sh" observe --quiet >/dev/null 2>&1 && learned=yes
|
|
340
|
-
fi
|
|
341
|
-
add host OK "host learnt=${learned}"
|
|
472
|
+
# Core host learning: sense THIS machine on EVERY host with the portable
|
|
473
|
+
# sensor (Rule 05). The Omarchy-specific RECORDING (omarchy-sense observe), the
|
|
474
|
+
# post-update hook, desktop placement, the plugin offer, and the desktop alarm
|
|
475
|
+
# channel belong to the Omarchy layer and are applied by step_omarchy; nothing
|
|
476
|
+
# here assumes Omarchy.
|
|
477
|
+
local seen="unknown"
|
|
478
|
+
if [ -x "$SCRIPT_DIR/host-sense.sh" ]; then
|
|
479
|
+
# host-sense TOON row 2 is: os, id, family, session, desktop
|
|
480
|
+
local row; row="$("$SCRIPT_DIR/host-sense.sh" 2>/dev/null | sed -n '2p')"
|
|
481
|
+
[ -n "$row" ] && seen="$(printf '%s' "$row" | sed -E 's/^[[:space:]]*"//; s/"[[:space:]]*$//; s/","/ \/ /g')"
|
|
342
482
|
fi
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
#
|
|
346
|
-
#
|
|
347
|
-
|
|
483
|
+
add host OK "host sensed: ${seen}"
|
|
484
|
+
|
|
485
|
+
# The operator's own agent setup, seeded from the tracked template into the
|
|
486
|
+
# home's settings dir — settings are the operator's, never the package's.
|
|
487
|
+
# Idempotent: the seeded file is private and is never overwritten once set.
|
|
488
|
+
# --check writes nothing, so it only reports. The template is code, so it is
|
|
489
|
+
# read from wherever the tree keeps it: `config/` in a clone (a symlink the
|
|
490
|
+
# npm tarball cannot carry), else `.agents/config/`.
|
|
491
|
+
if [ -e "$YMIR_SETTINGS_DIR/agents.yaml" ]; then
|
|
348
492
|
add agents-config OK "kept (private, never overwritten)"
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
493
|
+
return 0
|
|
494
|
+
fi
|
|
495
|
+
local tpl="$ROOT/config/agents.yaml.example"
|
|
496
|
+
[ -r "$tpl" ] || tpl="$ROOT/.agents/config/agents.yaml.example"
|
|
497
|
+
if [ "$CHECK" = 1 ]; then
|
|
498
|
+
add agents-config WARN "not seeded yet — a real run writes $YMIR_SETTINGS_DIR/agents.yaml"
|
|
499
|
+
elif [ -r "$tpl" ] && mkdir -p "$YMIR_SETTINGS_DIR" && cp "$tpl" "$YMIR_SETTINGS_DIR/agents.yaml" 2>/dev/null; then
|
|
500
|
+
add agents-config OK "seeded $YMIR_SETTINGS_DIR/agents.yaml from the template"
|
|
353
501
|
else
|
|
354
|
-
add agents-config WARN "could not seed
|
|
502
|
+
add agents-config WARN "could not seed $YMIR_SETTINGS_DIR/agents.yaml"
|
|
355
503
|
fi
|
|
356
504
|
}
|
|
357
505
|
|
|
@@ -398,7 +546,7 @@ step_smidja() {
|
|
|
398
546
|
if [ "$CHECK" = 1 ]; then
|
|
399
547
|
local dbok vizok
|
|
400
548
|
[ -f "$YMIR_HOME/smidja/smidja.db" ] && dbok=present || dbok=missing
|
|
401
|
-
[ -d "$
|
|
549
|
+
[ -n "${SMIDJA_VIZ:-}" ] && [ -d "$SMIDJA_VIZ/dist" ] && vizok=built || vizok=unbuilt
|
|
402
550
|
if [ "$dbok" = missing ]; then
|
|
403
551
|
add smidja WARN "smidja.db missing — a full run (without --check) creates it and seeds one bootstrap session"
|
|
404
552
|
else
|
|
@@ -414,7 +562,7 @@ step_smidja() {
|
|
|
414
562
|
else add smidja SKIP "no smidja-bootstrap.sh"; fi
|
|
415
563
|
# The visualizer API serves its UI from ./dist — without a build it answers
|
|
416
564
|
# the API but shows "No ./dist build found". Build it once when absent.
|
|
417
|
-
local viz="$
|
|
565
|
+
local viz="${SMIDJA_VIZ:-}"; [ -n "$viz" ] || return 0
|
|
418
566
|
[ -d "$viz" ] || return 0
|
|
419
567
|
if [ -d "$viz/dist" ]; then
|
|
420
568
|
add visualizer OK "UI built (served on :8437)"
|
|
@@ -487,12 +635,44 @@ step_gates() {
|
|
|
487
635
|
else
|
|
488
636
|
add gates WARN "hooks not installed — bin/secret-guard.sh --install && bin/changelog-guard.sh --install"
|
|
489
637
|
fi
|
|
638
|
+
# The home repo is where the private data lives, so its ward matters more
|
|
639
|
+
# than this repo's four. Report it separately so a dormant vault is visible.
|
|
640
|
+
local hh; hh="${YMIR_HOME:-$HOME/Documents/Ymir}/.git/hooks/pre-commit"
|
|
641
|
+
if [ -x "$hh" ]; then add hoard-gate OK "home pre-commit seated";
|
|
642
|
+
else add hoard-gate WARN "home hooks not installed — bin/hoard-guard.sh --install"; fi
|
|
490
643
|
return
|
|
491
644
|
fi
|
|
492
645
|
[ -x "$SCRIPT_DIR/secret-guard.sh" ] && "$SCRIPT_DIR/secret-guard.sh" --install >/dev/null 2>&1 || true
|
|
493
646
|
[ -x "$SCRIPT_DIR/changelog-guard.sh" ] && "$SCRIPT_DIR/changelog-guard.sh" --install >/dev/null 2>&1 || true
|
|
647
|
+
# Seat the ward on the PRIVATE home too — every install layer, every machine.
|
|
648
|
+
[ -x "$SCRIPT_DIR/hoard-guard.sh" ] && "$SCRIPT_DIR/hoard-guard.sh" --install >/dev/null 2>&1 || true
|
|
494
649
|
if [ -x "$pre_commit" ] && [ -x "$pre_push" ]; then add gates OK "pre-commit + pre-push installed"
|
|
495
650
|
else add gates WARN "could not write .git/hooks — gates are dormant"; fi
|
|
651
|
+
local hh; hh="${YMIR_HOME:-$HOME/Documents/Ymir}/.git/hooks/pre-commit"
|
|
652
|
+
if [ -x "$hh" ]; then add hoard-gate OK "home pre-commit seated";
|
|
653
|
+
else add hoard-gate WARN "home hooks not seated — bin/hoard-guard.sh --install"; fi
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
# ── 6c. desktop marks (the rune, the entry, the contract) ────────────────────
|
|
657
|
+
#
|
|
658
|
+
# Every app wears its OWN rune in the operator's desktop: the icon into the icon
|
|
659
|
+
# theme, the .desktop entry into their applications dir (so every app is
|
|
660
|
+
# dockable and pinnable), and the Ymir contract deployed into pi's agent home so
|
|
661
|
+
# every session - in ANY folder - loads Brokk. Idempotent, and it writes to the
|
|
662
|
+
# operator's real data dir, never a sandbox one.
|
|
663
|
+
step_marks() {
|
|
664
|
+
if [ ! -x "$SCRIPT_DIR/design-icon.sh" ]; then add marks SKIP "no design-icon.sh"; return; fi
|
|
665
|
+
if [ "$CHECK" = 1 ]; then
|
|
666
|
+
n=$(ls "$HOME/.local/share"/applications/ymir-*.desktop 2>/dev/null | wc -l | tr -d ' ')
|
|
667
|
+
add marks OK "$n desktop app marks installed"
|
|
668
|
+
return
|
|
669
|
+
fi
|
|
670
|
+
"$SCRIPT_DIR/design-icon.sh" mint --all >/dev/null 2>&1 || true
|
|
671
|
+
n=$("$SCRIPT_DIR/design-icon.sh" install 2>/dev/null | grep -c '"ymir-') || n=0
|
|
672
|
+
[ -r "$HOME/.pi/agent/AGENTS.md" ] || [ -d "$HOME/.pi/agent" ] && {
|
|
673
|
+
ln -sfn "$ROOT/AGENTS.md" "$HOME/.pi/agent/AGENTS.md" 2>/dev/null || true
|
|
674
|
+
}
|
|
675
|
+
add marks OK "$n app marks (rune icon + entry) · Ymir contract in the pi agent home"
|
|
496
676
|
}
|
|
497
677
|
|
|
498
678
|
# ── 7. services ──────────────────────────────────────────────────────────────
|
|
@@ -521,6 +701,22 @@ step_desktop() {
|
|
|
521
701
|
fi
|
|
522
702
|
# Placement is the Omarchy layer's job and has already run (step_omarchy runs
|
|
523
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
|
|
524
720
|
if "$ROOT/scripts/electron.sh" start --both >/dev/null 2>&1; then
|
|
525
721
|
add desktop OK "raised Hlidskjalf + Smíðja"
|
|
526
722
|
else
|
|
@@ -624,7 +820,11 @@ step_panes() {
|
|
|
624
820
|
# Ask before touching the machine; --check only previews and never asks.
|
|
625
821
|
[ "$CHECK" = 0 ] && confirm_install
|
|
626
822
|
|
|
627
|
-
step_panes; step_prereqs; step_tree; 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;
|
|
823
|
+
step_panes; step_prereqs; step_home; step_tree; step_apps; step_engines; step_models; step_hermes; step_sessrumnir; step_backend; step_host; step_sandbox; step_memory; step_smidja; step_spa; step_omarchy; step_loaders; step_gates; step_marks
|
|
824
|
+
# Migrations MOVE private data — that is a write, and `--check` promises none.
|
|
825
|
+
# Only a real run heals the home forward; the preview leaves it untouched.
|
|
826
|
+
if [ "$CHECK" = 0 ]; then bin/ymir-migrate.sh apply >/dev/null 2>&1 || true; fi
|
|
827
|
+
step_auth; step_invite; step_register
|
|
628
828
|
[ "$CHECK" = 0 ] && step_services
|
|
629
829
|
[ "$CHECK" = 0 ] && step_desktop
|
|
630
830
|
[ "$CHECK" = 0 ] && step_validate
|