@zerwiz/ymir 0.1.13 → 0.1.15
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/brokk.md +10 -9
- package/.agents/agents/forseti-reviewer.md +2 -2
- package/.agents/agents/mimir-planner.md +2 -2
- package/.agents/assets/agents/registry.md +22 -0
- package/.agents/config/agents.yaml +53 -0
- package/.agents/config/{cron.yaml → cron.yaml.example} +5 -3
- package/.agents/config/tailscale-sync.yaml +9 -0
- package/.agents/config/wedge-alarm +3 -0
- package/.agents/migrations/0003-private-data-separation.sh +4 -4
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +6 -1
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +5 -4
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +5 -5
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +59 -4
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +74 -338
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +1 -1
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +5 -3
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +1 -1
- package/.agents/skills/ratatoskr-a2a/SKILL.md +1 -1
- package/.agents/skills/tyr-check/SKILL.md +5 -5
- package/.agents/skills/ymir-host/SKILL.md +1 -1
- package/.agents/state/.wake-queue +0 -0
- package/.agents/state/.wake-queue.seq +1 -0
- package/.agents/state/.watcher-down +1 -0
- package/.agents/state/procevent-inbox/when-hall-button.1.adapter +1 -0
- package/.agents/state/procevent-inbox/when-hall-button.1.handled +0 -0
- package/.agents/state/procevent-inbox/when-hall-button.1.result +8 -0
- package/.agents/state/procevent-inbox/when-hall-button.2.adapter +1 -0
- package/.agents/state/procevent-inbox/when-hall-button.2.handled +0 -0
- package/.agents/state/procevent-inbox/when-hall-button.2.result +5 -0
- package/.agents/state/procevent-inbox/when-odrerir.1.adapter +1 -0
- package/.agents/state/procevent-inbox/when-odrerir.1.handled +0 -0
- package/.agents/state/procevent-inbox/when-odrerir.1.result +8 -0
- package/.agents/state/procevent-inbox/when-odrerir.2.adapter +1 -0
- package/.agents/state/procevent-inbox/when-odrerir.2.handled +0 -0
- package/.agents/state/procevent-inbox/when-odrerir.2.result +5 -0
- package/.agents/state/procevent-inbox/when-odrerir.3.adapter +1 -0
- package/.agents/state/procevent-inbox/when-odrerir.3.handled +0 -0
- package/.agents/state/procevent-inbox/when-odrerir.3.result +5 -0
- package/.agents/state/procevent-inbox/when-odrerir.4.adapter +1 -0
- package/.agents/state/procevent-inbox/when-odrerir.4.handled +0 -0
- package/.agents/state/procevent-inbox/when-odrerir.4.result +8 -0
- package/.agents/state/procevent-inbox/when-odrerir.5.adapter +1 -0
- package/.agents/state/procevent-inbox/when-odrerir.5.handled +0 -0
- package/.agents/state/procevent-inbox/when-odrerir.5.result +8 -0
- package/.agents/state/procevent-inbox/when-sessrumnir-cloth.1.adapter +1 -0
- package/.agents/state/procevent-inbox/when-sessrumnir-cloth.1.handled +0 -0
- package/.agents/state/procevent-inbox/when-sessrumnir-cloth.1.result +8 -0
- package/AGENTS.md +17 -10
- package/CHANGELOG.md +129 -0
- package/bin/a2a-mcp.sh +9 -2
- package/bin/app-lib.sh +8 -1
- package/bin/docs-guard.sh +1 -1
- package/bin/erindi-brief.sh +1 -1
- package/bin/hamr-harness.sh +1 -1
- package/bin/hoard-lib.sh +10 -0
- package/bin/mimir-bridge.py +8 -1
- package/bin/mimir-bridge.sh +7 -1
- package/bin/mimir-reflect.sh +8 -1
- package/bin/nornir-cron-start.sh +21 -1
- package/bin/nornir-job-bragi-scrape.sh +116 -0
- package/bin/nornir-job-daily-briefing.sh +7 -7
- package/bin/nornir-job-forgejo-git.sh +87 -0
- package/bin/nornir-job-observer.sh +5 -4
- package/bin/rodd-operational-input.sh +1 -1
- package/bin/saga-session-start.sh +4 -3
- package/bin/sessrumnir.sh +11 -0
- package/bin/syn-arm-pretool-check.sh +1 -1
- package/bin/syn-cd-pretool-check.sh +1 -1
- package/bin/syn-watch-arm.sh +1 -1
- package/bin/vor-crew-state.sh +1 -1
- package/bin/ymir-install.sh +47 -2
- package/bin/ymir-marketing-stack.sh +323 -0
- package/docs/runbooks/models.md +1 -1
- package/docs/workspaces.md +4 -0
- package/package.json +10 -4
- package/scripts/electron.sh +2 -2
- package/.agents/bus/messages.json +0 -1
- package/.agents/config/agents.machine.example.yaml +0 -16
|
@@ -0,0 +1 @@
|
|
|
1
|
+
when
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
when
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
when
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
when
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
when
|
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
when: when-sessrumnir-cloth
|
|
2
|
+
status: fired
|
|
3
|
+
detail: the condition held and the action exited 0
|
|
4
|
+
condition_polls: 105
|
|
5
|
+
action_exit: 0
|
|
6
|
+
output:
|
|
7
|
+
eindri-acclaim[1]{agent,detail}:
|
|
8
|
+
"sessrumnir-cloth","left working (herdr state flipped; read the pane: herdr agent read sessrumnir-cloth)"
|
package/AGENTS.md
CHANGED
|
@@ -89,7 +89,7 @@ governed[7]{path,load_first}:
|
|
|
89
89
|
"apps/hlidskjalf/**",".agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md"
|
|
90
90
|
"apps/odrerir/**",".agents/skills/galdr-ymirsystem/assets/odrerir-hall.md"
|
|
91
91
|
"bin/mimir*.sh | bin/mimir-bridge.py",".agents/skills/galdr-ymirsystem/assets/memory-well.md"
|
|
92
|
-
"bin/nornir-* | config/cron.yaml",".agents/skills/galdr-ymirsystem/assets/nornir-jobs.md"
|
|
92
|
+
"bin/nornir-* | config/cron.yaml*",".agents/skills/galdr-ymirsystem/assets/nornir-jobs.md"
|
|
93
93
|
"bin/valknut-load.sh | .pi/** | .opencode/**",".agents/skills/galdr-ymirsystem/assets/harness-integration/README.md"
|
|
94
94
|
"bin/smidja* | .agents/skills/smidja-factory/**",".agents/skills/galdr-ymirsystem/assets/smidja.md"
|
|
95
95
|
```
|
|
@@ -167,8 +167,8 @@ them may ever be written inside this repo** (see "Private data — YMIR_HOME").
|
|
|
167
167
|
outputs[7]{kind,path}:
|
|
168
168
|
"Business / strategy","$YMIR_HOME/hodd/identity/companies/"
|
|
169
169
|
"Marketing / social","$YMIR_HOME/hodd/workspaces/marketing/"
|
|
170
|
-
"Software specs","$YMIR_HOME/hodd/workspaces/
|
|
171
|
-
"Personal / schedules","$YMIR_HOME/hodd/workspaces/
|
|
170
|
+
"Software specs","$YMIR_HOME/hodd/workspaces/work/"
|
|
171
|
+
"Personal / schedules","$YMIR_HOME/hodd/workspaces/personal/"
|
|
172
172
|
"Daily logs","$YMIR_HOME/hodd/memory/daily/YYYY-MM-DD.md"
|
|
173
173
|
"Shared PUBLIC assets","midgard/"
|
|
174
174
|
"Global audit entries","$YMIR_HOME/hodd/memory/runes_audit.md"
|
|
@@ -207,7 +207,7 @@ isolation[8]{id,rule}:
|
|
|
207
207
|
- Redis pub/sub is the queue *under* the A2A task model (A2A = semantics, Redis =
|
|
208
208
|
throughput). Messages follow `.agents/bus/protocol.ts`.
|
|
209
209
|
- Every A2A message is observed into **Mimirsbrunn** and logged to **Runes**.
|
|
210
|
-
- **Status:** the native backbone is **planned, not built** — the plan is private at `
|
|
210
|
+
- **Status:** the native backbone is **planned, not built** — the plan is private at `memory/plans/mesh/25-ratatoskr-a2a.md` (in `$YMIR_HOME`). Today an external `a2abridge` daemon (A2A + MCP over Tailscale) is what runs; `.agents/bus/` is a stub. Way of Teams (`~/CodeP/wayofteams`) is the sold control plane; Ymir's daemon must also work standalone.
|
|
211
211
|
- Kaia orchestrates: dispatch Eindri as A2A tasks, recall memory before dispatch,
|
|
212
212
|
honour the anti-hallucination gate; specialists reach tools via MCP.
|
|
213
213
|
|
|
@@ -389,21 +389,28 @@ env-driven, **never** in this repo. The real layout:
|
|
|
389
389
|
|
|
390
390
|
```
|
|
391
391
|
YMIR_HOME/ ← private git repo (pushed to the user's private GitHub)
|
|
392
|
+
├── README.md / AGENTS.md # home map + the private home contract
|
|
392
393
|
├── hodd/ ← THE HOARD: all private data lives under here
|
|
393
|
-
│ ├── identity/ # projects.yaml, workspaces.yaml, companies/, realms
|
|
394
394
|
│ ├── data/ # operator, fleet, machines, inventories
|
|
395
|
-
│ ├── docs/ # masterplan,
|
|
395
|
+
│ ├── docs/ # masterplan.md, business/, daily/, server-knowledge/
|
|
396
|
+
│ ├── identity/ # companies/, realms, entity cards
|
|
397
|
+
│ ├── memory/ # well, daily logs, runes audit ledger
|
|
398
|
+
│ ├── plans/ # live working plans
|
|
396
399
|
│ ├── secrets/ # platform.env.age + age.key (the vault)
|
|
397
|
-
│ ├──
|
|
398
|
-
│ └──
|
|
400
|
+
│ ├── state/ # runtime state; stale-* backups
|
|
401
|
+
│ └── workspaces/ # marketing/ · personal/ · work/
|
|
402
|
+
├── memory/plans/<domain>/ # plan archive, domain-sorted (tracked)
|
|
399
403
|
├── config/ # agents.yaml and per-machine overlays
|
|
400
|
-
├── memory/ # the live engram (kaia.engram)
|
|
401
404
|
├── state/ # runtime state (ephemeral)
|
|
402
405
|
├── smidja/ # factory databases (ephemeral)
|
|
403
|
-
├── workspaces/ # work/, personal/, companies/
|
|
404
406
|
└── svartalfaheim/ # per-realm scoped material
|
|
405
407
|
```
|
|
406
408
|
|
|
409
|
+
> 2026-09-17: the home was re-laid out — plans moved from `hodd/docs/plans/`
|
|
410
|
+
> to `memory/plans/<domain>/`, workspaces moved under `hodd/workspaces/`, and
|
|
411
|
+
> dropped-from-git material rests in `hodd/state/stale-*`. The hodd example
|
|
412
|
+
> (`hodd/AGENTS.example.md`) teaches the shape.
|
|
413
|
+
|
|
407
414
|
`$YMIR_HOME/hodd/` **is** the private data path — there is no second, flat copy.
|
|
408
415
|
`bin/hoard-lib.sh` resolves it (`hoard_root`), and it is the single source of
|
|
409
416
|
truth: a script that needs the hoard calls it, never a hardcoded path.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,132 @@
|
|
|
1
1
|
|
|
2
|
+
|
|
3
|
+
## 2026-09-17 — the npm door stands the surfaces
|
|
4
|
+
|
|
5
|
+
- **`@zerwiz/ymir` now depends on its surfaces**: `@zerwiz/{hlidskjalf,
|
|
6
|
+
odrerir, sessrumnir, smidja-factory}` are declared dependencies (bump
|
|
7
|
+
`0.1.14` → `0.1.15`), so `npm i -g @zerwiz/ymir` (or a packaged install)
|
|
8
|
+
delivers the app shells under `node_modules/@zerwiz/*`, which `app-lib`
|
|
9
|
+
resolves. The audit's one gap — the platform carried no apps — is closed.
|
|
10
|
+
- **`@zerwiz/smidja-factory` `0.1.1` → `0.1.2`**: the visualizer's nanoid fix
|
|
11
|
+
and the eye's-home SKILL line reach npm.
|
|
12
|
+
|
|
13
|
+
## 2026-09-17 — the seat's name and the visualizer's home
|
|
14
|
+
|
|
15
|
+
- **omarchy, not omarchy-1** — the machine is `omarchy`; the fleet config docs
|
|
16
|
+
and the tailscale-sync map say so.
|
|
17
|
+
- **The Smíðja visualizer's home is the smidja repo clone** —
|
|
18
|
+
`apps/smidja-factory/apps/visualizer`; the skill tree carries no copy (the
|
|
19
|
+
stale build was moved aside). `smidja.md` (the governed asset), the smithy
|
|
20
|
+
SKILL, and the install asset all point at the clone.
|
|
21
|
+
|
|
22
|
+
## 2026-09-17 — the layout swept: every reader taught the new doors
|
|
23
|
+
|
|
24
|
+
- **bin/nornir-job-observer.sh** — masterplan, runes ledger, and the well now
|
|
25
|
+
resolve to the hoard homes (`hodd/docs/masterplan.md`,
|
|
26
|
+
`hodd/memory/runes_audit.md`, `hodd/memory/well`), overridable via
|
|
27
|
+
`BROKK_MASTERPLAN`/`BROKK_WELL_DIR`.
|
|
28
|
+
- **bin/saga-session-start.sh** — the Sága digest's open-orders read follows
|
|
29
|
+
the home (`hodd/docs/masterplan.md`).
|
|
30
|
+
- **bin/nornir-job-daily-briefing.sh** — message/comment paths updated to the
|
|
31
|
+
same doors.
|
|
32
|
+
- **bin/docs-guard.sh** — block message names both new homes (hodd/docs,
|
|
33
|
+
memory/plans).
|
|
34
|
+
- **.agents/agents/{brokk,forseti-reviewer,mimir-planner}.md** — the plan
|
|
35
|
+
shelf, masterplan, and append-only log pointers moved to their hoard
|
|
36
|
+
homes (`memory/plans/<domain>/`, `hodd/docs/*`).
|
|
37
|
+
- **.agents/skills/{tyr-check,ymir-host,ratatoskr-a2a}/SKILL.md** — the same
|
|
38
|
+
pointers, plus the Ratatoskr spec → `memory/plans/mesh/25-ratatoskr-a2a.md`
|
|
39
|
+
and `hodd/docs/a2a-runs.md`.
|
|
40
|
+
- **docs/workspaces.md** — status note: workspaces settled under
|
|
41
|
+
`hodd/workspaces/` (2026-09-17).
|
|
42
|
+
|
|
43
|
+
## 2026-09-17 — the home's re-laid layout taught to the system
|
|
44
|
+
|
|
45
|
+
- **The layout changed (2026-09-17):** plans filed by domain under
|
|
46
|
+
`$YMIR_HOME/memory/plans/<domain>/`, live plans in `hodd/plans/`,
|
|
47
|
+
workspaces under `hodd/workspaces/`, dropped-from-git material in
|
|
48
|
+
`hodd/state/stale-*`. The hodd example (`hodd/AGENTS.example.md`) now
|
|
49
|
+
teaches the new shape.
|
|
50
|
+
- **AGENTS.md** — the private-home tree block, the outputs table
|
|
51
|
+
(`hodd/workspaces/work/`, `hodd/workspaces/personal/`), and the Ratatoskr
|
|
52
|
+
plan pointer updated to the new homes.
|
|
53
|
+
- **bin/nornir-job-daily-briefing.sh** — the morning brief's masterplan and
|
|
54
|
+
plan-status reads follow the new layout (`hodd/docs/masterplan.md`,
|
|
55
|
+
`memory/plans`) with `BROKK_MASTERPLAN`/`BROKK_PLANS_DIR` overrides.
|
|
56
|
+
|
|
57
|
+
## 2026-09-17 — the installer has a design of its own
|
|
58
|
+
|
|
59
|
+
- **The result is rendered, not dumped.** The plan got the cloth and the install did not: twenty-nine raw TOON rows and a poem, which the Allfather rightly called zero design. Now the same rows are rendered for the eye on stderr — a rule, a verdict (*"it stands — 22 steps done · 7 warned · 1 skipped"*), every warning with its reason, and the skips in one line. The TOON stays the data on stdout; the design is for the person.
|
|
60
|
+
- **The consent shows the plan again.** `--colour` renders on stderr and I had sent *both* streams to `/dev/null`, so the plan was invisible at the very moment it was being accepted.
|
|
61
|
+
- **The seat can mend its own runtime**, and says why when it cannot — npm gates Electron postinstall, so a fresh install otherwise ends with a window that cannot open.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## 2026-09-17 — one well, in the hoard: every reader of Kaia's memory resolves the hoard store
|
|
65
|
+
|
|
66
|
+
- **The memory is ONE store and it lives in the hoard** —
|
|
67
|
+
`$YMIR_HOME/hodd/memory/kaia.engram` — never in the tree, never in a
|
|
68
|
+
migrated copy. `bin/hoard-lib.sh` gains `hoard_memory_store` (the one
|
|
69
|
+
resolver); every reader honors it or an explicit `ENGRAM_DB`:
|
|
70
|
+
`mimir-bridge.sh`/`mimir-bridge.py`, `mimir-reflect.sh`, `a2a-mcp.sh`,
|
|
71
|
+
`ymir-install.sh` step_memory, and the rendered `.pi/mcp.json.example`
|
|
72
|
+
(`__YMIR_HOME__/hodd/memory/kaia.engram`).
|
|
73
|
+
- **The duplicate is dead.** The old store (`$YMIR_HOME/memory/kaia.engram`,
|
|
74
|
+
plus the migration seed copy) is struck from this machine and the vault;
|
|
75
|
+
its 8 episodes were seeded into the hoard store first. The public ymir
|
|
76
|
+
repo's `.agents/memory/kaia.engram*` leftovers are deleted (already guarded
|
|
77
|
+
by `.gitignore`) and the 0003 migration now copies INTO the hoard.
|
|
78
|
+
- The Allfather's decision, accepted and recorded everywhere: the well must
|
|
79
|
+
travel between his computers, so it rides the private vault as one store.
|
|
80
|
+
|
|
81
|
+
galdr-reread: `.agents/skills/galdr-ymirsystem/assets/memory-well.md` — the
|
|
82
|
+
Store row, MCP command, law #4, and the Python recipe now name the hoard path.
|
|
83
|
+
|
|
84
|
+
## 2026-09-17 — the local git round joins the loom; the stack's doors are in the registry
|
|
85
|
+
|
|
86
|
+
- **`bin/nornir-job-forgejo-git.sh` (02:45)** — reads the local forge's open
|
|
87
|
+
issues each night (`$YMIR_HOME/config/forge.env` names the door; token
|
|
88
|
+
optional), writes a daily digest to the hoard, carves Rune
|
|
89
|
+
`forgejo / git.issues` — or `git.door-down` (exit 1) when the tunnel is
|
|
90
|
+
closed, so a dead door is seen at sunrise. Unarmed without a URL, honestly.
|
|
91
|
+
- **The marketing stack's doors are now in the registry** —
|
|
92
|
+
`marketing_doors[6]` (Mautic · Postiz · Activepieces · Forgejo · SearXNG ·
|
|
93
|
+
Grafana) with reach notes, mirrored to the Galdr asset. Agents (Bragi ·
|
|
94
|
+
Sindri) provision the stack for any user via
|
|
95
|
+
`bin/ymir-marketing-stack.sh`, or reach the Allfather's live server stack.
|
|
96
|
+
|
|
97
|
+
galdr-reread: `.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md` — §3.7
|
|
98
|
+
(Forgejo round) and §3.8 (marketing stack provisioner); registry mirrored.
|
|
99
|
+
|
|
100
|
+
## 2026-09-17 — the schedule is the user's: cron leaves the tree, lives in the home
|
|
101
|
+
|
|
102
|
+
- **The tracked tree no longer carries the live cron schedule.** The Nornir
|
|
103
|
+
scheduler now resolves the schedule in this order: `BROKK_CONFIG_OVERRIDE`
|
|
104
|
+
(explicit) → **the home's own `$YMIR_HOME/config/cron.yaml`** (users set
|
|
105
|
+
their jobs there) → the repo's `config/cron.yaml.example` (a template, never
|
|
106
|
+
the live list). `bin/nornir-cron-start.sh` resolves the home through
|
|
107
|
+
`bin/hoard-lib.sh`, the one answer the whole runtime shares.
|
|
108
|
+
- `config/cron.yaml` → `config/cron.yaml.example` (the tracked template stays;
|
|
109
|
+
the live schedule lives in the operator's home).
|
|
110
|
+
- **The marketing stack can be provisioned on any computer** —
|
|
111
|
+
`bin/ymir-marketing-stack.sh up|status|down|doors` stands Mautic + Postiz +
|
|
112
|
+
Activepieces (+ optionally Forgejo) from the same OSS engines the server
|
|
113
|
+
runs, env-driven, ports virtualized, secrets generated once into the home
|
|
114
|
+
(never inline); agents (Bragi · Sindri) provision it for any user.
|
|
115
|
+
- **Bragi's scrape round, honest to Firecrawl** — `bin/nornir-job-bragi-scrape.sh`
|
|
116
|
+
reads the operator's `config/scrape-sources.yaml` and scrapes with the real
|
|
117
|
+
Firecrawl SDK (scrape/search + markdown, BYOK key from the home's secrets),
|
|
118
|
+
landing clean markdown in the marketing workspace and carving a Rune.
|
|
119
|
+
|
|
120
|
+
galdr-reread: `.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md` — §2
|
|
121
|
+
rewritten (the schedule is the user's; example template; the four-inputs
|
|
122
|
+
contract unchanged).
|
|
123
|
+
|
|
124
|
+
## 2026-09-17 — 0.1.14: the panels reach the gate, and the fourth window ships
|
|
125
|
+
|
|
126
|
+
- **`@zerwiz/hlidskjalf` 0.1.1** — the package now declares its own `name`, `files` and the absence of `private`, and ships **`vite.config.ts`**. Without that config a packaged SPA is served by `vite preview` with **no `/api` proxy**, so every panel answered `index.html` and died on `Unexpected token '<'<`. That was the whole panel fault.
|
|
127
|
+
- **`@zerwiz/odrerir` 0.1.1** — ships its **`electron/`** half, so the fourth hall can have a window; its name is `@zerwiz/odrerir` and it is no longer marked private (npm refuses those, EPRIVATE).
|
|
128
|
+
- Both apps declared what ships **in their own repos**, instead of a manifest rewritten at publish time — which is how the wrong things shipped in the first place.
|
|
129
|
+
|
|
2
130
|
## 2026-09-17 — 0.1.13: the re-voiced seat arrives, and the packages are listed
|
|
3
131
|
|
|
4
132
|
- **`@zerwiz/sessrumnir` 0.1.9** — the seat that says Sessrúmnir and "Brokk is working", with the fork`s name gone from its source, its bundles, its installer and its package identity. Pinned so a fresh install receives it.
|
|
@@ -17,6 +145,7 @@
|
|
|
17
145
|
old voice through the `0.1.8-alpha` pin.
|
|
18
146
|
- **`@zerwiz/ymir@0.1.13`** is the packet that carries them.
|
|
19
147
|
|
|
148
|
+
|
|
20
149
|
## 2026-09-17 — 0.1.12: the icons reach the live tree, and a missing surface is a failure
|
|
21
150
|
|
|
22
151
|
- **The launcher entries name the door** (`Exec=ymir hlidskjalf`, …) instead of an absolute path into whichever tree minted them — the fault that made every icon do nothing.
|
package/bin/a2a-mcp.sh
CHANGED
|
@@ -36,8 +36,15 @@ if [ "$PROJECT" = 1 ]; then PI_MCP="$ROOT/.pi/mcp.json"; else PI_MCP="$HOME/.pi/
|
|
|
36
36
|
|
|
37
37
|
WOTES="$(command -v wayofteams-mcp 2>/dev/null || true)"
|
|
38
38
|
ENGRAM_BIN="${ENGRAM_BIN:-$HOME/.local/bin/engram-mcp}"
|
|
39
|
-
|
|
40
|
-
ENGRAM_DB
|
|
39
|
+
# The well is ONE memory and it lives in the hoard — always. An explicit
|
|
40
|
+
# ENGRAM_DB is the operator's escape hatch; without it the hoard decides.
|
|
41
|
+
if [ -n "${ENGRAM_DB:-}" ]; then
|
|
42
|
+
ENGRAM_DB_ABS="$ENGRAM_DB"
|
|
43
|
+
else
|
|
44
|
+
. "$SCRIPT_DIR/hoard-lib.sh" 2>/dev/null || true
|
|
45
|
+
hoard_memory_store ENGRAM_DB_ABS
|
|
46
|
+
fi
|
|
47
|
+
ENGRAM_DB="$ENGRAM_DB_ABS"
|
|
41
48
|
|
|
42
49
|
# The Teams plane (WayOfTeams) and Anchor memory are REMOTE MCP servers, named by
|
|
43
50
|
# URL. The URL is credential-ish, so it comes from the environment — never the
|
package/bin/app-lib.sh
CHANGED
|
@@ -49,7 +49,14 @@ app_dir() { # <surface> <result-var> — a clone's apps/<x>, else the package
|
|
|
49
49
|
"$_apd_root/node_modules/@zerwiz/$_apd_pkg" \
|
|
50
50
|
"$(dirname "$_apd_root")/$_apd_pkg"
|
|
51
51
|
do
|
|
52
|
-
|
|
52
|
+
# A hollow directory is not an app. An install step used to `mkdir` — or a
|
|
53
|
+
# clone used to fail — leaving `apps/<name>` empty, and that empty shell
|
|
54
|
+
# shadowed the real package beside it until the SPA could not be served and
|
|
55
|
+
# nobody knew why. An app declares itself: a manifest, a build, or sources.
|
|
56
|
+
if [ -d "$_apd_c" ] \
|
|
57
|
+
&& { [ -f "$_apd_c/package.json" ] || [ -d "$_apd_c/dist" ] || [ -d "$_apd_c/src" ]; }; then
|
|
58
|
+
printf -v "$_apd_rv" '%s' "$_apd_c"; return 0
|
|
59
|
+
fi
|
|
53
60
|
done
|
|
54
61
|
printf -v "$_apd_rv" '%s' ""
|
|
55
62
|
return 1
|
package/bin/docs-guard.sh
CHANGED
|
@@ -35,5 +35,5 @@ if [ "${1:-}" = "--all" ]; then
|
|
|
35
35
|
scan_list < <(git -C "$ROOT" ls-files)
|
|
36
36
|
else
|
|
37
37
|
scan_list < <(git -C "$ROOT" diff --cached --name-only --diff-filter=ACM)
|
|
38
|
-
fi || { printf 'docs-guard: blocked — docs/ is public; move it to $YMIR_HOME/hodd/docs/ (Rule 04)\n' >&2; exit 1; }
|
|
38
|
+
fi || { printf 'docs-guard: blocked — docs/ is public; move it to $YMIR_HOME/hodd/docs/ (or $YMIR_HOME/memory/plans/ for plans) (Rule 04)\n' >&2; exit 1; }
|
|
39
39
|
exit 0
|
package/bin/erindi-brief.sh
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# readable "Delivery contract: mode=<mode>" line, the worktree-isolation
|
|
7
7
|
# assertion, the status protocol, and the steering-inbox receive/ack section.
|
|
8
8
|
# Ported from the upstream distro brief scaffold for plan 29
|
|
9
|
-
# (
|
|
9
|
+
# (memory/plans/core/29-brokk-distro-runtime.md).
|
|
10
10
|
#
|
|
11
11
|
# Usage:
|
|
12
12
|
# erindi-brief.sh <task-id> <repo-name> --mode <no-mistakes|direct-PR|local-only>
|
package/bin/hamr-harness.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# runtime the process tree is wearing so Brokk can choose the correct adapter,
|
|
6
6
|
# launch flags, and dispatch profile. Ported from the upstream agent-distro
|
|
7
7
|
# reference (bin/hamr-harness.sh) and retargeted to the Brokk runtime
|
|
8
|
-
# (
|
|
8
|
+
# (memory/plans/core/29-brokk-distro-runtime.md).
|
|
9
9
|
#
|
|
10
10
|
# Usage:
|
|
11
11
|
# hamr-harness.sh print own harness:
|
package/bin/hoard-lib.sh
CHANGED
|
@@ -103,3 +103,13 @@ hoard_local_env() { # <result-var> — the operator's env file (secrets, ports)
|
|
|
103
103
|
ymir_home_root home
|
|
104
104
|
printf -v "$result_var" '%s' "${YMIR_ENV_FILE:-$home/.env.local}"
|
|
105
105
|
}
|
|
106
|
+
|
|
107
|
+
hoard_memory_store() { # <result-var> — Kaia's engram store, ALWAYS in the hoard
|
|
108
|
+
# The well is one memory and it lives in the hoard (hodd/memory) — never in
|
|
109
|
+
# the tree, never in a migrated copy. Everything that reads the store resolves
|
|
110
|
+
# through here so no script can point the well at a duplicate (Rule 04/07).
|
|
111
|
+
local result_var=${1-} home
|
|
112
|
+
[ -n "$result_var" ] || return 2
|
|
113
|
+
ymir_home_root home
|
|
114
|
+
printf -v "$result_var" '%s' "$home/hodd/memory/kaia.engram"
|
|
115
|
+
}
|
package/bin/mimir-bridge.py
CHANGED
|
@@ -29,7 +29,14 @@ from pathlib import Path
|
|
|
29
29
|
from urllib.parse import parse_qs, urlparse
|
|
30
30
|
|
|
31
31
|
ROOT = Path(__file__).resolve().parent.parent
|
|
32
|
-
|
|
32
|
+
# The well is ONE memory and it lives in the hoard — never in the tree and
|
|
33
|
+
# never in a migrated copy. The shell bridge passes ENGRAM_DB; this default
|
|
34
|
+
# (hoard | legacy-local) keeps a hand-started bridge honest either way.
|
|
35
|
+
_home = Path(os.environ.get("YMIR_HOME", str(Path.home() / "Documents/Ymir")))
|
|
36
|
+
STORE = Path(os.environ.get(
|
|
37
|
+
"ENGRAM_DB",
|
|
38
|
+
str(_home / "hodd/memory/kaia.engram")
|
|
39
|
+
)).expanduser()
|
|
33
40
|
HOST = os.environ.get("MIMIRSBRUNN_HOST", "127.0.0.1")
|
|
34
41
|
PORT = int(os.environ.get("MIMIRSBRUNN_PORT", os.environ.get("ENGRAM_PORT", "4602")))
|
|
35
42
|
DEFAULT_AGENT = os.environ.get("ENGRAM_AGENT_ID", "kaia")
|
package/bin/mimir-bridge.sh
CHANGED
|
@@ -33,7 +33,13 @@ hoard_state_dir YMIR_STATE_DIR
|
|
|
33
33
|
hoard_data_dir YMIR_DATA_DIR
|
|
34
34
|
YMIR_HOME="${YMIR_HOME:-$HOME/Documents/Ymir}"
|
|
35
35
|
BRIDGE="$ROOT/bin/mimir-bridge.py"
|
|
36
|
-
|
|
36
|
+
# The well is ONE memory and it lives in the hoard — always. An explicit
|
|
37
|
+
# ENGRAM_DB is the operator's escape hatch; without it the hoard decides.
|
|
38
|
+
if [ -n "${ENGRAM_DB:-}" ]; then
|
|
39
|
+
DB="$ENGRAM_DB"
|
|
40
|
+
else
|
|
41
|
+
hoard_memory_store DB
|
|
42
|
+
fi
|
|
37
43
|
PORT="${MIMIRSBRUNN_PORT:-4602}"
|
|
38
44
|
PID_FILE="${YMIR_STATE_DIR:-$YMIR_HOME/state}/mimir-bridge.pid"
|
|
39
45
|
LOG_FILE="${YMIR_STATE_DIR:-$YMIR_HOME/state}/mimir-bridge.log"
|
package/bin/mimir-reflect.sh
CHANGED
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
# YMIR_ROUTER_URL=http://127.0.0.1:8080/v1 YMIR_REFLECT_MODEL=qwen3.6-35b-a3b bin/mimir-reflect.sh
|
|
12
12
|
set -euo pipefail
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# The well is ONE memory and it lives in the hoard — always (Rule 04/07).
|
|
15
|
+
# An explicit YMIR_WELL/ENGRAM_DB is the operator's escape hatch.
|
|
16
|
+
if [ -n "${YMIR_WELL:-}" ] || [ -n "${ENGRAM_DB:-}" ]; then
|
|
17
|
+
WELL="${YMIR_WELL:-${ENGRAM_DB:-}}"
|
|
18
|
+
else
|
|
19
|
+
. "$SCRIPT_DIR/hoard-lib.sh" 2>/dev/null || true
|
|
20
|
+
hoard_memory_store WELL
|
|
21
|
+
fi
|
|
15
22
|
BASE="${YMIR_ROUTER_URL:-http://127.0.0.1:1234/v1}"
|
|
16
23
|
MODEL="${YMIR_REFLECT_MODEL:-apodex-1.0-mini}"
|
|
17
24
|
|
package/bin/nornir-cron-start.sh
CHANGED
|
@@ -34,8 +34,28 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
34
34
|
ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
35
35
|
BROKK_HOME="${BROKK_HOME:-$ROOT}"
|
|
36
36
|
STATE="${BROKK_STATE_OVERRIDE:-$BROKK_HOME/state}"
|
|
37
|
-
|
|
37
|
+
# The schedule is the USER'S, not the distro's: an explicit BROKK_CONFIG_OVERRIDE
|
|
38
|
+
# wins, then the home's own config/cron.yaml, then the repo's example (which
|
|
39
|
+
# ships what a fresh hour suggests but never claims to be the live schedule).
|
|
40
|
+
if [ -n "${BROKK_CONFIG_OVERRIDE:-}" ]; then
|
|
41
|
+
CONFIG="$BROKK_CONFIG_OVERRIDE"
|
|
42
|
+
else
|
|
43
|
+
. "$SCRIPT_DIR/hoard-lib.sh" # ymir_home_root — one answer, never drift
|
|
44
|
+
ymir_home_root _h
|
|
45
|
+
if [ -r "$_h/config/cron.yaml" ]; then
|
|
46
|
+
CONFIG="$_h/config"
|
|
47
|
+
elif [ -r "$BROKK_HOME/config/cron.yaml" ] && [ "$BROKK_HOME" != "$_h" ]; then
|
|
48
|
+
CONFIG="$BROKK_HOME/config"
|
|
49
|
+
else
|
|
50
|
+
CONFIG="$BROKK_HOME/config"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
38
53
|
CRON_CONFIG="$CONFIG/cron.yaml"
|
|
54
|
+
# Fall back to the shipped example ONLY when no user schedule exists yet — the
|
|
55
|
+
# example is a template, never the live job list.
|
|
56
|
+
if [ ! -r "$CRON_CONFIG" ] && [ -r "$BROKK_HOME/config/cron.yaml.example" ]; then
|
|
57
|
+
CRON_CONFIG="$BROKK_HOME/config/cron.yaml.example"
|
|
58
|
+
fi
|
|
39
59
|
PID_FILE="$STATE/cron.pid"
|
|
40
60
|
LOG_FILE="$STATE/cron.log"
|
|
41
61
|
STAMP_DIR="$STATE/.cron-fired"
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# nornir-job-bragi-scrape.sh — Bragi, on the loom: the scheduled scrape round.
|
|
3
|
+
#
|
|
4
|
+
# Reads a list of sources from the USER'S home (never the tree) and scrapes
|
|
5
|
+
# each with Firecrawl into clean markdown, landed in the marketing workspace.
|
|
6
|
+
# The source list is the operator's: $YMIR_HOME/config/scrape-sources.yaml
|
|
7
|
+
#
|
|
8
|
+
# format:
|
|
9
|
+
# sources:
|
|
10
|
+
# - name: monster-stack
|
|
11
|
+
# url: https://example.com/docs
|
|
12
|
+
# kind: scrape|search # default scrape
|
|
13
|
+
# note: why this source matters
|
|
14
|
+
#
|
|
15
|
+
# Firecrawl is BYOK: FIRECRAWL_API_KEY comes from the home's platform.env
|
|
16
|
+
# (bin/hodd.sh emit), never inline. A missing key is reported, not faked — the
|
|
17
|
+
# job is honest about what it could not do.
|
|
18
|
+
#
|
|
19
|
+
# Stateless: read sources → scrape → write markdown under the marketing
|
|
20
|
+
# workspace → carve one Rune → exit.
|
|
21
|
+
set -u
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
ROOT="${BROKK_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
24
|
+
|
|
25
|
+
# The home the operator chose — one answer, never drift (Rule 04/07).
|
|
26
|
+
if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
|
|
27
|
+
_yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
|
|
29
|
+
[ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
|
|
30
|
+
done
|
|
31
|
+
unset _yr _yc
|
|
32
|
+
fi
|
|
33
|
+
hoard_root _HOME
|
|
34
|
+
hoard_data_dir _DATA
|
|
35
|
+
|
|
36
|
+
SOURCES="${BROKK_SCRAPE_SOURCES:-$_HOME/config/scrape-sources.yaml}"
|
|
37
|
+
OUT_DIR="${BROKK_SCRAPE_OUT:-$_HOME/workspaces/marketing/scraped}"
|
|
38
|
+
mkdir -p "$OUT_DIR"
|
|
39
|
+
|
|
40
|
+
# Firecrawl key: from the home's secrets, by path — never inline.
|
|
41
|
+
FIRECRAWL_API_KEY="${FIRECRAWL_API_KEY:-}"
|
|
42
|
+
if [ -z "$FIRECRAWL_API_KEY" ] && [ -x "$ROOT/bin/hodd.sh" ]; then
|
|
43
|
+
FIRECRAWL_API_KEY="$("$ROOT/bin/hodd.sh" emit secrets/platform.env 2>/dev/null | grep -m1 '^FIRECRAWL_API_KEY=' | cut -d= -f2-)"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
[ -r "$SOURCES" ] || {
|
|
47
|
+
printf 'bragi-scrape: no sources at %s — nothing scheduled to scrape (exit 0)\n' "$SOURCES"
|
|
48
|
+
exit 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
command -v python3 >/dev/null 2>&1 || { printf 'error: python3 required for Firecrawl SDK\n'; exit 1; }
|
|
52
|
+
|
|
53
|
+
# Parse the YAML sources (name/url/kind) and scrape each with the SDK.
|
|
54
|
+
TODAY="$(date -u +%Y-%m-%d)"
|
|
55
|
+
ok=0; failed=0; skipped=0
|
|
56
|
+
rows="$(python3 - "$SOURCES" <<'PY'
|
|
57
|
+
import sys, re
|
|
58
|
+
txt = open(sys.argv[1]).read()
|
|
59
|
+
items = []
|
|
60
|
+
for m in re.finditer(r'^\s*-\s+name:\s*(\S+)\s*$', txt, re.M):
|
|
61
|
+
start = m.end()
|
|
62
|
+
name = m.group(1)
|
|
63
|
+
url = kind = ""
|
|
64
|
+
for line in txt[start:].splitlines():
|
|
65
|
+
line = line.strip()
|
|
66
|
+
if line.startswith("- name:") : break
|
|
67
|
+
if line.startswith("url:") and not url: url = line[4:].strip()
|
|
68
|
+
if line.startswith("kind:") and not kind: kind = line[5:].strip()
|
|
69
|
+
items.append((name, url, kind or "scrape"))
|
|
70
|
+
for i in items: print("|".join(i))
|
|
71
|
+
PY
|
|
72
|
+
)"
|
|
73
|
+
|
|
74
|
+
if [ -z "$FIRECRAWL_API_KEY" ]; then
|
|
75
|
+
failed=$failed; skipped=1
|
|
76
|
+
printf 'bragi-scrape: FIRECRAWL_API_KEY absent — sources listed, nothing scraped\n'
|
|
77
|
+
# still record the rune honestly
|
|
78
|
+
else
|
|
79
|
+
while IFS='|' read -r name url kind; do
|
|
80
|
+
[ -n "$name" ] || continue
|
|
81
|
+
out="$OUT_DIR/$TODAY-$name.md"
|
|
82
|
+
if python3 - "$url" "$kind" "$out" "$FIRECRAWL_API_KEY" <<'PY'
|
|
83
|
+
import os, sys
|
|
84
|
+
url, kind, out, key = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
85
|
+
try:
|
|
86
|
+
from firecrawl import FirecrawlApp
|
|
87
|
+
except ImportError:
|
|
88
|
+
sys.exit(2) # engine not installed
|
|
89
|
+
app = FirecrawlApp(api_key=key)
|
|
90
|
+
try:
|
|
91
|
+
doc = app.scrape_url(url, params={"formats": ["markdown"]}) if kind == "scrape" \
|
|
92
|
+
else app.search(url, params={"formats": ["markdown"]})
|
|
93
|
+
text = doc.get("markdown", "") if isinstance(doc, dict) else str(doc)
|
|
94
|
+
with open(out, "w") as f:
|
|
95
|
+
f.write("<!-- source: %s ; kind: %s ; fetched: %s -->\n\n%s\n" % (url, kind, __import__("datetime").datetime.utcnow().isoformat() + "Z", text))
|
|
96
|
+
sys.exit(0)
|
|
97
|
+
except Exception as exc:
|
|
98
|
+
print("scrape error: %s" % exc, file=sys.stderr)
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
PY
|
|
101
|
+
then ok=$((ok+1))
|
|
102
|
+
elif [ $? -eq 2 ]; then skipped=$((skipped+1))
|
|
103
|
+
else failed=$((failed+1)); fi
|
|
104
|
+
done <<< "$rows"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
printf 'bragi-scrape: sources=%s ok=%s failed=%s skipped=%s out=%s\n' \
|
|
108
|
+
"$(printf '%s\n' "$rows" | grep -c .)" "$ok" "$failed" "$skipped" "$OUT_DIR"
|
|
109
|
+
|
|
110
|
+
# Carve the Rune — the ledger lives in the hoard.
|
|
111
|
+
# shellcheck source=bin/runes-append.sh
|
|
112
|
+
. "$ROOT/bin/runes-append.sh"
|
|
113
|
+
runes_append "bragi" "scrape.round" --realm "${BROKK_REALM:-}" \
|
|
114
|
+
--message "scrape round: ok=$ok failed=$failed skipped=$skipped -> $OUT_DIR" >/dev/null \
|
|
115
|
+
|| printf 'bragi-scrape: rune append failed (non-fatal)\n'
|
|
116
|
+
[ "$failed" = 0 ] && exit 0 || exit 1
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# briefing — no model call — from four grounded inputs:
|
|
6
6
|
# 1. open forge orders parsed from docs/masterplan.md (append-only source)
|
|
7
7
|
# 2. live fleet/state under state/ (cron, metas, status, session lock)
|
|
8
|
-
# 3. active plan status from
|
|
8
|
+
# 3. active plan status from memory/plans/*.md
|
|
9
9
|
# 4. today's Runes tail from workspace/memory/runes_audit.md
|
|
10
10
|
#
|
|
11
11
|
# Output: svartalfaheim/<realm>/workspace/memory/daily/YYYY-MM-DD.md
|
|
@@ -40,8 +40,8 @@ BRIEF_DIR="${BROKK_BRIEF_DIR:-${YMIR_HOME:-$HOME/Documents/Ymir}/svartalfaheim/$
|
|
|
40
40
|
BRIEF_FILE="$BRIEF_DIR/$DATE.md"
|
|
41
41
|
MAX_ORDERS="${BROKK_BRIEF_MAX_ORDERS:-15}"
|
|
42
42
|
MAX_RUNES="${BROKK_BRIEF_MAX_RUNES:-12}"
|
|
43
|
-
MASTERPLAN="$
|
|
44
|
-
PLANS_DIR="$
|
|
43
|
+
MASTERPLAN="${BROKK_MASTERPLAN:-${YMIR_HOME:-$HOME/Documents/Ymir}/hodd/docs/masterplan.md}"
|
|
44
|
+
PLANS_DIR="${BROKK_PLANS_DIR:-${YMIR_HOME:-$HOME/Documents/Ymir}/memory/plans}"
|
|
45
45
|
|
|
46
46
|
mkdir -p "$BRIEF_DIR" || { printf 'error: cannot create %s\n' "$BRIEF_DIR"; exit 1; }
|
|
47
47
|
TMP="$BRIEF_DIR/.$DATE.md.tmp.$$"
|
|
@@ -90,11 +90,11 @@ if [ -r "$MASTERPLAN" ]; then
|
|
|
90
90
|
done
|
|
91
91
|
if [ "${total:-0}" -gt "$MAX_ORDERS" ]; then
|
|
92
92
|
emit ""
|
|
93
|
-
emit "_(truncated — ${total} open orders total; see docs/masterplan.md §3)_"
|
|
93
|
+
emit "_(truncated — ${total} open orders total; see hodd/docs/masterplan.md §3)_"
|
|
94
94
|
fi
|
|
95
95
|
rm -f "$orders_tmp"
|
|
96
96
|
else
|
|
97
|
-
emit "ABSENT: docs/masterplan.md not readable"
|
|
97
|
+
emit "ABSENT: hodd/docs/masterplan.md not readable"
|
|
98
98
|
fi
|
|
99
99
|
emit ""
|
|
100
100
|
|
|
@@ -105,7 +105,7 @@ if [ -r "$MASTERPLAN" ]; then
|
|
|
105
105
|
awk '/^## 2\. Active Queue/ {on=1; next} /^## 3\./ {on=0} on && /^[0-9]+\. / {print}' "$MASTERPLAN" \
|
|
106
106
|
| while IFS= read -r q; do emit "- ${q#* }"; done
|
|
107
107
|
else
|
|
108
|
-
emit "ABSENT: docs/masterplan.md not readable"
|
|
108
|
+
emit "ABSENT: hodd/docs/masterplan.md not readable"
|
|
109
109
|
fi
|
|
110
110
|
emit ""
|
|
111
111
|
|
|
@@ -156,7 +156,7 @@ if [ -d "$PLANS_DIR" ]; then
|
|
|
156
156
|
emit "- \`$pname\` — ${pstatus:-unknown}"
|
|
157
157
|
done
|
|
158
158
|
else
|
|
159
|
-
emit "ABSENT:
|
|
159
|
+
emit "ABSENT: memory/plans/ not readable"
|
|
160
160
|
fi
|
|
161
161
|
emit ""
|
|
162
162
|
|