@usecortex_ai/openclaw-cortex-ai 0.0.1 → 0.1.1

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.
@@ -0,0 +1,40 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ publish:
10
+ name: Publish package to npm
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Set up Node.js
18
+ uses: actions/setup-node@v4
19
+ with:
20
+ node-version: '20'
21
+ registry-url: 'https://registry.npmjs.org'
22
+
23
+ - name: Install dependencies
24
+ run: npm ci
25
+
26
+ - name: Build package
27
+ # If you have a build step, otherwise remove this
28
+ run: npm run build
29
+ continue-on-error: true
30
+
31
+ - name: Publish to npm
32
+ env:
33
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
34
+ run: |
35
+ # Only publish if this is not a pre-release and version has changed
36
+ if [ "$(npm view . version)" != "$(node -p "require('./package.json').version")" ]; then
37
+ npm publish --access public
38
+ else
39
+ echo "Version already published, skipping."
40
+ fi
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Cortex AI — OpenClaw Plugin
2
2
 
3
- Long-term memory for OpenClaw powered by [Cortex AI](https://usecortex.ai). Automatically captures conversations, recalls relevant context with knowledge-graph connections, and injects them before every AI turn.
3
+ State-of-the-art agentic memory for OpenClaw powered by [Cortex AI](https://usecortex.ai). Automatically captures conversations, recalls relevant context with knowledge-graph connections, and injects them before every AI turn.
4
4
 
5
5
  ## Install
6
6
 
@@ -39,47 +39,67 @@ Or configure directly in `openclaw.json`:
39
39
 
40
40
  ### Options
41
41
 
42
- | Key | Type | Default | Description |
43
- | ------------------ | --------- | ------------------ | -------------------------------------------------------------------------- |
44
- | `subTenantId` | `string` | `"cortex-openclaw"`| Sub-tenant for data partitioning within your tenant |
45
- | `autoRecall` | `boolean` | `true` | Inject relevant memories before every AI turn |
46
- | `autoCapture` | `boolean` | `true` | Store conversation exchanges after every AI turn |
47
- | `maxRecallResults` | `number` | `10` | Max memory chunks injected into context per turn |
48
- | `recallMode` | `string` | `"fast"` | `"fast"` or `"thinking"` (deeper personalised recall with graph traversal) |
49
- | `graphContext` | `boolean` | `true` | Include knowledge graph relations in recalled context |
50
- | `debug` | `boolean` | `false` | Verbose debug logs |
42
+ | Key | Type | Default | Description |
43
+ | -------------------- | ----------- | --------------------- | ------------------------------------------------------------------------------ |
44
+ | `subTenantId` | `string` | `"cortex-openclaw"` | Sub-tenant for data partitioning within your tenant |
45
+ | `autoRecall` | `boolean` | `true` | Inject relevant memories before every AI turn |
46
+ | `autoCapture` | `boolean` | `true` | Store conversation exchanges after every AI turn |
47
+ | `maxRecallResults` | `number` | `10` | Max memory chunks injected into context per turn |
48
+ | `recallMode` | `string` | `"fast"` | `"fast"` or `"thinking"` (deeper personalised recall with graph traversal) |
49
+ | `graphContext` | `boolean` | `true` | Include knowledge graph relations in recalled context |
50
+ | `ignoreTerm` | `string` | `"cortex-ignore"` | Messages containing this term are excluded from recall & capture |
51
+ | `debug` | `boolean` | `false` | Verbose debug logs |
51
52
 
52
53
  ## How It Works
53
54
 
54
55
  - **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).
55
-
56
56
  - **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
57
 
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
+
58
75
  ## Slash Commands
59
76
 
60
- | Command | Description |
61
- | -------------------------- | ------------------------------------------------- |
62
- | `/cortex-remember <text>` | Save something to Cortex memory |
63
- | `/cortex-recall <query>` | Search memories with relevance scores |
64
- | `/cortex-list` | List all stored user memories |
65
- | `/cortex-delete <id>` | Delete a specific memory by its ID |
66
- | `/cortex-get <source_id>` | Fetch the full content of a source |
77
+ | Command | Description |
78
+ | --------------------------- | ------------------------------------- |
79
+ | `/cortex-onboard` | Show current configuration status |
80
+ | `/cortex-remember <text>` | Save something to Cortex memory |
81
+ | `/cortex-recall <query>` | Search memories with relevance scores |
82
+ | `/cortex-list` | List all stored user memories |
83
+ | `/cortex-delete <id>` | Delete a specific memory by its ID |
84
+ | `/cortex-get <source_id>` | Fetch the full content of a source |
67
85
 
68
86
  ## AI Tools
69
87
 
70
- | Tool | Description |
71
- | ---------------- | --------------------------------------------------------- |
72
- | `cortex_store` | Save information to long-term memory |
73
- | `cortex_search` | Search memories with graph-enriched results |
88
+ | Tool | Description |
89
+ | ----------------- | ------------------------------------------- |
90
+ | `cortex_store` | Save information to memory |
91
+ | `cortex_search` | Search memories with graph-enriched results |
74
92
 
75
93
  ## CLI
76
94
 
77
95
  ```bash
78
- openclaw cortex search <query> # Search memories
79
- openclaw cortex list # List all user memories
80
- openclaw cortex delete <id> # Delete a memory
81
- openclaw cortex get <source_id> # Fetch source content
82
- openclaw cortex status # Show plugin configuration
96
+ openclaw cortex onboard # Interactive onboarding wizard
97
+ openclaw cortex onboard --advanced # Advanced onboarding wizard
98
+ openclaw cortex search <query> # Search memories
99
+ openclaw cortex list # List all user memories
100
+ openclaw cortex delete <id> # Delete a memory
101
+ openclaw cortex get <source_id> # Fetch source content
102
+ openclaw cortex status # Show plugin configuration
83
103
  ```
84
104
 
85
105
  ## Context Injection
package/client.ts CHANGED
@@ -18,7 +18,9 @@ const API_BASE = "https://api.usecortex.ai"
18
18
  const INGEST_INSTRUCTIONS =
19
19
  "Focus on extracting user preferences, habits, opinions, likes, dislikes, " +
20
20
  "goals, and recurring themes. Capture any stated or implied personal context " +
21
- "that would help personalise future interactions."
21
+ "that would help personalise future interactions. Capture important personal details like " +
22
+ "name, age, email ids, phone numbers, etc. along with the original name and context " +
23
+ "so that it can be used to personalise future interactions."
22
24
 
23
25
  export class CortexClient {
24
26
  private apiKey: string
@@ -74,7 +76,10 @@ export class CortexClient {
74
76
  async ingestConversation(
75
77
  turns: ConversationTurn[],
76
78
  sourceId: string,
77
- userName?: string,
79
+ opts?: {
80
+ userName?: string
81
+ metadata?: Record<string, unknown>
82
+ },
78
83
  ): Promise<AddMemoryResponse> {
79
84
  const payload: AddMemoryRequest = {
80
85
  memories: [
@@ -82,8 +87,11 @@ export class CortexClient {
82
87
  user_assistant_pairs: turns,
83
88
  infer: true,
84
89
  source_id: sourceId,
85
- user_name: userName ?? "User",
90
+ user_name: opts?.userName ?? "User",
86
91
  custom_instructions: INGEST_INSTRUCTIONS,
92
+ ...(opts?.metadata && {
93
+ document_metadata: JSON.stringify(opts.metadata),
94
+ }),
87
95
  },
88
96
  ],
89
97
  tenant_id: this.tenantId,
package/commands/cli.ts CHANGED
@@ -6,6 +6,7 @@ export function registerCliCommands(
6
6
  api: OpenClawPluginApi,
7
7
  client: CortexClient,
8
8
  cfg: CortexPluginConfig,
9
+ onboardingRegistrar?: (root: any) => void,
9
10
  ): void {
10
11
  api.registerCli(
11
12
  ({ program }: { program: any }) => {
@@ -86,7 +87,10 @@ export function registerCliCommands(
86
87
  console.log(`Recall Mode: ${cfg.recallMode}`)
87
88
  console.log(`Graph: ${cfg.graphContext}`)
88
89
  console.log(`Max Results: ${cfg.maxRecallResults}`)
90
+ console.log(`Ignore Term: ${cfg.ignoreTerm}`)
89
91
  })
92
+
93
+ if (onboardingRegistrar) onboardingRegistrar(root)
90
94
  },
91
95
  { commands: ["cortex"] },
92
96
  )
@@ -0,0 +1,442 @@
1
+ import * as readline from "node:readline"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { CortexClient } from "../client.ts"
4
+ import type { CortexPluginConfig } from "../config.ts"
5
+ import { log } from "../log.ts"
6
+
7
+ // ── ANSI helpers ──
8
+
9
+ const c = {
10
+ reset: "\x1b[0m",
11
+ bold: "\x1b[1m",
12
+ dim: "\x1b[2m",
13
+ cyan: "\x1b[36m",
14
+ green: "\x1b[32m",
15
+ yellow: "\x1b[33m",
16
+ red: "\x1b[31m",
17
+ magenta: "\x1b[35m",
18
+ white: "\x1b[37m",
19
+ bgCyan: "\x1b[46m",
20
+ bgGreen: "\x1b[42m",
21
+ black: "\x1b[30m",
22
+ }
23
+
24
+ function mask(value: string, visible = 4): string {
25
+ if (value.length <= visible) return "****"
26
+ return `${"*".repeat(value.length - visible)}${value.slice(-visible)}`
27
+ }
28
+
29
+ // ── Prompt primitives ──
30
+
31
+ function createRl(): readline.Interface {
32
+ return readline.createInterface({
33
+ input: process.stdin,
34
+ output: process.stdout,
35
+ })
36
+ }
37
+
38
+ function ask(rl: readline.Interface, question: string): Promise<string> {
39
+ return new Promise((resolve) => rl.question(question, resolve))
40
+ }
41
+
42
+ async function promptText(
43
+ rl: readline.Interface,
44
+ label: string,
45
+ opts?: { default?: string; required?: boolean; secret?: boolean },
46
+ ): Promise<string> {
47
+ const def = opts?.default
48
+ const hint = def ? `${c.dim} (${def})${c.reset}` : opts?.required ? `${c.red} *${c.reset}` : ""
49
+ const prefix = ` ${c.cyan}?${c.reset} ${c.bold}${label}${c.reset}${hint}${c.dim} ›${c.reset} `
50
+
51
+ while (true) {
52
+ const raw = await ask(rl, prefix)
53
+ const value = raw.trim()
54
+ if (value) return value
55
+ if (def) return def
56
+ if (opts?.required) {
57
+ console.log(` ${c.red}This field is required.${c.reset}`)
58
+ continue
59
+ }
60
+ return ""
61
+ }
62
+ }
63
+
64
+ async function promptChoice(
65
+ rl: readline.Interface,
66
+ label: string,
67
+ choices: string[],
68
+ defaultChoice: string,
69
+ ): Promise<string> {
70
+ const tags = choices
71
+ .map((ch) => (ch === defaultChoice ? `${c.green}${c.bold}${ch}${c.reset}` : `${c.dim}${ch}${c.reset}`))
72
+ .join(`${c.dim} / ${c.reset}`)
73
+
74
+ const prefix = ` ${c.cyan}?${c.reset} ${c.bold}${label}${c.reset} ${tags}${c.dim} ›${c.reset} `
75
+
76
+ while (true) {
77
+ const raw = await ask(rl, prefix)
78
+ const value = raw.trim().toLowerCase()
79
+ if (!value) return defaultChoice
80
+ const match = choices.find((ch) => ch.toLowerCase() === value)
81
+ if (match) return match
82
+ console.log(` ${c.yellow}Choose one of: ${choices.join(", ")}${c.reset}`)
83
+ }
84
+ }
85
+
86
+ async function promptBool(
87
+ rl: readline.Interface,
88
+ label: string,
89
+ defaultVal: boolean,
90
+ ): Promise<boolean> {
91
+ const hint = defaultVal
92
+ ? `${c.dim} (${c.green}Y${c.reset}${c.dim}/n)${c.reset}`
93
+ : `${c.dim} (y/${c.green}N${c.reset}${c.dim})${c.reset}`
94
+ const prefix = ` ${c.cyan}?${c.reset} ${c.bold}${label}${c.reset}${hint}${c.dim} ›${c.reset} `
95
+
96
+ const raw = await ask(rl, prefix)
97
+ const value = raw.trim().toLowerCase()
98
+ if (!value) return defaultVal
99
+ return value === "y" || value === "yes" || value === "true"
100
+ }
101
+
102
+ async function promptNumber(
103
+ rl: readline.Interface,
104
+ label: string,
105
+ defaultVal: number,
106
+ min: number,
107
+ max: number,
108
+ ): Promise<number> {
109
+ const prefix = ` ${c.cyan}?${c.reset} ${c.bold}${label}${c.reset}${c.dim} (${defaultVal}) [${min}–${max}] ›${c.reset} `
110
+
111
+ while (true) {
112
+ const raw = await ask(rl, prefix)
113
+ const value = raw.trim()
114
+ if (!value) return defaultVal
115
+ const n = Number.parseInt(value, 10)
116
+ if (!Number.isNaN(n) && n >= min && n <= max) return n
117
+ console.log(` ${c.yellow}Enter a number between ${min} and ${max}.${c.reset}`)
118
+ }
119
+ }
120
+
121
+ // ── Banner ──
122
+
123
+ function printBanner(): void {
124
+ console.log()
125
+ console.log(` ${c.bgCyan}${c.black}${c.bold} ${c.reset}`)
126
+ console.log(` ${c.bgCyan}${c.black}${c.bold} ◆ Cortex AI — Onboard ${c.reset}`)
127
+ console.log(` ${c.bgCyan}${c.black}${c.bold} ${c.reset}`)
128
+ console.log()
129
+ }
130
+
131
+ function printSection(title: string): void {
132
+ console.log()
133
+ console.log(` ${c.magenta}${c.bold}── ${title} ${"─".repeat(Math.max(0, 40 - title.length))}${c.reset}`)
134
+ console.log()
135
+ }
136
+
137
+ function printSummaryRow(label: string, value: string, sensitive = false): void {
138
+ const display = sensitive ? mask(value) : value
139
+ console.log(` ${c.dim}│${c.reset} ${c.bold}${label.padEnd(18)}${c.reset} ${c.cyan}${display}${c.reset}`)
140
+ }
141
+
142
+ function printSuccess(msg: string): void {
143
+ console.log()
144
+ console.log(` ${c.bgGreen}${c.black}${c.bold} ✓ ${c.reset} ${c.green}${msg}${c.reset}`)
145
+ console.log()
146
+ }
147
+
148
+ // ── Config output ──
149
+
150
+ type WizardResult = {
151
+ apiKey: string
152
+ tenantId: string
153
+ subTenantId: string
154
+ ignoreTerm: string
155
+ autoRecall?: boolean
156
+ autoCapture?: boolean
157
+ maxRecallResults?: number
158
+ recallMode?: "fast" | "thinking"
159
+ graphContext?: boolean
160
+ debug?: boolean
161
+ }
162
+
163
+ function buildConfigJson(result: WizardResult): string {
164
+ const obj: Record<string, unknown> = {}
165
+
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") {
173
+ obj.subTenantId = result.subTenantId
174
+ }
175
+ if (result.ignoreTerm !== "cortex-ignore") {
176
+ obj.ignoreTerm = result.ignoreTerm
177
+ }
178
+ if (result.autoRecall !== undefined && result.autoRecall !== true) {
179
+ obj.autoRecall = result.autoRecall
180
+ }
181
+ if (result.autoCapture !== undefined && result.autoCapture !== true) {
182
+ obj.autoCapture = result.autoCapture
183
+ }
184
+ if (result.maxRecallResults !== undefined && result.maxRecallResults !== 10) {
185
+ obj.maxRecallResults = result.maxRecallResults
186
+ }
187
+ if (result.recallMode !== undefined && result.recallMode !== "fast") {
188
+ obj.recallMode = result.recallMode
189
+ }
190
+ if (result.graphContext !== undefined && result.graphContext !== true) {
191
+ obj.graphContext = result.graphContext
192
+ }
193
+ if (result.debug !== undefined && result.debug !== false) {
194
+ obj.debug = result.debug
195
+ }
196
+
197
+ return JSON.stringify(obj, null, 2)
198
+ }
199
+
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}`)
204
+ }
205
+ if (result.tenantId && !result.tenantId.startsWith("$")) {
206
+ lines.push(`CORTEX_OPENCLAW_TENANT_ID=${result.tenantId}`)
207
+ }
208
+ return lines
209
+ }
210
+
211
+ // ── Wizards ──
212
+
213
+ async function runBasicWizard(cfg: CortexPluginConfig): Promise<void> {
214
+ const rl = createRl()
215
+
216
+ try {
217
+ printBanner()
218
+ console.log(` ${c.dim}Configure the essential settings for Cortex AI.${c.reset}`)
219
+ console.log(` ${c.dim}Press Enter to accept defaults shown in parentheses.${c.reset}`)
220
+
221
+ printSection("Credentials")
222
+
223
+ const apiKey = await promptText(rl, "API Key", {
224
+ required: true,
225
+ secret: true,
226
+ })
227
+
228
+ const tenantId = await promptText(rl, "Tenant ID", {
229
+ required: true,
230
+ })
231
+
232
+ printSection("Customisation")
233
+
234
+ const subTenantId = await promptText(rl, "Sub-Tenant ID", {
235
+ default: cfg.subTenantId,
236
+ })
237
+
238
+ const ignoreTerm = await promptText(rl, "Ignore Term", {
239
+ default: cfg.ignoreTerm,
240
+ })
241
+
242
+ const result: WizardResult = { apiKey, tenantId, subTenantId, ignoreTerm }
243
+
244
+ // ── Summary ──
245
+
246
+ printSection("Summary")
247
+
248
+ console.log(` ${c.dim}┌${"─".repeat(50)}${c.reset}`)
249
+ printSummaryRow("API Key", apiKey, true)
250
+ printSummaryRow("Tenant ID", tenantId)
251
+ printSummaryRow("Sub-Tenant ID", subTenantId)
252
+ printSummaryRow("Ignore Term", ignoreTerm)
253
+ console.log(` ${c.dim}└${"─".repeat(50)}${c.reset}`)
254
+
255
+ // ── Output config ──
256
+
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}`)
261
+ console.log()
262
+ for (const line of envLines) {
263
+ console.log(` ${c.green}${line}${c.reset}`)
264
+ }
265
+ }
266
+
267
+ const json = buildConfigJson(result)
268
+ if (json !== "{}") {
269
+ console.log()
270
+ console.log(` ${c.yellow}${c.bold}Plugin config (openclaw.plugin.json / settings):${c.reset}`)
271
+ console.log()
272
+ for (const line of json.split("\n")) {
273
+ console.log(` ${c.cyan}${line}${c.reset}`)
274
+ }
275
+ }
276
+
277
+ printSuccess("Onboarding complete! Run `cortex onboard --advanced` to fine-tune all options.")
278
+ } finally {
279
+ rl.close()
280
+ }
281
+ }
282
+
283
+ async function runAdvancedWizard(cfg: CortexPluginConfig): Promise<void> {
284
+ const rl = createRl()
285
+
286
+ try {
287
+ printBanner()
288
+ console.log(` ${c.dim}Full configuration wizard — customise every option.${c.reset}`)
289
+ console.log(` ${c.dim}Press Enter to accept defaults shown in parentheses.${c.reset}`)
290
+
291
+ printSection("Credentials")
292
+
293
+ const apiKey = await promptText(rl, "API Key", {
294
+ required: true,
295
+ secret: true,
296
+ })
297
+
298
+ const tenantId = await promptText(rl, "Tenant ID", {
299
+ required: true,
300
+ })
301
+
302
+ const subTenantId = await promptText(rl, "Sub-Tenant ID", {
303
+ default: cfg.subTenantId,
304
+ })
305
+
306
+ printSection("Behaviour")
307
+
308
+ const autoRecall = await promptBool(rl, "Enable Auto-Recall?", cfg.autoRecall)
309
+ const autoCapture = await promptBool(rl, "Enable Auto-Capture?", cfg.autoCapture)
310
+ const ignoreTerm = await promptText(rl, "Ignore Term", {
311
+ default: cfg.ignoreTerm,
312
+ })
313
+
314
+ printSection("Recall Settings")
315
+
316
+ const maxRecallResults = await promptNumber(
317
+ rl, "Max Recall Results", cfg.maxRecallResults, 1, 50,
318
+ )
319
+ const recallMode = await promptChoice(
320
+ rl, "Recall Mode", ["fast", "thinking"], cfg.recallMode,
321
+ ) as "fast" | "thinking"
322
+ const graphContext = await promptBool(rl, "Enable Graph Context?", cfg.graphContext)
323
+
324
+ printSection("Debug")
325
+
326
+ const debug = await promptBool(rl, "Enable Debug Logging?", cfg.debug)
327
+
328
+ const result: WizardResult = {
329
+ apiKey,
330
+ tenantId,
331
+ subTenantId,
332
+ ignoreTerm,
333
+ autoRecall,
334
+ autoCapture,
335
+ maxRecallResults,
336
+ recallMode,
337
+ graphContext,
338
+ debug,
339
+ }
340
+
341
+ // ── Summary ──
342
+
343
+ printSection("Summary")
344
+
345
+ console.log(` ${c.dim}┌${"─".repeat(50)}${c.reset}`)
346
+ printSummaryRow("API Key", apiKey, true)
347
+ printSummaryRow("Tenant ID", tenantId)
348
+ printSummaryRow("Sub-Tenant ID", subTenantId)
349
+ printSummaryRow("Auto-Recall", String(autoRecall))
350
+ printSummaryRow("Auto-Capture", String(autoCapture))
351
+ printSummaryRow("Ignore Term", ignoreTerm)
352
+ printSummaryRow("Max Results", String(maxRecallResults))
353
+ printSummaryRow("Recall Mode", recallMode)
354
+ printSummaryRow("Graph Context", String(graphContext))
355
+ printSummaryRow("Debug", String(debug))
356
+ console.log(` ${c.dim}└${"─".repeat(50)}${c.reset}`)
357
+
358
+ // ── Output config ──
359
+
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}`)
364
+ console.log()
365
+ for (const line of envLines) {
366
+ console.log(` ${c.green}${line}${c.reset}`)
367
+ }
368
+ }
369
+
370
+ const json = buildConfigJson(result)
371
+ if (json !== "{}") {
372
+ console.log()
373
+ console.log(` ${c.yellow}${c.bold}Plugin config (openclaw.plugin.json / settings):${c.reset}`)
374
+ console.log()
375
+ for (const line of json.split("\n")) {
376
+ console.log(` ${c.cyan}${line}${c.reset}`)
377
+ }
378
+ }
379
+
380
+ printSuccess("Onboarding complete! All options configured.")
381
+ } finally {
382
+ rl.close()
383
+ }
384
+ }
385
+
386
+ // ── Registration (CLI + Slash) ──
387
+
388
+ export function registerOnboardingCli(
389
+ cfg: CortexPluginConfig,
390
+ ): (root: any) => void {
391
+ return (root: any) => {
392
+ root
393
+ .command("onboard")
394
+ .description("Interactive Cortex AI onboarding wizard")
395
+ .option("--advanced", "Configure all options (credentials, behaviour, recall, debug)")
396
+ .action(async (opts: { advanced?: boolean }) => {
397
+ if (opts.advanced) {
398
+ await runAdvancedWizard(cfg)
399
+ } else {
400
+ await runBasicWizard(cfg)
401
+ }
402
+ })
403
+ }
404
+ }
405
+
406
+ export function registerOnboardingSlashCommands(
407
+ api: OpenClawPluginApi,
408
+ client: CortexClient,
409
+ cfg: CortexPluginConfig,
410
+ ): void {
411
+ api.registerCommand({
412
+ name: "cortex-onboard",
413
+ description: "Show Cortex plugin config status (run `cortex onboard` in CLI for interactive wizard)",
414
+ acceptsArgs: false,
415
+ requireAuth: false,
416
+ handler: async () => {
417
+ try {
418
+ const lines: string[] = [
419
+ "=== Cortex AI — Current Config ===",
420
+ "",
421
+ ` API Key: ${cfg.apiKey ? `${mask(cfg.apiKey)} ✓` : "NOT SET ✗"}`,
422
+ ` Tenant ID: ${cfg.tenantId ? `${mask(cfg.tenantId, 8)} ✓` : "NOT SET ✗"}`,
423
+ ` Sub-Tenant: ${client.getSubTenantId()}`,
424
+ ` Ignore Term: ${cfg.ignoreTerm}`,
425
+ ` Auto-Recall: ${cfg.autoRecall}`,
426
+ ` Auto-Capture: ${cfg.autoCapture}`,
427
+ ` Recall Mode: ${cfg.recallMode}`,
428
+ ` Graph Context: ${cfg.graphContext}`,
429
+ ` Max Results: ${cfg.maxRecallResults}`,
430
+ ` Debug: ${cfg.debug}`,
431
+ "",
432
+ "Tip: Run `cortex onboard` in the CLI for an interactive configuration wizard,",
433
+ " or `cortex onboard --advanced` for all options.",
434
+ ]
435
+ return { text: lines.join("\n") }
436
+ } catch (err) {
437
+ log.error("/cortex-onboard", err)
438
+ return { text: "Failed to show status. Check logs." }
439
+ }
440
+ },
441
+ })
442
+ }
package/commands/slash.ts CHANGED
@@ -2,7 +2,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
2
2
  import type { CortexClient } from "../client.ts"
3
3
  import type { CortexPluginConfig } from "../config.ts"
4
4
  import { log } from "../log.ts"
5
- import { toSourceId } from "../session.ts"
5
+ import { toToolSourceId } from "../session.ts"
6
6
 
7
7
  function preview(text: string, max = 80): string {
8
8
  return text.length > max ? `${text.slice(0, max)}…` : text
@@ -12,7 +12,7 @@ export function registerSlashCommands(
12
12
  api: OpenClawPluginApi,
13
13
  client: CortexClient,
14
14
  cfg: CortexPluginConfig,
15
- getSessionKey: () => string | undefined,
15
+ getSessionId: () => string | undefined,
16
16
  ): void {
17
17
  api.registerCommand({
18
18
  name: "cortex-remember",
@@ -24,8 +24,8 @@ export function registerSlashCommands(
24
24
  if (!text) return { text: "Usage: /cortex-remember <text to store>" }
25
25
 
26
26
  try {
27
- const sk = getSessionKey()
28
- const sourceId = sk ? toSourceId(sk) : undefined
27
+ const sid = getSessionId()
28
+ const sourceId = sid ? toToolSourceId(sid) : undefined
29
29
  await client.ingestText(text, { sourceId, title: "Manual Memory", infer: true })
30
30
  return { text: `Saved: "${preview(text, 60)}"` }
31
31
  } catch (err) {