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.
- package/README.md +156 -105
- package/bin/clawvault.js +0 -2
- package/bin/register-core-commands.js +20 -2
- package/dist/{chunk-3D6BCTP6.js → chunk-33UGEQRT.js} +70 -145
- package/dist/{chunk-ZVVFWOLW.js → chunk-3WRJEKN4.js} +1 -1
- package/dist/{chunk-DEFFDRVP.js → chunk-3ZIH425O.js} +3 -70
- package/dist/{chunk-K234IDRJ.js → chunk-D2H45LON.js} +1 -0
- package/dist/{chunk-YKTA5JOJ.js → chunk-H62BP7RI.js} +3 -3
- package/dist/{chunk-WGRQ6HDV.js → chunk-LI4O6NVK.js} +1 -1
- package/dist/{chunk-7R7O6STJ.js → chunk-OCGVIN3L.js} +1 -1
- package/dist/{chunk-GAJV4IGR.js → chunk-YCUNCH2I.js} +3 -7
- package/dist/cli/index.cjs +10 -1459
- package/dist/cli/index.js +5 -8
- package/dist/commands/compat.cjs +70 -145
- package/dist/commands/compat.js +1 -1
- package/dist/commands/context.cjs +1 -0
- package/dist/commands/context.js +3 -3
- package/dist/commands/doctor.cjs +68 -144
- package/dist/commands/doctor.js +4 -4
- package/dist/commands/embed.js +2 -2
- package/dist/commands/setup.cjs +2 -69
- package/dist/commands/setup.d.cts +0 -1
- package/dist/commands/setup.d.ts +0 -1
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sleep.cjs +1 -0
- package/dist/commands/sleep.js +2 -2
- package/dist/commands/status.cjs +1 -0
- package/dist/commands/status.js +2 -2
- package/dist/commands/wake.cjs +1 -0
- package/dist/commands/wake.js +2 -2
- package/dist/index.cjs +447 -2600
- package/dist/index.d.cts +0 -4
- package/dist/index.d.ts +0 -4
- package/dist/index.js +8 -69
- package/dist/plugin/index.cjs +3 -3
- package/dist/plugin/index.js +10 -10
- package/package.json +11 -17
- package/bin/register-tailscale-commands.js +0 -106
- package/dist/chunk-IVRIKYFE.js +0 -520
- package/dist/chunk-THRJVD4L.js +0 -373
- package/dist/chunk-TIGW564L.js +0 -628
- package/dist/commands/tailscale.cjs +0 -1532
- package/dist/commands/tailscale.d.cts +0 -52
- package/dist/commands/tailscale.d.ts +0 -52
- package/dist/commands/tailscale.js +0 -26
- package/dist/lib/canvas-layout.cjs +0 -136
- package/dist/lib/canvas-layout.d.cts +0 -31
- package/dist/lib/canvas-layout.d.ts +0 -31
- package/dist/lib/canvas-layout.js +0 -92
- package/dist/lib/tailscale.cjs +0 -1183
- package/dist/lib/tailscale.d.cts +0 -225
- package/dist/lib/tailscale.d.ts +0 -225
- package/dist/lib/tailscale.js +0 -50
- package/dist/lib/webdav.cjs +0 -568
- package/dist/lib/webdav.d.cts +0 -109
- package/dist/lib/webdav.d.ts +0 -109
- package/dist/lib/webdav.js +0 -35
- package/hooks/clawvault/HOOK.md +0 -83
- package/hooks/clawvault/handler.js +0 -879
- 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
|
|
3
|
+
Structured memory for AI agents. Typed markdown primitives that compound over time.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/clawvault)
|
|
6
6
|
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
19
|
+
openclaw plugins install clawvault
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
This installs ClawVault as a memory plugin. It replaces OpenClaw's built-in memory with:
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
clawvault setup --theme neural --canvas
|
|
29
|
+
After install, configure the vault path:
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
clawvault
|
|
31
|
+
```bash
|
|
32
|
+
openclaw config set plugins.clawvault.config.vaultPath ~/my-vault
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
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
|
-
|
|
44
|
-
openclaw hooks install clawvault
|
|
45
|
-
openclaw hooks enable clawvault
|
|
41
|
+
## Quick Start
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
+
### Malleable Schemas
|
|
60
85
|
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
+
ClawVault uses `qmd` for search — BM25 keyword matching combined with vector similarity and reranking. Entirely local. No API keys needed.
|
|
72
91
|
|
|
73
|
-
|
|
92
|
+
### Obsidian Integration
|
|
74
93
|
|
|
75
|
-
-
|
|
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
|
-
|
|
96
|
+
- All tasks
|
|
97
|
+
- Blocked items
|
|
98
|
+
- By project
|
|
99
|
+
- By owner
|
|
100
|
+
- Backlog
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
- `observe`, `reflect`, `session-recap`
|
|
82
|
-
- `graph`, `entities`, `link`, `embed`
|
|
102
|
+
## CLI Commands
|
|
83
103
|
|
|
84
|
-
|
|
104
|
+
### Core
|
|
85
105
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
114
|
+
### Search & Context
|
|
91
115
|
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
123
|
+
### Session Lifecycle
|
|
96
124
|
|
|
97
|
-
|
|
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
|
-
|
|
132
|
+
### Observation Pipeline
|
|
100
133
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
140
|
+
### Tasks & Projects
|
|
123
141
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
149
|
+
### Utilities
|
|
133
150
|
|
|
134
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
##
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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: "
|
|
69
|
+
label: "plugin manifest",
|
|
108
70
|
status: "error",
|
|
109
|
-
detail: "
|
|
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
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
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: "
|
|
118
|
-
status: "
|
|
119
|
-
detail: "
|
|
83
|
+
label: "plugin manifest",
|
|
84
|
+
status: "error",
|
|
85
|
+
detail: issues.join(", ")
|
|
120
86
|
};
|
|
121
87
|
}
|
|
122
88
|
return {
|
|
123
|
-
label: "
|
|
124
|
-
status: "
|
|
125
|
-
detail:
|
|
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: "
|
|
95
|
+
label: "plugin manifest",
|
|
130
96
|
status: "error",
|
|
131
|
-
detail: err?.message || "Unable to parse
|
|
97
|
+
detail: err?.message || "Unable to parse openclaw.plugin.json"
|
|
132
98
|
};
|
|
133
99
|
}
|
|
134
100
|
}
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
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: "
|
|
118
|
+
label: "plugin extensions registration",
|
|
140
119
|
status: "error",
|
|
141
|
-
detail: "
|
|
120
|
+
detail: "package.json not found"
|
|
142
121
|
};
|
|
143
122
|
}
|
|
144
123
|
try {
|
|
145
|
-
const parsed =
|
|
146
|
-
const
|
|
147
|
-
|
|
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: "
|
|
152
|
-
status: "
|
|
153
|
-
detail:
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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: "
|
|
185
|
-
status: "
|
|
186
|
-
detail: `
|
|
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: "
|
|
191
|
-
status: "
|
|
192
|
-
detail:
|
|
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: "
|
|
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: "
|
|
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(
|
|
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
|
-
|
|
277
|
-
|
|
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(
|
|
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
|
}
|