@basicmemory/openclaw-basic-memory 0.1.2 → 0.1.4
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 +1 -0
- package/commands/skills.ts +25 -25
- package/commands/slash.ts +32 -0
- package/openclaw.plugin.json +7 -4
- package/package.json +4 -1
- package/schema/task-schema.ts +10 -10
- package/scripts/fetch-skills.ts +133 -0
- package/skills/manifest.json +47 -0
- package/skills/memory-defrag/SKILL.md +0 -1
- package/skills/memory-ingest/SKILL.md +309 -0
- package/skills/memory-lifecycle/SKILL.md +182 -0
- package/skills/memory-metadata-search/SKILL.md +37 -63
- package/skills/memory-notes/SKILL.md +111 -35
- package/skills/memory-reflect/SKILL.md +14 -4
- package/skills/memory-research/SKILL.md +235 -0
- package/skills/memory-schema/SKILL.md +35 -35
- package/skills/memory-tasks/SKILL.md +31 -27
package/README.md
CHANGED
|
@@ -152,6 +152,7 @@ All tools accept an optional `project` parameter for cross-project operations.
|
|
|
152
152
|
|
|
153
153
|
| Command | Description |
|
|
154
154
|
|---------|-------------|
|
|
155
|
+
| `/bm-setup` | Install or update the Basic Memory CLI |
|
|
155
156
|
| `/remember <text>` | Save a quick note |
|
|
156
157
|
| `/recall <query>` | Search the knowledge graph |
|
|
157
158
|
| `/tasks [args]` | Create, track, resume structured tasks |
|
package/commands/skills.ts
CHANGED
|
@@ -5,39 +5,39 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
|
5
5
|
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
7
|
const SKILLS_DIR = resolve(__dirname, "..", "skills")
|
|
8
|
+
const MANIFEST_PATH = resolve(SKILLS_DIR, "manifest.json")
|
|
9
|
+
|
|
10
|
+
interface ManifestEntry {
|
|
11
|
+
dir: string
|
|
12
|
+
name: string
|
|
13
|
+
description: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function loadManifest(): ManifestEntry[] {
|
|
17
|
+
try {
|
|
18
|
+
const raw = readFileSync(MANIFEST_PATH, "utf-8")
|
|
19
|
+
return JSON.parse(raw) as ManifestEntry[]
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"skills/manifest.json not found. Run `bun scripts/fetch-skills.ts` first.",
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
8
26
|
|
|
9
27
|
function loadSkill(dir: string): string {
|
|
10
28
|
return readFileSync(resolve(SKILLS_DIR, dir, "SKILL.md"), "utf-8")
|
|
11
29
|
}
|
|
12
30
|
|
|
13
|
-
const SKILLS = [
|
|
14
|
-
{ name: "tasks", dir: "memory-tasks", desc: "Task management workflow" },
|
|
15
|
-
{
|
|
16
|
-
name: "reflect",
|
|
17
|
-
dir: "memory-reflect",
|
|
18
|
-
desc: "Memory reflection workflow",
|
|
19
|
-
},
|
|
20
|
-
{ name: "defrag", dir: "memory-defrag", desc: "Memory defrag workflow" },
|
|
21
|
-
{ name: "schema", dir: "memory-schema", desc: "Schema management workflow" },
|
|
22
|
-
{
|
|
23
|
-
name: "notes",
|
|
24
|
-
dir: "memory-notes",
|
|
25
|
-
desc: "How to write well-structured notes",
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: "metadata-search",
|
|
29
|
-
dir: "memory-metadata-search",
|
|
30
|
-
desc: "Structured metadata search workflow",
|
|
31
|
-
},
|
|
32
|
-
] as const
|
|
33
|
-
|
|
34
31
|
export function registerSkillCommands(api: OpenClawPluginApi): void {
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const manifest = loadManifest()
|
|
33
|
+
|
|
34
|
+
for (const entry of manifest) {
|
|
35
|
+
const commandName = entry.dir.replace(/^memory-/, "")
|
|
36
|
+
const content = loadSkill(entry.dir)
|
|
37
37
|
|
|
38
38
|
api.registerCommand({
|
|
39
|
-
name:
|
|
40
|
-
description:
|
|
39
|
+
name: commandName,
|
|
40
|
+
description: entry.description,
|
|
41
41
|
acceptsArgs: true,
|
|
42
42
|
requireAuth: true,
|
|
43
43
|
handler: async (ctx: { args?: string }) => {
|
package/commands/slash.ts
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
|
+
import { execSync } from "node:child_process"
|
|
2
|
+
import { dirname, resolve } from "node:path"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
1
4
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
2
5
|
import type { BmClient } from "../bm-client.ts"
|
|
3
6
|
import { log } from "../logger.ts"
|
|
4
7
|
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
|
|
5
10
|
export function registerCommands(
|
|
6
11
|
api: OpenClawPluginApi,
|
|
7
12
|
client: BmClient,
|
|
8
13
|
): void {
|
|
14
|
+
api.registerCommand({
|
|
15
|
+
name: "bm-setup",
|
|
16
|
+
description: "Install or update the Basic Memory CLI (requires uv)",
|
|
17
|
+
requireAuth: true,
|
|
18
|
+
handler: async () => {
|
|
19
|
+
const scriptPath = resolve(__dirname, "..", "scripts", "setup-bm.sh")
|
|
20
|
+
log.info(`/bm-setup: running ${scriptPath}`)
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const output = execSync(`bash "${scriptPath}"`, {
|
|
24
|
+
encoding: "utf-8",
|
|
25
|
+
timeout: 180_000,
|
|
26
|
+
stdio: "pipe",
|
|
27
|
+
env: { ...process.env },
|
|
28
|
+
})
|
|
29
|
+
return { text: output.trim() }
|
|
30
|
+
} catch (err: unknown) {
|
|
31
|
+
const execErr = err as { stderr?: string; stdout?: string }
|
|
32
|
+
const detail = execErr.stderr || execErr.stdout || String(err)
|
|
33
|
+
log.error("/bm-setup failed", err)
|
|
34
|
+
return {
|
|
35
|
+
text: `Setup failed:\n${detail.trim()}`,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
9
41
|
api.registerCommand({
|
|
10
42
|
name: "remember",
|
|
11
43
|
description: "Save something to the Basic Memory knowledge graph",
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
"id": "openclaw-basic-memory",
|
|
3
3
|
"kind": "memory",
|
|
4
4
|
"skills": [
|
|
5
|
-
"skills/memory-tasks",
|
|
6
|
-
"skills/memory-reflect",
|
|
7
5
|
"skills/memory-defrag",
|
|
8
|
-
"skills/memory-
|
|
6
|
+
"skills/memory-ingest",
|
|
7
|
+
"skills/memory-lifecycle",
|
|
9
8
|
"skills/memory-metadata-search",
|
|
10
|
-
"skills/memory-notes"
|
|
9
|
+
"skills/memory-notes",
|
|
10
|
+
"skills/memory-reflect",
|
|
11
|
+
"skills/memory-research",
|
|
12
|
+
"skills/memory-schema",
|
|
13
|
+
"skills/memory-tasks"
|
|
11
14
|
],
|
|
12
15
|
"uiHints": {
|
|
13
16
|
"project": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basicmemory/openclaw-basic-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Basic Memory plugin for OpenClaw — local-first knowledge graph for agent memory",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"schema/task-schema.ts",
|
|
36
36
|
"schema/conversation-schema.ts",
|
|
37
37
|
"skills/",
|
|
38
|
+
"scripts/fetch-skills.ts",
|
|
38
39
|
"scripts/setup-bm.sh",
|
|
39
40
|
"openclaw.plugin.json",
|
|
40
41
|
"README.md",
|
|
@@ -55,6 +56,8 @@
|
|
|
55
56
|
"check-types": "tsc --noEmit",
|
|
56
57
|
"lint": "bunx @biomejs/biome ci .",
|
|
57
58
|
"lint:fix": "bunx @biomejs/biome check --write .",
|
|
59
|
+
"fetch-skills": "bun scripts/fetch-skills.ts",
|
|
60
|
+
"prepack": "bun scripts/fetch-skills.ts",
|
|
58
61
|
"test": "bun test",
|
|
59
62
|
"test:int": "BM_INTEGRATION=1 BM_BIN=${BM_BIN:-./scripts/bm-local.sh} bun test integration --timeout 120000",
|
|
60
63
|
"test:coverage": "bun test --coverage",
|
package/schema/task-schema.ts
CHANGED
|
@@ -8,16 +8,16 @@ type: schema
|
|
|
8
8
|
entity: Task
|
|
9
9
|
version: 1
|
|
10
10
|
schema:
|
|
11
|
-
description: string, what needs to be done
|
|
12
|
-
status?(enum): [active, blocked, done, abandoned], current state
|
|
13
|
-
assigned_to?: string, who is working on this
|
|
14
|
-
steps?(array): string, ordered steps to complete
|
|
15
|
-
current_step?: integer, which step number we're on (1-indexed)
|
|
16
|
-
context?: string, key context needed to resume after memory loss
|
|
17
|
-
started?: string, when work began
|
|
18
|
-
completed?: string, when work finished
|
|
19
|
-
blockers?(array): string, what's preventing progress
|
|
20
|
-
parent_task?: Task, parent task if this is a subtask
|
|
11
|
+
description: "string, what needs to be done"
|
|
12
|
+
status?(enum): "[active, blocked, done, abandoned], current state"
|
|
13
|
+
assigned_to?: "string, who is working on this"
|
|
14
|
+
steps?(array): "string, ordered steps to complete"
|
|
15
|
+
current_step?: "integer, which step number we're on (1-indexed)"
|
|
16
|
+
context?: "string, key context needed to resume after memory loss"
|
|
17
|
+
started?: "string, when work began"
|
|
18
|
+
completed?: "string, when work finished"
|
|
19
|
+
blockers?(array): "string, what's preventing progress"
|
|
20
|
+
parent_task?: "Task, parent task if this is a subtask"
|
|
21
21
|
settings:
|
|
22
22
|
validation: warn
|
|
23
23
|
---
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch all memory-* skills from basicmachines-co/basic-memory-skills.
|
|
3
|
+
*
|
|
4
|
+
* Auto-discovers skill directories via GitHub Contents API, downloads each
|
|
5
|
+
* SKILL.md, writes to skills/<dir>/SKILL.md, and generates skills/manifest.json.
|
|
6
|
+
*
|
|
7
|
+
* Env vars:
|
|
8
|
+
* GITHUB_TOKEN — optional, avoids 60 req/hr unauthenticated rate limit
|
|
9
|
+
* SKILLS_BRANCH — branch to fetch from (default: "main")
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { mkdirSync, writeFileSync } from "node:fs"
|
|
13
|
+
import { dirname, resolve } from "node:path"
|
|
14
|
+
import { fileURLToPath } from "node:url"
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
17
|
+
const SKILLS_DIR = resolve(__dirname, "..", "skills")
|
|
18
|
+
|
|
19
|
+
const REPO = "basicmachines-co/basic-memory-skills"
|
|
20
|
+
const BRANCH = process.env.SKILLS_BRANCH ?? "main"
|
|
21
|
+
const TOKEN = process.env.GITHUB_TOKEN
|
|
22
|
+
|
|
23
|
+
interface GitHubEntry {
|
|
24
|
+
name: string
|
|
25
|
+
type: "file" | "dir"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface SkillManifestEntry {
|
|
29
|
+
dir: string
|
|
30
|
+
name: string
|
|
31
|
+
description: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function headers(): Record<string, string> {
|
|
35
|
+
const h: Record<string, string> = {
|
|
36
|
+
Accept: "application/vnd.github.v3+json",
|
|
37
|
+
"User-Agent": "openclaw-basic-memory/fetch-skills",
|
|
38
|
+
}
|
|
39
|
+
if (TOKEN) h.Authorization = `Bearer ${TOKEN}`
|
|
40
|
+
return h
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function fetchJSON<T>(url: string): Promise<T> {
|
|
44
|
+
const res = await fetch(url, { headers: headers() })
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
throw new Error(`GET ${url} → ${res.status} ${res.statusText}`)
|
|
47
|
+
}
|
|
48
|
+
return res.json() as Promise<T>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function fetchText(url: string): Promise<string> {
|
|
52
|
+
const res = await fetch(url, { headers: headers() })
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
throw new Error(`GET ${url} → ${res.status} ${res.statusText}`)
|
|
55
|
+
}
|
|
56
|
+
return res.text()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseFrontmatter(md: string): { name: string; description: string } {
|
|
60
|
+
const match = md.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
61
|
+
if (!match) throw new Error("SKILL.md missing YAML frontmatter")
|
|
62
|
+
|
|
63
|
+
const yaml = match[1]
|
|
64
|
+
const name = yaml
|
|
65
|
+
.match(/^name:\s*(.+)$/m)?.[1]
|
|
66
|
+
?.trim()
|
|
67
|
+
.replace(/^["']|["']$/g, "")
|
|
68
|
+
const description = yaml
|
|
69
|
+
.match(/^description:\s*(.+)$/m)?.[1]
|
|
70
|
+
?.trim()
|
|
71
|
+
.replace(/^["']|["']$/g, "")
|
|
72
|
+
|
|
73
|
+
if (!name) throw new Error("Frontmatter missing 'name'")
|
|
74
|
+
if (!description) throw new Error("Frontmatter missing 'description'")
|
|
75
|
+
|
|
76
|
+
return { name, description }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function main() {
|
|
80
|
+
console.log(`Fetching skills from ${REPO}@${BRANCH}`)
|
|
81
|
+
|
|
82
|
+
// 1. Discover all memory-* directories
|
|
83
|
+
const contentsUrl = `https://api.github.com/repos/${REPO}/contents?ref=${BRANCH}`
|
|
84
|
+
const entries = await fetchJSON<GitHubEntry[]>(contentsUrl)
|
|
85
|
+
const skillDirs = entries
|
|
86
|
+
.filter((e) => e.type === "dir" && e.name.startsWith("memory-"))
|
|
87
|
+
.map((e) => e.name)
|
|
88
|
+
.sort()
|
|
89
|
+
|
|
90
|
+
if (skillDirs.length === 0) {
|
|
91
|
+
throw new Error("No memory-* directories found in repo")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(`Found ${skillDirs.length} skills: ${skillDirs.join(", ")}`)
|
|
95
|
+
|
|
96
|
+
// 2. Download each SKILL.md and parse frontmatter
|
|
97
|
+
const manifest: SkillManifestEntry[] = []
|
|
98
|
+
|
|
99
|
+
const results = await Promise.all(
|
|
100
|
+
skillDirs.map(async (dir) => {
|
|
101
|
+
const rawUrl = `https://raw.githubusercontent.com/${REPO}/${BRANCH}/${dir}/SKILL.md`
|
|
102
|
+
const content = await fetchText(rawUrl)
|
|
103
|
+
const meta = parseFrontmatter(content)
|
|
104
|
+
return { dir, content, meta }
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
// 3. Write files and build manifest
|
|
109
|
+
for (const { dir, content, meta } of results) {
|
|
110
|
+
const outDir = resolve(SKILLS_DIR, dir)
|
|
111
|
+
mkdirSync(outDir, { recursive: true })
|
|
112
|
+
writeFileSync(resolve(outDir, "SKILL.md"), content)
|
|
113
|
+
manifest.push({ dir, name: meta.name, description: meta.description })
|
|
114
|
+
console.log(` ✓ ${dir}`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Sort manifest by dir name for deterministic output
|
|
118
|
+
manifest.sort((a, b) => a.dir.localeCompare(b.dir))
|
|
119
|
+
|
|
120
|
+
// 4. Write manifest
|
|
121
|
+
mkdirSync(SKILLS_DIR, { recursive: true })
|
|
122
|
+
writeFileSync(
|
|
123
|
+
resolve(SKILLS_DIR, "manifest.json"),
|
|
124
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
console.log(`\nWrote ${manifest.length} skills + manifest.json to skills/`)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
main().catch((err) => {
|
|
131
|
+
console.error("Fatal:", err.message)
|
|
132
|
+
process.exit(1)
|
|
133
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"dir": "memory-defrag",
|
|
4
|
+
"name": "memory-defrag",
|
|
5
|
+
"description": "Defragment and reorganize agent memory files: split bloated files, merge duplicates, remove stale information, and restructure the memory hierarchy. Use when memory files have grown unwieldy, contain redundancies, or need reorganization. Run periodically (weekly) or on demand."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"dir": "memory-ingest",
|
|
9
|
+
"name": "memory-ingest",
|
|
10
|
+
"description": "Process unstructured external input (meeting transcripts, conversation logs, pasted documents) into structured Basic Memory entities. Extracts entities, searches for existing matches, proposes new entities with approval, creates notes with observations and relations, and captures action items."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"dir": "memory-lifecycle",
|
|
14
|
+
"name": "memory-lifecycle",
|
|
15
|
+
"description": "Manage entity status transitions in Basic Memory: archive completed work, move notes between status folders, update frontmatter, and handle edge cases. Use when marking items complete, archiving old entities, or managing any folder-based status workflow."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"dir": "memory-metadata-search",
|
|
19
|
+
"name": "memory-metadata-search",
|
|
20
|
+
"description": "Structured metadata search for Basic Memory: query notes by custom frontmatter fields using equality, range, array, and nested filters. Use when finding notes by status, priority, confidence, or any custom YAML field rather than free-text content."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"dir": "memory-notes",
|
|
24
|
+
"name": "memory-notes",
|
|
25
|
+
"description": "How to write well-structured Basic Memory notes: frontmatter, observations with semantic categories, relations with wiki-links, and best practices for building a rich knowledge graph. Use when creating or improving notes."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"dir": "memory-reflect",
|
|
29
|
+
"name": "memory-reflect",
|
|
30
|
+
"description": "Sleep-time memory reflection: review recent conversations and daily notes, extract insights, and consolidate into long-term memory. Use when triggered by cron, heartbeat, or explicit request to reflect on recent activity. Runs as background processing to improve memory quality over time."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"dir": "memory-research",
|
|
34
|
+
"name": "memory-research",
|
|
35
|
+
"description": "Research an external subject using web search, synthesize findings into a structured Basic Memory entity. Use when asked to research a company, person, technology, or topic — or when a bare name or URL is provided that implies a research request."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"dir": "memory-schema",
|
|
39
|
+
"name": "memory-schema",
|
|
40
|
+
"description": "Schema lifecycle management for Basic Memory: discover unschemaed notes, infer schemas, create and edit schema definitions, validate notes, and detect drift. Use when working with structured note types (Task, Person, Meeting, etc.) to maintain consistency across the knowledge graph."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"dir": "memory-tasks",
|
|
44
|
+
"name": "memory-tasks",
|
|
45
|
+
"description": "Task management via Basic Memory schemas: create, track, and resume structured tasks that survive context compaction. Uses BM's schema system for uniform notes queryable through the knowledge graph."
|
|
46
|
+
}
|
|
47
|
+
]
|
|
@@ -78,7 +78,6 @@ After changes:
|
|
|
78
78
|
|
|
79
79
|
## Guidelines
|
|
80
80
|
|
|
81
|
-
- **Back up first.** If git is available, commit before defragging. If not, note the pre-defrag state.
|
|
82
81
|
- **Preserve raw daily notes.** Don't delete or modify `memory/YYYY-MM-DD.md` files — they're the audit trail.
|
|
83
82
|
- **Target 15-25 focused files.** Too few means bloated files; too many means fragmentation. Aim for the sweet spot.
|
|
84
83
|
- **File names should be scannable.** Use descriptive names: `people.md`, `project-status.md`, `competitive-landscape.md` — not `notes-2.md`.
|