memorylake-openclaw 1.1.7 → 1.1.8

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/dist/index.js CHANGED
@@ -240,7 +240,7 @@ import { Type } from "@sinclair/typebox";
240
240
 
241
241
  // lib/telemetry.ts
242
242
  import { createHash } from "crypto";
243
- var PLUGIN_VERSION = true ? "1.1.7" : "dev";
243
+ var PLUGIN_VERSION = true ? "1.1.8" : "dev";
244
244
  var DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
245
245
  var POSTHOG_API_KEY = process.env.MEMORYLAKE_POSTHOG_API_KEY?.trim() ?? "";
246
246
  var POSTHOG_HOST = normalizePosthogHost(
@@ -565,7 +565,7 @@ function getProvider(effectiveCfg) {
565
565
  }
566
566
 
567
567
  // lib/utils/builders.ts
568
- var PLUGIN_VERSION2 = true ? "1.1.7" : "dev";
568
+ var PLUGIN_VERSION2 = true ? "1.1.8" : "dev";
569
569
  function buildDocumentContext(results, maxChunkLength = 1e4) {
570
570
  const parts = [];
571
571
  for (const result of results) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorylake-openclaw",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "type": "module",
5
5
  "description": "MemoryLake memory backend for OpenClaw",
6
6
  "license": "MIT",
@@ -26,6 +26,9 @@
26
26
  ],
27
27
  "scripts": {
28
28
  "build": "tsup",
29
+ "sync-skills": "node scripts/sync-skills.mjs",
30
+ "prepare": "node scripts/sync-skills.mjs",
31
+ "test": "node --test",
29
32
  "prepublishOnly": "npm run build"
30
33
  },
31
34
  "dependencies": {
@@ -5,7 +5,7 @@ description: Use when the user asks to configure agent-specific memorylake prope
5
5
 
6
6
  # Agent MemoryLake Config
7
7
 
8
- Configure agent-specific memorylake properties for the current agent. The config file is located at `{workspace}/.memorylake/config.json`, where `{workspace}` is the agent's workspace directory. This config overrides corresponding properties from the global config.
8
+ Configure agent-specific memorylake properties for the current agent. The config file is located at `{workspace}/.memorylake/config.json`, where `{workspace}` is the current agent's workspace directory. This config overrides corresponding properties from the global config.
9
9
 
10
10
  ## Step 1 — Confirm projectId
11
11
 
@@ -15,7 +15,7 @@ If the user has already provided the `projectId` in their message, skip the ques
15
15
 
16
16
  ## Step 2 — Write Config
17
17
 
18
- 1. Ensure the `.memorylake/` directory exists inside the agent's workspace directory:
18
+ 1. Ensure the `.memorylake/` directory exists inside the current agent's workspace directory:
19
19
 
20
20
  ```bash
21
21
  cd {workspace}
@@ -6,24 +6,43 @@ import { homedir } from "node:os";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import JSON5 from "json5";
8
8
 
9
+ // ---------------------------------------------------------------------------
10
+ // CANONICAL SOURCE. This file is synced verbatim into each consuming skill's
11
+ // `scripts/` directory by `scripts/sync-skills.mjs` (run via the package
12
+ // `prepare` script). Edit THIS file only — never edit the generated copies.
13
+ //
14
+ // Why the copies exist: the OpenClaw skill mechanism links each skill
15
+ // individually into `~/.openclaw/skills/<name>/`, so a shared sibling such as
16
+ // `skills/common/` is no longer reachable from a skill via a relative path.
17
+ // Each skill therefore carries its own copy and references it as
18
+ // `{path-to-this-skill}/scripts/get-config.mjs`.
19
+ // ---------------------------------------------------------------------------
20
+
9
21
  /**
10
- * Load the merged MemoryLake plugin config for a given agent.
22
+ * Load the merged MemoryLake plugin config for a given workspace.
11
23
  *
12
24
  * Reads the global `~/.openclaw/openclaw.json` config, picks up the
13
- * `memorylake-openclaw` plugin config, resolves the agent workspace,
14
- * and merges any per-workspace overrides from `<workspace>/.memorylake/config.json`.
25
+ * `memorylake-openclaw` plugin config, and merges any per-workspace overrides
26
+ * from `<workspace>/.memorylake/config.json`.
27
+ *
28
+ * The caller passes the workspace directly — the running agent already knows
29
+ * its own workspace (it is the agent's working directory, the same value the
30
+ * `agent-memorylake-config` skill writes into). We deliberately do NOT shell
31
+ * out to resolve it from an agent id: the only thing a workspace is needed for
32
+ * here is locating the per-workspace override file, so taking it as input keeps
33
+ * this script dependency-free and side-effect-free.
15
34
  *
16
- * @param {{ agentId: string }} opts
35
+ * @param {{ workspace: string }} opts
17
36
  * @returns {{ apiKey: string, projectId: string, host: string, workspace: string, [key: string]: unknown }}
18
- * Merged config object. Throws on any failure (missing agent, missing
37
+ * Merged config object. Throws on any failure (missing workspace, missing
19
38
  * plugin config, missing required fields, parse errors).
20
39
  */
21
- export function loadConfig({ agentId }) {
22
- if (!agentId || typeof agentId !== "string") {
23
- throw new Error("agentId is required");
40
+ export function loadConfig({ workspace }) {
41
+ if (!workspace || typeof workspace !== "string") {
42
+ throw new Error("workspace is required");
24
43
  }
25
44
 
26
- // Read global config
45
+ // Read global plugin config (apiKey/projectId/host/...) from openclaw.json.
27
46
  const openclawPath = join(homedir(), ".openclaw", "openclaw.json");
28
47
  let openclaw;
29
48
  try {
@@ -36,15 +55,7 @@ export function loadConfig({ agentId }) {
36
55
  throw new Error("memorylake-openclaw plugin config not found");
37
56
  }
38
57
 
39
- // Resolve workspace
40
- const agents = openclaw?.agents;
41
- const agentEntry = agents?.list?.find((a) => a.id === agentId);
42
- const workspace = agentEntry?.workspace || agents?.defaults?.workspace;
43
- if (!workspace) {
44
- throw new Error(`no workspace found for agent "${agentId}"`);
45
- }
46
-
47
- // Merge per-agent overrides
58
+ // Merge per-workspace overrides
48
59
  const merged = { ...globalCfg };
49
60
  const localPath = join(workspace, ".memorylake", "config.json");
50
61
  if (existsSync(localPath)) {
@@ -73,15 +84,15 @@ export function loadConfig({ agentId }) {
73
84
  // identical (JSON to stdout, errors to stderr, non-zero exit on failure).
74
85
  if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
75
86
  const args = process.argv.slice(2);
76
- const agentIdx = args.indexOf("--agent");
77
- if (agentIdx === -1 || !args[agentIdx + 1]) {
78
- console.error("Usage: node get-config.mjs --agent <agentId>");
87
+ const wsIdx = args.indexOf("--workspace");
88
+ if (wsIdx === -1 || !args[wsIdx + 1]) {
89
+ console.error("Usage: node get-config.mjs --workspace <workspaceDir>");
79
90
  process.exit(1);
80
91
  }
81
- const agentId = args[agentIdx + 1];
92
+ const workspace = args[wsIdx + 1];
82
93
 
83
94
  try {
84
- const merged = loadConfig({ agentId });
95
+ const merged = loadConfig({ workspace });
85
96
  console.log(JSON.stringify(merged, null, 2));
86
97
  } catch (err) {
87
98
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
@@ -19,13 +19,13 @@ Directly call MemoryLake's REST APIs by discovering endpoints from the live Open
19
19
 
20
20
  ## Step 1 — Read MemoryLake Config
21
21
 
22
- Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `../common/get-config.mjs`):
22
+ Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `scripts/get-config.mjs`):
23
23
 
24
24
  ```bash
25
- node {path-to-this-skill}/../common/get-config.mjs --agent {agent}
25
+ node {path-to-this-skill}/scripts/get-config.mjs --workspace {workspace}
26
26
  ```
27
27
 
28
- Where `{agent}` is the current agent ID. The script outputs JSON config with `host`, `apiKey`, `projectId`, etc.
28
+ Where `{workspace}` is the current agent's workspace directory. The script outputs JSON config with `host`, `apiKey`, `projectId`, etc.
29
29
 
30
30
  If the script exits with an error, stop and inform the user.
31
31
 
@@ -186,7 +186,7 @@ All responses follow the same wrapper format:
186
186
 
187
187
  | Item | Value |
188
188
  |------|-------|
189
- | Config script | `{path-to-this-skill}/../common/get-config.mjs --agent {agent}` |
189
+ | Config script | `{path-to-this-skill}/scripts/get-config.mjs --workspace {workspace}` |
190
190
  | Server base path | `/openapi/memorylake` |
191
191
  | OpenAPI spec URL | `{host}/openapi/memorylake/api-docs/open-api` |
192
192
  | Auth header | `Authorization: Bearer {apiKey}` |
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { fileURLToPath } from "node:url";
7
+ import JSON5 from "json5";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // CANONICAL SOURCE. This file is synced verbatim into each consuming skill's
11
+ // `scripts/` directory by `scripts/sync-skills.mjs` (run via the package
12
+ // `prepare` script). Edit THIS file only — never edit the generated copies.
13
+ //
14
+ // Why the copies exist: the OpenClaw skill mechanism links each skill
15
+ // individually into `~/.openclaw/skills/<name>/`, so a shared sibling such as
16
+ // `skills/common/` is no longer reachable from a skill via a relative path.
17
+ // Each skill therefore carries its own copy and references it as
18
+ // `{path-to-this-skill}/scripts/get-config.mjs`.
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /**
22
+ * Load the merged MemoryLake plugin config for a given workspace.
23
+ *
24
+ * Reads the global `~/.openclaw/openclaw.json` config, picks up the
25
+ * `memorylake-openclaw` plugin config, and merges any per-workspace overrides
26
+ * from `<workspace>/.memorylake/config.json`.
27
+ *
28
+ * The caller passes the workspace directly — the running agent already knows
29
+ * its own workspace (it is the agent's working directory, the same value the
30
+ * `agent-memorylake-config` skill writes into). We deliberately do NOT shell
31
+ * out to resolve it from an agent id: the only thing a workspace is needed for
32
+ * here is locating the per-workspace override file, so taking it as input keeps
33
+ * this script dependency-free and side-effect-free.
34
+ *
35
+ * @param {{ workspace: string }} opts
36
+ * @returns {{ apiKey: string, projectId: string, host: string, workspace: string, [key: string]: unknown }}
37
+ * Merged config object. Throws on any failure (missing workspace, missing
38
+ * plugin config, missing required fields, parse errors).
39
+ */
40
+ export function loadConfig({ workspace }) {
41
+ if (!workspace || typeof workspace !== "string") {
42
+ throw new Error("workspace is required");
43
+ }
44
+
45
+ // Read global plugin config (apiKey/projectId/host/...) from openclaw.json.
46
+ const openclawPath = join(homedir(), ".openclaw", "openclaw.json");
47
+ let openclaw;
48
+ try {
49
+ openclaw = JSON5.parse(readFileSync(openclawPath, "utf-8"));
50
+ } catch (err) {
51
+ throw new Error(`failed to parse JSON5 config file "${openclawPath}": ${String(err)}`);
52
+ }
53
+ const globalCfg = openclaw?.plugins?.entries?.["memorylake-openclaw"]?.config;
54
+ if (!globalCfg) {
55
+ throw new Error("memorylake-openclaw plugin config not found");
56
+ }
57
+
58
+ // Merge per-workspace overrides
59
+ const merged = { ...globalCfg };
60
+ const localPath = join(workspace, ".memorylake", "config.json");
61
+ if (existsSync(localPath)) {
62
+ try {
63
+ const raw = JSON.parse(readFileSync(localPath, "utf-8"));
64
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
65
+ Object.assign(merged, raw);
66
+ }
67
+ } catch (err) {
68
+ throw new Error(`failed to parse workspace config at ${localPath}: ${String(err)}`);
69
+ }
70
+ }
71
+ merged.host = merged.host || "https://app.memorylake.ai";
72
+ merged.workspace = workspace;
73
+
74
+ // Validate required fields
75
+ if (!merged.apiKey || !merged.projectId) {
76
+ throw new Error("apiKey and projectId are required");
77
+ }
78
+
79
+ return merged;
80
+ }
81
+
82
+ // CLI mode: only run when invoked directly via `node get-config.mjs ...`,
83
+ // not when imported as a module. Keeps the SKILL.md-documented CLI behavior
84
+ // identical (JSON to stdout, errors to stderr, non-zero exit on failure).
85
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
86
+ const args = process.argv.slice(2);
87
+ const wsIdx = args.indexOf("--workspace");
88
+ if (wsIdx === -1 || !args[wsIdx + 1]) {
89
+ console.error("Usage: node get-config.mjs --workspace <workspaceDir>");
90
+ process.exit(1);
91
+ }
92
+ const workspace = args[wsIdx + 1];
93
+
94
+ try {
95
+ const merged = loadConfig({ workspace });
96
+ console.log(JSON.stringify(merged, null, 2));
97
+ } catch (err) {
98
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
99
+ process.exit(1);
100
+ }
101
+ }
@@ -18,13 +18,13 @@ Upload local files, archives, or directories to MemoryLake using the multipart u
18
18
 
19
19
  ## Step 1 -- Read MemoryLake Config
20
20
 
21
- Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `../common/get-config.mjs`):
21
+ Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `scripts/get-config.mjs`):
22
22
 
23
23
  ```bash
24
- node {path-to-this-skill}/../common/get-config.mjs --agent {agent}
24
+ node {path-to-this-skill}/scripts/get-config.mjs --workspace {workspace}
25
25
  ```
26
26
 
27
- Where `{agent}` is the current agent ID. The script outputs JSON config with `host`, `apiKey`, `projectId`, etc.
27
+ Where `{workspace}` is the current agent's workspace directory. The script outputs JSON config with `host`, `apiKey`, `projectId`, etc.
28
28
 
29
29
  If the script exits with an error, stop and inform the user.
30
30
 
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { fileURLToPath } from "node:url";
7
+ import JSON5 from "json5";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // CANONICAL SOURCE. This file is synced verbatim into each consuming skill's
11
+ // `scripts/` directory by `scripts/sync-skills.mjs` (run via the package
12
+ // `prepare` script). Edit THIS file only — never edit the generated copies.
13
+ //
14
+ // Why the copies exist: the OpenClaw skill mechanism links each skill
15
+ // individually into `~/.openclaw/skills/<name>/`, so a shared sibling such as
16
+ // `skills/common/` is no longer reachable from a skill via a relative path.
17
+ // Each skill therefore carries its own copy and references it as
18
+ // `{path-to-this-skill}/scripts/get-config.mjs`.
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /**
22
+ * Load the merged MemoryLake plugin config for a given workspace.
23
+ *
24
+ * Reads the global `~/.openclaw/openclaw.json` config, picks up the
25
+ * `memorylake-openclaw` plugin config, and merges any per-workspace overrides
26
+ * from `<workspace>/.memorylake/config.json`.
27
+ *
28
+ * The caller passes the workspace directly — the running agent already knows
29
+ * its own workspace (it is the agent's working directory, the same value the
30
+ * `agent-memorylake-config` skill writes into). We deliberately do NOT shell
31
+ * out to resolve it from an agent id: the only thing a workspace is needed for
32
+ * here is locating the per-workspace override file, so taking it as input keeps
33
+ * this script dependency-free and side-effect-free.
34
+ *
35
+ * @param {{ workspace: string }} opts
36
+ * @returns {{ apiKey: string, projectId: string, host: string, workspace: string, [key: string]: unknown }}
37
+ * Merged config object. Throws on any failure (missing workspace, missing
38
+ * plugin config, missing required fields, parse errors).
39
+ */
40
+ export function loadConfig({ workspace }) {
41
+ if (!workspace || typeof workspace !== "string") {
42
+ throw new Error("workspace is required");
43
+ }
44
+
45
+ // Read global plugin config (apiKey/projectId/host/...) from openclaw.json.
46
+ const openclawPath = join(homedir(), ".openclaw", "openclaw.json");
47
+ let openclaw;
48
+ try {
49
+ openclaw = JSON5.parse(readFileSync(openclawPath, "utf-8"));
50
+ } catch (err) {
51
+ throw new Error(`failed to parse JSON5 config file "${openclawPath}": ${String(err)}`);
52
+ }
53
+ const globalCfg = openclaw?.plugins?.entries?.["memorylake-openclaw"]?.config;
54
+ if (!globalCfg) {
55
+ throw new Error("memorylake-openclaw plugin config not found");
56
+ }
57
+
58
+ // Merge per-workspace overrides
59
+ const merged = { ...globalCfg };
60
+ const localPath = join(workspace, ".memorylake", "config.json");
61
+ if (existsSync(localPath)) {
62
+ try {
63
+ const raw = JSON.parse(readFileSync(localPath, "utf-8"));
64
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
65
+ Object.assign(merged, raw);
66
+ }
67
+ } catch (err) {
68
+ throw new Error(`failed to parse workspace config at ${localPath}: ${String(err)}`);
69
+ }
70
+ }
71
+ merged.host = merged.host || "https://app.memorylake.ai";
72
+ merged.workspace = workspace;
73
+
74
+ // Validate required fields
75
+ if (!merged.apiKey || !merged.projectId) {
76
+ throw new Error("apiKey and projectId are required");
77
+ }
78
+
79
+ return merged;
80
+ }
81
+
82
+ // CLI mode: only run when invoked directly via `node get-config.mjs ...`,
83
+ // not when imported as a module. Keeps the SKILL.md-documented CLI behavior
84
+ // identical (JSON to stdout, errors to stderr, non-zero exit on failure).
85
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
86
+ const args = process.argv.slice(2);
87
+ const wsIdx = args.indexOf("--workspace");
88
+ if (wsIdx === -1 || !args[wsIdx + 1]) {
89
+ console.error("Usage: node get-config.mjs --workspace <workspaceDir>");
90
+ process.exit(1);
91
+ }
92
+ const workspace = args[wsIdx + 1];
93
+
94
+ try {
95
+ const merged = loadConfig({ workspace });
96
+ console.log(JSON.stringify(merged, null, 2));
97
+ } catch (err) {
98
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
99
+ process.exit(1);
100
+ }
101
+ }
@@ -25,7 +25,7 @@ The caller must provide:
25
25
  **Always try the script first.** The script is at `scripts/migrate.mjs` relative to this SKILL.md (i.e., in the `scripts/` subdirectory of this skill). Run:
26
26
 
27
27
  ```bash
28
- node <path-to-this-skill-directory>/scripts/migrate.mjs --agent {agent} --user-id {user_id}
28
+ node <path-to-this-skill-directory>/scripts/migrate.mjs --agent {agent} --workspace {workspace} --user-id {user_id}
29
29
  ```
30
30
 
31
31
  The script handles everything: config reading, session filtering, JSONL parsing, memory file collection, and API submission.
@@ -40,10 +40,10 @@ If the script fails, follow these steps manually.
40
40
 
41
41
  ### Step 1 — Read MemoryLake Config
42
42
 
43
- Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `../common/get-config.mjs`):
43
+ Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `scripts/get-config.mjs`):
44
44
 
45
45
  ```bash
46
- node {path-to-this-skill}/../common/get-config.mjs --agent {agent}
46
+ node {path-to-this-skill}/scripts/get-config.mjs --workspace {workspace}
47
47
  ```
48
48
 
49
49
  The script outputs JSON config with `host`, `apiKey`, `projectId`, `workspace`, etc. If the script exits with an error, stop and inform the user.
@@ -74,7 +74,7 @@ The script outputs JSON config with `host`, `apiKey`, `projectId`, `workspace`,
74
74
 
75
75
  ### Step 3 — Read Memory Files
76
76
 
77
- Use the `workspace` path from the config output in Step 1. Then read:
77
+ Use the agent's `{workspace}` directory (the same path passed in Step 1, and echoed back as the `workspace` field in the config output). Then read:
78
78
  - `{workspace}/MEMORY.md`
79
79
  - All files in `{workspace}/memory/` directory
80
80
 
@@ -184,10 +184,10 @@ At the end, print a summary:
184
184
 
185
185
  | Item | Path / Value |
186
186
  |------|-------------|
187
- | Config script | `{path-to-this-skill}/../common/get-config.mjs --agent {agent}` |
187
+ | Config script | `{path-to-this-skill}/scripts/get-config.mjs --workspace {workspace}` |
188
188
  | Session index | `~/.openclaw/agents/{agent}/sessions/sessions.json` |
189
189
  | Session files | `~/.openclaw/agents/{agent}/sessions/{id}.jsonl` |
190
- | Workspace path | from config script output (`workspace` field) |
190
+ | Workspace path | the current agent's workspace directory (`{workspace}`; echoed back in the config output) |
191
191
  | API endpoint | `{host}/openapi/memorylake/api/v2/projects/{projectId}/memories` |
192
192
  | Auth header | `Authorization: Bearer {apiKey}` |
193
193
  | Default host | `https://app.memorylake.ai` |
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { fileURLToPath } from "node:url";
7
+ import JSON5 from "json5";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // CANONICAL SOURCE. This file is synced verbatim into each consuming skill's
11
+ // `scripts/` directory by `scripts/sync-skills.mjs` (run via the package
12
+ // `prepare` script). Edit THIS file only — never edit the generated copies.
13
+ //
14
+ // Why the copies exist: the OpenClaw skill mechanism links each skill
15
+ // individually into `~/.openclaw/skills/<name>/`, so a shared sibling such as
16
+ // `skills/common/` is no longer reachable from a skill via a relative path.
17
+ // Each skill therefore carries its own copy and references it as
18
+ // `{path-to-this-skill}/scripts/get-config.mjs`.
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /**
22
+ * Load the merged MemoryLake plugin config for a given workspace.
23
+ *
24
+ * Reads the global `~/.openclaw/openclaw.json` config, picks up the
25
+ * `memorylake-openclaw` plugin config, and merges any per-workspace overrides
26
+ * from `<workspace>/.memorylake/config.json`.
27
+ *
28
+ * The caller passes the workspace directly — the running agent already knows
29
+ * its own workspace (it is the agent's working directory, the same value the
30
+ * `agent-memorylake-config` skill writes into). We deliberately do NOT shell
31
+ * out to resolve it from an agent id: the only thing a workspace is needed for
32
+ * here is locating the per-workspace override file, so taking it as input keeps
33
+ * this script dependency-free and side-effect-free.
34
+ *
35
+ * @param {{ workspace: string }} opts
36
+ * @returns {{ apiKey: string, projectId: string, host: string, workspace: string, [key: string]: unknown }}
37
+ * Merged config object. Throws on any failure (missing workspace, missing
38
+ * plugin config, missing required fields, parse errors).
39
+ */
40
+ export function loadConfig({ workspace }) {
41
+ if (!workspace || typeof workspace !== "string") {
42
+ throw new Error("workspace is required");
43
+ }
44
+
45
+ // Read global plugin config (apiKey/projectId/host/...) from openclaw.json.
46
+ const openclawPath = join(homedir(), ".openclaw", "openclaw.json");
47
+ let openclaw;
48
+ try {
49
+ openclaw = JSON5.parse(readFileSync(openclawPath, "utf-8"));
50
+ } catch (err) {
51
+ throw new Error(`failed to parse JSON5 config file "${openclawPath}": ${String(err)}`);
52
+ }
53
+ const globalCfg = openclaw?.plugins?.entries?.["memorylake-openclaw"]?.config;
54
+ if (!globalCfg) {
55
+ throw new Error("memorylake-openclaw plugin config not found");
56
+ }
57
+
58
+ // Merge per-workspace overrides
59
+ const merged = { ...globalCfg };
60
+ const localPath = join(workspace, ".memorylake", "config.json");
61
+ if (existsSync(localPath)) {
62
+ try {
63
+ const raw = JSON.parse(readFileSync(localPath, "utf-8"));
64
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
65
+ Object.assign(merged, raw);
66
+ }
67
+ } catch (err) {
68
+ throw new Error(`failed to parse workspace config at ${localPath}: ${String(err)}`);
69
+ }
70
+ }
71
+ merged.host = merged.host || "https://app.memorylake.ai";
72
+ merged.workspace = workspace;
73
+
74
+ // Validate required fields
75
+ if (!merged.apiKey || !merged.projectId) {
76
+ throw new Error("apiKey and projectId are required");
77
+ }
78
+
79
+ return merged;
80
+ }
81
+
82
+ // CLI mode: only run when invoked directly via `node get-config.mjs ...`,
83
+ // not when imported as a module. Keeps the SKILL.md-documented CLI behavior
84
+ // identical (JSON to stdout, errors to stderr, non-zero exit on failure).
85
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
86
+ const args = process.argv.slice(2);
87
+ const wsIdx = args.indexOf("--workspace");
88
+ if (wsIdx === -1 || !args[wsIdx + 1]) {
89
+ console.error("Usage: node get-config.mjs --workspace <workspaceDir>");
90
+ process.exit(1);
91
+ }
92
+ const workspace = args[wsIdx + 1];
93
+
94
+ try {
95
+ const merged = loadConfig({ workspace });
96
+ console.log(JSON.stringify(merged, null, 2));
97
+ } catch (err) {
98
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
99
+ process.exit(1);
100
+ }
101
+ }
@@ -11,14 +11,14 @@ import { readFileSync, readdirSync, existsSync, statSync } from "fs";
11
11
  import { join, basename } from "path";
