@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/hodd.sh CHANGED
@@ -42,6 +42,24 @@ PY
42
42
  printf 'hodd[1]{action,file}:\n "load","%s"\n' "$f"
43
43
  }
44
44
 
45
+ # Resolve a secrets path: if the plaintext is absent but an .age sibling and the
46
+ # hoard key exist, decrypt to stdout. Prints a path to a readable file (a temp
47
+ # when decrypted) via the result var. Secrets stay encrypted at rest; the agent
48
+ # never reads ciphertext directly — it asks for the resolved path.
49
+ resolve_secret() { # <file> <result-var>
50
+ local f=$1 var=$2 tmp key
51
+ if [ -r "$f" ]; then printf -v "$var" '%s' "$f"; return 0; fi
52
+ if [ -r "$f.age" ]; then
53
+ key="$HOARD/secrets/age.key"
54
+ command -v age >/dev/null 2>&1 || { printf 'error: %s is encrypted but age is not installed\nhelp: sudo pacman -S age\n' "$f.age" >&2; return 1; }
55
+ [ -r "$key" ] || { printf 'error: encrypted %s but no key at %s\n' "$f.age" "$key" >&2; return 1; }
56
+ tmp="$(mktemp)"; chmod 600 "$tmp"
57
+ age -d -i "$key" "$f.age" >"$tmp" 2>/dev/null || { rm -f "$tmp"; printf 'error: cannot decrypt %s\n' "$f.age" >&2; return 1; }
58
+ printf -v "$var" '%s' "$tmp"; return 0
59
+ fi
60
+ printf 'error: not readable: %s (and no %s.age)\n' "$f" "$f" >&2; return 1
61
+ }
62
+
45
63
  case "$ACTION" in
46
64
  path) printf '%s\n' "$HOARD" ;;
47
65
  init)
@@ -56,23 +74,29 @@ case "$ACTION" in
56
74
  load)
57
75
  f="${1:-}"; [ -n "$f" ] || { printf 'error: load needs a path under the Hoard\n' >&2; exit 2; }
