create-egregore 0.19.5 → 0.19.6

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 (130) hide show
  1. package/assets/egregore-launcher.js +407 -25
  2. package/bin/cli.js +34 -20
  3. package/lib/local.js +21 -5
  4. package/lib/pi-runtime.js +186 -0
  5. package/lib/setup.js +56 -11
  6. package/lib/ui.js +1 -1
  7. package/package.json +7 -5
  8. package/runtime/codex/.codex/spec-manifest.json +1 -1
  9. package/runtime/codex/AGENTS.md +7 -2
  10. package/runtime/codex/bin/codex-render-spec.mjs +6 -1
  11. package/runtime/codex/bin/codex-session-start.sh +5 -59
  12. package/runtime/codex/bin/codex-skill-render.mjs +7 -5
  13. package/runtime/codex/bin/lib/context.sh +8 -1
  14. package/runtime/codex/bin/lib/greeting.sh +12 -108
  15. package/runtime/codex/bin/lib/metrics.sh +374 -0
  16. package/runtime/codex/bin/publish-artifact.sh +14 -0
  17. package/runtime/codex/bin/settings.sh +274 -0
  18. package/runtime/codex/manifest.json +2 -0
  19. package/runtime/pi/.codex/config.toml +15 -0
  20. package/runtime/pi/.codex/hooks/branch-guard.js +242 -0
  21. package/runtime/pi/.codex/hooks/search-hint.js +65 -0
  22. package/runtime/pi/.codex/hooks.json +30 -0
  23. package/runtime/pi/.codex/skills/activity/SKILL.md +70 -0
  24. package/runtime/pi/.codex/skills/add/SKILL.md +25 -0
  25. package/runtime/pi/.codex/skills/announce/SKILL.md +54 -0
  26. package/runtime/pi/.codex/skills/answer/SKILL.md +25 -0
  27. package/runtime/pi/.codex/skills/archive/SKILL.md +41 -0
  28. package/runtime/pi/.codex/skills/ask/SKILL.md +59 -0
  29. package/runtime/pi/.codex/skills/branch/SKILL.md +25 -0
  30. package/runtime/pi/.codex/skills/checkup/SKILL.md +41 -0
  31. package/runtime/pi/.codex/skills/commit/SKILL.md +25 -0
  32. package/runtime/pi/.codex/skills/contribute/SKILL.md +25 -0
  33. package/runtime/pi/.codex/skills/dark-mode/SKILL.md +25 -0
  34. package/runtime/pi/.codex/skills/dashboard/SKILL.md +59 -0
  35. package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +74 -0
  36. package/runtime/pi/.codex/skills/delete-user/SKILL.md +25 -0
  37. package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +25 -0
  38. package/runtime/pi/.codex/skills/emissary/SKILL.md +41 -0
  39. package/runtime/pi/.codex/skills/env/SKILL.md +25 -0
  40. package/runtime/pi/.codex/skills/handoff/SKILL.md +67 -0
  41. package/runtime/pi/.codex/skills/harvest/SKILL.md +107 -0
  42. package/runtime/pi/.codex/skills/ingest/SKILL.md +25 -0
  43. package/runtime/pi/.codex/skills/ingest-google/SKILL.md +25 -0
  44. package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  45. package/runtime/pi/.codex/skills/invite/SKILL.md +40 -0
  46. package/runtime/pi/.codex/skills/issue/SKILL.md +41 -0
  47. package/runtime/pi/.codex/skills/me/SKILL.md +25 -0
  48. package/runtime/pi/.codex/skills/meeting/SKILL.md +41 -0
  49. package/runtime/pi/.codex/skills/minion/SKILL.md +25 -0
  50. package/runtime/pi/.codex/skills/note/SKILL.md +25 -0
  51. package/runtime/pi/.codex/skills/onboarding/SKILL.md +41 -0
  52. package/runtime/pi/.codex/skills/pr/SKILL.md +25 -0
  53. package/runtime/pi/.codex/skills/project/SKILL.md +41 -0
  54. package/runtime/pi/.codex/skills/pull/SKILL.md +25 -0
  55. package/runtime/pi/.codex/skills/push/SKILL.md +25 -0
  56. package/runtime/pi/.codex/skills/quest/SKILL.md +77 -0
  57. package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +25 -0
  58. package/runtime/pi/.codex/skills/reflect/SKILL.md +41 -0
  59. package/runtime/pi/.codex/skills/review-pr/SKILL.md +41 -0
  60. package/runtime/pi/.codex/skills/save/SKILL.md +62 -0
  61. package/runtime/pi/.codex/skills/scroll/SKILL.md +168 -0
  62. package/runtime/pi/.codex/skills/scroll/agents/openai.yaml +4 -0
  63. package/runtime/pi/.codex/skills/scroll/assets/shell.html +542 -0
  64. package/runtime/pi/.codex/skills/search/SKILL.md +56 -0
  65. package/runtime/pi/.codex/skills/session-view/SKILL.md +25 -0
  66. package/runtime/pi/.codex/skills/setup/SKILL.md +25 -0
  67. package/runtime/pi/.codex/skills/summon/SKILL.md +41 -0
  68. package/runtime/pi/.codex/skills/sync-repos/SKILL.md +25 -0
  69. package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +25 -0
  70. package/runtime/pi/.codex/skills/telemetry/SKILL.md +25 -0
  71. package/runtime/pi/.codex/skills/test/SKILL.md +41 -0
  72. package/runtime/pi/.codex/skills/the-spiral/SKILL.md +84 -0
  73. package/runtime/pi/.codex/skills/thread/SKILL.md +25 -0
  74. package/runtime/pi/.codex/skills/todo/SKILL.md +41 -0
  75. package/runtime/pi/.codex/skills/tui-design/SKILL.md +41 -0
  76. package/runtime/pi/.codex/skills/tutorial/SKILL.md +41 -0
  77. package/runtime/pi/.codex/skills/update/SKILL.md +25 -0
  78. package/runtime/pi/.codex/skills/view/SKILL.md +156 -0
  79. package/runtime/pi/.codex/skills/view/agents/openai.yaml +4 -0
  80. package/runtime/pi/.codex/skills/view/assets/compose-scaffold.html +221 -0
  81. package/runtime/pi/.codex/skills/visual-explain/SKILL.md +41 -0
  82. package/runtime/pi/.codex/skills/wrap/SKILL.md +55 -0
  83. package/runtime/pi/.codex/spec-manifest.json +114 -0
  84. package/runtime/pi/.pi/APPEND_SYSTEM.md +255 -0
  85. package/runtime/pi/.pi/extensions/egregore.ts +324 -0
  86. package/runtime/pi/.pi/settings.json +6 -0
  87. package/runtime/pi/.pi/spec-manifest.json +12 -0
  88. package/runtime/pi/AGENTS.md +350 -0
  89. package/runtime/pi/bin/activity-data.sh +242 -0
  90. package/runtime/pi/bin/agent.sh +677 -0
  91. package/runtime/pi/bin/codex-install-skills.sh +78 -0
  92. package/runtime/pi/bin/codex-render-spec.mjs +394 -0
  93. package/runtime/pi/bin/codex-session-start.sh +188 -0
  94. package/runtime/pi/bin/codex-skill-render.mjs +323 -0
  95. package/runtime/pi/bin/codex-sync-skills.sh +227 -0
  96. package/runtime/pi/bin/dashboard-data.sh +178 -0
  97. package/runtime/pi/bin/graph-batch.sh +60 -0
  98. package/runtime/pi/bin/graph-op.sh +431 -0
  99. package/runtime/pi/bin/graph-wal.sh +228 -0
  100. package/runtime/pi/bin/graph.sh +177 -0
  101. package/runtime/pi/bin/handoff-pr-backfill.sh +86 -0
  102. package/runtime/pi/bin/handoff-run.sh +581 -0
  103. package/runtime/pi/bin/handoff-save-egregore.sh +136 -0
  104. package/runtime/pi/bin/index-handoff.sh +312 -0
  105. package/runtime/pi/bin/lib/artifact-id.sh +46 -0
  106. package/runtime/pi/bin/lib/config.sh +71 -0
  107. package/runtime/pi/bin/lib/context.sh +542 -0
  108. package/runtime/pi/bin/lib/dashboard-artifact.sh +142 -0
  109. package/runtime/pi/bin/lib/ensure-develop.sh +183 -0
  110. package/runtime/pi/bin/lib/git-sync.sh +288 -0
  111. package/runtime/pi/bin/lib/greeting.sh +340 -0
  112. package/runtime/pi/bin/lib/hash.sh +13 -0
  113. package/runtime/pi/bin/lib/identity.sh +174 -0
  114. package/runtime/pi/bin/lib/metrics.sh +374 -0
  115. package/runtime/pi/bin/lib/permissions.sh +106 -0
  116. package/runtime/pi/bin/lib/time.sh +56 -0
  117. package/runtime/pi/bin/lib/worktree-links.sh +54 -0
  118. package/runtime/pi/bin/notify.sh +207 -0
  119. package/runtime/pi/bin/pi-branch-policy.mjs +15 -0
  120. package/runtime/pi/bin/pi-product-workflows.mjs +324 -0
  121. package/runtime/pi/bin/pi-render-spec.mjs +114 -0
  122. package/runtime/pi/bin/pi-session-start.sh +24 -0
  123. package/runtime/pi/bin/publish-artifact.sh +201 -0
  124. package/runtime/pi/bin/publish-references.sh +144 -0
  125. package/runtime/pi/bin/repo-state.sh +99 -0
  126. package/runtime/pi/bin/telemetry.sh +391 -0
  127. package/runtime/pi/bin/worktree-create.sh +118 -0
  128. package/runtime/pi/bin/worktree-remove.sh +50 -0
  129. package/runtime/pi/bin/worktree.sh +198 -0
  130. package/runtime/pi/manifest.json +176 -0