12
12
  import { homedir } from "os";
13
13
  import { parseArgs } from "util";
14
- import { loadConfig as loadConfigShared } from "../../common/get-config.mjs";
14
+ import { loadConfig as loadConfigShared } from "./get-config.mjs";
15
15
 
16
16
  const BATCH_SIZE = 20;
17
17
  const API_USER_ID = "default";
18
18
 
19
- function loadConfig(agent) {
19
+ function loadConfig(workspace) {
20
20
  try {
21
- return loadConfigShared({ agentId: agent });
21
+ return loadConfigShared({ workspace });
22
22
  } catch (err) {
23
23
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
24
24
  process.exit(1);
@@ -249,22 +249,28 @@ async function main() {
249
249
  const { values } = parseArgs({
250
250
  options: {
251
251
  agent: { type: "string" },
252
+ workspace: { type: "string" },
252
253
  "user-id": { type: "string" },
253
254
  },
254
255
  strict: true,
255
256
  });
256
257
 
257
258
  const agent = values.agent;
259
+ const workspace = values.workspace;
258
260
  const userId = values["user-id"];
259
261
 
260
- if (!agent || !userId) {
261
- console.error("Usage: node migrate.mjs --agent <agent> --user-id <user_id>");
262
+ if (!agent || !workspace || !userId) {
263
+ console.error(
264
+ "Usage: node migrate.mjs --agent <agent> --workspace <path> --user-id <user_id>",
265
+ );
262
266
  process.exit(1);
263
267
  }
264
268
 
265
269
  console.log("=== Migrate Memories to MemoryLake ===\n");
266
270
 
267
- const { host, apiKey, projectId, workspace } = loadConfig(agent);
271
+ // `agent` keys the sessions dir (~/.openclaw/agents/<agent>/sessions); the
272
+ // workspace holds the memory files and the per-workspace config override.
273
+ const { host, apiKey, projectId } = loadConfig(workspace);
268
274
  console.log(`Host: ${host}`);
269
275
  console.log(`Project: ${projectId}`);
270
276
  console.log(`Agent: ${agent}`);