@bcmyguest/openmem 0.0.0-stage → 0.6.3

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 (191) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +136 -2
  3. package/bin/hindsight-project-index +530 -0
  4. package/cli/coding-agent/access-policy.mjs +38 -0
  5. package/cli/coding-agent/agents.mjs +34 -0
  6. package/cli/coding-agent/bank-from-cwd.mjs +140 -0
  7. package/cli/coding-agent/capabilities.mjs +23 -0
  8. package/cli/coding-agent/hook-harnesses.mjs +87 -0
  9. package/cli/coding-agent/hook-runner.mjs +260 -0
  10. package/cli/coding-agent/install-access.mjs +93 -0
  11. package/cli/coding-agent/install-shim.mjs +97 -0
  12. package/cli/coding-agent/opencode-config.mjs +91 -0
  13. package/cli/coding-agent/opencode-plugin.mjs +96 -0
  14. package/cli/coding-agent/runtime.mjs +39 -0
  15. package/cli/coding-agent/session-facade.mjs +62 -0
  16. package/cli/cognee-mcp.mjs +260 -0
  17. package/cli/feed-sync.mjs +11 -0
  18. package/cli/install-coding-agent.mjs +165 -0
  19. package/cli/install-memory-mcp.mjs +426 -0
  20. package/cli/is-main.mjs +18 -0
  21. package/cli/mcp/context.mjs +61 -0
  22. package/cli/mcp/export/cognee-dataset-name.mjs +12 -0
  23. package/cli/mcp/export/cognee.mjs +119 -0
  24. package/cli/mcp/export/hindsight.mjs +48 -0
  25. package/cli/mcp/export/index.mjs +26 -0
  26. package/cli/mcp/export/mem0.mjs +31 -0
  27. package/cli/mcp/llm-synthesize.mjs +50 -0
  28. package/cli/mcp/mem0-scope-id.mjs +22 -0
  29. package/cli/mcp/memory-facade.mjs +455 -0
  30. package/cli/mcp/memory-reflect-consolidate.mjs +120 -0
  31. package/cli/mcp/notebooklm-banks.mjs +21 -0
  32. package/cli/mcp/notebooklm-hindsight-rest.mjs +45 -0
  33. package/cli/mcp/notebooklm-provider.mjs +225 -0
  34. package/cli/mcp/pmm-records.mjs +23 -0
  35. package/cli/mcp/portability.mjs +158 -0
  36. package/cli/mcp/stdio.mjs +65 -0
  37. package/cli/mcp/unified-mcp-install.mjs +65 -0
  38. package/cli/mem0-mcp.mjs +108 -0
  39. package/cli/notebooklm-mcp.mjs +236 -0
  40. package/cli/openmem-mcp.mjs +35 -0
  41. package/cli/openmem.mjs +205 -0
  42. package/cli/roots.mjs +85 -0
  43. package/cli/stack.mjs +436 -0
  44. package/cli/switch-memory-provider.mjs +358 -0
  45. package/cli/wiring-report.mjs +125 -0
  46. package/compose.bench.yaml +12 -0
  47. package/compose.yaml +164 -0
  48. package/config/coding-agent.json +13 -0
  49. package/config/hindsight.env.example +24 -0
  50. package/config/index.yml +54 -0
  51. package/config/litellm.lemond.yaml +40 -0
  52. package/config/litellm.ollama.yaml +33 -0
  53. package/config/notebooklm.yml +33 -0
  54. package/config/openmem.toml.example +62 -0
  55. package/docs/OPENMEM.md +23 -0
  56. package/docs/architecture.md +219 -0
  57. package/docs/assets/README.md +10 -0
  58. package/docs/assets/setup-demo.gif +0 -0
  59. package/docs/coding-agent-hooks.md +51 -0
  60. package/docs/coding-agent-scopes.md +22 -0
  61. package/docs/coding-agents.md +51 -0
  62. package/docs/cognee.md +37 -0
  63. package/docs/concepts.md +112 -0
  64. package/docs/hindsight.md +74 -0
  65. package/docs/index-sources-roadmap.md +38 -0
  66. package/docs/index-sources.md +39 -0
  67. package/docs/indexer-sinks.md +122 -0
  68. package/docs/indexing.md +57 -0
  69. package/docs/manual-stack-setup.md +74 -0
  70. package/docs/mcp-install-guide.md +4 -0
  71. package/docs/mcp-setup.md +101 -0
  72. package/docs/mem0.md +103 -0
  73. package/docs/memory-portability.md +73 -0
  74. package/docs/memory-providers.md +246 -0
  75. package/docs/model-configuration.md +84 -0
  76. package/docs/notebooklm.md +83 -0
  77. package/docs/npx-install.md +170 -0
  78. package/docs/openmem-config.md +194 -0
  79. package/docs/provider-wizard-hooks.md +222 -0
  80. package/docs/quickstart.md +101 -0
  81. package/docs/setup-wizard.md +116 -0
  82. package/docs/sources-feeds.md +52 -0
  83. package/docs/sources-readwise.md +23 -0
  84. package/docs/sources-zotero.md +22 -0
  85. package/glama.json +4 -0
  86. package/indexer/Dockerfile +38 -0
  87. package/indexer/code_outline.py +146 -0
  88. package/indexer/feed_sync.py +107 -0
  89. package/indexer/index_config.py +290 -0
  90. package/indexer/pytest.ini +5 -0
  91. package/indexer/sinks/__init__.py +148 -0
  92. package/indexer/sinks/_http.py +44 -0
  93. package/indexer/sinks/cognee.py +329 -0
  94. package/indexer/sinks/hindsight.py +107 -0
  95. package/indexer/sinks/mem0.py +247 -0
  96. package/indexer/watch.py +240 -0
  97. package/package.json +95 -4
  98. package/providers/_template/compose.yaml +52 -0
  99. package/providers/cognee/OPERATOR.md +82 -0
  100. package/providers/cognee/compose.yaml +89 -0
  101. package/providers/cognee/lib/clack.mjs +157 -0
  102. package/providers/cognee/lib/coding-agent.mjs +26 -0
  103. package/providers/cognee/lib/cognee-mcp-install.mjs +153 -0
  104. package/providers/cognee/lib/config.mjs +98 -0
  105. package/providers/cognee/lib/env-file.mjs +194 -0
  106. package/providers/cognee/lib/env-setup.mjs +83 -0
  107. package/providers/cognee/lib/llm-env.mjs +382 -0
  108. package/providers/cognee/lib/ports.mjs +25 -0
  109. package/providers/cognee/lib/smoke.mjs +246 -0
  110. package/providers/cognee/lib/stack-env.mjs +134 -0
  111. package/providers/cognee/lib/state.mjs +101 -0
  112. package/providers/cognee/lib/summary.mjs +56 -0
  113. package/providers/cognee/lib/validate.mjs +97 -0
  114. package/providers/cognee/manifest.json +56 -0
  115. package/providers/cognee/wizard.mjs +134 -0
  116. package/providers/graphiti/manifest.json +15 -0
  117. package/providers/hindsight/lib/clack.mjs +245 -0
  118. package/providers/hindsight/lib/coding-agent.mjs +204 -0
  119. package/providers/hindsight/lib/config.mjs +73 -0
  120. package/providers/hindsight/lib/env-setup.mjs +71 -0
  121. package/providers/hindsight/lib/litellm-routes.mjs +187 -0
  122. package/providers/hindsight/lib/model-backend.mjs +214 -0
  123. package/providers/hindsight/lib/notebooklm-mcp-install.mjs +127 -0
  124. package/providers/hindsight/lib/ollama-compose.mjs +51 -0
  125. package/providers/hindsight/lib/ports.mjs +30 -0
  126. package/providers/hindsight/lib/probe.mjs +33 -0
  127. package/providers/hindsight/lib/stack-env.mjs +132 -0
  128. package/providers/hindsight/lib/state.mjs +75 -0
  129. package/providers/hindsight/lib/summary.mjs +43 -0
  130. package/providers/hindsight/lib/validate.mjs +75 -0
  131. package/providers/hindsight/manifest.json +58 -0
  132. package/providers/hindsight/wizard.mjs +149 -0
  133. package/providers/letta/manifest.json +29 -0
  134. package/providers/mem0/OPERATOR.md +124 -0
  135. package/providers/mem0/compose.yaml +57 -0
  136. package/providers/mem0/lib/coding-agent.mjs +58 -0
  137. package/providers/mem0/lib/config.mjs +45 -0
  138. package/providers/mem0/lib/configure.mjs +135 -0
  139. package/providers/mem0/lib/dotenv.mjs +71 -0
  140. package/providers/mem0/lib/env-file.mjs +110 -0
  141. package/providers/mem0/lib/http.mjs +69 -0
  142. package/providers/mem0/lib/mcp-install.mjs +165 -0
  143. package/providers/mem0/lib/secret.mjs +30 -0
  144. package/providers/mem0/lib/smoke.mjs +179 -0
  145. package/providers/mem0/lib/stack-env.mjs +216 -0
  146. package/providers/mem0/lib/state.mjs +67 -0
  147. package/providers/mem0/lib/summary.mjs +60 -0
  148. package/providers/mem0/lib/url.mjs +106 -0
  149. package/providers/mem0/lib/validate.mjs +158 -0
  150. package/providers/mem0/manifest.json +47 -0
  151. package/providers/mem0/wizard.mjs +133 -0
  152. package/providers/zep/manifest.json +29 -0
  153. package/scripts/check-pack.mjs +218 -0
  154. package/scripts/gen-masks.mjs +83 -0
  155. package/scripts/gen-masks.sh +4 -0
  156. package/scripts/promotion-provider-smoke.mjs +34 -0
  157. package/scripts/run-setup.sh +30 -0
  158. package/scripts/setup.mjs +94 -0
  159. package/scripts/setup.sh +5 -0
  160. package/scripts/spin-up.sh +45 -0
  161. package/scripts/wizard.sh +5 -0
  162. package/server.json +34 -0
  163. package/skills/openmem/SKILL.md +29 -0
  164. package/sync/Dockerfile +27 -0
  165. package/sync/memory_backend.py +87 -0
  166. package/sync/notebooklm_sync.py +664 -0
  167. package/wizard/bin/pm.mjs +97 -0
  168. package/wizard/bin/setup.mjs +33 -0
  169. package/wizard/lib/compose-version.mjs +32 -0
  170. package/wizard/lib/compose.mjs +70 -0
  171. package/wizard/lib/curl.mjs +103 -0
  172. package/wizard/lib/docker-build.mjs +148 -0
  173. package/wizard/lib/exec.mjs +41 -0
  174. package/wizard/lib/indexer.mjs +6 -0
  175. package/wizard/lib/load-machine-env.mjs +4 -0
  176. package/wizard/lib/log.mjs +32 -0
  177. package/wizard/lib/notebooklm-agent-access.mjs +92 -0
  178. package/wizard/lib/notebooklm.mjs +214 -0
  179. package/wizard/lib/parse-args.mjs +87 -0
  180. package/wizard/lib/paths.mjs +39 -0
  181. package/wizard/lib/pm-config.mjs +163 -0
  182. package/wizard/lib/ports.mjs +94 -0
  183. package/wizard/lib/prompts.mjs +57 -0
  184. package/wizard/lib/provider-wizard/helpers.mjs +98 -0
  185. package/wizard/lib/provider-wizard/run-steps.mjs +129 -0
  186. package/wizard/lib/providers/registry.mjs +262 -0
  187. package/wizard/lib/run-clack.mjs +108 -0
  188. package/wizard/lib/run-wizard.mjs +755 -0
  189. package/wizard/lib/validate-setup.mjs +62 -0
  190. package/wizard/lib/wizard-action.mjs +34 -0
  191. package/wizard/lib/wizard-conf.mjs +71 -0
