@basicmemory/openclaw-basic-memory 0.2.4 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +11 -1
  2. package/bm-client.ts +55 -21
  3. package/commands/cli.ts +0 -1
  4. package/commands/skills.ts +51 -2
  5. package/config.ts +0 -17
  6. package/context-engine/basic-memory-context-engine.ts +25 -74
  7. package/dist/bm-client.d.ts +2 -0
  8. package/dist/bm-client.js +42 -32
  9. package/dist/commands/cli.js +0 -1
  10. package/dist/commands/skills.js +41 -2
  11. package/dist/config.d.ts +0 -5
  12. package/dist/config.js +0 -10
  13. package/dist/context-engine/basic-memory-context-engine.d.ts +13 -73
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.js +21 -11
  16. package/dist/tools/list-memory-projects.js +2 -1
  17. package/index.ts +23 -12
  18. package/openclaw.plugin.json +2 -15
  19. package/package.json +5 -4
  20. package/scripts/fetch-skills.ts +62 -67
  21. package/scripts/setup-bm.sh +27 -10
  22. package/skills/manifest.json +25 -0
  23. package/skills/memory-capture/SKILL.md +275 -0
  24. package/skills/memory-ci-capture/SKILL.md +56 -0
  25. package/skills/memory-continue/SKILL.md +148 -0
  26. package/skills/memory-curate/SKILL.md +243 -0
  27. package/skills/memory-literary-analysis/SKILL.md +15 -12
  28. package/skills/memory-notes/SKILL.md +22 -6
  29. package/skills/memory-onboarding/SKILL.md +142 -0
  30. package/skills/memory-onboarding/evals/evals.json +29 -0
  31. package/skills/memory-onboarding/references/assistant-setup.md +64 -0
  32. package/skills/memory-onboarding/references/conventions.md +118 -0
  33. package/skills/memory-onboarding/references/domain-playbooks.md +159 -0
  34. package/skills/memory-onboarding/references/schema-guide.md +136 -0
  35. package/skills/memory-schema/SKILL.md +7 -5
  36. package/skills/memory-tasks/SKILL.md +1 -1
  37. package/tools/list-memory-projects.ts +2 -1
package/dist/bm-client.js CHANGED
@@ -54,7 +54,7 @@ function isRecoverableConnectionError(err) {
54
54
  msg.includes("failed to start bm mcp stdio") ||
55
55
  msg.includes("client is closed"));
56
56
  }
