@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.
Files changed (125) hide show
  1. package/.agents/agents/huginn-researcher.md +1 -1
  2. package/.agents/agents/kvasir-scout.md +1 -1
  3. package/.agents/agents/sindri-developer.md +1 -1
  4. package/.agents/backend/README.md +74 -0
  5. package/.agents/backend/model-bridge.py +74 -4
  6. package/.agents/config/cron.yaml +2 -0
  7. package/.agents/config/eindri-dispatch.json +6 -6
  8. package/.agents/migrations/0003-private-data-separation.sh +11 -1
  9. package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
  10. package/.agents/skills/README.md +1 -1
  11. package/.agents/skills/galdr-ymirsystem/SKILL.md +2 -1
  12. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +73 -10
  13. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +44 -1
  14. package/.agents/skills/galdr-ymirsystem/assets/installation.md +290 -24
  15. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +3 -1
  16. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +39 -1
  17. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +4 -0
  18. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +7 -2
  19. package/.agents/skills/galdr-ymirsystem/assets/registry.md +1 -1
  20. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +2 -2
  21. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +16 -11
  22. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +147 -4
  23. package/.agents/skills/groa-update/SKILL.md +20 -0
  24. package/.agents/skills/hamr-adapters/SKILL.md +2 -2
  25. package/.agents/skills/lifecycle/smoke_test.sh +7 -2
  26. package/.agents/skills/ymir-host/SKILL.md +2 -1
  27. package/.agents/skills/ymir-host/assets/install.md +69 -0
  28. package/AGENTS.md +78 -34
  29. package/CHANGELOG.md +1362 -1
  30. package/README.md +1 -1
  31. package/RULES/02-agents.md +2 -2
  32. package/RULES/04-hoard.md +32 -0
  33. package/RULES/06-append-only.md +50 -0
  34. package/RULES/07-config.md +56 -0
  35. package/RULES/09-electron.md +58 -0
  36. package/RULES/README.md +2 -0
  37. package/bin/a2a-mcp.sh +42 -7
  38. package/bin/agents-config.sh +30 -7
  39. package/bin/apodex-smoke-test.sh +14 -1
  40. package/bin/bifrost-bridge.sh +25 -6
  41. package/bin/bootstrap-macos.sh +6 -1
  42. package/bin/brokk-send.sh +14 -1
  43. package/bin/changelog-assemble.sh +154 -0
  44. package/bin/changelog-guard.sh +39 -7
  45. package/bin/crash-sense.sh +14 -1
  46. package/bin/design-check.sh +80 -0
  47. package/bin/design-icon.sh +156 -0
  48. package/bin/docs-guard.sh +2 -2
  49. package/bin/editor-place.sh +33 -15
  50. package/bin/eindri-acclaim.sh +14 -1
  51. package/bin/eindri-seat.sh +83 -0
  52. package/bin/eindri-seen.sh +14 -1
  53. package/bin/eindri-start.sh +14 -1
  54. package/bin/einherjar-spawn.sh +9 -3
  55. package/bin/eir-doctor.sh +89 -1
  56. package/bin/electron-lib.sh +34 -0
  57. package/bin/feature-inventory.sh +73 -0
  58. package/bin/fleet-apply.sh +15 -2
  59. package/bin/github-deploy.sh +14 -1
  60. package/bin/gjallarhorn-expose.sh +43 -9
  61. package/bin/gjallarhorn-notify.sh +14 -1
  62. package/bin/gjallarhorn-purge.sh +14 -1
  63. package/bin/gjallarhorn-tunnel.sh +16 -3
  64. package/bin/groa-update.sh +9 -1
  65. package/bin/hall-snapshot.sh +21 -2
  66. package/bin/herdr-run.sh +23 -2
  67. package/bin/hlidskjalf-agents.sh +172 -0
  68. package/bin/hlidskjalf-usage.sh +196 -0
  69. package/bin/hoard-guard.sh +135 -0
  70. package/bin/hoard-lib.sh +91 -11
  71. package/bin/hodd.sh +28 -4
  72. package/bin/huginn-research-worker.sh +14 -1
  73. package/bin/local-model-lock.sh +22 -3
  74. package/bin/mimir-bridge.py +33 -0
  75. package/bin/mimir-bridge.sh +14 -1
  76. package/bin/mimir-reflect.sh +27 -0
  77. package/bin/mjollnir-webhook.sh +14 -1
  78. package/bin/mjollnir.sh +22 -3
  79. package/bin/model-hardware.sh +17 -3
  80. package/bin/model-resolve.sh +14 -1
  81. package/bin/nornir-job-hall-snapshot.sh +28 -0
  82. package/bin/nornir-job-nsr-compliance.sh +54 -0
  83. package/bin/npm-publish.sh +136 -0
  84. package/bin/omarchy-install.sh +18 -5
  85. package/bin/omarchy-plugins.sh +15 -2
  86. package/bin/omarchy-sense.sh +14 -1
  87. package/bin/pi-model-agnostic.sh +71 -0
  88. package/bin/private-guard.sh +20 -0
  89. package/bin/project-git.sh +18 -4
  90. package/bin/public-guard.sh +3 -2
  91. package/bin/sessrumnir-ensure.sh +1 -1
  92. package/bin/sessrumnir.sh +16 -3
  93. package/bin/smidja-board.sh +136 -0
  94. package/bin/smidja-bootstrap.sh +5 -1
  95. package/bin/smidja-lib.sh +49 -0
  96. package/bin/syn-asset-pretool-check.sh +14 -1
  97. package/bin/syn-spawn-pretool-check.sh +58 -0
  98. package/bin/tailscale-sync.sh +15 -2
  99. package/bin/telegram-bot.sh +14 -1
  100. package/bin/ui-truth-check.sh +75 -0
  101. package/bin/valknut-load.sh +42 -4
  102. package/bin/wedge-notify.sh +14 -1
  103. package/bin/workspace-rag.sh +14 -1
  104. package/bin/ymir-install.sh +257 -57
  105. package/bin/ymir-plan.sh +390 -0
  106. package/bin/ymir-say.sh +14 -1
  107. package/bin/ymir-setup-auth.sh +12 -1
  108. package/bin/ymir-style.sh +108 -0
  109. package/bin/ymir-validate.sh +64 -11
  110. package/bin/ymir.js +82 -34
  111. package/docs/design.md +2 -2
  112. package/docs/lore.md +5 -1
  113. package/docs/runbooks/agents.md +1 -1
  114. package/package.json +13 -3
  115. package/scripts/electron.sh +74 -15
  116. package/scripts/lower.sh +34 -0
  117. package/scripts/raise.sh +42 -0
  118. package/scripts/start.sh +65 -3
  119. package/.agents/config/agents.yaml +0 -50
  120. package/.agents/config/tailscale-sync.yaml +0 -27
  121. package/.agents/memory/kaia.engram +0 -0
  122. package/.agents/memory/kaia.engram-shm +0 -0
  123. package/.agents/memory/kaia.engram-wal +0 -0
  124. package/.agents/memory/well/episodes.jsonl +0 -367
  125. package/.agents/memory/well/workspace.jsonl +0 -27