@@ -0,0 +1,170 @@
1
+ # Installing with npx
2
+
3
+ `openmem` is an npm package whose CLI runs the same setup wizard as `./setup` in a
4
+ checkout. The package name lives in one place, the `name` field of `package.json` (the CLI
5
+ command stays `openmem` if you switch to a scoped name such as
6
+ `openmem`; older installs may still see `openmem`).
7
+
8
+ Requirements: Node ≥ 20.12 (the floor of `@clack/prompts`), Docker with the compose v2
9
+ plugin (`docker compose version`), `curl`. Linux and macOS are supported; there is no
10
+ bash dependency in the CLI (macOS ships bash 3.2).
11
+
12
+ Wizard phases and flags live in the repository: [docs/setup-wizard.md](setup-wizard.md). Runtime architecture: [architecture.md](architecture.md).
13
+
14
+ ## Install paths
15
+
16
+ ```sh
17
+ npx @bcmyguest/openmem # interactive setup, nothing installed globally
18
+ npx @bcmyguest/openmem setup # same as the bare command
19
+ npx @bcmyguest/openmem configure --yes # non-interactive setup and start
20
+ npx @bcmyguest/openmem up # start stack
21
+ npx @bcmyguest/openmem install-mcp --yes
22
+ npx @bcmyguest/openmem switch mem0 --yes
23
+ npm i -g openmem # then: openmem setup | up | down | ...
24
+ # From a git checkout (package not required on the registry):
25
+ git clone https://github.com/bcmyguest/openmem && cd openmem
26
+ npm install && npx . setup # same wizard; also: npm run setup / ./setup
27
+ ```
28
+
29
+ No global `pm` command is installed: it collides with Android's `pm`. `./pm` and `npm run pm`
30
+ exist only inside a checkout; prefer `npx @bcmyguest/openmem …` (or `npx . …` in a checkout).
31
+
32
+ ### Commands
33
+
34
+ | Command | What it does |
35
+ | --- | --- |
36
+ | `openmem` / `setup` | interactive setup; extra flags go to the wizard (`--yes`, `--dry-run`, `--no-smoke`, `--rebuild`) |
37
+ | `configure` | non-interactive setup and start (`setup --yes`) |
38
+ | `up` / `down` | start / stop the stack with the saved configuration |
39
+ | `doctor` | validate the configuration without the smoke test |
40
+ | `install-mcp` | merge **openmem** unified MCP (`memory_*`) by default; NotebookLM when sync is on or `--notebooklm`; native mem0/Cognee with `--provider-only`; `--no-unified-mcp` to skip |
41
+ | `install-coding-agent` | `install-mcp` + session hooks for all providers; optional `--hindsight-plugin` runs `@vectorize-io/hindsight-coding-agents` on Hindsight stacks |
42
+ | `wiring` | report `MEMORY_PROVIDER`, API URLs, and `mcp.json` alignment (`--json`) |
43
+ | `switch <provider>` | change `MEMORY_PROVIDER` (optional stack restart + MCP; no data migration) |
44
+ | `providers [--json]` | list the provider manifests shipped in the package with their status |
45
+ | `init [--force] [--dry-run] [--json]` | create or upgrade the stack root without running the wizard |
46
+ | `--version`, `--help` | |
47
+
48
+ `--version`, `--help` and `providers` touch nothing on disk. Every other command first
49
+ brings the stack root up to date, then runs the wizard.
50
+
51
+ ## Stack root
52
+
53
+ Under npx the package directory is a read-only, garbage-collected cache, but the wizard
54
+ writes `.env`, `.build-hash` and `compose.override.yaml` and drives compose files and docker
55
+ build contexts relative to its repo root. The CLI therefore keeps a stable, writable
56
+ **stack root**:
57
+
58
+ ```
59
+ ${PM_STACK_ROOT:-${XDG_DATA_HOME:-~/.local/share}/personal-memory/stack}
60
+ ```
61
+
62
+ Layout after `openmem init`:
63
+
64
+ ```
65
+ stack/
66
+ .pm-stack-manifest.json sha256 of every shipped file as last written (do not edit)
67
+ compose.yaml shipped
68
+ compose.bench.yaml shipped
69
+ indexer/ bin/ sync/ shipped (docker build contexts)
70
+ config/ shipped templates: *.example, index.yml, notebooklm.yml,
71
+ litellm.*.yaml, coding-agent.json
72
+ providers/<id>/compose*.yaml shipped provider compose fragments
73
+ .env .build-hash compose.override.yaml personal-memory.toml ... generated: never touched
74
+ ```
75
+
76
+ The wizard code itself (`wizard/`, `scripts/`, provider manifests and hooks) is **not**
77
+ copied: it runs from the package, so `@clack/prompts` resolves from the package's own `node_modules`.
78
+ Per-user config stays where it always was: `~/.config/hindsight/` and
79
+ `~/.config/personal-memory/config.toml`.
80
+
81
+ ### Environment variables
82
+
83
+ | Variable | Meaning |
84
+ | --- | --- |
85
+ | `PM_STACK_ROOT` | Mutable stack root = the wizard's `ctx.repoRoot` (compose project dir, `.env`, generated files, docker build context). Overrides the default location. |
86
+ | `PM_USE_CHECKOUT=1` | Use the package directory itself as the stack root and copy nothing. Wins over `PM_STACK_ROOT`. A directory containing `.git` next to `cli/` behaves this way automatically. |
87
+ | `PM_PACKAGE_ROOT` | Read-only code root (provider manifests and hooks, `scripts/setup.mjs`, `wizard/lib`), never written to. Set by the CLI; defaults to the package directory. |
88
+ | `PM_PROJECT` | compose project name (default `openmem`). |
89
+ | `XDG_DATA_HOME` | Base for the default stack root (must be absolute). |
90
+
91
+ `wizard/bin/setup.mjs` and `wizard/bin/pm.mjs` read `PM_STACK_ROOT` and `PM_PACKAGE_ROOT`
92
+ (defaulting both to the checkout root) and export them to child processes. Running them
93
+ directly from inside an npm package directory without `PM_STACK_ROOT` is refused.
94
+
95
+ ## Upgrades
96
+
97
+ Each command compares the package's shipped files with the stack root using the manifest:
98
+
99
+ - Missing file: created (restored if it was shipped before).
100
+ - Identical: left alone.
101
+ - On disk equals what the previous version wrote: overwritten with the new version
102
+ (atomic write: temp file in the same directory, then rename).
103
+ - Modified by you, or present but not tracked, or a symlink: **kept**, reported as
104
+ `CONFLICT <path>`. The wizard still runs against your version. Merge by hand, or run
105
+ `openmem init --force`, which overwrites it and saves yours as
106
+ `<file>.user-<timestamp>.bak`.
107
+ - No longer shipped: left in place and reported as a note.
108
+ - Generated files (`.env`, `.build-hash`, `compose.override.yaml`, `personal-memory.toml`,
109
+ anything not in the manifest) are never overwritten or deleted.
110
+
111
+ After an upgrade the wizard's `.build-hash` check rebuilds the images when their sources
112
+ changed.
113
+
114
+ ## Uninstall
115
+
116
+ ```sh
117
+ openmem down # stops the stack (docker compose down)
118
+ npm uninstall -g openmem # if installed globally
119
+ rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}/personal-memory" # the stack root
120
+ ```
121
+
122
+ `~/.config/hindsight/` (secrets, `index.yml`) and `~/.config/personal-memory/` are kept
123
+ until you delete them yourself.
124
+
125
+ ## Linux and macOS notes
126
+
127
+ - Linux: the user must be able to run `docker` without sudo. `host.docker.internal`
128
+ is mapped through `extra_hosts` in `compose.yaml`.
129
+ - macOS: use Docker Desktop or Colima with the compose v2 plugin and allow file sharing
130
+ for your home directory (the indexer bind-mounts `$HOME` read-only).
131
+ - Windows is untested; use WSL2.
132
+
133
+ ## Troubleshooting
134
+
135
+ - `CONFLICT <path>`: see Upgrades. It is a warning; the stack still works.
136
+ - `cannot prepare the stack root`: the directory is not writable. Set `PM_STACK_ROOT` to
137
+ a writable path.
138
+ - `read-only ... Run it through the CLI`: you ran `wizard/bin/*.mjs` from inside the npm
139
+ cache. Use `npx @bcmyguest/openmem` or set `PM_STACK_ROOT`.
140
+ - `missing dependencies (@clack/prompts ...)`: reinstall the package (`npm i -g
141
+ openmem`), or in a checkout run `npm install` at the checkout root.
142
+ - `... is not valid JSON` for `.pm-stack-manifest.json`: delete the manifest; shipped
143
+ files that differ from the package are then reported as untracked conflicts instead of
144
+ being overwritten.
145
+ - Port already in use: set `HINDSIGHT_PORT` / `HINDSIGHT_CP_PORT`, or let the wizard pick
146
+ a free port.
147
+
148
+ ## Maintainers: publishing
149
+
150
+ Before the first publish (these are human steps; nothing in this repo does them):
151
+
152
+ 1. **License.** The repository ships `LICENSE` (Apache-2.0) and `package.json` matches.
153
+ 2. **Public source.** `npm publish --provenance` needs a public GitHub repository.
154
+ 3. **Pack gate.** `npm run pack:check` must pass (no operator home paths in shipped files).
155
+ 4. **Create the npm package.** Log in with 2FA (`npm login`), then publish `0.1.0` once by
156
+ hand (`npm publish --access public`). The name `openmem` was verified free on
157
+ 2026-10-19 (so was `openmem`); re-check with `npm view openmem`.
158
+ 5. **Trusted publishing.** On npmjs.com, package Settings -> Trusted Publisher -> GitHub
159
+ Actions: owner `bcmyguest`, repository `openmem`, workflow `release.yml`.
160
+ 6. **Release.** Run the *Prepare release* workflow on `main` (or bump `package.json`, tag
161
+ `vX.Y.Z`, and push the tag). `release.yml` runs CI, then publishes with `--provenance`
162
+ when the version is not already on npm, then creates the GitHub Release.
163
+
164
+ Local rehearsal without publishing:
165
+
166
+ ```sh
167
+ npm run pack:check
168
+ npm pack --pack-destination /tmp/pm-pack
169
+ cd "$(mktemp -d)" && npx --yes /tmp/pm-pack/openmem-*.tgz --version
170
+ ```
@@ -0,0 +1,194 @@
1
+ # Mise-like config (`config.toml`)
2
+
3
+ openmem is a **meta** orchestrator (see [architecture.md](architecture.md) on the site; [memory-providers.md](memory-providers.md) in the repo for contributors). Its
4
+ declarative config is intentionally similar to **[mise](https://mise.jdx.dev/)**:
5
+
6
+ | mise | openmem |
7
+ | --- | --- |
8
+ | `~/.config/mise/config.toml` + `.mise.toml` | `~/.config/personal-memory/config.toml` + `openmem.toml` |
9
+ | `[tools]` plugins | `[memory] provider = "…"` + `providers/*/manifest.json` |
10
+ | `[env]` | Host ports / compose project under `[stack]` |
11
+ | `[tasks]` | `npx @bcmyguest/openmem up` / `down` / `doctor` / `install-mcp` / `switch` |
12
+ | `mise install` applies config | `npx @bcmyguest/openmem configure --yes` applies config |
13
+
14
+ We **do not** replace mise (or mem0/Hindsight native config). You can use both: mise installs
15
+ Node/Docker; this file describes **how agent memory runs on this machine**.
16
+
17
+ ## Files
18
+
19
+ | Path | Purpose |
20
+ | --- | --- |
21
+ | `config/openmem.toml.example` | Shipped template; copy to your user config |
22
+ | `~/.config/personal-memory/config.toml` | **Primary** user config (gitignored on your machine) |
23
+ | `./openmem.toml` | Optional per-checkout overrides (legacy `personal-memory.toml` still loads) |
24
+ | `~/.config/hindsight/wizard.conf` | Last configure run (`MEMORY_PROVIDER`, ports, …); updated when setup applies |
25
+
26
+ ## Precedence
27
+
28
+ 1. CLI flags (`npx @bcmyguest/openmem setup --up`, `--down`, …)
29
+ 2. Environment (`MEMORY_PROVIDER`, `PM_BACKEND`, `HINDSIGHT_PORT`, …)
30
+ 3. `~/.config/personal-memory/config.toml`
31
+ 4. `openmem.toml` / `config/openmem.toml` in the checkout stack root (legacy `personal-memory.toml` names still load)
32
+ 5. Existing `wizard.conf` (provider, ports, model backend from the last wizard run)
33
+
34
+ Interactive `./setup` uses this file for **defaults** (Clack pre-selects); your answers still win.
35
+
36
+ ## Sections
37
+
38
+ - **`[memory]`** — `provider` id (`hindsight`, future `mem0`, …).
39
+ - **`[stack]`** — compose project name and host ports.
40
+ - **`[wizard]`** — default action (`configure`, `up`, `down`).
41
+ - **`[model]`** — meta **LLM routing** for the Hindsight stack only; other providers add
42
+ `[memory.<id>]` tables (see `[memory.mem0]` below).
43
+ - **`[memory.mem0]`** — the mem0 provider (URL-only: you run a mem0 REST server, this stack points
44
+ the indexer at it). Never holds a key.
45
+
46
+ ```toml
47
+ [memory]
48
+ provider = "mem0"
49
+
50
+ [memory.mem0]
51
+ api_url = "http://127.0.0.1:18888" # the server as this host sees it
52
+ # env_file = "~/.config/personal-memory/mem0.env" # optional; where MEM0_API_KEY lives
53
+ ```
54
+
55
+ `api_url` is resolved as: `MEM0_API_URL`, then `PM_MEM0_API_URL`, then this table, then the stack
56
+ `.env`, then `wizard.conf`, then the default `http://127.0.0.1:18888`. The indexer container
57
+ gets the same URL with `127.0.0.1`/`localhost` rewritten to `host.docker.internal`
58
+ (`MEM0_INDEXER_URL` in the stack `.env`). The API key is written by `./setup` (from
59
+ `MEM0_API_KEY` under `--yes`, or a prompt) as the single line `MEM0_API_KEY=your-key-here` in the env file
60
+ (`MEM0_ENV_FILE`, then `env_file`, then `~/.config/personal-memory/mem0.env`; mode 600 in a mode
61
+ 700 directory). You can also create that file by hand; a server run with `AUTH_DISABLED` needs
62
+ none. The key never goes into the stack `.env`, `wizard.conf` or `config.toml`. A non-default
63
+ `env_file` is exported to compose as `MEM0_ENV_FILE` for the run; export it yourself when you run
64
+ `docker compose` by hand.
65
+ - **`[memory.cognee]`** — the Cognee provider (a pinned `cognee/cognee` container on a loopback
66
+ port, single-user mode, no authentication). Never holds a key.
67
+
68
+ ```toml
69
+ [memory]
70
+ provider = "cognee"
71
+
72
+ [memory.cognee]
73
+ port = 8766 # host port; the container listens on 8000
74
+ env_file = "~/.config/personal-memory/cognee.env" # cognify settings (GLiNER/FastEmbed by default)
75
+ ```
76
+
77
+ `port` is resolved as: `COGNEE_PORT`, then this table, then the stack `.env`, then the port
78
+ saved in `wizard.conf`, then the default `8766`. Both env file locations follow the same order:
79
+ the variable (`COGNEE_ENV_FILE`, `COGNEE_INDEXER_ENV_FILE`), then `env_file` /
80
+ `indexer_env_file` in this table, then the stack `.env`, then the defaults
81
+ `~/.config/personal-memory/cognee.env` and `~/.config/personal-memory/cognee-indexer.env`.
82
+ The second file is optional and is the one the indexer container reads; it holds only
83
+ `COGNEE_API_KEY`. A non-default location (`~` is expanded, a relative path is taken relative
84
+ to the stack root) is written to the stack `.env` as a path, so a plain `docker compose` finds
85
+ the same files; the file contents never go there. Keep custom files inside
86
+ `~/.config/personal-memory`: that is the directory hidden from the indexer's read-only `$HOME`
87
+ mount, and setup warns about a file elsewhere under `$HOME`.
88
+
89
+ Documents are stored but not searchable until cognify can run. **Default cognify path** (no
90
+ API key): local GLiNER extraction + FastEmbed embeddings. `./setup` / `--yes` with no LLM
91
+ answers writes that block into the env file (mode 600 in a mode 700 directory), merging into
92
+ any existing file and replacing only the keys it was given; it prints the path and the key
93
+ names, never a value. Where the answers come from:
94
+
95
+ | Variable (environment) | Written to the env file as |
96
+ | --- | --- |
97
+ | `PM_COGNEE_LLM_API_KEY`; under `--yes` or `--dry-run` also plain `LLM_API_KEY` | `LLM_API_KEY` |
98
+ | `PM_COGNEE_LLM_PROVIDER`, `PM_COGNEE_LLM_MODEL`, `PM_COGNEE_LLM_ENDPOINT` | `LLM_PROVIDER`, `LLM_MODEL`, `LLM_ENDPOINT` |
99
+ | `PM_COGNEE_EMBEDDING_PROVIDER`, `_MODEL`, `_ENDPOINT`, `_DIMENSIONS` | the same names without `PM_COGNEE_` |
100
+ | `PM_COGNEE_EMBEDDING_API_KEY`; under `--yes` or `--dry-run` also plain `EMBEDDING_API_KEY` | `EMBEDDING_API_KEY` |
101
+ | `PM_COGNEE_HUGGINGFACE_TOKENIZER` | `HUGGINGFACE_TOKENIZER` |
102
+ | `PM_COGNEE_GRAPH_EXTRACTOR`, `PM_COGNEE_AUTO_FEEDBACK` | `GRAPH_EXTRACTOR`, `AUTO_FEEDBACK` |
103
+
104
+ **Default (GLiNER + FastEmbed)** when no cognify answers are given:
105
+
106
+ ```env
107
+ GRAPH_EXTRACTOR=gliner_demo
108
+ EMBEDDING_PROVIDER=fastembed
109
+ EMBEDDING_MODEL=BAAI/bge-small-en-v1.5
110
+ EMBEDDING_DIMENSIONS=384
111
+ AUTO_FEEDBACK=false
112
+ ```
113
+
114
+ Compose pins `HF_HOME` / `FASTEMBED_CACHE_PATH` under the `cognee-data` volume so model
115
+ downloads survive container recreates. Changing embedding dimensions requires rebuilding
116
+ vectors (prune datasets or recreate the volume).
117
+
118
+ **Local Ollama** (LLM + Ollama embeddings): set `PM_COGNEE_LLM_PROVIDER=ollama` (and optionally
119
+ `PM_COGNEE_LLM_MODEL=...`). The wizard fills in a complete local block when fields are missing:
120
+
121
+ ```bash
122
+ PM_COGNEE_LLM_PROVIDER=ollama PM_COGNEE_LLM_MODEL=llama3.2 ./setup --yes
123
+ ```
124
+
125
+ That writes (defaults only where unset):
126
+
127
+ ```env
128
+ LLM_PROVIDER=ollama
129
+ LLM_API_KEY=ollama
130
+ LLM_ENDPOINT=http://host.docker.internal:11434
131
+ EMBEDDING_PROVIDER=ollama
132
+ EMBEDDING_MODEL=nomic-embed-text:latest
133
+ EMBEDDING_ENDPOINT=http://host.docker.internal:11434/api/embed
134
+ EMBEDDING_DIMENSIONS=768
135
+ ```
136
+
137
+ Ollama LLM + FastEmbed is also valid: set `PM_COGNEE_EMBEDDING_PROVIDER=fastembed` (and model /
138
+ dimensions) alongside `PM_COGNEE_LLM_PROVIDER=ollama`. Use the bare Ollama host (`…:11434`),
139
+ not `…:11434/v1` — Cognee's Ollama path appends `/api/generate` itself. **Host Ollama**
140
+ (listening on the Docker bridge or all interfaces): `host.docker.internal` is correct.
141
+ **Compose Ollama** published as `127.0.0.1:11434` only (this repo's default): that address is
142
+ unreachable via `host.docker.internal` — set `PM_COGNEE_OLLAMA_COMPOSE=1` (or reuse
143
+ `MEMORY_OLLAMA_IN_COMPOSE=1` from a Hindsight Ollama setup) so the wizard writes
144
+ `http://ollama:11434`, and attach the Cognee container to the same Docker network as the
145
+ `ollama` service. Slow local models may need `COGNEE_SKIP_CONNECTION_TEST=true` in
146
+ `cognee.env`. The interactive setup offers GLiNER+FastEmbed (default), Ollama, or an OpenAI
147
+ key (hidden prompt). Do not type the key into a non-Clack `./setup` prompt — put
148
+ `LLM_API_KEY` in `cognee.env` (mode 600) by hand (Ollama accepts any non-empty value), or use
149
+ the UI / `--yes` with `PM_COGNEE_LLM_API_KEY`. Secret variables (including plain `LLM_API_KEY` /
150
+ `EMBEDDING_API_KEY` / `COGNEE_API_KEY`) are removed from the wizard's environment before it
151
+ starts docker. Secrets never go into the stack `.env`, `wizard.conf`, `config.toml` or the
152
+ summary. Authentication is off: `ENABLE_BACKEND_ACCESS_CONTROL` is pinned false in compose and
153
+ overrides the env file (see `providers/cognee/compose.yaml`).
154
+ - **`[tasks.*]`** — descriptions for `npx @bcmyguest/openmem` / `./pm` (see `wizard/bin/pm.mjs` and
155
+ `cli/openmem.mjs`). Built-in tasks:
156
+ - `configure` / `up` / `down` / `doctor` — map to the setup wizard with the matching flags
157
+ - `install-mcp` — runs `cli/install-memory-mcp.mjs` (NotebookLM MCP on Hindsight; mem0/Cognee
158
+ with `--provider-only`; merges into `~/.cursor/mcp.json`)
159
+ - `switch <provider>` — runs `cli/switch-memory-provider.mjs`: sets `MEMORY_PROVIDER` in
160
+ `wizard.conf`, updates `[memory].provider` in `config.toml` when present
161
+ (`updatePmConfigProvider`), optionally restarts the stack and re-points provider MCP.
162
+ Flags: `--yes`, `--dry-run`, `--no-stack`, `--no-mcp`. Does not migrate data between
163
+ backends. See [README — Switch memory provider](../README.md#switch-memory-provider).
164
+ - `feed-sync` — runs `cli/feed-sync.mjs` (RSS + OpenAlex → `feeds.out_dir` in `index.yml`).
165
+ - `install-coding-agent` — unified MCP + session hooks (all providers); optional `--hindsight-plugin`; see
166
+ [coding-agent hooks](coding-agent-hooks.md).
167
+
168
+ ## MCP (unified `memory_*` tools)
169
+
170
+ | Variable | Default | Purpose |
171
+ | --- | --- | --- |
172
+ | `PM_MEMORY_ADD_ASYNC` | `true` (implicit) | Hindsight `memory_add` sends `async: true` unless the tool passes `async: false`. Async ingest returns in ~200ms and avoids Cursor/Claude MCP `-32001` timeouts while extraction runs in the background. Set `PM_MEMORY_ADD_ASYNC=false` to force synchronous ingest. |
173
+
174
+ After changing MCP code, re-run `npx @bcmyguest/openmem install-coding-agent --yes` (or copy the repo into `~/.local/share/personal-memory/agent-runtime`) and **restart** the openmem MCP server in Cursor.
175
+
176
+ ## Apply
177
+
178
+ ```bash
179
+ cp config/openmem.toml.example ~/.config/personal-memory/config.toml
180
+ # edit provider, ports, model backend
181
+ npx @bcmyguest/openmem configure --yes
182
+ # checkout before publish: npx . configure --yes
183
+ ```
184
+
185
+ After a successful configure, the wizard **writes back** `~/.config/personal-memory/config.toml` so
186
+ the file matches what is running (mise-style source of truth).
187
+
188
+ ## Doctor
189
+
190
+ ```bash
191
+ npx @bcmyguest/openmem doctor
192
+ ```
193
+
194
+ Runs the same checks as setup step “Validate memory setup” without mutating the stack.
@@ -0,0 +1,222 @@
1
+ # Provider wizard hooks (sub-wizards)
2
+
3
+ The meta wizard (`./setup`, `./pm`) owns cross-provider steps. Each **provider plugin** ships a
4
+ **`providers/<id>/wizard.mjs`** module, the same relationship as asdf/mise plugins: the core tool
5
+ runs the plugin's hooks; it does not reimplement Node, Rust, mem0, or a model router.
6
+
7
+ The meta wizard knows nothing about any one provider. `wizard/lib/run-wizard.mjs`,
8
+ `wizard/lib/ports.mjs` and `wizard/lib/validate-setup.mjs` contain no provider names (a test greps
9
+ for this). A provider that exports no hook for a phase gets a clean no-op for that phase: there is
10
+ no default provider and no fallback implementation.
11
+
12
+ ## Hook API version
13
+
14
+ `helpers.apiVersion` is `2`. It is bumped when a helper is removed or changes meaning; adding a
15
+ helper does not bump it. Version 1 (the `chooseBackend` helper and `fallback:` argument) is gone.
16
+ The v1 phase names still work unchanged.
17
+
18
+ ## Package root vs stack root
19
+
20
+ | Field | Env | Meaning | Used for |
21
+ | --- | --- | --- | --- |
22
+ | `ctx.repoRoot` | `PM_STACK_ROOT` | Mutable stack root | compose project dir (`cwd` of every `docker compose` call), `.env`, `compose.override.yaml`, `.build-hash`, generated files |
23
+ | `ctx.packageRoot` | `PM_PACKAGE_ROOT` (default: `repoRoot`) | Read-only code root | `providers/` manifests and hook modules, `scripts/setup.mjs`, `scripts/gen-masks.mjs` |
24
+
25
+ For a git checkout they are the same directory. For an installed package the integrator passes the
26
+ stack root as `repoRoot` (`runWizard(stackRoot, argv)`) and the package directory through
27
+ `PM_PACKAGE_ROOT`. `runWizard` exports `PM_PACKAGE_ROOT` so child code sees it.
28
+ `PM_PROVIDERS_DIR` overrides the providers directory entirely (tests use it for fake providers).
29
+ Provider code must write only under `ctx.repoRoot` and read bundled files only under
30
+ `ctx.packageRoot`.
31
+
32
+ ## Execution order
33
+
34
+ ```
35
+ meta.preflight
36
+ meta.provider pick memory provider (Clack / TOML / --yes)
37
+ provider.init quiet: seed ctx defaults (also runs before preflight)
38
+ provider.resolvePorts host ports (also after the provider is chosen)
39
+ provider.afterProvider
40
+ provider.configureModelRouting models, keys, logins
41
+ meta.wizardConf written unless the provider already saved it
42
+ meta.setupEnv indexer state dir, then provider.setupEnv or scripts/setup.mjs
43
+ meta.index index.yml
44
+ meta.notebooklm optional profile
45
+ provider.beforeStack compose fragments, migrations
46
+ meta.stack build, up, health, provider.afterStackHealthy, then smoke
47
+ meta.codingAgent generic npx plugin, then provider.setupCodingAgent
48
+ meta.validate generic checks, then provider.extendValidation
49
+ meta.syncConfig config.toml (provider.configToml sections)
50
+ meta.summary provider.summary
51
+ ```
52
+
53
+ **Stack-only actions** (`./setup --up`, `./pm up`) skip the configure phases:
54
+
55
+ ```
56
+ meta.preflight -> meta.provider -> hydrate -> provider.resolvePorts -> provider.beforeStack
57
+ -> meta.stack -> meta.validate -> meta.summary
58
+ ```
59
+
60
+ **Down** runs `meta.preflight -> meta.stackDown -> meta.summary`. There are no down hooks yet.
61
+
62
+ ## Hook module contract
63
+
64
+ Path: `providers/<id>/wizard.mjs` (ESM), found through the manifest:
65
+
66
+ ```json
67
+ "wizardHooks": { "module": "wizard.mjs" }
68
+ ```
69
+
70
+ The path must stay inside the provider directory. Every export is an optional
71
+ `async function(ctx, helpers)`; implement only what you need. Hooks marked announced print
72
+ `provider <id>: <phase>` when they run; quiet hooks return a value to the meta wizard.
73
+
74
+ | Export | Kind | Return | When it runs |
75
+ | --- | --- | --- | --- |
76
+ | `init` | quiet | ignored | Seed provider defaults on `ctx` before anything reads them |
77
+ | `hydrate` | quiet | ignored | Restore provider state from the saved `wizard.conf` |
78
+ | `afterProvider` | announced | ignored | Right after the provider is selected |
79
+ | `configureModelRouting` | announced | ignored | Model backend, API keys, logins. Honor `ctx.skipBackendStep` (Clack already asked) |
80
+ | `resolvePorts` | announced | ignored | Choose free host ports (`helpers.choosePort`, `helpers.resolveHostPort`) |
81
+ | `stackEnv` | quiet | `{ description?, env? }` | What the generic `scripts/setup.mjs` run should print and receive |
82
+ | `setupEnv` | announced | ignored | Replace the generic secrets/env step; call `helpers.runStackSetup` to still run `scripts/setup.mjs` |
83
+ | `beforeStack` | announced | ignored | After env and index, before `docker compose up` |
84
+ | `apiBase` | quiet | string | HTTP base URL when it is not `http://127.0.0.1:<compose.hostPortEnv>` |
85
+ | `smoke` | announced | ignored | Replace the manifest HTTP smoke test |
86
+ | `afterStackHealthy` | announced | ignored | After health, before smoke (e.g. pull compose Ollama models) |
87
+ | `setupCodingAgent` | announced | ignored | After the generic coding-agent plugin step |
88
+ | `extendValidation` | announced | ignored | Push to `ctx.validationErrors` / `ctx.validationChecks`; runs after the generic checks |
89
+ | `configToml` | quiet | `{ stack?, memory?, <table>: {...} }` | Sections for `config.toml`: `stack` merges into `[stack]`, `memory` sub-tables (for example `memory.mem0`) merge into `[memory]` and `provider` there is always set by the wizard, every other key is a table |
90
+ | `summary` | quiet | `{ running?, configFiles?, notes?, logServices? }` | Lines for the closing summary |
91
+ | `clackSetup` | none | `{ env }` | Called by the Clack front end as `(ctxStub, ui, helpers)` |
92
+ | `prepareStackEnv` | script | `{ entries, drop, report }` | Called by `scripts/setup.mjs`; `.env` always gets `HOST_UID`/`HOST_GID` |
93
+ | `migrateEnv` | script | n/a | `scripts/setup.mjs --migrate-env` |
94
+ | `setupNotices` | script | `string[]` | Lines `scripts/setup.mjs` prints at the end |
95
+
96
+ `prepareStackEnv`, `migrateEnv` and `setupNotices` receive `{ repo, home, env }` (`repo` is the
97
+ stack root) and run in a separate Node process, so they may not use `helpers`.
98
+
99
+ Unrecognised exports are ignored. Providers must not import `wizard/lib/run-wizard.mjs`; it
100
+ imports them. Everything the meta wizard owns reaches a hook through `helpers`.
101
+
102
+ ## Helpers
103
+
104
+ `createHelpers()` in `wizard/lib/provider-wizard/helpers.mjs` returns a frozen object. Functions
105
+ that take `ctx` honor `--dry-run` (they print instead of acting).
106
+
107
+ | Helper | Purpose |
108
+ | --- | --- |
109
+ | `apiVersion` | `2` |
110
+ | `step`, `info`, `ok`, `warn`, `die` | Logging; `die` exits 1 |
111
+ | `problem(ctx, msg)` | `die`, or `warn` in a dry run |
112
+ | `ask`, `confirm` | Prompts (answers come from `ctx.yes` and `PM_*` env when non-interactive) |
113
+ | `act(ctx, desc, cmd, args, {env, cwd})`, `actWithEnv` | Run a command, printed only in a dry run |
114
+ | `commandExists(name)` | PATH lookup |
115
+ | `envValue(file, key)`, `expandHome`, `ensureConfigDir(ctx)` | Paths and wizard.conf reads |
116
+ | `curlGet`, `curlJson`, `curlRequest` | HTTP via curl |
117
+ | `portFree`, `choosePort(ctx, label, port, {envVars})`, `resolveHostPort(ctx, label)` | Ports; `resolveHostPort` reads the manifest `compose.hostPortEnv` and `defaultHostPort` |
118
+ | `composeArgs(ctx, ...rest)` | `docker compose` argv: project, optional `-f` files, optional `--profile` |
119
+ | `saveWizardConf(ctx, extra, {pmSections})` | Write `wizard.conf` (and `config.toml` sections); no-op in a dry run. Always includes `MEMORY_PROVIDER` |
120
+ | `runStackSetup(ctx, opts)` | Run `<packageRoot>/scripts/setup.mjs` against the stack root |
121
+ | `apiBase(ctx)` | The provider's resolved HTTP base URL |
122
+
123
+ ## Manifest fields the meta wizard reads
124
+
125
+ ```json
126
+ "compose": {
127
+ "profile": "mem0",
128
+ "files": ["compose.mem0.yaml"],
129
+ "hostPortEnv": "MEM0_PORT",
130
+ "defaultHostPort": 8000,
131
+ "healthService": "mem0",
132
+ "logServices": ["mem0"]
133
+ },
134
+ "wizard": { "recallHint": "check the server ({compose} logs mem0)" }
135
+ ```
136
+
137
+ - `compose.profile` adds `--profile <name>`; `compose.files` adds `-f` flags after `compose.yaml`
138
+ and after `compose.override.yaml` when that exists. Both apply to every compose call: `up`,
139
+ `down`, `build`, `ps`, and the indexer run. Files must be inside the stack root (no absolute
140
+ paths, no `..`). With neither field the command text is exactly what it was before.
141
+ - `compose.services` is documentation only: `up` is scoped by the profile, not by service list.
142
+ - The API base URL comes from `compose.hostPortEnv` and `defaultHostPort`, or from an `apiBase`
143
+ hook. `ctx.apiUrl` (`--api-url`) wins over both.
144
+ - `wizard.recallHint` is the error hint when the smoke recall fails. `{compose}` expands to the
145
+ full compose command prefix and `{project}` to the project name. Without it the generic
146
+ message uses `compose.logServices` or `compose.healthService`.
147
+ - A provider with no model backend sets none of `MEMORY_BACKEND`, `MEMORY_MODEL`, ...; generic
148
+ validation does not require them.
149
+
150
+ ## Delegated commands (asdf-style)
151
+
152
+ A manifest may declare commands the provider owns outright:
153
+
154
+ ```json
155
+ "wizardHooks": {
156
+ "module": "wizard.mjs",
157
+ "delegates": {
158
+ "mem0-login": { "description": "Run mem0 CLI login", "run": ["mem0", "login"] }
159
+ }
160
+ }
161
+ ```
162
+
163
+ `./pm mem0-login` runs `run` with inherited stdio when that provider is active. Prefer delegates
164
+ over reimplementing provider CLIs in Node.
165
+
166
+ ## Hindsight
167
+
168
+ Everything about LiteLLM routes, lemond, Ollama, custom OpenAI-compatible backends and the Clack
169
+ model prompts lives in `providers/hindsight/`:
170
+
171
+ | File | Role |
172
+ | --- | --- |
173
+ | `wizard.mjs` | Thin hook exports; re-exports `prepareStackEnv`, `migrateEnv`, `setupNotices` |
174
+ | `lib/state.mjs` | `ensureState`, backend defaults, `wizard.conf` reads |
175
+ | `lib/model-backend.mjs` | Backend choice (lemond / ollama / custom), reflect model, model pulls |
176
+ | `lib/litellm-routes.mjs` | Write the LiteLLM route file and `hindsight.env`; saves `wizard.conf` |
177
+ | `lib/probe.mjs` | Probe lemond/ollama endpoints and list models |
178
+ | `lib/config.mjs` | `defaultsFromConfig`, `applyConfigEnv`, `configToml` (`[stack]` ports, `[model]`) |
179
+ | `lib/ports.mjs` | `resolvePorts` for the API and control-plane ports |
180
+ | `lib/env-setup.mjs` | `stackEnv`, `setupEnv` (secrets, LiteLLM env) |
181
+ | `lib/stack-env.mjs` | `prepareStackEnv`, `migrateEnv`, `setupNotices` for `scripts/setup.mjs` |
182
+ | `lib/validate.mjs` | Hindsight validation (model backend, route file, ports) |
183
+ | `lib/summary.mjs` | Closing summary lines |
184
+ | `lib/coding-agent.mjs` | seed/materialize `~/.hindsight/coding-agent.json` (real file) and opt-in paths check |
185
+ | `lib/clack.mjs` | `clackSetup`: backend and model prompts for the Clack front end |
186
+
187
+ Its `wizard.conf` keeps `MEMORY_PROVIDER`, `MEMORY_BACKEND`, `MEMORY_MODEL`, `MEMORY_API_BASE` and
188
+ `MEMORY_REFLECT_MODEL`. The manifest sets `compose.profile: "hindsight"`, so a
189
+ non-Hindsight provider never starts its services.
190
+
191
+ `configureModelRouting` takes `ctx.skipBackendStep` (`WIZARD_SKIP_BACKEND_STEP=1`) to skip the
192
+ prompts when Clack has already collected the answers.
193
+
194
+ ## Loader
195
+
196
+ `wizard/lib/provider-wizard/run-steps.mjs`:
197
+
198
+ - `loadProviderWizard(manifest)`: import `providers/<id>/wizard.mjs`, cached per file; `{}` when absent
199
+ - `callProviderHook(ctx, phase, helpers)`: returns `{ ran, value }`; announces the announced phases
200
+ - `runProviderPhase(ctx, phase, helpers)`: the same, returning whether the hook ran
201
+ - `runDelegatedCommand`, `listDelegateCommands`: manifest `delegates`
202
+
203
+ ## Tests
204
+
205
+ - `scripts/tests/test_provider_wizard_hooks.mjs`: helpers `apiVersion`, frozen helpers, hook loader,
206
+ stubbed-helpers tests of the Hindsight `configureModelRouting` (`--yes`, `skipBackendStep`, the
207
+ three backends), static checks (no provider imports `run-wizard.mjs`; no provider names in the
208
+ meta files)
209
+ - `scripts/tests/test_provider_manifests.mjs`: manifest schema incl. `compose.profile`/`files`
210
+ - `scripts/tests/test_wizard.sh`: dry-run and real-run scenarios with a docker stub, including a
211
+ provider with no hooks, a provider with every v2 hook (fixtures in
212
+ `scripts/tests/fixtures/providers/`, selected with `PM_PROVIDERS_DIR`), and a package-root /
213
+ stack-root split
214
+
215
+ ## Adding hooks (checklist)
216
+
217
+ 1. Add `wizardHooks.module` (and `compose.*`) to `manifest.json`
218
+ 2. Create `providers/<id>/wizard.mjs`; implement only the phases you need
219
+ 3. Keep provider state on `ctx` through `init`/`hydrate`; return `configToml`/`summary` data instead of writing in the meta files
220
+ 4. Add `delegates` only when a native CLI must run on the host
221
+ 5. Document native vs meta ownership in manifest `settings`
222
+ 6. Extend the tests and the [add-memory-provider](../.agents/skills/add-memory-provider/SKILL.md) skill