memorylake-openclaw 0.0.7 → 0.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorylake-openclaw",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "description": "MemoryLake memory backend for OpenClaw",
6
6
  "license": "MIT",
@@ -0,0 +1,52 @@
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
+
7
+ // Parse --agent
8
+ const args = process.argv.slice(2);
9
+ const agentIdx = args.indexOf("--agent");
10
+ if (agentIdx === -1 || !args[agentIdx + 1]) {
11
+ console.error("Usage: node get-config.mjs --agent <agentId>");
12
+ process.exit(1);
13
+ }
14
+ const agentId = args[agentIdx + 1];
15
+
16
+ // Read global config
17
+ const openclawPath = join(homedir(), ".openclaw", "openclaw.json");
18
+ const openclaw = JSON.parse(readFileSync(openclawPath, "utf-8"));
19
+ const globalCfg = openclaw?.plugins?.entries?.["memorylake-openclaw"]?.config;
20
+ if (!globalCfg) {
21
+ console.error("Error: memorylake-openclaw plugin config not found");
22
+ process.exit(1);
23
+ }
24
+
25
+ // Resolve workspace
26
+ const agents = openclaw?.agents;
27
+ const agentEntry = agents?.list?.find((a) => a.id === agentId);
28
+ const workspace = agentEntry?.workspace || agents?.defaults?.workspace;
29
+ if (!workspace) {
30
+ console.error(`Error: no workspace found for agent "${agentId}"`);
31
+ process.exit(1);
32
+ }
33
+
34
+ // Merge per-agent overrides
35
+ const merged = { ...globalCfg };
36
+ const localPath = join(workspace, ".memorylake", "config.json");
37
+ if (existsSync(localPath)) {
38
+ const raw = JSON.parse(readFileSync(localPath, "utf-8"));
39
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
40
+ Object.assign(merged, raw);
41
+ }
42
+ }
43
+ merged.host = merged.host || "https://app.memorylake.ai";
44
+ merged.workspace = workspace;
45
+
46
+ // Validate required fields
47
+ if (!merged.apiKey || !merged.projectId) {
48
+ console.error("Error: apiKey and projectId are required");
49
+ process.exit(1);
50
+ }
51
+
52
+ console.log(JSON.stringify(merged, null, 2));
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: memorylake-api
3
+ description: Use when the user asks about MemoryLake features, capabilities, or wants to perform a MemoryLake action but no specific tool or skill matches. This is the catch-all for any MemoryLake-related request -- discovers available APIs from the remote OpenAPI spec and calls them directly.
4
+ ---
5
+
6
+ # MemoryLake API
7
+
8
+ ## Overview
9
+
10
+ Directly call MemoryLake's REST APIs by discovering endpoints from the live OpenAPI spec. This skill covers any MemoryLake capability not already handled by a dedicated tool or skill — project management, document management, file uploads, memory trace, statistics, and more.
11
+
12
+ ## When to Use
13
+
14
+ - User asks about MemoryLake capabilities or features, and no existing tool or skill covers the request
15
+ - User wants to manage MemoryLake projects (create, update, delete, list, view stats)
16
+ - User wants to manage documents (upload files, add to project, list, delete)
17
+ - User wants to view the change history (trace) of a memory
18
+ - User wants to call any MemoryLake API endpoint directly
19
+
20
+ ## Step 1 — Read MemoryLake Config
21
+
22
+ Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `../common/get-config.mjs`):
23
+
24
+ ```bash
25
+ node {path-to-this-skill}/../common/get-config.mjs --agent {agent}
26
+ ```
27
+
28
+ Where `{agent}` is the current agent ID. The script outputs JSON config with `host`, `apiKey`, `projectId`, etc.
29
+
30
+ If the script exits with an error, stop and inform the user.
31
+
32
+ Auth header for all requests:
33
+
34
+ ```
35
+ Authorization: Bearer {apiKey}
36
+ ```
37
+
38
+ ## Step 2 — Identify the Right API Endpoint
39
+
40
+ ### Option A: Use the Quick Reference Table
41
+
42
+ If the user's intent clearly maps to one of the endpoints below, skip to Step 3.
43
+
44
+ #### Projects
45
+
46
+ | Method | Path | Description |
47
+ |--------|------|-------------|
48
+ | `GET` | `/api/v1/projects` | List projects (paginated, filterable by name) |
49
+ | `POST` | `/api/v1/projects` | Create a new project |
50
+ | `GET` | `/api/v1/projects/{id}` | Get project details (includes stats) |
51
+ | `PUT` | `/api/v1/projects/{id}` | Update project name/description |
52
+ | `DELETE` | `/api/v1/projects/{id}` | Delete a project |
53
+
54
+ #### Memories (V2)
55
+
56
+ | Method | Path | Description |
57
+ |--------|------|-------------|
58
+ | `POST` | `/api/v2/projects/{id}/memories` | Add memory from conversation |
59
+ | `GET` | `/api/v2/projects/{id}/memories` | List memories (paginated, filter by `user_id`/`keyword`) |
60
+ | `POST` | `/api/v2/projects/{id}/memories/search` | Search memories by natural language |
61
+ | `GET` | `/api/v2/projects/{id}/memories/{memoryId}` | Get a single memory |
62
+ | `DELETE` | `/api/v2/projects/{id}/memories/{memoryId}` | Delete a memory |
63
+ | `GET` | `/api/v2/projects/{id}/memories/{memoryId}/trace` | Get memory change history |
64
+
65
+ #### Documents
66
+
67
+ | Method | Path | Description |
68
+ |--------|------|-------------|
69
+ | `GET` | `/api/v1/projects/{id}/documents` | List documents in project (paginated) |
70
+ | `GET` | `/api/v1/projects/{id}/documents/{documentId}` | Get document details |
71
+ | `DELETE` | `/api/v1/projects/{id}/documents` | Batch delete documents |
72
+ | `POST` | `/api/v1/projects/{id}/documents/search` | Semantic search over documents |
73
+
74
+ ### Option B: Fetch the Live OpenAPI Spec
75
+
76
+ If the user's request is ambiguous or might involve a new/undocumented endpoint, fetch the spec:
77
+
78
+ ```bash
79
+ curl -s "{host}/openapi/memorylake/api-docs/open-api" | jq '.paths | keys'
80
+ ```
81
+
82
+ To inspect a specific endpoint's schema:
83
+
84
+ ```bash
85
+ curl -s "{host}/openapi/memorylake/api-docs/open-api" | jq '.paths["/api/v1/projects/{id}"]'
86
+ ```
87
+
88
+ To inspect request/response schemas:
89
+
90
+ ```bash
91
+ curl -s "{host}/openapi/memorylake/api-docs/open-api" | jq '.components.schemas["ProjectCreateRequest"]'
92
+ ```
93
+
94
+ ## Step 3 — Construct and Execute the API Call
95
+
96
+ Build a `curl` command using:
97
+
98
+ - **Base URL**: `{host}/openapi/memorylake` (the server base path from the OpenAPI spec)
99
+ - **Full URL**: `{host}/openapi/memorylake{path}` (e.g., `{host}/openapi/memorylake/api/v1/projects`)
100
+ - **Path params**: replace `{id}` with `{projectId}` from config (for project-scoped endpoints)
101
+ - **Auth header**: `Authorization: Bearer {apiKey}`
102
+ - **Content-Type**: `application/json` (for POST/PUT requests with a body)
103
+
104
+ ### Example: List Projects
105
+
106
+ ```bash
107
+ curl -s -X GET "{host}/openapi/memorylake/api/v1/projects?page=1&size=20" \
108
+ -H "Authorization: Bearer {apiKey}" | jq
109
+ ```
110
+
111
+ ### Example: Create a Project
112
+
113
+ ```bash
114
+ curl -s -X POST "{host}/openapi/memorylake/api/v1/projects" \
115
+ -H "Authorization: Bearer {apiKey}" \
116
+ -H "Content-Type: application/json" \
117
+ -d '{
118
+ "name": "My New Project",
119
+ "description": "Project description"
120
+ }' | jq
121
+ ```
122
+
123
+ ### Example: Get Project Details (with Stats)
124
+
125
+ ```bash
126
+ curl -s -X GET "{host}/openapi/memorylake/api/v1/projects/{projectId}" \
127
+ -H "Authorization: Bearer {apiKey}" | jq
128
+ ```
129
+
130
+ ### Example: Get Memory Trace
131
+
132
+ ```bash
133
+ curl -s -X GET "{host}/openapi/memorylake/api/v2/projects/{projectId}/memories/{memoryId}/trace" \
134
+ -H "Authorization: Bearer {apiKey}" | jq
135
+ ```
136
+
137
+ ### Example: Search Documents
138
+
139
+ ```bash
140
+ curl -s -X POST "{host}/openapi/memorylake/api/v1/projects/{projectId}/documents/search" \
141
+ -H "Authorization: Bearer {apiKey}" \
142
+ -H "Content-Type: application/json" \
143
+ -d '{
144
+ "query": "quarterly sales figures",
145
+ "top_n": 10
146
+ }' | jq
147
+ ```
148
+
149
+ ## Step 4 — Present Results
150
+
151
+ Parse the JSON response and present it to the user in a readable format:
152
+
153
+ - Check `success` field — if `false`, report `message` and `error_code`
154
+ - For list/search responses, format the `data.items` or `data.results` array as a table or structured list
155
+ - For single-item responses, display key fields clearly
156
+ - For paginated responses, report `page`, `total`, `total_pages` so the user knows if there are more results
157
+
158
+ ## Error Handling
159
+
160
+ All responses follow the same wrapper format:
161
+
162
+ ```json
163
+ {
164
+ "success": true|false,
165
+ "message": "Human-readable message",
166
+ "data": { ... },
167
+ "error_code": "VALIDATION_ERROR"
168
+ }
169
+ ```
170
+
171
+ | HTTP Status | Meaning | Action |
172
+ |-------------|---------|--------|
173
+ | 200 | Success | Parse `data` field |
174
+ | 400 | Invalid request | Check `message` for validation details |
175
+ | 404 | Not found | Verify project ID / memory ID / document ID |
176
+ | 401/403 | Auth failure | Verify `apiKey` is correct and not expired |
177
+
178
+ ## Common Mistakes
179
+
180
+ - **Wrong base URL**: The full URL must include `/openapi/memorylake` before the API path. E.g., `/openapi/memorylake/api/v1/projects`, NOT just `/api/v1/projects`
181
+ - **Missing auth header**: Every request requires `Authorization: Bearer {apiKey}`
182
+ - **Hardcoded project ID**: Always read `projectId` from the config script output, not from user input (unless the user explicitly wants a different project)
183
+ - **Pagination**: List endpoints default to `page=1, size=20`. Pass `page` and `size` query params if the user needs more results
184
+
185
+ ## Quick Reference
186
+
187
+ | Item | Value |
188
+ |------|-------|
189
+ | Config script | `{path-to-this-skill}/../common/get-config.mjs --agent {agent}` |
190
+ | Server base path | `/openapi/memorylake` |
191
+ | OpenAPI spec URL | `{host}/openapi/memorylake/api-docs/open-api` |
192
+ | Auth header | `Authorization: Bearer {apiKey}` |
193
+ | Default host | `https://app.memorylake.ai` |
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: memorylake-upload
3
+ description: Use when the user wants to upload files, documents, PDFs, or other data files to MemoryLake and associate them with a project.
4
+ ---
5
+
6
+ # MemoryLake File Upload
7
+
8
+ ## Overview
9
+
10
+ Upload local files to MemoryLake using the multipart upload API, then associate them with a project.
11
+
12
+ ## When to Use
13
+
14
+ - User wants to upload a file (PDF, DOCX, image, etc.) to MemoryLake
15
+ - User wants to add a local document to a MemoryLake project
16
+
17
+ ## Step 1 -- Read MemoryLake Config
18
+
19
+ Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `../common/get-config.mjs`):
20
+
21
+ ```bash
22
+ node {path-to-this-skill}/../common/get-config.mjs --agent {agent}
23
+ ```
24
+
25
+ Where `{agent}` is the current agent ID. The script outputs JSON config with `host`, `apiKey`, `projectId`, etc.
26
+
27
+ If the script exits with an error, stop and inform the user.
28
+
29
+ ## Step 2 -- Run the Upload Script
30
+
31
+ The upload script is at `scripts/upload.mjs` relative to **this skill's SKILL.md**.
32
+
33
+ ```bash
34
+ node {path-to-this-skill}/scripts/upload.mjs \
35
+ --host {host} \
36
+ --api-key {apiKey} \
37
+ --project-id {projectId} \
38
+ --file-name {fileName} \
39
+ /path/to/file
40
+ ```
41
+
42
+ `--file-name` is the original file name as provided by the user (e.g., `report-Q1.pdf`). This is required because the local file path may be a temp path or renamed file that doesn't reflect the real name.
43
+
44
+ ## Step 3 -- Handle Output
45
+
46
+ The script prints progress for each step (create upload, upload parts, complete, add to project).
47
+
48
+ - **Success**: Report the document ID and file name to the user
49
+ - **Failure**: The script prints the specific error (file not found, auth failed, API error). Read the error message and relay it to the user — don't guess the cause
50
+
51
+ ## Common Mistakes
52
+
53
+ - **Skipping Step 1**: Directly hardcoding host/apiKey/projectId instead of using the config script
54
+ - **Relative file paths**: Always resolve the user's file path to an absolute path before passing to the script
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MemoryLake File Upload Script
5
+ *
6
+ * Uploads a local file to MemoryLake using multipart upload,
7
+ * then associates it with a project.
8
+ *
9
+ * Usage:
10
+ * node upload.mjs --host <url> --api-key <key> --project-id <id> <file_path>
11
+ *
12
+ * Parameters:
13
+ * host - Base URL (e.g., http://10.71.10.71:3002)
14
+ * apiKey - API key for authentication
15
+ * projectId - Project ID to associate the document with (required)
16
+ * filePath - Path to the file to upload
17
+ */
18
+
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+ import https from 'https';
22
+ import http from 'http';
23
+
24
+ // API base path
25
+ const API_BASE = '/openapi/memorylake';
26
+
27
+ /**
28
+ * Make an HTTP request
29
+ */
30
+ function request(method, urlStr, body = null, headers = {}) {
31
+ return new Promise((resolve, reject) => {
32
+ const url = new URL(urlStr);
33
+ const isHttps = url.protocol === 'https:';
34
+ const lib = isHttps ? https : http;
35
+
36
+ const options = {
37
+ hostname: url.hostname,
38
+ port: url.port || (isHttps ? 443 : 80),
39
+ path: url.pathname + url.search,
40
+ method,
41
+ headers: {
42
+ ...headers,
43
+ },
44
+ };
45
+
46
+ if (body && typeof body === 'object' && !(body instanceof Buffer)) {
47
+ options.headers['Content-Type'] = 'application/json';
48
+ }
49
+
50
+ const req = lib.request(options, (res) => {
51
+ const chunks = [];
52
+ res.on('data', (chunk) => chunks.push(chunk));
53
+ res.on('end', () => {
54
+ const buffer = Buffer.concat(chunks);
55
+ const text = buffer.toString('utf8');
56
+ resolve({
57
+ status: res.statusCode,
58
+ headers: res.headers,
59
+ body: text,
60
+ });
61
+ });
62
+ });
63
+
64
+ req.on('error', reject);
65
+
66
+ if (body) {
67
+ if (body instanceof Buffer) {
68
+ req.write(body);
69
+ } else if (typeof body === 'object') {
70
+ req.write(JSON.stringify(body));
71
+ } else {
72
+ req.write(body);
73
+ }
74
+ }
75
+
76
+ req.end();
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Create multipart upload session
82
+ */
83
+ async function createMultipartUpload(host, apiKey, fileSize) {
84
+ const url = `${host}${API_BASE}/api/v1/upload/create-multipart`;
85
+ const response = await request('POST', url, { file_size: fileSize }, {
86
+ 'Authorization': `Bearer ${apiKey}`,
87
+ 'Content-Type': 'application/json',
88
+ });
89
+
90
+ const data = JSON.parse(response.body);
91
+ if (!data.success) {
92
+ throw new Error(`Create multipart failed: ${data.message || JSON.stringify(data)}`);
93
+ }
94
+ return data.data;
95
+ }
96
+
97
+ /**
98
+ * Upload a single part to pre-signed URL
99
+ */
100
+ async function uploadPart(uploadUrl, buffer, partNumber, totalParts) {
101
+ process.stdout.write(` Uploading part ${partNumber + 1}/${totalParts}...`);
102
+
103
+ const response = await request('PUT', uploadUrl, buffer, {
104
+ 'Content-Length': buffer.length.toString(),
105
+ });
106
+
107
+ if (response.status < 200 || response.status >= 300) {
108
+ throw new Error(`Part upload failed with status ${response.status}: ${response.body}`);
109
+ }
110
+
111
+ // Get ETag from response headers (may be quoted)
112
+ let etag = response.headers['etag'] || response.headers['ETag'];
113
+ if (etag) {
114
+ etag = etag.replace(/"/g, '');
115
+ }
116
+
117
+ console.log(` done (ETag: ${etag || 'none'})`);
118
+ return etag;
119
+ }
120
+
121
+ /**
122
+ * Complete multipart upload
123
+ */
124
+ async function completeMultipartUpload(host, apiKey, uploadId, objectKey, partEtags) {
125
+ const url = `${host}${API_BASE}/api/v1/upload/complete-multipart`;
126
+ const response = await request('POST', url, {
127
+ upload_id: uploadId,
128
+ object_key: objectKey,
129
+ part_etags: partEtags,
130
+ }, {
131
+ 'Authorization': `Bearer ${apiKey}`,
132
+ 'Content-Type': 'application/json',
133
+ });
134
+
135
+ const data = JSON.parse(response.body);
136
+ if (!data.success) {
137
+ throw new Error(`Complete multipart failed: ${data.message || JSON.stringify(data)}`);
138
+ }
139
+ return data;
140
+ }
141
+
142
+ /**
143
+ * Add document to project
144
+ */
145
+ async function quickAddDocument(host, apiKey, projectId, objectKey, fileName) {
146
+ const url = `${host}${API_BASE}/api/v1/projects/${projectId}/documents/quick-add`;
147
+ const response = await request('POST', url, {
148
+ object_key: objectKey,
149
+ file_name: fileName,
150
+ }, {
151
+ 'Authorization': `Bearer ${apiKey}`,
152
+ 'Content-Type': 'application/json',
153
+ });
154
+
155
+ const data = JSON.parse(response.body);
156
+ if (!data.success) {
157
+ throw new Error(`Quick add document failed: ${data.message || JSON.stringify(data)}`);
158
+ }
159
+ return data.data;
160
+ }
161
+
162
+ /**
163
+ * Format file size for display
164
+ */
165
+ function formatSize(bytes) {
166
+ if (bytes < 1024) return `${bytes} B`;
167
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
168
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
169
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
170
+ }
171
+
172
+ /**
173
+ * Main upload function
174
+ */
175
+ export async function upload({ host, apiKey, projectId, filePath, fileName }) {
176
+ if (!host) throw new Error('host is required');
177
+ if (!apiKey) throw new Error('apiKey is required');
178
+ if (!projectId) throw new Error('projectId is required');
179
+ if (!filePath) throw new Error('filePath is required');
180
+
181
+ if (!fs.existsSync(filePath)) {
182
+ throw new Error(`File not found: ${filePath}`);
183
+ }
184
+
185
+ const stats = fs.statSync(filePath);
186
+ const fileSize = stats.size;
187
+ if (!fileName) fileName = path.basename(filePath);
188
+
189
+ console.log(`\nUploading: ${fileName} (${formatSize(fileSize)})`);
190
+
191
+ // Step 1: Create multipart upload
192
+ console.log('Creating multipart upload...');
193
+ const uploadInfo = await createMultipartUpload(host, apiKey, fileSize);
194
+ const { upload_id, object_key, part_items } = uploadInfo;
195
+ console.log(` Upload ID: ${upload_id}`);
196
+ console.log(` Object Key: ${object_key}`);
197
+ console.log(` Parts: ${part_items.length}`);
198
+
199
+ // Step 2: Upload each part (stream each chunk to avoid loading entire file into memory)
200
+ console.log('\nUploading parts:');
201
+ const fd = fs.openSync(filePath, 'r');
202
+ const partEtags = [];
203
+
204
+ let offset = 0;
205
+ for (const part of part_items) {
206
+ const partBuffer = Buffer.alloc(part.size);
207
+ fs.readSync(fd, partBuffer, 0, part.size, offset);
208
+ const etag = await uploadPart(part.upload_url, partBuffer, part.number, part_items.length);
209
+ partEtags.push({
210
+ number: part.number,
211
+ etag: etag,
212
+ });
213
+ offset += part.size;
214
+ }
215
+ fs.closeSync(fd);
216
+
217
+ // Step 3: Complete multipart upload
218
+ console.log('\nCompleting multipart upload...');
219
+ await completeMultipartUpload(host, apiKey, upload_id, object_key, partEtags);
220
+ console.log(' Upload completed');
221
+
222
+ // Step 4: Add to project
223
+ console.log(`\nAdding to project: ${projectId}`);
224
+ const doc = await quickAddDocument(host, apiKey, projectId, object_key, fileName);
225
+ console.log(' Document added to project');
226
+ console.log(` Document ID: ${doc.document_id}`);
227
+ console.log(` File name: ${doc.file_name}`);
228
+ return doc;
229
+ }
230
+
231
+ // CLI entry point
232
+ async function main() {
233
+ const args = process.argv.slice(2);
234
+
235
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
236
+ console.log(`
237
+ MemoryLake File Upload
238
+
239
+ Usage:
240
+ node upload.mjs --host <url> --api-key <key> --project-id <id> <file_path>
241
+
242
+ Arguments:
243
+ --host Base URL (e.g., http://10.71.10.71:3002)
244
+ --api-key API key for authentication
245
+ --project-id Project ID to associate the document with (required)
246
+ --file-name Custom file name (default: basename of file_path)
247
+ file_path Path to the file to upload
248
+
249
+ Examples:
250
+ node upload.mjs --host http://10.71.10.71:3002 --api-key sk-xxx --project-id proj-abc123 document.pdf
251
+ `);
252
+ process.exit(0);
253
+ }
254
+
255
+ let host, apiKey, projectId, filePath, fileName;
256
+ for (let i = 0; i < args.length; i++) {
257
+ if (args[i] === '--host') host = args[++i];
258
+ else if (args[i] === '--api-key') apiKey = args[++i];
259
+ else if (args[i] === '--project-id') projectId = args[++i];
260
+ else if (args[i] === '--file-name') fileName = args[++i];
261
+ else if (!args[i].startsWith('-')) filePath = args[i];
262
+ }
263
+
264
+ try {
265
+ await upload({ host, apiKey, projectId, filePath, fileName });
266
+ console.log('\nDone!\n');
267
+ } catch (err) {
268
+ console.error(`\nError: ${err.message}\n`);
269
+ process.exit(1);
270
+ }
271
+ }
272
+
273
+ main();
@@ -17,8 +17,8 @@ Extract memory files and conversation history from session files, then submit th
17
17
  ## Prerequisites
18
18
 
19
19
  The caller must provide:
20
+ - **`agent`**: The agent name (e.g., `main`). Used to resolve config (Step 1) and locate session files (Step 2).
20
21
  - **`user_id`**: The user ID for filtering sessions (e.g., a Feishu user ID like `ou_xxx`). **This is only used for session filtering, NOT for the API request.**
21
- - **`agent`**: The agent name (e.g., `main`)
22
22
 
23
23
  ## Preferred: Run the Migration Script
24
24
 
@@ -40,26 +40,15 @@ If the script fails, follow these steps manually.
40
40
 
41
41
  ### Step 1 — Read MemoryLake Config
42
42
 
43
- Read `~/.openclaw/openclaw.json` and extract the plugin config:
43
+ Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `../common/get-config.mjs`):
44
44
 
45
45
  ```bash
46
- cat ~/.openclaw/openclaw.json | jq '.plugins.entries["memorylake-openclaw"].config'
46
+ node {path-to-this-skill}/../common/get-config.mjs --agent {agent}
47
47
  ```
48
48
 
49
- Extract these values:
50
- - **`host`** — API host (default: `https://app.memorylake.ai`)
51
- - **`apiKey`** — API key for authentication
52
- - **`projectId`** — MemoryLake project ID
49
+ The script outputs JSON config with `host`, `apiKey`, `projectId`, `workspace`, etc. If the script exits with an error, stop and inform the user.
53
50
 
54
- ### Step 2 — Identify User and Agent
55
-
56
- Use the `user_id` and `agent` provided by the caller:
57
- - `user_id` — only for filtering sessions in Step 3 (session keys contain the user ID)
58
- - `agent` — determines which agent's session directory to read
59
-
60
- **When POSTing to the API, always use `"user_id": "default"`. Do NOT use the caller-provided `user_id`.**
61
-
62
- ### Step 3 — Filter Sessions by User ID
51
+ ### Step 2 — Filter Sessions by User ID
63
52
 
64
53
  **You MUST use `sessions.json` to filter sessions. Do NOT grep/search JSONL files directly.**
65
54
 
@@ -83,29 +72,21 @@ Use the `user_id` and `agent` provided by the caller:
83
72
  ~/.openclaw/agents/{agent}/sessions/{sessionId}.jsonl
84
73
  ```
85
74
 
86
- ### Step 4 — Read Memory Files
87
-
88
- Resolve the workspace path for the agent:
89
-
90
- 1. Check `agents.list` for an entry with `id` matching the agent. If it has a `workspace` field, use it.
91
- 2. Otherwise, fall back to `agents.defaults.workspace`.
75
+ ### Step 3 — Read Memory Files
92
76
 
93
- ```bash
94
- # Agent-specific workspace (e.g., agent "foo"):
95
- cat ~/.openclaw/openclaw.json | jq -r '.agents.list[] | select(.id == "{agent}") | .workspace'
96
- # Default workspace:
97
- cat ~/.openclaw/openclaw.json | jq -r '.agents.defaults.workspace'
98
- ```
99
-
100
- Then read:
77
+ Use the `workspace` path from the config output in Step 1. Then read:
101
78
  - `{workspace}/MEMORY.md`
102
79
  - All files in `{workspace}/memory/` directory
103
80
 
104
81
  These contain curated memory that should also be migrated.
105
82
 
106
- ### Step 5 — Submit Data to MemoryLake
83
+ ### Step 4 — Submit Data to MemoryLake
84
+
85
+ Use `host`, `apiKey`, `projectId` from the config output in Step 1.
86
+
87
+ **When POSTing to the API, always use `"user_id": "default"`. Do NOT use the caller-provided `user_id`.**
107
88
 
108
- #### 5a — Submit Session Conversations
89
+ #### 4a — Submit Session Conversations
109
90
 
110
91
  For each matched `.jsonl` session file:
111
92
 
@@ -156,7 +137,7 @@ For each matched `.jsonl` session file:
156
137
  }
157
138
  }
158
139
  ```
159
- #### 5b — Submit Memory Files
140
+ #### 4b — Submit Memory Files
160
141
 
161
142
  For each memory file (`MEMORY.md` and files in `memory/`):
162
143
 
@@ -203,11 +184,10 @@ At the end, print a summary:
203
184
 
204
185
  | Item | Path / Value |
205
186
  |------|-------------|
206
- | Config file | `~/.openclaw/openclaw.json` |
207
- | Plugin config key | `plugins.entries["memorylake-openclaw"].config` |
187
+ | Config script | `{path-to-this-skill}/../common/get-config.mjs --agent {agent}` |
208
188
  | Session index | `~/.openclaw/agents/{agent}/sessions/sessions.json` |
209
189
  | Session files | `~/.openclaw/agents/{agent}/sessions/{id}.jsonl` |
210
- | Workspace path | `agents.defaults.workspace` in config |
190
+ | Workspace path | from config script output (`workspace` field) |
211
191
  | API endpoint | `{host}/openapi/memorylake/api/v2/projects/{projectId}/memories` |
212
192
  | Auth header | `Authorization: Bearer {apiKey}` |
213
193
  | Default host | `https://app.memorylake.ai` |