@usecortex_ai/openclaw-cortex-ai 0.1.1 → 0.1.3
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/README.md +83 -25
- package/commands/onboarding.ts +108 -65
- package/config.ts +27 -1
- package/index.ts +139 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,16 +10,61 @@ openclaw plugins install @usecortex_ai/openclaw-cortex-ai
|
|
|
10
10
|
|
|
11
11
|
Restart OpenClaw after installing.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
If you run OpenClaw via the local gateway, restart it too:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
```bash
|
|
16
|
+
openclaw gateway restart
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Interactive Onboarding
|
|
20
|
+
|
|
21
|
+
Run the interactive CLI wizard (recommended):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Basic onboarding (API key, tenant ID, sub-tenant, ignore term)
|
|
25
|
+
openclaw cortex onboard
|
|
26
|
+
|
|
27
|
+
# Advanced onboarding (all options including recall mode, graph context, etc.)
|
|
28
|
+
openclaw cortex onboard --advanced
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The wizard guides you through configuration with colored prompts and **writes your config to** `plugins.entries.openclaw-cortex-ai.config` inside OpenClaw's settings file.
|
|
32
|
+
|
|
33
|
+
The path is resolved in the same order OpenClaw itself uses:
|
|
34
|
+
|
|
35
|
+
1. `$OPENCLAW_CONFIG_PATH` — if set, used directly
|
|
36
|
+
2. `$OPENCLAW_STATE_DIR/openclaw.json` — if `OPENCLAW_STATE_DIR` is set
|
|
37
|
+
3. `$OPENCLAW_HOME/.openclaw/openclaw.json` — if `OPENCLAW_HOME` is set
|
|
38
|
+
4. Default: `~/.openclaw/openclaw.json` (macOS/Linux) or `%USERPROFILE%\.openclaw\openclaw.json` (Windows)
|
|
39
|
+
|
|
40
|
+
No manual adjustment needed — the wizard auto-detects the correct path.
|
|
41
|
+
|
|
42
|
+
After onboarding, restart the gateway:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
openclaw gateway restart
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Manual Configuration
|
|
49
|
+
|
|
50
|
+
If you prefer, you can configure credentials manually.
|
|
51
|
+
|
|
52
|
+
Two required values:
|
|
53
|
+
|
|
54
|
+
- **API key**
|
|
55
|
+
- **Tenant ID**
|
|
56
|
+
|
|
57
|
+
Environment variables (recommended for secrets):
|
|
16
58
|
|
|
17
59
|
```bash
|
|
18
60
|
export CORTEX_OPENCLAW_API_KEY="your-api-key"
|
|
19
61
|
export CORTEX_OPENCLAW_TENANT_ID="your-tenant-id"
|
|
20
62
|
```
|
|
21
63
|
|
|
22
|
-
Or configure directly in
|
|
64
|
+
Or configure directly in OpenClaw's settings file:
|
|
65
|
+
|
|
66
|
+
- **macOS / Linux:** `~/.openclaw/openclaw.json`
|
|
67
|
+
- **Windows:** `%USERPROFILE%\.openclaw\openclaw.json`
|
|
23
68
|
|
|
24
69
|
```json5
|
|
25
70
|
{
|
|
@@ -37,11 +82,17 @@ Or configure directly in `openclaw.json`:
|
|
|
37
82
|
}
|
|
38
83
|
```
|
|
39
84
|
|
|
85
|
+
After changing config, restart the gateway so the plugin reloads:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
openclaw gateway restart
|
|
89
|
+
```
|
|
90
|
+
|
|
40
91
|
### Options
|
|
41
92
|
|
|
42
93
|
| Key | Type | Default | Description |
|
|
43
94
|
| -------------------- | ----------- | --------------------- | ------------------------------------------------------------------------------ |
|
|
44
|
-
| `subTenantId` | `string` | `"cortex-openclaw"` | Sub-tenant for data partitioning within your tenant
|
|
95
|
+
| `subTenantId` | `string` | `"cortex-openclaw-plugin"` | Sub-tenant for data partitioning within your tenant |
|
|
45
96
|
| `autoRecall` | `boolean` | `true` | Inject relevant memories before every AI turn |
|
|
46
97
|
| `autoCapture` | `boolean` | `true` | Store conversation exchanges after every AI turn |
|
|
47
98
|
| `maxRecallResults` | `number` | `10` | Max memory chunks injected into context per turn |
|
|
@@ -55,23 +106,6 @@ Or configure directly in `openclaw.json`:
|
|
|
55
106
|
- **Auto-Recall** — Before every AI turn, queries Cortex (`/recall/recall_preferences`) for relevant memories and injects graph-enriched context (entity paths, chunk relations, extra context).
|
|
56
107
|
- **Auto-Capture** — After every AI turn, the last user/assistant exchange is sent to Cortex (`/memories/add_memory`) as conversation pairs with `infer: true` and `upsert: true`. The session ID is used as `source_id` so Cortex groups exchanges per session and builds a knowledge graph automatically.
|
|
57
108
|
|
|
58
|
-
## Interactive Onboarding
|
|
59
|
-
|
|
60
|
-
Run the interactive CLI wizard to configure Cortex AI:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
# Basic onboarding (API key, tenant ID, sub-tenant, ignore term)
|
|
64
|
-
openclaw cortex onboard
|
|
65
|
-
|
|
66
|
-
# Advanced onboarding (all options including recall mode, graph context, etc.)
|
|
67
|
-
openclaw cortex onboard --advanced
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
The wizard guides you through configuration with colored prompts, validates inputs, and outputs:
|
|
71
|
-
- `.env` file lines for credentials
|
|
72
|
-
- Plugin config JSON for non-default settings
|
|
73
|
-
- A summary table with masked sensitive values
|
|
74
|
-
|
|
75
109
|
## Slash Commands
|
|
76
110
|
|
|
77
111
|
| Command | Description |
|
|
@@ -85,10 +119,13 @@ The wizard guides you through configuration with colored prompts, validates inpu
|
|
|
85
119
|
|
|
86
120
|
## AI Tools
|
|
87
121
|
|
|
88
|
-
| Tool
|
|
89
|
-
|
|
|
90
|
-
| `cortex_store`
|
|
91
|
-
| `cortex_search`
|
|
122
|
+
| Tool | Description |
|
|
123
|
+
| ---------------------- | ----------- |
|
|
124
|
+
| `cortex_store` | Save the recent conversation history to Cortex as memory |
|
|
125
|
+
| `cortex_search` | Search Cortex memories (returns graph-enriched context) |
|
|
126
|
+
| `cortex_list_memories` | List all stored user memories (IDs + summaries) |
|
|
127
|
+
| `cortex_get_content` | Fetch full content for a specific `source_id` |
|
|
128
|
+
| `cortex_delete_memory` | Delete a memory by `memory_id` (use only when user explicitly asks) |
|
|
92
129
|
|
|
93
130
|
## CLI
|
|
94
131
|
|
|
@@ -102,6 +139,27 @@ openclaw cortex get <source_id> # Fetch source content
|
|
|
102
139
|
openclaw cortex status # Show plugin configuration
|
|
103
140
|
```
|
|
104
141
|
|
|
142
|
+
## Troubleshooting
|
|
143
|
+
|
|
144
|
+
### `Not configured. Run openclaw cortex onboard`
|
|
145
|
+
|
|
146
|
+
This means the plugin is enabled, but credentials are missing.
|
|
147
|
+
|
|
148
|
+
Run:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
openclaw cortex onboard
|
|
152
|
+
openclaw gateway restart
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### CLI says a command is unknown
|
|
156
|
+
|
|
157
|
+
Update/restart the gateway so it reloads the plugin:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
openclaw gateway restart
|
|
161
|
+
```
|
|
162
|
+
|
|
105
163
|
## Context Injection
|
|
106
164
|
|
|
107
165
|
Recalled context is injected inside `<cortex-context>` tags containing:
|
package/commands/onboarding.ts
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
import * as fs from "node:fs"
|
|
2
|
+
import * as os from "node:os"
|
|
3
|
+
import * as path from "node:path"
|
|
1
4
|
import * as readline from "node:readline"
|
|
2
5
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
3
6
|
import type { CortexClient } from "../client.ts"
|
|
4
7
|
import type { CortexPluginConfig } from "../config.ts"
|
|
5
8
|
import { log } from "../log.ts"
|
|
6
9
|
|
|
10
|
+
// ── Defaults (used when config is not yet available) ──
|
|
11
|
+
|
|
12
|
+
const DEFAULTS = {
|
|
13
|
+
subTenantId: "cortex-openclaw-plugin",
|
|
14
|
+
ignoreTerm: "cortex-ignore",
|
|
15
|
+
autoRecall: true,
|
|
16
|
+
autoCapture: true,
|
|
17
|
+
maxRecallResults: 10,
|
|
18
|
+
recallMode: "fast" as const,
|
|
19
|
+
graphContext: true,
|
|
20
|
+
debug: false,
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
// ── ANSI helpers ──
|
|
8
24
|
|
|
9
25
|
const c = {
|
|
@@ -160,57 +176,85 @@ type WizardResult = {
|
|
|
160
176
|
debug?: boolean
|
|
161
177
|
}
|
|
162
178
|
|
|
163
|
-
function
|
|
179
|
+
function buildConfigObj(result: WizardResult): Record<string, unknown> {
|
|
164
180
|
const obj: Record<string, unknown> = {}
|
|
165
181
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (result.
|
|
170
|
-
obj.tenantId = result.tenantId
|
|
171
|
-
}
|
|
172
|
-
if (result.subTenantId !== "cortex-openclaw-plugin") {
|
|
182
|
+
obj.apiKey = result.apiKey
|
|
183
|
+
obj.tenantId = result.tenantId
|
|
184
|
+
|
|
185
|
+
if (result.subTenantId !== DEFAULTS.subTenantId) {
|
|
173
186
|
obj.subTenantId = result.subTenantId
|
|
174
187
|
}
|
|
175
|
-
if (result.ignoreTerm !==
|
|
188
|
+
if (result.ignoreTerm !== DEFAULTS.ignoreTerm) {
|
|
176
189
|
obj.ignoreTerm = result.ignoreTerm
|
|
177
190
|
}
|
|
178
|
-
if (result.autoRecall !== undefined && result.autoRecall !==
|
|
191
|
+
if (result.autoRecall !== undefined && result.autoRecall !== DEFAULTS.autoRecall) {
|
|
179
192
|
obj.autoRecall = result.autoRecall
|
|
180
193
|
}
|
|
181
|
-
if (result.autoCapture !== undefined && result.autoCapture !==
|
|
194
|
+
if (result.autoCapture !== undefined && result.autoCapture !== DEFAULTS.autoCapture) {
|
|
182
195
|
obj.autoCapture = result.autoCapture
|
|
183
196
|
}
|
|
184
|
-
if (result.maxRecallResults !== undefined && result.maxRecallResults !==
|
|
197
|
+
if (result.maxRecallResults !== undefined && result.maxRecallResults !== DEFAULTS.maxRecallResults) {
|
|
185
198
|
obj.maxRecallResults = result.maxRecallResults
|
|
186
199
|
}
|
|
187
|
-
if (result.recallMode !== undefined && result.recallMode !==
|
|
200
|
+
if (result.recallMode !== undefined && result.recallMode !== DEFAULTS.recallMode) {
|
|
188
201
|
obj.recallMode = result.recallMode
|
|
189
202
|
}
|
|
190
|
-
if (result.graphContext !== undefined && result.graphContext !==
|
|
203
|
+
if (result.graphContext !== undefined && result.graphContext !== DEFAULTS.graphContext) {
|
|
191
204
|
obj.graphContext = result.graphContext
|
|
192
205
|
}
|
|
193
|
-
if (result.debug !== undefined && result.debug !==
|
|
206
|
+
if (result.debug !== undefined && result.debug !== DEFAULTS.debug) {
|
|
194
207
|
obj.debug = result.debug
|
|
195
208
|
}
|
|
196
209
|
|
|
197
|
-
return
|
|
210
|
+
return obj
|
|
198
211
|
}
|
|
199
212
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
213
|
+
// ── Persist to openclaw.json ──
|
|
214
|
+
// Mirrors openclaw's own path resolution (src/config/paths.ts):
|
|
215
|
+
// 1. $OPENCLAW_CONFIG_PATH (explicit override)
|
|
216
|
+
// 2. $OPENCLAW_STATE_DIR/openclaw.json
|
|
217
|
+
// 3. $OPENCLAW_HOME/.openclaw/openclaw.json
|
|
218
|
+
// 4. os.homedir()/.openclaw/openclaw.json (default)
|
|
219
|
+
|
|
220
|
+
function resolveOpenClawConfigPath(): string {
|
|
221
|
+
if (process.env.OPENCLAW_CONFIG_PATH) {
|
|
222
|
+
return process.env.OPENCLAW_CONFIG_PATH
|
|
223
|
+
}
|
|
224
|
+
if (process.env.OPENCLAW_STATE_DIR) {
|
|
225
|
+
return path.join(process.env.OPENCLAW_STATE_DIR, "openclaw.json")
|
|
204
226
|
}
|
|
205
|
-
if (
|
|
206
|
-
|
|
227
|
+
if (process.env.OPENCLAW_HOME) {
|
|
228
|
+
return path.join(process.env.OPENCLAW_HOME, ".openclaw", "openclaw.json")
|
|
229
|
+
}
|
|
230
|
+
return path.join(os.homedir(), ".openclaw", "openclaw.json")
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const OPENCLAW_CONFIG_PATH = resolveOpenClawConfigPath()
|
|
234
|
+
|
|
235
|
+
function persistConfig(configObj: Record<string, unknown>): boolean {
|
|
236
|
+
try {
|
|
237
|
+
const raw = fs.readFileSync(OPENCLAW_CONFIG_PATH, "utf-8")
|
|
238
|
+
const root = JSON.parse(raw)
|
|
239
|
+
|
|
240
|
+
if (!root.plugins) root.plugins = {}
|
|
241
|
+
if (!root.plugins.entries) root.plugins.entries = {}
|
|
242
|
+
if (!root.plugins.entries["openclaw-cortex-ai"]) {
|
|
243
|
+
root.plugins.entries["openclaw-cortex-ai"] = { enabled: true }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
root.plugins.entries["openclaw-cortex-ai"].config = configObj
|
|
247
|
+
|
|
248
|
+
fs.writeFileSync(OPENCLAW_CONFIG_PATH, JSON.stringify(root, null, 2) + "\n")
|
|
249
|
+
return true
|
|
250
|
+
} catch {
|
|
251
|
+
return false
|
|
207
252
|
}
|
|
208
|
-
return lines
|
|
209
253
|
}
|
|
210
254
|
|
|
211
255
|
// ── Wizards ──
|
|
212
256
|
|
|
213
|
-
async function runBasicWizard(cfg
|
|
257
|
+
async function runBasicWizard(cfg?: CortexPluginConfig): Promise<void> {
|
|
214
258
|
const rl = createRl()
|
|
215
259
|
|
|
216
260
|
try {
|
|
@@ -232,14 +276,15 @@ async function runBasicWizard(cfg: CortexPluginConfig): Promise<void> {
|
|
|
232
276
|
printSection("Customisation")
|
|
233
277
|
|
|
234
278
|
const subTenantId = await promptText(rl, "Sub-Tenant ID", {
|
|
235
|
-
default: cfg.subTenantId,
|
|
279
|
+
default: cfg?.subTenantId ?? DEFAULTS.subTenantId,
|
|
236
280
|
})
|
|
237
281
|
|
|
238
282
|
const ignoreTerm = await promptText(rl, "Ignore Term", {
|
|
239
|
-
default: cfg.ignoreTerm,
|
|
283
|
+
default: cfg?.ignoreTerm ?? DEFAULTS.ignoreTerm,
|
|
240
284
|
})
|
|
241
285
|
|
|
242
286
|
const result: WizardResult = { apiKey, tenantId, subTenantId, ignoreTerm }
|
|
287
|
+
const configObj = buildConfigObj(result)
|
|
243
288
|
|
|
244
289
|
// ── Summary ──
|
|
245
290
|
|
|
@@ -252,35 +297,35 @@ async function runBasicWizard(cfg: CortexPluginConfig): Promise<void> {
|
|
|
252
297
|
printSummaryRow("Ignore Term", ignoreTerm)
|
|
253
298
|
console.log(` ${c.dim}└${"─".repeat(50)}${c.reset}`)
|
|
254
299
|
|
|
255
|
-
// ──
|
|
300
|
+
// ── Persist config ──
|
|
256
301
|
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
302
|
+
const saved = await promptBool(rl, `Write config to ${OPENCLAW_CONFIG_PATH}?`, true)
|
|
303
|
+
|
|
304
|
+
if (saved && persistConfig(configObj)) {
|
|
305
|
+
printSuccess("Config saved! Restart the gateway (`openclaw gateway restart`) to apply.")
|
|
306
|
+
} else if (saved) {
|
|
307
|
+
console.log(` ${c.red}Failed to write config. Add manually:${c.reset}`)
|
|
261
308
|
console.log()
|
|
262
|
-
for (const line of
|
|
263
|
-
console.log(` ${c.
|
|
309
|
+
for (const line of JSON.stringify(configObj, null, 2).split("\n")) {
|
|
310
|
+
console.log(` ${c.cyan}${line}${c.reset}`)
|
|
264
311
|
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const json = buildConfigJson(result)
|
|
268
|
-
if (json !== "{}") {
|
|
312
|
+
} else {
|
|
269
313
|
console.log()
|
|
270
|
-
console.log(` ${c.yellow}${c.bold}
|
|
314
|
+
console.log(` ${c.yellow}${c.bold}Add to openclaw.json plugins.entries.openclaw-cortex-ai.config:${c.reset}`)
|
|
271
315
|
console.log()
|
|
272
|
-
for (const line of
|
|
316
|
+
for (const line of JSON.stringify(configObj, null, 2).split("\n")) {
|
|
273
317
|
console.log(` ${c.cyan}${line}${c.reset}`)
|
|
274
318
|
}
|
|
275
319
|
}
|
|
276
320
|
|
|
277
|
-
|
|
321
|
+
console.log()
|
|
322
|
+
console.log(` ${c.dim}Run \`cortex onboard --advanced\` to fine-tune all options.${c.reset}`)
|
|
278
323
|
} finally {
|
|
279
324
|
rl.close()
|
|
280
325
|
}
|
|
281
326
|
}
|
|
282
327
|
|
|
283
|
-
async function runAdvancedWizard(cfg
|
|
328
|
+
async function runAdvancedWizard(cfg?: CortexPluginConfig): Promise<void> {
|
|
284
329
|
const rl = createRl()
|
|
285
330
|
|
|
286
331
|
try {
|
|
@@ -300,30 +345,30 @@ async function runAdvancedWizard(cfg: CortexPluginConfig): Promise<void> {
|
|
|
300
345
|
})
|
|
301
346
|
|
|
302
347
|
const subTenantId = await promptText(rl, "Sub-Tenant ID", {
|
|
303
|
-
default: cfg.subTenantId,
|
|
348
|
+
default: cfg?.subTenantId ?? DEFAULTS.subTenantId,
|
|
304
349
|
})
|
|
305
350
|
|
|
306
351
|
printSection("Behaviour")
|
|
307
352
|
|
|
308
|
-
const autoRecall = await promptBool(rl, "Enable Auto-Recall?", cfg.autoRecall)
|
|
309
|
-
const autoCapture = await promptBool(rl, "Enable Auto-Capture?", cfg.autoCapture)
|
|
353
|
+
const autoRecall = await promptBool(rl, "Enable Auto-Recall?", cfg?.autoRecall ?? DEFAULTS.autoRecall)
|
|
354
|
+
const autoCapture = await promptBool(rl, "Enable Auto-Capture?", cfg?.autoCapture ?? DEFAULTS.autoCapture)
|
|
310
355
|
const ignoreTerm = await promptText(rl, "Ignore Term", {
|
|
311
|
-
default: cfg.ignoreTerm,
|
|
356
|
+
default: cfg?.ignoreTerm ?? DEFAULTS.ignoreTerm,
|
|
312
357
|
})
|
|
313
358
|
|
|
314
359
|
printSection("Recall Settings")
|
|
315
360
|
|
|
316
361
|
const maxRecallResults = await promptNumber(
|
|
317
|
-
rl, "Max Recall Results", cfg.maxRecallResults, 1, 50,
|
|
362
|
+
rl, "Max Recall Results", cfg?.maxRecallResults ?? DEFAULTS.maxRecallResults, 1, 50,
|
|
318
363
|
)
|
|
319
364
|
const recallMode = await promptChoice(
|
|
320
|
-
rl, "Recall Mode", ["fast", "thinking"], cfg.recallMode,
|
|
365
|
+
rl, "Recall Mode", ["fast", "thinking"], cfg?.recallMode ?? DEFAULTS.recallMode,
|
|
321
366
|
) as "fast" | "thinking"
|
|
322
|
-
const graphContext = await promptBool(rl, "Enable Graph Context?", cfg.graphContext)
|
|
367
|
+
const graphContext = await promptBool(rl, "Enable Graph Context?", cfg?.graphContext ?? DEFAULTS.graphContext)
|
|
323
368
|
|
|
324
369
|
printSection("Debug")
|
|
325
370
|
|
|
326
|
-
const debug = await promptBool(rl, "Enable Debug Logging?", cfg.debug)
|
|
371
|
+
const debug = await promptBool(rl, "Enable Debug Logging?", cfg?.debug ?? DEFAULTS.debug)
|
|
327
372
|
|
|
328
373
|
const result: WizardResult = {
|
|
329
374
|
apiKey,
|
|
@@ -355,29 +400,27 @@ async function runAdvancedWizard(cfg: CortexPluginConfig): Promise<void> {
|
|
|
355
400
|
printSummaryRow("Debug", String(debug))
|
|
356
401
|
console.log(` ${c.dim}└${"─".repeat(50)}${c.reset}`)
|
|
357
402
|
|
|
358
|
-
// ──
|
|
403
|
+
// ── Persist config ──
|
|
359
404
|
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
405
|
+
const configObj = buildConfigObj(result)
|
|
406
|
+
const saved = await promptBool(rl, `Write config to ${OPENCLAW_CONFIG_PATH}?`, true)
|
|
407
|
+
|
|
408
|
+
if (saved && persistConfig(configObj)) {
|
|
409
|
+
printSuccess("Config saved! Restart the gateway (`openclaw gateway restart`) to apply.")
|
|
410
|
+
} else if (saved) {
|
|
411
|
+
console.log(` ${c.red}Failed to write config. Add manually:${c.reset}`)
|
|
364
412
|
console.log()
|
|
365
|
-
for (const line of
|
|
366
|
-
console.log(` ${c.
|
|
413
|
+
for (const line of JSON.stringify(configObj, null, 2).split("\n")) {
|
|
414
|
+
console.log(` ${c.cyan}${line}${c.reset}`)
|
|
367
415
|
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const json = buildConfigJson(result)
|
|
371
|
-
if (json !== "{}") {
|
|
416
|
+
} else {
|
|
372
417
|
console.log()
|
|
373
|
-
console.log(` ${c.yellow}${c.bold}
|
|
418
|
+
console.log(` ${c.yellow}${c.bold}Add to openclaw.json plugins.entries.openclaw-cortex-ai.config:${c.reset}`)
|
|
374
419
|
console.log()
|
|
375
|
-
for (const line of
|
|
420
|
+
for (const line of JSON.stringify(configObj, null, 2).split("\n")) {
|
|
376
421
|
console.log(` ${c.cyan}${line}${c.reset}`)
|
|
377
422
|
}
|
|
378
423
|
}
|
|
379
|
-
|
|
380
|
-
printSuccess("Onboarding complete! All options configured.")
|
|
381
424
|
} finally {
|
|
382
425
|
rl.close()
|
|
383
426
|
}
|
|
@@ -386,7 +429,7 @@ async function runAdvancedWizard(cfg: CortexPluginConfig): Promise<void> {
|
|
|
386
429
|
// ── Registration (CLI + Slash) ──
|
|
387
430
|
|
|
388
431
|
export function registerOnboardingCli(
|
|
389
|
-
cfg
|
|
432
|
+
cfg?: CortexPluginConfig,
|
|
390
433
|
): (root: any) => void {
|
|
391
434
|
return (root: any) => {
|
|
392
435
|
root
|
package/config.ts
CHANGED
|
@@ -97,6 +97,32 @@ export function parseConfig(raw: unknown): CortexPluginConfig {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
export function tryParseConfig(raw: unknown): CortexPluginConfig | null {
|
|
101
|
+
try {
|
|
102
|
+
return parseConfig(raw)
|
|
103
|
+
} catch {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Permissive schema parse — validates key names but does NOT require credentials.
|
|
110
|
+
* This lets the plugin load so the onboarding wizard can run.
|
|
111
|
+
*/
|
|
112
|
+
function parseConfigSoft(raw: unknown): Record<string, unknown> {
|
|
113
|
+
const cfg =
|
|
114
|
+
raw && typeof raw === "object" && !Array.isArray(raw)
|
|
115
|
+
? (raw as Record<string, unknown>)
|
|
116
|
+
: {}
|
|
117
|
+
|
|
118
|
+
const unknown = Object.keys(cfg).filter((k) => !KNOWN_KEYS.has(k))
|
|
119
|
+
if (unknown.length > 0) {
|
|
120
|
+
throw new Error(`cortex-ai: unrecognized config keys: ${unknown.join(", ")}`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return cfg
|
|
124
|
+
}
|
|
125
|
+
|
|
100
126
|
export const cortexConfigSchema = {
|
|
101
|
-
parse:
|
|
127
|
+
parse: parseConfigSoft,
|
|
102
128
|
}
|
package/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
2
2
|
import { CortexClient } from "./client.ts"
|
|
3
|
-
import {
|
|
4
|
-
import { registerOnboardingCli, registerOnboardingSlashCommands } from "./commands/onboarding.ts"
|
|
3
|
+
import type { CortexPluginConfig } from "./config.ts"
|
|
4
|
+
import { registerOnboardingCli as createOnboardingCliRegistrar, registerOnboardingSlashCommands } from "./commands/onboarding.ts"
|
|
5
5
|
import { registerSlashCommands } from "./commands/slash.ts"
|
|
6
|
-
import { cortexConfigSchema,
|
|
6
|
+
import { cortexConfigSchema, tryParseConfig } from "./config.ts"
|
|
7
7
|
import { createIngestionHook } from "./hooks/capture.ts"
|
|
8
8
|
import { createRecallHook } from "./hooks/recall.ts"
|
|
9
9
|
import { log } from "./log.ts"
|
|
@@ -13,6 +13,9 @@ import { registerListTool } from "./tools/list.ts"
|
|
|
13
13
|
import { registerSearchTool } from "./tools/search.ts"
|
|
14
14
|
import { registerStoreTool } from "./tools/store.ts"
|
|
15
15
|
|
|
16
|
+
const NOT_CONFIGURED_MSG =
|
|
17
|
+
"[cortex-ai] Not configured. Run `openclaw cortex onboard` to set up credentials."
|
|
18
|
+
|
|
16
19
|
export default {
|
|
17
20
|
id: "openclaw-cortex-ai",
|
|
18
21
|
name: "Cortex AI",
|
|
@@ -22,8 +25,33 @@ export default {
|
|
|
22
25
|
configSchema: cortexConfigSchema,
|
|
23
26
|
|
|
24
27
|
register(api: OpenClawPluginApi) {
|
|
25
|
-
const cfg =
|
|
28
|
+
const cfg = tryParseConfig(api.pluginConfig)
|
|
29
|
+
const cliClient = cfg ? new CortexClient(cfg.apiKey, cfg.tenantId, cfg.subTenantId) : null
|
|
30
|
+
|
|
31
|
+
// Always register ALL CLI commands so they appear in help text.
|
|
32
|
+
// Non-onboard commands guard on credentials at runtime.
|
|
33
|
+
api.registerCli(
|
|
34
|
+
({ program }: { program: any }) => {
|
|
35
|
+
const root = program
|
|
36
|
+
.command("cortex")
|
|
37
|
+
.description("Cortex AI memory commands")
|
|
38
|
+
|
|
39
|
+
createOnboardingCliRegistrar(cfg ?? undefined)(root)
|
|
40
|
+
registerCortexCliCommands(root, cliClient, cfg)
|
|
41
|
+
},
|
|
42
|
+
{ commands: ["cortex"] },
|
|
43
|
+
)
|
|
26
44
|
|
|
45
|
+
if (!cfg) {
|
|
46
|
+
api.registerService({
|
|
47
|
+
id: "openclaw-cortex-ai",
|
|
48
|
+
start: () => console.log(NOT_CONFIGURED_MSG),
|
|
49
|
+
stop: () => {},
|
|
50
|
+
})
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Full plugin registration — credentials present
|
|
27
55
|
log.init(api.logger, cfg.debug)
|
|
28
56
|
|
|
29
57
|
const client = new CortexClient(cfg.apiKey, cfg.tenantId, cfg.subTenantId)
|
|
@@ -67,7 +95,6 @@ export default {
|
|
|
67
95
|
|
|
68
96
|
registerSlashCommands(api, client, cfg, getSessionId)
|
|
69
97
|
registerOnboardingSlashCommands(api, client, cfg)
|
|
70
|
-
registerCliCommands(api, client, cfg, registerOnboardingCli(cfg))
|
|
71
98
|
|
|
72
99
|
api.registerService({
|
|
73
100
|
id: "openclaw-cortex-ai",
|
|
@@ -76,3 +103,110 @@ export default {
|
|
|
76
103
|
})
|
|
77
104
|
},
|
|
78
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Register all `cortex *` CLI subcommands.
|
|
109
|
+
* Commands other than `onboard` guard on valid credentials at runtime.
|
|
110
|
+
*/
|
|
111
|
+
function registerCortexCliCommands(
|
|
112
|
+
root: any,
|
|
113
|
+
client: CortexClient | null,
|
|
114
|
+
cfg: CortexPluginConfig | null,
|
|
115
|
+
): void {
|
|
116
|
+
const requireCreds = (): { client: CortexClient; cfg: CortexPluginConfig } | null => {
|
|
117
|
+
if (client && cfg) return { client, cfg }
|
|
118
|
+
console.error(NOT_CONFIGURED_MSG)
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
root
|
|
123
|
+
.command("search")
|
|
124
|
+
.argument("<query>", "Search query")
|
|
125
|
+
.option("--limit <n>", "Max results", "10")
|
|
126
|
+
.action(async (query: string, opts: { limit: string }) => {
|
|
127
|
+
const ctx = requireCreds()
|
|
128
|
+
if (!ctx) return
|
|
129
|
+
|
|
130
|
+
const limit = Number.parseInt(opts.limit, 10) || 10
|
|
131
|
+
const res = await ctx.client.recall(query, {
|
|
132
|
+
maxResults: limit,
|
|
133
|
+
mode: ctx.cfg.recallMode,
|
|
134
|
+
graphContext: ctx.cfg.graphContext,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
if (!res.chunks || res.chunks.length === 0) {
|
|
138
|
+
console.log("No memories found.")
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const chunk of res.chunks) {
|
|
143
|
+
const score = chunk.relevancy_score != null
|
|
144
|
+
? ` (${(chunk.relevancy_score * 100).toFixed(0)}%)`
|
|
145
|
+
: ""
|
|
146
|
+
const title = chunk.source_title ? `[${chunk.source_title}] ` : ""
|
|
147
|
+
console.log(`- ${title}${chunk.chunk_content.slice(0, 200)}${score}`)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
root
|
|
152
|
+
.command("list")
|
|
153
|
+
.description("List all user memories")
|
|
154
|
+
.action(async () => {
|
|
155
|
+
const ctx = requireCreds()
|
|
156
|
+
if (!ctx) return
|
|
157
|
+
|
|
158
|
+
const res = await ctx.client.listMemories()
|
|
159
|
+
const memories = res.user_memories ?? []
|
|
160
|
+
if (memories.length === 0) {
|
|
161
|
+
console.log("No memories stored.")
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
for (const m of memories) {
|
|
165
|
+
console.log(`[${m.memory_id}] ${m.memory_content.slice(0, 150)}`)
|
|
166
|
+
}
|
|
167
|
+
console.log(`\nTotal: ${memories.length}`)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
root
|
|
171
|
+
.command("delete")
|
|
172
|
+
.argument("<memory_id>", "Memory ID to delete")
|
|
173
|
+
.action(async (memoryId: string) => {
|
|
174
|
+
const ctx = requireCreds()
|
|
175
|
+
if (!ctx) return
|
|
176
|
+
|
|
177
|
+
const res = await ctx.client.deleteMemory(memoryId)
|
|
178
|
+
console.log(res.user_memory_deleted ? `Deleted: ${memoryId}` : `Not found: ${memoryId}`)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
root
|
|
182
|
+
.command("get")
|
|
183
|
+
.argument("<source_id>", "Source ID to fetch")
|
|
184
|
+
.action(async (sourceId: string) => {
|
|
185
|
+
const ctx = requireCreds()
|
|
186
|
+
if (!ctx) return
|
|
187
|
+
|
|
188
|
+
const res = await ctx.client.fetchContent(sourceId)
|
|
189
|
+
if (!res.success || res.error) {
|
|
190
|
+
console.error(`Error: ${res.error ?? "unknown"}`)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
console.log(res.content ?? res.content_base64 ?? "(no text content)")
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
root
|
|
197
|
+
.command("status")
|
|
198
|
+
.description("Show plugin configuration")
|
|
199
|
+
.action(() => {
|
|
200
|
+
const ctx = requireCreds()
|
|
201
|
+
if (!ctx) return
|
|
202
|
+
|
|
203
|
+
console.log(`Tenant: ${ctx.client.getTenantId()}`)
|
|
204
|
+
console.log(`Sub-Tenant: ${ctx.client.getSubTenantId()}`)
|
|
205
|
+
console.log(`Auto-Recall: ${ctx.cfg.autoRecall}`)
|
|
206
|
+
console.log(`Auto-Capture: ${ctx.cfg.autoCapture}`)
|
|
207
|
+
console.log(`Recall Mode: ${ctx.cfg.recallMode}`)
|
|
208
|
+
console.log(`Graph: ${ctx.cfg.graphContext}`)
|
|
209
|
+
console.log(`Max Results: ${ctx.cfg.maxRecallResults}`)
|
|
210
|
+
console.log(`Ignore Term: ${ctx.cfg.ignoreTerm}`)
|
|
211
|
+
})
|
|
212
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usecortex_ai/openclaw-cortex-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin for Cortex AI — the State-of-the-art agentic memory system with auto-capture, recall, and knowledge graph context for open-claw",
|
|
6
6
|
"license": "MIT",
|