58
76
  case "$f" in /*) ;; *) f="$HOARD/$f" ;; esac
59
- load_env "$f" ;;
77
+ resolve_secret "$f" rf || exit 1
78
+ load_env "$rf"
79
+ case "$rf" in "$f") ;; *) rm -f "$rf" ;; esac ;;
60
80
  emit)
61
81
  # Print `export KEY=value` lines (quoting-safe) for the CALLER to eval, so
62
82
  # `eval "$(bin/hodd.sh emit <file>)"` sets the vars in the invoking shell.
83
+ # Transparently decrypts an .age sibling when the plaintext is absent.
63
84
  f="${1:-}"; [ -n "$f" ] || { printf 'error: emit needs a path under the Hoard\n' >&2; exit 2; }
64
85
  case "$f" in /*) ;; *) f="$HOARD/$f" ;; esac
65
- [ -r "$f" ] || { printf 'error: not readable: %s\n' "$f" >&2; exit 1; }
66
- python3 - "$f" <<'PY'
86
+ resolve_secret "$f" rf || exit 1
87
+ python3 - "$rf" <<'PY'
67
88
  import re, shlex, sys
68
89
  for line in open(sys.argv[1]):
69
90
  m = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)=(.*)$', line.rstrip("\n"))
70
91
  if m:
71
92
  print(f"export {m.group(1)}={shlex.quote(m.group(2))}")
72
93
  PY
94
+ case "$rf" in "$f") ;; *) rm -f "$rf" ;; esac
73
95
  ;;
74
96
  tenant)
75
97
  t="${1:-}"; [ -n "$t" ] || { printf 'error: tenant needs a name\n' >&2; exit 2; }
76
- load_env "$HOARD/tenants/$t/.env" ;;
98
+ resolve_secret "$HOARD/tenants/$t/.env" rf || exit 1
99
+ load_env "$rf"
100
+ case "$rf" in "$HOARD/tenants/$t/.env") ;; *) rm -f "$rf" ;; esac ;;
77
101
  *) printf 'error: unknown action %s\nhelp: bin/hodd.sh [path|init|ls|load|emit|tenant]\n' "$ACTION" >&2; exit 2 ;;
78
102
  esac
@@ -12,6 +12,19 @@ set -u
12
12
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
13
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
14
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
+
15
28
  BRIEF="" OUTDIR="" MODEL=""
16
29
  while [ $# -gt 0 ]; do
17
30
  case "$1" in
@@ -28,7 +41,7 @@ done
28
41
  mkdir -p "$OUTDIR"
29
42
 
30
43
  # Resolve env — source .env.local if present.
31
- for f in "$ROOT/.env.local" "${YMIR_HOARD:-$HOME/Documents/Ymir}/.env.local"; do
44
+ for f in "$YMIR_ENV_FILE" "${YMIR_HOARD:-$HOME/Documents/Ymir}/.env.local"; do
32
45
  [ -f "$f" ] && . "$f" 2>/dev/null || true
33
46
  done
34
47
 
@@ -12,8 +12,27 @@ set -u
12
12
 
13
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
14
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
15
- CFG="${YMIR_AGENTS_YAML:-$ROOT/config/agents.yaml}"
16
- LOCK="$ROOT/state/local-model.lock"
15
+
16
+ # The operator's settings and secrets live in the home they chose, never in the
17
+ # code tree — a packaged install replaces its tree on upgrade, and a credential
18
+ # must never sit in a tree that ships (Rule 04).
19
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
20
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
22
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
23
+ done
24
+ unset _yr _yc
25
+ fi
26
+ hoard_settings_dir YMIR_SETTINGS_DIR
27
+ hoard_local_env YMIR_ENV_FILE
28
+ hoard_state_dir YMIR_STATE_DIR
29
+ hoard_data_dir YMIR_DATA_DIR
30
+
31
+ # The roots that live OUTSIDE the code tree: this machine's records and the
32
+ # runtime state belong to the home the operator chose at installation, never in
33
+ # the tree — a packaged install replaces its tree on upgrade (Rule 04).
34
+ CFG="${YMIR_AGENTS_YAML:-$YMIR_SETTINGS_DIR/agents.yaml}"
35
+ LOCK="$YMIR_STATE_DIR/local-model.lock"
17
36
  WAIT="${LOCAL_MODEL_WAIT:-1800}"
18
37
 
19
38
  [ $# -gt 0 ] || { printf 'error: usage: bin/local-model-lock.sh <command...>\n' >&2; exit 2; }
@@ -31,7 +50,7 @@ PY
31
50
  )"
32
51
  [ -n "$N" ] || N=1
33
52
 
34
- mkdir -p "$ROOT/state"
53
+ mkdir -p "$YMIR_STATE_DIR"
35
54
 
36
55
  if [ "$N" -le 1 ]; then
37
56
  # one at a time — exclusive lock, wait for the peer to finish
@@ -49,6 +49,35 @@ def engine(agent: str | None) -> Engram:
49
49
  return _pool[key]
50
50
 
51
51
 
52
+ def layers():
53
+ """What the well actually holds, layer by layer - the record a UI reads."""
54
+ out = {"facts": 0, "facts_active": 0, "facts_superseded": 0, "entities": 0,
55
+ "edges": 0, "reflections": 0, "last_reflection": None, "vec_index": 0}
56
+ try:
57
+ con = sqlite3.connect(f"file:{STORE}?mode=ro", uri=True)
58
+ for key, sql in (
59
+ ("facts", "select count(*) from facts"),
60
+ ("facts_active", "select count(*) from facts where superseded_at is null"),
61
+ ("facts_superseded", "select count(*) from facts where superseded_at is not null"),
62
+ ("entities", "select count(*) from entities"),
63
+ ("edges", "select count(*) from edges"),
64
+ ("reflections", "select count(*) from reflections"),
65
+ ("vec_index", "select count(*) from vec_episodes_rowids"),
66
+ ):
67
+ try:
68
+ out[key] = int(con.execute(sql).fetchone()[0])
69
+ except Exception:
70
+ pass
71
+ try:
72
+ out["last_reflection"] = con.execute(
73
+ "select max(finished_at) from reflections").fetchone()[0]
74
+ except Exception:
75
+ pass
76
+ except Exception as e:
77
+ out["error"] = str(e)
78
+ return out
79
+
80
+
52
81
  def count_episodes() -> int:
53
82
  try:
54
83
  con = sqlite3.connect(f"file:{STORE}?mode=ro", uri=True)
@@ -98,10 +127,14 @@ class Handler(BaseHTTPRequestHandler):
98
127
  "agents": engine(None).list_agents(),
99
128
  })
100
129
  if u.path == "/inspect":
130
+ # THE LAYERS, not just the count. The smithy shows what it is told,
131
+ # and it was told three fields while the well holds six: a panel that
132
+ # reads only episodes cannot show that facts and entities exist.
101
133
  return self._json(200, {
102
134
  "store": str(STORE),
103
135
  "episodes": count_episodes(),
104
136
  "agents": engine(None).list_agents(),
137
+ **layers(),
105
138
  })
106
139
  if u.path == "/recent":
107
140
  limit = int((q.get("limit") or ["60"])[0])
@@ -18,6 +18,19 @@ fi
18
18
  VERSION="1.0.0"
19
19
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
20
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
21
+
22
+ # The roots that live OUTSIDE the code tree: this machine's records and the
23
+ # runtime state belong to the home the operator chose at installation, never in
24
+ # the tree — a packaged install replaces its tree on upgrade (Rule 04).
25
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
26
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
28
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
29
+ done
30
+ unset _yr _yc
31
+ fi
32
+ hoard_state_dir YMIR_STATE_DIR
33
+ hoard_data_dir YMIR_DATA_DIR
21
34
  YMIR_HOME="${YMIR_HOME:-$HOME/Documents/Ymir}"
22
35
  BRIDGE="$ROOT/bin/mimir-bridge.py"
23
36
  DB="${ENGRAM_DB:-$YMIR_HOME/memory/kaia.engram}"
@@ -75,7 +88,7 @@ if ! "$PY" -c "import engram" 2>/dev/null; then
75
88
  printf 'error: engram not installed for %s\nhelp: bin/prereq-ensure.sh engram (installs it into a compatible Python)\n' "$PY" >&2; exit 1
76
89
  fi
77
90
 
78
- mkdir -p "$ROOT/state" "$(dirname "$DB")"
91
+ mkdir -p "$YMIR_STATE_DIR" "$(dirname "$DB")"
79
92
  ENGRAM_DB="$DB" MIMIRSBRUNN_PORT="$PORT" nohup "$PY" "$BRIDGE" >"$LOG_FILE" 2>&1 &
80
93
  echo $! >"$PID_FILE"
81
94
  sleep 3
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ # mimir-reflect — smelt the well's episodes into facts, through the LLAMA-ROUTER.
3
+ #
4
+ # The router is Ymir's one door to local models: its seats are llama.cpp on :8080
5
+ # (coding) and Apodex on :1234 (research/planning). NEVER point this at Ollama —
6
+ # ollama is the machine's own server, not a Ymir provider, and one local model
7
+ # rides the router at a time. Swap seats by re-pointing the router, not by
8
+ # reaching past it.
9
+ #
10
+ # bin/mimir-reflect.sh # the well, with the default seat
11
+ # YMIR_ROUTER_URL=http://127.0.0.1:8080/v1 YMIR_REFLECT_MODEL=qwen3.6-35b-a3b bin/mimir-reflect.sh
12
+ set -euo pipefail
13
+
14
+ WELL="${YMIR_WELL:-${YMIR_HOME:-$HOME/Documents/Ymir}/memory/kaia.engram}"
15
+ BASE="${YMIR_ROUTER_URL:-http://127.0.0.1:1234/v1}"
16
+ MODEL="${YMIR_REFLECT_MODEL:-apodex-1.0-mini}"
17
+
18
+ [ -f "$WELL" ] || { echo "error: no well at $WELL" >&2; echo "help: set YMIR_WELL or run bin/mimir-bridge.py first" >&2; exit 1; }
19
+
20
+ echo "mimir-reflect[well,seat]:"
21
+ echo " \"$WELL\""
22
+ echo " \"$BASE\" model \"$MODEL\""
23
+ echo
24
+
25
+ # A local seat needs no key; an empty one keeps the OpenAI client quiet.
26
+ OPENAI_API_KEY="${OPENAI_API_KEY:-local}" \
27
+ exec engram reflect "$WELL" --llm openai --model "$MODEL" --base-url "$BASE"
@@ -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
  case "${1-}" in
19
32
  -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
package/bin/mjollnir.sh CHANGED
@@ -13,7 +13,26 @@ 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
+ hoard_state_dir YMIR_STATE_DIR
30
+ hoard_data_dir YMIR_DATA_DIR
31
+
32
+ # The roots that live OUTSIDE the code tree: this machine's records and the
33
+ # runtime state belong to the home the operator chose at installation, never in
34
+ # the tree — a packaged install replaces its tree on upgrade (Rule 04).
35
+ ENV_FILE="${BROKK_ENV_FILE:-$YMIR_ENV_FILE}"
17
36
 
18
37
  usage() { sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//'; }
19
38
  CMD="${1-}"; shift || true
@@ -40,7 +59,7 @@ done
40
59
  if [ "$CMD" = status ]; then
41
60
  printf 'mjollnir[2]{field,value}:\n'
42
61
  printf ' "repo","%s"\n' "${REPO:-$ROOT}"
43
- printf ' "pending","%s"\n' "$(find "$ROOT/data" -maxdepth 2 -name 'brief.md' -path '*issue-*' 2>/dev/null | wc -l | tr -d ' ')"
62
+ printf ' "pending","%s"\n' "$(find "$YMIR_DATA_DIR" -maxdepth 2 -name 'brief.md' -path '*issue-*' 2>/dev/null | wc -l | tr -d ' ')"
44
63
  exit 0
45
64
  fi
46
65
  [ "$CMD" = run ] || { printf 'error: unknown command %s\nhelp: bin/mjollnir.sh [run|status|--version]\n' "$CMD" >&2; exit 2; }
@@ -69,7 +88,7 @@ printf 'mjollnir[1]{issue,task,mode,repo}:\n "%s","%s","%s","%s"\n' "$ISSUE" "$
69
88
 
70
89
  # 2. Scaffold the brief (the worker's contract).
71
90
  "$SCRIPT_DIR/erindi-brief.sh" "$ID" "$(basename "$REPO")" --mode "$MODE" >/dev/null 2>&1 || true
72
- BRIEF="$ROOT/data/$ID/brief.md"
91
+ BRIEF="$YMIR_DATA_DIR/$ID/brief.md"
73
92
  if [ -f "$BRIEF" ]; then
74
93
  { printf '\n\n## Issue #%s — %s\n\n%s\n' "$ISSUE" "$TITLE" "$BODY"; } >>"$BRIEF"
75
94
  fi
@@ -17,7 +17,20 @@ 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
- OUT="${YMIR_LOCAL_MODELS_DOC:-$ROOT/data/local-models.md}"
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
33
+ OUT="${YMIR_LOCAL_MODELS_DOC:-$YMIR_DATA_DIR/local-models.md}"
21
34
  PRINT_ONLY=0
22
35
 
23
36
  case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help) sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
@@ -53,8 +66,9 @@ block="<!-- model-hardware:begin -->
53
66
  build/run the CUDA build; keep K and V the same KV type (q8_0).
54
67
  - **Coding wants >= 80000 context.** Measure the real ceiling: load more until a
55
68
  request OOMs, then drop one step (ctx is per-model).
56
- - **Bench to the maximum:** load the \`.agents/skills/modeltesting\` skill, then
57
- \`bin/models-detect.sh --write\` to register what serves.
69
+ - **The method** engines, wiring, and honest measurement — is the Galdr asset
70
+ \`.agents/skills/galdr-ymirsystem/assets/local-models.md\`. Then
71
+ \`bin/models-detect.sh --write\` registers what serves.
58
72
  <!-- model-hardware:end -->"
59
73
 
60
74
  # The researched settings are NOT in the managed block, so a re-run never clobbers
@@ -15,7 +15,20 @@ set -u
15
15
  VERSION="1.0.0"
16
16
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
17
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
18
- CFG="${YMIR_AGENTS_YAML:-$ROOT/config/agents.yaml}"
18
+
19
+ # The operator's settings and secrets live in the home they chose, never in the
20
+ # code tree — a packaged install replaces its tree on upgrade, and a credential
21
+ # must never sit in a tree that ships (Rule 04).
22
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
23
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
25
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
26
+ done
27
+ unset _yr _yc
28
+ fi
29
+ hoard_settings_dir YMIR_SETTINGS_DIR
30
+ hoard_local_env YMIR_ENV_FILE
31
+ CFG="${YMIR_AGENTS_YAML:-$YMIR_SETTINGS_DIR/agents.yaml}"
19
32
 
20
33
  case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
21
34
  -h|--help|"") sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ # nornir-job-hall-snapshot.sh — Óðrerir: refresh the Live Hall's board.
3
+ #
4
+ # The Hall is a glass: it reads apps/odrerir/public/livehall.json, written by
5
+ # this job from the real system state (runes · projects · cron · wake queue ·
6
+ # standing smiths · armed when-sources · landed errands). Scheduled at 08:00,
7
+ # after the 06:00 observer and the 07:00 briefing, so the morning's board
8
+ # carries the day's fresh runes. Stateless: read inputs → write the snapshot →
9
+ # carve a Rune → exit.
10
+ #
11
+ # Overrides: BROKK_ROOT_OVERRIDE (hall-snapshot.sh), YMIR_HOME (the hoard).
12
+ set -u
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
15
+
16
+ # The snapshot is runtime, never repo: it is gitignored where it lands.
17
+ if ! "$ROOT/bin/hall-snapshot.sh" >/dev/null 2>&1; then
18
+ printf 'error: hall-snapshot.sh failed — the board is stale\n'
19
+ printf 'help: run bin/hall-snapshot.sh by hand to see the reason\n'
20
+ exit 1
21
+ fi
22
+
23
+ # Carve the Rune — the ledger lives with the hoard.
24
+ # shellcheck source=bin/runes-append.sh
25
+ . "$ROOT/bin/runes-append.sh"
26
+ runes_append "odrerir" "hall.snapshot" --message "Live Hall board refreshed from real state" >/dev/null \
27
+ || printf 'hall-snapshot: rune append failed (non-fatal)\n'
28
+ printf 'hall-snapshot: Live Hall board refreshed\n'
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ # nornir-job-nsr-compliance.sh — the NorthStar compliance round, nightly.
3
+ #
4
+ # Runs the deterministic NSR gates (.compliance/gates/check_*.sh — danger,
5
+ # env, paths, platform, wiring) and carves one Rune with the verdict. A FAIL
6
+ # is reported loudly in the Rune message — the morning briefing reads the
7
+ # ledger, so a broken door is seen at sunrise, not found by accident.
8
+ #
9
+ # Stateless: run gates → write nothing but state/last + the Rune → exit.
10
+ set -u
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
13
+
14
+ # The roots that live OUTSIDE the code tree: this machine's records and the
15
+ # runtime state belong to the home the operator chose at installation, never in
16
+ # the tree — a packaged install replaces its tree on upgrade (Rule 04).
17
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
18
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
20
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
21
+ done
22
+ unset _yr _yc
23
+ fi
24
+ hoard_state_dir YMIR_STATE_DIR
25
+ hoard_data_dir YMIR_DATA_DIR
26
+ STATE="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
27
+
28
+ GATES_DIR="$ROOT/.compliance/gates"
29
+ [ -d "$GATES_DIR" ] || { printf 'error: no NSR gates at %s\n' "$GATES_DIR"; exit 1; }
30
+
31
+ passed=0; failed=0; failed_names=""
32
+ for g in "$GATES_DIR"/check_*.sh; do
33
+ name=$(basename "$g" .sh)
34
+ if bash "$g" >/dev/null 2>&1; then
35
+ passed=$((passed+1))
36
+ else
37
+ failed=$((failed+1)); failed_names="$failed_names $name"
38
+ fi
39
+ done
40
+
41
+ printf 'nsr-compliance: gates=%d passed=%d failed=%d%s\n' \
42
+ "$((passed+failed))" "$passed" "$failed" "$failed_names"
43
+
44
+ # shellcheck source=bin/runes-append.sh
45
+ . "$ROOT/bin/runes-append.sh"
46
+ if [ "$failed" = 0 ]; then
47
+ runes_append "nornir" "nsr.compliance" --message "NSR compliance: $passed/$passed gates passed" >/dev/null \
48
+ || printf 'nsr-compliance: rune append failed (non-fatal)\n'
49
+ exit 0
50
+ else
51
+ runes_append "nornir" "nsr.compliance.failed" --message "NSR compliance FAIL: gates $failed_names FAILED — mend at sunrise" >/dev/null \
52
+ || printf 'nsr-compliance: rune append failed (non-fatal)\n'
53
+ exit 1
54
+ fi
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bash
2
+ # npm-publish.sh — publish through the HOARD's npm token, every single time.
3
+ #
4
+ # Why this exists: this machine once held two tokens, and the one in ~/.npmrc was
5
+ # stale. A publish then failed with a 2FA refusal that looked like a policy
6
+ # problem but was a credential problem, and a session was spent on it. The token
7
+ # with bypass-2FA lives in the hoard. So the rule is: never trust ~/.npmrc — read
8
+ # the token from the hoard, hand it to npm through a temporary userconfig, and
9
+ # throw the file away.
10
+ #
11
+ # bin/npm-publish.sh # the platform (the repo root manifest)
12
+ # bin/npm-publish.sh apps/hlidskjalf # one package
13
+ # bin/npm-publish.sh --all # the platform + every app package
14
+ # bin/npm-publish.sh --whoami # which account the hoard token is
15
+ # bin/npm-publish.sh --dry-run apps/odrerir
16
+ # bin/npm-publish.sh --unpublish @zerwiz/ymir@0.1.5 # take ONE version back
17
+ # bin/npm-publish.sh --unpublish @zerwiz/ymir@0.1.5 --dry-run
18
+ #
19
+ # Unpublish: npm allows ONE VERSION back for 72 hours after it was published;
20
+ # past that only npm support can remove it, which is why this door names the
21
+ # version and refuses anything vaguer. A whole package is not a one-word act —
22
+ # that needs npm's own tooling and a deliberate decision, not a flag here.
23
+ #
24
+ # A prerelease version (0.1.8-alpha) is published under `--tag next` automatically
25
+ # — npm refuses a prerelease on `latest`, which is the second half of the same
26
+ # lesson. A scoped package always goes out with public access.
27
+ set -u
28
+
29
+ VERSION="1.0.0"
30
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
32
+ # shellcheck source=bin/hoard-lib.sh
33
+ . "$SCRIPT_DIR/hoard-lib.sh"
34
+
35
+ DRY=0; WHO=0; ALL=0; UNPUBLISH=0; UNPUB_SPEC=""; TARGETS=()
36
+ for a in "$@"; do
37
+ case "$a" in
38
+ --whoami) WHO=1 ;;
39
+ --dry-run) DRY=1 ;;
40
+ --all) ALL=1 ;;
41
+ --unpublish) UNPUBLISH=1 ;;
42
+ --unpublish=*) UNPUBLISH=1; UNPUB_SPEC="${a#*=}" ;;
43
+ -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;;
44
+ -h|--help|"") sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
45
+ -*) printf 'error: unknown flag %s\nhelp: bin/npm-publish.sh [--all|--whoami|--unpublish <spec>|--dry-run] [<package-dir>...]\n' "$a" >&2; exit 2 ;;
46
+ *) TARGETS+=("$a") ;;
47
+ esac
48
+ done
49
+ # --all is the platform plus every app package. The smithy's own repo is
50
+ # cloned at apps/smidja-factory (the registry maps smidja there), so it is a
51
+ # target like any other app: it publishes as @zerwiz/smidja.
52
+ [ "$ALL" = 1 ] && TARGETS=("$ROOT" "$ROOT/apps/hlidskjalf" "$ROOT/apps/hlidskjalf-mobile" "$ROOT/apps/odrerir" "$ROOT/apps/sessrumnir" "$ROOT/apps/smidja-factory")
53
+ [ "${#TARGETS[@]}" -eq 0 ] && [ "$WHO" = 0 ] && [ "$UNPUBLISH" = 0 ] && TARGETS=("$ROOT")
54
+ # `--unpublish @scope/name@1.2.3` — the spec may also arrive positionally.
55
+ [ "$UNPUBLISH" = 1 ] && UNPUB_SPEC="${UNPUB_SPEC:-${TARGETS[0]:-}}"
56
+
57
+ # ── the token, from the hoard ────────────────────────────────────────────────
58
+ envfile=""; hoard_env envfile
59
+ TOKEN=""
60
+ if [ -r "$envfile" ]; then
61
+ TOKEN="$(sed -n 's/^NPM_TOKEN=//p' "$envfile" | head -1 | tr -d '"'"'"' ')"
62
+ fi
63
+ if [ -z "$TOKEN" ] && [ "$WHO" = 1 ]; then
64
+ printf 'npm[1]{token,state}:\n "NPM_TOKEN","absent in %s"\n' "${envfile#"$HOME"/}"
65
+ exit 1
66
+ fi
67
+ if [ -z "$TOKEN" ]; then
68
+ printf 'error: the hoard holds no NPM_TOKEN\nhelp: add NPM_TOKEN=<granular token with bypass 2FA> to %s\n' "${envfile#"$HOME"/}" >&2
69
+ printf 'help: npmjs.com → Access Tokens → Generate → granular, scope @zerwiz, bypass 2FA on\n' >&2
70
+ exit 1
71
+ fi
72
+
73
+ cfg="$(mktemp)"; chmod 600 "$cfg"
74
+ printf '//registry.npmjs.org/:_authToken=%s\n' "$TOKEN" >"$cfg"
75
+ trap 'rm -f "$cfg"' EXIT
76
+
77
+ if [ "$WHO" = 1 ]; then
78
+ who="$(npm whoami --userconfig "$cfg" 2>/dev/null || printf 'unknown')"
79
+ printf 'npm[2]{token,account}:\n "source","%s"\n "whoami","%s"\n' "${envfile#"$HOME"/}" "$who"
80
+ exit 0
81
+ fi
82
+
83
+ # ── unpublish ────────────────────────────────────────────────────────────────
84
+ # Take ONE version back. npm permits it for 72 hours after publication; beyond
85
+ # that it is npm support's door, not this one. The spec must name a version:
86
+ # `@scope/name@1.2.3`, never a bare name.
87
+ if [ "$UNPUBLISH" = 1 ]; then
88
+ case "$UNPUB_SPEC" in
89
+ *@*@*) ;; # @scope/name@version
90
+ *@[0-9]*) ;;
91
+ *) printf 'error: --unpublish needs a package AND a version\nhelp: bin/npm-publish.sh --unpublish @scope/name@<version>\n' >&2; exit 2 ;;
92
+ esac
93
+ if [ "$DRY" = 1 ]; then
94
+ printf 'npm_unpublish[1]{spec,state}:\n "%s","dry-run — nothing was removed"\n' "$UNPUB_SPEC"
95
+ exit 0
96
+ fi
97
+ out="$(npm unpublish "$UNPUB_SPEC" --force --userconfig "$cfg" 2>&1)"; rc=$?
98
+ if [ "$rc" = 0 ]; then
99
+ printf 'npm_unpublish[1]{spec,state}:\n "%s","unpublished"\n' "$UNPUB_SPEC"
100
+ printf 'note: the registry CDN may serve the tarball for a while after this; publishing a\n'
101
+ printf 'note: newer version moves the "latest" tag off it at once.\n'
102
+ else
103
+ why="$(printf '%s' "$out" | grep -m1 -E 'npm error (code )?[A-Z0-9]+|E[0-9]{3}' | cut -c1-90)"
104
+ printf 'error: unpublish failed — %s\nhelp: is it inside the 72-hour window, and is the token the right account? (bin/npm-publish.sh --whoami)\n' "${why:-see npm log}" >&2
105
+ exit 1
106
+ fi
107
+ exit 0
108
+ fi
109
+
110
+ # ── publish ──────────────────────────────────────────────────────────────────
111
+ printf 'npm_publish[%d]{package,version,tag,state}:\n' "${#TARGETS[@]}"
112
+ failed=0
113
+ for dir in "${TARGETS[@]}"; do
114
+ pkg="$dir/package.json"
115
+ if [ ! -f "$pkg" ]; then
116
+ printf ' "%s","-","-","SKIP (no package.json)"\n' "${dir#"$ROOT"/}"
117
+ continue
118
+ fi
119
+ name="$(sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$pkg" | head -1)"
120
+ ver="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$pkg" | head -1)"
121
+ case "$ver" in *-*) tag=next ;; *) tag=latest ;; esac # a prerelease never rides `latest`
122
+ if [ "$DRY" = 1 ]; then
123
+ printf ' "%s","%s","%s","dry-run"\n' "$name" "$ver" "$tag"
124
+ continue
125
+ fi
126
+ out="$(cd "$dir" && npm publish --access public --tag "$tag" --userconfig "$cfg" 2>&1)"; rc=$?
127
+ if [ "$rc" = 0 ]; then
128
+ printf ' "%s","%s","%s","published"\n' "$name" "$ver" "$tag"
129
+ else
130
+ why="$(printf '%s' "$out" | grep -m1 -E 'npm error (code )?[A-Z0-9]+|E[0-9]{3}' | cut -c1-90)"
131
+ printf ' "%s","%s","%s","FAILED (%s)"\n' "$name" "$ver" "$tag" "${why:-see npm log}"
132
+ failed=$((failed+1))
133
+ fi
134
+ done
135
+ [ "$failed" = 0 ] || { printf 'error: %d package(s) did not publish\nhelp: bin/npm-publish.sh --whoami (is the hoard token still the right account?)\n' "$failed" >&2; exit 1; }
136
+ exit 0
@@ -24,6 +24,19 @@ VERSION="1.0.0"
24
24
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
25
25
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
26
26
 
27
+ # The operator's settings and secrets live in the home they chose, never in the
28
+ # code tree — a packaged install replaces its tree on upgrade, and a credential
29
+ # must never sit in a tree that ships (Rule 04).
30
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
31
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
32
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
33
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
34
+ done
35
+ unset _yr _yc
36
+ fi
37
+ hoard_settings_dir YMIR_SETTINGS_DIR
38
+ hoard_local_env YMIR_ENV_FILE
39
+
27
40
  # portability shim (bin/ymir-platform.sh)
28
41
  if [ -z "${YMIR_PLATFORM_LOADED:-}" ]; then
29
42
  _ymir_dir="$SCRIPT_DIR"
@@ -122,15 +135,15 @@ else
122
135
  fi
123
136
 
124
137
  # ── 6. the away-mode alarm channel + the crash sensor ────────────────────────
125
- if [ -e "$ROOT/config/wedge-alarm" ]; then
138
+ if [ -e "$YMIR_SETTINGS_DIR/wedge-alarm" ]; then
126
139
  add alarm OK "channel kept"
127
140
  elif [ "$CHECK" = 1 ]; then
128
141
  add alarm WARN "channel not written (run without --check)"
129
- elif mkdir -p "$ROOT/config" 2>/dev/null && \
130
- printf '# The channel the away-mode wedge alarm fires on when an escalation\n# cannot be delivered into the pane. See bin/wedge-notify.sh.\ndesktop\n' >"$ROOT/config/wedge-alarm" 2>/dev/null; then
131
- add alarm OK "channel written (config/wedge-alarm)"
142
+ elif mkdir -p "$YMIR_SETTINGS_DIR" 2>/dev/null && \
143
+ printf '# The channel the away-mode wedge alarm fires on when an escalation\n# cannot be delivered into the pane. See bin/wedge-notify.sh.\ndesktop\n' >"$YMIR_SETTINGS_DIR/wedge-alarm" 2>/dev/null; then
144
+ add alarm OK "channel written ($YMIR_SETTINGS_DIR/wedge-alarm)"
132
145
  else
133
- add alarm WARN "could not write config/wedge-alarm"
146
+ add alarm WARN "could not write $YMIR_SETTINGS_DIR/wedge-alarm"
134
147
  fi
135
148
 
136
149
  # ── 7. the terminal backend Ymir needs ───────────────────────────────────────