memorylake-openclaw 0.0.8 → 0.0.10
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 +1 -1
- package/scripts/install.ps1 +455 -0
- package/scripts/install.sh +336 -0
- package/skills/common/get-config.mjs +52 -0
- package/skills/memorylake-api/SKILL.md +6 -13
- package/skills/memorylake-upload/SKILL.md +5 -11
- package/skills/migrate-memories-to-memorylake/SKILL.md +16 -36
- package/skills/migrate-memories-to-memorylake/scripts/migrate.mjs +13 -36
package/package.json
CHANGED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# MemoryLake OpenClaw Plugin Installer for Windows
|
|
2
|
+
# Usage (with env vars): $env:MEMORYLAKE_API_KEY="sk-..."; $env:MEMORYLAKE_PROJECT_ID="proj-..."; iwr -useb https://app.memorylake.ai/memorylake-openclaw/install.ps1 | iex
|
|
3
|
+
# Or (with params): & ([scriptblock]::Create((iwr -useb https://app.memorylake.ai/memorylake-openclaw/install.ps1))) -ApiKey <key> -ProjectId <id>
|
|
4
|
+
|
|
5
|
+
param(
|
|
6
|
+
[string]$ApiKey = $env:MEMORYLAKE_API_KEY,
|
|
7
|
+
[string]$ProjectId = $env:MEMORYLAKE_PROJECT_ID,
|
|
8
|
+
[switch]$Help
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
$ErrorActionPreference = "Stop"
|
|
12
|
+
$script:HadNpmRegistry = -not [string]::IsNullOrWhiteSpace($env:NPM_REGISTRY)
|
|
13
|
+
|
|
14
|
+
# Set UTF-8 encoding for Chinese characters in output
|
|
15
|
+
try {
|
|
16
|
+
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
17
|
+
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
18
|
+
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
19
|
+
chcp 65001 | Out-Null
|
|
20
|
+
} catch {
|
|
21
|
+
Write-Warning "UTF-8 encoding setup failed: $_"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Colors (prefixed to avoid $Error etc.)
|
|
25
|
+
$ColorAccent = "`e[38;2;255;77;77m" # coral-bright
|
|
26
|
+
$ColorSuccess = "`e[38;2;0;229;204m" # cyan-bright
|
|
27
|
+
$ColorWarn = "`e[38;2;255;176;32m" # amber
|
|
28
|
+
$ColorError = "`e[38;2;230;57;70m" # coral-mid
|
|
29
|
+
$ColorMuted = "`e[38;2;90;100;128m" # text-muted
|
|
30
|
+
$ColorReset = "`e[0m" # No Color
|
|
31
|
+
|
|
32
|
+
$OPENCLAW_CONFIG = "$env:USERPROFILE\.openclaw\openclaw.json"
|
|
33
|
+
$QCLAW_CONFIG = "$env:USERPROFILE\.qclaw\openclaw.json"
|
|
34
|
+
|
|
35
|
+
function Write-Message {
|
|
36
|
+
param([string]$Message, [string]$Level = "info")
|
|
37
|
+
$msg = switch ($Level) {
|
|
38
|
+
"success" { "$ColorSuccess✓$ColorReset $Message" }
|
|
39
|
+
"warn" { "$ColorWarn!$ColorReset $Message" }
|
|
40
|
+
"error" { "$ColorError✗$ColorReset $Message" }
|
|
41
|
+
default { "$ColorMuted·$ColorReset $Message" }
|
|
42
|
+
}
|
|
43
|
+
Write-Host $msg
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Show-Usage {
|
|
47
|
+
Write-Host @"
|
|
48
|
+
MemoryLake OpenClaw Plugin Installer (Windows)
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
`$env:MEMORYLAKE_API_KEY="sk-..."; `$env:MEMORYLAKE_PROJECT_ID="proj-..."; iwr -useb https://app.memorylake.ai/memorylake-openclaw/install.ps1 | iex
|
|
52
|
+
Or: & ([scriptblock]::Create((iwr -useb https://app.memorylake.ai/memorylake-openclaw/install.ps1))) -ApiKey <key> -ProjectId <id>
|
|
53
|
+
|
|
54
|
+
Parameters:
|
|
55
|
+
-ApiKey <key> MemoryLake API key (required)
|
|
56
|
+
-ProjectId <id> MemoryLake project ID (required)
|
|
57
|
+
-Help Show this help
|
|
58
|
+
|
|
59
|
+
Environment variables:
|
|
60
|
+
MEMORYLAKE_API_KEY API key (alternative to -ApiKey)
|
|
61
|
+
MEMORYLAKE_PROJECT_ID Project ID (alternative to -ProjectId)
|
|
62
|
+
NPM_REGISTRY Override npm registry (e.g. https://registry.npmmirror.com for China)
|
|
63
|
+
|
|
64
|
+
Get apiKey and projectId from https://app.memorylake.ai
|
|
65
|
+
"@
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function Test-Params {
|
|
69
|
+
if ([string]::IsNullOrWhiteSpace($ApiKey) -or [string]::IsNullOrWhiteSpace($ProjectId)) {
|
|
70
|
+
Write-Message "Missing required parameters: apiKey and projectId" -Level error
|
|
71
|
+
Write-Host ""
|
|
72
|
+
Write-Host "Please provide apiKey and projectId. Get them from https://app.memorylake.ai"
|
|
73
|
+
Write-Host ""
|
|
74
|
+
Write-Host "Usage: -ApiKey <key> -ProjectId <id>"
|
|
75
|
+
Write-Host " or: `$env:MEMORYLAKE_API_KEY=<key>; `$env:MEMORYLAKE_PROJECT_ID=<id>; .\install-memorylake-openclaw.ps1"
|
|
76
|
+
exit 1
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Get-QClawScriptPath {
|
|
81
|
+
try {
|
|
82
|
+
# $proc = Get-Process qclaw -ErrorAction Stop
|
|
83
|
+
$path = (Get-Process qclaw).path[0] | Where-Object {$_}
|
|
84
|
+
$dir = Split-Path -Parent $path
|
|
85
|
+
$scriptPath = Join-Path $dir "resources\openclaw\config\skills\qclaw-openclaw\scripts\openclaw-win.cmd"
|
|
86
|
+
if (Test-Path $scriptPath) { return $scriptPath }
|
|
87
|
+
} catch {
|
|
88
|
+
Write-Message "Get-QClawScriptPath: $_" -Level warn
|
|
89
|
+
}
|
|
90
|
+
return $null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Refresh-Path {
|
|
94
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function Test-NodeInstalled {
|
|
98
|
+
$nodeVersion = $null
|
|
99
|
+
try {
|
|
100
|
+
$nodeVersion = & node -v 2>&1
|
|
101
|
+
} catch {
|
|
102
|
+
return $false
|
|
103
|
+
}
|
|
104
|
+
if ($nodeVersion -and $nodeVersion -match '^v(\d+)') {
|
|
105
|
+
$majorVersion = [int]$Matches[1]
|
|
106
|
+
return $majorVersion -ge 22
|
|
107
|
+
}
|
|
108
|
+
return $false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Install-NodeJS {
|
|
112
|
+
Write-Message "Installing Node.js..." -Level info
|
|
113
|
+
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
|
114
|
+
Write-Message "Using winget..." -Level info
|
|
115
|
+
& winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements
|
|
116
|
+
if ($LASTEXITCODE -eq 0) {
|
|
117
|
+
Refresh-Path
|
|
118
|
+
Write-Message "Node.js installed via winget" -Level success
|
|
119
|
+
return $true
|
|
120
|
+
}
|
|
121
|
+
Write-Message "winget install failed (exit code: $LASTEXITCODE), trying next method..." -Level warn
|
|
122
|
+
}
|
|
123
|
+
if (Get-Command choco -ErrorAction SilentlyContinue) {
|
|
124
|
+
Write-Message "Using Chocolatey..." -Level info
|
|
125
|
+
& choco install nodejs-lts -y
|
|
126
|
+
if ($LASTEXITCODE -eq 0) {
|
|
127
|
+
Refresh-Path
|
|
128
|
+
Write-Message "Node.js installed via Chocolatey" -Level success
|
|
129
|
+
return $true
|
|
130
|
+
}
|
|
131
|
+
Write-Message "choco install failed (exit code: $LASTEXITCODE), trying next method..." -Level warn
|
|
132
|
+
}
|
|
133
|
+
if (Get-Command scoop -ErrorAction SilentlyContinue) {
|
|
134
|
+
Write-Message "Using Scoop..." -Level info
|
|
135
|
+
& scoop install nodejs-lts
|
|
136
|
+
if ($LASTEXITCODE -eq 0) {
|
|
137
|
+
Refresh-Path
|
|
138
|
+
Write-Message "Node.js installed via Scoop" -Level success
|
|
139
|
+
return $true
|
|
140
|
+
}
|
|
141
|
+
Write-Message "scoop install failed (exit code: $LASTEXITCODE), trying next method..." -Level warn
|
|
142
|
+
}
|
|
143
|
+
# Fallback: download and install .msi
|
|
144
|
+
Write-Message "Falling back to .msi installer..." -Level info
|
|
145
|
+
$nodeVersion = "22.22.1"
|
|
146
|
+
$arch = if ([System.Environment]::Is64BitOperatingSystem) {
|
|
147
|
+
if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "x64" }
|
|
148
|
+
} else { "x86" }
|
|
149
|
+
$msiName = "node-v${nodeVersion}-${arch}.msi"
|
|
150
|
+
|
|
151
|
+
if ($script:ChinaNetwork) {
|
|
152
|
+
$msiUrl = "https://registry.npmmirror.com/-/binary/node/v${nodeVersion}/${msiName}"
|
|
153
|
+
Write-Message "Downloading Node.js from npmmirror..." -Level info
|
|
154
|
+
} else {
|
|
155
|
+
$msiUrl = "https://nodejs.org/dist/v${nodeVersion}/${msiName}"
|
|
156
|
+
Write-Message "Downloading Node.js from nodejs.org..." -Level info
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
$tmpDir = Join-Path $env:TEMP "node-install-$(Get-Random)"
|
|
160
|
+
New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null
|
|
161
|
+
$msiPath = Join-Path $tmpDir $msiName
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
Invoke-WebRequest -Uri $msiUrl -OutFile $msiPath -UseBasicParsing
|
|
165
|
+
Write-Message "Installing Node.js via .msi (may prompt for admin permission)..." -Level info
|
|
166
|
+
# Use /passive instead of /qn: shows progress bar and can auto-elevate via UAC.
|
|
167
|
+
# /qn silently fails without admin privileges.
|
|
168
|
+
$msiProc = Start-Process msiexec.exe -ArgumentList "/i", "`"$msiPath`"", "/passive", "/norestart" -Wait -PassThru
|
|
169
|
+
if ($msiProc.ExitCode -ne 0) {
|
|
170
|
+
Write-Message "msiexec failed (exit code: $($msiProc.ExitCode))" -Level error
|
|
171
|
+
Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
172
|
+
return $false
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
Refresh-Path
|
|
176
|
+
|
|
177
|
+
Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
178
|
+
if (Test-NodeInstalled) {
|
|
179
|
+
Write-Message "Node.js installed via .msi" -Level success
|
|
180
|
+
return $true
|
|
181
|
+
}
|
|
182
|
+
Write-Message "Node.js .msi install completed but node not found in PATH" -Level error
|
|
183
|
+
return $false
|
|
184
|
+
} catch {
|
|
185
|
+
Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
186
|
+
Write-Message "Node.js .msi download/install failed: $_" -Level error
|
|
187
|
+
Write-Host "Please install Node.js 22+ manually: https://nodejs.org/en/download/"
|
|
188
|
+
return $false
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function Ensure-NodeForQClaw {
|
|
193
|
+
if (Test-NodeInstalled) {
|
|
194
|
+
$ver = & node -v 2>&1
|
|
195
|
+
Write-Message "Node.js $ver found" -Level success
|
|
196
|
+
return $true
|
|
197
|
+
}
|
|
198
|
+
Write-Message "Node.js not found (v22+ required for QClaw plugin install)" -Level warn
|
|
199
|
+
if (-not (Install-NodeJS)) {
|
|
200
|
+
exit 1
|
|
201
|
+
}
|
|
202
|
+
if (-not (Test-NodeInstalled)) {
|
|
203
|
+
Write-Message "Node.js installation failed or version too old" -Level error
|
|
204
|
+
exit 1
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Configure npm mirror for China network
|
|
208
|
+
if ($script:ChinaNetwork) {
|
|
209
|
+
& npm config set registry $env:NPM_REGISTRY
|
|
210
|
+
Write-Message "npm registry set to $($env:NPM_REGISTRY)" -Level info
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return $true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function Get-ClawMode {
|
|
217
|
+
# Priority: OpenClaw first
|
|
218
|
+
if (Get-Command openclaw -ErrorAction SilentlyContinue) {
|
|
219
|
+
$script:ClawMode = "openclaw"
|
|
220
|
+
Write-Message "Detected OpenClaw" -Level success
|
|
221
|
+
return $true
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
$qclawScript = Get-QClawScriptPath
|
|
225
|
+
if ($qclawScript) {
|
|
226
|
+
$script:ClawMode = "qclaw"
|
|
227
|
+
$script:QClawScript = $qclawScript
|
|
228
|
+
Write-Message "Detected QClaw" -Level success
|
|
229
|
+
return $true
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Write-Message "OpenClaw or QClaw not found." -Level error
|
|
233
|
+
Write-Host "QClaw not installed or not running. If using QClaw, install and start it first."
|
|
234
|
+
Write-Host "Install OpenClaw: iwr -useb https://openclaw.ai/install.ps1 | iex"
|
|
235
|
+
exit 1
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function Test-PluginInstalled {
|
|
239
|
+
if ($script:ClawMode -eq "openclaw") {
|
|
240
|
+
$out = openclaw plugins list
|
|
241
|
+
if ($LASTEXITCODE -ne 0) {
|
|
242
|
+
Write-Message "openclaw plugins list failed (exit code: $LASTEXITCODE)" -Level error
|
|
243
|
+
exit 1
|
|
244
|
+
}
|
|
245
|
+
return $out -match "memorylake-openclaw"
|
|
246
|
+
} else {
|
|
247
|
+
$out = & $script:QClawScript plugins list
|
|
248
|
+
if ($LASTEXITCODE -ne 0) {
|
|
249
|
+
Write-Message "QClaw plugins list failed (exit code: $LASTEXITCODE)" -Level error
|
|
250
|
+
exit 1
|
|
251
|
+
}
|
|
252
|
+
return $out -match "memorylake-openclaw"
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function Test-NetworkChina {
|
|
257
|
+
$script:ChinaNetwork = $false
|
|
258
|
+
|
|
259
|
+
if (-not [string]::IsNullOrWhiteSpace($env:NPM_REGISTRY)) {
|
|
260
|
+
Write-Message "Using NPM_REGISTRY=$($env:NPM_REGISTRY)"
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
$timeoutSec = 5
|
|
265
|
+
$job = Start-Job -ScriptBlock {
|
|
266
|
+
try {
|
|
267
|
+
$r = Invoke-WebRequest -Uri "https://google.com/generate_204" -UseBasicParsing -TimeoutSec 3 -ErrorAction Stop
|
|
268
|
+
if ($r.StatusCode -eq 204) { "ok" } else { "fail" }
|
|
269
|
+
} catch { "fail" }
|
|
270
|
+
}
|
|
271
|
+
$completed = Wait-Job $job -Timeout $timeoutSec
|
|
272
|
+
if ($completed) {
|
|
273
|
+
$result = Receive-Job $job
|
|
274
|
+
Remove-Job $job -Force
|
|
275
|
+
if ($result -eq "ok") { return }
|
|
276
|
+
} else {
|
|
277
|
+
Stop-Job $job -ErrorAction SilentlyContinue
|
|
278
|
+
Remove-Job $job -Force -ErrorAction SilentlyContinue
|
|
279
|
+
}
|
|
280
|
+
$script:ChinaNetwork = $true
|
|
281
|
+
$env:NPM_REGISTRY = "https://registry.npmmirror.com"
|
|
282
|
+
Write-Message "China network detected, using npmmirror"
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function Install-Plugin {
|
|
286
|
+
param([switch]$TryOnly)
|
|
287
|
+
|
|
288
|
+
if ($script:ClawMode -eq "openclaw") {
|
|
289
|
+
if (-not [string]::IsNullOrWhiteSpace($env:NPM_REGISTRY)) {
|
|
290
|
+
$env:NPM_CONFIG_REGISTRY = $env:NPM_REGISTRY
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
$prevEAP = $ErrorActionPreference
|
|
295
|
+
$ErrorActionPreference = "Continue"
|
|
296
|
+
|
|
297
|
+
if ($TryOnly) {
|
|
298
|
+
# stdout redirected to Out-Host (out of return pipeline), error returns false
|
|
299
|
+
if ($script:ClawMode -eq "openclaw") {
|
|
300
|
+
openclaw plugins install memorylake-openclaw | Out-Host
|
|
301
|
+
} else {
|
|
302
|
+
& $script:QClawScript plugins install memorylake-openclaw | Out-Host
|
|
303
|
+
}
|
|
304
|
+
$exitCode = $LASTEXITCODE
|
|
305
|
+
$ErrorActionPreference = $prevEAP
|
|
306
|
+
Write-Message "Install-Plugin exit code: $exitCode" -Level info
|
|
307
|
+
return $exitCode -eq 0
|
|
308
|
+
} else {
|
|
309
|
+
# stdout direct to console, error exits
|
|
310
|
+
if ($script:ClawMode -eq "openclaw") {
|
|
311
|
+
openclaw plugins install memorylake-openclaw
|
|
312
|
+
} else {
|
|
313
|
+
& $script:QClawScript plugins install memorylake-openclaw
|
|
314
|
+
}
|
|
315
|
+
$exitCode = $LASTEXITCODE
|
|
316
|
+
$ErrorActionPreference = $prevEAP
|
|
317
|
+
Write-Message "Install-Plugin exit code: $exitCode" -Level info
|
|
318
|
+
if ($exitCode -ne 0) {
|
|
319
|
+
exit 1
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function Write-Config {
|
|
325
|
+
$configPath = if ($script:ClawMode -eq "openclaw") { $OPENCLAW_CONFIG } else { $QCLAW_CONFIG }
|
|
326
|
+
$configDir = Split-Path -Parent $configPath
|
|
327
|
+
|
|
328
|
+
if (!(Test-Path $configDir)) {
|
|
329
|
+
New-Item -ItemType Directory -Path $configDir -Force | Out-Null
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
$config = [PSCustomObject]@{}
|
|
333
|
+
if (Test-Path $configPath) {
|
|
334
|
+
try {
|
|
335
|
+
$config = Get-Content $configPath -Raw -ErrorAction Stop | ConvertFrom-Json
|
|
336
|
+
} catch {
|
|
337
|
+
Write-Message "Config parse failed: $_" -Level error
|
|
338
|
+
exit 1
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (-not $config.PSObject.Properties["plugins"]) {
|
|
343
|
+
$config | Add-Member -NotePropertyName "plugins" -NotePropertyValue ([PSCustomObject]@{}) -Force
|
|
344
|
+
}
|
|
345
|
+
if (-not $config.plugins.PSObject.Properties["entries"]) {
|
|
346
|
+
$config.plugins | Add-Member -NotePropertyName "entries" -NotePropertyValue ([PSCustomObject]@{}) -Force
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
$pluginEntry = [PSCustomObject]@{
|
|
350
|
+
enabled = $true
|
|
351
|
+
config = [PSCustomObject]@{
|
|
352
|
+
apiKey = $ApiKey
|
|
353
|
+
projectId = $ProjectId
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
$config.plugins.entries | Add-Member -NotePropertyName "memorylake-openclaw" -NotePropertyValue $pluginEntry -Force
|
|
357
|
+
|
|
358
|
+
# Merge tools config (QClaw only)
|
|
359
|
+
if ($script:ClawMode -eq "qclaw") {
|
|
360
|
+
if (-not $config.PSObject.Properties["tools"]) {
|
|
361
|
+
$config | Add-Member -NotePropertyName "tools" -NotePropertyValue ([PSCustomObject]@{}) -Force
|
|
362
|
+
}
|
|
363
|
+
$existing = $config.tools.alsoAllow
|
|
364
|
+
$arr = @()
|
|
365
|
+
if ($null -ne $existing) {
|
|
366
|
+
$arr = @($existing)
|
|
367
|
+
}
|
|
368
|
+
if ($arr -notcontains "advanced_web_search") {
|
|
369
|
+
$arr = $arr + "advanced_web_search"
|
|
370
|
+
}
|
|
371
|
+
$config.tools | Add-Member -NotePropertyName "alsoAllow" -NotePropertyValue $arr -Force
|
|
372
|
+
|
|
373
|
+
if (-not $config.tools.PSObject.Properties["web"]) {
|
|
374
|
+
$config.tools | Add-Member -NotePropertyName "web" -NotePropertyValue ([PSCustomObject]@{}) -Force
|
|
375
|
+
}
|
|
376
|
+
if (-not $config.tools.web.PSObject.Properties["search"]) {
|
|
377
|
+
$config.tools.web | Add-Member -NotePropertyName "search" -NotePropertyValue ([PSCustomObject]@{}) -Force
|
|
378
|
+
}
|
|
379
|
+
$config.tools.web.search | Add-Member -NotePropertyName "enabled" -NotePropertyValue $false -Force
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
$json = $config | ConvertTo-Json -Depth 10
|
|
383
|
+
$lines = $json -split "`r?`n"
|
|
384
|
+
$indentLevel = 0
|
|
385
|
+
$fixed = @()
|
|
386
|
+
foreach ($line in $lines) {
|
|
387
|
+
if ($line -match '[\}\]]') { $indentLevel-- }
|
|
388
|
+
$trimmed = $line.TrimStart().Replace(': ', ': ')
|
|
389
|
+
$fixed += (' ' * ($indentLevel * 2)) + $trimmed
|
|
390
|
+
if ($line -match '[\{\[]') { $indentLevel++ }
|
|
391
|
+
}
|
|
392
|
+
$json = $fixed -join "`n"
|
|
393
|
+
Set-Content $configPath -Value $json -Encoding UTF8
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function Restart-Gateway {
|
|
397
|
+
if ($script:ClawMode -eq "openclaw") {
|
|
398
|
+
openclaw gateway restart
|
|
399
|
+
if ($LASTEXITCODE -ne 0) {
|
|
400
|
+
Write-Message "openclaw gateway restart failed (exit code: $LASTEXITCODE)" -Level error
|
|
401
|
+
exit 1
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
# Main
|
|
407
|
+
function Main {
|
|
408
|
+
if ($Help) {
|
|
409
|
+
Show-Usage
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
Test-Params
|
|
414
|
+
Get-ClawMode | Out-Null
|
|
415
|
+
|
|
416
|
+
if (Test-PluginInstalled) {
|
|
417
|
+
Write-Message "memorylake-openclaw is already installed."
|
|
418
|
+
exit 0
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
Test-NetworkChina
|
|
422
|
+
|
|
423
|
+
# Try install; if qclaw fails, install Node.js and retry
|
|
424
|
+
if ($script:ClawMode -eq "qclaw") {
|
|
425
|
+
if (-not (Install-Plugin -TryOnly)) {
|
|
426
|
+
Write-Message "Plugin install failed, attempting to install Node.js..." -Level warn
|
|
427
|
+
Ensure-NodeForQClaw | Out-Null
|
|
428
|
+
Write-Message "Retrying plugin installation..." -Level info
|
|
429
|
+
Install-Plugin
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
Install-Plugin
|
|
433
|
+
}
|
|
434
|
+
Write-Message "Plugin installed" -Level success
|
|
435
|
+
|
|
436
|
+
Write-Config
|
|
437
|
+
Write-Message "Config updated" -Level success
|
|
438
|
+
|
|
439
|
+
Restart-Gateway
|
|
440
|
+
if ($script:ClawMode -eq "openclaw") {
|
|
441
|
+
Write-Message "Gateway restarted" -Level success
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
Write-Message "memorylake-openclaw setup complete!" -Level success
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
Main
|
|
449
|
+
} finally {
|
|
450
|
+
# Clean up env vars set during this script to avoid polluting the caller's shell session
|
|
451
|
+
if (-not $script:HadNpmRegistry) {
|
|
452
|
+
Remove-Item Env:\NPM_REGISTRY -ErrorAction SilentlyContinue
|
|
453
|
+
}
|
|
454
|
+
Remove-Item Env:\NPM_CONFIG_REGISTRY -ErrorAction SilentlyContinue
|
|
455
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# MemoryLake OpenClaw Plugin Installer for macOS and Linux
|
|
5
|
+
# Usage: curl -fsSL --proto '=https' --tlsv1.2 https://app.memorylake.ai/memorylake-openclaw/install.sh | bash -s -- --api-key <key> --project-id <id>
|
|
6
|
+
|
|
7
|
+
# shellcheck disable=SC2034
|
|
8
|
+
BOLD='\033[1m'
|
|
9
|
+
# shellcheck disable=SC2034
|
|
10
|
+
ACCENT='\033[38;2;255;77;77m' # coral-bright #ff4d4d
|
|
11
|
+
# shellcheck disable=SC2034
|
|
12
|
+
INFO='\033[38;2;136;146;176m' # text-secondary #8892b0
|
|
13
|
+
SUCCESS='\033[38;2;0;229;204m' # cyan-bright #00e5cc
|
|
14
|
+
WARN='\033[38;2;255;176;32m' # amber
|
|
15
|
+
ERROR='\033[38;2;230;57;70m' # coral-mid #e63946
|
|
16
|
+
MUTED='\033[38;2;90;100;128m' # text-muted #5a6480
|
|
17
|
+
NC='\033[0m' # No Color
|
|
18
|
+
|
|
19
|
+
QCLAW_SCRIPT="/Applications/QClaw.app/Contents/Resources/openclaw/config/skills/qclaw-openclaw/scripts/openclaw-mac.sh"
|
|
20
|
+
OPENCLAW_CONFIG="${HOME}/.openclaw/openclaw.json"
|
|
21
|
+
QCLAW_CONFIG="${HOME}/.qclaw/openclaw.json"
|
|
22
|
+
|
|
23
|
+
ui_info() {
|
|
24
|
+
echo -e "${MUTED}·${NC} $*"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ui_warn() {
|
|
28
|
+
echo -e "${WARN}!${NC} $*"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
ui_success() {
|
|
32
|
+
echo -e "${SUCCESS}✓${NC} $*"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ui_error() {
|
|
36
|
+
echo -e "${ERROR}✗${NC} $*"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
print_usage() {
|
|
40
|
+
cat <<EOF
|
|
41
|
+
MemoryLake OpenClaw Plugin Installer (macOS + Linux)
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
curl -fsSL --proto '=https' --tlsv1.2 https://app.memorylake.ai/memorylake-openclaw/install.sh | bash -s -- --api-key <key> --project-id <id>
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--api-key, -a <key> MemoryLake API key (required)
|
|
48
|
+
--project-id, -p <id> MemoryLake project ID (required)
|
|
49
|
+
--help, -h Show this help
|
|
50
|
+
|
|
51
|
+
Environment variables:
|
|
52
|
+
MEMORYLAKE_API_KEY API key (alternative to --api-key)
|
|
53
|
+
MEMORYLAKE_PROJECT_ID Project ID (alternative to --project-id)
|
|
54
|
+
NPM_REGISTRY Override npm registry (e.g. https://registry.npmmirror.com for China)
|
|
55
|
+
|
|
56
|
+
Get apiKey and projectId from https://app.memorylake.ai
|
|
57
|
+
EOF
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
parse_args() {
|
|
61
|
+
API_KEY="${MEMORYLAKE_API_KEY:-}"
|
|
62
|
+
PROJECT_ID="${MEMORYLAKE_PROJECT_ID:-}"
|
|
63
|
+
HELP=0
|
|
64
|
+
|
|
65
|
+
while [[ $# -gt 0 ]]; do
|
|
66
|
+
case "$1" in
|
|
67
|
+
--api-key|-a)
|
|
68
|
+
API_KEY="${2:-}"
|
|
69
|
+
shift 2
|
|
70
|
+
;;
|
|
71
|
+
--project-id|-p)
|
|
72
|
+
PROJECT_ID="${2:-}"
|
|
73
|
+
shift 2
|
|
74
|
+
;;
|
|
75
|
+
--help|-h)
|
|
76
|
+
HELP=1
|
|
77
|
+
shift
|
|
78
|
+
;;
|
|
79
|
+
*)
|
|
80
|
+
shift
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
done
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
validate_params() {
|
|
87
|
+
if [[ -z "$API_KEY" || -z "$PROJECT_ID" ]]; then
|
|
88
|
+
ui_error "Missing required parameters: apiKey and projectId"
|
|
89
|
+
echo ""
|
|
90
|
+
echo "Please provide apiKey and projectId. Get them from https://app.memorylake.ai"
|
|
91
|
+
echo ""
|
|
92
|
+
echo "Usage: $0 --api-key <key> --project-id <id>"
|
|
93
|
+
echo " or: MEMORYLAKE_API_KEY=<key> MEMORYLAKE_PROJECT_ID=<id> $0"
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
detect_claw_mode() {
|
|
99
|
+
if command -v openclaw &>/dev/null; then
|
|
100
|
+
CLAW_MODE="openclaw"
|
|
101
|
+
ui_success "Detected OpenClaw"
|
|
102
|
+
return 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
if [[ "$(uname -s)" == "Darwin" ]] && [[ -x "$QCLAW_SCRIPT" ]]; then
|
|
106
|
+
CLAW_MODE="qclaw"
|
|
107
|
+
ui_success "Detected QClaw"
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
ui_error "OpenClaw or QClaw not found."
|
|
112
|
+
echo "Install OpenClaw first: curl -fsSL https://openclaw.ai/install.sh | bash"
|
|
113
|
+
exit 1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
plugin_already_installed() {
|
|
117
|
+
if [[ "$CLAW_MODE" == "openclaw" ]]; then
|
|
118
|
+
openclaw plugins list | grep -q "memorylake-openclaw" || return 1
|
|
119
|
+
else
|
|
120
|
+
"$QCLAW_SCRIPT" plugins list | grep -q "memorylake-openclaw" || return 1
|
|
121
|
+
fi
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
detect_network() {
|
|
125
|
+
CHINA_NETWORK=false
|
|
126
|
+
|
|
127
|
+
if [[ -n "${NPM_REGISTRY:-}" ]]; then
|
|
128
|
+
ui_info "Using NPM_REGISTRY=$NPM_REGISTRY"
|
|
129
|
+
return 0
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
local code
|
|
133
|
+
code="$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 5 https://google.com/generate_204 2>/dev/null || echo "000")"
|
|
134
|
+
|
|
135
|
+
if [[ "$code" != "204" ]]; then
|
|
136
|
+
CHINA_NETWORK=true
|
|
137
|
+
NPM_REGISTRY="https://registry.npmmirror.com"
|
|
138
|
+
ui_info "China network detected, using npmmirror"
|
|
139
|
+
fi
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
install_plugin() {
|
|
143
|
+
if [[ "$CLAW_MODE" == "openclaw" ]]; then
|
|
144
|
+
if [[ -n "${NPM_REGISTRY:-}" ]]; then
|
|
145
|
+
NPM_CONFIG_REGISTRY="$NPM_REGISTRY" openclaw plugins install memorylake-openclaw
|
|
146
|
+
else
|
|
147
|
+
openclaw plugins install memorylake-openclaw
|
|
148
|
+
fi
|
|
149
|
+
else
|
|
150
|
+
"$QCLAW_SCRIPT" plugins install memorylake-openclaw
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
brew_has_mirror() {
|
|
155
|
+
# Check if any of the common Homebrew mirror env vars are set
|
|
156
|
+
[[ -n "${HOMEBREW_BOTTLE_DOMAIN:-}" ]] || [[ -n "${HOMEBREW_API_DOMAIN:-}" ]] || [[ -n "${HOMEBREW_BREW_GIT_REMOTE:-}" ]]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
install_node_via_pkg() {
|
|
160
|
+
local node_version="22.22.1"
|
|
161
|
+
local pkg_name="node-v${node_version}.pkg"
|
|
162
|
+
local pkg_url
|
|
163
|
+
|
|
164
|
+
if [[ "$CHINA_NETWORK" == "true" ]]; then
|
|
165
|
+
pkg_url="https://registry.npmmirror.com/-/binary/node/v${node_version}/${pkg_name}"
|
|
166
|
+
ui_info "Downloading Node.js from npmmirror..."
|
|
167
|
+
else
|
|
168
|
+
pkg_url="https://nodejs.org/dist/v${node_version}/${pkg_name}"
|
|
169
|
+
ui_info "Downloading Node.js from nodejs.org..."
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
local tmp_dir
|
|
173
|
+
tmp_dir=$(mktemp -d)
|
|
174
|
+
|
|
175
|
+
curl -Lo "${tmp_dir}/${pkg_name}" "$pkg_url"
|
|
176
|
+
|
|
177
|
+
if sudo -n true 2>/dev/null; then
|
|
178
|
+
sudo installer -pkg "${tmp_dir}/${pkg_name}" -target /
|
|
179
|
+
else
|
|
180
|
+
ui_warn "sudo requires a password. Please enter your Mac login password below:"
|
|
181
|
+
sudo installer -pkg "${tmp_dir}/${pkg_name}" -target /
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
rm -rf "$tmp_dir"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
install_node() {
|
|
188
|
+
ui_info "npm/node not found, installing Node.js..."
|
|
189
|
+
|
|
190
|
+
local brew_ok=false
|
|
191
|
+
if command -v brew &>/dev/null; then
|
|
192
|
+
if [[ "$CHINA_NETWORK" == "true" ]] && ! brew_has_mirror; then
|
|
193
|
+
ui_info "China network detected but Homebrew has no mirror configured, skipping brew"
|
|
194
|
+
else
|
|
195
|
+
ui_info "Installing Node.js via brew..."
|
|
196
|
+
if brew install node; then
|
|
197
|
+
brew_ok=true
|
|
198
|
+
else
|
|
199
|
+
ui_warn "brew install node failed, falling back to .pkg installer..."
|
|
200
|
+
fi
|
|
201
|
+
fi
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
if [[ "$brew_ok" == "false" ]]; then
|
|
205
|
+
install_node_via_pkg
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# Verify installation
|
|
209
|
+
if command -v node &>/dev/null && command -v npm &>/dev/null; then
|
|
210
|
+
ui_success "Node.js $(node --version) installed"
|
|
211
|
+
else
|
|
212
|
+
ui_error "Node.js installation failed"
|
|
213
|
+
exit 1
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# Configure npm mirror for China network
|
|
217
|
+
if [[ "$CHINA_NETWORK" == "true" ]]; then
|
|
218
|
+
npm config set registry "$NPM_REGISTRY"
|
|
219
|
+
ui_info "npm registry set to $NPM_REGISTRY"
|
|
220
|
+
fi
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
write_config() {
|
|
224
|
+
local config_path
|
|
225
|
+
if [[ "$CLAW_MODE" == "openclaw" ]]; then
|
|
226
|
+
config_path="$OPENCLAW_CONFIG"
|
|
227
|
+
else
|
|
228
|
+
config_path="$QCLAW_CONFIG"
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
local config_dir
|
|
232
|
+
config_dir="$(dirname "$config_path")"
|
|
233
|
+
mkdir -p "$config_dir"
|
|
234
|
+
|
|
235
|
+
CONFIG_PATH="$config_path" API_KEY="$API_KEY" PROJECT_ID="$PROJECT_ID" CLAW_MODE="$CLAW_MODE" python3 -c '
|
|
236
|
+
import json
|
|
237
|
+
import os
|
|
238
|
+
|
|
239
|
+
path = os.environ["CONFIG_PATH"]
|
|
240
|
+
api_key = os.environ["API_KEY"]
|
|
241
|
+
project_id = os.environ["PROJECT_ID"]
|
|
242
|
+
claw_mode = os.environ.get("CLAW_MODE", "")
|
|
243
|
+
|
|
244
|
+
config = {}
|
|
245
|
+
if os.path.exists(path):
|
|
246
|
+
with open(path) as f:
|
|
247
|
+
config = json.load(f)
|
|
248
|
+
|
|
249
|
+
if "plugins" not in config:
|
|
250
|
+
config["plugins"] = {}
|
|
251
|
+
if "entries" not in config["plugins"]:
|
|
252
|
+
config["plugins"]["entries"] = {}
|
|
253
|
+
|
|
254
|
+
config["plugins"]["entries"]["memorylake-openclaw"] = {
|
|
255
|
+
"enabled": True,
|
|
256
|
+
"config": {
|
|
257
|
+
"apiKey": api_key,
|
|
258
|
+
"projectId": project_id
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
# Merge tools config (QClaw only)
|
|
263
|
+
if claw_mode == "qclaw":
|
|
264
|
+
if "tools" not in config:
|
|
265
|
+
config["tools"] = {}
|
|
266
|
+
if "alsoAllow" not in config["tools"]:
|
|
267
|
+
config["tools"]["alsoAllow"] = []
|
|
268
|
+
arr = config["tools"]["alsoAllow"]
|
|
269
|
+
if "advanced_web_search" not in arr:
|
|
270
|
+
arr.append("advanced_web_search")
|
|
271
|
+
|
|
272
|
+
if "web" not in config["tools"]:
|
|
273
|
+
config["tools"]["web"] = {}
|
|
274
|
+
if "search" not in config["tools"]["web"]:
|
|
275
|
+
config["tools"]["web"]["search"] = {}
|
|
276
|
+
config["tools"]["web"]["search"]["enabled"] = False
|
|
277
|
+
|
|
278
|
+
with open(path, "w") as f:
|
|
279
|
+
json.dump(config, f, indent=2)
|
|
280
|
+
'
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
restart_gateway() {
|
|
284
|
+
if [[ "$CLAW_MODE" == "openclaw" ]]; then
|
|
285
|
+
openclaw gateway restart
|
|
286
|
+
fi
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
main() {
|
|
290
|
+
parse_args "$@"
|
|
291
|
+
|
|
292
|
+
if [[ "$HELP" == "1" ]]; then
|
|
293
|
+
print_usage
|
|
294
|
+
return 0
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
validate_params
|
|
298
|
+
detect_claw_mode
|
|
299
|
+
|
|
300
|
+
if plugin_already_installed; then
|
|
301
|
+
ui_info "memorylake-openclaw is already installed."
|
|
302
|
+
exit 0
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
detect_network
|
|
306
|
+
|
|
307
|
+
# Try install; if qclaw on macOS fails with npm error, install Node.js and retry
|
|
308
|
+
local install_output install_exit
|
|
309
|
+
if install_output=$(install_plugin 2>&1); then
|
|
310
|
+
echo "$install_output"
|
|
311
|
+
else
|
|
312
|
+
install_exit=$?
|
|
313
|
+
echo "$install_output" >&2
|
|
314
|
+
if [[ "$CLAW_MODE" == "qclaw" ]] && [[ "$(uname -s)" == "Darwin" ]] && echo "$install_output" | grep -qi "npm"; then
|
|
315
|
+
ui_warn "Plugin install failed due to npm issue, attempting to install Node.js..."
|
|
316
|
+
install_node
|
|
317
|
+
ui_info "Retrying plugin installation..."
|
|
318
|
+
install_plugin
|
|
319
|
+
else
|
|
320
|
+
exit "$install_exit"
|
|
321
|
+
fi
|
|
322
|
+
fi
|
|
323
|
+
ui_success "Plugin installed"
|
|
324
|
+
|
|
325
|
+
write_config
|
|
326
|
+
ui_success "Config updated"
|
|
327
|
+
|
|
328
|
+
restart_gateway
|
|
329
|
+
if [[ "$CLAW_MODE" == "openclaw" ]]; then
|
|
330
|
+
ui_success "Gateway restarted"
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
ui_success "memorylake-openclaw setup complete!"
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
main "$@"
|
|
@@ -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));
|
|
@@ -19,21 +19,15 @@ 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
|
-
|
|
22
|
+
Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `../common/get-config.mjs`):
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
|
|
25
|
+
node {path-to-this-skill}/../common/get-config.mjs --agent {agent}
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Where `{agent}` is the current agent ID. The script outputs JSON config with `host`, `apiKey`, `projectId`, etc.
|
|
29
29
|
|
|
30
|
-
|
|
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.
|
|
30
|
+
If the script exits with an error, stop and inform the user.
|
|
37
31
|
|
|
38
32
|
Auth header for all requests:
|
|
39
33
|
|
|
@@ -185,15 +179,14 @@ All responses follow the same wrapper format:
|
|
|
185
179
|
|
|
186
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`
|
|
187
181
|
- **Missing auth header**: Every request requires `Authorization: Bearer {apiKey}`
|
|
188
|
-
- **Hardcoded project ID**: Always read `projectId` from
|
|
182
|
+
- **Hardcoded project ID**: Always read `projectId` from the config script output, not from user input (unless the user explicitly wants a different project)
|
|
189
183
|
- **Pagination**: List endpoints default to `page=1, size=20`. Pass `page` and `size` query params if the user needs more results
|
|
190
184
|
|
|
191
185
|
## Quick Reference
|
|
192
186
|
|
|
193
187
|
| Item | Value |
|
|
194
188
|
|------|-------|
|
|
195
|
-
|
|
|
196
|
-
| Plugin config key | `plugins.entries["memorylake-openclaw"].config` |
|
|
189
|
+
| Config script | `{path-to-this-skill}/../common/get-config.mjs --agent {agent}` |
|
|
197
190
|
| Server base path | `/openapi/memorylake` |
|
|
198
191
|
| OpenAPI spec URL | `{host}/openapi/memorylake/api-docs/open-api` |
|
|
199
192
|
| Auth header | `Authorization: Bearer {apiKey}` |
|
|
@@ -16,21 +16,15 @@ Upload local files to MemoryLake using the multipart upload API, then associate
|
|
|
16
16
|
|
|
17
17
|
## Step 1 -- Read MemoryLake Config
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Run the common config script (path is relative to **this skill's SKILL.md**, i.e. `../common/get-config.mjs`):
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
22
|
+
node {path-to-this-skill}/../common/get-config.mjs --agent {agent}
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Where `{agent}` is the current agent ID. The script outputs JSON config with `host`, `apiKey`, `projectId`, etc.
|
|
26
26
|
|
|
27
|
-
|
|
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.
|
|
27
|
+
If the script exits with an error, stop and inform the user.
|
|
34
28
|
|
|
35
29
|
## Step 2 -- Run the Upload Script
|
|
36
30
|
|
|
@@ -56,5 +50,5 @@ The script prints progress for each step (create upload, upload parts, complete,
|
|
|
56
50
|
|
|
57
51
|
## Common Mistakes
|
|
58
52
|
|
|
59
|
-
- **Skipping Step 1**: Directly hardcoding host/apiKey/projectId instead of
|
|
53
|
+
- **Skipping Step 1**: Directly hardcoding host/apiKey/projectId instead of using the config script
|
|
60
54
|
- **Relative file paths**: Always resolve the user's file path to an absolute path before passing to the script
|
|
@@ -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
|
-
|
|
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
|
-
|
|
46
|
+
node {path-to-this-skill}/../common/get-config.mjs --agent {agent}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
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 —
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
####
|
|
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
|
-
####
|
|
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
|
|
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 | `
|
|
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` |
|
|
@@ -8,50 +8,27 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { readFileSync, readdirSync, existsSync, statSync } from "fs";
|
|
11
|
-
import { join, basename } from "path";
|
|
11
|
+
import { join, basename, dirname } from "path";
|
|
12
12
|
import { homedir } from "os";
|
|
13
13
|
import { parseArgs } from "util";
|
|
14
|
+
import { spawnSync } from "node:child_process";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
14
16
|
|
|
15
|
-
const OPENCLAW_CONFIG = join(homedir(), ".openclaw", "openclaw.json");
|
|
16
17
|
const BATCH_SIZE = 20;
|
|
17
18
|
const API_USER_ID = "default";
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const pluginCfg =
|
|
23
|
-
config?.plugins?.entries?.["memorylake-openclaw"]?.config ?? {};
|
|
24
|
-
|
|
25
|
-
const host = (pluginCfg.host || "https://app.memorylake.ai").replace(
|
|
26
|
-
/\/$/,
|
|
27
|
-
""
|
|
28
|
-
);
|
|
29
|
-
const apiKey = pluginCfg.apiKey || "";
|
|
30
|
-
const projectId = pluginCfg.projectId || "";
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const GET_CONFIG_PATH = join(__dirname, "../../common/get-config.mjs");
|
|
31
22
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Resolve workspace: agent-specific first, then defaults
|
|
42
|
-
const agentsCfg = config.agents ?? {};
|
|
43
|
-
let workspace = "";
|
|
44
|
-
for (const entry of agentsCfg.list ?? []) {
|
|
45
|
-
if (entry.id === agent) {
|
|
46
|
-
workspace = entry.workspace || "";
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (!workspace) {
|
|
51
|
-
workspace = agentsCfg.defaults?.workspace || "";
|
|
23
|
+
function loadConfig(agent) {
|
|
24
|
+
const result = spawnSync(process.execPath, [GET_CONFIG_PATH, "--agent", agent], {
|
|
25
|
+
encoding: "utf-8",
|
|
26
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
27
|
+
});
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
process.exit(result.status ?? 1);
|
|
52
30
|
}
|
|
53
|
-
|
|
54
|
-
return { host, apiKey, projectId, workspace };
|
|
31
|
+
return JSON.parse(result.stdout);
|
|
55
32
|
}
|
|
56
33
|
|
|
57
34
|
async function postMemories(host, apiKey, projectId, payload) {
|