@basicmemory/openclaw-basic-memory 0.1.0-alpha.12 → 0.1.0-alpha.13
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 +140 -484
- package/bm-client.ts +4 -3
- package/config.ts +0 -24
- package/index.ts +2 -24
- package/openclaw.plugin.json +0 -12
- package/package.json +1 -3
- package/dashboard/index.html +0 -182
- package/dashboard/server.ts +0 -146
package/README.md
CHANGED
|
@@ -1,131 +1,66 @@
|
|
|
1
1
|
# openclaw-basic-memory
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Give your OpenClaw agent persistent, searchable memory — in plain text files you can read and edit.
|
|
4
4
|
|
|
5
|
-
## What
|
|
5
|
+
## What is Basic Memory?
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[Basic Memory](https://basicmemory.com) stores AI knowledge in local Markdown files and indexes them into a semantic knowledge graph. Your agent writes notes. You can open them in any editor, read them, change them, and the changes sync back automatically. No black box. No proprietary format. Just files.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- **Persistent MCP stdio session** — keeps a single `bm mcp --transport stdio --project <name>` process alive
|
|
11
|
-
- **Auto-recall** — injects active tasks and recent activity as context at session start
|
|
12
|
-
- **Auto-capture** — records agent conversations as structured daily notes
|
|
13
|
-
- **Graph tools** — search, read, write, edit, delete, move, and navigate notes via `memory://` URLs
|
|
14
|
-
- **Skill workflows** — `/tasks`, `/reflect`, `/defrag`, `/schema` slash commands for guided memory management
|
|
9
|
+
It does three things that work together:
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
- **Stores knowledge in plain Markdown** — everything lives in plain text files on your computer, not locked inside a database you can't read
|
|
12
|
+
- **Creates connections automatically** — notes link to each other through a searchable, traversable knowledge graph
|
|
13
|
+
- **Searches by meaning, not just keywords** — vector search finds relevant context even when the exact words don't match
|
|
14
|
+
- **Keeps notes consistent** — dynamic schemas and validation ensure your knowledge base stays structured and useful as it grows
|
|
15
|
+
- **Enables two-way collaboration** — both you and the AI read and write the same files
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
Over time, your agent builds a knowledge base that grows with you. Context that survives across sessions. Memory that belongs to you.
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
Learn more: [basicmemory.com](https://basicmemory.com) · [GitHub](https://github.com/basicmachines-co/basic-memory) · [Docs](https://docs.basicmemory.com)
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# macOS
|
|
25
|
-
brew install uv
|
|
21
|
+
## What this plugin does
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
29
|
-
```
|
|
23
|
+
This plugin connects Basic Memory to OpenClaw so your agent can:
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
- **Remember across sessions** — search and recall past conversations, decisions, and context
|
|
26
|
+
- **Track work in progress** — structured tasks that survive context compaction
|
|
27
|
+
- **Build knowledge over time** — notes, observations, and relations that grow into a connected graph
|
|
28
|
+
- **Search intelligently** — composited search across working memory, the knowledge graph, and active tasks in parallel
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
All data stays on your machine as Markdown files indexed locally with SQLite. Cloud sync is available but entirely optional.
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
# Install the plugin (automatically installs the bm CLI via uv)
|
|
37
|
-
openclaw plugins install @basicmemory/openclaw-basic-memory
|
|
32
|
+
## Install
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
openclaw gateway restart
|
|
41
|
-
```
|
|
34
|
+
**Prerequisite:** [uv](https://docs.astral.sh/uv/) (Python package manager) — used to install the Basic Memory CLI.
|
|
42
35
|
|
|
43
|
-
Verify:
|
|
44
36
|
```bash
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```
|
|
37
|
+
# macOS
|
|
38
|
+
brew install uv
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
bash ~/.openclaw/extensions/openclaw-basic-memory/scripts/setup-bm.sh
|
|
40
|
+
# macOS / Linux
|
|
41
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
53
42
|
```
|
|
54
43
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Everything works locally — cloud adds cross-device, team, and production capabilities:
|
|
58
|
-
|
|
59
|
-
- **Your agent's memory travels with you** — same knowledge graph on laptop, desktop, and hosted environments
|
|
60
|
-
- **Team knowledge sharing** — org workspaces let multiple agents and team members build on a shared knowledge base
|
|
61
|
-
- **Durable memory for production agents** — persistent memory that survives CI teardowns and container restarts
|
|
62
|
-
- **Multi-agent coordination** — multiple agents can read and write to the same graph
|
|
63
|
-
|
|
64
|
-
Cloud extends local-first — still plain markdown, still yours. Start with a [7-day free trial](https://basicmemory.com) and use code `BMCLAW` for 20% off for 3 months. See [BASIC_MEMORY.md](./BASIC_MEMORY.md) for cloud setup.
|
|
65
|
-
|
|
66
|
-
### Development (local directory)
|
|
67
|
-
|
|
68
|
-
For plugin development, clone and link locally:
|
|
44
|
+
Then install the plugin:
|
|
69
45
|
|
|
70
46
|
```bash
|
|
71
|
-
|
|
72
|
-
cd openclaw-basic-memory
|
|
73
|
-
bun install
|
|
74
|
-
openclaw plugins install -l "$PWD"
|
|
75
|
-
openclaw plugins enable openclaw-basic-memory --slot memory
|
|
47
|
+
openclaw plugins install @basicmemory/openclaw-basic-memory
|
|
76
48
|
openclaw gateway restart
|
|
77
49
|
```
|
|
78
50
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```json5
|
|
82
|
-
{
|
|
83
|
-
plugins: {
|
|
84
|
-
load: {
|
|
85
|
-
paths: ["~/dev/openclaw-basic-memory"]
|
|
86
|
-
},
|
|
87
|
-
entries: {
|
|
88
|
-
"openclaw-basic-memory": {
|
|
89
|
-
enabled: true
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
slots: {
|
|
93
|
-
memory: "openclaw-basic-memory"
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Bundled Skills
|
|
100
|
-
|
|
101
|
-
This plugin ships with six skills that are automatically available when the plugin is enabled — no manual installation needed:
|
|
102
|
-
|
|
103
|
-
- **`memory-tasks`** — structured task tracking that survives context compaction
|
|
104
|
-
- **`memory-reflect`** — periodic consolidation of recent notes into durable memory
|
|
105
|
-
- **`memory-defrag`** — periodic cleanup/reorganization of memory files
|
|
106
|
-
- **`memory-schema`** — schema lifecycle management (infer, create, validate, diff, evolve)
|
|
107
|
-
- **`memory-metadata-search`** — structured metadata search by custom frontmatter fields (status, priority, etc.)
|
|
108
|
-
- **`memory-notes`** — guidance for writing well-structured notes with observations and relations
|
|
109
|
-
|
|
110
|
-
Skills are loaded from the plugin's `skills/` directory. See the upstream source at [`basic-memory-skills`](https://github.com/basicmachines-co/basic-memory-skills).
|
|
111
|
-
|
|
112
|
-
#### Updating or adding skills with `npx skills`
|
|
51
|
+
That's it. The plugin auto-installs the `bm` CLI on first startup if it's not already on your PATH. See [SECURITY.md](./SECURITY.md) for details on how this works.
|
|
113
52
|
|
|
114
|
-
|
|
53
|
+
Verify:
|
|
115
54
|
|
|
116
55
|
```bash
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# Install a specific skill
|
|
121
|
-
npx skills add basicmachines-co/basic-memory-skills --name memory-tasks --agent openclaw
|
|
56
|
+
openclaw plugins list
|
|
57
|
+
openclaw plugins info openclaw-basic-memory
|
|
122
58
|
```
|
|
123
59
|
|
|
124
|
-
This installs to the same `skills/` directory the plugin reads from, so updated skills take effect on the next session.
|
|
125
|
-
|
|
126
60
|
## Configuration
|
|
127
61
|
|
|
128
|
-
###
|
|
62
|
+
### Zero-config (recommended)
|
|
63
|
+
|
|
129
64
|
```json5
|
|
130
65
|
{
|
|
131
66
|
"openclaw-basic-memory": {
|
|
@@ -134,459 +69,180 @@ This installs to the same `skills/` directory the plugin reads from, so updated
|
|
|
134
69
|
}
|
|
135
70
|
```
|
|
136
71
|
|
|
137
|
-
This uses sensible defaults: auto-generated project name, maps
|
|
72
|
+
This uses sensible defaults: auto-generated project name, maps to your workspace root, captures conversations, and recalls active tasks on session start.
|
|
73
|
+
|
|
74
|
+
### Full options
|
|
138
75
|
|
|
139
|
-
### Full configuration
|
|
140
76
|
```json5
|
|
141
77
|
{
|
|
142
78
|
"openclaw-basic-memory": {
|
|
143
79
|
enabled: true,
|
|
144
80
|
config: {
|
|
145
|
-
project: "my-agent",
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
autoRecall: true, // Inject active tasks + recent activity at session start
|
|
153
|
-
recallPrompt: "Check for active tasks and recent activity. Summarize anything relevant to the current session.",
|
|
154
|
-
debug: false, // Verbose logging
|
|
81
|
+
project: "my-agent", // BM project name (default: "openclaw-{hostname}")
|
|
82
|
+
projectPath: ".", // Project directory (default: workspace root)
|
|
83
|
+
memoryDir: "memory/", // Where task notes live
|
|
84
|
+
memoryFile: "MEMORY.md", // Working memory file
|
|
85
|
+
autoCapture: true, // Record conversations as daily notes
|
|
86
|
+
autoRecall: true, // Inject active tasks + recent activity at session start
|
|
87
|
+
debug: false // Verbose logging
|
|
155
88
|
}
|
|
156
89
|
}
|
|
157
90
|
}
|
|
158
91
|
```
|
|
159
92
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
|
163
|
-
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `recallPrompt` | string | *(see above)* | Instruction appended to recalled context — customize to change what the agent focuses on |
|
|
173
|
-
| `debug` | boolean | `false` | Enable verbose debug logs |
|
|
174
|
-
|
|
175
|
-
Snake_case aliases (`memory_dir`, `memory_file`, `auto_recall`, `recall_prompt`, `capture_min_chars`) are also supported.
|
|
176
|
-
|
|
177
|
-
Cloud sync is optional — see [BASIC_MEMORY.md](./BASIC_MEMORY.md) for cloud configuration.
|
|
178
|
-
|
|
179
|
-
On startup, the plugin ensures the configured BM project exists at `projectPath` via MCP `create_memory_project` in idempotent mode, and sets it as the default Basic Memory project.
|
|
180
|
-
|
|
181
|
-
## How It Works
|
|
182
|
-
|
|
183
|
-
### MCP Session Lifecycle
|
|
184
|
-
On startup, the plugin starts one persistent MCP stdio session:
|
|
185
|
-
1. Spawns `bm mcp --transport stdio --project <name>`
|
|
186
|
-
2. Verifies required MCP tool capabilities at connect time
|
|
187
|
-
3. Uses bounded reconnect attempts (`500ms`, `1000ms`, `2000ms`) when the session drops
|
|
188
|
-
|
|
189
|
-
Basic Memory MCP lifecycle handles sync and watch behavior for the project.
|
|
190
|
-
|
|
191
|
-
### Composited `memory_search`
|
|
192
|
-
When the agent calls `memory_search`, three sources are queried in parallel:
|
|
193
|
-
|
|
194
|
-
1. **MEMORY.md** — grep/text search with ±1 line context
|
|
195
|
-
2. **BM Knowledge Graph** — hybrid FTS + vector search (top 5 results with scores)
|
|
196
|
-
3. **Active Tasks** — scans `memory/tasks/` for non-done tasks
|
|
197
|
-
|
|
198
|
-
Results are formatted into clear sections:
|
|
199
|
-
```
|
|
200
|
-
## MEMORY.md
|
|
201
|
-
- matching lines with context...
|
|
202
|
-
|
|
203
|
-
## Knowledge Graph (memory/)
|
|
204
|
-
- note-title (0.85)
|
|
205
|
-
> preview of content...
|
|
93
|
+
| Option | Default | Description |
|
|
94
|
+
|--------|---------|-------------|
|
|
95
|
+
| `project` | `"openclaw-{hostname}"` | Basic Memory project name |
|
|
96
|
+
| `bmPath` | `"bm"` | Path to BM CLI binary |
|
|
97
|
+
| `projectPath` | `"."` | Project data directory |
|
|
98
|
+
| `memoryDir` | `"memory/"` | Relative path for task scanning |
|
|
99
|
+
| `memoryFile` | `"MEMORY.md"` | Working memory file for text search |
|
|
100
|
+
| `autoCapture` | `true` | Auto-index agent conversations |
|
|
101
|
+
| `captureMinChars` | `10` | Min chars to trigger capture |
|
|
102
|
+
| `autoRecall` | `true` | Inject context at session start |
|
|
103
|
+
| `recallPrompt` | *(default)* | Instruction appended to recalled context |
|
|
104
|
+
| `debug` | `false` | Verbose logs |
|
|
206
105
|
|
|
207
|
-
##
|
|
208
|
-
- **Task Name** (status: active, step: 3)
|
|
209
|
-
context snippet...
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Memory + Task Management Flow
|
|
213
|
-
|
|
214
|
-
This plugin works best if you treat memory as three lanes:
|
|
215
|
-
|
|
216
|
-
1. **Working memory (`MEMORY.md`)** — short-horizon context and current focus.
|
|
217
|
-
2. **Knowledge graph (`memory/**/*.md`)** — long-term notes indexed by Basic Memory.
|
|
218
|
-
3. **Task notes (`memory/tasks/*.md`)** — active execution state for in-flight work.
|
|
219
|
-
|
|
220
|
-
> **Note:** OpenClaw's default convention treats `MEMORY.md` as [long-term curated memory](https://docs.openclaw.ai/concepts/memory). This plugin flips that role — the BM knowledge graph handles durable storage, so `MEMORY.md` serves as short-horizon working memory. See [MEMORY_TASK_FLOW.md](./MEMORY_TASK_FLOW.md) for details.
|
|
221
|
-
|
|
222
|
-
Typical loop:
|
|
223
|
-
|
|
224
|
-
1. Capture or update notes/tasks with `write_note` / `edit_note`.
|
|
225
|
-
2. The persistent BM MCP process syncs markdown updates into the BM project index.
|
|
226
|
-
3. `memory_search` queries:
|
|
227
|
-
- `MEMORY.md` text snippets
|
|
228
|
-
- BM search results (semantic + FTS)
|
|
229
|
-
- active tasks
|
|
230
|
-
4. Drill into one result with `memory_get` or `read_note`.
|
|
231
|
-
5. Advance tasks by updating `current_step`, checkboxes, and context.
|
|
232
|
-
6. Complete tasks by setting `status: done` (done tasks are excluded from active task results).
|
|
233
|
-
|
|
234
|
-
```mermaid
|
|
235
|
-
flowchart LR
|
|
236
|
-
A["Write/Update notes"] --> B["BM MCP indexes changes"]
|
|
237
|
-
B --> C["memory_search(query)"]
|
|
238
|
-
C --> D["MEMORY.md"]
|
|
239
|
-
C --> E["Knowledge Graph"]
|
|
240
|
-
C --> F["Active Tasks"]
|
|
241
|
-
D --> G["Composited result"]
|
|
242
|
-
E --> G
|
|
243
|
-
F --> G
|
|
244
|
-
G --> H["memory_get / read_note"]
|
|
245
|
-
H --> A
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### Task Note Shape (Recommended)
|
|
249
|
-
|
|
250
|
-
`memory_search` task extraction is strongest when task notes include:
|
|
106
|
+
## How it works
|
|
251
107
|
|
|
252
|
-
|
|
253
|
-
- frontmatter fields: `status:` and `current_step:`
|
|
254
|
-
- a `## Context` section for preview snippets
|
|
255
|
-
|
|
256
|
-
Example:
|
|
257
|
-
|
|
258
|
-
```markdown
|
|
259
|
-
---
|
|
260
|
-
title: auth-middleware-rollout
|
|
261
|
-
type: Task
|
|
262
|
-
status: active
|
|
263
|
-
current_step: 2
|
|
264
|
-
---
|
|
108
|
+
### Memory search
|
|
265
109
|
|
|
266
|
-
|
|
267
|
-
Rolling JWT middleware to all API routes. Staging verification is in progress.
|
|
110
|
+
When your agent calls `memory_search`, three sources are queried in parallel:
|
|
268
111
|
|
|
269
|
-
|
|
270
|
-
-
|
|
271
|
-
|
|
272
|
-
- [ ] Roll out to staging
|
|
273
|
-
- [ ] Verify logs and error rates
|
|
274
|
-
```
|
|
112
|
+
1. **MEMORY.md** — text search with surrounding context
|
|
113
|
+
2. **Knowledge Graph** — hybrid full-text + vector search across all indexed notes
|
|
114
|
+
3. **Active Tasks** — scans `memory/tasks/` for in-progress work
|
|
275
115
|
|
|
276
|
-
|
|
116
|
+
Results come back in clear sections so the agent knows where each piece of context came from.
|
|
277
117
|
|
|
278
|
-
|
|
279
|
-
status: done
|
|
280
|
-
```
|
|
118
|
+
### Auto-recall
|
|
281
119
|
|
|
282
|
-
|
|
120
|
+
On each session start, the plugin loads active tasks and recently modified notes, giving the agent immediate awareness of ongoing work without being asked.
|
|
283
121
|
|
|
284
|
-
### Auto-
|
|
285
|
-
On each `agent_start` event (when `autoRecall: true`), the plugin:
|
|
286
|
-
1. Queries the knowledge graph for active tasks (`type: Task`, `status: active`, up to 5)
|
|
287
|
-
2. Fetches notes modified in the last 24 hours
|
|
288
|
-
3. Formats both into structured context and returns it to the agent
|
|
122
|
+
### Auto-capture
|
|
289
123
|
|
|
290
|
-
|
|
124
|
+
After each conversation turn, the plugin records the exchange as a timestamped entry in a daily note. This builds a searchable history of everything your agent has discussed.
|
|
291
125
|
|
|
292
|
-
###
|
|
293
|
-
After each agent turn (when `autoCapture: true`), the plugin:
|
|
294
|
-
1. Extracts the last user + assistant messages
|
|
295
|
-
2. Appends them as timestamped entries to a daily conversation note (`conversations-YYYY-MM-DD`)
|
|
296
|
-
3. Skips very short exchanges (< `captureMinChars` chars each, default 10)
|
|
126
|
+
### Persistent connection
|
|
297
127
|
|
|
298
|
-
|
|
128
|
+
The plugin keeps a long-lived Basic Memory process running over standard I/O. No cold starts per tool call. The connection auto-reconnects if it drops.
|
|
299
129
|
|
|
300
|
-
|
|
130
|
+
## Agent tools
|
|
301
131
|
|
|
302
|
-
|
|
303
|
-
List all workspaces (personal and organization) accessible to this user.
|
|
304
|
-
```
|
|
305
|
-
list_workspaces()
|
|
306
|
-
```
|
|
132
|
+
All tools accept an optional `project` parameter for cross-project operations.
|
|
307
133
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
134
|
+
| Tool | Description |
|
|
135
|
+
|------|-------------|
|
|
136
|
+
| `memory_search` | Composited search across all memory sources |
|
|
137
|
+
| `memory_get` | Read a specific note by title or path |
|
|
138
|
+
| `search_notes` | Search the knowledge graph directly |
|
|
139
|
+
| `read_note` | Read a note by title, permalink, or `memory://` URL |
|
|
140
|
+
| `write_note` | Create or update a note |
|
|
141
|
+
| `edit_note` | Append, prepend, find/replace, or replace a section |
|
|
142
|
+
| `delete_note` | Delete a note |
|
|
143
|
+
| `move_note` | Move a note to a different folder |
|
|
144
|
+
| `build_context` | Navigate the knowledge graph — follow relations and connections |
|
|
145
|
+
| `list_memory_projects` | List accessible projects |
|
|
146
|
+
| `list_workspaces` | List workspaces (personal and org) |
|
|
147
|
+
| `schema_validate` | Validate notes against Picoschema definitions |
|
|
148
|
+
| `schema_infer` | Analyze notes and suggest a schema |
|
|
149
|
+
| `schema_diff` | Detect drift between schema and actual usage |
|
|
314
150
|
|
|
315
|
-
|
|
316
|
-
Search the knowledge graph.
|
|
317
|
-
```
|
|
318
|
-
search_notes(query="API design", limit=5)
|
|
319
|
-
search_notes(query="API design", project="other-project") # cross-project
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### `read_note`
|
|
323
|
-
Read a note by title, permalink, or `memory://` URL.
|
|
324
|
-
```
|
|
325
|
-
read_note(identifier="memory://projects/api-redesign")
|
|
326
|
-
read_note(identifier="memory://projects/api-redesign", include_frontmatter=true) # raw markdown + YAML
|
|
327
|
-
read_note(identifier="notes/readme", project="docs") # cross-project
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### `write_note`
|
|
331
|
-
Create a new note.
|
|
332
|
-
```
|
|
333
|
-
write_note(title="Auth Strategy", content="## Overview\n...", folder="decisions")
|
|
334
|
-
write_note(title="Shared Note", content="...", folder="shared", project="team") # cross-project
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### `edit_note`
|
|
338
|
-
Edit an existing note (`append`, `prepend`, `find_replace`, `replace_section`).
|
|
339
|
-
```
|
|
340
|
-
edit_note(identifier="weekly-review", operation="append", content="\n## Update\nDone.")
|
|
341
|
-
edit_note(
|
|
342
|
-
identifier="weekly-review",
|
|
343
|
-
operation="find_replace",
|
|
344
|
-
find_text="status: active",
|
|
345
|
-
content="status: done",
|
|
346
|
-
expected_replacements=1,
|
|
347
|
-
)
|
|
348
|
-
edit_note(
|
|
349
|
-
identifier="weekly-review",
|
|
350
|
-
operation="replace_section",
|
|
351
|
-
section="## This Week",
|
|
352
|
-
content="- Done\n- Next",
|
|
353
|
-
)
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### `delete_note`
|
|
357
|
-
Delete a note.
|
|
358
|
-
```
|
|
359
|
-
delete_note(identifier="notes/old-draft")
|
|
360
|
-
delete_note(identifier="notes/old-draft", project="archive") # cross-project
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### `move_note`
|
|
364
|
-
Move a note to a different folder.
|
|
365
|
-
```
|
|
366
|
-
move_note(identifier="notes/my-note", newFolder="archive")
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### `build_context`
|
|
370
|
-
Navigate the knowledge graph — get a note with its observations and relations.
|
|
371
|
-
```
|
|
372
|
-
build_context(url="memory://projects/api-redesign", depth=2)
|
|
373
|
-
build_context(url="memory://decisions", depth=1, project="team") # cross-project
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
### `schema_validate`
|
|
377
|
-
Validate notes against their Picoschema definitions.
|
|
378
|
-
```
|
|
379
|
-
schema_validate(noteType="person")
|
|
380
|
-
schema_validate(identifier="notes/john-doe")
|
|
381
|
-
schema_validate(noteType="person", project="contacts") # cross-project
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
### `schema_infer`
|
|
385
|
-
Analyze existing notes and suggest a Picoschema definition.
|
|
386
|
-
```
|
|
387
|
-
schema_infer(noteType="meeting")
|
|
388
|
-
schema_infer(noteType="person", threshold=0.5)
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### `schema_diff`
|
|
392
|
-
Detect drift between a schema definition and actual note usage.
|
|
393
|
-
```
|
|
394
|
-
schema_diff(noteType="person")
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
## Slash Commands
|
|
398
|
-
|
|
399
|
-
### Memory
|
|
400
|
-
- **`/remember <text>`** — Save a quick note to the knowledge graph
|
|
401
|
-
- **`/recall <query>`** — Search the knowledge graph (top 5 results)
|
|
402
|
-
|
|
403
|
-
### Skill workflows
|
|
404
|
-
These inject step-by-step instructions from the bundled skill files. Each accepts optional arguments for context.
|
|
151
|
+
## Slash commands
|
|
405
152
|
|
|
406
153
|
| Command | Description |
|
|
407
154
|
|---------|-------------|
|
|
408
|
-
| `/
|
|
409
|
-
| `/
|
|
410
|
-
| `/
|
|
411
|
-
| `/
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
```
|
|
415
|
-
/tasks create a task for the API migration
|
|
416
|
-
/reflect
|
|
417
|
-
/defrag clean up completed tasks older than 2 weeks
|
|
418
|
-
/schema infer a schema for Meeting notes
|
|
419
|
-
```
|
|
155
|
+
| `/remember <text>` | Save a quick note |
|
|
156
|
+
| `/recall <query>` | Search the knowledge graph |
|
|
157
|
+
| `/tasks [args]` | Create, track, resume structured tasks |
|
|
158
|
+
| `/reflect [args]` | Consolidate recent notes into long-term memory |
|
|
159
|
+
| `/defrag [args]` | Reorganize and clean up memory files |
|
|
160
|
+
| `/schema [args]` | Manage Picoschema definitions |
|
|
420
161
|
|
|
421
|
-
## CLI
|
|
162
|
+
## CLI
|
|
422
163
|
|
|
423
164
|
```bash
|
|
424
165
|
openclaw basic-memory search "auth patterns" --limit 5
|
|
425
166
|
openclaw basic-memory read "projects/api-redesign"
|
|
426
|
-
openclaw basic-memory read "projects/api-redesign" --raw
|
|
427
|
-
openclaw basic-memory edit "projects/api-redesign" --operation append --content $'\n## Update\nDone.'
|
|
428
167
|
openclaw basic-memory context "memory://projects/api-redesign" --depth 2
|
|
429
168
|
openclaw basic-memory recent --timeframe 24h
|
|
430
169
|
openclaw basic-memory status
|
|
431
170
|
```
|
|
432
171
|
|
|
433
|
-
##
|
|
172
|
+
## Bundled skills
|
|
434
173
|
|
|
435
|
-
|
|
436
|
-
```bash
|
|
437
|
-
which bm # Check if installed
|
|
438
|
-
bm --version # Check version
|
|
439
|
-
bm mcp --help # Verify MCP server command exists
|
|
440
|
-
```
|
|
441
|
-
If `bm mcp` doesn't exist, update Basic Memory to a newer version.
|
|
174
|
+
Six skills ship with the plugin — no installation needed:
|
|
442
175
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
176
|
+
- **memory-tasks** — structured task tracking that survives context compaction
|
|
177
|
+
- **memory-reflect** — periodic consolidation of recent notes into durable memory
|
|
178
|
+
- **memory-defrag** — cleanup and reorganization of memory files
|
|
179
|
+
- **memory-schema** — schema lifecycle (infer, create, validate, diff)
|
|
180
|
+
- **memory-metadata-search** — query notes by frontmatter fields
|
|
181
|
+
- **memory-notes** — guidance for writing well-structured notes
|
|
446
182
|
|
|
447
|
-
###
|
|
448
|
-
```bash
|
|
449
|
-
rm -rf /tmp/jiti/ "$TMPDIR/jiti/"
|
|
450
|
-
openclaw gateway stop && openclaw gateway start
|
|
451
|
-
```
|
|
183
|
+
### Updating skills
|
|
452
184
|
|
|
453
|
-
### Disabling semantic search
|
|
454
|
-
If you want to run without vector/embedding dependencies (faster startup, less memory), set the environment variable before launching:
|
|
455
185
|
```bash
|
|
456
|
-
|
|
457
|
-
```
|
|
458
|
-
Or in `~/.basic-memory/config.json`:
|
|
459
|
-
```json
|
|
460
|
-
{ "semantic_search_enabled": false }
|
|
461
|
-
```
|
|
462
|
-
Search will fall back to full-text search only.
|
|
463
|
-
|
|
464
|
-
### Search returns no results
|
|
465
|
-
1. Check that the MCP session is connected (look for `connected to BM MCP stdio` in logs)
|
|
466
|
-
2. Verify files exist in the project directory
|
|
467
|
-
3. Try `bm mcp --transport stdio --project <name>` and run `search_notes` through an MCP inspector/client
|
|
468
|
-
4. Check project status: `bm project list`
|
|
469
|
-
|
|
470
|
-
## Integration Tests
|
|
471
|
-
|
|
472
|
-
This repo includes real end-to-end integration tests for `BmClient` in:
|
|
473
|
-
|
|
474
|
-
- `integration/bm-client.integration.test.ts`
|
|
475
|
-
|
|
476
|
-
These tests launch a real `bm mcp --transport stdio --project <name>` process,
|
|
477
|
-
run write/read/edit/search/context/move/delete calls, and assert actual filesystem/index results.
|
|
478
|
-
|
|
479
|
-
Run integration tests:
|
|
480
|
-
|
|
481
|
-
```bash
|
|
482
|
-
bun run test:int
|
|
186
|
+
npx skills add basicmachines-co/basic-memory-skills --agent openclaw
|
|
483
187
|
```
|
|
484
188
|
|
|
485
|
-
|
|
486
|
-
`../basic-memory` checkout via `uv run --project ...` when present, and falls
|
|
487
|
-
back to `bm` on `PATH` otherwise.
|
|
189
|
+
## Task notes
|
|
488
190
|
|
|
489
|
-
|
|
191
|
+
The plugin works well with structured task notes in `memory/tasks/`:
|
|
490
192
|
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
193
|
+
```markdown
|
|
194
|
+
---
|
|
195
|
+
title: auth-middleware-rollout
|
|
196
|
+
type: Task
|
|
197
|
+
status: active
|
|
198
|
+
current_step: 2
|
|
199
|
+
---
|
|
498
200
|
|
|
499
|
-
##
|
|
201
|
+
## Context
|
|
202
|
+
Rolling JWT middleware to all API routes.
|
|
500
203
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
204
|
+
## Plan
|
|
205
|
+
- [x] Implement middleware
|
|
206
|
+
- [x] Add refresh-token validation
|
|
207
|
+
- [ ] Roll out to staging
|
|
208
|
+
- [ ] Verify logs and error rates
|
|
506
209
|
```
|
|
507
210
|
|
|
508
|
-
|
|
211
|
+
Set `status: done` to mark complete. Done tasks are filtered out of active task results.
|
|
509
212
|
|
|
510
|
-
|
|
213
|
+
## Basic Memory Cloud
|
|
511
214
|
|
|
512
|
-
|
|
513
|
-
# 1) Verify release readiness (types + tests + npm pack dry run)
|
|
514
|
-
just release-check
|
|
215
|
+
Everything works locally. Cloud adds cross-device sync, team workspaces, and persistent memory for hosted agents.
|
|
515
216
|
|
|
516
|
-
|
|
517
|
-
|
|
217
|
+
- Same knowledge graph on laptop, desktop, and CI
|
|
218
|
+
- Shared workspaces for teams
|
|
219
|
+
- Durable memory for production agents
|
|
518
220
|
|
|
519
|
-
|
|
520
|
-
npm login
|
|
221
|
+
Cloud extends local-first — still plain Markdown, still yours. [Start a free trial](https://basicmemory.com) and use code `BMCLAW` for 20% off for 3 months. See [BASIC_MEMORY.md](./BASIC_MEMORY.md) for setup.
|
|
521
222
|
|
|
522
|
-
|
|
523
|
-
just release-publish
|
|
524
|
-
```
|
|
223
|
+
## Troubleshooting
|
|
525
224
|
|
|
526
|
-
|
|
225
|
+
**`bm` not found** — Install uv, then restart the gateway. Or install manually: `uv tool install basic-memory`
|
|
527
226
|
|
|
528
|
-
|
|
529
|
-
just release patch # or: minor, major, 0.2.0, etc.
|
|
530
|
-
```
|
|
227
|
+
**Search returns nothing** — Check that Basic Memory connected (look for `connected to BM` in logs). Verify files exist in the project directory.
|
|
531
228
|
|
|
532
|
-
|
|
229
|
+
**Jiti cache issues** — `rm -rf /tmp/jiti/ "$TMPDIR/jiti/"` then restart the gateway.
|
|
533
230
|
|
|
534
|
-
|
|
535
|
-
- Release workflow: `.github/workflows/release.yml` runs manually (`workflow_dispatch`) and will:
|
|
536
|
-
1. run release checks
|
|
537
|
-
2. bump version and create a git tag
|
|
538
|
-
3. push commit + tag
|
|
539
|
-
4. publish to npm
|
|
540
|
-
5. create a GitHub release
|
|
231
|
+
**Disable semantic search** — Set `BASIC_MEMORY_SEMANTIC_SEARCH_ENABLED=false` to fall back to full-text only.
|
|
541
232
|
|
|
542
|
-
|
|
233
|
+
## More
|
|
543
234
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
├── tools/ # Agent tools
|
|
551
|
-
│ ├── search-notes.ts # search_notes
|
|
552
|
-
│ ├── read-note.ts # read_note
|
|
553
|
-
│ ├── write-note.ts # write_note
|
|
554
|
-
│ ├── edit-note.ts # edit_note
|
|
555
|
-
│ ├── delete-note.ts # delete_note
|
|
556
|
-
│ ├── move-note.ts # move_note
|
|
557
|
-
│ ├── build-context.ts # build_context
|
|
558
|
-
│ ├── list-memory-projects.ts # list_memory_projects
|
|
559
|
-
│ ├── list-workspaces.ts # list_workspaces
|
|
560
|
-
│ ├── schema-validate.ts # schema_validate
|
|
561
|
-
│ ├── schema-infer.ts # schema_infer
|
|
562
|
-
│ ├── schema-diff.ts # schema_diff
|
|
563
|
-
│ └── memory-provider.ts # Composited memory_search + memory_get
|
|
564
|
-
├── commands/
|
|
565
|
-
│ ├── slash.ts # /remember, /recall
|
|
566
|
-
│ ├── skills.ts # /tasks, /reflect, /defrag, /schema
|
|
567
|
-
│ └── cli.ts # openclaw basic-memory CLI
|
|
568
|
-
└── hooks/
|
|
569
|
-
├── capture.ts # Auto-capture conversations
|
|
570
|
-
└── recall.ts # Auto-recall (active tasks + recent activity)
|
|
571
|
-
```
|
|
235
|
+
- [Memory + Task Flow](./MEMORY_TASK_FLOW.md) — practical runbook
|
|
236
|
+
- [Cloud Setup](./BASIC_MEMORY.md) — configure Basic Memory Cloud
|
|
237
|
+
- [Security](./SECURITY.md) — how auto-installation and data handling work
|
|
238
|
+
- [Development](./DEVELOPMENT.md) — contributing, tests, publishing
|
|
239
|
+
- [Basic Memory docs](https://docs.basicmemory.com)
|
|
240
|
+
- [Issues](https://github.com/basicmachines-co/openclaw-basic-memory/issues)
|
|
572
241
|
|
|
573
242
|
## Telemetry
|
|
574
243
|
|
|
575
|
-
This plugin
|
|
576
|
-
|
|
577
|
-
To opt out of Basic Memory CLI telemetry:
|
|
578
|
-
|
|
579
|
-
```bash
|
|
580
|
-
export BASIC_MEMORY_NO_PROMOS=1
|
|
581
|
-
```
|
|
244
|
+
This plugin does not collect telemetry. The Basic Memory CLI may send anonymous usage analytics — see the [Basic Memory docs](https://github.com/basicmachines-co/basic-memory) for opt-out instructions.
|
|
582
245
|
|
|
583
246
|
## License
|
|
584
247
|
|
|
585
|
-
MIT
|
|
586
|
-
|
|
587
|
-
## Links
|
|
588
|
-
|
|
589
|
-
- [Basic Memory](https://github.com/basicmachines-co/basic-memory)
|
|
590
|
-
- [Basic Memory Skills](https://github.com/basicmachines-co/basic-memory-skills)
|
|
591
|
-
- [OpenClaw](https://docs.openclaw.ai)
|
|
592
|
-
- [Issues](https://github.com/basicmachines-co/openclaw-basic-memory/issues)
|
|
248
|
+
MIT
|
package/bm-client.ts
CHANGED
|
@@ -659,11 +659,12 @@ export class BmClient {
|
|
|
659
659
|
identifier,
|
|
660
660
|
operation,
|
|
661
661
|
content,
|
|
662
|
-
find_text: options.find_text,
|
|
663
|
-
section: options.section,
|
|
664
|
-
expected_replacements: options.expected_replacements,
|
|
665
662
|
output_format: "json",
|
|
666
663
|
}
|
|
664
|
+
if (options.find_text) args.find_text = options.find_text
|
|
665
|
+
if (options.section) args.section = options.section
|
|
666
|
+
if (options.expected_replacements != null)
|
|
667
|
+
args.expected_replacements = options.expected_replacements
|
|
667
668
|
if (project) args.project = project
|
|
668
669
|
|
|
669
670
|
const payload = await this.callTool("edit_note", args)
|
package/config.ts
CHANGED
|
@@ -6,11 +6,6 @@ export type CloudConfig = {
|
|
|
6
6
|
api_key: string
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export type DashboardConfig = {
|
|
10
|
-
enabled: boolean
|
|
11
|
-
port: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
9
|
export type BasicMemoryConfig = {
|
|
15
10
|
project: string
|
|
16
11
|
bmPath: string
|
|
@@ -23,7 +18,6 @@ export type BasicMemoryConfig = {
|
|
|
23
18
|
recallPrompt: string
|
|
24
19
|
debug: boolean
|
|
25
20
|
cloud?: CloudConfig
|
|
26
|
-
dashboard: DashboardConfig
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
const ALLOWED_KEYS = [
|
|
@@ -43,7 +37,6 @@ const ALLOWED_KEYS = [
|
|
|
43
37
|
"recall_prompt",
|
|
44
38
|
"debug",
|
|
45
39
|
"cloud",
|
|
46
|
-
"dashboard",
|
|
47
40
|
]
|
|
48
41
|
|
|
49
42
|
function assertAllowedKeys(
|
|
@@ -113,22 +106,6 @@ export function parseConfig(raw: unknown): BasicMemoryConfig {
|
|
|
113
106
|
}
|
|
114
107
|
}
|
|
115
108
|
|
|
116
|
-
let dashboard: DashboardConfig = { enabled: false, port: 3838 }
|
|
117
|
-
if (
|
|
118
|
-
cfg.dashboard &&
|
|
119
|
-
typeof cfg.dashboard === "object" &&
|
|
120
|
-
!Array.isArray(cfg.dashboard)
|
|
121
|
-
) {
|
|
122
|
-
const d = cfg.dashboard as Record<string, unknown>
|
|
123
|
-
dashboard = {
|
|
124
|
-
enabled: typeof d.enabled === "boolean" ? d.enabled : false,
|
|
125
|
-
port:
|
|
126
|
-
typeof d.port === "number" && d.port > 0 && d.port < 65536
|
|
127
|
-
? d.port
|
|
128
|
-
: 3838,
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
109
|
return {
|
|
133
110
|
project:
|
|
134
111
|
typeof cfg.project === "string" && cfg.project.length > 0
|
|
@@ -167,7 +144,6 @@ export function parseConfig(raw: unknown): BasicMemoryConfig {
|
|
|
167
144
|
: "Check for active tasks and recent activity. Summarize anything relevant to the current session.",
|
|
168
145
|
debug: typeof cfg.debug === "boolean" ? cfg.debug : false,
|
|
169
146
|
cloud,
|
|
170
|
-
dashboard,
|
|
171
147
|
}
|
|
172
148
|
}
|
|
173
149
|
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from "node:child_process"
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
4
4
|
import { BmClient } from "./bm-client.ts"
|
|
5
5
|
import { registerCli } from "./commands/cli.ts"
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
parseConfig,
|
|
11
11
|
resolveProjectPath,
|
|
12
12
|
} from "./config.ts"
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
import { buildCaptureHandler } from "./hooks/capture.ts"
|
|
15
15
|
import { buildRecallHandler } from "./hooks/recall.ts"
|
|
16
16
|
import { initLogger, log } from "./logger.ts"
|
|
@@ -83,8 +83,6 @@ export default {
|
|
|
83
83
|
registerCli(api, client, cfg)
|
|
84
84
|
|
|
85
85
|
// --- Service lifecycle ---
|
|
86
|
-
let dashboardServer: Server | undefined
|
|
87
|
-
|
|
88
86
|
api.registerService({
|
|
89
87
|
id: "openclaw-basic-memory",
|
|
90
88
|
start: async (ctx: { config?: unknown; workspaceDir?: string }) => {
|
|
@@ -147,30 +145,10 @@ export default {
|
|
|
147
145
|
|
|
148
146
|
setWorkspaceDir(workspace)
|
|
149
147
|
|
|
150
|
-
// Start dashboard if enabled
|
|
151
|
-
if (cfg.dashboard.enabled) {
|
|
152
|
-
dashboardServer = createDashboardServer({
|
|
153
|
-
port: cfg.dashboard.port,
|
|
154
|
-
client,
|
|
155
|
-
})
|
|
156
|
-
dashboardServer.listen(cfg.dashboard.port, () => {
|
|
157
|
-
log.info(
|
|
158
|
-
`dashboard running at http://localhost:${cfg.dashboard.port}`,
|
|
159
|
-
)
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
|
|
163
148
|
log.info("connected — BM MCP stdio session running")
|
|
164
149
|
},
|
|
165
150
|
stop: async () => {
|
|
166
151
|
log.info("stopping BM MCP session...")
|
|
167
|
-
if (dashboardServer) {
|
|
168
|
-
await new Promise<void>((resolve) =>
|
|
169
|
-
dashboardServer?.close(() => resolve()),
|
|
170
|
-
)
|
|
171
|
-
dashboardServer = undefined
|
|
172
|
-
log.info("dashboard stopped")
|
|
173
|
-
}
|
|
174
152
|
await client.stop()
|
|
175
153
|
log.info("stopped")
|
|
176
154
|
},
|
package/openclaw.plugin.json
CHANGED
|
@@ -56,11 +56,6 @@
|
|
|
56
56
|
"label": "Cloud Backend",
|
|
57
57
|
"help": "Optional cloud backend config (url + api_key). If present, uses cloud instead of local BM.",
|
|
58
58
|
"advanced": true
|
|
59
|
-
},
|
|
60
|
-
"dashboard": {
|
|
61
|
-
"label": "Dashboard",
|
|
62
|
-
"help": "Web dashboard for visualizing the knowledge graph (enabled, port)",
|
|
63
|
-
"advanced": true
|
|
64
59
|
}
|
|
65
60
|
},
|
|
66
61
|
"configSchema": {
|
|
@@ -81,13 +76,6 @@
|
|
|
81
76
|
"url": { "type": "string" },
|
|
82
77
|
"api_key": { "type": "string" }
|
|
83
78
|
}
|
|
84
|
-
},
|
|
85
|
-
"dashboard": {
|
|
86
|
-
"type": "object",
|
|
87
|
-
"properties": {
|
|
88
|
-
"enabled": { "type": "boolean" },
|
|
89
|
-
"port": { "type": "number", "minimum": 1, "maximum": 65535 }
|
|
90
|
-
}
|
|
91
79
|
}
|
|
92
80
|
},
|
|
93
81
|
"required": []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basicmemory/openclaw-basic-memory",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Basic Memory plugin for OpenClaw — local-first knowledge graph for agent memory",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,8 +33,6 @@
|
|
|
33
33
|
"tools/write-note.ts",
|
|
34
34
|
"types/openclaw.d.ts",
|
|
35
35
|
"schema/task-schema.ts",
|
|
36
|
-
"dashboard/server.ts",
|
|
37
|
-
"dashboard/index.html",
|
|
38
36
|
"skills/",
|
|
39
37
|
"scripts/setup-bm.sh",
|
|
40
38
|
"openclaw.plugin.json",
|
package/dashboard/index.html
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Memory Dashboard</title>
|
|
7
|
-
<style>
|
|
8
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
-
body { background: #0a0a0a; color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
10
|
-
code, .mono { font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; }
|
|
11
|
-
|
|
12
|
-
/* Stats Bar */
|
|
13
|
-
.stats-bar {
|
|
14
|
-
display: flex; gap: 24px; padding: 16px 24px;
|
|
15
|
-
background: #111; border-bottom: 1px solid #222;
|
|
16
|
-
}
|
|
17
|
-
.stat { text-align: center; }
|
|
18
|
-
.stat-value { font-size: 28px; font-weight: 700; }
|
|
19
|
-
.stat-label { font-size: 12px; color: #888; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
20
|
-
.stat-active .stat-value { color: #3b82f6; }
|
|
21
|
-
.stat-done .stat-value { color: #22c55e; }
|
|
22
|
-
.stat-explore .stat-value { color: #a855f7; }
|
|
23
|
-
.stat-total .stat-value { color: #e0e0e0; }
|
|
24
|
-
|
|
25
|
-
/* Layout */
|
|
26
|
-
.main { display: flex; height: calc(100vh - 70px); }
|
|
27
|
-
.kanban-area { flex: 1; overflow-x: auto; padding: 16px; }
|
|
28
|
-
.sidebar { width: 320px; border-left: 1px solid #222; padding: 16px; overflow-y: auto; flex-shrink: 0; }
|
|
29
|
-
|
|
30
|
-
/* Kanban */
|
|
31
|
-
.kanban { display: flex; gap: 12px; height: 100%; }
|
|
32
|
-
.column { flex: 1; min-width: 220px; background: #111; border-radius: 8px; display: flex; flex-direction: column; }
|
|
33
|
-
.column-header { padding: 12px; font-weight: 600; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 2px solid; }
|
|
34
|
-
.col-active .column-header { border-color: #3b82f6; color: #3b82f6; }
|
|
35
|
-
.col-blocked .column-header { border-color: #ef4444; color: #ef4444; }
|
|
36
|
-
.col-done .column-header { border-color: #22c55e; color: #22c55e; }
|
|
37
|
-
.col-abandoned .column-header { border-color: #6b7280; color: #6b7280; }
|
|
38
|
-
.column-body { padding: 8px; overflow-y: auto; flex: 1; }
|
|
39
|
-
|
|
40
|
-
/* Cards */
|
|
41
|
-
.card {
|
|
42
|
-
background: #1a1a1a; border: 1px solid #333; border-radius: 6px;
|
|
43
|
-
padding: 10px; margin-bottom: 8px; cursor: pointer; transition: border-color 0.15s;
|
|
44
|
-
}
|
|
45
|
-
.card:hover { border-color: #555; }
|
|
46
|
-
.card-title { font-size: 13px; font-weight: 600; margin-bottom: 6px; word-break: break-word; }
|
|
47
|
-
.card-meta { font-size: 11px; color: #888; font-family: 'SF Mono', monospace; }
|
|
48
|
-
.card-meta span { margin-right: 10px; }
|
|
49
|
-
.card-detail { display: none; margin-top: 8px; font-size: 12px; color: #aaa; border-top: 1px solid #333; padding-top: 8px; }
|
|
50
|
-
.card.expanded .card-detail { display: block; }
|
|
51
|
-
|
|
52
|
-
/* Sidebar */
|
|
53
|
-
.sidebar h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 12px; }
|
|
54
|
-
.activity-item {
|
|
55
|
-
padding: 8px 0; border-bottom: 1px solid #1a1a1a; font-size: 13px;
|
|
56
|
-
}
|
|
57
|
-
.activity-title { font-weight: 500; }
|
|
58
|
-
.activity-time { font-size: 11px; color: #666; font-family: 'SF Mono', monospace; }
|
|
59
|
-
|
|
60
|
-
/* Refresh indicator */
|
|
61
|
-
.refresh { position: fixed; top: 8px; right: 8px; font-size: 11px; color: #444; }
|
|
62
|
-
|
|
63
|
-
@media (max-width: 900px) {
|
|
64
|
-
.main { flex-direction: column; }
|
|
65
|
-
.sidebar { width: 100%; border-left: none; border-top: 1px solid #222; max-height: 300px; }
|
|
66
|
-
.kanban { flex-wrap: wrap; }
|
|
67
|
-
.column { min-width: 180px; }
|
|
68
|
-
}
|
|
69
|
-
</style>
|
|
70
|
-
</head>
|
|
71
|
-
<body>
|
|
72
|
-
|
|
73
|
-
<div class="stats-bar" id="stats-bar">
|
|
74
|
-
<div class="stat stat-total"><div class="stat-value" id="stat-total">-</div><div class="stat-label">Total Notes</div></div>
|
|
75
|
-
<div class="stat stat-active"><div class="stat-value" id="stat-active">-</div><div class="stat-label">Active Tasks</div></div>
|
|
76
|
-
<div class="stat stat-done"><div class="stat-value" id="stat-done">-</div><div class="stat-label">Completed</div></div>
|
|
77
|
-
<div class="stat stat-explore"><div class="stat-value" id="stat-explore">-</div><div class="stat-label">Explorations</div></div>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<div class="main">
|
|
81
|
-
<div class="kanban-area">
|
|
82
|
-
<div class="kanban">
|
|
83
|
-
<div class="column col-active"><div class="column-header">Active <span class="col-count"></span></div><div class="column-body" id="col-active"></div></div>
|
|
84
|
-
<div class="column col-blocked"><div class="column-header">Blocked <span class="col-count"></span></div><div class="column-body" id="col-blocked"></div></div>
|
|
85
|
-
<div class="column col-done"><div class="column-header">Done <span class="col-count"></span></div><div class="column-body" id="col-done"></div></div>
|
|
86
|
-
<div class="column col-abandoned"><div class="column-header">Abandoned <span class="col-count"></span></div><div class="column-body" id="col-abandoned"></div></div>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
<div class="sidebar">
|
|
90
|
-
<h2>Activity Feed</h2>
|
|
91
|
-
<div id="activity-feed"></div>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
<div class="refresh" id="refresh">⏳</div>
|
|
96
|
-
|
|
97
|
-
<script>
|
|
98
|
-
const API = '';
|
|
99
|
-
|
|
100
|
-
function makeCard(task) {
|
|
101
|
-
const fm = task.frontmatter || {};
|
|
102
|
-
const status = (fm.status || 'active').toLowerCase();
|
|
103
|
-
const step = fm.current_step || '?';
|
|
104
|
-
const total = fm.total_steps || '?';
|
|
105
|
-
const assigned = fm.assigned_to || '';
|
|
106
|
-
const div = document.createElement('div');
|
|
107
|
-
div.className = 'card';
|
|
108
|
-
div.innerHTML = `
|
|
109
|
-
<div class="card-title">${esc(task.title)}</div>
|
|
110
|
-
<div class="card-meta">
|
|
111
|
-
${assigned ? `<span>👤 ${esc(assigned)}</span>` : ''}
|
|
112
|
-
<span>📊 ${esc(String(step))}/${esc(String(total))}</span>
|
|
113
|
-
</div>
|
|
114
|
-
<div class="card-detail mono">${esc(task.content || '').slice(0, 300)}</div>
|
|
115
|
-
`;
|
|
116
|
-
div.onclick = () => div.classList.toggle('expanded');
|
|
117
|
-
return { el: div, status };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
121
|
-
|
|
122
|
-
async function fetchJson(url) {
|
|
123
|
-
try { const r = await fetch(url); return r.ok ? r.json() : null; } catch { return null; }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function refresh() {
|
|
127
|
-
document.getElementById('refresh').textContent = '🔄';
|
|
128
|
-
|
|
129
|
-
const [tasks, activity, stats] = await Promise.all([
|
|
130
|
-
fetchJson(`${API}/api/tasks`),
|
|
131
|
-
fetchJson(`${API}/api/activity`),
|
|
132
|
-
fetchJson(`${API}/api/stats`),
|
|
133
|
-
]);
|
|
134
|
-
|
|
135
|
-
// Stats
|
|
136
|
-
if (stats) {
|
|
137
|
-
document.getElementById('stat-total').textContent = stats.totalNotes;
|
|
138
|
-
document.getElementById('stat-active').textContent = stats.activeTasks;
|
|
139
|
-
document.getElementById('stat-done').textContent = stats.completedTasks;
|
|
140
|
-
document.getElementById('stat-explore').textContent = stats.explorations;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Kanban
|
|
144
|
-
const cols = { active: [], blocked: [], done: [], abandoned: [] };
|
|
145
|
-
if (tasks) {
|
|
146
|
-
for (const t of tasks) {
|
|
147
|
-
const { el, status } = makeCard(t);
|
|
148
|
-
const bucket = cols[status] ? status : 'active';
|
|
149
|
-
cols[bucket].push(el);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
for (const [key, items] of Object.entries(cols)) {
|
|
153
|
-
const container = document.getElementById(`col-${key}`);
|
|
154
|
-
container.innerHTML = '';
|
|
155
|
-
for (const el of items) container.appendChild(el);
|
|
156
|
-
container.parentElement.querySelector('.col-count').textContent = `(${items.length})`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Activity
|
|
160
|
-
const feed = document.getElementById('activity-feed');
|
|
161
|
-
feed.innerHTML = '';
|
|
162
|
-
if (activity?.length) {
|
|
163
|
-
for (const a of activity.slice(0, 30)) {
|
|
164
|
-
const div = document.createElement('div');
|
|
165
|
-
div.className = 'activity-item';
|
|
166
|
-
const time = a.created_at ? new Date(a.created_at).toLocaleTimeString() : '';
|
|
167
|
-
div.innerHTML = `<div class="activity-title">${esc(a.title)}</div><div class="activity-time">${time}</div>`;
|
|
168
|
-
feed.appendChild(div);
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
feed.innerHTML = '<div style="color:#555">No recent activity</div>';
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
document.getElementById('refresh').textContent = '✓';
|
|
175
|
-
setTimeout(() => { document.getElementById('refresh').textContent = ''; }, 2000);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
refresh();
|
|
179
|
-
setInterval(refresh, 30000);
|
|
180
|
-
</script>
|
|
181
|
-
</body>
|
|
182
|
-
</html>
|
package/dashboard/server.ts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs"
|
|
2
|
-
import {
|
|
3
|
-
createServer,
|
|
4
|
-
type IncomingMessage,
|
|
5
|
-
type Server,
|
|
6
|
-
type ServerResponse,
|
|
7
|
-
} from "node:http"
|
|
8
|
-
import { join } from "node:path"
|
|
9
|
-
import type { BmClient, SearchResult } from "../bm-client.ts"
|
|
10
|
-
|
|
11
|
-
export interface DashboardServerOptions {
|
|
12
|
-
port: number
|
|
13
|
-
client: BmClient
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function createDashboardServer(options: DashboardServerOptions): Server {
|
|
17
|
-
const { client, port } = options
|
|
18
|
-
|
|
19
|
-
const indexHtml = readFileSync(
|
|
20
|
-
join(import.meta.dirname ?? __dirname, "index.html"),
|
|
21
|
-
"utf-8",
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
const server = createServer(
|
|
25
|
-
async (req: IncomingMessage, res: ServerResponse) => {
|
|
26
|
-
const url = new URL(req.url ?? "/", `http://localhost:${port}`)
|
|
27
|
-
const path = url.pathname
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
if (path === "/" && req.method === "GET") {
|
|
31
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" })
|
|
32
|
-
res.end(indexHtml)
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (path === "/api/tasks" && req.method === "GET") {
|
|
37
|
-
const results = await client.search("type:Task", 50, undefined, {
|
|
38
|
-
filters: { type: "Task" },
|
|
39
|
-
})
|
|
40
|
-
const tasks = await enrichWithFrontmatter(client, results)
|
|
41
|
-
json(res, tasks)
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (path === "/api/activity" && req.method === "GET") {
|
|
46
|
-
const results = await client.recentActivity("24h")
|
|
47
|
-
json(res, results)
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (path === "/api/explorations" && req.method === "GET") {
|
|
52
|
-
const results = await client.search(
|
|
53
|
-
"type:Exploration",
|
|
54
|
-
50,
|
|
55
|
-
undefined,
|
|
56
|
-
{
|
|
57
|
-
filters: { type: "Exploration" },
|
|
58
|
-
},
|
|
59
|
-
)
|
|
60
|
-
json(res, results)
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (path === "/api/notes/daily" && req.method === "GET") {
|
|
65
|
-
const today = new Date().toISOString().split("T")[0]
|
|
66
|
-
const results = await client.search(today, 5)
|
|
67
|
-
const daily = results.filter((r) => r.title.includes(today))
|
|
68
|
-
json(res, daily)
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (path === "/api/stats" && req.method === "GET") {
|
|
73
|
-
const [allNotes, tasks, explorations] = await Promise.all([
|
|
74
|
-
client.recentActivity("720h").catch(() => []),
|
|
75
|
-
client
|
|
76
|
-
.search("type:Task", 100, undefined, {
|
|
77
|
-
filters: { type: "Task" },
|
|
78
|
-
})
|
|
79
|
-
.catch(() => []),
|
|
80
|
-
client
|
|
81
|
-
.search("type:Exploration", 100, undefined, {
|
|
82
|
-
filters: { type: "Exploration" },
|
|
83
|
-
})
|
|
84
|
-
.catch(() => []),
|
|
85
|
-
])
|
|
86
|
-
|
|
87
|
-
const tasksWithFm = await enrichWithFrontmatter(client, tasks)
|
|
88
|
-
const active = tasksWithFm.filter(
|
|
89
|
-
(t) => t.frontmatter?.status === "active",
|
|
90
|
-
).length
|
|
91
|
-
const completed = tasksWithFm.filter(
|
|
92
|
-
(t) =>
|
|
93
|
-
t.frontmatter?.status === "done" ||
|
|
94
|
-
t.frontmatter?.status === "completed",
|
|
95
|
-
).length
|
|
96
|
-
|
|
97
|
-
json(res, {
|
|
98
|
-
totalNotes: allNotes.length,
|
|
99
|
-
activeTasks: active,
|
|
100
|
-
completedTasks: completed,
|
|
101
|
-
explorations: explorations.length,
|
|
102
|
-
})
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
res.writeHead(404, { "Content-Type": "application/json" })
|
|
107
|
-
res.end(JSON.stringify({ error: "not found" }))
|
|
108
|
-
} catch (err: unknown) {
|
|
109
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
110
|
-
res.writeHead(500, { "Content-Type": "application/json" })
|
|
111
|
-
res.end(JSON.stringify({ error: message }))
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
return server
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function json(res: ServerResponse, data: unknown): void {
|
|
120
|
-
res.writeHead(200, {
|
|
121
|
-
"Content-Type": "application/json",
|
|
122
|
-
"Access-Control-Allow-Origin": "*",
|
|
123
|
-
})
|
|
124
|
-
res.end(JSON.stringify(data))
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function enrichWithFrontmatter(
|
|
128
|
-
client: BmClient,
|
|
129
|
-
results: SearchResult[],
|
|
130
|
-
): Promise<
|
|
131
|
-
Array<SearchResult & { frontmatter?: Record<string, unknown> | null }>
|
|
132
|
-
> {
|
|
133
|
-
const enriched = await Promise.all(
|
|
134
|
-
results.map(async (r) => {
|
|
135
|
-
try {
|
|
136
|
-
const note = await client.readNote(r.permalink, {
|
|
137
|
-
includeFrontmatter: true,
|
|
138
|
-
})
|
|
139
|
-
return { ...r, frontmatter: note.frontmatter ?? null }
|
|
140
|
-
} catch {
|
|
141
|
-
return { ...r, frontmatter: null }
|
|
142
|
-
}
|
|
143
|
-
}),
|
|
144
|
-
)
|
|
145
|
-
return enriched
|
|
146
|
-
}
|