package/bin/eir-doctor.sh CHANGED
@@ -18,6 +18,11 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
18
18
  YMIR_HOME="${YMIR_HOME:-$HOME/Documents/Ymir}"
19
19
  STATE="${BROKK_STATE_OVERRIDE:-$YMIR_HOME/state}"
20
20
 
21
+ # The cloth: colour and marks for the human reading this report; the TOON rows on
22
+ # stdout stay the data (bin/ymir-style.sh).
23
+ if [ -z "${YMIR_STYLE_LOADED:-}" ]; then . "$SCRIPT_DIR/ymir-style.sh"; YMIR_STYLE_LOADED=1; fi
24
+ style_init
25
+
21
26
  case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help|"") sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
22
27
  ACTION="${1:-check}"; shift || true
23
28
 
@@ -25,7 +30,7 @@ have() { command -v "$1" >/dev/null 2>&1; }
25
30
  say_ok() { return 0; }
26
31
 
27
32
  # Each surface: `s_<name>` = healthy? (exit 0), `f_<name>` = the repair.
28
- SURFACES=(floors herdr a2abridge hermes sessrumnir well mcp lock migrations)
33
+ SURFACES=(floors herdr a2abridge hermes sessrumnir shells well mcp lock migrations hoard)
29
34
 
30
35
  s_floors() { [ -x "$SCRIPT_DIR/prereq-ensure.sh" ] && "$SCRIPT_DIR/prereq-ensure.sh" status >/dev/null 2>&1; }
31
36
  f_floors() { "$SCRIPT_DIR/prereq-ensure.sh" ensure --install >/dev/null 2>&1; }
