@zerwiz/ymir 0.1.5 → 0.1.7
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 +215 -23
- 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 +1033 -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 +153 -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/eir-doctor.sh +47 -1
- 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-bootstrap.sh +5 -1
- 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 +219 -54
- package/bin/ymir-plan.sh +356 -0
- package/bin/ymir-say.sh +14 -1
- package/bin/ymir-setup-auth.sh +12 -1
- package/bin/ymir-validate.sh +28 -8
- 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 +52 -2
- 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,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"
|
package/bin/mjollnir-webhook.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 "$
|
|
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="$
|
|
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
|
package/bin/model-hardware.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
-
- **
|
|
57
|
-
|
|
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
|
package/bin/model-resolve.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
package/bin/omarchy-install.sh
CHANGED
|
@@ -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 "$
|
|
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 "$
|
|
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' >"$
|
|
131
|
-
add alarm OK "channel written (
|
|
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
|
|
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 ───────────────────────────────────────
|
package/bin/omarchy-plugins.sh
CHANGED
|
@@ -26,6 +26,19 @@ set -u
|
|
|
26
26
|
VERSION="1.0.0"
|
|
27
27
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
28
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
29
|
+
|
|
30
|
+
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
31
|
+
# runtime state belong to the home the operator chose at installation, never in
|
|
32
|
+
# the tree — a packaged install replaces its tree on upgrade (Rule 04).
|
|
33
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
34
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
35
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
36
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
37
|
+
done
|
|
38
|
+
unset _yr _yc
|
|
39
|
+
fi
|
|
40
|
+
hoard_state_dir YMIR_STATE_DIR
|
|
41
|
+
hoard_data_dir YMIR_DATA_DIR
|
|
29
42
|
YES=0
|
|
30
43
|
|
|
31
44
|
case "${1-}" in
|
|
@@ -77,7 +90,7 @@ is_installed() { # <id>
|
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
repo_for() { # <id> — read the repo from the registry's own catalog when reachable
|
|
80
|
-
local id=$1 cache="$
|
|
93
|
+
local id=$1 cache="$YMIR_STATE_DIR/omarchy-catalog.json"
|
|
81
94
|
if [ -r "$cache" ]; then
|
|
82
95
|
python3 - "$cache" "$id" <<'PY' 2>/dev/null
|
|
83
96
|
import json,sys
|
|
@@ -135,7 +148,7 @@ case "$ACTION" in
|
|
|
135
148
|
printf 'omarchy-plugins[1]{id,state}:\n "%s","already installed"\n' "$id"; exit 0
|
|
136
149
|
fi
|
|
137
150
|
# Resolve the repo from the registry catalog; refresh it if absent.
|
|
138
|
-
cache="$
|
|
151
|
+
cache="$YMIR_STATE_DIR/omarchy-catalog.json"
|
|
139
152
|
if [ ! -r "$cache" ] && have curl; then
|
|
140
153
|
mkdir -p "$(dirname "$cache")"
|
|
141
154
|
curl -fsS --max-time 25 https://plugins.omarchy.org/catalog.json -o "$cache" 2>/dev/null || true
|
package/bin/omarchy-sense.sh
CHANGED
|
@@ -21,7 +21,20 @@ set -u
|
|
|
21
21
|
VERSION="1.0.0"
|
|
22
22
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
23
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
# The roots that live OUTSIDE the code tree: this machine's records and the
|
|
26
|
+
# runtime state belong to the home the operator chose at installation, never in
|
|
27
|
+
# the tree — a packaged install replaces its tree on upgrade (Rule 04).
|
|
28
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
29
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
31
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
32
|
+
done
|
|
33
|
+
unset _yr _yc
|
|
34
|
+
fi
|
|
35
|
+
hoard_state_dir YMIR_STATE_DIR
|
|
36
|
+
hoard_data_dir YMIR_DATA_DIR
|
|
37
|
+
STATE_DIR="${BROKK_STATE_OVERRIDE:-$YMIR_STATE_DIR}"
|
|
25
38
|
SNAP="$STATE_DIR/omarchy-setup.json"
|
|
26
39
|
|
|
27
40
|
# Rule 05: this is the OMARCHY layer's sensor, so it is gated on its host. Ymir
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pi-model-agnostic — pi must not EXPECT any model. Local and online are peers.
|
|
3
|
+
#
|
|
4
|
+
# The law (Allfather, 2026-09-17): a seat that answers is used; a seat that does not
|
|
5
|
+
# must not stop pi from starting. pi was failing at launch because providers pointed at
|
|
6
|
+
# local ports with nothing behind them:
|
|
7
|
+
# [llama-cpp] failed to reach http://localhost:8080/v1/models: fetch failed
|
|
8
|
+
# An unreachable local seat is a SKIP, not an error.
|
|
9
|
+
#
|
|
10
|
+
# This prunes unreachable LOCAL providers from pi's models config, keeping every seat
|
|
11
|
+
# that answers and every online provider untouched.
|
|
12
|
+
#
|
|
13
|
+
# bin/pi-model-agnostic.sh # report and prune
|
|
14
|
+
# bin/pi-model-agnostic.sh --dry-run # report only
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
CFG="${PI_MODELS_JSON:-$HOME/.pi/agent/models.json}"
|
|
18
|
+
DRY=0
|
|
19
|
+
[ "${1:-}" = "--dry-run" ] && DRY=1
|
|
20
|
+
[ -f "$CFG" ] || { printf 'error: no pi models config at %s\nhelp: set PI_MODELS_JSON\n' "$CFG" >&2; exit 1; }
|
|
21
|
+
|
|
22
|
+
CFG="$CFG" DRY="$DRY" python3 - <<'INNER'
|
|
23
|
+
import json, os, shutil, sys, time, urllib.request
|
|
24
|
+
|
|
25
|
+
cfg = os.environ["CFG"]
|
|
26
|
+
dry = os.environ.get("DRY") == "1"
|
|
27
|
+
d = json.load(open(cfg))
|
|
28
|
+
providers = d.get("providers", {})
|
|
29
|
+
|
|
30
|
+
def reachable(url):
|
|
31
|
+
if not url:
|
|
32
|
+
return False
|
|
33
|
+
probe = url.rstrip("/")
|
|
34
|
+
if not probe.endswith("/v1"):
|
|
35
|
+
probe += "/v1"
|
|
36
|
+
try:
|
|
37
|
+
with urllib.request.urlopen(probe + "/models", timeout=3) as r:
|
|
38
|
+
return r.status == 200
|
|
39
|
+
except Exception:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
kept, dropped = [], []
|
|
43
|
+
for name, prov in providers.items():
|
|
44
|
+
base = str(prov.get("baseUrl", ""))
|
|
45
|
+
local = ("127.0.0.1" in base) or ("localhost" in base) or ("0.0.0.0" in base)
|
|
46
|
+
if local and not reachable(base):
|
|
47
|
+
dropped.append((name, base))
|
|
48
|
+
else:
|
|
49
|
+
kept.append(name)
|
|
50
|
+
|
|
51
|
+
print("pi_models[%d]{provider,base,verdict}:" % (len(kept) + len(dropped)))
|
|
52
|
+
for n in kept:
|
|
53
|
+
print(' "%s","%s","kept"' % (n, providers[n].get("baseUrl", "")))
|
|
54
|
+
for n, b in dropped:
|
|
55
|
+
print(' "%s","%s","unreachable local seat - pruned"' % (n, b))
|
|
56
|
+
|
|
57
|
+
if dropped and not dry:
|
|
58
|
+
shutil.copy2(cfg, cfg + ".bak-" + time.strftime("%Y%m%d%H%M%S"))
|
|
59
|
+
d["providers"] = {k: v for k, v in providers.items() if k in kept}
|
|
60
|
+
dm = d.get("defaultProvider")
|
|
61
|
+
if dm and dm not in kept and kept:
|
|
62
|
+
d["defaultProvider"] = kept[0]
|
|
63
|
+
ids = (d["providers"][kept[0]].get("models") or [{}])
|
|
64
|
+
if ids and ids[0].get("id"):
|
|
65
|
+
d["defaultModel"] = ids[0]["id"]
|
|
66
|
+
print(" default -> %s / %s" % (d.get("defaultProvider"), d.get("defaultModel")))
|
|
67
|
+
json.dump(d, open(cfg, "w"), indent=2)
|
|
68
|
+
print("pi_models: %d pruned, backup beside the config" % len(dropped))
|
|
69
|
+
elif dry:
|
|
70
|
+
print("pi_models: dry run - nothing written")
|
|
71
|
+
INNER
|
package/bin/private-guard.sh
CHANGED
|
@@ -18,6 +18,20 @@ FORBID='^(hodd/|svartalfaheim/|state/|data/|workspace/(work|personal|memory|comp
|
|
|
18
18
|
# Allowed tracked exceptions inside those roots.
|
|
19
19
|
ALLOW='^(hodd/(README\.md|\.gitignore|AGENTS\.example\.md|[^/]+\.example(\.md)?)$|svartalfaheim/.*(README\.md|\.gitkeep|\.gitignore|\.example(\.md)?)$|state/\.gitkeep$|data/[^/]+\.example$|assets/reference/state/[^/]+\.example$|\.env\.example|\.env\.sample)$'
|
|
20
20
|
|
|
21
|
+
# Tenant DOCUMENTS — a wiki, policy set, vision, roadmap or strategy file is
|
|
22
|
+
# company/operator knowledge, not a public asset. It belongs at
|
|
23
|
+
# $YMIR_HOME/hodd/identity/companies/<company>/ and nowhere in this repo.
|
|
24
|
+
# Added 2026-09-17 after `midgard/company_wiki/` was found sitting in the
|
|
25
|
+
# public tree: it was an empty placeholder, but the shape invited the leak.
|
|
26
|
+
#
|
|
27
|
+
# The match is deliberately a DIRECTORY or a doc suffixed with tenant content —
|
|
28
|
+
# NOT the bare word. `svartalfaheim/examples/SECRETS.md` is a public how-to (it
|
|
29
|
+
# teaches where secrets go and holds no value), so a bare `secrets` match would
|
|
30
|
+
# be a false positive.
|
|
31
|
+
TENANT_DOC='((^|/)(company_wiki|wiki|policies|handbook|playbook|internal|confidential)(/|\.)|(^|/)(vision|strategy|roadmap|policies)[^/]*\.(md|txt|rst|pdf|docx?)$)'
|
|
32
|
+
# Public exceptions: a wiki-shaped name that is a genuine scaffold or doc.
|
|
33
|
+
TENANT_DOC_ALLOW='(\.example$|\.example\.md$|README\.md$|/\.gitkeep$)'
|
|
34
|
+
|
|
21
35
|
scan_list() {
|
|
22
36
|
local hit=0 f
|
|
23
37
|
while IFS= read -r f; do
|
|
@@ -25,6 +39,12 @@ scan_list() {
|
|
|
25
39
|
if printf '%s\n' "$f" | grep -Eq "$FORBID" && ! printf '%s\n' "$f" | grep -Eq "$ALLOW"; then
|
|
26
40
|
printf 'private-guard: tracked private path: %s\n' "$f" >&2
|
|
27
41
|
hit=1
|
|
42
|
+
continue
|
|
43
|
+
fi
|
|
44
|
+
if printf '%s\n' "$f" | grep -Eq "$TENANT_DOC" && ! printf '%s\n' "$f" | grep -Eq "$TENANT_DOC_ALLOW"; then
|
|
45
|
+
printf 'private-guard: tenant/company document in the public tree: %s\n' "$f" >&2
|
|
46
|
+
printf ' company knowledge belongs at $YMIR_HOME/hodd/identity/companies/, never here\n' >&2
|
|
47
|
+
hit=1
|
|
28
48
|
fi
|
|
29
49
|
done
|
|
30
50
|
return "$hit"
|
package/bin/project-git.sh
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
# remote. Auth is a REFERENCE (app|pat|ssh|gh), never a value.
|
|
5
5
|
#
|
|
6
6
|
# Usage:
|
|
7
|
-
# project-git.sh <project-id> [--field host|owner|repo|remote|default_branch|auth]
|
|
7
|
+
# project-git.sh <project-id> [--field host|owner|repo|remote|default_branch|auth|machine]
|
|
8
8
|
# project-git.sh list
|
|
9
9
|
# project-git.sh --version
|
|
10
10
|
set -u
|
|
11
11
|
|
|
12
|
-
VERSION="1.
|
|
12
|
+
VERSION="1.2.0"
|
|
13
13
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
14
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
15
15
|
# shellcheck source=bin/hoard-lib.sh
|
|
@@ -26,6 +26,14 @@ while [ $# -gt 0 ]; do case "$1" in --field) FIELD=${2-}; shift 2 ;; *) shift ;;
|
|
|
26
26
|
|
|
27
27
|
if [ "$ID" = "list" ]; then
|
|
28
28
|
n=$(grep -cE '^\s*-\s+id:' "$REG" 2>/dev/null || echo 0)
|
|
29
|
+
if [ "$FIELD" = "about" ]; then
|
|
30
|
+
printf 'projects[%s]{id,about}:\n' "$n"
|
|
31
|
+
awk '
|
|
32
|
+
/^[[:space:]]*-[[:space:]]+id:/ { sub(/^[[:space:]]*-[[:space:]]+id:[[:space:]]*/, ""); id=$0 }
|
|
33
|
+
/^[[:space:]]*about:/ { sub(/^[[:space:]]*about:[[:space:]]*"/, ""); sub(/"[[:space:]]*$/, ""); if (id != "") printf " \"%s\",\"%s\"\n", id, $0 }
|
|
34
|
+
' "$REG"
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
29
37
|
printf 'projects[%s]{id}:\n' "$n"
|
|
30
38
|
grep -E '^\s*-\s+id:' "$REG" | sed -E 's/^\s*-\s+id:\s*/ "/; s/\s*$/" /'
|
|
31
39
|
exit 0
|
|
@@ -41,15 +49,21 @@ block="$(awk -v id="$ID" '
|
|
|
41
49
|
|
|
42
50
|
gitline="$(printf '%s\n' "$block" | grep -m1 'git:')"
|
|
43
51
|
val() { printf '%s' "$gitline" | sed -nE "s/.*[ {,]?$1:[[:space:]]*([^,}]+).*/\1/p" | tr -d ' '; }
|
|
52
|
+
# non-git top-level keys resolve from the block itself, not the git line
|
|
53
|
+
blockval() { printf '%s' "$block" | sed -nE "s/^[[:space:]]*$1:[[:space:]]*([^,}]+).*/\1/p" | head -1 | tr -d ' '; }
|
|
44
54
|
|
|
45
55
|
host="$(val host)"; owner="$(val owner)"; repo="$(val repo)"; remote="$(val remote)"; br="$(val default_branch)"; auth="$(val auth)"
|
|
56
|
+
machine="$(blockval machine)"; company="$(blockval company)"; workspace="$(blockval workspace)"
|
|
57
|
+
about="$(printf '%s' "$block" | sed -nE 's/^[[:space:]]*about:[[:space:]]*"(.*)"[[:space:]]*$/\1/p' | head -1)"
|
|
46
58
|
if [ -n "$FIELD" ]; then
|
|
47
59
|
case "$FIELD" in
|
|
48
60
|
host) printf '%s\n' "$host" ;; owner) printf '%s\n' "$owner" ;; repo) printf '%s\n' "$repo" ;;
|
|
49
61
|
remote) printf '%s\n' "$remote" ;; default_branch) printf '%s\n' "$br" ;; auth) printf '%s\n' "$auth" ;;
|
|
62
|
+
machine) printf '%s\n' "$machine" ;; company) printf '%s\n' "$company" ;; workspace) printf '%s\n' "$workspace" ;;
|
|
63
|
+
about) printf '%s\n' "$about" ;;
|
|
50
64
|
*) printf 'error: unknown field %s\n' "$FIELD" >&2; exit 2 ;;
|
|
51
65
|
esac
|
|
52
66
|
exit 0
|
|
53
67
|
fi
|
|
54
|
-
printf 'project[1]{id,host,owner,repo,remote,default_branch,auth}:\n'
|
|
55
|
-
printf ' "%s","%s","%s","%s","%s","%s","%s"\n' "$ID" "$host" "$owner" "$repo" "$remote" "$br" "$auth"
|
|
68
|
+
printf 'project[1]{id,host,owner,repo,remote,default_branch,auth,machine,company,workspace}:\n'
|
|
69
|
+
printf ' "%s","%s","%s","%s","%s","%s","%s","%s","%s","%s"\n' "$ID" "$host" "$owner" "$repo" "$remote" "$br" "$auth" "$machine" "$company" "$workspace"
|