@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.
- package/.github/workflows/publish.yaml +40 -0
- package/README.md +47 -27
- package/client.ts +11 -3
- package/commands/cli.ts +4 -0
- package/commands/onboarding.ts +442 -0
- package/commands/slash.ts +4 -4
- package/config.ts +8 -1
- package/hooks/capture.ts +76 -60
- package/hooks/recall.ts +6 -0
- package/index.ts +30 -9
- package/log.ts +27 -4
- package/messages.ts +88 -0
- package/openclaw.plugin.json +6 -0
- package/package.json +1 -1
- package/session.ts +10 -2
- package/tools/delete.ts +54 -0
- package/tools/get.ts +57 -0
- package/tools/list.ts +56 -0
- package/tools/search.ts +1 -1
- package/tools/store.ts +68 -14
- package/types/cortex.ts +2 -0
|
@@ -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
|
-
|
|
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
|
|
43
|
-
|
|
|
44
|
-
| `subTenantId` | `string` | `"cortex-openclaw"
|
|
45
|
-
| `autoRecall` | `boolean` | `true`
|
|
46
|
-
| `autoCapture` | `boolean` | `true`
|
|
47
|
-
| `maxRecallResults` | `number` | `10`
|
|
48
|
-
| `recallMode` | `string` | `"fast"`
|
|
49
|
-
| `graphContext` | `boolean` | `true`
|
|
50
|
-
| `
|
|
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
|
|
61
|
-
|
|
|
62
|
-
| `/cortex-
|
|
63
|
-
| `/cortex-
|
|
64
|
-
| `/cortex-
|
|
65
|
-
| `/cortex-
|
|
66
|
-
| `/cortex-
|
|
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
|
|
71
|
-
|
|
|
72
|
-
| `cortex_store`
|
|
73
|
-
| `cortex_search`
|
|
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
|
|
79
|
-
openclaw cortex
|
|
80
|
-
openclaw cortex
|
|
81
|
-
openclaw cortex
|
|
82
|
-
openclaw cortex
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
28
|
-
const sourceId =
|
|
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) {
|