@@ -37,6 +42,34 @@ s_hermes() { [ -x "$SCRIPT_DIR/hermes-ensure.sh" ] && "$SCRIPT_DIR/hermes-ens
37
42
  f_hermes() { "$SCRIPT_DIR/hermes-ensure.sh" ensure --install >/dev/null 2>&1; }
38
43
  s_sessrumnir(){ [ -x "$SCRIPT_DIR/sessrumnir-ensure.sh" ] && "$SCRIPT_DIR/sessrumnir-ensure.sh" status >/dev/null 2>&1; }
39
44
  f_sessrumnir(){ "$SCRIPT_DIR/sessrumnir-ensure.sh" ensure --install >/dev/null 2>&1; }
45
+ # The desktop shells: absent is healthy (the web surfaces stand alone), PARTIAL is
46
+ # not — npm gated the Electron postinstall, so the window cannot open while every
47
+ # build still passes (bin/electron-lib.sh).
48
+ shell_dirs() {
49
+ local a d
50
+ for a in hlidskjalf odrerir sessrumnir; do
51
+ for d in "$ROOT/apps/$a" "$ROOT/node_modules/@zerwiz/$a"; do
52
+ [ -d "$d" ] && { printf '%s\n' "$d"; break; }
53
+ done
54
+ done
55
+ }
56
+ s_shells() {
57
+ local d state
58
+ while IFS= read -r d; do
59
+ state="$(electron_runtime_state "$d" 2>/dev/null || true)"
60
+ [ "$state" = partial ] && return 1
61
+ done < <(shell_dirs)
62
+ return 0
63
+ }
64
+ f_shells() {
65
+ printf 'eir: the shells need YOUR hand — npm gated the runtime download:\n' >&2
66
+ electron_remedy >&2
67
+ return 1
68
+ }
69
+ if [ -z "${YMIR_ELECTRON_LIB_LOADED:-}" ] && [ -r "$SCRIPT_DIR/electron-lib.sh" ]; then
70
+ . "$SCRIPT_DIR/electron-lib.sh"; YMIR_ELECTRON_LIB_LOADED=1
71
+ fi
72
+
40
73
  s_well() { [ -x "$SCRIPT_DIR/mimir.sh" ] && "$SCRIPT_DIR/mimir.sh" health >/dev/null 2>&1; }
41
74
  f_well() { "$SCRIPT_DIR/mimir.sh" start >/dev/null 2>&1; }
42
75
  # MCP: both A2A servers wired into opencode + pi.
@@ -69,6 +102,51 @@ f_lock() {
69
102
  }
70
103
  s_migrations(){ [ ! -x "$SCRIPT_DIR/ymir-migrate.sh" ] || "$SCRIPT_DIR/ymir-migrate.sh" status >/dev/null 2>&1; }
71
104
  f_migrations(){ [ -x "$SCRIPT_DIR/ymir-migrate.sh" ] && "$SCRIPT_DIR/ymir-migrate.sh" apply >/dev/null 2>&1; }
105
+ # Hoard: private data sits under the hoard, and .ymir-layout.yaml tells the truth.
106
+ # Two failures this catches: (1) a declared layout path that does not exist — a
107
+ # stale map is what lets private work land outside the hoard; (2) a flat
108
+ # $YMIR_HOME/{identity,data,docs,secrets,tenants} duplicate beside hodd/ — the
109
+ # drift that RULES/04-hoard.md (correction 2026-09-17) forbids.
110
+ _hoard_root() { local r; if [ -x "$SCRIPT_DIR/hoard-lib.sh" ]; then . "$SCRIPT_DIR/hoard-lib.sh"; hoard_root r; printf '%s' "$r"; else printf '%s' "${YMIR_HOARD:-$YMIR_HOME/hodd}"; fi; }
111
+ s_hoard() {
112
+ local h; h="$(_hoard_root)"
113
+ [ -d "$h" ] || return 1
114
+ local lay="$YMIR_HOME/.ymir-layout.yaml" k p
115
+ if [ -f "$lay" ]; then
116
+ for k in config secrets identity workspaces memory smidja state data; do
117
+ p="$(sed -nE "s/^ $k: \"([^\"]+)\".*/\1/p" "$lay" | head -1)"
118
+ [ -n "$p" ] && [ ! -d "$p" ] && return 1
119
+ done
120
+ fi
121
+ for k in identity data docs secrets tenants; do
122
+ [ -d "$YMIR_HOME/$k" ] && return 1
123
+ done
124
+ return 0
125
+ }
126
+ f_hoard() {
127
+ local h; h="$(_hoard_root)"
128
+ mkdir -p "$h" 2>/dev/null || return 1
129
+ # A flat duplicate beside hodd/ is the drift: merge it in, never clobber.
130
+ local k
131
+ for k in identity data docs secrets tenants; do
132
+ local src="$YMIR_HOME/$k"
133
+ [ -d "$src" ] || continue
134
+ mkdir -p "$h/$k" 2>/dev/null || continue
135
+ cp -an "$src/." "$h/$k/" 2>/dev/null || true
136
+ rm -rf "$src" 2>/dev/null || true
137
+ done
138
+ # A layout entry naming a nonexistent dir is repointed at the hoard.
139
+ local lay="$YMIR_HOME/.ymir-layout.yaml" k2 p
140
+ if [ -f "$lay" ] && [ -d "$h" ]; then
141
+ for k2 in identity data secrets docs tenants; do
142
+ p="$(sed -nE "s/^ $k2: \"([^\"]+)\".*/\1/p" "$lay" | head -1)"
143
+ if [ -n "$p" ] && [ ! -d "$p" ]; then
144
+ sed -i "s|^ $k2: .*| $k2: \"$h/$k2\"|" "$lay"
145
+ fi
146
+ done
147
+ fi
148
+ s_hoard
149
+ }
72
150
 
73
151
  detail() { # <name> -> one short fact
74
152
  case "$1" in
@@ -81,6 +159,8 @@ detail() { # <name> -> one short fact
81
159
  mcp) "echo 'a2abridge + engram'" ;;
82
160
  lock) "cat $STATE/.lock 2>/dev/null | tr -d '[:space:]' | sed 's/^/pid /' || echo none" ;;
83
161
  migrations)"echo 'structure'" ;;
162
+ shells) 'shell_dirs | while IFS= read -r d; do printf "%s %s; " "$(basename "$d")" "$(electron_runtime_state "$d" 2>/dev/null || true)"; done' ;;
163
+ hoard) "printf 'hoard %s' \"$(_hoard_root)\" ; [ -d \"$YMIR_HOME/identity\" ] && printf ' +flat-duplicate' ; printf '\\n'" ;;
84
164
  esac