57
- function _isNoteNotFoundError(err) {
57
+ function isNoteNotFoundError(err) {
58
58
  const msg = getErrorMessage(err).toLowerCase();
59
59
  return (msg.includes("entity not found") ||
60
60
  msg.includes("note not found") ||
@@ -65,6 +65,16 @@ function _isNoteNotFoundError(err) {
65
65
  function asString(value) {
66
66
  return typeof value === "string" ? value : null;
67
67
  }
68
+ function projectMatchesWorkspace(project, workspace) {
69
+ const requested = workspace.trim().toLowerCase();
70
+ if (!requested)
71
+ return true;
72
+ return [
73
+ project.workspace_name,
74
+ project.workspace_slug,
75
+ project.workspace_tenant_id,
76
+ ].some((value) => typeof value === "string" && value.toLowerCase() === requested);
77
+ }
68
78
  export class BmClient {
69
79
  bmPath;
70
80
  project;
@@ -259,6 +269,9 @@ export class BmClient {
259
269
  }
260
270
  return structuredPayload;
261
271
  }
272
+ routedProject(project) {
273
+ return project ?? this.project;
274
+ }
262
275
  async ensureProject(projectPath) {
263
276
  const payload = await this.callTool("create_memory_project", {
264
277
  project_name: this.project,
@@ -280,12 +293,15 @@ export class BmClient {
280
293
  throw new Error("invalid list_workspaces response");
281
294
  }
282
295
  async listProjects(workspace) {
283
- const args = { output_format: "json" };
284
- if (workspace)
285
- args.workspace = workspace;
286
- const payload = await this.callTool("list_memory_projects", args);
296
+ const payload = await this.callTool("list_memory_projects", {
297
+ output_format: "json",
298
+ });
287
299
  if (isRecord(payload) && Array.isArray(payload.projects)) {
288
- return payload.projects;
300
+ const projects = payload.projects;
301
+ if (workspace) {
302
+ return projects.filter((project) => projectMatchesWorkspace(project, workspace));
303
+ }
304
+ return projects;
289
305
  }
290
306
  throw new Error("invalid list_memory_projects response");
291
307
  }
@@ -294,11 +310,10 @@ export class BmClient {
294
310
  page: 1,
295
311
  page_size: limit,
296
312
  output_format: "json",
313
+ project: this.routedProject(project),
297
314
  };
298
315
  if (query)
299
316
  args.query = query;
300
- if (project)
301
- args.project = project;
302
317
  if (metadata?.filters)
303
318
  args.metadata_filters = metadata.filters;
304
319
  if (metadata?.tags)
@@ -320,9 +335,8 @@ export class BmClient {
320
335
  identifier,
321
336
  include_frontmatter: options.includeFrontmatter === true,
322
337
  output_format: "json",
338
+ project: this.routedProject(project),
323
339
  };
324
- if (project)
325
- args.project = project;
326
340
  const payload = await this.callTool("read_note", args);
327
341
  if (!isRecord(payload)) {
328
342
  throw new Error("invalid read_note response");
@@ -348,9 +362,8 @@ export class BmClient {
348
362
  content,
349
363
  directory: folder,
350
364
  output_format: "json",
365
+ project: this.routedProject(project),
351
366
  };
352
- if (project)
353
- args.project = project;
354
367
  if (overwrite !== undefined)
355
368
  args.overwrite = overwrite;
356
369
  const payload = await this.callTool("write_note", args);
@@ -382,9 +395,8 @@ export class BmClient {
382
395
  url,
383
396
  depth,
384
397
  output_format: "json",
398
+ project: this.routedProject(project),
385
399
  };
386
- if (project)
387
- args.project = project;
388
400
  const payload = await this.callTool("build_context", args);
389
401
  if (!isRecord(payload) || !Array.isArray(payload.results)) {
390
402
  throw new Error("invalid build_context response");
@@ -395,9 +407,8 @@ export class BmClient {
395
407
  const args = {
396
408
  timeframe,
397
409
  output_format: "json",
410
+ project: this.routedProject(project),
398
411
  };
399
- if (project)
400
- args.project = project;
401
412
  const payload = await this.callTool("recent_activity", args);
402
413
  if (Array.isArray(payload)) {
403
414
  return payload;
@@ -410,6 +421,7 @@ export class BmClient {
410
421
  operation,
411
422
  content,
412
423
  output_format: "json",
424
+ project: this.routedProject(project),
413
425
  };
414
426
  if (options.find_text)
415
427
  args.find_text = options.find_text;
@@ -417,8 +429,6 @@ export class BmClient {
417
429
  args.section = options.section;
418
430
  if (options.expected_replacements != null)
419
431
  args.expected_replacements = options.expected_replacements;
420
- if (project)
421
- args.project = project;
422
432
  const payload = await this.callTool("edit_note", args);
423
433
  if (!isRecord(payload)) {
424
434
  throw new Error("invalid edit_note response");
@@ -441,9 +451,8 @@ export class BmClient {
441
451
  const args = {
442
452
  identifier,
443
453
  output_format: "json",
454
+ project: this.routedProject(project),
444
455
  };
445
- if (project)
446
- args.project = project;
447
456
  const payload = await this.callTool("delete_note", args);
448
457
  if (!isRecord(payload)) {
449
458
  throw new Error("invalid delete_note response");
@@ -462,9 +471,8 @@ export class BmClient {
462
471
  identifier,
463
472
  destination_folder: newFolder,
464
473
  output_format: "json",
474
+ project: this.routedProject(project),
465
475
  };
466
- if (project)
467
- args.project = project;
468
476
  const payload = await this.callTool("move_note", args);
469
477
  if (!isRecord(payload)) {
470
478
  throw new Error("invalid move_note response");
@@ -481,13 +489,14 @@ export class BmClient {
481
489
  };
482
490
  }
483
491
  async schemaValidate(noteType, identifier, project) {
484
- const args = { output_format: "json" };
492
+ const args = {
493
+ output_format: "json",
494
+ project: this.routedProject(project),
495
+ };
485
496
  if (noteType)
486
497
  args.note_type = noteType;
487
498
  if (identifier)
488
499
  args.identifier = identifier;
489
- if (project)
490
- args.project = project;
491
500
  const payload = await this.callTool("schema_validate", args);
492
501
  if (!isRecord(payload)) {
493
502
  throw new Error("invalid schema_validate response");
@@ -499,9 +508,8 @@ export class BmClient {
499
508
  note_type: noteType,
500
509
  threshold,
501
510
  output_format: "json",
511
+ project: this.routedProject(project),
502
512
  };
503
- if (project)
504
- args.project = project;
505
513
  const payload = await this.callTool("schema_infer", args);
506
514
  if (!isRecord(payload)) {
507
515
  throw new Error("invalid schema_infer response");
@@ -512,9 +520,8 @@ export class BmClient {
512
520
  const args = {
513
521
  note_type: noteType,
514
522
  output_format: "json",
523
+ project: this.routedProject(project),
515
524
  };
516
- if (project)
517
- args.project = project;
518
525
  const payload = await this.callTool("schema_diff", args);
519
526
  if (!isRecord(payload)) {
520
527
  throw new Error("invalid schema_diff response");
@@ -537,14 +544,17 @@ export class BmClient {
537
544
  "",
538
545
  "---",
539
546
  ].join("\n");
540
- // Try append first — if it fails for ANY reason, fall through to create
541
547
  try {
542
548
  await this.editNote(title, "append", entry);
543
549
  log.debug(`appended conversation to: ${title}`);
544
550
  return;
545
551
  }
546
552
  catch (err) {
547
- log.debug(`append failed, will create: ${getErrorMessage(err)}`);
553
+ if (!isNoteNotFoundError(err)) {
554
+ log.error(`conversation append failed: ${getErrorMessage(err)}`, err);
555
+ throw err;
556
+ }
557
+ log.debug(`conversation note missing, will create: ${getErrorMessage(err)}`);
548
558
  }
549
559
  // Create the note with frontmatter and first entry
550
560
  const content = [
@@ -559,7 +569,7 @@ export class BmClient {
559
569
  entry,
560
570
  ].join("\n");
561
571
  try {
562
- await this.writeNote(title, content, "conversations", undefined, true);
572
+ await this.writeNote(title, content, "conversations");
563
573
  log.debug(`created conversation note: ${title}`);
564
574
  }
565
575
  catch (err) {
@@ -110,7 +110,6 @@ export function registerCli(api, client, cfg) {
110
110
  console.log(`Memory dir: ${cfg.memoryDir}`);
111
111
  console.log(`Memory file: ${cfg.memoryFile}`);
112
112
  console.log(`Auto-capture: ${cfg.autoCapture}`);
113
- console.log(`Cloud: ${cfg.cloud ? cfg.cloud.url : "disabled"}`);
114
113
  console.log(`Debug: ${cfg.debug}`);
115
114
  });
116
115
  }, { commands: ["basic-memory"] });
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -24,12 +24,51 @@ function loadManifest(skillsDir) {
24
24
  function loadSkill(skillsDir, dir) {
25
25
  return readFileSync(resolve(skillsDir, dir, "SKILL.md"), "utf-8");
26
26
  }
27
+ /**
28
+ * List a skill's bundled resource files (references/, assets/, ...) so the
29
+ * command can tell the assistant where they live on disk. SKILL.md workflows
30
+ * reference these by relative path; without the mapping, bundled commands
31
+ * would direct the assistant to files it was never given. evals/ is excluded
32
+ * as test fixtures.
33
+ */
34
+ function listResourceFiles(skillsDir, dir) {
35
+ const root = resolve(skillsDir, dir);
36
+ const files = [];
37
+ const walk = (rel) => {
38
+ for (const entry of readdirSync(resolve(root, rel), {
39
+ withFileTypes: true,
40
+ })) {
41
+ const relPath = rel ? `${rel}/${entry.name}` : entry.name;
42
+ if (entry.isDirectory()) {
43
+ if (relPath !== "evals")
44
+ walk(relPath);
45
+ }
46
+ else if (relPath !== "SKILL.md") {
47
+ files.push(relPath);
48
+ }
49
+ }
50
+ };
51
+ walk("");
52
+ return files.sort();
53
+ }
54
+ function resourceSection(skillsDir, dir, files) {
55
+ if (files.length === 0)
56
+ return "";
57
+ const lines = files
58
+ .map((f) => `- \`${f}\` → ${resolve(skillsDir, dir, f)}`)
59
+ .join("\n");
60
+ return ("\n\n## Bundled resource files\n\n" +
61
+ "This skill references companion files by relative path. They are installed at:\n\n" +
62
+ `${lines}\n\n` +
63
+ "When the workflow directs you to read one of these files, read it from the absolute path listed above.");
64
+ }
27
65
  export function registerSkillCommands(api) {
28
66
  const skillsDir = resolveSkillsDir(api);
29
67
  const manifest = loadManifest(skillsDir);
30
68
  for (const entry of manifest) {
31
69
  const commandName = entry.dir.replace(/^memory-/, "");
32
- const content = loadSkill(skillsDir, entry.dir);
70
+ const content = loadSkill(skillsDir, entry.dir) +
71
+ resourceSection(skillsDir, entry.dir, listResourceFiles(skillsDir, entry.dir));
33
72
  api.registerCommand({
34
73
  name: commandName,
35
74
  description: entry.description,
package/dist/config.d.ts CHANGED
@@ -1,7 +1,3 @@
1
- export type CloudConfig = {
2
- url: string;
3
- api_key: string;
4
- };
5
1
  export type BasicMemoryConfig = {
6
2
  project: string;
7
3
  bmPath: string;
@@ -13,7 +9,6 @@ export type BasicMemoryConfig = {
13
9
  autoRecall: boolean;
14
10
  recallPrompt: string;
15
11
  debug: boolean;
16
- cloud?: CloudConfig;
17
12
  };
18
13
  export declare function resolveProjectPath(projectPath: string, workspaceDir: string): string;
19
14
  export declare function parseConfig(raw: unknown): BasicMemoryConfig;
package/dist/config.js CHANGED
@@ -16,7 +16,6 @@ const ALLOWED_KEYS = [
16
16
  "recallPrompt",
17
17
  "recall_prompt",
18
18
  "debug",
19
- "cloud",
20
19
  ];
21
20
  function assertAllowedKeys(value, allowed, label) {
22
21
  const unknown = Object.keys(value).filter((k) => !allowed.includes(k));
@@ -62,14 +61,6 @@ export function parseConfig(raw) {
62
61
  cfg.memory_file.length > 0
63
62
  ? cfg.memory_file
64
63
  : "MEMORY.md";
65
- let cloud;
66
- if (cfg.cloud && typeof cfg.cloud === "object" && !Array.isArray(cfg.cloud)) {
67
- const c = cfg.cloud;
68
- assertAllowedKeys(c, ["url", "api_key"], "basic-memory cloud config");
69
- if (typeof c.url === "string" && typeof c.api_key === "string") {
70
- cloud = { url: c.url, api_key: c.api_key };
71
- }
72
- }
73
64
  return {
74
65
  project: typeof cfg.project === "string" && cfg.project.length > 0
75
66
  ? cfg.project
@@ -101,7 +92,6 @@ export function parseConfig(raw) {
101
92
  ? cfg.recall_prompt
102
93
  : "Check for active tasks and recent activity. Summarize anything relevant to the current session.",
103
94
  debug: typeof cfg.debug === "boolean" ? cfg.debug : false,
104
- cloud,
105
95
  };
106
96
  }
107
97
  export const basicMemoryConfigSchema = {
@@ -1,39 +1,13 @@
1
- import type { AgentMessage } from "@mariozechner/pi-agent-core";
1
+ import type { AssembleResult, BootstrapResult, CompactResult, ContextEngine, SubagentSpawnPreparation } from "openclaw/plugin-sdk";
2
2
  import type { BmClient } from "../bm-client.ts";
3
3
  import type { BasicMemoryConfig } from "../config.ts";
4
4
  export declare const MAX_ASSEMBLE_RECALL_CHARS = 1200;
5
- type AssembleResult = {
6
- messages: AgentMessage[];
7
- estimatedTokens: number;
8
- systemPromptAddition?: string;
9
- };
10
- type BootstrapResult = {
11
- bootstrapped: boolean;
12
- importedMessages?: number;
13
- reason?: string;
14
- };
15
- type CompactResult = {
16
- ok: boolean;
17
- compacted: boolean;
18
- reason?: string;
19
- result?: {
20
- summary?: string;
21
- firstKeptEntryId?: string;
22
- tokensBefore: number;
23
- tokensAfter?: number;
24
- details?: unknown;
25
- sessionId?: string;
26
- sessionFile?: string;
27
- };
28
- };
29
- interface ContextEngine {
30
- readonly info: {
31
- id: string;
32
- name: string;
33
- version?: string;
34
- ownsCompaction?: boolean;
35
- };
36
- }
5
+ type BootstrapParams = Parameters<NonNullable<ContextEngine["bootstrap"]>>[0];
6
+ type AssembleParams = Parameters<ContextEngine["assemble"]>[0];
7
+ type AfterTurnParams = Parameters<NonNullable<ContextEngine["afterTurn"]>>[0];
8
+ type CompactParams = Parameters<ContextEngine["compact"]>[0];
9
+ type PrepareSubagentSpawnParams = Parameters<NonNullable<ContextEngine["prepareSubagentSpawn"]>>[0];
10
+ type OnSubagentEndedParams = Parameters<NonNullable<ContextEngine["onSubagentEnded"]>>[0];
37
11
  export declare class BasicMemoryContextEngine implements ContextEngine {
38
12
  private readonly client;
39
13
  private readonly cfg;
@@ -46,49 +20,15 @@ export declare class BasicMemoryContextEngine implements ContextEngine {
46
20
  private readonly sessionState;
47
21
  private readonly subagentState;
48
22
  constructor(client: BmClient, cfg: BasicMemoryConfig);
49
- bootstrap(params: {
50
- sessionId: string;
51
- sessionFile: string;
52
- }): Promise<BootstrapResult>;
23
+ bootstrap(params: BootstrapParams): Promise<BootstrapResult>;
53
24
  ingest(): Promise<{
54
25
  ingested: boolean;
55
26
  }>;
56
- assemble(params: {
57
- sessionId: string;
58
- messages: AgentMessage[];
59
- tokenBudget?: number;
60
- }): Promise<AssembleResult>;
61
- afterTurn(params: {
62
- sessionId: string;
63
- sessionFile: string;
64
- messages: AgentMessage[];
65
- prePromptMessageCount: number;
66
- autoCompactionSummary?: string;
67
- isHeartbeat?: boolean;
68
- tokenBudget?: number;
69
- runtimeContext?: Record<string, unknown>;
70
- }): Promise<void>;
71
- compact(params: {
72
- sessionId: string;
73
- sessionFile: string;
74
- tokenBudget?: number;
75
- force?: boolean;
76
- currentTokenCount?: number;
77
- compactionTarget?: "budget" | "threshold";
78
- customInstructions?: string;
79
- runtimeContext?: Record<string, unknown>;
80
- }): Promise<CompactResult>;
81
- prepareSubagentSpawn(params: {
82
- parentSessionKey: string;
83
- childSessionKey: string;
84
- ttlMs?: number;
85
- }): Promise<{
86
- rollback: () => Promise<void>;
87
- } | undefined>;
88
- onSubagentEnded(params: {
89
- childSessionKey: string;
90
- reason: "deleted" | "completed" | "swept" | "released";
91
- }): Promise<void>;
27
+ assemble(params: AssembleParams): Promise<AssembleResult>;
28
+ afterTurn(params: AfterTurnParams): Promise<void>;
29
+ compact(params: CompactParams): Promise<CompactResult>;
30
+ prepareSubagentSpawn(params: PrepareSubagentSpawnParams): Promise<SubagentSpawnPreparation | undefined>;
31
+ onSubagentEnded(params: OnSubagentEndedParams): Promise<void>;
92
32
  dispose(): Promise<void>;
93
33
  }
94
34
  export {};
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
+ export declare function isCommandAvailable(command: string): boolean;
1
2
  declare const _default: {
2
3
  id: string;
3
4
  name: string;
4
5
  description: string;
5
- configSchema: import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginConfigSchema;
6
+ configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
6
7
  register: NonNullable<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition["register"]>;
7
8
  } & Pick<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
8
9
  export default _default;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { execSync } from "node:child_process";
1
+ import { execFileSync } from "node:child_process";
2
2
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
3
  import { BmClient } from "./bm-client.js";
4
4
  import { registerCli } from "./commands/cli.js";
@@ -22,7 +22,15 @@ import { registerSchemaInferTool } from "./tools/schema-infer.js";
22
22
  import { registerSchemaValidateTool } from "./tools/schema-validate.js";
23
23
  import { registerSearchTool } from "./tools/search-notes.js";
24
24
  import { registerWriteTool } from "./tools/write-note.js";
25
- const BASIC_MEMORY_RELEASE_TAG = "v0.20.2";
25
+ export function isCommandAvailable(command) {
26
+ try {
27
+ execFileSync(command, ["--version"], { stdio: "ignore" });
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
26
34
  export default definePluginEntry({
27
35
  id: "openclaw-basic-memory",
28
36
  name: "Basic Memory",
@@ -62,22 +70,24 @@ export default definePluginEntry({
62
70
  log.info("starting...");
63
71
  // Auto-install bm CLI if not found
64
72
  const bmBin = cfg.bmPath || "bm";
65
- try {
66
- execSync(`command -v ${bmBin}`, { stdio: "ignore" });
67
- }
68
- catch {
73
+ if (!isCommandAvailable(bmBin)) {
69
74
  log.info("bm CLI not found on PATH — attempting auto-install...");
70
75
  try {
71
- execSync("command -v uv", { stdio: "ignore" });
76
+ if (!isCommandAvailable("uv")) {
77
+ throw new Error("uv not found");
78
+ }
72
79
  log.info("installing basic-memory via uv (this may take a minute)...");
73
- const result = execSync(`uv tool install "basic-memory @ git+https://github.com/basicmachines-co/basic-memory.git@${BASIC_MEMORY_RELEASE_TAG}" --force`, { encoding: "utf-8", timeout: 120_000, stdio: "pipe" });
80
+ const result = execFileSync("uv", ["tool", "install", "basic-memory", "--force"], {
81
+ encoding: "utf-8",
82
+ timeout: 120_000,
83
+ stdio: ["ignore", "pipe", "pipe"],
84
+ });
74
85
  log.info(`basic-memory installed: ${result.trim().split("\n").pop()}`);
75
86
  // Verify it worked
76
- try {
77
- execSync(`command -v ${bmBin}`, { stdio: "ignore" });
87
+ if (isCommandAvailable(bmBin)) {
78
88
  log.info("bm CLI now available on PATH");
79
89
  }
80
- catch {
90
+ else {
81
91
  log.error("bm installed but not found on PATH. You may need to add uv's bin directory to your PATH (typically ~/.local/bin).");
82
92
  }
83
93
  }
@@ -8,6 +8,7 @@ function normalizeProject(project) {
8
8
  is_private: project.is_private === true,
9
9
  is_default: project.is_default === true || project.isDefault === true,
10
10
  workspace_name: project.workspace_name ?? null,
11
+ workspace_slug: project.workspace_slug ?? null,
11
12
  workspace_type: project.workspace_type ?? null,
12
13
  workspace_tenant_id: project.workspace_tenant_id ?? null,
13
14
  };
@@ -19,7 +20,7 @@ export function registerProjectListTool(api, client) {
19
20
  description: "List all Basic Memory projects accessible to this agent",
20
21
  parameters: Type.Object({
21
22
  workspace: Type.Optional(Type.String({
22
- description: "Filter by workspace name or tenant_id",
23
+ description: "Filter by workspace name, slug, or tenant_id",
23
24
  })),
24
25
  }),
25
26
  async execute(_toolCallId, params) {
package/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { execSync } from "node:child_process"
1
+ import { execFileSync } from "node:child_process"
2
2
 
3
3
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"
4
4
  import { BmClient } from "./bm-client.ts"
@@ -31,7 +31,14 @@ import { registerSchemaValidateTool } from "./tools/schema-validate.ts"
31
31
  import { registerSearchTool } from "./tools/search-notes.ts"
32
32
  import { registerWriteTool } from "./tools/write-note.ts"
33
33
 
34
- const BASIC_MEMORY_RELEASE_TAG = "v0.20.2"
34
+ export function isCommandAvailable(command: string): boolean {
35
+ try {
36
+ execFileSync(command, ["--version"], { stdio: "ignore" })
37
+ return true
38
+ } catch {
39
+ return false
40
+ }
41
+ }
35
42
 
36
43
  export default definePluginEntry({
37
44
  id: "openclaw-basic-memory",
@@ -87,27 +94,31 @@ export default definePluginEntry({
87
94
 
88
95
  // Auto-install bm CLI if not found
89
96
  const bmBin = cfg.bmPath || "bm"
90
- try {
91
- execSync(`command -v ${bmBin}`, { stdio: "ignore" })
92
- } catch {
97
+ if (!isCommandAvailable(bmBin)) {
93
98
  log.info("bm CLI not found on PATH — attempting auto-install...")
94
99
  try {
95
- execSync("command -v uv", { stdio: "ignore" })
100
+ if (!isCommandAvailable("uv")) {
101
+ throw new Error("uv not found")
102
+ }
96
103
  log.info(
97
104
  "installing basic-memory via uv (this may take a minute)...",
98
105
  )
99
- const result = execSync(
100
- `uv tool install "basic-memory @ git+https://github.com/basicmachines-co/basic-memory.git@${BASIC_MEMORY_RELEASE_TAG}" --force`,
101
- { encoding: "utf-8", timeout: 120_000, stdio: "pipe" },
106
+ const result = execFileSync(
107
+ "uv",
108
+ ["tool", "install", "basic-memory", "--force"],
109
+ {
110
+ encoding: "utf-8",
111
+ timeout: 120_000,
112
+ stdio: ["ignore", "pipe", "pipe"],
113
+ },
102
114
  )
103
115
  log.info(
104
116
  `basic-memory installed: ${result.trim().split("\n").pop()}`,
105
117
  )
106
118
  // Verify it worked
107
- try {
108
- execSync(`command -v ${bmBin}`, { stdio: "ignore" })
119
+ if (isCommandAvailable(bmBin)) {
109
120
  log.info("bm CLI now available on PATH")
110
- } catch {
121
+ } else {
111
122
  log.error(
112
123
  "bm installed but not found on PATH. You may need to add uv's bin directory to your PATH (typically ~/.local/bin).",
113
124
  )
@@ -26,6 +26,7 @@
26
26
  "skills/memory-literary-analysis",
27
27
  "skills/memory-metadata-search",
28
28
  "skills/memory-notes",
29
+ "skills/memory-onboarding",
29
30
  "skills/memory-reflect",
30
31
  "skills/memory-research",
31
32
  "skills/memory-schema",
@@ -83,11 +84,6 @@
83
84
  "placeholder": ".",
84
85
  "help": "Filesystem path for Basic Memory project data (defaults to workspace root; relative paths resolve from workspace)",
85
86
  "advanced": true
86
- },
87
- "cloud": {
88
- "label": "Cloud Backend",
89
- "help": "Optional cloud backend config (url + api_key). If present, uses cloud instead of local BM.",
90
- "advanced": true
91
87
  }
92
88
  },
93
89
  "configSchema": {
@@ -108,16 +104,7 @@
108
104
  "recallPrompt": { "type": "string" },
109
105
  "recall_prompt": { "type": "string" },
110
106
  "debug": { "type": "boolean" },
111
- "projectPath": { "type": "string" },
112
- "cloud": {
113
- "type": "object",
114
- "additionalProperties": false,
115
- "properties": {
116
- "url": { "type": "string" },
117
- "api_key": { "type": "string" }
118
- },
119
- "required": ["url", "api_key"]
120
- }
107
+ "projectPath": { "type": "string" }
121
108
  },
122
109
  "required": []
123
110
  }
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@basicmemory/openclaw-basic-memory",
3
- "version": "0.2.4",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
- "description": "Basic Memory plugin for OpenClaw — local-first knowledge graph for agent memory",
7
+ "description": "Basic Memory plugin for OpenClaw \u2014 local-first knowledge graph for agent memory",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/basicmachines-co/openclaw-basic-memory"
11
+ "url": "https://github.com/basicmachines-co/basic-memory.git",
12
+ "directory": "integrations/openclaw"
12
13
  },
13
14
  "files": [
14
15
  "index.ts",
@@ -65,7 +66,7 @@
65
66
  "test": "bun test",
66
67
  "test:int": "BM_INTEGRATION=1 BM_BIN=${BM_BIN:-./scripts/bm-local.sh} bun test integration --timeout 120000",
67
68
  "test:coverage": "bun test --coverage",
68
- "release:check": "bun run check-types && bun run build && bun test && npm pack --dry-run"
69
+ "release:check": "bun run fetch-skills && bun run check-types && bun run lint && bun run build && bun test && npm pack --dry-run"
69
70
  },
70
71
  "openclaw": {
71
72
  "extensions": [