@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.
Files changed (118) 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 +215 -23
  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 +1033 -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 +153 -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/eir-doctor.sh +47 -1
  55. package/bin/feature-inventory.sh +73 -0
  56. package/bin/fleet-apply.sh +15 -2
  57. package/bin/github-deploy.sh +14 -1
  58. package/bin/gjallarhorn-expose.sh +43 -9
  59. package/bin/gjallarhorn-notify.sh +14 -1
  60. package/bin/gjallarhorn-purge.sh +14 -1
  61. package/bin/gjallarhorn-tunnel.sh +16 -3
  62. package/bin/groa-update.sh +9 -1
  63. package/bin/hall-snapshot.sh +21 -2
  64. package/bin/herdr-run.sh +23 -2
  65. package/bin/hlidskjalf-agents.sh +172 -0
  66. package/bin/hlidskjalf-usage.sh +196 -0
  67. package/bin/hoard-guard.sh +135 -0
  68. package/bin/hoard-lib.sh +91 -11
  69. package/bin/hodd.sh +28 -4
  70. package/bin/huginn-research-worker.sh +14 -1
  71. package/bin/local-model-lock.sh +22 -3
  72. package/bin/mimir-bridge.py +33 -0
  73. package/bin/mimir-bridge.sh +14 -1
  74. package/bin/mimir-reflect.sh +27 -0
  75. package/bin/mjollnir-webhook.sh +14 -1
  76. package/bin/mjollnir.sh +22 -3
  77. package/bin/model-hardware.sh +17 -3
  78. package/bin/model-resolve.sh +14 -1
  79. package/bin/nornir-job-hall-snapshot.sh +28 -0
  80. package/bin/nornir-job-nsr-compliance.sh +54 -0
  81. package/bin/npm-publish.sh +136 -0
  82. package/bin/omarchy-install.sh +18 -5
  83. package/bin/omarchy-plugins.sh +15 -2
  84. package/bin/omarchy-sense.sh +14 -1
  85. package/bin/pi-model-agnostic.sh +71 -0
  86. package/bin/private-guard.sh +20 -0
  87. package/bin/project-git.sh +18 -4
  88. package/bin/public-guard.sh +3 -2
  89. package/bin/sessrumnir-ensure.sh +1 -1
  90. package/bin/sessrumnir.sh +16 -3
  91. package/bin/smidja-bootstrap.sh +5 -1
  92. package/bin/syn-asset-pretool-check.sh +14 -1
  93. package/bin/syn-spawn-pretool-check.sh +58 -0
  94. package/bin/tailscale-sync.sh +15 -2
  95. package/bin/telegram-bot.sh +14 -1
  96. package/bin/ui-truth-check.sh +75 -0
  97. package/bin/valknut-load.sh +42 -4
  98. package/bin/wedge-notify.sh +14 -1
  99. package/bin/workspace-rag.sh +14 -1
  100. package/bin/ymir-install.sh +219 -54
  101. package/bin/ymir-plan.sh +356 -0
  102. package/bin/ymir-say.sh +14 -1
  103. package/bin/ymir-setup-auth.sh +12 -1
  104. package/bin/ymir-validate.sh +28 -8
  105. package/docs/lore.md +5 -1
  106. package/docs/runbooks/agents.md +1 -1
  107. package/package.json +13 -3
  108. package/scripts/electron.sh +74 -15
  109. package/scripts/lower.sh +34 -0
  110. package/scripts/raise.sh +42 -0
  111. package/scripts/start.sh +52 -2
  112. package/.agents/config/agents.yaml +0 -50
  113. package/.agents/config/tailscale-sync.yaml +0 -27
  114. package/.agents/memory/kaia.engram +0 -0
  115. package/.agents/memory/kaia.engram-shm +0 -0
  116. package/.agents/memory/kaia.engram-wal +0 -0
  117. package/.agents/memory/well/episodes.jsonl +0 -367
  118. 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"
@@ -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 ───────────────────────────────────────
@@ -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="$ROOT/state/omarchy-catalog.json"
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="$ROOT/state/omarchy-catalog.json"
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
@@ -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
- STATE_DIR="${BROKK_STATE_OVERRIDE:-$ROOT/state}"
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
@@ -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"
@@ -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.0.0"
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"