85
165
  }
86
166
 
@@ -105,6 +185,14 @@ done
105
185
  printf 'eir[1]{action,root,broken}:\n "%s","%s",%s\n' "$ACTION" "$ROOT" "$broken"
106
186
  printf 'health[%d]{surface,state,detail}:\n' "$count"
107
187
  printf '%b' "$rows"
188
+ # For the eye, on stderr: marks and colour; for the pipe, the TOON above.
189
+ if [ -t 2 ]; then
190
+ printf '\n' >&2
191
+ while IFS='|' read -r surface state detail; do
192
+ [ -n "$surface" ] || continue
193
+ style_line "$state" "$surface" "$detail"
194
+ done <<<"$(printf '%b' "$rows" | sed 's/^ //; s/"//g; s/,/|/; s/,/|/')"
195
+ fi
108
196
  if [ "$broken" != 0 ]; then
109
197
  printf 'eir: %s surface(s) need mending — run: bin/eir-doctor.sh fix\n' "$broken"
110
198
  exit 1
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # electron-lib.sh — is a desktop shell's runtime actually there?
3
+ #
4
+ # The trap this exists for: npm blocks a package's install scripts by default
5
+ # (npm 11.16+ warns, npm 12 refuses), and Electron's postinstall is what downloads
6
+ # its ~100 MB runtime. Without it the install still reports SUCCESS and the web app
7
+ # still builds — `node_modules/electron/dist/` is simply partial and `path.txt` is
8
+ # never written. So the shell fails to launch while every check says healthy, and
9
+ # nobody is told.
10
+ #
11
+ # Ymir refuses that silence: a shell is only "ready" when the runtime verifies.
12
+ # Source-safe, functions only.
13
+ #
14
+ # electron_runtime_state <app-dir> ok | partial | absent (exit 0 only for ok)
15
+ # electron_remedy the exact command that mends a partial one
16
+ set -u
17
+
18
+ electron_runtime_state() { # <app-dir>
19
+ local dir="${1-}"
20
+ [ -n "$dir" ] && [ -d "$dir" ] || { printf 'absent'; return 1; }
21
+ [ -d "$dir/node_modules/electron" ] || { printf 'absent'; return 1; }
22
+ # path.txt is written by the postinstall; dist/electron is what it points at.
23
+ if [ -f "$dir/node_modules/electron/path.txt" ] \
24
+ && [ -x "$dir/node_modules/electron/dist/electron" ]; then
25
+ printf 'ok'; return 0
26
+ fi
27
+ printf 'partial'; return 1
28
+ }
29
+
30
+ electron_remedy() {
31
+ printf 'the runtime download was skipped by npm — approve and rebuild:\n'
32
+ printf ' npm install-scripts approve electron && npm rebuild electron\n'
33
+ printf 'yonder: bin/ymir-install.sh --no-desktop if the web surfaces are enough'
34
+ }
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env bash
2
+ # feature-inventory.sh — every feature the system CLAIMS, in one checklist.
3
+ #
4
+ # "Every single feature must work" is a hope until there is a list. This builds
5
+ # the list from the tree itself rather than from anyone's memory: every tool, job,
6
+ # skill, agent, app, workflow, service and registered project. It asserts nothing
7
+ # about whether they work — that is the verification pass, and it needs this
8
+ # inventory to exist first.
9
+ #
10
+ # bin/feature-inventory.sh # the inventory, as TOON
11
+ # bin/feature-inventory.sh --counts # just the tallies
12
+ # bin/feature-inventory.sh --verify-plan # the inventory as a checklist to fill in
13
+ set -u
14
+
15
+ VERSION="1.0.0"
16
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
18
+ HOARD="${YMIR_HOARD:-${YMIR_HOME:-$HOME/Documents/Ymir}/hodd}"
19
+
20
+ case "${1-}" in
21
+ -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
22
+ -h|--help|"") sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
23
+ esac
24
+ MODE="${1:-all}"
25
+
26
+ n_tools=$(find "$ROOT/bin" -maxdepth 1 -type f \( -name '*.sh' -o -name '*.py' -o -name '*.js' \) 2>/dev/null | wc -l | tr -d ' ')
27
+ n_jobs=$(ls "$ROOT"/bin/nornir-job-*.sh 2>/dev/null | wc -l | tr -d ' ')
28
+ n_skills=$(find -L "$ROOT/.agents/skills" -mindepth 2 -maxdepth 2 -name SKILL.md 2>/dev/null | wc -l | tr -d ' ')
29
+ n_agents=$(ls "$ROOT"/.agents/agents/*.md 2>/dev/null | wc -l | tr -d ' ')
30
+ n_workflows=$(ls "$ROOT"/.github/workflows/*.yml 2>/dev/null | wc -l | tr -d ' ')
31
+ n_apps=$(find "$ROOT/apps" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
32
+ n_migrations=$(ls "$ROOT"/.agents/migrations/*.sh 2>/dev/null | wc -l | tr -d ' ')
33
+ n_projects=0
34
+ [ -r "$HOARD/identity/projects.yaml" ] && n_projects=$(grep -c '^ - id:' "$HOARD/identity/projects.yaml" 2>/dev/null | tr -d ' ')
35
+
36
+ if [ "$MODE" = "--counts" ]; then
37
+ printf 'features[8]{kind,count}:\n'
38
+ printf ' "tools (bin/)",%s\n' "$n_tools"
39
+ printf ' "nornir jobs",%s\n' "$n_jobs"
40
+ printf ' "skills",%s\n' "$n_skills"
41
+ printf ' "agents",%s\n' "$n_agents"
42
+ printf ' "workflows (CI)",%s\n' "$n_workflows"
43
+ printf ' "apps",%s\n' "$n_apps"
44
+ printf ' "migrations",%s\n' "$n_migrations"
45
+ printf ' "registered projects",%s\n' "$n_projects"
46
+ exit 0
47
+ fi
48
+
49
+ # The checklist: one row per thing that must be proven.
50
+ printf 'inventory[8]{kind,count,how_proven}:\n'
51
+ printf ' "tools (bin/)",%s,"bash -n + --version/--help runs, exit 0"\n' "$n_tools"
52
+ printf ' "nornir jobs",%s,"one run each, --check or dry-run, writes as documented"\n' "$n_jobs"
53
+ printf ' "skills",%s,"its scripts run; its asset paths resolve (skillindex gate)"\n' "$n_skills"
54
+ printf ' "agents",%s,"bound into all five harnesses (harnesses gate) + a dispatch"\n' "$n_agents"
55
+ printf ' "workflows (CI)",%s,"YAML parses + the job runs green on the branch"\n' "$n_workflows"
56
+ printf ' "apps",%s,"builds, serves, wears its rune, installs its entry, opens in Electron"\n' "$n_apps"
57
+ printf ' "migrations",%s,"idempotent; re-run leaves the home unchanged"\n' "$n_migrations"
58
+ printf ' "registered projects",%s,"gh resolves the repo; PR ledger reports its PRs"\n' "$n_projects"
59
+
60
+ printf 'tools[%s]{tool}:\n' "$n_tools"
61
+ for f in "$ROOT"/bin/*; do
62
+ [ -f "$f" ] || continue
63
+ case "$f" in *.sh|*.py|*.js) printf ' "%s"\n' "$(basename "$f")" ;; esac
64
+ done
65
+
66
+ printf 'jobs[%s]{job}:\n' "$n_jobs"
67
+ for f in "$ROOT"/bin/nornir-job-*.sh; do [ -f "$f" ] && printf ' "%s"\n' "$(basename "$f")"; done
68
+
69
+ printf 'apps[%s]{app}:\n' "$n_apps"
70
+ for d in "$ROOT"/apps/*/; do [ -d "$d" ] && printf ' "%s"\n' "$(basename "$d")"; done
71
+
72
+ printf 'skills[%s]{skill,owner}:\n' "$n_skills"
73
+ grep -oE '^ "[a-z0-9-]+","[^"]*","[a-z]+"' "$ROOT/.agents/skills/README.md" 2>/dev/null | sed 's/^ //' || true
@@ -14,8 +14,21 @@ set -u
14
14
  VERSION="1.0.0"
15
15
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
16
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
17
- REG="${BROKK_EINDRI_HOMES:-$ROOT/data/eindri-homes.md}"
18
- PREFS="${BROKK_FLEET_PREFS:-$ROOT/data/fleet.md}"
17
+
18
+ # The roots that live OUTSIDE the code tree: this machine's records and the
19
+ # runtime state belong to the home the operator chose at installation, never in
20
+ # the tree — a packaged install replaces its tree on upgrade (Rule 04).
21
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
22
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
24
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
25
+ done
26
+ unset _yr _yc
27
+ fi
28
+ hoard_state_dir YMIR_STATE_DIR
29
+ hoard_data_dir YMIR_DATA_DIR
30
+ REG="${BROKK_EINDRI_HOMES:-$YMIR_DATA_DIR/eindri-homes.md}"
31
+ PREFS="${BROKK_FLEET_PREFS:-$YMIR_DATA_DIR/fleet.md}"
19
32
  CHECK=0
20
33
 
21
34
  case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
@@ -16,7 +16,20 @@ 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
- ENV_FILE="${BROKK_ENV_FILE:-$ROOT/.env.local}"
19
+
20
+ # The operator's settings and secrets live in the home they chose, never in the
21
+ # code tree — a packaged install replaces its tree on upgrade, and a credential
22
+ # must never sit in a tree that ships (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_settings_dir YMIR_SETTINGS_DIR
31
+ hoard_local_env YMIR_ENV_FILE
32
+ ENV_FILE="${BROKK_ENV_FILE:-$YMIR_ENV_FILE}"
20
33
  SECRETS="NETLIFY_AUTH_TOKEN NETLIFY_SITE_ID VERCEL_TOKEN GITHUB_TOKEN TELEGRAM_BOT_TOKEN"
21
34
 
22
35
  case "${1-}" in
@@ -17,6 +17,19 @@ set -u
17
17
  VERSION="1.0.0"
18
18
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
19
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
20
+
21
+ # The roots that live OUTSIDE the code tree: this machine's records and the
22
+ # runtime state belong to the home the operator chose at installation, never in
23
+ # the tree — a packaged install replaces its tree on upgrade (Rule 04).
24
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
25
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
27
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
28
+ done
29
+ unset _yr _yc
30
+ fi
31
+ hoard_state_dir YMIR_STATE_DIR
32
+ hoard_data_dir YMIR_DATA_DIR
20
33
  TUNNEL="${YMIR_TUNNEL_NAME:-ymir}"
21
34
  DOMAIN="${YMIR_TUNNEL_DOMAIN:-zerwiz.org}"
22
35
  SUFFIX="${YMIR_TUNNEL_SUFFIX:-dell}"
@@ -31,13 +44,16 @@ case "${1:-plan}" in
31
44
  esac
32
45
  ACTION="${1:-plan}"
33
46
 
34
- # host_key port app-path
47
+ # Every public hostname points at the GATE (:3889), never at an app's own port:
48
+ # the gate holds the login and proxies on by host, so the web door is locked for
49
+ # every app. An app port here would be a hole straight past the login.
50
+ GATE_PORT="${HLIDSKJALF_API_PORT:-3889}"
35
51
  APPS=(
36
- "ymir:$(( ${#SUFFIX} ? 3889 : 3889 ))"
37
- "hlidskjalf:3889"
38
- "odrerir:4322"
39
- "smidja:8437"
40
- "smidjafactory:8437"
52
+ "ymir:$GATE_PORT"
53
+ "hlidskjalf:$GATE_PORT"
54
+ "odrerir:$GATE_PORT"
55
+ "smidja:$GATE_PORT"
56
+ "smidjafactory:$GATE_PORT"
41
57
  )
42
58
 
43
59
  if [ -z "$SUFFIX" ]; then
@@ -71,7 +87,9 @@ mkdir -p "$(dirname "$CONFIG")"
71
87
  {
72
88
  printf '# Gjallarhorn — this machine (%s). Generated by bin/gjallarhorn-expose.sh.\n' "$SUFFIX"
73
89
  printf 'tunnel: %s\n' "$TUNNEL"
74
- printf 'credentials-file: %s/.cloudflared/%s.json\n' "$HOME" "$TUNNEL"
90
+ # No credentials-file line: cloudflared resolves <UUID>.json for a named
91
+ # tunnel itself. Guessing the filename (ymir.json) is what made it refuse to
92
+ # start, and a tunnel that will not start answers 530 to the world.
75
93
  printf 'ingress:\n'
76
94
  for row in "${APPS[@]}"; do
77
95
  app="${row%%:*}"; port="${row##*:}"
@@ -81,16 +99,31 @@ mkdir -p "$(dirname "$CONFIG")"
81
99
  } >"$CONFIG"
82
100
 
83
101
  # 2. the routes (idempotent: an existing record is left alone)
102
+ # Route by the tunnel's own UUID, never by its name. `route dns <name> <host>`
103
+ # resolves the name through the account certificate, which can point at a
104
+ # DIFFERENT (even deleted) tunnel: every hostname then answers 530 while the
105
+ # named tunnel runs happily beside it. Resolve the UUID first, then route.
106
+ TUNNEL_ID="$(cloudflared tunnel info "$TUNNEL" 2>/dev/null | grep -m1 -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')"
107
+ [ -n "$TUNNEL_ID" ] || TUNNEL_ID="$TUNNEL"
84
108
  made=0
85
109
  for row in "${APPS[@]}"; do
86
110
  app="${row%%:*}"
87
- cloudflared tunnel route dns "$TUNNEL" "$app$SUFFIX.$DOMAIN" >/dev/null 2>&1 && made=$((made+1))
111
+ cloudflared tunnel route dns --overwrite-dns "$TUNNEL_ID" "$app$SUFFIX.$DOMAIN" >/dev/null 2>&1 && made=$((made+1))
88
112
  done
89
113
 
114
+ # 2b. the gate must know which host belongs to which app (it routes by host)
115
+ HOSTMAP="$YMIR_STATE_DIR/gjallarhorn-hosts.env"
116
+ {
117
+ printf '# Written by bin/gjallarhorn-expose.sh — the gate routes these hosts.\n'
118
+ printf 'YMIR_PRIMARY_HOST=ymir%s.%s\n' "$SUFFIX" "$DOMAIN"
119
+ printf 'SMIDJA_HOST=smidja%s.%s\n' "$SUFFIX" "$DOMAIN"
120
+ printf 'ODRERIR_HOST=odrerir%s.%s\n' "$SUFFIX" "$DOMAIN"
121
+ } >"$HOSTMAP"
122
+
90
123
  # 3. raise it
91
124
  started="already"
92
125
  if ! pgrep -f "cloudflared.*tunnel.*--config $CONFIG" >/dev/null 2>&1; then
93
- nohup cloudflared tunnel --config "$CONFIG" run "$TUNNEL" >"$ROOT/state/gjallarhorn.log" 2>&1 &
126
+ nohup cloudflared tunnel --config "$CONFIG" run "$TUNNEL" >"$YMIR_STATE_DIR/gjallarhorn.log" 2>&1 &
94
127
  started="pid $!"
95
128
  sleep 4
96
129
  fi
@@ -98,5 +131,6 @@ fi
98
131
  printf 'gjallarhorn_apply[3]{what,detail,count}:\n'
99
132
  printf ' "config","%s","%s"\n' "${CONFIG#"$HOME"/}" "${#APPS[@]}"
100
133
  printf ' "routes","%s%s.%s","%s"\n' "" "$SUFFIX" "$DOMAIN" "$made"
134
+ printf ' "hostmap","%s","the gate routes by host"\n' "${HOSTMAP#"$ROOT"/}"
101
135
  printf ' "tunnel","%s","%s"\n' "$started" "$TUNNEL"
102
136
  plan
@@ -13,7 +13,20 @@ set -u
13
13
  VERSION="1.0.0"
14
14
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
15
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
16
- ENV_FILE="${BROKK_ENV_FILE:-$ROOT/.env.local}"
16
+
17
+ # The operator's settings and secrets live in the home they chose, never in the
18
+ # code tree — a packaged install replaces its tree on upgrade, and a credential
19
+ # must never sit in a tree that ships (Rule 04).
20
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
21
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
23
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
24
+ done
25
+ unset _yr _yc
26
+ fi
27
+ hoard_settings_dir YMIR_SETTINGS_DIR
28
+ hoard_local_env YMIR_ENV_FILE
29
+ ENV_FILE="${BROKK_ENV_FILE:-$YMIR_ENV_FILE}"
17
30
 
18
31
  usage() { sed -n '2,11p' "$0" | sed 's/^# \{0,1\}//'; }
19
32
 
@@ -11,7 +11,20 @@ set -u
11
11
  VERSION="1.0.0"
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
14
- ENV_FILE="${BROKK_ENV_FILE:-$ROOT/.env.local}"
14
+
15
+ # The operator's settings and secrets live in the home they chose, never in the
16
+ # code tree — a packaged install replaces its tree on upgrade, and a credential
17
+ # must never sit in a tree that ships (Rule 04).
18
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
19
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
21
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
22
+ done
23
+ unset _yr _yc
24
+ fi
25
+ hoard_settings_dir YMIR_SETTINGS_DIR
26
+ hoard_local_env YMIR_ENV_FILE
27
+ ENV_FILE="${BROKK_ENV_FILE:-$YMIR_ENV_FILE}"
15
28
  DOMAIN="${YMIR_DOMAIN:-${YMIR_TUNNEL_HOST:-}}"
16
29
  [ -n "$DOMAIN" ] || { printf 'gjallarhorn-purge: set YMIR_DOMAIN (or YMIR_TUNNEL_HOST)\n' >&2; exit 2; }
17
30
  ZONE_NAME="${CLOUDFLARE_ZONE_NAME:-}"
@@ -19,10 +19,23 @@ fi
19
19
  VERSION="1.0.0"
20
20
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
21
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
22
+
23
+ # The roots that live OUTSIDE the code tree: this machine's records and the
24
+ # runtime state belong to the home the operator chose at installation, never in
25
+ # the tree — a packaged install replaces its tree on upgrade (Rule 04).
26
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
27
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
29
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
30
+ done
31
+ unset _yr _yc
32
+ fi
33
+ hoard_state_dir YMIR_STATE_DIR
34
+ hoard_data_dir YMIR_DATA_DIR
22
35
  CONFIG="${YMIR_TUNNEL_CONFIG:-$HOME/.cloudflared/config-ymir.yml}"
23
36
  TUNNEL="${YMIR_TUNNEL_NAME:-ymir}"
24
- PID_FILE="$ROOT/state/gjallarhorn.pid"
25
- LOG_FILE="$ROOT/state/gjallarhorn.log"
37
+ PID_FILE="$YMIR_STATE_DIR/gjallarhorn.pid"
38
+ LOG_FILE="$YMIR_STATE_DIR/gjallarhorn.log"
26
39
 
27
40
  case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help|"") sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
