@cortexmemory/cli 0.1.0 → 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/dist/commands/conversations.js +1 -1
- package/dist/commands/conversations.js.map +1 -1
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +550 -102
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/dev.js +2 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/facts.d.ts.map +1 -1
- package/dist/commands/facts.js +31 -30
- package/dist/commands/facts.js.map +1 -1
- package/dist/commands/memory.d.ts.map +1 -1
- package/dist/commands/memory.js +22 -23
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +186 -16
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/users.d.ts.map +1 -1
- package/dist/commands/users.js +79 -10
- package/dist/commands/users.js.map +1 -1
- package/dist/index.js +20 -6
- package/dist/index.js.map +1 -1
- package/dist/utils/client.d.ts +1 -0
- package/dist/utils/client.d.ts.map +1 -1
- package/dist/utils/client.js +7 -1
- package/dist/utils/client.js.map +1 -1
- package/dist/utils/env-file.d.ts +48 -0
- package/dist/utils/env-file.d.ts.map +1 -0
- package/dist/utils/env-file.js +152 -0
- package/dist/utils/env-file.js.map +1 -0
- package/dist/utils/formatting.d.ts.map +1 -1
- package/dist/utils/formatting.js +4 -0
- package/dist/utils/formatting.js.map +1 -1
- package/package.json +13 -7
package/dist/commands/db.js
CHANGED
|
@@ -11,9 +11,67 @@ import ora from "ora";
|
|
|
11
11
|
import { withClient } from "../utils/client.js";
|
|
12
12
|
import { resolveConfig } from "../utils/config.js";
|
|
13
13
|
import { formatOutput, printSuccess, printError, printWarning, printSection, formatTimestamp, formatBytes, } from "../utils/formatting.js";
|
|
14
|
-
import { validateFilePath, requireConfirmation
|
|
14
|
+
import { validateFilePath, requireConfirmation } from "../utils/validation.js";
|
|
15
15
|
import { writeFile, readFile } from "fs/promises";
|
|
16
16
|
import pc from "picocolors";
|
|
17
|
+
import prompts from "prompts";
|
|
18
|
+
import { listDeployments } from "../utils/config.js";
|
|
19
|
+
const MAX_LIMIT = 1000;
|
|
20
|
+
/**
|
|
21
|
+
* Select a database deployment interactively or from options
|
|
22
|
+
* Returns updated globalOpts with the selected deployment
|
|
23
|
+
*/
|
|
24
|
+
async function selectDatabase(config, globalOpts, actionDescription) {
|
|
25
|
+
const deployments = listDeployments(config);
|
|
26
|
+
if (deployments.length === 0) {
|
|
27
|
+
printError("No deployments configured. Run 'cortex setup' first.");
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
// Determine target deployment
|
|
31
|
+
let targetDeployment = deployments.find((d) => d.isDefault);
|
|
32
|
+
let targetUrl = targetDeployment?.url ?? "";
|
|
33
|
+
let targetName = targetDeployment?.name ?? config.default;
|
|
34
|
+
// If --deployment flag was passed, use that
|
|
35
|
+
if (globalOpts.deployment) {
|
|
36
|
+
const specified = deployments.find((d) => d.name === globalOpts.deployment);
|
|
37
|
+
if (specified) {
|
|
38
|
+
targetDeployment = specified;
|
|
39
|
+
targetUrl = specified.url;
|
|
40
|
+
targetName = specified.name;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
printError(`Deployment "${globalOpts.deployment}" not found`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// If multiple deployments and none specified, ask which one
|
|
48
|
+
if (deployments.length > 1 && !globalOpts.deployment) {
|
|
49
|
+
console.log();
|
|
50
|
+
console.log(`Current target: ${pc.cyan(targetName)} (${pc.dim(targetUrl)})`);
|
|
51
|
+
console.log();
|
|
52
|
+
const selectResponse = await prompts({
|
|
53
|
+
type: "select",
|
|
54
|
+
name: "deployment",
|
|
55
|
+
message: `Select database to ${actionDescription}:`,
|
|
56
|
+
choices: deployments.map((d) => ({
|
|
57
|
+
title: d.isDefault ? `${d.name} (default)` : d.name,
|
|
58
|
+
description: d.url,
|
|
59
|
+
value: d.name,
|
|
60
|
+
})),
|
|
61
|
+
initial: deployments.findIndex((d) => d.name === targetName),
|
|
62
|
+
});
|
|
63
|
+
if (!selectResponse.deployment) {
|
|
64
|
+
printWarning("Operation cancelled");
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
targetName = selectResponse.deployment;
|
|
68
|
+
const selected = deployments.find((d) => d.name === targetName);
|
|
69
|
+
targetUrl = selected?.url ?? "";
|
|
70
|
+
// Update globalOpts to use selected deployment
|
|
71
|
+
globalOpts = { ...globalOpts, deployment: targetName };
|
|
72
|
+
}
|
|
73
|
+
return { globalOpts, targetName, targetUrl };
|
|
74
|
+
}
|
|
17
75
|
/**
|
|
18
76
|
* Register database commands
|
|
19
77
|
*/
|
|
@@ -24,65 +82,137 @@ export function registerDbCommands(program, config) {
|
|
|
24
82
|
.description("Show database statistics")
|
|
25
83
|
.option("-f, --format <format>", "Output format: table, json")
|
|
26
84
|
.action(async (options) => {
|
|
27
|
-
|
|
85
|
+
let globalOpts = program.opts();
|
|
28
86
|
const resolved = resolveConfig(config, globalOpts);
|
|
29
87
|
const format = (options.format ?? resolved.format);
|
|
30
|
-
const spinner = ora("Loading database statistics...").start();
|
|
31
88
|
try {
|
|
89
|
+
// Select database
|
|
90
|
+
const selection = await selectDatabase(config, globalOpts, "view stats for");
|
|
91
|
+
if (!selection)
|
|
92
|
+
return;
|
|
93
|
+
globalOpts = selection.globalOpts;
|
|
94
|
+
const { targetName, targetUrl } = selection;
|
|
95
|
+
const spinner = ora(`Loading statistics for ${targetName}...`).start();
|
|
32
96
|
await withClient(config, globalOpts, async (client) => {
|
|
33
|
-
// Get
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
97
|
+
// Get deployment info
|
|
98
|
+
const info = {
|
|
99
|
+
url: targetUrl,
|
|
100
|
+
isLocal: targetUrl.includes("127.0.0.1") ||
|
|
101
|
+
targetUrl.includes("localhost"),
|
|
102
|
+
};
|
|
103
|
+
const rawClient = client.getClient();
|
|
104
|
+
// Get comprehensive counts from all tables using admin function
|
|
105
|
+
spinner.text = "Counting all tables...";
|
|
106
|
+
let tableCounts = {};
|
|
107
|
+
try {
|
|
108
|
+
tableCounts = await rawClient.query("admin:getAllCounts", {});
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Fall back to individual counts if admin function not available
|
|
112
|
+
tableCounts = {
|
|
113
|
+
agents: 0,
|
|
114
|
+
contexts: 0,
|
|
115
|
+
conversations: 0,
|
|
116
|
+
facts: 0,
|
|
117
|
+
governanceEnforcement: 0,
|
|
118
|
+
governancePolicies: 0,
|
|
119
|
+
graphSyncQueue: 0,
|
|
120
|
+
immutable: 0,
|
|
121
|
+
memories: 0,
|
|
122
|
+
memorySpaces: 0,
|
|
123
|
+
mutable: 0,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Get user count from SDK (users may be managed separately)
|
|
127
|
+
let usersCount = 0;
|
|
128
|
+
try {
|
|
129
|
+
usersCount = await client.users.count();
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Users API may not be available
|
|
133
|
+
}
|
|
134
|
+
// Count messages in conversations
|
|
135
|
+
spinner.text = "Counting messages...";
|
|
136
|
+
let totalMessages = 0;
|
|
137
|
+
try {
|
|
138
|
+
const convos = await client.conversations.list({
|
|
139
|
+
limit: MAX_LIMIT,
|
|
140
|
+
});
|
|
141
|
+
for (const convo of convos) {
|
|
142
|
+
totalMessages += convo.messageCount ?? 0;
|
|
52
143
|
}
|
|
53
144
|
}
|
|
145
|
+
catch {
|
|
146
|
+
// Skip if not available
|
|
147
|
+
}
|
|
54
148
|
spinner.stop();
|
|
55
149
|
const stats = {
|
|
56
|
-
memorySpaces:
|
|
57
|
-
conversations:
|
|
58
|
-
memories:
|
|
59
|
-
facts:
|
|
150
|
+
memorySpaces: tableCounts.memorySpaces ?? 0,
|
|
151
|
+
conversations: tableCounts.conversations ?? 0,
|
|
152
|
+
memories: tableCounts.memories ?? 0,
|
|
153
|
+
facts: tableCounts.facts ?? 0,
|
|
60
154
|
users: usersCount,
|
|
61
|
-
immutableRecords: 0,
|
|
62
|
-
mutableRecords: 0,
|
|
63
|
-
contexts: 0,
|
|
155
|
+
immutableRecords: tableCounts.immutable ?? 0,
|
|
156
|
+
mutableRecords: tableCounts.mutable ?? 0,
|
|
157
|
+
contexts: tableCounts.contexts ?? 0,
|
|
64
158
|
};
|
|
65
159
|
if (format === "json") {
|
|
66
|
-
console.log(formatOutput(
|
|
160
|
+
console.log(formatOutput({
|
|
161
|
+
...stats,
|
|
162
|
+
agents: tableCounts.agents ?? 0,
|
|
163
|
+
messages: totalMessages,
|
|
164
|
+
governancePolicies: tableCounts.governancePolicies ?? 0,
|
|
165
|
+
governanceEnforcement: tableCounts.governanceEnforcement ?? 0,
|
|
166
|
+
graphSyncQueue: tableCounts.graphSyncQueue ?? 0,
|
|
167
|
+
deployment: {
|
|
168
|
+
name: globalOpts.deployment ?? config.default ?? "default",
|
|
169
|
+
url: info.url,
|
|
170
|
+
isLocal: info.isLocal,
|
|
171
|
+
},
|
|
172
|
+
}, "json"));
|
|
67
173
|
}
|
|
68
174
|
else {
|
|
69
175
|
console.log();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
console.log(
|
|
176
|
+
console.log(pc.bold(`📊 Database Statistics: ${pc.cyan(targetName)}`));
|
|
177
|
+
console.log(pc.dim("─".repeat(45)));
|
|
178
|
+
console.log();
|
|
179
|
+
// Core entities
|
|
180
|
+
console.log(pc.bold(" Core Entities"));
|
|
181
|
+
console.log(` Memory Spaces: ${pc.yellow(String(stats.memorySpaces))}`);
|
|
182
|
+
console.log(` Users: ${pc.yellow(String(stats.users))}`);
|
|
183
|
+
console.log(` Agents: ${pc.yellow(String(tableCounts.agents ?? 0))}`);
|
|
184
|
+
console.log();
|
|
185
|
+
// Memory data
|
|
186
|
+
console.log(pc.bold(" Memory Data"));
|
|
187
|
+
console.log(` Memories: ${pc.yellow(String(stats.memories))}`);
|
|
188
|
+
console.log(` Facts: ${pc.yellow(String(stats.facts))}`);
|
|
189
|
+
console.log(` Contexts: ${pc.yellow(String(stats.contexts))}`);
|
|
190
|
+
console.log();
|
|
191
|
+
// Conversation data
|
|
192
|
+
console.log(pc.bold(" Conversations"));
|
|
193
|
+
console.log(` Conversations: ${pc.yellow(String(stats.conversations))}`);
|
|
194
|
+
console.log(` Messages: ${pc.yellow(String(totalMessages))}`);
|
|
195
|
+
console.log();
|
|
196
|
+
// Shared stores
|
|
197
|
+
console.log(pc.bold(" Shared Stores"));
|
|
198
|
+
console.log(` Immutable: ${pc.yellow(String(stats.immutableRecords))}`);
|
|
199
|
+
console.log(` Mutable: ${pc.yellow(String(stats.mutableRecords))}`);
|
|
200
|
+
console.log();
|
|
201
|
+
// System tables
|
|
202
|
+
console.log(pc.bold(" System Tables"));
|
|
203
|
+
console.log(` Gov. Policies: ${pc.yellow(String(tableCounts.governancePolicies ?? 0))}`);
|
|
204
|
+
console.log(` Gov. Logs: ${pc.yellow(String(tableCounts.governanceEnforcement ?? 0))}`);
|
|
205
|
+
console.log(` Graph Sync Queue: ${pc.yellow(String(tableCounts.graphSyncQueue ?? 0))}`);
|
|
206
|
+
console.log();
|
|
207
|
+
// Deployment info
|
|
208
|
+
console.log(pc.bold(" Deployment"));
|
|
209
|
+
console.log(` URL: ${pc.dim(info.url)}`);
|
|
210
|
+
console.log(` Mode: ${info.isLocal ? pc.green("Local") : pc.blue("Cloud")}`);
|
|
211
|
+
console.log();
|
|
81
212
|
}
|
|
82
213
|
});
|
|
83
214
|
}
|
|
84
215
|
catch (error) {
|
|
85
|
-
spinner.stop();
|
|
86
216
|
printError(error instanceof Error ? error.message : "Failed to load statistics");
|
|
87
217
|
process.exit(1);
|
|
88
218
|
}
|
|
@@ -90,67 +220,368 @@ export function registerDbCommands(program, config) {
|
|
|
90
220
|
// db clear
|
|
91
221
|
db.command("clear")
|
|
92
222
|
.description("Clear entire database (DANGEROUS!)")
|
|
93
|
-
.option("--
|
|
223
|
+
.option("-y, --yes", "Skip confirmation prompt", false)
|
|
94
224
|
.action(async (options) => {
|
|
95
|
-
|
|
225
|
+
let globalOpts = program.opts();
|
|
96
226
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
227
|
+
console.log();
|
|
228
|
+
console.log(pc.red(pc.bold("⚠️ DANGER: Clear Database")));
|
|
229
|
+
// Select database
|
|
230
|
+
const selection = await selectDatabase(config, globalOpts, "clear");
|
|
231
|
+
if (!selection)
|
|
232
|
+
return;
|
|
233
|
+
globalOpts = selection.globalOpts;
|
|
234
|
+
const { targetName, targetUrl } = selection;
|
|
235
|
+
console.log();
|
|
236
|
+
console.log("This will permanently delete:");
|
|
237
|
+
console.log(" • All memory spaces and memories");
|
|
238
|
+
console.log(" • All conversations and messages");
|
|
239
|
+
console.log(" • All facts and user profiles");
|
|
240
|
+
// Check if graph sync is enabled (same logic as Cortex.create())
|
|
241
|
+
const neo4jUri = process.env.NEO4J_URI;
|
|
242
|
+
const memgraphUri = process.env.MEMGRAPH_URI;
|
|
243
|
+
const graphSyncEnabled = process.env.CORTEX_GRAPH_SYNC === "true" ||
|
|
244
|
+
!!(neo4jUri || memgraphUri);
|
|
245
|
+
// Debug: Show env var detection
|
|
246
|
+
if (process.env.DEBUG || program.opts().debug) {
|
|
247
|
+
console.log(pc.dim(` [DEBUG] CORTEX_GRAPH_SYNC=${process.env.CORTEX_GRAPH_SYNC}`));
|
|
248
|
+
console.log(pc.dim(` [DEBUG] NEO4J_URI=${neo4jUri ? "set" : "unset"}`));
|
|
249
|
+
console.log(pc.dim(` [DEBUG] MEMGRAPH_URI=${memgraphUri ? "set" : "unset"}`));
|
|
250
|
+
console.log(pc.dim(` [DEBUG] graphSyncEnabled=${graphSyncEnabled}`));
|
|
251
|
+
}
|
|
252
|
+
if (graphSyncEnabled) {
|
|
253
|
+
const dbType = neo4jUri ? "Neo4j" : "Memgraph";
|
|
254
|
+
console.log(` • All graph database nodes and relationships (${dbType})`);
|
|
255
|
+
}
|
|
256
|
+
console.log();
|
|
257
|
+
// Simple y/N confirmation
|
|
258
|
+
if (!options.yes) {
|
|
259
|
+
const confirmResponse = await prompts({
|
|
260
|
+
type: "confirm",
|
|
261
|
+
name: "confirmed",
|
|
262
|
+
message: `Clear ALL data from ${pc.red(targetName)}?`,
|
|
263
|
+
initial: false,
|
|
264
|
+
});
|
|
265
|
+
if (!confirmResponse.confirmed) {
|
|
116
266
|
printWarning("Operation cancelled");
|
|
117
267
|
return;
|
|
118
268
|
}
|
|
119
269
|
}
|
|
120
|
-
const spinner = ora(
|
|
270
|
+
const spinner = ora(`Clearing ${targetName}...`).start();
|
|
121
271
|
await withClient(config, globalOpts, async (client) => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
272
|
+
const deleted = {
|
|
273
|
+
agents: 0,
|
|
274
|
+
contexts: 0,
|
|
275
|
+
conversations: 0,
|
|
276
|
+
messages: 0,
|
|
277
|
+
facts: 0,
|
|
278
|
+
memories: 0,
|
|
279
|
+
memorySpaces: 0,
|
|
280
|
+
immutable: 0,
|
|
281
|
+
mutable: 0,
|
|
282
|
+
users: 0,
|
|
283
|
+
governancePolicies: 0,
|
|
284
|
+
governanceEnforcement: 0,
|
|
285
|
+
graphSyncQueue: 0,
|
|
286
|
+
};
|
|
287
|
+
// Get raw Convex client for direct table access via admin functions
|
|
288
|
+
const rawClient = client.getClient();
|
|
289
|
+
// Helper to clear a table using the admin:clearTable mutation
|
|
290
|
+
const clearTableDirect = async (tableName, counter) => {
|
|
291
|
+
let hasMore = true;
|
|
292
|
+
while (hasMore) {
|
|
293
|
+
spinner.text = `Clearing ${tableName}... (${deleted[counter]} deleted)`;
|
|
294
|
+
try {
|
|
295
|
+
const result = await rawClient.mutation("admin:clearTable", { table: tableName, limit: MAX_LIMIT });
|
|
296
|
+
deleted[counter] += result.deleted;
|
|
297
|
+
hasMore = result.hasMore;
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
hasMore = false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
// 1. Clear agents (using SDK for proper unregister)
|
|
305
|
+
let hasMoreAgents = true;
|
|
306
|
+
while (hasMoreAgents) {
|
|
307
|
+
spinner.text = `Clearing agents... (${deleted.agents} deleted)`;
|
|
126
308
|
try {
|
|
127
|
-
await client.
|
|
128
|
-
|
|
309
|
+
const agents = await client.agents.list({ limit: MAX_LIMIT });
|
|
310
|
+
if (agents.length === 0) {
|
|
311
|
+
hasMoreAgents = false;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
for (const agent of agents) {
|
|
315
|
+
try {
|
|
316
|
+
await client.agents.unregister(agent.id, { cascade: false });
|
|
317
|
+
deleted.agents++;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Continue on error
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (agents.length < MAX_LIMIT) {
|
|
324
|
+
hasMoreAgents = false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Fall back to direct table clear if SDK fails
|
|
329
|
+
await clearTableDirect("agents", "agents");
|
|
330
|
+
hasMoreAgents = false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// 2. Clear contexts (using SDK for cascade)
|
|
334
|
+
let hasMoreContexts = true;
|
|
335
|
+
while (hasMoreContexts) {
|
|
336
|
+
spinner.text = `Clearing contexts... (${deleted.contexts} deleted)`;
|
|
337
|
+
try {
|
|
338
|
+
const contexts = await client.contexts.list({ limit: MAX_LIMIT });
|
|
339
|
+
if (contexts.length === 0) {
|
|
340
|
+
hasMoreContexts = false;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
for (const ctx of contexts) {
|
|
344
|
+
try {
|
|
345
|
+
await client.contexts.delete(ctx.contextId, {
|
|
346
|
+
cascadeChildren: true,
|
|
347
|
+
});
|
|
348
|
+
deleted.contexts++;
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// Continue on error
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (contexts.length < MAX_LIMIT) {
|
|
355
|
+
hasMoreContexts = false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
await clearTableDirect("contexts", "contexts");
|
|
360
|
+
hasMoreContexts = false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// 3. Clear conversations (count messages)
|
|
364
|
+
let hasMoreConvos = true;
|
|
365
|
+
while (hasMoreConvos) {
|
|
366
|
+
spinner.text = `Clearing conversations... (${deleted.conversations} deleted, ${deleted.messages} messages)`;
|
|
367
|
+
try {
|
|
368
|
+
const convos = await client.conversations.list({
|
|
369
|
+
limit: MAX_LIMIT,
|
|
129
370
|
});
|
|
130
|
-
|
|
371
|
+
if (convos.length === 0) {
|
|
372
|
+
hasMoreConvos = false;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
for (const convo of convos) {
|
|
376
|
+
try {
|
|
377
|
+
deleted.messages += convo.messageCount || 0;
|
|
378
|
+
await client.conversations.delete(convo.conversationId);
|
|
379
|
+
deleted.conversations++;
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// Continue on error
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (convos.length < MAX_LIMIT) {
|
|
386
|
+
hasMoreConvos = false;
|
|
387
|
+
}
|
|
131
388
|
}
|
|
132
389
|
catch {
|
|
133
|
-
|
|
390
|
+
await clearTableDirect("conversations", "conversations");
|
|
391
|
+
hasMoreConvos = false;
|
|
134
392
|
}
|
|
135
393
|
}
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
394
|
+
// 4. Clear facts (direct table clear)
|
|
395
|
+
await clearTableDirect("facts", "facts");
|
|
396
|
+
// 5. Clear memories (direct table clear)
|
|
397
|
+
await clearTableDirect("memories", "memories");
|
|
398
|
+
// 6. Clear memory spaces (using raw client for invalid IDs)
|
|
399
|
+
let hasMoreSpaces = true;
|
|
400
|
+
while (hasMoreSpaces) {
|
|
401
|
+
spinner.text = `Clearing memorySpaces... (${deleted.memorySpaces} deleted)`;
|
|
140
402
|
try {
|
|
141
|
-
await client.
|
|
142
|
-
|
|
403
|
+
const spaces = await client.memorySpaces.list({
|
|
404
|
+
limit: MAX_LIMIT,
|
|
405
|
+
});
|
|
406
|
+
if (spaces.length === 0) {
|
|
407
|
+
hasMoreSpaces = false;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
for (const space of spaces) {
|
|
411
|
+
try {
|
|
412
|
+
await rawClient.mutation("memorySpaces:deleteSpace", { memorySpaceId: space.memorySpaceId, cascade: true });
|
|
413
|
+
deleted.memorySpaces++;
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// Continue on error
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (spaces.length < MAX_LIMIT) {
|
|
420
|
+
hasMoreSpaces = false;
|
|
421
|
+
}
|
|
143
422
|
}
|
|
144
423
|
catch {
|
|
145
|
-
|
|
424
|
+
await clearTableDirect("memorySpaces", "memorySpaces");
|
|
425
|
+
hasMoreSpaces = false;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// 7. Clear immutable (using SDK)
|
|
429
|
+
let hasMoreImmutable = true;
|
|
430
|
+
while (hasMoreImmutable) {
|
|
431
|
+
spinner.text = `Clearing immutable... (${deleted.immutable} deleted)`;
|
|
432
|
+
try {
|
|
433
|
+
const records = await client.immutable.list({ limit: MAX_LIMIT });
|
|
434
|
+
if (records.length === 0) {
|
|
435
|
+
hasMoreImmutable = false;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
for (const record of records) {
|
|
439
|
+
try {
|
|
440
|
+
await client.immutable.purge(record.type, record.id);
|
|
441
|
+
deleted.immutable++;
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
// Continue on error
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (records.length < MAX_LIMIT) {
|
|
448
|
+
hasMoreImmutable = false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
await clearTableDirect("immutable", "immutable");
|
|
453
|
+
hasMoreImmutable = false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// 8. Clear mutable (direct table clear)
|
|
457
|
+
await clearTableDirect("mutable", "mutable");
|
|
458
|
+
// 9. Clear users (using SDK for cascade - users table is virtual/SDK-managed)
|
|
459
|
+
let hasMoreUsers = true;
|
|
460
|
+
while (hasMoreUsers) {
|
|
461
|
+
spinner.text = `Clearing users... (${deleted.users} deleted)`;
|
|
462
|
+
try {
|
|
463
|
+
const users = await client.users.list({ limit: MAX_LIMIT });
|
|
464
|
+
if (users.length === 0) {
|
|
465
|
+
hasMoreUsers = false;
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
for (const user of users) {
|
|
469
|
+
try {
|
|
470
|
+
await client.users.delete(user.id, { cascade: true });
|
|
471
|
+
deleted.users++;
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
// Continue on error
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (users.length < MAX_LIMIT) {
|
|
478
|
+
hasMoreUsers = false;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
hasMoreUsers = false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// 10. Clear governance policies
|
|
486
|
+
await clearTableDirect("governancePolicies", "governancePolicies");
|
|
487
|
+
// 11. Clear governance enforcement logs
|
|
488
|
+
await clearTableDirect("governanceEnforcement", "governanceEnforcement");
|
|
489
|
+
// 12. Clear graph sync queue
|
|
490
|
+
await clearTableDirect("graphSyncQueue", "graphSyncQueue");
|
|
491
|
+
// 13. Clear graph database if graph sync is enabled
|
|
492
|
+
// Check both explicit flag and auto-detection (same logic as Cortex.create())
|
|
493
|
+
const neo4jUri = process.env.NEO4J_URI;
|
|
494
|
+
const memgraphUri = process.env.MEMGRAPH_URI;
|
|
495
|
+
const graphSyncEnabled = process.env.CORTEX_GRAPH_SYNC === "true" ||
|
|
496
|
+
!!(neo4jUri || memgraphUri);
|
|
497
|
+
if (graphSyncEnabled) {
|
|
498
|
+
spinner.text = "Clearing graph database...";
|
|
499
|
+
let graphCleared = false;
|
|
500
|
+
try {
|
|
501
|
+
if (neo4jUri || memgraphUri) {
|
|
502
|
+
// Dynamically import neo4j-driver only when needed
|
|
503
|
+
const neo4j = await import("neo4j-driver");
|
|
504
|
+
// Determine which database to connect to
|
|
505
|
+
const uri = neo4jUri || memgraphUri;
|
|
506
|
+
const username = neo4jUri
|
|
507
|
+
? process.env.NEO4J_USERNAME || "neo4j"
|
|
508
|
+
: process.env.MEMGRAPH_USERNAME || "memgraph";
|
|
509
|
+
const password = neo4jUri
|
|
510
|
+
? process.env.NEO4J_PASSWORD || ""
|
|
511
|
+
: process.env.MEMGRAPH_PASSWORD || "";
|
|
512
|
+
// Connect to graph database
|
|
513
|
+
const driver = neo4j.default.driver(uri, neo4j.default.auth.basic(username, password));
|
|
514
|
+
// Verify connectivity
|
|
515
|
+
await driver.verifyConnectivity();
|
|
516
|
+
// Create session and clear all data
|
|
517
|
+
const session = driver.session();
|
|
518
|
+
try {
|
|
519
|
+
// DETACH DELETE removes nodes and all their relationships
|
|
520
|
+
// Works for both Neo4j and Memgraph
|
|
521
|
+
await session.run("MATCH (n) DETACH DELETE n");
|
|
522
|
+
graphCleared = true;
|
|
523
|
+
}
|
|
524
|
+
finally {
|
|
525
|
+
await session.close();
|
|
526
|
+
}
|
|
527
|
+
await driver.close();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
// Log warning but don't fail the entire operation
|
|
532
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
533
|
+
spinner.warn(pc.yellow(`Graph database clear failed: ${errorMsg}`));
|
|
534
|
+
}
|
|
535
|
+
if (graphCleared) {
|
|
536
|
+
// Only show success if we actually cleared
|
|
537
|
+
deleted.graphSyncQueue = -1; // Use as flag to indicate graph was cleared
|
|
146
538
|
}
|
|
147
539
|
}
|
|
148
540
|
spinner.stop();
|
|
149
|
-
printSuccess(
|
|
541
|
+
printSuccess(`Database "${targetName}" cleared`);
|
|
542
|
+
console.log();
|
|
150
543
|
printSection("Deletion Summary", {
|
|
151
|
-
|
|
152
|
-
|
|
544
|
+
Database: targetName,
|
|
545
|
+
URL: targetUrl,
|
|
153
546
|
});
|
|
547
|
+
console.log();
|
|
548
|
+
// Show counts with categories
|
|
549
|
+
const coreEntities = {
|
|
550
|
+
Agents: deleted.agents,
|
|
551
|
+
Users: deleted.users,
|
|
552
|
+
"Memory Spaces": deleted.memorySpaces,
|
|
553
|
+
};
|
|
554
|
+
const memoryData = {
|
|
555
|
+
Memories: deleted.memories,
|
|
556
|
+
Facts: deleted.facts,
|
|
557
|
+
Contexts: deleted.contexts,
|
|
558
|
+
};
|
|
559
|
+
const conversationData = {
|
|
560
|
+
Conversations: deleted.conversations,
|
|
561
|
+
Messages: deleted.messages,
|
|
562
|
+
};
|
|
563
|
+
const sharedStores = {
|
|
564
|
+
Immutable: deleted.immutable,
|
|
565
|
+
Mutable: deleted.mutable,
|
|
566
|
+
};
|
|
567
|
+
const systemTables = {
|
|
568
|
+
"Governance Policies": deleted.governancePolicies,
|
|
569
|
+
"Governance Logs": deleted.governanceEnforcement,
|
|
570
|
+
"Graph Sync Queue": deleted.graphSyncQueue >= 0 ? deleted.graphSyncQueue : 0,
|
|
571
|
+
};
|
|
572
|
+
printSection("Core Entities", coreEntities);
|
|
573
|
+
printSection("Memory Data", memoryData);
|
|
574
|
+
printSection("Conversations", conversationData);
|
|
575
|
+
printSection("Shared Stores", sharedStores);
|
|
576
|
+
printSection("System Tables", systemTables);
|
|
577
|
+
// Show graph database status if it was cleared
|
|
578
|
+
if (deleted.graphSyncQueue === -1) {
|
|
579
|
+
const dbType = process.env.NEO4J_URI ? "Neo4j" : "Memgraph";
|
|
580
|
+
console.log();
|
|
581
|
+
printSection("Graph Database", {
|
|
582
|
+
[dbType]: pc.green("Cleared ✓"),
|
|
583
|
+
});
|
|
584
|
+
}
|
|
154
585
|
});
|
|
155
586
|
}
|
|
156
587
|
catch (error) {
|
|
@@ -164,25 +595,31 @@ export function registerDbCommands(program, config) {
|
|
|
164
595
|
.option("-o, --output <file>", "Output file path", "cortex-backup.json")
|
|
165
596
|
.option("--include-all", "Include all data (may be large)", false)
|
|
166
597
|
.action(async (options) => {
|
|
167
|
-
|
|
168
|
-
const spinner = ora("Creating backup...").start();
|
|
598
|
+
let globalOpts = program.opts();
|
|
169
599
|
try {
|
|
170
600
|
validateFilePath(options.output);
|
|
601
|
+
// Select database
|
|
602
|
+
const selection = await selectDatabase(config, globalOpts, "backup");
|
|
603
|
+
if (!selection)
|
|
604
|
+
return;
|
|
605
|
+
globalOpts = selection.globalOpts;
|
|
606
|
+
const { targetName, targetUrl } = selection;
|
|
607
|
+
const spinner = ora(`Creating backup of ${targetName}...`).start();
|
|
171
608
|
await withClient(config, globalOpts, async (client) => {
|
|
172
609
|
const backup = {
|
|
173
610
|
version: "1.0",
|
|
174
611
|
timestamp: Date.now(),
|
|
175
|
-
deployment:
|
|
612
|
+
deployment: targetUrl,
|
|
176
613
|
data: {},
|
|
177
614
|
};
|
|
178
|
-
// Backup memory spaces
|
|
615
|
+
// Backup memory spaces (paginate if needed)
|
|
179
616
|
spinner.text = "Backing up memory spaces...";
|
|
180
617
|
backup.data.memorySpaces = await client.memorySpaces.list({
|
|
181
|
-
limit:
|
|
618
|
+
limit: MAX_LIMIT,
|
|
182
619
|
});
|
|
183
|
-
// Backup users
|
|
620
|
+
// Backup users (paginate if needed)
|
|
184
621
|
spinner.text = "Backing up users...";
|
|
185
|
-
backup.data.users = await client.users.list({ limit:
|
|
622
|
+
backup.data.users = await client.users.list({ limit: MAX_LIMIT });
|
|
186
623
|
if (options.includeAll) {
|
|
187
624
|
// Backup conversations
|
|
188
625
|
spinner.text = "Backing up conversations...";
|
|
@@ -191,7 +628,7 @@ export function registerDbCommands(program, config) {
|
|
|
191
628
|
for (const space of spaces) {
|
|
192
629
|
const convs = await client.conversations.list({
|
|
193
630
|
memorySpaceId: space.memorySpaceId,
|
|
194
|
-
limit:
|
|
631
|
+
limit: MAX_LIMIT,
|
|
195
632
|
});
|
|
196
633
|
backup.data.conversations.push(...convs);
|
|
197
634
|
}
|
|
@@ -201,7 +638,7 @@ export function registerDbCommands(program, config) {
|
|
|
201
638
|
for (const space of spaces) {
|
|
202
639
|
const memories = await client.memory.list({
|
|
203
640
|
memorySpaceId: space.memorySpaceId,
|
|
204
|
-
limit:
|
|
641
|
+
limit: MAX_LIMIT,
|
|
205
642
|
});
|
|
206
643
|
backup.data.memories.push(...memories);
|
|
207
644
|
}
|
|
@@ -211,7 +648,7 @@ export function registerDbCommands(program, config) {
|
|
|
211
648
|
for (const space of spaces) {
|
|
212
649
|
const facts = await client.facts.list({
|
|
213
650
|
memorySpaceId: space.memorySpaceId,
|
|
214
|
-
limit:
|
|
651
|
+
limit: MAX_LIMIT,
|
|
215
652
|
});
|
|
216
653
|
backup.data.facts.push(...facts);
|
|
217
654
|
}
|
|
@@ -241,7 +678,6 @@ export function registerDbCommands(program, config) {
|
|
|
241
678
|
});
|
|
242
679
|
}
|
|
243
680
|
catch (error) {
|
|
244
|
-
spinner.stop();
|
|
245
681
|
printError(error instanceof Error ? error.message : "Backup failed");
|
|
246
682
|
process.exit(1);
|
|
247
683
|
}
|
|
@@ -253,10 +689,10 @@ export function registerDbCommands(program, config) {
|
|
|
253
689
|
.option("--dry-run", "Preview what would be restored", false)
|
|
254
690
|
.option("-y, --yes", "Skip confirmation", false)
|
|
255
691
|
.action(async (options) => {
|
|
256
|
-
|
|
692
|
+
let globalOpts = program.opts();
|
|
257
693
|
try {
|
|
258
694
|
validateFilePath(options.input);
|
|
259
|
-
// Read backup file
|
|
695
|
+
// Read backup file first to show info before selecting target
|
|
260
696
|
const content = await readFile(options.input, "utf-8");
|
|
261
697
|
const backup = JSON.parse(content);
|
|
262
698
|
// Validate backup format
|
|
@@ -279,14 +715,20 @@ export function registerDbCommands(program, config) {
|
|
|
279
715
|
printWarning("DRY RUN - No data will be restored");
|
|
280
716
|
return;
|
|
281
717
|
}
|
|
718
|
+
// Select target database
|
|
719
|
+
const selection = await selectDatabase(config, globalOpts, "restore to");
|
|
720
|
+
if (!selection)
|
|
721
|
+
return;
|
|
722
|
+
globalOpts = selection.globalOpts;
|
|
723
|
+
const { targetName } = selection;
|
|
282
724
|
if (!options.yes) {
|
|
283
|
-
const confirmed = await requireConfirmation(
|
|
725
|
+
const confirmed = await requireConfirmation(`Restore this backup to ${targetName}? Existing data may be overwritten.`, config);
|
|
284
726
|
if (!confirmed) {
|
|
285
727
|
printWarning("Restore cancelled");
|
|
286
728
|
return;
|
|
287
729
|
}
|
|
288
730
|
}
|
|
289
|
-
const spinner = ora(
|
|
731
|
+
const spinner = ora(`Restoring backup to ${targetName}...`).start();
|
|
290
732
|
await withClient(config, globalOpts, async (client) => {
|
|
291
733
|
let restored = {
|
|
292
734
|
spaces: 0,
|
|
@@ -345,15 +787,22 @@ export function registerDbCommands(program, config) {
|
|
|
345
787
|
.description("Export all data to JSON")
|
|
346
788
|
.option("-o, --output <file>", "Output file path", "cortex-export.json")
|
|
347
789
|
.action(async (options) => {
|
|
348
|
-
|
|
349
|
-
const spinner = ora("Exporting data...").start();
|
|
790
|
+
let globalOpts = program.opts();
|
|
350
791
|
try {
|
|
351
792
|
validateFilePath(options.output);
|
|
793
|
+
// Select database
|
|
794
|
+
const selection = await selectDatabase(config, globalOpts, "export");
|
|
795
|
+
if (!selection)
|
|
796
|
+
return;
|
|
797
|
+
globalOpts = selection.globalOpts;
|
|
798
|
+
const { targetName, targetUrl } = selection;
|
|
799
|
+
const spinner = ora(`Exporting data from ${targetName}...`).start();
|
|
352
800
|
await withClient(config, globalOpts, async (client) => {
|
|
353
801
|
const exportData = {
|
|
354
802
|
exportedAt: Date.now(),
|
|
355
|
-
|
|
356
|
-
|
|
803
|
+
deployment: { name: targetName, url: targetUrl },
|
|
804
|
+
memorySpaces: await client.memorySpaces.list({ limit: MAX_LIMIT }),
|
|
805
|
+
users: await client.users.list({ limit: MAX_LIMIT }),
|
|
357
806
|
};
|
|
358
807
|
const content = JSON.stringify(exportData, null, 2);
|
|
359
808
|
await writeFile(options.output, content, "utf-8");
|
|
@@ -362,7 +811,6 @@ export function registerDbCommands(program, config) {
|
|
|
362
811
|
});
|
|
363
812
|
}
|
|
364
813
|
catch (error) {
|
|
365
|
-
spinner.stop();
|
|
366
814
|
printError(error instanceof Error ? error.message : "Export failed");
|
|
367
815
|
process.exit(1);
|
|
368
816
|
}
|