memorylake-openclaw 0.0.9 → 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
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 "$@"
|
|
@@ -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) {
|