@cortexmemory/cli 0.1.0 → 0.22.0

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