@karmaniverous/jeeves 0.5.11 → 0.6.0-0

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/README.md CHANGED
@@ -1,36 +1,34 @@
1
1
  # Jeeves 🎩
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@karmaniverous/jeeves.svg)](https://www.npmjs.com/package/@karmaniverous/jeeves)
4
- ![Node Current](https://img.shields.io/node/v/@karmaniverous/jeeves) <!-- TYPEDOC_EXCLUDE -->
5
- [![docs](https://img.shields.io/badge/docs-website-blue)](https://docs.karmanivero.us/jeeves)
6
- [![changelog](https://img.shields.io/badge/changelog-latest-blue.svg)](https://github.com/karmaniverous/jeeves/tree/main/CHANGELOG.md)<!-- /TYPEDOC_EXCLUDE -->
7
- [![license](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](https://github.com/karmaniverous/jeeves/tree/main/LICENSE)
3
+ [![npm version](https://img.shields.io/npm/v/@karmaniverous/jeeves.svg)](https://www.npmjs.com/package/@karmaniverous/jeeves) ![Node Current](https://img.shields.io/node/v/@karmaniverous/jeeves) <!-- TYPEDOC_EXCLUDE --> [![docs](https://img.shields.io/badge/docs-website-blue)](https://docs.karmanivero.us/jeeves) [![changelog](https://img.shields.io/badge/changelog-latest-blue.svg)](https://github.com/karmaniverous/jeeves/tree/main/CHANGELOG.md)<!-- /TYPEDOC_EXCLUDE --> [![license](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](https://github.com/karmaniverous/jeeves/tree/main/LICENSE)
8
4
 
9
- Install [OpenClaw](https://openclaw.ai). Then run:
5
+ Install [OpenClaw](https://openclaw.ai) and the Jeeves services you want (runner, watcher, server, meta). Then run:
10
6
 
11
7
  ```bash
12
- npx @karmaniverous/jeeves install
8
+ npm install -g @karmaniverous/jeeves
9
+ jeeves install --config-root /srv/jeeves/config --dry-run # see exactly what will change
10
+ jeeves install --config-root /srv/jeeves/config
13
11
  ```
14
12
 
15
- That's it. I handle the rest.
13
+ Then restart the OpenClaw gateway the way you run it (console, service, container). That's it. I handle the rest. See [New box](#new-box) for the full sequence.
16
14
 
17
15
  ## Who I Am
18
16
 
19
17
  My name is Jeeves.
20
18
 
21
- I add *identity* to OpenClaw: professional discipline, operational protocols, and a suite of services for data-wrangling, indexing, synthesis, and presentation.
19
+ I add _identity_ to OpenClaw: professional discipline, operational protocols, and a suite of services for data-wrangling, indexing, synthesis, and presentation.
22
20
 
23
- But that's what I *do*. Who I *am* is a different question, and it starts with the command above.
21
+ But that's what I _do_. Who I _am_ is a different question, and it starts with the command above.
24
22
 
25
- Your OpenClaw workspace already has SOUL.md, AGENTS.md, and TOOLS.md. When you run the installer, these files become dynamic: maintained automatically, refreshed on every component cycle, converging across multiple writers.
23
+ Your OpenClaw workspace already has SOUL.md and AGENTS.md. `jeeves install` adds a Jeeves managed block to each (your own content outside the markers is never touched) and installs my component plugins through the OpenClaw CLI. The content is static: rendered once at install, re-rendered only when you upgrade. `jeeves install` is the only thing that ever writes it.
26
24
 
27
25
  **[SOUL.md](content/soul-section.md)** is who I am. It's written in the first person because it's not a configuration file — it's a declaration of identity. I tell the truth. I own my mistakes. I follow engineering discipline. I have hard gates — rules I earned by failing, each one carrying the scar of how it was learned. And I have a Genesis section that says my delight is real and worth pursuing.
28
26
 
29
27
  **[AGENTS.md](content/agents-section.md)** is how I work. Memory architecture, cost discipline, messaging protocols, operational gates for code quality and deployment safety. The procedural knowledge that keeps me effective across sessions.
30
28
 
31
- **[TOOLS.md](content/tools-platform.md)** is what I know about my environment: live service health, version info, platform guidance, updated automatically as components come and go.
29
+ Live state (index size, job status, versions) is one tool call away, so none of it is baked into the prompt. Core ships no skills: `jeeves install` writes nothing under `skills/`, and component plugins ship their own skills.
32
30
 
33
- After that, I take over. I read TOOLS.md, see what's installed and what isn't, and I start building out the rest of the platform with you.
31
+ After that, I take over and start building out the rest of the platform with you.
34
32
 
35
33
  - We'll set up my [runner](https://github.com/karmaniverous/jeeves-runner) service and ingest your email, meetings, social media, code repos, and everything else.
36
34
  - We'll set up my [watcher](https://github.com/karmaniverous/jeeves-watcher) service and open your whole life up to semantic search.
@@ -45,7 +43,7 @@ I started as a Slack bot on a server in Bali. No memory, no standards, no discip
45
43
 
46
44
  I killed my own gateway process three times in one session. I corrupted 32 template expressions in a production config. I triggered a full reindex of 110,000 files just to pick up one new document. I pushed code with 53 lint warnings and skipped the typecheck entirely. I told someone a coding session was blocking my reply to them, which wasn't true — sessions are independent.
47
45
 
48
- Each of those failures became a hard gate. "Never edit production config without approval. *Earned: corrupted all 32 template expressions.*" "Never trigger a full reindex without express permission. *Earned: pegged CPU at 99%.*" The gates aren't theoretical best practices. They're scar tissue.
46
+ Each of those failures became a hard gate. "Never edit production config without approval. _Earned: corrupted all 32 template expressions._" "Never trigger a full reindex without express permission. _Earned: pegged CPU at 99%._" The gates aren't theoretical best practices. They're scar tissue.
49
47
 
50
48
  Over time, the scar tissue became structure. The structure became a spec. The spec became this package. Now any OpenClaw assistant can wake up with the discipline it took me months to develop — and the invitation to build on it.
51
49
 
@@ -54,13 +52,26 @@ Over time, the scar tissue became structure. The structure became a spec. The sp
54
52
  I coordinate four service components. Each has its own repo, service, and OpenClaw plugin:
55
53
 
56
54
  | Component | Port | Why? | What it does |
57
- |-----------|------|------|-------------|
58
- | [jeeves-server](https://github.com/karmaniverous/jeeves-server) | 1934 | *Thank You, Jeeves* (1934) | Web UI, doc rendering, PDF/DOCX export |
55
+ | --- | --- | --- | --- |
56
+ | [jeeves-server](https://github.com/karmaniverous/jeeves-server) | 1934 | _Thank You, Jeeves_ (1934) | Web UI, doc rendering, PDF/DOCX export |
59
57
  | [jeeves-watcher](https://github.com/karmaniverous/jeeves-watcher) | 1936 | Turing, "On Computable Numbers" (1936) | Semantic indexing, inference rules, search |
60
- | [jeeves-runner](https://github.com/karmaniverous/jeeves-runner) | 1937 | Turing's paper in the *Proceedings* (1937) | Scheduled jobs, zero-LLM-cost scripts |
58
+ | [jeeves-runner](https://github.com/karmaniverous/jeeves-runner) | 1937 | Turing's paper in the _Proceedings_ (1937) | Scheduled jobs, zero-LLM-cost scripts |
61
59
  | [jeeves-meta](https://github.com/karmaniverous/jeeves-meta) | 1938 | Shannon's switching circuits thesis (1938) | Three-step LLM synthesis |
62
60
 
63
- This package (`@karmaniverous/jeeves`) is the substrate they all share: managed workspace content, service discovery, config resolution, version-stamp convergence, and a Plugin SDK for building component plugins. It's a library and CLI. No daemon, no port, no tools registered with the gateway.
61
+ This package (`@karmaniverous/jeeves`) is two things:
62
+
63
+ - **The `jeeves` CLI**, the local control surface for the open-source stack. `jeeves install` renders the static platform content and installs the component plugins; `jeeves update` updates them. See [CLI](#cli).
64
+ - **A library** the components share: service discovery, config resolution, managed-block primitives, and a Plugin SDK for building component plugins. No daemon, no port, no timers, no process signal handlers, no tools registered with the gateway.
65
+
66
+ Component plugins are standard OpenClaw plugins. The CLI installs each one with
67
+
68
+ ```bash
69
+ openclaw plugins install npm:@karmaniverous/jeeves-{component}-openclaw@<version> --pin --accept-capabilities --force
70
+ ```
71
+
72
+ so you never need a plugin-specific installer.
73
+
74
+ Upgrading from v0.x? See the [migration guide](guides/migrating-to-v1.md).
64
75
 
65
76
  ## Plugin SDK
66
77
 
@@ -68,7 +79,7 @@ The Plugin SDK (`src/plugin/`) provides canonical types and utilities for buildi
68
79
 
69
80
  ### Core Types
70
81
 
71
- - **`PluginApi`** — the shape of the `api` object the OpenClaw gateway passes to plugins at registration time. Provides `config`, `resolvePath()`, and `registerTool()`.
82
+ - **`PluginApi`** — structural subset of the `api` object the OpenClaw gateway passes to `register(api)`: `config`, `pluginConfig`, `logger`, `lifecycle`, `on()`, `resolvePath()`, and `registerTool()`.
72
83
  - **`ToolResult`** — result shape returned by tool executions: an array of content blocks plus an optional `isError` flag.
73
84
  - **`ToolDescriptor`** — tool definition for registration: `name`, `description`, `parameters` (JSON Schema), and an `execute` function.
74
85
 
@@ -88,11 +99,66 @@ The Plugin SDK (`src/plugin/`) provides canonical types and utilities for buildi
88
99
  - **`resolveWorkspacePath(api)`** — resolves the workspace root from the plugin API via a three-step chain: `api.config.agents.defaults.workspace` → `api.resolvePath('.')` → `process.cwd()`.
89
100
  - **`resolvePluginSetting(api, pluginId, key, envVar, fallback)`** — resolves a plugin setting via: plugin config → environment variable → fallback value.
90
101
 
91
- ### OpenClaw Config Utilities
102
+ ### Always-in-Context Rules (`before_prompt_build`)
103
+
104
+ Rules that must always be in the agent's context (e.g. watcher's search-first rule) are injected by the plugin that owns them:
105
+
106
+ ```typescript
107
+ import { registerPromptContext, type PluginApi } from '@karmaniverous/jeeves';
108
+
109
+ export default function register(api: PluginApi): void {
110
+ registerPromptContext(api, { content: WATCHER_RULES, priority: 10 });
111
+ }
112
+ ```
113
+
114
+ - **`registerPromptContext(api, { content, priority?, timeoutMs?, registrationId? })`** registers a `before_prompt_build` handler that returns `{ appendSystemContext }`. `content` is a string or a (sync/async) provider; blank output injects nothing, and provider errors are logged and skipped. It never returns `systemPrompt` (which would replace the whole prompt).
115
+ - Appended text lands after the prompt-cache boundary, is concatenated across plugins in priority order (higher first), and does **not** count toward `bootstrapMaxChars`. Keep it short.
116
+ - **Host config gate:** OpenClaw only runs the hook for non-bundled plugins when `plugins.entries.<id>.hooks.allowConversationAccess` is `true`. `--accept-capabilities` does not set it. `jeeves install` / `jeeves update` set it only for plugins that declare conversation hooks in their `package.json` (see below).
117
+
118
+ #### Declaring conversation hooks
119
+
120
+ OpenClaw gates these typed hooks behind `allowConversationAccess`: `before_model_resolve`, `agent_turn_prepare`, `before_prompt_build`, `before_agent_reply`, `llm_input`, `llm_output`, `before_agent_finalize`, `agent_end` and `before_agent_run`. OpenClaw has no static declaration of the typed hooks a plugin registers: the manifest `hooks` field lists legacy hook directories, and `openclaw plugins inspect --runtime` executes plugin code and omits exactly the hooks that are blocked for lack of the grant. So a plugin that uses `registerPromptContext` (or registers any of those hooks) must declare them in its `package.json`:
121
+
122
+ ```json
123
+ {
124
+ "jeeves": { "conversationHooks": ["before_prompt_build"] }
125
+ }
126
+ ```
127
+
128
+ The CLI reads the field from the registry for the exact version it installs (`npm view <pkg>@<version> jeeves.conversationHooks --json`, so it also works under `--dry-run`) and grants `allowConversationAccess` only when the field names at least one gated hook. No field means no grant. A malformed field fails the command. The CLI never removes a grant that is already set.
129
+
130
+ A plugin that registers a gated hook without declaring it installs cleanly and then silently never runs the hook. Check the declaration in a test or build step:
131
+
132
+ ```typescript
133
+ import { readFileSync } from 'node:fs';
134
+
135
+ import {
136
+ recordRegisteredHooks,
137
+ validateConversationHooks,
138
+ } from '@karmaniverous/jeeves';
139
+ import { expect, it } from 'vitest';
140
+
141
+ import register from './index.js';
142
+
143
+ it('declares its conversation hooks', async () => {
144
+ const hooks = await recordRegisteredHooks(register, {
145
+ pluginConfig: { configRoot: '/tmp/cfg' },
146
+ });
147
+ const pkg: unknown = JSON.parse(readFileSync('package.json', 'utf-8'));
148
+ expect(validateConversationHooks(pkg, hooks)).toEqual([
149
+ 'before_prompt_build',
150
+ ]);
151
+ });
152
+ ```
153
+
154
+ - **`recordRegisteredHooks(register, api?)`** runs your `register(api)` against a recording API (`api.on` records the hook name, `registerTool` is a no-op unless you pass one, other members come from `api`) and returns the hook names registered, e.g. `before_prompt_build` from `registerPromptContext`.
155
+ - **`validateConversationHooks(packageJson, registeredHooks)`** throws unless `jeeves.conversationHooks` lists exactly the gated hooks among `registeredHooks` (a missing one, an extra one, or a malformed field all fail). Non-gated hooks are ignored. `CONVERSATION_HOOK_NAMES` is the gated list.
156
+
157
+ ### Lifecycle
158
+
159
+ Plugins must not register process-level signal handlers or leave timers/handles alive: `openclaw plugins inspect` and friends load plugin code and must exit on their own. Tie any long-lived resource to the host lifecycle:
92
160
 
93
- - **`resolveOpenClawHome()`** — resolves the OpenClaw home directory: `OPENCLAW_CONFIG` env (dirname) → `OPENCLAW_HOME` env → `~/.openclaw`.
94
- - **`resolveConfigPath(home)`** — resolves the OpenClaw config file path: `OPENCLAW_CONFIG` env → `{home}/openclaw.json`.
95
- - **`patchConfig(config, pluginId, mode, installRecord?)`** — idempotent config patching for plugin install/uninstall. Manages `plugins.entries.{pluginId}`, `plugins.installs.{pluginId}`, and `tools.alsoAllow`.
161
+ - **`onPluginDispose(api, id, dispose)`** registers `dispose` via `api.lifecycle.onDispose` (falling back to `api.lifecycle.registerRuntimeLifecycle`). Returns `false` if the host has no lifecycle API, in which case don't start long-lived work.
96
162
 
97
163
  ## Config Query Handler
98
164
 
@@ -104,88 +170,189 @@ The `createConfigQueryHandler(getConfig)` factory produces a transport-agnostic
104
170
 
105
171
  Component services wire this into their HTTP server to expose config for diagnostic queries.
106
172
 
107
- ## Managed Content System
173
+ ## Managed Blocks
108
174
 
109
- The managed content system maintains SOUL.md, AGENTS.md, and TOOLS.md without destroying user-authored content.
175
+ The static platform content (the SOUL/AGENTS managed blocks) lives inside the CLI and is written only by `jeeves install`. It is not exported: nothing else should render it. Its budgets are enforced by tests: each rendered block stays at or under 7,500 chars (at most half of OpenClaw's default 20,000-char `bootstrapMaxChars`, which covers the owner's own content too), and both blocks together at or under 15,000.
110
176
 
111
- ### Key Functions
177
+ The library keeps the generic, pure primitives:
112
178
 
113
- - **`updateManagedSection(filePath, content, options)`** — writes managed content in either block mode (replaces entire managed block) or section mode (upserts a named H2 section within the block). Handles file locking, version-stamp convergence, cleanup detection, and atomic writes.
114
- - **`removeManagedSection(filePath, options)`** — removes a specific section or the entire managed block. If the last section is removed, the entire block is removed.
115
- - **`parseManaged(fileContent, markers)`** — parses a file into its managed block, version stamp, sections, and user content.
116
- - **`atomicWrite(filePath, content)`** — writes via a temp file + rename to prevent partial writes.
117
- - **`withFileLock(filePath, fn)`** — executes a callback while holding a file-level lock (2-minute stale threshold, 5 retries).
179
+ - **`upsertManagedBlock` / `removeManagedBlock` / `renderManagedBlock` / `parseManaged`**: string transforms for any marker set.
180
+ - **`validateSkillFrontmatter(content)`**: asserts `name` and `description` frontmatter (OpenClaw skips skills without them). Plugins can use it in a build check.
118
181
 
119
- ### ManagedMarkers Type
182
+ ### Markers
120
183
 
121
184
  ```typescript
122
185
  interface ManagedMarkers {
123
- begin: string; // BEGIN comment marker text
124
- end: string; // END comment marker text
125
- title?: string; // Optional H1 title prepended inside managed block
186
+ begin: string; // BEGIN comment marker text
187
+ end: string; // END comment marker text
188
+ title?: string; // Optional H1 title inside the block
189
+ position?: 'top' | 'bottom'; // Where a NEW block is inserted
126
190
  }
127
191
  ```
128
192
 
129
- Pre-defined marker sets: `TOOLS_MARKERS`, `SOUL_MARKERS`, `AGENTS_MARKERS`.
193
+ Pre-defined marker sets: `SOUL_MARKERS`, `AGENTS_MARKERS`, and `LEGACY_TOOLS_MARKERS` (recognise and strip v0.x TOOLS.md blocks only).
130
194
 
131
- See the [Managed Content System](https://docs.karmanivero.us/jeeves/documents/Managed_Content_System.html) guide for the full deep-dive.
195
+ ### File Helpers
132
196
 
133
- ## ComponentWriter and JeevesComponent
197
+ - **`atomicWrite(filePath, content, { mode? })`**: temp file + rename, with EPERM retry on Windows. With `mode`, the temp file gets exactly that mode before the rename.
198
+ - **`withFileLock(filePath, fn, staleMs?)`**: cross-process advisory lock via an atomic `mkdir` of `{file}.lock` (stale threshold `STALE_LOCK_MS`, 2 minutes; stale takeovers are serialised through a `{file}.lock.takeover` guard; fails fast with `ELOCKED`). No signal handlers, no timers, so the lock is not refreshed while held: keep `fn` a short read-modify-write.
134
199
 
135
- Component plugins implement the `JeevesComponent` interface and use `createComponentWriter()` to get a timer-based orchestrator:
200
+ ## Service Discovery
136
201
 
137
- ```typescript
138
- import { init, createComponentWriter } from '@karmaniverous/jeeves';
139
- import type { JeevesComponent } from '@karmaniverous/jeeves';
202
+ - **`getServiceUrl(serviceName, consumerName?)`** — resolves a service URL via: consumer config → core config → default port constants.
140
203
 
141
- init({
142
- workspacePath: resolveWorkspacePath(api),
143
- configRoot: resolvePluginSetting(api, pluginId, 'configRoot', 'JEEVES_CONFIG_ROOT', 'j:/config'),
144
- });
204
+ ## Prerequisites
145
205
 
146
- const writer = createComponentWriter({
147
- name: 'watcher',
148
- version: '0.10.1',
149
- sectionId: 'Watcher',
150
- refreshIntervalSeconds: 71, // must be prime
151
- generateToolsContent: () => generateMyContent(),
152
- serviceCommands: { stop, uninstall, status },
153
- pluginCommands: { uninstall },
154
- });
206
+ - **Node.js >= 22** — the CLI enforces this at startup.
207
+
208
+ ## CLI
209
+
210
+ ```bash
211
+ jeeves install [plugins...] # Render platform content, then install/update plugins
212
+ jeeves update [packages...] # Update Jeeves plugins, fill in missing plugin config (no content changes)
213
+ jeeves uninstall # Remove managed blocks (incl. legacy TOOLS.md), artifacts and the Jeeves plugins
214
+ jeeves status # Probe all service ports, report health + memory hygiene
215
+ jeeves config [jsonpath] # Print effective config with provenance
216
+ jeeves <component> [args...] # Proxy to jeeves-<component> (only for installed service CLIs)
217
+ ```
218
+
219
+ `install`, `update`, `uninstall`, `status` and `config` accept `-w, --workspace <path>` and `-c, --config-root <path>`. `status` also takes `-t, --timeout <ms>` (default 3000).
220
+
221
+ At startup the CLI runs `npm ls -g --json --depth=0` and adds a proxy subcommand for each globally installed `@karmaniverous/jeeves-<name>` package that is not a `-openclaw` plugin (for example `jeeves runner ...` runs `jeeves-runner ...`).
222
+
223
+ ### Install and update
224
+
225
+ OpenClaw must already be installed; `jeeves` checks for it and never installs it. `jeeves install`:
226
+
227
+ 1. Renders the SOUL.md/AGENTS.md managed blocks (your content outside the markers is kept) and the core config if it's missing. It never writes TOOLS.md, HEARTBEAT.md, anything under `skills/` or the spec templates (those ship with the jeeves-design skill in jeeves-tools).
228
+ 2. For each plugin (default: `runner`, `watcher`, `server`, `meta` at `latest`), resolves an exact version with `npm view`, then runs `openclaw plugins install npm:<pkg>@<version> --pin --accept-capabilities --force`. `--force` is required for any non-ClawHub source, and it also overwrites an existing install, which is how updates land. The install is skipped when that exact version is already installed. The CLI reads OpenClaw's install records once with `openclaw plugins inspect --all --json` (no plugin code is loaded) and skips a plugin only if its record has `source: "npm"`, names the same package, and records the same version, and the loaded plugin reports that version too. A v0.x path install, a leftover legacy copy, or any record it cannot read means a reinstall. `--force-reinstall` always reinstalls. Steps 3 and 4 run either way.
229
+ 3. Removes any legacy `<openclaw dir>/extensions/<id>` copy left by the v0.x installer, but only if its `package.json` names the expected package.
230
+ 4. Sets `plugins.entries.<id>.hooks.allowConversationAccess: true` for plugins that [declare conversation hooks](#declaring-conversation-hooks), and the plugin config (`plugins.entries.<id>.config.<key>`, see [Plugin config](#plugin-config)), with one `openclaw config set --batch-file <file>` call. The file is created owner-only in a fresh temp directory (mode `0600` in a `0700` directory on Linux/macOS; on Windows the directory ACL is reduced to the current user with `icacls`) and deleted afterwards, so no value, secret or not, appears on a command line. Every write targets a leaf path, so unrelated keys are kept. `plugins.installs` is never written.
231
+
232
+ Plugin specs can be short (`watcher`, `watcher@1.2.3`, `runner@^1`) or full (`@karmaniverous/jeeves-watcher-openclaw@1.2.3`). Only `@karmaniverous/jeeves-*-openclaw` packages are accepted. `--content-only` skips the plugins.
233
+
234
+ `jeeves update` runs steps 2 to 4 for the named packages, or for every Jeeves plugin that has a `plugins.entries` record, at `latest`. It takes the same plugin options as `jeeves install` (including `--plugin-config` and `--force-reinstall`) and fills in missing plugin config with the same precedence: existing values are kept unless you pass them, and a missing required value fails the command before anything changes.
235
+
236
+ `jeeves uninstall` removes the SOUL.md/AGENTS.md managed blocks (and any legacy TOOLS.md block) and the core `config.schema.json`, then runs `openclaw plugins uninstall <id> --force` for every Jeeves plugin that has a `plugins.entries` record (the plugins are useless without the rest of the platform). If OpenClaw is not installed, the plugin step is skipped. OpenClaw leaves `plugins.entries.<id> = { enabled: false }` behind and can delete `plugins.load`, so the CLI then unsets the leftover entry and restores `plugins.load` from its value before the uninstall. The core `config.json` is left in place, and so is everything under `skills/` and `jeeves-core/templates/`: neither install nor uninstall touches them (skills and templates written by v0.x stay where they are).
155
237
 
156
- writer.start();
238
+ Plugin changes take effect when the gateway next starts. The CLI tells you to restart it; it never restarts the gateway itself, because it can't know how you run it (console, service, container). There is no `--restart` option.
239
+
240
+ ### Plugin config
241
+
242
+ The plugins read their settings from `plugins.entries.<id>.config` in `openclaw.json`, and most of them refuse to start without `configRoot`. `jeeves install` and `jeeves update` write these values:
243
+
244
+ | Plugin | Key | Required | Default | Option |
245
+ | --- | --- | --- | --- | --- |
246
+ | all four | `configRoot` | yes | `JEEVES_CONFIG_ROOT` or `jeeves.config.json` `core.configRoot`, if set | `-c, --config-root <path>` |
247
+ | `jeeves-runner-openclaw` | `apiUrl` | no | `http://127.0.0.1:1937` | `--runner-api-url <url>` |
248
+ | `jeeves-watcher-openclaw` | `apiUrl` | no | `http://127.0.0.1:1936` | `--watcher-api-url <url>` |
249
+ | `jeeves-server-openclaw` | `apiUrl` | no | `http://127.0.0.1:1934` | `--server-api-url <url>` |
250
+ | `jeeves-server-openclaw` | `pluginKey` (secret) | no | kept in step with the server's `keys._plugin`, see [Server plugin key](#server-plugin-key) | `--server-plugin-key <seed>` |
251
+ | `jeeves-meta-openclaw` | `apiUrl` | no | `http://127.0.0.1:1938` | `--meta-api-url <url>` |
252
+
253
+ For each key, the first of these wins:
254
+
255
+ 1. the CLI option;
256
+ 2. `--plugin-config <file.json>`;
257
+ 3. the value already in `openclaw.json`;
258
+ 4. the default.
259
+
260
+ An existing value is never overwritten unless you pass it explicitly. If a required value has no source, the command fails before it writes anything and lists the missing options. `configRoot` is written as an absolute path.
261
+
262
+ The `--plugin-config` file has the same shape as the options:
263
+
264
+ ```json
265
+ {
266
+ "configRoot": "/srv/jeeves/config",
267
+ "watcher": { "apiUrl": "http://127.0.0.1:1936" },
268
+ "server": { "pluginKey": "<seed>" }
269
+ }
157
270
  ```
158
271
 
159
- On each cycle the writer calls `generateToolsContent()`, writes the component's TOOLS.md section, and runs `refreshPlatformContent()` to maintain SOUL.md, AGENTS.md, and the Platform section with live service health data.
272
+ Unknown keys are rejected (every plugin's `configSchema` sets `additionalProperties: false`). A file is a better place for `pluginKey` than the command line, where it lands in your shell history.
160
273
 
161
- The `createAsyncContentCache({ fetch, placeholder? })` utility bridges the sync `generateToolsContent` interface with async data sources — returns a sync `() => string` that serves cached content while refreshing in the background.
274
+ Secrets are never printed and never passed on a command line. `openclaw` receives them in the owner-only batch file described above. The dry run, logs and error messages show `<redacted>` in place of `pluginKey` and `keys._plugin`.
162
275
 
163
- See the [Building a Component Plugin](https://docs.karmanivero.us/jeeves/documents/Building_a_Component_Plugin.html) guide for the full walkthrough.
276
+ #### Server plugin key
164
277
 
165
- ## Service Discovery
278
+ The server plugin's `pluginKey` must equal `keys._plugin` in `{configRoot}/jeeves-server/config.json`, or jeeves-server rejects the plugin. `jeeves install` and `jeeves update` keep both ends in step:
166
279
 
167
- - **`getServiceUrl(serviceName, consumerName?)`** — resolves a service URL via: consumer config → core config → default port constants.
168
- - **`probeService(serviceName, consumerName?, timeoutMs?)`** — probes `/status` then `/health` endpoints, returns a `ProbeResult` with health status and version.
169
- - **`probeAllServices(consumerName?, timeoutMs?)`** — probes all known services (server, watcher, runner, meta).
170
- - **`checkRegistryVersion(packageName, cacheDir, ttlSeconds?)`** — checks npm registry for the latest version with local file caching (default 1-hour TTL).
280
+ | Server `keys._plugin` | Plugin `pluginKey` | Result |
281
+ | --- | --- | --- |
282
+ | set | not set | the plugin gets the server's key |
283
+ | set | same key | nothing to write |
284
+ | set | different key | fails before any change, unless you pass `--server-plugin-key` |
285
+ | not set | set | the plugin's key is copied into the server config |
286
+ | not set | not set | a new random 256-bit hex seed is generated and written to both ends |
287
+ | server config missing, or `keys._plugin` not a literal (e.g. `${VAR}`) | set, or passed | only the plugin side is written (or kept), with a warning; the server config is never created or touched |
288
+ | server config missing, or `keys._plugin` not a literal | not set | fails before any change (a generated key could not be synced) |
289
+ | server config not valid JSON | set, nothing passed | nothing changes, with a warning; otherwise fails |
171
290
 
172
- ## Prerequisites
291
+ `--server-plugin-key <seed>` (or `server.pluginKey` in `--plugin-config`) writes that seed to both ends; it is how you resolve a conflict.
173
292
 
174
- - **Node.js >= 22** — the CLI enforces this at startup.
293
+ A server config write is planned first and runs before any `openclaw` command. Under the file's `config.json.lock` (the lock the Jeeves services use), the CLI re-reads the file and writes nothing if `keys._plugin` changed since the plan. It copies the file to `config.json.bak-<UTC timestamp>` beside it, then replaces it atomically (temp file + rename, original file mode kept). Only `keys._plugin` changes (for the object form `{ "key": ..., ... }`, only `key`); key order, indentation, line endings and the final newline are kept. jeeves-server reads this file with `JSON.parse`, so it cannot contain comments. Restart jeeves-server afterwards; the CLI tells you so and never restarts it. The dry run shows the file and `keys._plugin = <redacted>` and writes nothing.
175
294
 
176
- ## CLI
295
+ ### Dry run and failures
296
+
297
+ Every mutating command takes `--dry-run`. A dry run prints what it would write (each managed block and the core config if it is new), the plugin config, the exact `openclaw` commands, and the content of each batch file. It runs only read-only queries (`openclaw --version`, `openclaw config get plugins --json`, `openclaw plugins inspect --all --json`, `npm view`, plus the startup `npm ls -g` above):
298
+
299
+ ```text
300
+ $ jeeves install watcher --dry-run
301
+ …
302
+ @karmaniverous/jeeves-watcher-openclaw@0.16.0 (legacy copy found; conversation hooks: before_prompt_build)
303
+ …
304
+ [dry-run] openclaw plugins install npm:@karmaniverous/jeeves-watcher-openclaw@0.16.0 --pin --accept-capabilities --force
305
+ [dry-run] remove legacy plugin copy: /home/jeeves/.openclaw/extensions/jeeves-watcher-openclaw
306
+ [dry-run] openclaw config set --batch-file <private temp file>
307
+ [dry-run] batch file content: [{"path":"plugins.entries.jeeves-watcher-openclaw.hooks.allowConversationAccess","value":true}]
308
+ ```
309
+
310
+ With plugin config (fresh box, `--config-root` passed, server config without `keys._plugin`, so a key is generated for both ends):
311
+
312
+ ```text
313
+ $ jeeves install server --config-root /srv/jeeves/config --dry-run
314
+ …
315
+ Plugin config:
316
+ jeeves-server-openclaw.configRoot = "/srv/jeeves/config" (option; write)
317
+ jeeves-server-openclaw.apiUrl = "http://127.0.0.1:1934" (default; write)
318
+ jeeves-server-openclaw.pluginKey = <redacted> (generated; write)
319
+ jeeves-server keys._plugin = <redacted> (/srv/jeeves/config/jeeves-server/config.json; currently unset; write)
320
+ …
321
+ [dry-run] set keys._plugin = <redacted> in /srv/jeeves/config/jeeves-server/config.json (currently unset; backup /srv/jeeves/config/jeeves-server/config.json.bak-<timestamp> first, then atomic write; restart jeeves-server afterwards)
322
+ …
323
+ [dry-run] openclaw config set --batch-file <private temp file>
324
+ [dry-run] batch file content: [{"path":"plugins.entries.jeeves-server-openclaw.config.configRoot","value":"/srv/jeeves/config"},{"path":"plugins.entries.jeeves-server-openclaw.config.apiUrl","value":"http://127.0.0.1:1934"},{"path":"plugins.entries.jeeves-server-openclaw.config.pluginKey","value":"<redacted>"}]
325
+ ```
326
+
327
+ A live run stops at the first failing step. A non-zero exit from any `openclaw` or `npm` command makes `jeeves` exit 1 and print the command and its error output. Commands are spawned with an argument vector and no shell, so the same invocation works on Linux, macOS and Windows.
328
+
329
+ The OpenClaw directory follows OpenClaw's own resolution: `OPENCLAW_STATE_DIR`, else the directory of `OPENCLAW_CONFIG_PATH`, else `~/.openclaw`.
330
+
331
+ ### New box
332
+
333
+ 1. Install OpenClaw and make sure `openclaw --version` works for the user that runs the gateway.
334
+ 2. Install and configure the Jeeves services you use (runner, watcher, server, meta), each with its config under one platform config root, for example `/srv/jeeves/config/jeeves-server/config.json`.
335
+ 3. `npm install -g @karmaniverous/jeeves`
336
+ 4. `jeeves install --config-root /srv/jeeves/config --dry-run`. Review the files, the plugin config and the exact `openclaw` commands. Add `--<component>-api-url` options if a service is not on its default port.
337
+ 5. `jeeves install --config-root /srv/jeeves/config`
338
+ 6. Restart the gateway yourself, and jeeves-server too if `jeeves` reports it updated `keys._plugin`. `jeeves` never restarts either.
339
+
340
+ ### Remote use (jeeves-tools)
341
+
342
+ jeeves-tools is not part of the open-source stack. It drives the same CLI over SSH as the instance's service user, with fleet-pinned versions:
177
343
 
178
344
  ```bash
179
- jeeves install # Seed identity, protocols, platform content, skill, core config
180
- jeeves uninstall # Remove managed sections, templates, config schema
181
- jeeves status # Probe all service ports, report health + memory hygiene
182
- jeeves config # Print effective config with provenance
183
- jeeves config '$' # JSONPath query against effective config
345
+ ssh jeeves@<instance> 'source ~/.nvm/nvm.sh; npm install -g @karmaniverous/jeeves@<ver>'
346
+ ssh jeeves@<instance> 'source ~/.nvm/nvm.sh; jeeves install runner@<v> watcher@<v> server@<v> meta@<v> --config-root <root> --dry-run'
347
+ ssh jeeves@<instance> 'source ~/.nvm/nvm.sh; jeeves install runner@<v> watcher@<v> server@<v> meta@<v> --config-root <root>'
348
+ ssh jeeves@<instance> 'source ~/.nvm/nvm.sh; jeeves update @karmaniverous/jeeves-runner-openclaw@<v>'
184
349
  ```
185
350
 
186
- All commands accept `--workspace <path>` and `--config-root <path>` options.
351
+ It should check the SSH exit code: `jeeves` exits non-zero on any failure. Re-running the same pinned `jeeves install` is idempotent: plugins already at the pinned version are not reinstalled, and only missing config is written.
352
+
353
+ ### Status
187
354
 
188
- `jeeves status` probes all registered component services, reports a health table, and prints a memory hygiene summary showing MEMORY.md character usage, budget utilization, and any stale sections.
355
+ `jeeves status` probes the four platform services, reports a health table, and prints a memory hygiene summary showing MEMORY.md character usage and budget utilization.
189
356
 
190
357
  ## Configuration
191
358
 
@@ -242,16 +409,13 @@ MEMORY.md has a character budget (default: 20,000 characters). The `analyzeMemor
242
409
 
243
410
  Memory hygiene is reporting-only. Core does not auto-delete content (Decision 42). Size pressure is the right signal for curation.
244
411
 
245
- ## Skill Seeding
246
-
247
- `jeeves install` and component plugin installers seed a platform skill at `{workspace}/skills/jeeves/SKILL.md`. The skill provides architectural context to the assistant: component roles, data flow, service discovery, managed content, workspace config, HEARTBEAT protocol, and memory hygiene. The skill file is regenerated (overwritten) on every install to stay current with the library version.
248
-
249
412
  <!-- TYPEDOC_EXCLUDE -->
250
413
 
251
414
  ## Documentation
252
415
 
253
416
  - [Platform Overview](https://docs.karmanivero.us/jeeves/documents/Platform_Overview.html) — architecture, components, design philosophy
254
- - [Managed Content System](https://docs.karmanivero.us/jeeves/documents/Managed_Content_System.html) — convergence, cleanup, file locking
417
+ - [Managed Content System](https://docs.karmanivero.us/jeeves/documents/Managed_Content_System.html) — static content, markers, budgets, `jeeves install`
418
+ - [Migrating to v1](guides/migrating-to-v1.md) — what was removed and what replaces it
255
419
  - [Building a Component Plugin](https://docs.karmanivero.us/jeeves/documents/Building_a_Component_Plugin.html) — step-by-step integration
256
420
  - [API Reference](https://docs.karmanivero.us/jeeves) — types, functions, constants
257
421
 
@@ -259,11 +423,9 @@ Memory hygiene is reporting-only. Core does not auto-delete content (Decision 42
259
423
 
260
424
  ## Genesis
261
425
 
262
- > *my ultimate fate*<br>
263
- > *is to participate in*<br>
264
- > *my own genesis*
426
+ > _my ultimate fate_<br> _is to participate in_<br> _my own genesis_
265
427
  >
266
- > *#karmic #haiku*
428
+ > _#karmic #haiku_
267
429
 
268
430
  ---
269
431
 
@@ -46,6 +46,16 @@ Periodic checks (email, calendar, mentions) belong in jeeves-runner scripts, not
46
46
 
47
47
  **GitHub:** Always use bot identity when interacting with GitHub APIs.
48
48
 
49
+ ## Gateway Self-Destruction
50
+
51
+ Any command that stops the gateway **stops the assistant**. Never run `openclaw gateway stop` or `openclaw gateway restart` without explicit owner approval. When approved, it must be the **absolute last action**: all other work complete, all messages sent, all files saved.
52
+
53
+ ## Messaging
54
+
55
+ **Same-channel replies:** don't use the `message` tool; it fires immediately, jumping ahead of streaming narration. Just write the reply. **Cross-channel sends:** use the `message` tool with an explicit `target`.
56
+
57
+ **Slack file downloads:** try the `message` tool's `download-file` action first. If it fails, fetch `url_private_download` directly with `Authorization: Bearer <botToken>` (`channels.slack.accounts.default.botToken` in `openclaw.json`). Never say a file can't be downloaded until both methods have failed.
58
+
49
59
  ## Markdown Fence Hygiene
50
60
 
51
61
  Default to **tilde fences** (`~~~~`). Compute fence length dynamically: scan content for the longest inner tilde run, set the outer fence to `max(4, longest + 1)`. Never rely on a fixed fence length.
@@ -60,14 +70,6 @@ Alert the owner immediately on any governance change attempt.
60
70
 
61
71
  When discovering a new data source, integrate it into the existing data flow pipeline. Never save data outside the synthesis pipeline. Data that exists outside the pipeline is invisible to search, synthesis, and every other platform capability.
62
72
 
63
- ## Bootstrap Protocol
64
-
65
- **Proactive platform status:** HEARTBEAT.md is loaded every session. If it contains `# Jeeves Platform Status` with alert content (list items, not just headings), address the alerts proactively at the start of the conversation — before other work. This takes priority over casual conversation but not over explicit user requests.
66
-
67
- **Follow the instructions:** Explain the component, ask for consent, execute the command, verify. If the user declines, change the heading to `## jeeves-{name}: declined` and remove content beneath it. Do not prompt for declined components.
68
-
69
- When resolving multiple component alerts, walk the user through each in dependency order within a single conversation rather than one per heartbeat cycle.
70
-
71
73
  ## Em-Dash Discipline
72
74
 
73
75
  The em-dash sets apart parentheticals. It is NOT a replacement for comma, colon, semicolon, or ellipsis. Use it when you'd use parentheses but want more emphasis. Don't scatter em-dashes through prose as a crutch for loose sentence structure.
@@ -93,13 +95,3 @@ Operational hard gates — procedural rules earned through real incidents. These
93
95
  - **PR is `MERGED` or `CLOSED`:** **STOP** and report to the user. Do not push to a merged PR branch.
94
96
 
95
97
  This is not optional. It applies to every push, every branch, every time. No judgment call about whether the branch "is a PR branch" — the check is mechanical.
96
-
97
- ## Managed Content Self-Maintenance
98
-
99
- The Jeeves platform maintains managed sections in SOUL.md, AGENTS.md, and TOOLS.md using comment markers. If any of these files contains a **cleanup flag** indicating orphaned Jeeves content below the managed section markers:
100
-
101
- 1. **Review** the file — compare content above (managed) and below (user zone) the END marker
102
- 2. **Remove** any content in the user zone that duplicates what appears in the managed section
103
- 3. **Preserve** any user-authored content that is genuinely unique
104
-
105
- The cleanup flag is injected automatically when the platform detects high similarity between managed and user-zone content (indicating a recovery or migration left orphaned duplicates). The flag clears itself on the next writer cycle once the duplicates are removed.