28
41
  ACTION="${1:-status}"; shift || true
@@ -59,7 +72,7 @@ esac
59
72
 
60
73
  command -v cloudflared >/dev/null 2>&1 || { printf 'error: cloudflared not found\nhelp: install cloudflared\n' >&2; exit 1; }
61
74
  [ -r "$CONFIG" ] || { printf 'error: tunnel config not found: %s\nhelp: cp midgard/infrastructure/ingress/cloudflared-ymir.yml ~/.cloudflared/config-ymir.yml\n' "$CONFIG" >&2; exit 1; }
62
- mkdir -p "$ROOT/state"
75
+ mkdir -p "$YMIR_STATE_DIR"
63
76
  if running; then printf 'gjallarhorn[1]{state,host}:\n "already up","%s"\n' "$HOST"; exit 0; fi
64
77
 
65
78
  nohup cloudflared tunnel --config "$CONFIG" run "$TUNNEL" >"$LOG_FILE" 2>&1 &
@@ -25,7 +25,15 @@ VERSION="2.0.0"
25
25
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
26
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
27
27
  YMIR_HOME="${YMIR_HOME:-$HOME/Documents/Ymir}"
28
- REG="${BROKK_EINDRI_HOMES:-$YMIR_HOME/data/eindri-homes.md}"
28
+ # The registry lives in the hoard (RULES/04-hoard.md; correction 2026-09-17).
29
+ # Resolve through hoard-lib so the path can never drift again.
30
+ if [ -r "$SCRIPT_DIR/hoard-lib.sh" ]; then
31
+ # shellcheck source=bin/hoard-lib.sh
32
+ . "$SCRIPT_DIR/hoard-lib.sh"; hoard_root _hoard
33
+ else
34
+ _hoard="${YMIR_HOARD:-$YMIR_HOME/hodd}"
35
+ fi
36
+ REG="${BROKK_EINDRI_HOMES:-$_hoard/data/eindri-homes.md}"
29
37
  REMOTE=""; BRANCH=""; CHECK=0
