@hydra_db/openclaw 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 +172 -0
- package/client.ts +214 -0
- package/commands/cli.ts +97 -0
- package/commands/onboarding.ts +485 -0
- package/commands/slash.ts +138 -0
- package/config.ts +128 -0
- package/context.ts +191 -0
- package/hooks/capture.ts +101 -0
- package/hooks/recall.ts +46 -0
- package/index.ts +212 -0
- package/log.ts +48 -0
- package/messages.ts +88 -0
- package/openclaw.plugin.json +74 -0
- package/package.json +27 -0
- package/session.ts +11 -0
- package/tools/delete.ts +54 -0
- package/tools/get.ts +57 -0
- package/tools/list.ts +56 -0
- package/tools/search.ts +64 -0
- package/tools/store.ts +116 -0
- package/tsconfig.json +23 -0
- package/types/hydra.ts +166 -0
- package/types/openclaw.d.ts +19 -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
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Hydra DB — OpenClaw Plugin
|
|
2
|
+
|
|
3
|
+
State-of-the-art agentic memory for OpenClaw powered by [Hydra DB](https://hydradb.com). Automatically captures conversations, recalls relevant context with knowledge-graph connections, and injects them before every AI turn.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
openclaw plugins install @hydra_db/openclaw-hydra-db
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Restart OpenClaw after installing.
|
|
12
|
+
|
|
13
|
+
If you run OpenClaw via the local gateway, restart it too:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
openclaw gateway restart
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Get Your Credentials
|
|
20
|
+
1. Get your Hydra API Key from [Hydra DB](https://app.hydradb.com)
|
|
21
|
+
2. Get your Tenant ID from the Hydra dashboard
|
|
22
|
+
|
|
23
|
+
## Interactive Onboarding
|
|
24
|
+
|
|
25
|
+
Run the interactive CLI wizard (recommended):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Basic onboarding (API key, tenant ID, sub-tenant, ignore term)
|
|
29
|
+
openclaw hydra onboard
|
|
30
|
+
|
|
31
|
+
# Advanced onboarding (all options including recall mode, graph context, etc.)
|
|
32
|
+
openclaw hydra onboard --advanced
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The wizard guides you through configuration with colored prompts and **writes your config to** `plugins.entries.openclaw-hydra-db.config` inside OpenClaw's settings file.
|
|
36
|
+
|
|
37
|
+
The path is resolved in the same order OpenClaw itself uses:
|
|
38
|
+
|
|
39
|
+
1. `$OPENCLAW_CONFIG_PATH` — if set, used directly
|
|
40
|
+
2. `$OPENCLAW_STATE_DIR/openclaw.json` — if `OPENCLAW_STATE_DIR` is set
|
|
41
|
+
3. `$OPENCLAW_HOME/.openclaw/openclaw.json` — if `OPENCLAW_HOME` is set
|
|
42
|
+
4. Default: `~/.openclaw/openclaw.json` (macOS/Linux) or `%USERPROFILE%\.openclaw\openclaw.json` (Windows)
|
|
43
|
+
|
|
44
|
+
No manual adjustment needed — the wizard auto-detects the correct path.
|
|
45
|
+
|
|
46
|
+
After onboarding, restart the gateway:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
openclaw gateway restart
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Manual Configuration
|
|
53
|
+
|
|
54
|
+
If you prefer, you can configure credentials manually.
|
|
55
|
+
|
|
56
|
+
Two required values:
|
|
57
|
+
|
|
58
|
+
- **API key**
|
|
59
|
+
- **Tenant ID**
|
|
60
|
+
|
|
61
|
+
Environment variables (recommended for secrets):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
export HYDRA_OPENCLAW_API_KEY="your-api-key"
|
|
65
|
+
export HYDRA_OPENCLAW_TENANT_ID="your-tenant-id"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Or configure directly in OpenClaw's settings file:
|
|
69
|
+
|
|
70
|
+
- **macOS / Linux:** `~/.openclaw/openclaw.json`
|
|
71
|
+
- **Windows:** `%USERPROFILE%\.openclaw\openclaw.json`
|
|
72
|
+
|
|
73
|
+
```json5
|
|
74
|
+
{
|
|
75
|
+
"plugins": {
|
|
76
|
+
"entries": {
|
|
77
|
+
"openclaw-hydra-db": {
|
|
78
|
+
"enabled": true,
|
|
79
|
+
"config": {
|
|
80
|
+
"apiKey": "${HYDRA_OPENCLAW_API_KEY}",
|
|
81
|
+
"tenantId": "${HYDRA_OPENCLAW_TENANT_ID}"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
After changing config, restart the gateway so the plugin reloads:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
openclaw gateway restart
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Options
|
|
96
|
+
|
|
97
|
+
| Key | Type | Default | Description |
|
|
98
|
+
| -------------------- | ----------- | --------------------- | ------------------------------------------------------------------------------ |
|
|
99
|
+
| `subTenantId` | `string` | `"hydra-openclaw-plugin"` | Sub-tenant for data partitioning within your tenant |
|
|
100
|
+
| `autoRecall` | `boolean` | `true` | Inject relevant memories before every AI turn |
|
|
101
|
+
| `autoCapture` | `boolean` | `true` | Store conversation exchanges after every AI turn |
|
|
102
|
+
| `maxRecallResults` | `number` | `10` | Max memory chunks injected into context per turn |
|
|
103
|
+
| `recallMode` | `string` | `"fast"` | `"fast"` or `"thinking"` (deeper personalised recall with graph traversal) |
|
|
104
|
+
| `graphContext` | `boolean` | `true` | Include knowledge graph relations in recalled context |
|
|
105
|
+
| `ignoreTerm` | `string` | `"hydra-ignore"` | Messages containing this term are excluded from recall & capture |
|
|
106
|
+
| `debug` | `boolean` | `false` | Verbose debug logs |
|
|
107
|
+
|
|
108
|
+
## How It Works
|
|
109
|
+
|
|
110
|
+
- **Auto-Recall** — Before every AI turn, queries Hydra (`/recall/recall_preferences`) for relevant memories and injects graph-enriched context (entity paths, chunk relations, extra context).
|
|
111
|
+
- **Auto-Capture** — After every AI turn, the last user/assistant exchange is sent to Hydra (`/memories/add_memory`) as conversation pairs with `infer: true` and `upsert: true`. The session ID is used as `source_id` so Hydra groups exchanges per session and builds a knowledge graph automatically.
|
|
112
|
+
|
|
113
|
+
## Slash Commands
|
|
114
|
+
|
|
115
|
+
| Command | Description |
|
|
116
|
+
| --------------------------- | ------------------------------------- |
|
|
117
|
+
| `/hydra-onboard` | Show current configuration status |
|
|
118
|
+
| `/hydra-remember <text>` | Save something to Hydra memory |
|
|
119
|
+
| `/hydra-recall <query>` | Search memories with relevance scores |
|
|
120
|
+
| `/hydra-list` | List all stored user memories |
|
|
121
|
+
| `/hydra-delete <id>` | Delete a specific memory by its ID |
|
|
122
|
+
| `/hydra-get <source_id>` | Fetch the full content of a source |
|
|
123
|
+
|
|
124
|
+
## AI Tools
|
|
125
|
+
|
|
126
|
+
| Tool | Description |
|
|
127
|
+
| ---------------------- | ----------- |
|
|
128
|
+
| `hydra_store` | Save the recent conversation history to Hydra as memory |
|
|
129
|
+
| `hydra_search` | Search Hydra memories (returns graph-enriched context) |
|
|
130
|
+
| `hydra_list_memories` | List all stored user memories (IDs + summaries) |
|
|
131
|
+
| `hydra_get_content` | Fetch full content for a specific `source_id` |
|
|
132
|
+
| `hydra_delete_memory` | Delete a memory by `memory_id` (use only when user explicitly asks) |
|
|
133
|
+
|
|
134
|
+
## CLI
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
openclaw hydra onboard # Interactive onboarding wizard
|
|
138
|
+
openclaw hydra onboard --advanced # Advanced onboarding wizard
|
|
139
|
+
openclaw hydra search <query> # Search memories
|
|
140
|
+
openclaw hydra list # List all user memories
|
|
141
|
+
openclaw hydra delete <id> # Delete a memory
|
|
142
|
+
openclaw hydra get <source_id> # Fetch source content
|
|
143
|
+
openclaw hydra status # Show plugin configuration
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Troubleshooting
|
|
147
|
+
|
|
148
|
+
### `Not configured. Run openclaw hydra onboard`
|
|
149
|
+
|
|
150
|
+
This means the plugin is enabled, but credentials are missing.
|
|
151
|
+
|
|
152
|
+
Run:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
openclaw hydra onboard
|
|
156
|
+
openclaw gateway restart
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### CLI says a command is unknown
|
|
160
|
+
|
|
161
|
+
Update/restart the gateway so it reloads the plugin:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
openclaw gateway restart
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Context Injection
|
|
168
|
+
|
|
169
|
+
Recalled context is injected inside `<hydra-context>` tags containing:
|
|
170
|
+
|
|
171
|
+
- **Entity Paths** — Knowledge graph paths connecting entities relevant to the query
|
|
172
|
+
- **Context Chunks** — Retrieved memory chunks with source titles, graph relations, and linked extra context
|
package/client.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { log } from "./log.ts"
|
|
2
|
+
import type {
|
|
3
|
+
AddMemoryRequest,
|
|
4
|
+
AddMemoryResponse,
|
|
5
|
+
ConversationTurn,
|
|
6
|
+
DeleteMemoryResponse,
|
|
7
|
+
FetchContentRequest,
|
|
8
|
+
FetchContentResponse,
|
|
9
|
+
ListDataRequest,
|
|
10
|
+
ListMemoriesResponse,
|
|
11
|
+
ListSourcesResponse,
|
|
12
|
+
RecallRequest,
|
|
13
|
+
RecallResponse,
|
|
14
|
+
} from "./types/hydra.ts"
|
|
15
|
+
|
|
16
|
+
const API_BASE = "https://api.hydradb.com"
|
|
17
|
+
|
|
18
|
+
const INGEST_INSTRUCTIONS =
|
|
19
|
+
"Focus on extracting user preferences, habits, opinions, likes, dislikes, " +
|
|
20
|
+
"goals, and recurring themes. Capture any stated or implied personal context " +
|
|
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."
|
|
24
|
+
|
|
25
|
+
export class HydraClient {
|
|
26
|
+
private apiKey: string
|
|
27
|
+
private tenantId: string
|
|
28
|
+
private subTenantId: string
|
|
29
|
+
|
|
30
|
+
constructor(apiKey: string, tenantId: string, subTenantId: string) {
|
|
31
|
+
this.apiKey = apiKey
|
|
32
|
+
this.tenantId = tenantId
|
|
33
|
+
this.subTenantId = subTenantId
|
|
34
|
+
log.info(`connected (tenant=${tenantId}, sub=${subTenantId})`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private headers(): Record<string, string> {
|
|
38
|
+
return {
|
|
39
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private async post<T>(path: string, body: unknown): Promise<T> {
|
|
45
|
+
const url = `${API_BASE}${path}`
|
|
46
|
+
log.debug("POST", path, body)
|
|
47
|
+
const res = await fetch(url, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: this.headers(),
|
|
50
|
+
body: JSON.stringify(body),
|
|
51
|
+
})
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
const text = await res.text().catch(() => "")
|
|
54
|
+
throw new Error(`Hydra ${path} → ${res.status}: ${text}`)
|
|
55
|
+
}
|
|
56
|
+
return res.json() as Promise<T>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async del<T>(path: string, params: Record<string, string>): Promise<T> {
|
|
60
|
+
const qs = new URLSearchParams(params).toString()
|
|
61
|
+
const url = `${API_BASE}${path}?${qs}`
|
|
62
|
+
log.debug("DELETE", path, params)
|
|
63
|
+
const res = await fetch(url, {
|
|
64
|
+
method: "DELETE",
|
|
65
|
+
headers: this.headers(),
|
|
66
|
+
})
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
const text = await res.text().catch(() => "")
|
|
69
|
+
throw new Error(`Hydra ${path} → ${res.status}: ${text}`)
|
|
70
|
+
}
|
|
71
|
+
return res.json() as Promise<T>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- Ingest ---
|
|
75
|
+
|
|
76
|
+
async ingestConversation(
|
|
77
|
+
turns: ConversationTurn[],
|
|
78
|
+
sourceId: string,
|
|
79
|
+
opts?: {
|
|
80
|
+
userName?: string
|
|
81
|
+
metadata?: Record<string, unknown>
|
|
82
|
+
},
|
|
83
|
+
): Promise<AddMemoryResponse> {
|
|
84
|
+
const payload: AddMemoryRequest = {
|
|
85
|
+
memories: [
|
|
86
|
+
{
|
|
87
|
+
user_assistant_pairs: turns,
|
|
88
|
+
infer: true,
|
|
89
|
+
source_id: sourceId,
|
|
90
|
+
user_name: opts?.userName ?? "User",
|
|
91
|
+
custom_instructions: INGEST_INSTRUCTIONS,
|
|
92
|
+
...(opts?.metadata && {
|
|
93
|
+
document_metadata: JSON.stringify(opts.metadata),
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
tenant_id: this.tenantId,
|
|
98
|
+
sub_tenant_id: this.subTenantId,
|
|
99
|
+
upsert: true,
|
|
100
|
+
}
|
|
101
|
+
return this.post<AddMemoryResponse>("/memories/add_memory", payload)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async ingestText(
|
|
105
|
+
text: string,
|
|
106
|
+
opts?: {
|
|
107
|
+
sourceId?: string
|
|
108
|
+
title?: string
|
|
109
|
+
infer?: boolean
|
|
110
|
+
isMarkdown?: boolean
|
|
111
|
+
customInstructions?: string
|
|
112
|
+
},
|
|
113
|
+
): Promise<AddMemoryResponse> {
|
|
114
|
+
const shouldInfer = opts?.infer ?? true
|
|
115
|
+
const payload: AddMemoryRequest = {
|
|
116
|
+
memories: [
|
|
117
|
+
{
|
|
118
|
+
text,
|
|
119
|
+
infer: shouldInfer,
|
|
120
|
+
is_markdown: opts?.isMarkdown ?? false,
|
|
121
|
+
...(shouldInfer && {
|
|
122
|
+
custom_instructions: opts?.customInstructions ?? INGEST_INSTRUCTIONS,
|
|
123
|
+
}),
|
|
124
|
+
...(opts?.sourceId && { source_id: opts.sourceId }),
|
|
125
|
+
...(opts?.title && { title: opts.title }),
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
tenant_id: this.tenantId,
|
|
129
|
+
sub_tenant_id: this.subTenantId,
|
|
130
|
+
upsert: true,
|
|
131
|
+
}
|
|
132
|
+
return this.post<AddMemoryResponse>("/memories/add_memory", payload)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- Recall ---
|
|
136
|
+
|
|
137
|
+
async recall(
|
|
138
|
+
query: string,
|
|
139
|
+
opts?: {
|
|
140
|
+
maxResults?: number
|
|
141
|
+
mode?: "fast" | "thinking"
|
|
142
|
+
graphContext?: boolean
|
|
143
|
+
recencyBias?: number
|
|
144
|
+
},
|
|
145
|
+
): Promise<RecallResponse> {
|
|
146
|
+
const payload: RecallRequest = {
|
|
147
|
+
tenant_id: this.tenantId,
|
|
148
|
+
sub_tenant_id: this.subTenantId,
|
|
149
|
+
query,
|
|
150
|
+
max_results: opts?.maxResults ?? 10,
|
|
151
|
+
mode: opts?.mode ?? "thinking",
|
|
152
|
+
alpha: 0.8,
|
|
153
|
+
recency_bias: opts?.recencyBias ?? 0,
|
|
154
|
+
graph_context: opts?.graphContext ?? true,
|
|
155
|
+
}
|
|
156
|
+
return this.post<RecallResponse>("/recall/recall_preferences", payload)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --- List ---
|
|
160
|
+
|
|
161
|
+
async listMemories(): Promise<ListMemoriesResponse> {
|
|
162
|
+
const payload: ListDataRequest = {
|
|
163
|
+
tenant_id: this.tenantId,
|
|
164
|
+
sub_tenant_id: this.subTenantId,
|
|
165
|
+
kind: "memories",
|
|
166
|
+
}
|
|
167
|
+
return this.post<ListMemoriesResponse>("/list/data", payload)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async listSources(sourceIds?: string[]): Promise<ListSourcesResponse> {
|
|
171
|
+
const payload: ListDataRequest = {
|
|
172
|
+
tenant_id: this.tenantId,
|
|
173
|
+
sub_tenant_id: this.subTenantId,
|
|
174
|
+
kind: "memories",
|
|
175
|
+
...(sourceIds && { source_ids: sourceIds }),
|
|
176
|
+
}
|
|
177
|
+
return this.post<ListSourcesResponse>("/list/data", payload)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// --- Delete ---
|
|
181
|
+
|
|
182
|
+
async deleteMemory(memoryId: string): Promise<DeleteMemoryResponse> {
|
|
183
|
+
return this.del<DeleteMemoryResponse>("/memories/delete_memory", {
|
|
184
|
+
tenant_id: this.tenantId,
|
|
185
|
+
memory_id: memoryId,
|
|
186
|
+
sub_tenant_id: this.subTenantId,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// --- Fetch Content ---
|
|
191
|
+
|
|
192
|
+
async fetchContent(
|
|
193
|
+
sourceId: string,
|
|
194
|
+
mode: "content" | "url" | "both" = "content",
|
|
195
|
+
): Promise<FetchContentResponse> {
|
|
196
|
+
const payload: FetchContentRequest = {
|
|
197
|
+
tenant_id: this.tenantId,
|
|
198
|
+
sub_tenant_id: this.subTenantId,
|
|
199
|
+
source_id: sourceId,
|
|
200
|
+
mode,
|
|
201
|
+
}
|
|
202
|
+
return this.post<FetchContentResponse>("/fetch/content", payload)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// --- Accessors ---
|
|
206
|
+
|
|
207
|
+
getTenantId(): string {
|
|
208
|
+
return this.tenantId
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
getSubTenantId(): string {
|
|
212
|
+
return this.subTenantId
|
|
213
|
+
}
|
|
214
|
+
}
|
package/commands/cli.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
|
|
2
|
+
import type { HydraClient } from "../client.ts"
|
|
3
|
+
import type { HydraPluginConfig } from "../config.ts"
|
|
4
|
+
|
|
5
|
+
export function registerCliCommands(
|
|
6
|
+
api: OpenClawPluginApi,
|
|
7
|
+
client: HydraClient,
|
|
8
|
+
cfg: HydraPluginConfig,
|
|
9
|
+
onboardingRegistrar?: (root: any) => void,
|
|
10
|
+
): void {
|
|
11
|
+
api.registerCli(
|
|
12
|
+
({ program }: { program: any }) => {
|
|
13
|
+
const root = program
|
|
14
|
+
.command("hydra")
|
|
15
|
+
.description("Hydra DB memory commands")
|
|
16
|
+
|
|
17
|
+
root
|
|
18
|
+
.command("search")
|
|
19
|
+
.argument("<query>", "Search query")
|
|
20
|
+
.option("--limit <n>", "Max results", "10")
|
|
21
|
+
.action(async (query: string, opts: { limit: string }) => {
|
|
22
|
+
const limit = Number.parseInt(opts.limit, 10) || 10
|
|
23
|
+
const res = await client.recall(query, {
|
|
24
|
+
maxResults: limit,
|
|
25
|
+
mode: cfg.recallMode,
|
|
26
|
+
graphContext: cfg.graphContext,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
if (!res.chunks || res.chunks.length === 0) {
|
|
30
|
+
console.log("No memories found.")
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const chunk of res.chunks) {
|
|
35
|
+
const score = chunk.relevancy_score != null
|
|
36
|
+
? ` (${(chunk.relevancy_score * 100).toFixed(0)}%)`
|
|
37
|
+
: ""
|
|
38
|
+
const title = chunk.source_title ? `[${chunk.source_title}] ` : ""
|
|
39
|
+
console.log(`- ${title}${chunk.chunk_content.slice(0, 200)}${score}`)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
root
|
|
44
|
+
.command("list")
|
|
45
|
+
.description("List all user memories")
|
|
46
|
+
.action(async () => {
|
|
47
|
+
const res = await client.listMemories()
|
|
48
|
+
const memories = res.user_memories ?? []
|
|
49
|
+
if (memories.length === 0) {
|
|
50
|
+
console.log("No memories stored.")
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
for (const m of memories) {
|
|
54
|
+
console.log(`[${m.memory_id}] ${m.memory_content.slice(0, 150)}`)
|
|
55
|
+
}
|
|
56
|
+
console.log(`\nTotal: ${memories.length}`)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
root
|
|
60
|
+
.command("delete")
|
|
61
|
+
.argument("<memory_id>", "Memory ID to delete")
|
|
62
|
+
.action(async (memoryId: string) => {
|
|
63
|
+
const res = await client.deleteMemory(memoryId)
|
|
64
|
+
console.log(res.user_memory_deleted ? `Deleted: ${memoryId}` : `Not found: ${memoryId}`)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
root
|
|
68
|
+
.command("get")
|
|
69
|
+
.argument("<source_id>", "Source ID to fetch")
|
|
70
|
+
.action(async (sourceId: string) => {
|
|
71
|
+
const res = await client.fetchContent(sourceId)
|
|
72
|
+
if (!res.success || res.error) {
|
|
73
|
+
console.error(`Error: ${res.error ?? "unknown"}`)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
console.log(res.content ?? res.content_base64 ?? "(no text content)")
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
root
|
|
80
|
+
.command("status")
|
|
81
|
+
.description("Show plugin configuration")
|
|
82
|
+
.action(() => {
|
|
83
|
+
console.log(`Tenant: ${client.getTenantId()}`)
|
|
84
|
+
console.log(`Sub-Tenant: ${client.getSubTenantId()}`)
|
|
85
|
+
console.log(`Auto-Recall: ${cfg.autoRecall}`)
|
|
86
|
+
console.log(`Auto-Capture: ${cfg.autoCapture}`)
|
|
87
|
+
console.log(`Recall Mode: ${cfg.recallMode}`)
|
|
88
|
+
console.log(`Graph: ${cfg.graphContext}`)
|
|
89
|
+
console.log(`Max Results: ${cfg.maxRecallResults}`)
|
|
90
|
+
console.log(`Ignore Term: ${cfg.ignoreTerm}`)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
if (onboardingRegistrar) onboardingRegistrar(root)
|
|
94
|
+
},
|
|
95
|
+
{ commands: ["hydra"] },
|
|
96
|
+
)
|
|
97
|
+
}
|