@basicmemory/openclaw-basic-memory 0.1.0-alpha.1

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.
@@ -0,0 +1,100 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { BmClient } from "../bm-client.ts"
4
+ import { log } from "../logger.ts"
5
+
6
+ export function registerSchemaValidateTool(
7
+ api: OpenClawPluginApi,
8
+ client: BmClient,
9
+ ): void {
10
+ api.registerTool(
11
+ {
12
+ name: "schema_validate",
13
+ label: "Schema Validate",
14
+ description:
15
+ "Validate notes against their Picoschema definitions. " +
16
+ "Validates a specific note by identifier, or all notes of a given type.",
17
+ parameters: Type.Object({
18
+ noteType: Type.Optional(
19
+ Type.String({
20
+ description:
21
+ 'Note type to batch-validate (e.g., "person", "meeting")',
22
+ }),
23
+ ),
24
+ identifier: Type.Optional(
25
+ Type.String({
26
+ description:
27
+ "Specific note to validate (permalink, title, or path)",
28
+ }),
29
+ ),
30
+ project: Type.Optional(
31
+ Type.String({
32
+ description: "Target project name (defaults to current project)",
33
+ }),
34
+ ),
35
+ }),
36
+ async execute(
37
+ _toolCallId: string,
38
+ params: { noteType?: string; identifier?: string; project?: string },
39
+ ) {
40
+ log.debug(
41
+ `schema_validate: noteType="${params.noteType ?? ""}" identifier="${params.identifier ?? ""}"`,
42
+ )
43
+
44
+ try {
45
+ const result = await client.schemaValidate(
46
+ params.noteType,
47
+ params.identifier,
48
+ params.project,
49
+ )
50
+
51
+ // Handle error responses from BM (e.g., no schema found)
52
+ const resultRecord = result as unknown as Record<string, unknown>
53
+ if ("error" in result && typeof resultRecord.error === "string") {
54
+ return {
55
+ content: [{ type: "text" as const, text: resultRecord.error }],
56
+ }
57
+ }
58
+
59
+ const lines: string[] = []
60
+ if (result.entity_type) {
61
+ lines.push(`**Type:** ${result.entity_type}`)
62
+ }
63
+ lines.push(
64
+ `**Notes:** ${result.total_notes ?? 0} | **Valid:** ${result.valid_count ?? 0} | **Warnings:** ${result.warning_count ?? 0} | **Errors:** ${result.error_count ?? 0}`,
65
+ )
66
+
67
+ if (result.results && result.results.length > 0) {
68
+ lines.push("")
69
+ for (const r of result.results) {
70
+ const status = r.valid ? "valid" : "invalid"
71
+ lines.push(`- **${r.identifier}** — ${status}`)
72
+ for (const w of r.warnings) {
73
+ lines.push(` - warning: ${w}`)
74
+ }
75
+ for (const e of r.errors) {
76
+ lines.push(` - error: ${e}`)
77
+ }
78
+ }
79
+ }
80
+
81
+ return {
82
+ content: [{ type: "text" as const, text: lines.join("\n") }],
83
+ details: result,
84
+ }
85
+ } catch (err) {
86
+ log.error("schema_validate failed", err)
87
+ return {
88
+ content: [
89
+ {
90
+ type: "text" as const,
91
+ text: "Schema validation failed. Check logs for details.",
92
+ },
93
+ ],
94
+ }
95
+ }
96
+ },
97
+ },
98
+ { name: "schema_validate" },
99
+ )
100
+ }
@@ -0,0 +1,130 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { BmClient } from "../bm-client.ts"
4
+ import { log } from "../logger.ts"
5
+
6
+ export function registerSearchTool(
7
+ api: OpenClawPluginApi,
8
+ client: BmClient,
9
+ ): void {
10
+ api.registerTool(
11
+ {
12
+ name: "search_notes",
13
+ label: "Knowledge Search",
14
+ description:
15
+ "Search the Basic Memory knowledge graph for relevant notes, concepts, and connections. " +
16
+ "Returns matching notes with titles, content previews, and relevance scores. " +
17
+ "Optionally filter by frontmatter metadata fields, tags, or status.",
18
+ parameters: Type.Object({
19
+ query: Type.String({ description: "Search query" }),
20
+ limit: Type.Optional(
21
+ Type.Number({ description: "Max results (default: 10)" }),
22
+ ),
23
+ project: Type.Optional(
24
+ Type.String({
25
+ description: "Target project name (defaults to current project)",
26
+ }),
27
+ ),
28
+ metadata_filters: Type.Optional(
29
+ Type.Record(Type.String(), Type.Unknown(), {
30
+ description:
31
+ "Filter by frontmatter fields. Supports equality, $in, $gt/$gte/$lt/$lte, $between, and array-contains operators.",
32
+ }),
33
+ ),
34
+ tags: Type.Optional(
35
+ Type.Array(Type.String(), {
36
+ description: "Filter by frontmatter tags (all must match)",
37
+ }),
38
+ ),
39
+ status: Type.Optional(
40
+ Type.String({
41
+ description: "Filter by frontmatter status field",
42
+ }),
43
+ ),
44
+ }),
45
+ async execute(
46
+ _toolCallId: string,
47
+ params: {
48
+ query: string
49
+ limit?: number
50
+ project?: string
51
+ metadata_filters?: Record<string, unknown>
52
+ tags?: string[]
53
+ status?: string
54
+ },
55
+ ) {
56
+ const limit = params.limit ?? 10
57
+ log.debug(
58
+ `search_notes: query="${params.query}" limit=${limit} project="${params.project ?? "default"}"`,
59
+ )
60
+
61
+ const metadata =
62
+ params.metadata_filters || params.tags || params.status
63
+ ? {
64
+ filters: params.metadata_filters,
65
+ tags: params.tags,
66
+ status: params.status,
67
+ }
68
+ : undefined
69
+
70
+ try {
71
+ const results = await client.search(
72
+ params.query,
73
+ limit,
74
+ params.project,
75
+ metadata,
76
+ )
77
+
78
+ if (results.length === 0) {
79
+ return {
80
+ content: [
81
+ {
82
+ type: "text" as const,
83
+ text: "No matching notes found in the knowledge graph.",
84
+ },
85
+ ],
86
+ }
87
+ }
88
+
89
+ const text = results
90
+ .map((r, i) => {
91
+ const score = r.score ? ` (${(r.score * 100).toFixed(0)}%)` : ""
92
+ const content = r.content ?? ""
93
+ const preview =
94
+ content.length > 200 ? `${content.slice(0, 200)}...` : content
95
+ return `${i + 1}. **${r.title}**${score}\n ${preview}`
96
+ })
97
+ .join("\n\n")
98
+
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text" as const,
103
+ text: `Found ${results.length} notes:\n\n${text}`,
104
+ },
105
+ ],
106
+ details: {
107
+ count: results.length,
108
+ results: results.map((r) => ({
109
+ title: r.title,
110
+ permalink: r.permalink,
111
+ score: r.score,
112
+ })),
113
+ },
114
+ }
115
+ } catch (err) {
116
+ log.error("search_notes failed", err)
117
+ return {
118
+ content: [
119
+ {
120
+ type: "text" as const,
121
+ text: "Search failed. Is Basic Memory running? Check logs for details.",
122
+ },
123
+ ],
124
+ }
125
+ }
126
+ },
127
+ },
128
+ { name: "search_notes" },
129
+ )
130
+ }
@@ -0,0 +1,78 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { BmClient } from "../bm-client.ts"
4
+ import { log } from "../logger.ts"
5
+
6
+ export function registerWriteTool(
7
+ api: OpenClawPluginApi,
8
+ client: BmClient,
9
+ ): void {
10
+ api.registerTool(
11
+ {
12
+ name: "write_note",
13
+ label: "Write Note",
14
+ description:
15
+ "Create or update a note in the Basic Memory knowledge graph. " +
16
+ "Notes are stored as Markdown files with semantic structure " +
17
+ "(observations, relations) that build a connected knowledge graph.",
18
+ parameters: Type.Object({
19
+ title: Type.String({ description: "Note title" }),
20
+ content: Type.String({
21
+ description: "Note content in Markdown format",
22
+ }),
23
+ folder: Type.String({ description: "Folder to write the note in" }),
24
+ project: Type.Optional(
25
+ Type.String({
26
+ description: "Target project name (defaults to current project)",
27
+ }),
28
+ ),
29
+ }),
30
+ async execute(
31
+ _toolCallId: string,
32
+ params: {
33
+ title: string
34
+ content: string
35
+ folder: string
36
+ project?: string
37
+ },
38
+ ) {
39
+ log.debug(`write_note: title=${params.title} folder=${params.folder}`)
40
+
41
+ try {
42
+ const note = await client.writeNote(
43
+ params.title,
44
+ params.content,
45
+ params.folder,
46
+ params.project,
47
+ )
48
+
49
+ const msg = `Note saved: ${note.title} (${note.permalink})`
50
+ return {
51
+ content: [
52
+ {
53
+ type: "text" as const,
54
+ text: msg,
55
+ },
56
+ ],
57
+ details: {
58
+ title: note.title,
59
+ permalink: note.permalink,
60
+ file_path: note.file_path,
61
+ },
62
+ }
63
+ } catch (err) {
64
+ log.error("write_note failed", err)
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text" as const,
69
+ text: "Failed to write note. Is Basic Memory running? Check logs for details.",
70
+ },
71
+ ],
72
+ }
73
+ }
74
+ },
75
+ },
76
+ { name: "write_note" },
77
+ )
78
+ }
@@ -0,0 +1,24 @@
1
+ declare module "openclaw/plugin-sdk" {
2
+ export interface OpenClawPluginApi {
3
+ pluginConfig: unknown
4
+ logger: {
5
+ info: (msg: string) => void
6
+ warn: (msg: string) => void
7
+ error: (msg: string, ...args: unknown[]) => void
8
+ debug: (msg: string) => void
9
+ }
10
+ // biome-ignore lint/suspicious/noExplicitAny: openclaw SDK does not ship types
11
+ registerTool(tool: any, options: any): void
12
+ // biome-ignore lint/suspicious/noExplicitAny: openclaw SDK does not ship types
13
+ registerCommand(command: any): void
14
+ // biome-ignore lint/suspicious/noExplicitAny: openclaw SDK does not ship types
15
+ registerCli(handler: any, options?: any): void
16
+ // biome-ignore lint/suspicious/noExplicitAny: openclaw SDK does not ship types
17
+ registerService(service: any): void
18
+ // biome-ignore lint/suspicious/noExplicitAny: openclaw SDK does not ship types
19
+ on(event: string, handler: (...args: any[]) => any): void
20
+ }
21
+
22
+ // biome-ignore lint/suspicious/noExplicitAny: openclaw SDK does not ship types
23
+ export function stringEnum(values: readonly string[]): any
24
+ }