@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 CHANGED
@@ -10,16 +10,61 @@ openclaw plugins install @usecortex_ai/openclaw-cortex-ai
10
10
 
11
11
  Restart OpenClaw after installing.
12
12
 
13
- ## Configuration
13
+ If you run OpenClaw via the local gateway, restart it too:
14
14
 
15
- Two required values: your Cortex API key and tenant ID.
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 `openclaw.json`:
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 | Description |
89
- | ----------------- | ------------------------------------------- |
90
- | `cortex_store` | Save information to memory |
91
- | `cortex_search` | Search memories with graph-enriched results |
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:
@@ -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 buildConfigJson(result: WizardResult): string {
179
+ function buildConfigObj(result: WizardResult): Record<string, unknown> {
164
180
  const obj: Record<string, unknown> = {}
165
181
 
166
- if (result.apiKey && !result.apiKey.startsWith("$")) {
167
- obj.apiKey = result.apiKey
168
- }
169
- if (result.tenantId && !result.tenantId.startsWith("$")) {
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 !== "cortex-ignore") {
188
+ if (result.ignoreTerm !== DEFAULTS.ignoreTerm) {
176
189
  obj.ignoreTerm = result.ignoreTerm
177
190
  }
178
- if (result.autoRecall !== undefined && result.autoRecall !== true) {
191
+ if (result.autoRecall !== undefined && result.autoRecall !== DEFAULTS.autoRecall) {
179
192
  obj.autoRecall = result.autoRecall
180
193
  }
181
- if (result.autoCapture !== undefined && result.autoCapture !== true) {
194
+ if (result.autoCapture !== undefined && result.autoCapture !== DEFAULTS.autoCapture) {
182
195
  obj.autoCapture = result.autoCapture
183
196
  }
184
- if (result.maxRecallResults !== undefined && result.maxRecallResults !== 10) {
197
+ if (result.maxRecallResults !== undefined && result.maxRecallResults !== DEFAULTS.maxRecallResults) {
185
198
  obj.maxRecallResults = result.maxRecallResults
186
199
  }
187
- if (result.recallMode !== undefined && result.recallMode !== "fast") {
200
+ if (result.recallMode !== undefined && result.recallMode !== DEFAULTS.recallMode) {
188
201
  obj.recallMode = result.recallMode
189
202
  }
190
- if (result.graphContext !== undefined && result.graphContext !== true) {
203
+ if (result.graphContext !== undefined && result.graphContext !== DEFAULTS.graphContext) {
191
204
  obj.graphContext = result.graphContext
192
205
  }
193
- if (result.debug !== undefined && result.debug !== false) {
206
+ if (result.debug !== undefined && result.debug !== DEFAULTS.debug) {
194
207
  obj.debug = result.debug
195
208
  }
196
209
 
197
- return JSON.stringify(obj, null, 2)
210
+ return obj
198
211
  }
199
212
 
200
- function buildEnvLines(result: WizardResult): string[] {
201
- const lines: string[] = []
202
- if (result.apiKey && !result.apiKey.startsWith("$")) {
203
- lines.push(`CORTEX_OPENCLAW_API_KEY=${result.apiKey}`)
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 (result.tenantId && !result.tenantId.startsWith("$")) {
206
- lines.push(`CORTEX_OPENCLAW_TENANT_ID=${result.tenantId}`)
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: CortexPluginConfig): Promise<void> {
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
- // ── Output config ──
300
+ // ── Persist config ──
256
301
 
257
- const envLines = buildEnvLines(result)
258
- if (envLines.length > 0) {
259
- console.log()
260
- console.log(` ${c.yellow}${c.bold}Add to your .env file:${c.reset}`)
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 envLines) {
263
- console.log(` ${c.green}${line}${c.reset}`)
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}Plugin config (openclaw.plugin.json / settings):${c.reset}`)
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 json.split("\n")) {
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
- printSuccess("Onboarding complete! Run `cortex onboard --advanced` to fine-tune all options.")
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: CortexPluginConfig): Promise<void> {
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
- // ── Output config ──
403
+ // ── Persist config ──
359
404
 
360
- const envLines = buildEnvLines(result)
361
- if (envLines.length > 0) {
362
- console.log()
363
- console.log(` ${c.yellow}${c.bold}Add to your .env file:${c.reset}`)
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 envLines) {
366
- console.log(` ${c.green}${line}${c.reset}`)
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}Plugin config (openclaw.plugin.json / settings):${c.reset}`)
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 json.split("\n")) {
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: CortexPluginConfig,
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: parseConfig,
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 { registerCliCommands } from "./commands/cli.ts"
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, parseConfig } from "./config.ts"
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 = parseConfig(api.pluginConfig)
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.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",