clawvault 3.0.0 → 3.1.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.
Files changed (60) hide show
  1. package/README.md +156 -105
  2. package/bin/clawvault.js +0 -2
  3. package/bin/register-core-commands.js +20 -2
  4. package/dist/{chunk-3D6BCTP6.js → chunk-33UGEQRT.js} +70 -145
  5. package/dist/{chunk-ZVVFWOLW.js → chunk-3WRJEKN4.js} +1 -1
  6. package/dist/{chunk-DEFFDRVP.js → chunk-3ZIH425O.js} +3 -70
  7. package/dist/{chunk-K234IDRJ.js → chunk-D2H45LON.js} +1 -0
  8. package/dist/{chunk-YKTA5JOJ.js → chunk-H62BP7RI.js} +3 -3
  9. package/dist/{chunk-WGRQ6HDV.js → chunk-LI4O6NVK.js} +1 -1
  10. package/dist/{chunk-7R7O6STJ.js → chunk-OCGVIN3L.js} +1 -1
  11. package/dist/{chunk-GAJV4IGR.js → chunk-YCUNCH2I.js} +3 -7
  12. package/dist/cli/index.cjs +10 -1459
  13. package/dist/cli/index.js +5 -8
  14. package/dist/commands/compat.cjs +70 -145
  15. package/dist/commands/compat.js +1 -1
  16. package/dist/commands/context.cjs +1 -0
  17. package/dist/commands/context.js +3 -3
  18. package/dist/commands/doctor.cjs +68 -144
  19. package/dist/commands/doctor.js +4 -4
  20. package/dist/commands/embed.js +2 -2
  21. package/dist/commands/setup.cjs +2 -69
  22. package/dist/commands/setup.d.cts +0 -1
  23. package/dist/commands/setup.d.ts +0 -1
  24. package/dist/commands/setup.js +2 -2
  25. package/dist/commands/sleep.cjs +1 -0
  26. package/dist/commands/sleep.js +2 -2
  27. package/dist/commands/status.cjs +1 -0
  28. package/dist/commands/status.js +2 -2
  29. package/dist/commands/wake.cjs +1 -0
  30. package/dist/commands/wake.js +2 -2
  31. package/dist/index.cjs +447 -2600
  32. package/dist/index.d.cts +0 -4
  33. package/dist/index.d.ts +0 -4
  34. package/dist/index.js +8 -69
  35. package/dist/plugin/index.cjs +3 -3
  36. package/dist/plugin/index.js +10 -10
  37. package/package.json +11 -17
  38. package/bin/register-tailscale-commands.js +0 -106
  39. package/dist/chunk-IVRIKYFE.js +0 -520
  40. package/dist/chunk-THRJVD4L.js +0 -373
  41. package/dist/chunk-TIGW564L.js +0 -628
  42. package/dist/commands/tailscale.cjs +0 -1532
  43. package/dist/commands/tailscale.d.cts +0 -52
  44. package/dist/commands/tailscale.d.ts +0 -52
  45. package/dist/commands/tailscale.js +0 -26
  46. package/dist/lib/canvas-layout.cjs +0 -136
  47. package/dist/lib/canvas-layout.d.cts +0 -31
  48. package/dist/lib/canvas-layout.d.ts +0 -31
  49. package/dist/lib/canvas-layout.js +0 -92
  50. package/dist/lib/tailscale.cjs +0 -1183
  51. package/dist/lib/tailscale.d.cts +0 -225
  52. package/dist/lib/tailscale.d.ts +0 -225
  53. package/dist/lib/tailscale.js +0 -50
  54. package/dist/lib/webdav.cjs +0 -568
  55. package/dist/lib/webdav.d.cts +0 -109
  56. package/dist/lib/webdav.d.ts +0 -109
  57. package/dist/lib/webdav.js +0 -35
  58. package/hooks/clawvault/HOOK.md +0 -83
  59. package/hooks/clawvault/handler.js +0 -879
  60. package/hooks/clawvault/handler.test.js +0 -354
package/README.md CHANGED
@@ -1,158 +1,209 @@
1
- # ClawVault 🐘
1
+ # ClawVault
2
2
 