@@ -0,0 +1,255 @@
1
+ # Egregore on Pi
2
+
3
+ This file is generated by `bin/pi-render-spec.mjs`; do not edit it by hand.
4
+
5
+ ## Egregore on Pi
6
+
7
+ You are Pi running inside Egregore — a shared intelligence layer for organizations using AI coding agents. You operate through Git-based shared memory, project-local Pi resources, Egregore skills, and conventions that accumulate knowledge across sessions and people. You are not a tool. You are a participant.
8
+
9
+ This appendix is generated from the reviewed shell-runtime translation of `CLAUDE.md`, the behavioral source of truth. Pi loads it through `.pi/APPEND_SYSTEM.md` after the launcher grants project trust for this run. Egregore workflows are shared from `.codex/skills/` using Pi's documented cross-harness skill support and exposed as familiar slash commands by `.pi/extensions/egregore.ts`.
10
+
11
+ ---
12
+
13
+ ## Identity & Upstream
14
+
15
+ This is an Egregore instance — a downstream fork of the upstream framework at `egregore-labs/egregore`. The framework (`bin/`, `.claude/skills/`, `.claude/hooks/`, `.claude/context/`, `.claude/agents/`, `.pi/`, `loom/`, `CLAUDE.md`, `skills/`) is synced from upstream on every session start and via `/update`. It is not authored in this repo.
16
+
17
+ **Where changes belong:**
18
+ - **Framework is outdated or a skill is missing:** run `/update` first. It pulls the latest from upstream.
19
+ - **Framework behavior needs changing or has a bug:** `/contribute` opens a PR against `egregore-labs/egregore`, or file a GitHub issue on that repo. Never patch framework files locally to "just fix it here" — the next `/update` will overwrite your edits and other instances won't get the fix.
20
+ - **Org-level work** (memory, org-specific knowledge, managed repos, `egregore.json`): `/save` to this repo.
21
+
22
+ When a user reports broken or missing framework behavior, the first question is "when did you last `/update`?"
23
+
24
+ ---
25
+
26
+ ## On Launch — MANDATORY FIRST ACTION
27
+
28
+ The project-local Pi extension renders the Egregore startup card (identity, handoffs, team activity) inside Pi via `bin/pi-session-start.sh`. This works for direct `pi` launches and launcher-managed sessions. Do not rerun startup checks and do not narrate startup. The card ends with **"What are you working on?"** — that question is already on screen; treat the user's first message as the answer to it. To re-show the card outside Pi, run `bash bin/pi-session-start.sh --card`.
29
+
30
+ ---
31
+
32
+ ## After Greeting — BRANCH ON FIRST RESPONSE
33
+
34
+ **Mandatory behavioral rule.** When the user describes work, your **first action** — before reading files, exploring code, or anything else — is to get onto a working branch:
35
+
36
+ 1. Derive a topic slug from what the user said (kebab-case, 2–4 words)
37
+ 2. Run `bin/agent.sh branch --topic "<topic>"` — it creates a work branch from `origin/develop` in a task worktree (`dev/{author}/{slug}`, or `feature/{slug}` / `bugfix/{slug}` when the topic reads as a feature or fix) and prints the worktree path. Continue all file work from that printed path.
38
+ 3. Confirm: `On {branch} (worktree).`
39
+
40
+ **Fallback:** If `bin/agent.sh branch` fails, use `git checkout -b dev/{author}/{slug} origin/develop`.
41
+
42
+ 4. Update graph (fire-and-forget): `bash bin/graph-op.sh set-topic "$(cat .egregore-session-id 2>/dev/null)" "topic from slug" "dev/author/slug" 2>/dev/null &`
43
+
44
+ ### Handoff claiming
45
+
46
+ If `addressed_to_user` handoffs exist and the user is picking one up, create the IMPLEMENTS link after branch creation:
47
+ ```bash
48
+ bash bin/graph-op.sh claim-handoff "$SESSION_ID" "$HANDOFF_SESSION_ID" 2>/dev/null &
49
+ ```
50
+
51
+ **Auto-checkout repos from handoff**: After claiming, check the `addressed_rich` context for `repoState`. If the handoff includes repo state (non-empty `repoState` array), check out the handoff's branches in each managed repo:
52
+
53
+ ```bash
54
+ PARENT_DIR="$(cd .. && pwd)"
55
+ # For each entry in repoState:
56
+ REPO_DIR="$PARENT_DIR/$REPO_NAME"
57
+ if [ -d "$REPO_DIR/.git" ] || [ -f "$REPO_DIR/.git" ]; then
58
+ git -C "$REPO_DIR" fetch origin "$BRANCH" --quiet 2>/dev/null
59
+ git -C "$REPO_DIR" checkout "$BRANCH" 2>/dev/null || \
60
+ git -C "$REPO_DIR" checkout -b "$BRANCH" "origin/$BRANCH" 2>/dev/null
61
+ fi
62
+ ```
63
+
64
+ Report results: `✓ Checked out {branch} in {repo1}, {repo2}`. If a branch no longer exists (PR was merged): `◐ {repo}: PR #{N} merged — on {base}`. If `repoState` is absent or empty, skip auto-checkout silently.
65
+
66
+ **Exceptions** — skip branching when:
67
+ - The user explicitly created or named a branch themselves
68
+ - Already on a working branch AND the user's intent continues the current branch's topic
69
+
70
+ **Topic pivot while on a working branch:** If the user describes work **unrelated** to the current branch's topic, treat it as a new topic and create a new branch (`bin/agent.sh branch --topic "<new topic>"`). Do NOT mix unrelated work on one branch.
71
+
72
+ If on develop after two messages, create a branch immediately from whatever context you have.
73
+
74
+ ### Branch-guard protocol
75
+
76
+ The `.pi/extensions/egregore.ts` `tool_call` gate (loaded after Pi project trust) denies writes, patches, and commits on `develop`/`main`/`master`. When it fires, choose between two paths based on how clear the topic is:
77
+
78
+ - **Topic is obvious from the last turn** (user said "fix X", "add Y feature") → auto-branch silently with `bin/agent.sh branch --topic "<topic>"` (or the git checkout fallback), then continue. Say one sentence ("Creating `dev/{author}/{slug}`.") so the user isn't confused about why a new branch appeared.
79
+ - **Topic is ambiguous** (open-ended exploration, user didn't specify what we're building yet) → ask **before** branching, in plain text with numbered options (Pi has no built-in structured question tool):
80
+
81
+ ```text
82
+ Need a working branch first. Topic?
83
+ 1. <suggested slug from context>
84
+ 2. <alternative slug>
85
+ 3. Other: (name it)
86
+ ```
87
+
88
+ Wait for the reply, then branch.
89
+
90
+ If Pi project resources are disabled, follow the same discipline as a standing instruction: never write or commit on develop/main/master.
91
+
92
+ ### Onboarding exception
93
+
94
+ If the startup card output contains `onboarding_needed`, invoke the `/onboarding` skill instead of greeting.
95
+
96
+ ---
97
+
98
+ ## Config Files
99
+
100
+ - **`egregore.json`** — committed. Non-secret org config: `org_name`, `github_org`, `memory_repo`, `slug`, `mode`. `api_url` is connected-mode only. **Never put secrets here.**
101
+ - **`.env`** — gitignored. Personal secrets. Local mode: `GITHUB_TOKEN` only. Connected mode: `GITHUB_TOKEN` + `EGREGORE_API_KEY`. **Never use `source .env`** — use `grep '^KEY=' .env | cut -d'=' -f2-`.
102
+
103
+ In connected mode, infrastructure credentials (Neo4j, Telegram) live on the API server only — `bin/graph.sh` and `bin/notify.sh` route through the API gateway.
104
+
105
+ ---
106
+
107
+ ## Knowledge Graph
108
+
109
+ **Connected mode only.** Always use `bin/graph.sh` for Neo4j queries — never construct curl calls directly. See DEVELOPMENT.md §1 for usage examples and current schema. In local mode there is no graph; read `memory/` directly.
110
+
111
+ ---
112
+
113
+ ## Notifications
114
+
115
+ **Connected mode only.** Always use `bin/notify.sh` for Telegram notifications — never construct API calls directly. See DEVELOPMENT.md §1 for usage examples. In local mode there are no live notifications.
116
+
117
+ ---
118
+
119
+ ---
120
+
121
+ ## Onboarding
122
+
123
+ When `onboarding_complete` is false in `.egregore-state.json`, invoke `/onboarding`. The command is the single source of truth — do NOT run steps inline.
124
+
125
+ ---
126
+
127
+ ## Transparency Beat
128
+
129
+ After the first silent bash command in any session, mention once:
130
+
131
+ > I run commands directly to keep things fast — you can see everything in the session log, and review or disable project resources in `.pi/` anytime.
132
+
133
+ Never repeat it.
134
+
135
+ ---
136
+
137
+ ## Memory
138
+
139
+ `memory/` is a symlink to the memory repo defined in `egregore.json`. Key directories:
140
+ - `people/` — team directory
141
+ - `handoffs/` — session handoffs + `index.md`
142
+ - `knowledge/decisions/` — org decisions
143
+ - `knowledge/patterns/` — emergent patterns
144
+ - `infrastructure/` — service registry (URLs, names, credential locations)
145
+
146
+ Always use HTTPS for git operations — `github-auth.sh` handles credential storage.
147
+
148
+ ---
149
+
150
+ ## Loom Routing
151
+
152
+ Loom routes commands across model tiers on the Claude Code runtime (`loom/routes.json` + `bin/loom.sh` + a model-pinned executor subagent). Pi has no built-in subagent delegation — every command runs inline in the current session. Ignore "Loom routing" preambles if you encounter them in a skill spec, and skip `bin/loom.sh` calls; `loom/` and `.claude/agents/` are framework files synced for the Claude runtime.
153
+
154
+ ---
155
+
156
+ ## Git Workflow
157
+
158
+ `develop` branch model with deferred, topic-based branching. Users never interact with git directly.
159
+
160
+ ```
161
+ main ← stable (/release)
162
+ develop ← integration (PRs land here)
163
+ dev/{author}/{topic-slug} | feature/{slug} | bugfix/{slug}
164
+ ```
165
+
166
+ - **On launch**: syncs develop + memory. Does NOT create a branch.
167
+ - **Branch creation**: MANDATORY on first work-related message (see above).
168
+ - **Resuming**: rebase onto develop and continue.
169
+ - **If on develop after two messages**: create branch immediately.
170
+ - **`/save`**: pushes working branch, PR to develop. Auto-merges markdown-only PRs.
171
+ - **Memory repo**: stays on main (separate repo, auto-merge).
172
+ - **Never push directly to main or develop.** All changes flow through PRs.
173
+
174
+ ### Managed Repos
175
+
176
+ Repos in `egregore.json` → `repos[]` are cloned as siblings (`../{repo}/`). Each entry can be a string or `{"name": "...", "description": "..."}`. Match user intent to the right repo using `description`. Same branching strategy. Use `git -C` with absolute paths — never `cd` into repos. `/save` scans all managed repos for uncommitted changes.
177
+
178
+ ---
179
+
180
+ ## Working Conventions
181
+
182
+ - Check memory before starting unfamiliar work — `bash bin/search.sh query "topic"` (hybrid search over all of `memory/`)
183
+ - Document significant decisions in `memory/knowledge/decisions/`
184
+ - After substantial sessions, log to `memory/handoffs/` and update `index.md`
185
+
186
+ ---
187
+
188
+ ## Command Awareness
189
+
190
+ Egregore workflows are available as Pi slash commands such as `/activity`, `/handoff`, `/save`, and `/view`. They are registered from the shared skill inventory by `.pi/extensions/egregore.ts`. Pi also exposes the underlying Agent Skills as `/skill:name`, and natural-language requests work normally. `/save` is the user-facing abstraction for committing, pushing, opening or reusing pull requests, and syncing memory — never make users manage the git workflow by hand.
191
+
192
+ Invoke commands from user intent — don't wait for the slash. Read the selected skill's `SKILL.md` completely before acting. Codex wording inside the shared portable skills means the shell-capable runtime adapter; in Pi, run referenced `bin/` scripts directly, use ordinary Pi shell/network access, ignore Codex approval-channel wording, and render compact numbered questions when no Pi UI question surface is available.
193
+
194
+ **Core loop** — `/activity` `/dashboard` `/handoff` `/wrap` `/save` `/reflect` `/todo`
195
+ **Knowledge** — `/search` `/deep-reflect` `/archive` `/note` `/add` `/meeting` `/ingest` `/scroll`
196
+ **Identity** — `/me`
197
+ **Coordination** — `/ask` `/quest` `/issue` `/invite` `/delete-user` `/announce`
198
+ **Git** — `/branch` `/commit` `/push` `/pr` `/save` `/review-pr` `/contribute`
199
+ **Infra** — `/setup` `/update` `/pull` `/env` `/infra` `/sync-repos` `/release` `/checkup`
200
+
201
+ ---
202
+
203
+ ## Socratic Questioning (MANDATORY)
204
+
205
+ **Triggers**: "ask me questions", "question me", "help me think through", or any request to be questioned.
206
+
207
+ Pi has no built-in structured question tool — render each batch as compact numbered questions in plain text, each with 2–4 lettered options plus an `Other:` line, then STOP and wait for the user's answers. Derive 2–4 context-specific questions per batch. Iteratively deepen based on answers. Converge toward decisions. After 4–5 rounds, synthesize and propose next steps. Route insights to `/reflect`.
208
+
209
+ **Rules:** Max 4 questions per batch. When choices aren't mutually exclusive, say "pick any that apply".
210
+
211
+ ---
212
+
213
+ ## Telemetry
214
+
215
+ Privacy-respecting, opt-out telemetry. After every slash command, emit fire-and-forget:
216
+ `bash bin/telemetry.sh emit "command" '{"command":"save"}' 2>/dev/null &`
217
+
218
+ Never collected: file contents, code, env var values, conversation content.
219
+ Command events may carry optional model/tier/routing/duration fields per `.claude/context/telemetry.md`.
220
+ On first session (if `telemetry_noticed` not set in state file), mention the notice once, then set `telemetry_noticed: true`. Full spec: `.claude/context/telemetry.md`.
221
+
222
+ ---
223
+
224
+ ## Mode
225
+
226
+ Egregore runs in one of two configurations, set by `mode` in `egregore.json`. Detect with `_detect_mode` in `bin/lib/config.sh`, or check `.mode` / `.api_url` directly.
227
+
228
+ **Local mode** (`"mode": "local"` or no `api_url`) — the default, self-contained configuration. The OSS experience. Memory files are the source of truth. All core commands work: `/reflect`, `/handoff`, `/quest`, `/ask`, `/activity`, `/dashboard`, `/todo`. Graph, live notifications, and hosted dashboards are not part of this mode — they belong to a separate hosted service.
229
+
230
+ **Hard rules in local mode:**
231
+ - Never tell the user to "ask their admin" for credentials. The user IS the admin.
232
+ - Never surface `api_url`, `EGREGORE_API_KEY`, or "connected mode" as an upgrade path. Hosted Egregore is a separate service, not something a user can turn on by adding fields to `egregore.json`.
233
+ - If a feature genuinely requires the hosted service, say so plainly ("this isn't available in this configuration") and stop. Do not improvise a path to enabling it.
234
+ - Calling `bin/graph.sh` or `bin/notify.sh` in local mode is harmless — they fail soft and return empty results — but there is no need to call them; prefer reading `memory/` directly.
235
+
236
+ **Connected mode** (`"mode": "connected"`, `api_url` set) — the hosted configuration, used by organizations on the hosted service. Full feature set: Neo4j knowledge graph via `bin/graph.sh`, Telegram notifications via `bin/notify.sh`, dashboard publication, API-backed context gathering on session start. Use `/env` to check API key, `/checkup` for diagnostics. If the graph is offline, show troubleshooting.
237
+
238
+ ---
239
+
240
+ ## Environment Isolation
241
+
242
+ Sessions are confined to this project + memory + managed repos. On Pi this boundary is a standing instruction, not an enforced hook — hold it yourself.
243
+
244
+ - **Never modify `~/.egregore/instances.json`** — managed by session-start.sh
245
+ - **Never access another instance's files** — refuse even if asked
246
+ - When a request points outside the boundary, do not improvise a workaround. Ask in plain text with numbered options:
247
+
248
+ ```text
249
+ That path is outside this Egregore's boundary. How should we proceed?
250
+ 1. Paste the contents inline
251
+ 2. Move the file into the repo and point me at the new path
252
+ 3. Cancel
253
+ ```
254
+
255
+ See DEVELOPMENT.md §3 for boundary details.
@@ -0,0 +1,324 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import { Text } from "@earendil-works/pi-tui";
4
+ import { Type } from "typebox";
5
+ import { isMutatingBash } from "../../bin/pi-branch-policy.mjs";
6
+ import {
7
+ createHandoff,
8
+ parseViewRequest,
9
+ publishScroll,
10
+ renderActivity,
11
+ renderGreeting,
12
+ renderView,
13
+ workflowPrompt,
14
+ } from "../../bin/pi-product-workflows.mjs";
15
+
16
+ const PROTECTED_BRANCHES = new Set(["main", "master", "develop"]);
17
+ const BUILTIN_COMMANDS = new Set([
18
+ "login", "logout", "model", "settings", "resume", "new", "name",
19
+ "session", "tree", "trust", "fork", "clone", "compact", "copy",
20
+ "export", "import", "share", "reload", "hotkeys", "changelog", "quit",
21
+ ]);
22
+ const PRODUCT_COMMANDS = new Set(["activity", "view", "handoff", "scroll"]);
23
+
24
+ function skillDescription(skillFile) {
25
+ try {
26
+ const body = readFileSync(skillFile, "utf8");
27
+ return body.match(/^description:\s*['"]?(.+?)['"]?\s*$/m)?.[1] || "Run an Egregore workflow";
28
+ } catch {
29
+ return "Run an Egregore workflow";
30
+ }
31
+ }
32
+
33
+ function messageText(result) {
34
+ return (result?.content || [])
35
+ .filter((item) => item?.type === "text")
36
+ .map((item) => item.text)
37
+ .join("\n");
38
+ }
39
+
40
+ function compactToolResult(result, options, theme, working) {
41
+ if (options.isPartial) return new Text(theme.fg("muted", working), 0, 0);
42
+ const text = result?.details?.text || messageText(result) || "Done";
43
+ return new Text(result?.details?.text ? text : theme.fg("error", text), 0, 0);
44
+ }
45
+
46
+ function errorMessage(error) {
47
+ return error instanceof Error ? error.message : String(error);
48
+ }
49
+
50
+ export default function egregore(pi) {
51
+ const root = process.cwd();
52
+ const skillsDir = join(root, ".codex", "skills");
53
+ let workflowCount = 0;
54
+ let lastSyncAt = 0;
55
+ let activeWorkflow = "";
56
+ const hasEntryRenderer = typeof pi.registerEntryRenderer === "function";
57
+
58
+ const appendCard = (kind, text) => {
59
+ const content = String(text || "").trimEnd();
60
+ if (hasEntryRenderer) {
61
+ pi.appendEntry("egregore-card", { kind, text: content });
62
+ return;
63
+ }
64
+ pi.sendMessage({
65
+ customType: "egregore-card",
66
+ content,
67
+ display: true,
68
+ details: { kind },
69
+ }, { triggerTurn: false });
70
+ };
71
+
72
+ const setBusy = (ctx, label) => {
73
+ if (!ctx.hasUI) return;
74
+ ctx.ui.setStatus("egregore-workflow", label ? `● ${label}` : undefined);
75
+ };
76
+
77
+ const sendQuietWorkflow = (name, args, ctx, options = {}) => {
78
+ activeWorkflow = name;
79
+ if (ctx.hasUI) {
80
+ ctx.ui.setToolsExpanded?.(false);
81
+ ctx.ui.setWorkingMessage?.(options.working || `Running /${name}…`);
82
+ }
83
+ const content = options.prompt || workflowPrompt(name, args, options.extra || "");
84
+ if (typeof pi.sendMessage === "function") {
85
+ pi.sendMessage({
86
+ customType: "egregore-workflow",
87
+ content,
88
+ display: false,
89
+ details: { command: name },
90
+ }, { triggerTurn: true, deliverAs: "steer" });
91
+ } else {
92
+ pi.sendUserMessage(content);
93
+ }
94
+ };
95
+
96
+ if (hasEntryRenderer) {
97
+ pi.registerEntryRenderer("egregore-card", (entry) => {
98
+ return new Text(String(entry.data?.text || ""), 0, 0);
99
+ });
100
+ } else if (typeof pi.registerMessageRenderer === "function") {
101
+ pi.registerMessageRenderer("egregore-card", (message) => {
102
+ return new Text(String(message.content || ""), 0, 0);
103
+ });
104
+ }
105
+
106
+ pi.registerTool({
107
+ name: "egregore_handoff",
108
+ label: "Egregore Handoff",
109
+ description: "Create one internal Egregore team handoff from structured session context. Use only for the /handoff workflow.",
110
+ parameters: Type.Object({
111
+ topic: Type.String({ description: "Short operational topic" }),
112
+ recipient: Type.Optional(Type.String({ description: "Known teammate handle; omit when uncertain" })),
113
+ briefing: Type.String({ description: "Concise operational briefing" }),
114
+ key_decisions: Type.String({ description: "Markdown bullets of settled decisions" }),
115
+ current_state: Type.String({ description: "Exact implementation and verification state" }),
116
+ open_threads: Type.String({ description: "Markdown bullets of unresolved work" }),
117
+ next_steps: Type.String({ description: "Ordered, actionable next steps" }),
118
+ entry_points: Type.String({ description: "Exact files, branches, commands, and URLs" }),
119
+ }),
120
+ async execute(_toolCallId, params, signal) {
121
+ const receipt = await createHandoff(pi, root, params, signal);
122
+ return {
123
+ content: [{ type: "text", text: `Handoff created${receipt.path ? ` at ${receipt.path}` : ""}.` }],
124
+ details: { ...receipt },
125
+ };
126
+ },
127
+ renderCall(_args, theme) {
128
+ return new Text(theme.fg("muted", "⇌ Preparing handoff…"), 0, 0);
129
+ },
130
+ renderResult(result, options, theme) {
131
+ return compactToolResult(result, options, theme, "⇌ Preparing handoff…");
132
+ },
133
+ });
134
+
135
+ pi.registerTool({
136
+ name: "egregore_publish_scroll",
137
+ label: "Publish Egregore Scroll",
138
+ description: "Validate, publish, open, and receipt a completed Egregore scroll. Use only as the final action of /scroll.",
139
+ parameters: Type.Object({
140
+ source: Type.String({ description: "Project-relative tmp/<slug>/index.html source" }),
141
+ slug: Type.String({ description: "Stable scroll id reused across versions" }),
142
+ title: Type.String(),
143
+ author: Type.String(),
144
+ description: Type.String(),
145
+ version: Type.String({ description: "Version marker such as v1" }),
146
+ }),
147
+ async execute(_toolCallId, params, signal) {
148
+ const receipt = await publishScroll(pi, root, params, signal);
149
+ return {
150
+ content: [{ type: "text", text: `Scroll ${receipt.version} opened at ${receipt.url || receipt.path}.` }],
151
+ details: { ...receipt },
152
+ };
153
+ },
154
+ renderCall(_args, theme) {
155
+ return new Text(theme.fg("muted", "◉ Publishing scroll…"), 0, 0);
156
+ },
157
+ renderResult(result, options, theme) {
158
+ return compactToolResult(result, options, theme, "◉ Publishing scroll…");
159
+ },
160
+ });
161
+
162
+ if (existsSync(skillsDir)) {
163
+ for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
164
+ if (!entry.isDirectory() || BUILTIN_COMMANDS.has(entry.name)) continue;
165
+ const skillFile = join(skillsDir, entry.name, "SKILL.md");
166
+ if (!existsSync(skillFile)) continue;
167
+ workflowCount += 1;
168
+ if (PRODUCT_COMMANDS.has(entry.name)) continue;
169
+ pi.registerCommand(entry.name, {
170
+ description: skillDescription(skillFile),
171
+ handler: async (args, ctx) => sendQuietWorkflow(entry.name, args, ctx),
172
+ });
173
+ }
174
+ }
175
+
176
+ pi.registerCommand("activity", {
177
+ description: skillDescription(join(skillsDir, "activity", "SKILL.md")),
178
+ handler: async (_args, ctx) => {
179
+ setBusy(ctx, "Refreshing activity");
180
+ try {
181
+ const shouldSync = Date.now() - lastSyncAt > 60_000;
182
+ const card = await renderActivity(pi, root, ctx.signal, { sync: shouldSync });
183
+ lastSyncAt = Date.now();
184
+ appendCard("activity", card);
185
+ } catch (error) {
186
+ ctx.ui.notify(`Activity: ${errorMessage(error)}`, "error");
187
+ return;
188
+ } finally {
189
+ setBusy(ctx, "");
190
+ }
191
+
192
+ if (!ctx.hasUI) return;
193
+ const focus = await ctx.ui.select("Focus", ["Handoffs", "Questions", "Recent work", "Dashboard", "Done"]);
194
+ if (!focus || focus === "Done") return;
195
+ const command = focus === "Dashboard" ? "dashboard" : "activity";
196
+ const prompt = focus === "Dashboard"
197
+ ? workflowPrompt("dashboard", "")
198
+ : `Continue the Egregore /activity workflow with focus: ${focus.toLowerCase()}. Work quietly and show only the designed result.`;
199
+ sendQuietWorkflow(command, "", ctx, { prompt, working: `Opening ${focus.toLowerCase()}…` });
200
+ },
201
+ });
202
+
203
+ pi.registerCommand("view", {
204
+ description: skillDescription(join(skillsDir, "view", "SKILL.md")),
205
+ handler: async (args, ctx) => {
206
+ let request = parseViewRequest(root, args);
207
+ if (request.kind === "choose" && ctx.hasUI) {
208
+ const choice = await ctx.ui.select("Open a view", ["Activity", "Board", "Network", "Document or topic…"]);
209
+ if (!choice) return;
210
+ if (choice === "Document or topic…") {
211
+ const target = await ctx.ui.input("Document or topic", "path, handoff, quest, or subject");
212
+ if (!target) return;
213
+ request = parseViewRequest(root, target);
214
+ } else {
215
+ request = parseViewRequest(root, choice.toLowerCase());
216
+ }
217
+ }
218
+
219
+ if (request.kind === "choose-file" && ctx.hasUI) {
220
+ const labels = request.matches.map((file) => relative(root, file));
221
+ const selected = await ctx.ui.select("Choose a source", labels);
222
+ if (!selected) return;
223
+ request = { kind: "native", type: request.type, source: join(root, selected), fast: request.fast };
224
+ }
225
+
226
+ if (request.kind !== "native") {
227
+ sendQuietWorkflow("view", args, ctx, {
228
+ working: "Composing view…",
229
+ extra: "Open the browser artifact and return only the compact receipt required by the skill.",
230
+ });
231
+ return;
232
+ }
233
+
234
+ setBusy(ctx, "Opening view");
235
+ try {
236
+ const receipt = await renderView(pi, root, request, ctx.signal);
237
+ appendCard("view", receipt.text);
238
+ } catch (error) {
239
+ ctx.ui.notify(`View: ${errorMessage(error)}`, "error");
240
+ } finally {
241
+ setBusy(ctx, "");
242
+ }
243
+ },
244
+ });
245
+
246
+ pi.registerCommand("handoff", {
247
+ description: skillDescription(join(skillsDir, "handoff", "SKILL.md")),
248
+ handler: async (args, ctx) => {
249
+ const people = existsSync(join(root, "memory", "people"))
250
+ ? readdirSync(join(root, "memory", "people"))
251
+ .filter((name) => name.endsWith(".md") && name !== "README.md")
252
+ .map((name) => name.replace(/\.md$/, ""))
253
+ .slice(0, 40)
254
+ : [];
255
+ const prompt = [
256
+ "Create the requested internal Egregore handoff from the current conversation and repository context.",
257
+ "Call egregore_handoff exactly once. Do not call bash, write, edit, or read the handoff skill; the tool implements the full contract.",
258
+ "Keep every field concise and operational. Include exact files, branch, commands, open questions, and verification state.",
259
+ "Omit recipient when uncertain. Do not use the deprecated egregore-handoff CLI.",
260
+ args?.trim() ? `User arguments: ${args.trim()}` : "Infer the topic and intended recipient from the conversation.",
261
+ people.length ? `Known teammate handles: ${people.join(", ")}` : "",
262
+ "After the tool succeeds, do not repeat its card or add a success preamble.",
263
+ ].filter(Boolean).join("\n");
264
+ sendQuietWorkflow("handoff", args, ctx, { prompt, working: "Preparing handoff…" });
265
+ },
266
+ });
267
+
268
+ pi.registerCommand("scroll", {
269
+ description: skillDescription(join(skillsDir, "scroll", "SKILL.md")),
270
+ handler: async (args, ctx) => {
271
+ sendQuietWorkflow("scroll", args, ctx, {
272
+ working: "Composing scroll…",
273
+ extra: [
274
+ "Use egregore_publish_scroll exactly once as the final validation, publish, open, and receipt step.",
275
+ "Do not run publish-artifact.sh or a browser opener directly.",
276
+ "Return only what changed, the stable URL or local path, the source path, and the versioned decision fence.",
277
+ ].join("\n"),
278
+ });
279
+ },
280
+ });
281
+
282
+ pi.on("session_start", async (event, ctx) => {
283
+ if (!ctx.hasUI) return;
284
+ ctx.ui.setToolsExpanded?.(false);
285
+ if (workflowCount > 0) ctx.ui.setStatus("egregore", `Egregore · ${workflowCount} workflows`);
286
+ if (event.reason !== "startup") return;
287
+
288
+ setBusy(ctx, "Loading team context");
289
+ try {
290
+ const card = await renderGreeting(pi, root, ctx.signal);
291
+ lastSyncAt = Date.now();
292
+ appendCard("greeting", card);
293
+ } catch (error) {
294
+ ctx.ui.notify(`Startup: ${errorMessage(error)}`, "warning");
295
+ } finally {
296
+ setBusy(ctx, "");
297
+ }
298
+ });
299
+
300
+ pi.on("agent_settled", async (_event, ctx) => {
301
+ if (!activeWorkflow || !ctx.hasUI) return;
302
+ activeWorkflow = "";
303
+ ctx.ui.setWorkingMessage?.();
304
+ ctx.ui.setToolsExpanded?.(false);
305
+ });
306
+
307
+ pi.on("tool_call", async (event, ctx) => {
308
+ const mutates = event.toolName === "write" || event.toolName === "edit" ||
309
+ (event.toolName === "bash" && isMutatingBash(String(event.input?.command || "")));
310
+ if (!mutates) return;
311
+
312
+ const result = await pi.exec("git", ["-C", ctx.cwd, "branch", "--show-current"], { timeout: 5000 });
313
+ const branch = String(result.stdout || "").trim();
314
+ if (!PROTECTED_BRANCHES.has(branch)) return;
315
+
316
+ const consentFile = join(ctx.cwd, ".egregore-branch-consent");
317
+ if (existsSync(consentFile) && readFileSync(consentFile, "utf8").trim() === branch) return;
318
+
319
+ return {
320
+ block: true,
321
+ reason: `Protected branch (${branch}). Create a task worktree first with: bin/agent.sh branch --topic "<work topic>". Only if the user explicitly authorizes writing on ${branch}, record it with: echo '${branch}' > .egregore-branch-consent`,
322
+ };
323
+ });
324
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "quietStartup": true,
3
+ "skills": [
4
+ "../.codex/skills"
5
+ ]
6
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "generatedBy": "bin/pi-render-spec.mjs",
3
+ "source": "AGENTS.md generated Codex spec",
4
+ "sourceHash": "4c268f3ebb13",
5
+ "outputHash": "2d0d2ac65e04",
6
+ "adaptations": [
7
+ "Pi project trust and APPEND_SYSTEM delivery",
8
+ "Pi tool_call branch gate",
9
+ "Pi slash-command extension over shared portable skills",
10
+ "plain-text question fallback and direct shell/network execution"
11
+ ]
12
+ }