@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 CHANGED
@@ -1,131 +1,66 @@
1
1
  # openclaw-basic-memory
2
2
 
3
- Local-first knowledge graph plugin for OpenClaw persistent memory with graph search and composited memory search. Everything works locally with no cloud account required.
3
+ Give your OpenClaw agent persistent, searchable memory in plain text files you can read and edit.
4
4
 
5
- ## What this plugin does
5
+ ## What is Basic Memory?
6
6
 
7
- The `openclaw-basic-memory` plugin integrates [Basic Memory](https://github.com/basicmachines-co/basic-memory) with OpenClaw to provide:
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
- - **Composited `memory_search`** queries MEMORY.md, the BM knowledge graph, and active tasks in parallel
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
- All data stays on your machine as markdown files indexed locally with SQLite. Cloud sync is available but entirely optional see [BASIC_MEMORY.md](./BASIC_MEMORY.md) for cloud setup.
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
- For a practical runbook, see [Memory + Task Flow](./MEMORY_TASK_FLOW.md).
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
- ## Requirements
19
+ Learn more: [basicmemory.com](https://basicmemory.com) · [GitHub](https://github.com/basicmachines-co/basic-memory) · [Docs](https://docs.basicmemory.com)
21
20
 
22
- 1. **[uv](https://docs.astral.sh/uv/)** Python package manager used to install Basic Memory CLI
23
- ```bash
24
- # macOS
25
- brew install uv
21
+ ## What this plugin does
26
22
 
27
- # macOS / Linux
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
- 2. **[OpenClaw](https://docs.openclaw.ai)** with plugin support
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
- ## Installation
30
+ All data stays on your machine as Markdown files indexed locally with SQLite. Cloud sync is available but entirely optional.
34
31
 
35
- ```bash
36
- # Install the plugin (automatically installs the bm CLI via uv)
37
- openclaw plugins install @basicmemory/openclaw-basic-memory
32
+ ## Install
38
33
 
39
- # Restart the gateway
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
- openclaw plugins list
46
- openclaw plugins info openclaw-basic-memory
47
- ```
37
+ # macOS
38
+ brew install uv
48
39
 
49
- If `uv` is not installed, the `bm` CLI setup is skipped gracefully during install. Install `uv` first, then re-run the postinstall script:
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
- ### Basic Memory Cloud
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
- git clone https://github.com/basicmachines-co/openclaw-basic-memory.git
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
- Or load directly from a path in your OpenClaw config:
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
- You can update bundled skills or install new ones as they become available using the [Skills CLI](https://github.com/vercel-labs/skills):
53
+ Verify:
115
54
 
116
55
  ```bash
117
- # Update all basic-memory skills to latest
118
- npx skills add basicmachines-co/basic-memory-skills --agent openclaw
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
- ### Minimal (zero-config)
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 Basic Memory to your workspace root, sets it as the default BM project, and captures conversations.
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", // BM project name (default: "openclaw-{hostname}")
146
- bmPath: "bm", // Path to BM CLI binary
147
- projectPath: ".", // Defaults to workspace root; supports absolute, ~/..., or workspace-relative paths
148
- memoryDir: "memory/", // Relative memory dir for task scanning
149
- memoryFile: "MEMORY.md", // Working memory file for grep search
150
- autoCapture: true, // Index conversations automatically
151
- captureMinChars: 10, // Min chars to trigger auto-capture
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
- ### Configuration Options
161
-
162
- | Option | Type | Default | Description |
163
- |--------|------|---------|-------------|
164
- | `project` | string | `"openclaw-{hostname}"` | Basic Memory project name |
165
- | `bmPath` | string | `"bm"` | Path to Basic Memory CLI binary |
166
- | `projectPath` | string | `"."` | Directory for BM project data (defaults to workspace root; resolved from workspace unless absolute) |
167
- | `memoryDir` | string | `"memory/"` | Relative path for task scanning |
168
- | `memoryFile` | string | `"MEMORY.md"` | Working memory file (grep-searched) |
169
- | `autoCapture` | boolean | `true` | Auto-index agent conversations |
170
- | `captureMinChars` | number | `10` | Minimum character threshold for auto-capture (both messages must be shorter to skip) |
171
- | `autoRecall` | boolean | `true` | Inject active tasks and recent activity as context at session start |
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
- ## Active Tasks
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
- - file location: `memory/tasks/*.md`
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
- ## Context
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
- ## Plan
270
- - [x] Implement middleware
271
- - [x] Add refresh-token validation
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
- To mark complete, update:
116
+ Results come back in clear sections so the agent knows where each piece of context came from.
277
117
 
278
- ```yaml
279
- status: done
280
- ```
118
+ ### Auto-recall
281
119
 
282
- Done tasks are filtered out of the `Active Tasks` section in composited `memory_search`.
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-Recall
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
- This gives the agent immediate awareness of ongoing work and recent changes without the user needing to ask. The `recallPrompt` config field controls the instruction appended to the context — customize it to steer what the agent prioritizes.
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
- ### Auto-Capture
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
- ## Agent Tools
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
- All content tools accept an optional `project` parameter to operate on a different project than the default (cross-project operations).
130
+ ## Agent tools
301
131
 
302
- ### `list_workspaces`
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
- ### `list_memory_projects`
309
- List all projects, optionally filtered by workspace.
310
- ```
311
- list_memory_projects()
312
- list_memory_projects(workspace="my-org")
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
- ### `search_notes`
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
- | `/tasks [args]` | Task management create, track, resume structured tasks |
409
- | `/reflect [args]` | Memory reflection consolidate recent activity into long-term memory |
410
- | `/defrag [args]` | Memory defrag — reorganize, split, prune memory files |
411
- | `/schema [args]` | Schema management infer, create, validate, evolve Picoschema definitions |
412
-
413
- Examples:
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 Commands
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
- ## Troubleshooting
172
+ ## Bundled skills
434
173
 
435
- ### `bm` command not found
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
- ### `edit_note` says `edit-note` is required
444
- Your installed `basic-memory` version is missing native `tool edit-note`.
445
- Upgrade `basic-memory` and rerun.
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
- ### Jiti cache issues
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
- BASIC_MEMORY_SEMANTIC_SEARCH_ENABLED=false
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
- By default this uses `./scripts/bm-local.sh`, which runs BM from a sibling
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
- Optional overrides:
191
+ The plugin works well with structured task notes in `memory/tasks/`:
490
192
 
491
- ```bash
492
- # Use a non-default bm binary
493
- BM_BIN=/absolute/path/to/bm bun run test:int
494
-
495
- # Use a specific basic-memory source checkout
496
- BASIC_MEMORY_REPO=/absolute/path/to/basic-memory bun run test:int
497
- ```
193
+ ```markdown
194
+ ---
195
+ title: auth-middleware-rollout
196
+ type: Task
197
+ status: active
198
+ current_step: 2
199
+ ---
498
200
 
499
- ## Development
201
+ ## Context
202
+ Rolling JWT middleware to all API routes.
500
203
 
501
- ```bash
502
- bun run check-types # Type checking
503
- bun run lint # Linting
504
- bun test # Run tests (156 tests)
505
- bun run test:int # Real BM MCP integration tests
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
- ## Publish to npm
211
+ Set `status: done` to mark complete. Done tasks are filtered out of active task results.
509
212
 
510
- This package is published as `@basicmemory/openclaw-basic-memory`.
213
+ ## Basic Memory Cloud
511
214
 
512
- ```bash
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
- # 2) Inspect publish payload
517
- just release-pack
217
+ - Same knowledge graph on laptop, desktop, and CI
218
+ - Shared workspaces for teams
219
+ - Durable memory for production agents
518
220
 
519
- # 3) Authenticate once (if needed)
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
- # 4) Publish current version from package.json
523
- just release-publish
524
- ```
223
+ ## Troubleshooting
525
224
 
526
- For a full release (version bump + publish + push tag):
225
+ **`bm` not found** Install uv, then restart the gateway. Or install manually: `uv tool install basic-memory`
527
226
 
528
- ```bash
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
- ### GitHub Actions CI/CD
229
+ **Jiti cache issues** — `rm -rf /tmp/jiti/ "$TMPDIR/jiti/"` then restart the gateway.
533
230
 
534
- - CI workflow: `.github/workflows/ci.yml` runs on PRs and `main` pushes.
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
- Publishing uses npm OIDC trusted publishing — no secrets required. The trusted publisher is configured on npmjs.com to accept provenance from this repo's `release.yml` workflow.
233
+ ## More
543
234
 
544
- ### Project Structure
545
- ```
546
- openclaw-basic-memory/
547
- ├── index.ts # Plugin entry manages MCP lifecycle, registers tools
548
- ├── config.ts # Configuration parsing
549
- ├── bm-client.ts # Persistent Basic Memory MCP stdio client
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 itself does not collect any telemetry. However, the **Basic Memory CLI** (`bm`) that the plugin spawns may send anonymous usage analytics. See the [Basic Memory documentation](https://github.com/basicmachines-co/basic-memory) for details.
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 — see LICENSE file.
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
- import type { Server } from "node:http"
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
- import { createDashboardServer } from "./dashboard/server.ts"
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
  },
@@ -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.12",
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",
@@ -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>
@@ -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
- }