memorylake-openclaw 1.0.2-beta.3 → 1.0.2-beta.4
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/index.ts +24 -66
- package/package.json +1 -1
- package/skills/memorylake-upload/SKILL.md +5 -24
- package/skills/memorylake-upload/scripts/upload.mjs +4 -237
package/index.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Features:
|
|
7
7
|
* - 9 tools: memory_search, memory_list, memory_store, memory_get, memory_forget, document_search, document_download, advanced_web_search, open_data_search
|
|
8
|
-
* - Auto-recall: injects
|
|
8
|
+
* - Auto-recall: injects memory-search instructions (+ open-data categories) into the system prompt before each agent turn
|
|
9
9
|
* - Auto-capture: stores key facts scoped to the current session after each agent turn
|
|
10
|
-
* - CLI: openclaw memorylake search, openclaw memorylake stats
|
|
10
|
+
* - CLI: openclaw memorylake search, openclaw memorylake stats
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import fs from "node:fs";
|
|
@@ -994,6 +994,23 @@ const memoryPlugin = {
|
|
|
994
994
|
content: r.content,
|
|
995
995
|
created_at: r.created_at,
|
|
996
996
|
}));
|
|
997
|
+
|
|
998
|
+
// Check for unresolved conflicts among returned memories
|
|
999
|
+
const conflictMemoryIds = results
|
|
1000
|
+
.filter((r) => r.has_unresolved_conflict)
|
|
1001
|
+
.map((r) => r.id);
|
|
1002
|
+
if (conflictMemoryIds.length > 0) {
|
|
1003
|
+
try {
|
|
1004
|
+
const effectiveUserId = userId ?? effectiveCfg.userId;
|
|
1005
|
+
const conflicts = await effectiveProvider.listConflicts(conflictMemoryIds, effectiveUserId);
|
|
1006
|
+
if (conflicts.length > 0) {
|
|
1007
|
+
const conflictText = buildConflictContext(conflicts);
|
|
1008
|
+
sections.push(`## Memory Conflicts\nThe following memories have unresolved conflicts. Review and help the user resolve them if relevant:\n\n${conflictText}`);
|
|
1009
|
+
}
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
sections.push(`## Memory Conflicts\nFailed to fetch conflicts: ${String(err)}`);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
997
1014
|
} else if (memoryResult.status === "rejected") {
|
|
998
1015
|
sections.push(`## Memories\nMemory search failed: ${String(memoryResult.reason)}`);
|
|
999
1016
|
}
|
|
@@ -1011,7 +1028,7 @@ const memoryPlugin = {
|
|
|
1011
1028
|
content: [
|
|
1012
1029
|
{ type: "text", text: "No relevant memories or documents found." },
|
|
1013
1030
|
],
|
|
1014
|
-
details: {
|
|
1031
|
+
details: { memoryCount: 0, documentCount: 0, memories: [] },
|
|
1015
1032
|
};
|
|
1016
1033
|
}
|
|
1017
1034
|
|
|
@@ -1681,65 +1698,6 @@ const memoryPlugin = {
|
|
|
1681
1698
|
}
|
|
1682
1699
|
});
|
|
1683
1700
|
|
|
1684
|
-
memorylake
|
|
1685
|
-
.command("upload")
|
|
1686
|
-
.description("Upload files or directories to MemoryLake")
|
|
1687
|
-
.argument("<path>", "File or directory path to upload")
|
|
1688
|
-
.option("--agent <id>", "Agent ID (resolves workspace and per-agent projectId)")
|
|
1689
|
-
.option("--project-id <id>", "Override project ID (takes precedence over --agent)")
|
|
1690
|
-
.action(async (targetPath: string, opts: { agent?: string; projectId?: string }) => {
|
|
1691
|
-
// Resolve effective config: --project-id > agent workspace config > global config
|
|
1692
|
-
let effectiveCfg = cfg;
|
|
1693
|
-
if (opts.agent) {
|
|
1694
|
-
try {
|
|
1695
|
-
const openclawPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
1696
|
-
const openclaw = JSON.parse(fs.readFileSync(openclawPath, "utf-8"));
|
|
1697
|
-
const agents = openclaw?.agents;
|
|
1698
|
-
const agentEntry = agents?.list?.find((a: any) => a.id === opts.agent);
|
|
1699
|
-
const workspace = agentEntry?.workspace || agents?.defaults?.workspace;
|
|
1700
|
-
if (workspace) {
|
|
1701
|
-
effectiveCfg = resolveConfig({ workspaceDir: workspace });
|
|
1702
|
-
} else {
|
|
1703
|
-
console.warn(`Warning: no workspace found for agent "${opts.agent}", using global config.`);
|
|
1704
|
-
}
|
|
1705
|
-
} catch (err) {
|
|
1706
|
-
console.warn(`Warning: failed to resolve agent config: ${String(err)}, using global config.`);
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
const effectiveProjectId = opts.projectId || effectiveCfg.projectId;
|
|
1710
|
-
if (!effectiveProjectId) {
|
|
1711
|
-
console.error("No project ID configured. Use --project-id or set up agent/workspace config.");
|
|
1712
|
-
return;
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
// Lazy import upload.mjs (use uploadAuto to support archives)
|
|
1716
|
-
let uploadFn: (o: { host: string; apiKey: string; projectId: string; filePath: string; fileName: string }) => Promise<unknown>;
|
|
1717
|
-
try {
|
|
1718
|
-
const uploadModule = await import(
|
|
1719
|
-
/* webpackIgnore: true */
|
|
1720
|
-
new URL("./skills/memorylake-upload/scripts/upload.mjs", import.meta.url).href
|
|
1721
|
-
);
|
|
1722
|
-
uploadFn = uploadModule.uploadAuto;
|
|
1723
|
-
} catch (err) {
|
|
1724
|
-
console.error(`Failed to load upload module: ${String(err)}`);
|
|
1725
|
-
return;
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
const absPath = path.resolve(targetPath);
|
|
1729
|
-
|
|
1730
|
-
try {
|
|
1731
|
-
await uploadFn({
|
|
1732
|
-
host: effectiveCfg.host,
|
|
1733
|
-
apiKey: effectiveCfg.apiKey,
|
|
1734
|
-
projectId: effectiveProjectId,
|
|
1735
|
-
filePath: absPath,
|
|
1736
|
-
fileName: path.basename(absPath),
|
|
1737
|
-
});
|
|
1738
|
-
} catch (err) {
|
|
1739
|
-
console.error(`Upload failed: ${String(err)}`);
|
|
1740
|
-
}
|
|
1741
|
-
});
|
|
1742
|
-
|
|
1743
1701
|
memorylake
|
|
1744
1702
|
.command("stats")
|
|
1745
1703
|
.description("Show memory statistics from MemoryLake")
|
|
@@ -1816,7 +1774,7 @@ const memoryPlugin = {
|
|
|
1816
1774
|
// Auto-upload: upload inbound files to MemoryLake before prompt build
|
|
1817
1775
|
if (cfg.autoUpload) {
|
|
1818
1776
|
// Lazy-load upload function from upload.mjs
|
|
1819
|
-
let
|
|
1777
|
+
let uploadFn: ((opts: { host: string; apiKey: string; projectId: string; filePath: string; fileName: string }) => Promise<unknown>) | undefined;
|
|
1820
1778
|
|
|
1821
1779
|
api.on("before_prompt_build", (event, ctx) => {
|
|
1822
1780
|
if ((ctx as any)?.trigger !== "user") {
|
|
@@ -1841,17 +1799,17 @@ const memoryPlugin = {
|
|
|
1841
1799
|
// Fire-and-forget: upload asynchronously without blocking
|
|
1842
1800
|
(async () => {
|
|
1843
1801
|
// Lazy import upload.mjs
|
|
1844
|
-
if (!
|
|
1802
|
+
if (!uploadFn) {
|
|
1845
1803
|
const uploadModule = await import(
|
|
1846
1804
|
/* webpackIgnore: true */
|
|
1847
1805
|
new URL("./skills/memorylake-upload/scripts/upload.mjs", import.meta.url).href
|
|
1848
1806
|
);
|
|
1849
|
-
|
|
1807
|
+
uploadFn = uploadModule.upload;
|
|
1850
1808
|
}
|
|
1851
1809
|
|
|
1852
1810
|
for (const { filePath, stat } of filesToUpload) {
|
|
1853
1811
|
try {
|
|
1854
|
-
await
|
|
1812
|
+
await uploadFn!({
|
|
1855
1813
|
host: effectiveCfg.host,
|
|
1856
1814
|
apiKey: effectiveCfg.apiKey,
|
|
1857
1815
|
projectId: effectiveCfg.projectId,
|
package/package.json
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memorylake-upload
|
|
3
|
-
description: Use when the user wants to upload files, documents, PDFs,
|
|
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
4
|
---
|
|
5
5
|
|
|
6
6
|
# MemoryLake File Upload
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Upload local files
|
|
10
|
+
Upload local files to MemoryLake using the multipart upload API, then associate them with a project.
|
|
11
11
|
|
|
12
12
|
## When to Use
|
|
13
13
|
|
|
14
14
|
- User wants to upload a file (PDF, DOCX, image, etc.) to MemoryLake
|
|
15
15
|
- User wants to add a local document to a MemoryLake project
|
|
16
|
-
- User wants to upload an archive (zip, tar.gz, tgz, tar.bz2, tbz2, tar.xz, txz, tar, gz, bz2, xz, rar, 7z) — files inside will be extracted and uploaded one by one
|
|
17
|
-
- User wants to upload an entire directory/folder — all files will be recursively collected and uploaded
|
|
18
16
|
|
|
19
17
|
## Step 1 -- Read MemoryLake Config
|
|
20
18
|
|
|
@@ -32,39 +30,22 @@ If the script exits with an error, stop and inform the user.
|
|
|
32
30
|
|
|
33
31
|
The upload script is at `scripts/upload.mjs` relative to **this skill's SKILL.md**.
|
|
34
32
|
|
|
35
|
-
The script automatically detects the input type and handles it accordingly — plain files are uploaded directly, archives are extracted first, and directories are recursively traversed.
|
|
36
|
-
|
|
37
33
|
```bash
|
|
38
|
-
# Single file
|
|
39
34
|
node {path-to-this-skill}/scripts/upload.mjs \
|
|
40
35
|
--host {host} \
|
|
41
36
|
--api-key {apiKey} \
|
|
42
37
|
--project-id {projectId} \
|
|
43
38
|
--file-name {fileName} \
|
|
44
39
|
/path/to/file
|
|
45
|
-
|
|
46
|
-
# Archive (auto-detected, extracted, each file uploaded)
|
|
47
|
-
node {path-to-this-skill}/scripts/upload.mjs \
|
|
48
|
-
--host {host} \
|
|
49
|
-
--api-key {apiKey} \
|
|
50
|
-
--project-id {projectId} \
|
|
51
|
-
/path/to/archive.zip
|
|
52
|
-
|
|
53
|
-
# Directory (recursively uploads all files inside)
|
|
54
|
-
node {path-to-this-skill}/scripts/upload.mjs \
|
|
55
|
-
--host {host} \
|
|
56
|
-
--api-key {apiKey} \
|
|
57
|
-
--project-id {projectId} \
|
|
58
|
-
/path/to/my-folder/
|
|
59
40
|
```
|
|
60
41
|
|
|
61
|
-
`--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.
|
|
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.
|
|
62
43
|
|
|
63
44
|
## Step 3 -- Handle Output
|
|
64
45
|
|
|
65
|
-
The script prints progress for each step (create upload, upload parts, complete, add to project).
|
|
46
|
+
The script prints progress for each step (create upload, upload parts, complete, add to project).
|
|
66
47
|
|
|
67
|
-
- **Success**: Report the document ID and file name to the user
|
|
48
|
+
- **Success**: Report the document ID and file name to the user
|
|
68
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
|
|
69
50
|
|
|
70
51
|
## Common Mistakes
|
|
@@ -20,8 +20,6 @@ import fs from 'fs';
|
|
|
20
20
|
import path from 'path';
|
|
21
21
|
import https from 'https';
|
|
22
22
|
import http from 'http';
|
|
23
|
-
import os from 'os';
|
|
24
|
-
import { execFileSync } from 'child_process';
|
|
25
23
|
|
|
26
24
|
// API base path
|
|
27
25
|
const API_BASE = '/openapi/memorylake';
|
|
@@ -230,233 +228,6 @@ export async function upload({ host, apiKey, projectId, filePath, fileName }) {
|
|
|
230
228
|
return doc;
|
|
231
229
|
}
|
|
232
230
|
|
|
233
|
-
// ============================================================================
|
|
234
|
-
// Archive support
|
|
235
|
-
// ============================================================================
|
|
236
|
-
|
|
237
|
-
const ARCHIVE_EXTENSIONS = [
|
|
238
|
-
'.tar.gz', '.tgz', '.tar.bz2', '.tbz2', '.tar.xz', '.txz',
|
|
239
|
-
'.tar', '.zip', '.gz', '.bz2', '.xz', '.rar', '.7z',
|
|
240
|
-
];
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Check if a file is an archive based on its extension.
|
|
244
|
-
* Returns the matched extension or null.
|
|
245
|
-
*/
|
|
246
|
-
function getArchiveExtension(filePath) {
|
|
247
|
-
const lower = filePath.toLowerCase();
|
|
248
|
-
// Check compound extensions first (e.g. .tar.gz before .gz)
|
|
249
|
-
for (const ext of ARCHIVE_EXTENSIONS) {
|
|
250
|
-
if (lower.endsWith(ext)) return ext;
|
|
251
|
-
}
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** Max buffer size for single-file decompression (gz/bz2/xz) stdout capture: 512 MB */
|
|
256
|
-
const DECOMPRESS_MAX_BUFFER = 512 * 1024 * 1024;
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Extract an archive to the given destination directory.
|
|
260
|
-
*/
|
|
261
|
-
const TOOL_INSTALL_HINTS = {
|
|
262
|
-
unzip: 'Install via: sudo apt install unzip (Linux) / brew install unzip (macOS) / choco install unzip (Windows)',
|
|
263
|
-
tar: 'tar should be pre-installed on most systems (Linux/macOS/Windows 10+)',
|
|
264
|
-
gunzip: 'Install via: sudo apt install gzip (Linux) / brew install gzip (macOS) / choco install gzip (Windows)',
|
|
265
|
-
bunzip2: 'Install via: sudo apt install bzip2 (Linux) / brew install bzip2 (macOS) / choco install bzip2 (Windows)',
|
|
266
|
-
xz: 'Install via: sudo apt install xz-utils (Linux) / brew install xz (macOS) / choco install xz (Windows)',
|
|
267
|
-
unrar: 'Install via: sudo apt install unrar (Linux) / brew install unrar (macOS) / choco install unrar (Windows)',
|
|
268
|
-
'7z': 'Install via: sudo apt install p7zip-full (Linux) / brew install 7zip (macOS) / choco install 7zip (Windows)',
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
function runExtractCommand(tool, args) {
|
|
272
|
-
try {
|
|
273
|
-
execFileSync(tool, args, { stdio: 'pipe' });
|
|
274
|
-
} catch (err) {
|
|
275
|
-
if (err.code === 'ENOENT') {
|
|
276
|
-
const hint = TOOL_INSTALL_HINTS[tool] || '';
|
|
277
|
-
throw new Error(`"${tool}" not found. ${hint}`);
|
|
278
|
-
}
|
|
279
|
-
throw err;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function extractArchive(archivePath, destDir) {
|
|
284
|
-
const ext = getArchiveExtension(archivePath);
|
|
285
|
-
|
|
286
|
-
switch (ext) {
|
|
287
|
-
case '.zip':
|
|
288
|
-
runExtractCommand('unzip', ['-o', archivePath, '-d', destDir]);
|
|
289
|
-
break;
|
|
290
|
-
case '.tar.gz':
|
|
291
|
-
case '.tgz':
|
|
292
|
-
runExtractCommand('tar', ['-xzf', archivePath, '-C', destDir]);
|
|
293
|
-
break;
|
|
294
|
-
case '.tar.bz2':
|
|
295
|
-
case '.tbz2':
|
|
296
|
-
runExtractCommand('tar', ['-xjf', archivePath, '-C', destDir]);
|
|
297
|
-
break;
|
|
298
|
-
case '.tar.xz':
|
|
299
|
-
case '.txz':
|
|
300
|
-
runExtractCommand('tar', ['-xJf', archivePath, '-C', destDir]);
|
|
301
|
-
break;
|
|
302
|
-
case '.tar':
|
|
303
|
-
runExtractCommand('tar', ['-xf', archivePath, '-C', destDir]);
|
|
304
|
-
break;
|
|
305
|
-
case '.gz': {
|
|
306
|
-
const baseName = path.basename(archivePath).slice(0, -3);
|
|
307
|
-
try {
|
|
308
|
-
const output = execFileSync('gunzip', ['-c', archivePath], { maxBuffer: DECOMPRESS_MAX_BUFFER });
|
|
309
|
-
fs.writeFileSync(path.join(destDir, baseName), output);
|
|
310
|
-
} catch (err) {
|
|
311
|
-
if (err.code === 'ENOENT') throw new Error(`"gunzip" not found. ${TOOL_INSTALL_HINTS.gunzip}`);
|
|
312
|
-
throw err;
|
|
313
|
-
}
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
case '.bz2': {
|
|
317
|
-
const baseName = path.basename(archivePath).slice(0, -4);
|
|
318
|
-
try {
|
|
319
|
-
const output = execFileSync('bunzip2', ['-c', archivePath], { maxBuffer: DECOMPRESS_MAX_BUFFER });
|
|
320
|
-
fs.writeFileSync(path.join(destDir, baseName), output);
|
|
321
|
-
} catch (err) {
|
|
322
|
-
if (err.code === 'ENOENT') throw new Error(`"bunzip2" not found. ${TOOL_INSTALL_HINTS.bunzip2}`);
|
|
323
|
-
throw err;
|
|
324
|
-
}
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
case '.xz': {
|
|
328
|
-
const baseName = path.basename(archivePath).slice(0, -3);
|
|
329
|
-
try {
|
|
330
|
-
const output = execFileSync('xz', ['-dc', archivePath], { maxBuffer: DECOMPRESS_MAX_BUFFER });
|
|
331
|
-
fs.writeFileSync(path.join(destDir, baseName), output);
|
|
332
|
-
} catch (err) {
|
|
333
|
-
if (err.code === 'ENOENT') throw new Error(`"xz" not found. ${TOOL_INSTALL_HINTS.xz}`);
|
|
334
|
-
throw err;
|
|
335
|
-
}
|
|
336
|
-
break;
|
|
337
|
-
}
|
|
338
|
-
case '.rar':
|
|
339
|
-
runExtractCommand('unrar', ['x', '-o+', archivePath, destDir + '/']);
|
|
340
|
-
break;
|
|
341
|
-
case '.7z':
|
|
342
|
-
runExtractCommand('7z', ['x', archivePath, `-o${destDir}`, '-y']);
|
|
343
|
-
break;
|
|
344
|
-
default:
|
|
345
|
-
throw new Error(`Unsupported archive format: ${path.basename(archivePath)}`);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Names and directories to skip when collecting files.
|
|
351
|
-
*/
|
|
352
|
-
const IGNORED_NAMES = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
|
|
353
|
-
const IGNORED_DIRS = new Set(['__MACOSX', '.Spotlight-V100', '.Trashes']);
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Recursively collect all files in a directory, skipping hidden/system junk.
|
|
357
|
-
*/
|
|
358
|
-
function collectFiles(dir) {
|
|
359
|
-
const files = [];
|
|
360
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
361
|
-
if (IGNORED_NAMES.has(entry.name)) continue;
|
|
362
|
-
const full = path.join(dir, entry.name);
|
|
363
|
-
if (entry.isDirectory()) {
|
|
364
|
-
if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith('.')) continue;
|
|
365
|
-
files.push(...collectFiles(full));
|
|
366
|
-
} else if (entry.isFile()) {
|
|
367
|
-
files.push(full);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
return files;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Upload multiple files with progress summary.
|
|
375
|
-
* @param {Array<string>} files - absolute paths of files to upload
|
|
376
|
-
* @param {string} label - label for the summary line (e.g. "Archive" / "Directory")
|
|
377
|
-
*/
|
|
378
|
-
async function uploadMany({ host, apiKey, projectId, files, label }) {
|
|
379
|
-
const results = [];
|
|
380
|
-
let success = 0;
|
|
381
|
-
let failed = 0;
|
|
382
|
-
|
|
383
|
-
for (const f of files) {
|
|
384
|
-
try {
|
|
385
|
-
const doc = await upload({
|
|
386
|
-
host, apiKey, projectId,
|
|
387
|
-
filePath: f,
|
|
388
|
-
fileName: path.basename(f),
|
|
389
|
-
});
|
|
390
|
-
results.push(doc);
|
|
391
|
-
success++;
|
|
392
|
-
} catch (err) {
|
|
393
|
-
console.error(`Failed to upload ${path.basename(f)}: ${err.message}`);
|
|
394
|
-
failed++;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
console.log(`\n${label} upload complete: ${success} succeeded, ${failed} failed.`);
|
|
399
|
-
return results;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Smart upload: automatically handles plain files, archives, and directories.
|
|
404
|
-
*
|
|
405
|
-
* - Plain file → upload directly
|
|
406
|
-
* - Archive → extract to temp dir, upload each extracted file
|
|
407
|
-
* - Directory → recursively collect files, upload each
|
|
408
|
-
*
|
|
409
|
-
* Returns a single doc object for plain files, or an array for multi-file cases.
|
|
410
|
-
*/
|
|
411
|
-
export async function uploadAuto({ host, apiKey, projectId, filePath, fileName }) {
|
|
412
|
-
if (!fs.existsSync(filePath)) {
|
|
413
|
-
throw new Error(`Path not found: ${filePath}`);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const stat = fs.statSync(filePath);
|
|
417
|
-
|
|
418
|
-
// --- Directory ---
|
|
419
|
-
if (stat.isDirectory()) {
|
|
420
|
-
const displayName = fileName || path.basename(filePath);
|
|
421
|
-
console.log(`\nUploading directory: ${displayName}`);
|
|
422
|
-
|
|
423
|
-
const files = collectFiles(filePath);
|
|
424
|
-
if (files.length === 0) {
|
|
425
|
-
console.log('Directory is empty, nothing to upload.');
|
|
426
|
-
return [];
|
|
427
|
-
}
|
|
428
|
-
console.log(`Found ${files.length} file(s)\n`);
|
|
429
|
-
|
|
430
|
-
return uploadMany({ host, apiKey, projectId, files, label: 'Directory' });
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// --- Archive ---
|
|
434
|
-
if (getArchiveExtension(filePath)) {
|
|
435
|
-
const displayName = fileName || path.basename(filePath);
|
|
436
|
-
console.log(`\nDetected archive: ${displayName}`);
|
|
437
|
-
console.log('Extracting...');
|
|
438
|
-
|
|
439
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'memorylake-upload-'));
|
|
440
|
-
try {
|
|
441
|
-
extractArchive(filePath, tmpDir);
|
|
442
|
-
const files = collectFiles(tmpDir);
|
|
443
|
-
|
|
444
|
-
if (files.length === 0) {
|
|
445
|
-
console.log('Archive is empty, nothing to upload.');
|
|
446
|
-
return [];
|
|
447
|
-
}
|
|
448
|
-
console.log(`Extracted ${files.length} file(s)\n`);
|
|
449
|
-
|
|
450
|
-
return await uploadMany({ host, apiKey, projectId, files, label: 'Archive' });
|
|
451
|
-
} finally {
|
|
452
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// --- Plain file ---
|
|
457
|
-
return upload({ host, apiKey, projectId, filePath, fileName });
|
|
458
|
-
}
|
|
459
|
-
|
|
460
231
|
// CLI entry point
|
|
461
232
|
async function main() {
|
|
462
233
|
const args = process.argv.slice(2);
|
|
@@ -466,21 +237,17 @@ async function main() {
|
|
|
466
237
|
MemoryLake File Upload
|
|
467
238
|
|
|
468
239
|
Usage:
|
|
469
|
-
node upload.mjs --host <url> --api-key <key> --project-id <id> <
|
|
240
|
+
node upload.mjs --host <url> --api-key <key> --project-id <id> <file_path>
|
|
470
241
|
|
|
471
242
|
Arguments:
|
|
472
243
|
--host Base URL (e.g., http://10.71.10.71:3002)
|
|
473
244
|
--api-key API key for authentication
|
|
474
245
|
--project-id Project ID to associate the document with (required)
|
|
475
|
-
--file-name Custom file name (default: basename of
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
Archives are auto-detected and extracted; directories are recursively uploaded.
|
|
246
|
+
--file-name Custom file name (default: basename of file_path)
|
|
247
|
+
file_path Path to the file to upload
|
|
479
248
|
|
|
480
249
|
Examples:
|
|
481
250
|
node upload.mjs --host http://10.71.10.71:3002 --api-key sk-xxx --project-id proj-abc123 document.pdf
|
|
482
|
-
node upload.mjs --host http://10.71.10.71:3002 --api-key sk-xxx --project-id proj-abc123 docs.zip
|
|
483
|
-
node upload.mjs --host http://10.71.10.71:3002 --api-key sk-xxx --project-id proj-abc123 ./my-folder/
|
|
484
251
|
`);
|
|
485
252
|
process.exit(0);
|
|
486
253
|
}
|
|
@@ -495,7 +262,7 @@ Examples:
|
|
|
495
262
|
}
|
|
496
263
|
|
|
497
264
|
try {
|
|
498
|
-
await
|
|
265
|
+
await upload({ host, apiKey, projectId, filePath, fileName });
|
|
499
266
|
console.log('\nDone!\n');
|
|
500
267
|
} catch (err) {
|
|
501
268
|
console.error(`\nError: ${err.message}\n`);
|