memorylake-openclaw 0.0.7 → 0.0.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/package.json
CHANGED
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
Read `~/.openclaw/openclaw.json` and extract the plugin config:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cat ~/.openclaw/openclaw.json | jq '.plugins.entries["memorylake-openclaw"].config'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Extract these values:
|
|
29
|
+
|
|
30
|
+
| Variable | Description | Default |
|
|
31
|
+
|----------|-------------|---------|
|
|
32
|
+
| `host` | API host | `https://app.memorylake.ai` |
|
|
33
|
+
| `apiKey` | API key for authentication | (required) |
|
|
34
|
+
| `projectId` | MemoryLake project ID | (required) |
|
|
35
|
+
|
|
36
|
+
If `apiKey` or `projectId` is missing, stop and inform the user.
|
|
37
|
+
|
|
38
|
+
Auth header for all requests:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Authorization: Bearer {apiKey}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Step 2 — Identify the Right API Endpoint
|
|
45
|
+
|
|
46
|
+
### Option A: Use the Quick Reference Table
|
|
47
|
+
|
|
48
|
+
If the user's intent clearly maps to one of the endpoints below, skip to Step 3.
|
|
49
|
+
|
|
50
|
+
#### Projects
|
|
51
|
+
|
|
52
|
+
| Method | Path | Description |
|
|
53
|
+
|--------|------|-------------|
|
|
54
|
+
| `GET` | `/api/v1/projects` | List projects (paginated, filterable by name) |
|
|
55
|
+
| `POST` | `/api/v1/projects` | Create a new project |
|
|
56
|
+
| `GET` | `/api/v1/projects/{id}` | Get project details (includes stats) |
|
|
57
|
+
| `PUT` | `/api/v1/projects/{id}` | Update project name/description |
|
|
58
|
+
| `DELETE` | `/api/v1/projects/{id}` | Delete a project |
|
|
59
|
+
|
|
60
|
+
#### Memories (V2)
|
|
61
|
+
|
|
62
|
+
| Method | Path | Description |
|
|
63
|
+
|--------|------|-------------|
|
|
64
|
+
| `POST` | `/api/v2/projects/{id}/memories` | Add memory from conversation |
|
|
65
|
+
| `GET` | `/api/v2/projects/{id}/memories` | List memories (paginated, filter by `user_id`/`keyword`) |
|
|
66
|
+
| `POST` | `/api/v2/projects/{id}/memories/search` | Search memories by natural language |
|
|
67
|
+
| `GET` | `/api/v2/projects/{id}/memories/{memoryId}` | Get a single memory |
|
|
68
|
+
| `DELETE` | `/api/v2/projects/{id}/memories/{memoryId}` | Delete a memory |
|
|
69
|
+
| `GET` | `/api/v2/projects/{id}/memories/{memoryId}/trace` | Get memory change history |
|
|
70
|
+
|
|
71
|
+
#### Documents
|
|
72
|
+
|
|
73
|
+
| Method | Path | Description |
|
|
74
|
+
|--------|------|-------------|
|
|
75
|
+
| `GET` | `/api/v1/projects/{id}/documents` | List documents in project (paginated) |
|
|
76
|
+
| `GET` | `/api/v1/projects/{id}/documents/{documentId}` | Get document details |
|
|
77
|
+
| `DELETE` | `/api/v1/projects/{id}/documents` | Batch delete documents |
|
|
78
|
+
| `POST` | `/api/v1/projects/{id}/documents/search` | Semantic search over documents |
|
|
79
|
+
|
|
80
|
+
### Option B: Fetch the Live OpenAPI Spec
|
|
81
|
+
|
|
82
|
+
If the user's request is ambiguous or might involve a new/undocumented endpoint, fetch the spec:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
curl -s "{host}/openapi/memorylake/api-docs/open-api" | jq '.paths | keys'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
To inspect a specific endpoint's schema:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
curl -s "{host}/openapi/memorylake/api-docs/open-api" | jq '.paths["/api/v1/projects/{id}"]'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
To inspect request/response schemas:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
curl -s "{host}/openapi/memorylake/api-docs/open-api" | jq '.components.schemas["ProjectCreateRequest"]'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Step 3 — Construct and Execute the API Call
|
|
101
|
+
|
|
102
|
+
Build a `curl` command using:
|
|
103
|
+
|
|
104
|
+
- **Base URL**: `{host}/openapi/memorylake` (the server base path from the OpenAPI spec)
|
|
105
|
+
- **Full URL**: `{host}/openapi/memorylake{path}` (e.g., `{host}/openapi/memorylake/api/v1/projects`)
|
|
106
|
+
- **Path params**: replace `{id}` with `{projectId}` from config (for project-scoped endpoints)
|
|
107
|
+
- **Auth header**: `Authorization: Bearer {apiKey}`
|
|
108
|
+
- **Content-Type**: `application/json` (for POST/PUT requests with a body)
|
|
109
|
+
|
|
110
|
+
### Example: List Projects
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
curl -s -X GET "{host}/openapi/memorylake/api/v1/projects?page=1&size=20" \
|
|
114
|
+
-H "Authorization: Bearer {apiKey}" | jq
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Example: Create a Project
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
curl -s -X POST "{host}/openapi/memorylake/api/v1/projects" \
|
|
121
|
+
-H "Authorization: Bearer {apiKey}" \
|
|
122
|
+
-H "Content-Type: application/json" \
|
|
123
|
+
-d '{
|
|
124
|
+
"name": "My New Project",
|
|
125
|
+
"description": "Project description"
|
|
126
|
+
}' | jq
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Example: Get Project Details (with Stats)
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
curl -s -X GET "{host}/openapi/memorylake/api/v1/projects/{projectId}" \
|
|
133
|
+
-H "Authorization: Bearer {apiKey}" | jq
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Example: Get Memory Trace
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
curl -s -X GET "{host}/openapi/memorylake/api/v2/projects/{projectId}/memories/{memoryId}/trace" \
|
|
140
|
+
-H "Authorization: Bearer {apiKey}" | jq
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Example: Search Documents
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
curl -s -X POST "{host}/openapi/memorylake/api/v1/projects/{projectId}/documents/search" \
|
|
147
|
+
-H "Authorization: Bearer {apiKey}" \
|
|
148
|
+
-H "Content-Type: application/json" \
|
|
149
|
+
-d '{
|
|
150
|
+
"query": "quarterly sales figures",
|
|
151
|
+
"top_n": 10
|
|
152
|
+
}' | jq
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Step 4 — Present Results
|
|
156
|
+
|
|
157
|
+
Parse the JSON response and present it to the user in a readable format:
|
|
158
|
+
|
|
159
|
+
- Check `success` field — if `false`, report `message` and `error_code`
|
|
160
|
+
- For list/search responses, format the `data.items` or `data.results` array as a table or structured list
|
|
161
|
+
- For single-item responses, display key fields clearly
|
|
162
|
+
- For paginated responses, report `page`, `total`, `total_pages` so the user knows if there are more results
|
|
163
|
+
|
|
164
|
+
## Error Handling
|
|
165
|
+
|
|
166
|
+
All responses follow the same wrapper format:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"success": true|false,
|
|
171
|
+
"message": "Human-readable message",
|
|
172
|
+
"data": { ... },
|
|
173
|
+
"error_code": "VALIDATION_ERROR"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
| HTTP Status | Meaning | Action |
|
|
178
|
+
|-------------|---------|--------|
|
|
179
|
+
| 200 | Success | Parse `data` field |
|
|
180
|
+
| 400 | Invalid request | Check `message` for validation details |
|
|
181
|
+
| 404 | Not found | Verify project ID / memory ID / document ID |
|
|
182
|
+
| 401/403 | Auth failure | Verify `apiKey` is correct and not expired |
|
|
183
|
+
|
|
184
|
+
## Common Mistakes
|
|
185
|
+
|
|
186
|
+
- **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`
|
|
187
|
+
- **Missing auth header**: Every request requires `Authorization: Bearer {apiKey}`
|
|
188
|
+
- **Hardcoded project ID**: Always read `projectId` from `~/.openclaw/openclaw.json` config, not from user input (unless the user explicitly wants a different project)
|
|
189
|
+
- **Pagination**: List endpoints default to `page=1, size=20`. Pass `page` and `size` query params if the user needs more results
|
|
190
|
+
|
|
191
|
+
## Quick Reference
|
|
192
|
+
|
|
193
|
+
| Item | Value |
|
|
194
|
+
|------|-------|
|
|
195
|
+
| OpenClaw config | `~/.openclaw/openclaw.json` |
|
|
196
|
+
| Plugin config key | `plugins.entries["memorylake-openclaw"].config` |
|
|
197
|
+
| Server base path | `/openapi/memorylake` |
|
|
198
|
+
| OpenAPI spec URL | `{host}/openapi/memorylake/api-docs/open-api` |
|
|
199
|
+
| Auth header | `Authorization: Bearer {apiKey}` |
|
|
200
|
+
| Default host | `https://app.memorylake.ai` |
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
Read `~/.openclaw/openclaw.json` and extract the plugin config:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cat ~/.openclaw/openclaw.json | jq '.plugins.entries["memorylake-openclaw"].config'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Extract these values:
|
|
26
|
+
|
|
27
|
+
| Variable | Description | Default |
|
|
28
|
+
|----------|-------------|---------|
|
|
29
|
+
| `host` | API host | `https://app.memorylake.ai` |
|
|
30
|
+
| `apiKey` | API key for authentication | (required) |
|
|
31
|
+
| `projectId` | MemoryLake project ID | (required) |
|
|
32
|
+
|
|
33
|
+
If `apiKey` or `projectId` is missing, stop and inform the user.
|
|
34
|
+
|
|
35
|
+
## Step 2 -- Run the Upload Script
|
|
36
|
+
|
|
37
|
+
The upload script is at `scripts/upload.mjs` relative to **this skill's SKILL.md**.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
node {path-to-this-skill}/scripts/upload.mjs \
|
|
41
|
+
--host {host} \
|
|
42
|
+
--api-key {apiKey} \
|
|
43
|
+
--project-id {projectId} \
|
|
44
|
+
--file-name {fileName} \
|
|
45
|
+
/path/to/file
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`--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.
|
|
49
|
+
|
|
50
|
+
## Step 3 -- Handle Output
|
|
51
|
+
|
|
52
|
+
The script prints progress for each step (create upload, upload parts, complete, add to project).
|
|
53
|
+
|
|
54
|
+
- **Success**: Report the document ID and file name to the user
|
|
55
|
+
- **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
|
|
56
|
+
|
|
57
|
+
## Common Mistakes
|
|
58
|
+
|
|
59
|
+
- **Skipping Step 1**: Directly hardcoding host/apiKey/projectId instead of reading from `~/.openclaw/openclaw.json`
|
|
60
|
+
- **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();
|