30
38
 
31
39
  case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help) sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
@@ -17,13 +17,32 @@
17
17
  set -u
18
18
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
19
  ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
20
- STATE="${BROKK_STATE_OVERRIDE:-$ROOT/state}"
20
+
21
+ # The operator's settings and secrets live in the home they chose, never in the
22
+ # code tree — a packaged install replaces its tree on upgrade, and a credential
23
+ # must never sit in a tree that ships (Rule 04).
24
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
25
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
27
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
28
+ done
29
+ unset _yr _yc
30
+ fi
31
+ hoard_settings_dir YMIR_SETTINGS_DIR
32
+ hoard_local_env YMIR_ENV_FILE
33
+ hoard_state_dir YMIR_STATE_DIR
34
+ hoard_data_dir YMIR_DATA_DIR
35
+
36
+ # The roots that live OUTSIDE the code tree: this machine's records and the
37
+ # runtime state belong to the home the operator chose at installation, never in
38
+ # the tree — a packaged install replaces its tree on upgrade (Rule 04).
39
+ STATE="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
21
40
  OUT="${1:-$ROOT/apps/odrerir/public/livehall.json}"
22
41
 
23
42
  runes_file="${BROKK_RUNES_FILE:-${YMIR_HOME:-$HOME/Documents/Ymir}/hodd/memory/runes_audit.md}"