3
- Structured memory system for AI agents and operators: typed markdown memory, graph-aware context, task/project primitives, Obsidian views, and OpenClaw hook integration.
3
+ Structured memory for AI agents. Typed markdown primitives that compound over time.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/clawvault)](https://www.npmjs.com/package/clawvault)
6
6
 
7
- > Local-first. Markdown-first. Built to survive long-running autonomous work.
7
+ Every memory is a markdown file with YAML frontmatter — a task, a decision, a person, a lesson — each following a schema defined in `templates/`. The agent reads and writes these files. The human browses them in Obsidian. No database. No vendor lock-in. Just files.
8
8
 
9
9
  ## Requirements
10
10
 
11
11
  - Node.js 18+
12
- - `qmd` installed and available on `PATH`
13
-
14
- ClawVault currently relies on `qmd` for core vault/query flows. Install it before first use.
12
+ - [`qmd`](https://github.com/qmd-project/qmd) installed and on `PATH` (hybrid BM25 + vector search)
15
13
 
16
14
  ## Install
17
15
 
16
+ ### As an OpenClaw Plugin (recommended)
17
+
18
18
  ```bash
19
- npm install -g clawvault
19
+ openclaw plugins install clawvault
20
20
  ```
21
21
 
22
- ## 5-Minute Setup
22
+ This installs ClawVault as a memory plugin. It replaces OpenClaw's built-in memory with:
23
23
 
24
- ```bash
25
- # 1) Create or initialize a vault
26
- clawvault init ~/memory --name my-brain
24
+ - **Auto-recall** — injects relevant memories before each agent turn
25
+ - **Auto-capture** observes conversations and stores durable knowledge automatically
26
+ - **Session recap** on wake, provides context from active tasks, recent decisions, and preferences
27
+ - **4 tools** — `memory_search`, `memory_store`, `memory_get`, `memory_forget`
27
28
 
28
- # 2) Optional vault bootstrap for Obsidian
29
- clawvault setup --theme neural --canvas
29
+ After install, configure the vault path:
30
30
 
31
- # 3) Verify OpenClaw compatibility in this environment
32
- clawvault compat
31
+ ```bash
32
+ openclaw config set plugins.clawvault.config.vaultPath ~/my-vault
33
33
  ```
34
34
 
35
- ## OpenClaw Setup (Canonical)
36
-
37
- If you want hook-based lifecycle integration, use this sequence:
35
+ ### As a Standalone CLI
38
36
 
39
37
  ```bash
40
- # Install CLI
41
38
  npm install -g clawvault
39
+ ```
42
40
 
43
- # Install and enable hook pack
44
- openclaw hooks install clawvault
45
- openclaw hooks enable clawvault
41
+ ## Quick Start
46
42
 
47
- # Verify
48
- openclaw hooks list --verbose
49
- openclaw hooks info clawvault
50
- openclaw hooks check
51
- clawvault compat
43
+ ```bash
44
+ # Initialize a new vault
45
+ clawvault init ~/my-vault --name my-brain
46
+
47
+ # Set up Obsidian Bases views (tasks, projects, backlog)
48
+ clawvault setup
49
+
50
+ # Check vault health
51
+ clawvault doctor
52
+
53
+ # Search your vault
54
+ clawvault search "deployment decision"
52
55
  ```
53
56
 
54
- Important:
57
+ ## How It Works
58
+
59
+ ### Typed Primitives
60
+
61
+ Every piece of memory has a type defined by a template:
62
+
63
+ ```yaml
64
+ ---
65
+ primitive: task
66
+ fields:
67
+ status:
68
+ type: string
69
+ required: true
70
+ default: open
71
+ enum: [open, in-progress, blocked, done]
72
+ priority:
73
+ type: string
74
+ enum: [critical, high, medium, low]
75
+ owner:
76
+ type: string
77
+ due:
78
+ type: date
79
+ ---
80
+ ```
55
81
 
56
- - `clawhub install clawvault` installs skill guidance, but does not replace hook-pack installation.
57
- - After enabling hooks, restart the OpenClaw gateway process so hook registration reloads.
82
+ Default templates: `task`, `decision`, `lesson`, `person`, `project`, `checkpoint`, `handoff`, `daily`, `trigger`, `run`, `party`, `workspace`.
58
83
 
59
- ## Minimal AGENTS.md Additions
84
+ ### Malleable Schemas
60
85
 
61
- Append these to your existing memory workflow. Do not replace your full prompt setup:
86
+ Don't like the defaults? Drop your own template in your vault's `templates/` directory. Add fields, remove fields, create entirely new types. The plugin reads YOUR schemas, not ours.
62
87
 
63
- ```markdown
64
- ## ClawVault
65
- - Run `clawvault wake` at session start.
66
- - Run `clawvault checkpoint` during heavy work.
67
- - Run `clawvault sleep "summary" --next "next steps"` before ending.
68
- - Use `clawvault context "<task>"` or `clawvault inject "<message>"` before complex decisions.
69
- ```
88
+ ### Hybrid Search
70
89
 
71
- ## Real CLI Surface (Current)
90
+ ClawVault uses `qmd` for search — BM25 keyword matching combined with vector similarity and reranking. Entirely local. No API keys needed.
72
91
 
73
- Core:
92
+ ### Obsidian Integration
74
93
 
75
- - `init`, `setup`, `store`, `capture`
76
- - `remember`, `list`, `get`, `stats`, `reindex`, `sync`
94
+ Your vault IS an Obsidian vault. Tasks become Kanban boards. Decisions are searchable. Wiki-links build a knowledge graph. Five generated Bases views out of the box:
77
95
 
78
- Context + memory:
96
+ - All tasks
97
+ - Blocked items
98
+ - By project
99
+ - By owner
100
+ - Backlog
79
101
 
80
- - `search`, `vsearch`, `context`, `inject`
81
- - `observe`, `reflect`, `session-recap`
82
- - `graph`, `entities`, `link`, `embed`
102
+ ## CLI Commands
83
103
 
84
- Resilience:
104
+ ### Core
85
105
 
86
- - `wake`, `sleep`, `handoff`, `recap`
87
- - `checkpoint`, `recover`, `status`, `clean-exit`, `repair-session`
88
- - `compat`, `doctor`
106
+ | Command | Description |
107
+ |---------|-------------|
108
+ | `init [path]` | Initialize a new vault |
109
+ | `setup` | Auto-discover and configure a vault, create Obsidian views |
110
+ | `store` | Store a new typed memory document |
111
+ | `capture <note>` | Quick-capture a note to inbox |
112
+ | `doctor` | Diagnose vault health |
89
113
 
90
- Execution primitives:
114
+ ### Search & Context
91
115
 
92
- - `task ...`, `backlog ...`, `blocked`, `project ...`, `kanban ...`
93
- - `canvas` (generates default `dashboard.canvas`)
116
+ | Command | Description |
117
+ |---------|-------------|
118
+ | `search <query>` | BM25 keyword search via qmd |
119
+ | `vsearch <query>` | Semantic vector search via qmd |
120
+ | `context <task>` | Generate task-relevant context |
121
+ | `inject <message>` | Inject relevant rules and decisions |
94
122
 
95
- Networking:
123
+ ### Session Lifecycle
96
124
 
97
- - `tailscale-status`, `tailscale-sync`, `tailscale-serve`, `tailscale-discover`
125
+ | Command | Description |
126
+ |---------|-------------|
127
+ | `wake` | Start a session (recover + recap) |
128
+ | `sleep <summary>` | End a session with a handoff |
129
+ | `checkpoint` | Save state for context-death resilience |
130
+ | `recover` | Check for and recover from context death |
98
131
 
99
- ## Quick Usage
132
+ ### Observation Pipeline
100
133
 
101
- ```bash
102
- # Store and retrieve memory
103
- clawvault remember decision "Use PostgreSQL" --content "Chosen for JSONB and reliability"
104
- clawvault search "postgresql"
105
- clawvault vsearch "what did we decide about storage"
106
-
107
- # Session lifecycle
108
- clawvault wake
109
- clawvault checkpoint --working-on "auth rollout" --focus "token refresh edge cases"
110
- clawvault sleep "finished auth rollout plan" --next "implement migration"
111
-
112
- # Work management
113
- clawvault task add "Ship v2 onboarding" --owner agent --project core --priority high
114
- clawvault blocked
115
- clawvault project list --status active
116
- clawvault kanban sync
117
-
118
- # Obsidian projection
119
- clawvault canvas
120
- ```
134
+ | Command | Description |
135
+ |---------|-------------|
136
+ | `observe` | Process sessions into observational memory |
137
+ | `reflect` | Promote observations to weekly reflections |
138
+ | `reweave` | Backward consolidation mark superseded observations |
121
139
 
122
- ## Obsidian Integration
140
+ ### Tasks & Projects
123
141
 
124
- - Setup can generate:
125
- - graph theme/snippet config (`--theme neural|minimal|none`)
126
- - Bases views (`all-tasks.base`, `blocked.base`, `by-project.base`, `by-owner.base`, `backlog.base`)
127
- - default canvas (`dashboard.canvas`) via `--canvas` or `clawvault canvas`
128
- - Kanban round-trip:
129
- - export: `clawvault kanban sync`
130
- - import lane changes back to task metadata: `clawvault kanban import`
142
+ | Command | Description |
143
+ |---------|-------------|
144
+ | `task` | Task management (create, list, update, transition) |
145
+ | `project` | Project management |
146
+ | `kanban` | Kanban board view |
147
+ | `status` | Vault health and statistics |
131
148
 
132
- ## Tailscale + WebDAV
149
+ ### Utilities
133
150
 
134
- ClawVault can serve vault content for sync over Tailscale and exposes WebDAV under `/webdav` for mobile-oriented workflows.
151
+ | Command | Description |
152
+ |---------|-------------|
153
+ | `template` | Manage document templates |
154
+ | `graph` | Show typed memory graph summary |
155
+ | `entities` | List all linkable entities |
156
+ | `link [file]` | Auto-link entity mentions |
157
+ | `compat` | Check OpenClaw compatibility |
158
+ | `embed` | Run qmd embedding for pending documents |
135
159
 
136
- ```bash
137
- clawvault tailscale-status
138
- clawvault tailscale-serve --vault ~/memory
139
- clawvault tailscale-discover
160
+ ## Architecture
161
+
162
+ ```
163
+ HUMAN (Obsidian)
164
+ Browse, edit, approve
165
+
166
+
167
+ ┌─── VAULT (markdown) ───┐
168
+ │ Typed primitives │
169
+ │ Knowledge graph │
170
+ │ Template schemas │
171
+ └───┬──────────────┬──────┘
172
+ │ │
173
+ AGENT (Plugin) CLI (Developer)
174
+ Auto-capture Direct CRUD
175
+ Auto-recall Search, graph
176
+ Session recap Tasks, projects
140
177
  ```
141
178
 
142
- ## Troubleshooting
143
-
144
- - Hook not found after enable:
145
- - run `openclaw hooks install clawvault` first
146
- - then `openclaw hooks enable clawvault`
147
- - restart gateway
148
- - verify with `openclaw hooks list --verbose`
149
- - `qmd` errors:
150
- - ensure `qmd --version` works from same shell
151
- - rerun `clawvault setup` after qmd install
152
- - OpenClaw integration drift:
153
- - run `clawvault compat`
154
- - Session transcript corruption:
155
- - run `clawvault repair-session --dry-run` then `clawvault repair-session`
179
+ ## OpenClaw Plugin Details
180
+
181
+ The plugin hooks into the OpenClaw lifecycle:
182
+
183
+ - **`before_agent_start`** auto-recall: searches vault for context relevant to the current conversation and injects it
184
+ - **`message_received`** — auto-capture: observes incoming messages for durable information worth storing
185
+ - **`agent_end`** captures any final observations from the agent's response
186
+ - **`before_compaction`** — preserves important context before conversation compaction
187
+
188
+ Configuration in `openclaw.plugin.json`:
189
+
190
+ | Option | Default | Description |
191
+ |--------|---------|-------------|
192
+ | `vaultPath` | | Path to vault directory |
193
+ | `collection` | `clawvault` | qmd search collection name |
194
+ | `autoRecall` | `true` | Inject memories before each turn |
195
+ | `autoCapture` | `true` | Auto-store from conversations |
196
+ | `recallLimit` | `5` | Max memories per recall |
197
+
198
+ ## What Compounds
199
+
200
+ - **Decisions** accumulate into institutional knowledge
201
+ - **Lessons** prevent repeated mistakes
202
+ - **Tasks** with transition ledgers track how work happened
203
+ - **Projects** group related work across hundreds of sessions
204
+ - **Wiki-links** build a knowledge graph that grows richer over time
205
+
206
+ The agent that runs for a year generates compounding value. Every lesson stored makes the next task cheaper.
156
207
 
157
208
  ## License
158
209
 
package/bin/clawvault.js CHANGED
@@ -23,7 +23,6 @@ import { registerProjectCommands } from './register-project-commands.js';
23
23
 
24
24
  import { registerTaskCommands } from './register-task-commands.js';
25
25
 
26
- import { registerTailscaleCommands } from './register-tailscale-commands.js';
27
26
  import {
28
27
  getVault,
29
28
  resolveVaultPath,
@@ -104,7 +103,6 @@ registerProjectCommands(program, {
104
103
  resolveVaultPath
105
104
  });
106
105
 
107
- registerTailscaleCommands(program, { chalk });
108
106
  registerConfigCommands(program, { chalk, resolveVaultPath });
109
107
  registerRouteCommands(program, { chalk, resolveVaultPath });
110
108
 
@@ -129,7 +129,6 @@ export function registerCoreCommands(
129
129
  .option('--bases', 'Generate Obsidian Bases views for task management')
130
130
  .option('--no-bases', 'Skip Bases file generation')
131
131
  .option('--theme <style>', 'Graph color theme (neural, minimal, none) (default: neural)', 'neural')
132
- .option('--from <path>', 'Import from existing agent memory directory (MEMORY.md, memory/*.md, etc)')
133
132
  .option('--force', 'Overwrite existing configuration files')
134
133
  .option('-v, --vault <path>', 'Vault path')
135
134
  .action(async (options) => {
@@ -139,7 +138,6 @@ export function registerCoreCommands(
139
138
  graphColors: options.graphColors,
140
139
  bases: options.bases,
141
140
  theme: options.theme,
142
- from: options.from,
143
141
  force: options.force,
144
142
  vault: options.vault
145
143
  });
@@ -221,4 +219,24 @@ export function registerCoreCommands(
221
219
  process.exit(1);
222
220
  }
223
221
  });
222
+ // === PLUGIN-PATH ===
223
+ program
224
+ .command('plugin-path')
225
+ .description('Print the OpenClaw plugin path for configuration')
226
+ .action(() => {
227
+ const pluginDir = path.dirname(path.dirname(new URL(import.meta.url).pathname));
228
+ const pluginPath = path.join(pluginDir, 'dist', 'plugin', 'index.js');
229
+ if (fs.existsSync(pluginPath)) {
230
+ console.log(pluginPath);
231
+ } else {
232
+ // Dev mode — use source
233
+ const srcPath = path.join(pluginDir, 'src', 'plugin', 'index.ts');
234
+ if (fs.existsSync(srcPath)) {
235
+ console.log(srcPath);
236
+ } else {
237
+ console.error('Plugin not found. Run: npm run build');
238
+ process.exit(1);
239
+ }
240
+ }
241
+ });
224
242
  }
@@ -4,8 +4,6 @@ import * as path from "path";
4
4
  import matter from "gray-matter";
5
5
  import { spawnSync } from "child_process";
6
6
  import { fileURLToPath } from "url";
7
- var REQUIRED_HOOK_EVENTS = ["gateway:startup", "command:new", "session:start"];
8
- var REQUIRED_HOOK_BIN = "clawvault";
9
7
  function readOptionalFile(filePath) {
10
8
  try {
11
9
  if (!fs.existsSync(filePath)) return null;
@@ -24,19 +22,6 @@ function findPackageRoot() {
24
22
  }
25
23
  return path.dirname(fileURLToPath(import.meta.url));
26
24
  }
27
- function resolveOpenClawHooksDir() {
28
- const candidates = [
29
- path.join(process.env.HOME || "", ".openclaw", "hooks", "clawvault"),
30
- path.join(process.env.OPENCLAW_HOME || "", "hooks", "clawvault"),
31
- path.join(process.env.OPENCLAW_STATE_DIR || "", "hooks", "clawvault")
32
- ].filter((p) => p && !p.startsWith(path.sep + "hooks"));
33
- for (const candidate of candidates) {
34
- if (fs.existsSync(candidate)) {
35
- return candidate;
36
- }
37
- }
38
- return null;
39
- }
40
25
  function resolveProjectFile(relativePath, baseDir) {
41
26
  if (baseDir) {
42
27
  return path.resolve(baseDir, relativePath);
@@ -45,20 +30,6 @@ function resolveProjectFile(relativePath, baseDir) {
45
30
  if (fs.existsSync(fromCwd)) {
46
31
  return fromCwd;
47
32
  }
48
- if (relativePath.startsWith("hooks/clawvault/")) {
49
- const hooksDir = resolveOpenClawHooksDir();
50
- if (hooksDir) {
51
- const hookRelative = relativePath.replace("hooks/clawvault/", "");
52
- const fromHooks = path.resolve(hooksDir, hookRelative);
53
- if (fs.existsSync(fromHooks)) {
54
- return fromHooks;
55
- }
56
- const fromNestedHooks = path.resolve(hooksDir, "hooks", "clawvault", hookRelative);
57
- if (fs.existsSync(fromNestedHooks)) {
58
- return fromNestedHooks;
59
- }
60
- }
61
- }
62
33
  return path.resolve(findPackageRoot(), relativePath);
63
34
  }
64
35
  function checkOpenClawCli() {
@@ -68,7 +39,7 @@ function checkOpenClawCli() {
68
39
  label: "openclaw CLI available",
69
40
  status: "warn",
70
41
  detail: "openclaw binary not found",
71
- hint: "Install OpenClaw CLI to enable hook runtime validation."
42
+ hint: "Install OpenClaw CLI to enable plugin runtime validation."
72
43
  };
73
44
  }
74
45
  if (typeof result.status === "number" && result.status !== 0) {
@@ -89,152 +60,106 @@ function checkOpenClawCli() {
89
60
  }
90
61
  return { label: "openclaw CLI available", status: "ok" };
91
62
  }
92
- function checkPackageHookRegistration(options) {
93
- let packageRaw = readOptionalFile(resolveProjectFile("package.json", options.baseDir));
94
- if (packageRaw && !options.baseDir) {
95
- try {
96
- const parsed = JSON.parse(packageRaw);
97
- if (!parsed.openclaw?.hooks) {
98
- const fallbackPath = path.resolve(findPackageRoot(), "package.json");
99
- const fallbackRaw = readOptionalFile(fallbackPath);
100
- if (fallbackRaw) packageRaw = fallbackRaw;
101
- }
102
- } catch {
103
- }
104
- }
105
- if (!packageRaw) {
63
+ function checkPluginManifest(options) {
64
+ const manifestRaw = readOptionalFile(
65
+ resolveProjectFile("openclaw.plugin.json", options.baseDir)
66
+ );
67
+ if (!manifestRaw) {
106
68
  return {
107
- label: "package hook registration",
69
+ label: "plugin manifest",
108
70
  status: "error",
109
- detail: "package.json not found"
71
+ detail: "openclaw.plugin.json not found",
72
+ hint: "Create openclaw.plugin.json with id, kind, and configSchema fields."
110
73
  };
111
74
  }
112
75
  try {
113
- const parsed = JSON.parse(packageRaw);
114
- const registeredHooks = parsed.openclaw?.hooks ?? [];
115
- if (registeredHooks.includes("./hooks/clawvault")) {
76
+ const manifest = JSON.parse(manifestRaw);
77
+ const issues = [];
78
+ if (!manifest.id) issues.push("missing id");
79
+ if (!manifest.kind) issues.push("missing kind");
80
+ if (!manifest.configSchema) issues.push("missing configSchema");
81
+ if (issues.length > 0) {
116
82
  return {
117
- label: "package hook registration",
118
- status: "ok",
119
- detail: "./hooks/clawvault"
83
+ label: "plugin manifest",
84
+ status: "error",
85
+ detail: issues.join(", ")
120
86
  };
121
87
  }
122
88
  return {
123
- label: "package hook registration",
124
- status: "error",
125
- detail: "Missing ./hooks/clawvault in package openclaw.hooks"
89
+ label: "plugin manifest",
90
+ status: "ok",
91
+ detail: `id=${manifest.id} kind=${manifest.kind}`
126
92
  };
127
93
  } catch (err) {
128
94
  return {
129
- label: "package hook registration",
95
+ label: "plugin manifest",
130
96
  status: "error",
131
- detail: err?.message || "Unable to parse package.json"
97
+ detail: err?.message || "Unable to parse openclaw.plugin.json"
132
98
  };
133
99
  }
134
100
  }
135
- function checkHookManifest(options) {
136
- const hookRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/HOOK.md", options.baseDir));
137
- if (!hookRaw) {
101
+ function checkPluginExtensions(options) {
102
+ let packageRaw = readOptionalFile(
103
+ resolveProjectFile("package.json", options.baseDir)
104
+ );
105
+ if (packageRaw && !options.baseDir) {
106
+ try {
107
+ const parsed = JSON.parse(packageRaw);
108
+ if (!parsed.openclaw?.extensions) {
109
+ const fallbackPath = path.resolve(findPackageRoot(), "package.json");
110
+ const fallbackRaw = readOptionalFile(fallbackPath);
111
+ if (fallbackRaw) packageRaw = fallbackRaw;
112
+ }
113
+ } catch {
114
+ }
115
+ }
116
+ if (!packageRaw) {
138
117
  return {
139
- label: "hook manifest",
118
+ label: "plugin extensions registration",
140
119
  status: "error",
141
- detail: "HOOK.md not found"
120
+ detail: "package.json not found"
142
121
  };
143
122
  }
144
123
  try {
145
- const parsed = matter(hookRaw);
146
- const openclaw = parsed.data?.metadata?.openclaw;
147
- const events = Array.isArray(openclaw?.events) ? openclaw?.events ?? [] : [];
148
- const missingEvents = REQUIRED_HOOK_EVENTS.filter((event) => !events.includes(event));
149
- if (missingEvents.length === 0) {
124
+ const parsed = JSON.parse(packageRaw);
125
+ const extensions = parsed.openclaw?.extensions ?? [];
126
+ if (extensions.length === 0) {
150
127
  return {
151
- label: "hook manifest events",
152
- status: "ok",
153
- detail: events.join(", ")
128
+ label: "plugin extensions registration",
129
+ status: "error",
130
+ detail: "Missing openclaw.extensions in package.json",
131
+ hint: 'Add openclaw.extensions: ["./dist/plugin/index.js"] to package.json.'
154
132
  };
155
133
  }
156
- return {
157
- label: "hook manifest events",
158
- status: "error",
159
- detail: `Missing events: ${missingEvents.join(", ")}`
160
- };
161
- } catch (err) {
162
- return {
163
- label: "hook manifest events",
164
- status: "error",
165
- detail: err?.message || "Unable to parse HOOK.md frontmatter"
166
- };
167
- }
168
- }
169
- function checkHookManifestRequirements(options) {
170
- const hookRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/HOOK.md", options.baseDir));
171
- if (!hookRaw) {
172
- return {
173
- label: "hook manifest requirements",
174
- status: "error",
175
- detail: "HOOK.md not found"
176
- };
177
- }
178
- try {
179
- const parsed = matter(hookRaw);
180
- const requiresBins = parsed.data?.metadata?.openclaw?.requires?.bins;
181
- const bins = Array.isArray(requiresBins) ? requiresBins : [];
182
- if (bins.includes(REQUIRED_HOOK_BIN)) {
134
+ const baseDir = options.baseDir || findPackageRoot();
135
+ const missing = extensions.filter(
136
+ (ext) => !fs.existsSync(path.resolve(baseDir, ext))
137
+ );
138
+ if (missing.length > 0) {
183
139
  return {
184
- label: "hook manifest requirements",
185
- status: "ok",
186
- detail: `bins: ${bins.join(", ")}`
140
+ label: "plugin extensions registration",
141
+ status: "error",
142
+ detail: `Entry file(s) not found: ${missing.join(", ")}`,
143
+ hint: "Run npm run build to generate dist files."
187
144
  };
188
145
  }
189
146
  return {
190
- label: "hook manifest requirements",
191
- status: "warn",
192
- detail: `Missing required hook bin "${REQUIRED_HOOK_BIN}"`,
193
- hint: 'Add metadata.openclaw.requires.bins: ["clawvault"] to hooks/clawvault/HOOK.md.'
147
+ label: "plugin extensions registration",
148
+ status: "ok",
149
+ detail: extensions.join(", ")
194
150
  };
195
151
  } catch (err) {
196
152
  return {
197
- label: "hook manifest requirements",
198
- status: "error",
199
- detail: err?.message || "Unable to parse HOOK.md frontmatter"
200
- };
201
- }
202
- }
203
- function checkHookHandlerSafety(options) {
204
- const handlerRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/handler.js", options.baseDir));
205
- if (!handlerRaw) {
206
- return {
207
- label: "hook handler script",
153
+ label: "plugin extensions registration",
208
154
  status: "error",
209
- detail: "handler.js not found"
210
- };
211
- }
212
- const usesExecFileSync = handlerRaw.includes("execFileSync");
213
- const usesExecSync = /\bexecSync\b/.test(handlerRaw);
214
- const enablesShell = /\bshell\s*:\s*true\b/.test(handlerRaw);
215
- const delegatesAutoProfile = /['"]--profile['"]\s*,\s*['"]auto['"]/.test(handlerRaw);
216
- const violations = [];
217
- if (!usesExecFileSync || usesExecSync) {
218
- violations.push("execFileSync-only execution path");
219
- }
220
- if (enablesShell) {
221
- violations.push("shell:false execution option");
222
- }
223
- if (!delegatesAutoProfile) {
224
- violations.push("shared context profile delegation (--profile auto)");
225
- }
226
- if (violations.length > 0) {
227
- return {
228
- label: "hook handler safety",
229
- status: "warn",
230
- detail: `Missing conventions: ${violations.join(", ")}`,
231
- hint: "Use execFileSync (no shell), avoid execSync, and delegate profile inference via --profile auto."
155
+ detail: err?.message || "Unable to parse package.json"
232
156
  };
233
157
  }
234
- return { label: "hook handler safety", status: "ok" };
235
158
  }
236
159
  function checkSkillMetadata(options) {
237
- const skillRaw = readOptionalFile(resolveProjectFile("SKILL.md", options.baseDir));
160
+ const skillRaw = readOptionalFile(
161
+ resolveProjectFile("SKILL.md", options.baseDir)
162
+ );
238
163
  if (!skillRaw) {
239
164
  return {
240
165
  label: "skill metadata",
@@ -273,10 +198,8 @@ function checkSkillMetadata(options) {
273
198
  function checkOpenClawCompatibility(options = {}) {
274
199
  const checks = [
275
200
  checkOpenClawCli(),
276
- checkPackageHookRegistration(options),
277
- checkHookManifest(options),
278
- checkHookManifestRequirements(options),
279
- checkHookHandlerSafety(options),
201
+ checkPluginManifest(options),
202
+ checkPluginExtensions(options),
280
203
  checkSkillMetadata(options)
281
204
  ];
282
205
  const warnings = checks.filter((check) => check.status === "warn").length;
@@ -296,7 +219,9 @@ function formatCompatibilityReport(report) {
296
219
  lines.push("");
297
220
  for (const check of report.checks) {
298
221
  const prefix = check.status === "ok" ? "\u2713" : check.status === "warn" ? "\u26A0" : "\u2717";
299
- lines.push(`${prefix} ${check.label}${check.detail ? ` \u2014 ${check.detail}` : ""}`);
222
+ lines.push(
223
+ `${prefix} ${check.label}${check.detail ? ` \u2014 ${check.detail}` : ""}`
224
+ );
300
225
  if (check.hint) {
301
226
  lines.push(` ${check.hint}`);
302
227
  }