24
43
  PROJECTS_FILE="${YMIR_HOME:-$HOME/Documents/Ymir}/hodd/identity/projects.yaml"
25
44
  projects_file="$PROJECTS_FILE"
26
- cron_file="$ROOT/config/cron.yaml"
45
+ cron_file="$YMIR_SETTINGS_DIR/cron.yaml"
27
46
 
28
47
  # --- tally the pieces ---
29
48
  runes=$(grep -c '^{' "$runes_file" 2>/dev/null || echo 0)
package/bin/herdr-run.sh CHANGED
@@ -37,7 +37,20 @@ set -u
37
37
  VERSION="2.0.0"
38
38
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
39
39
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
40
- STATE_DIR="${BROKK_STATE_OVERRIDE:-$ROOT/state}"
40
+
41
+ # The roots that live OUTSIDE the code tree: this machine's records and the
42
+ # runtime state belong to the home the operator chose at installation, never in
43
+ # the tree — a packaged install replaces its tree on upgrade (Rule 04).
44
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
45
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
46
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
47
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
48
+ done
49
+ unset _yr _yc
50
+ fi
51
+ hoard_state_dir YMIR_STATE_DIR
52
+ hoard_data_dir YMIR_DATA_DIR
53
+ STATE_DIR="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
41
54
  TAB_LOG="$STATE_DIR/herdr-seats"
42
55
  SETTLE="${YMIR_HERDR_SETTLE:-300}"
43
56
 
@@ -302,7 +315,15 @@ case "$ACTION" in
302
315
  h="$(printf '%s' "$r" | cut -d'"' -f6)"
303
316
  pv="$(printf '%s' "$r" | cut -d'"' -f8)"
304
317
  md="$(printf '%s' "$r" | cut -d'"' -f10)"
305
- [ -n "$h" ] && KIND="$h"
318
+ # The harness FOLLOWS the model: a local provider is pi's work, an
319
+ # online one is opencode's. Trusting a field position here seated
320
+ # pi with an online model id, so the worker started and sat IDLE -
321
+ # "sent but never started" - while the dispatch row reported success.
322
+ case "${md:-}:${pv:-}:${h:-}" in
323
+ *llama*|*lmstudio*|*ollama*) KIND=pi ;;
324
+ "") KIND=pi ;; # nothing resolved: stay local
325
+ *) KIND=opencode ;;
326
+ esac
306
327
  [ -n "$md" ] && MODEL_ARGS=(-- --model "$pv/$md")
307
328
  printf 'herdr-run[1]{model,harness,provider,model_id}:\n "%s","%s","%s","%s"\n' "$MODEL_REQ" "$h" "$pv" "$md" >&2 ;;
308
329
  esac