@cortexmemory/cli 0.1.1 → 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.
- package/README.md +8 -0
- package/dist/commands/conversations.d.ts +1 -1
- package/dist/commands/conversations.d.ts.map +1 -1
- package/dist/commands/conversations.js +57 -27
- package/dist/commands/conversations.js.map +1 -1
- package/dist/commands/convex.d.ts +1 -1
- package/dist/commands/convex.d.ts.map +1 -1
- package/dist/commands/convex.js +237 -64
- package/dist/commands/convex.js.map +1 -1
- package/dist/commands/db.d.ts +1 -1
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +59 -109
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/dev.d.ts +8 -8
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +734 -513
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/facts.d.ts +1 -1
- package/dist/commands/facts.d.ts.map +1 -1
- package/dist/commands/facts.js +50 -21
- package/dist/commands/facts.js.map +1 -1
- package/dist/commands/init.d.ts +28 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +895 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/memory.d.ts +1 -1
- package/dist/commands/memory.d.ts.map +1 -1
- package/dist/commands/memory.js +64 -27
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/setup.d.ts +4 -5
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +428 -250
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/spaces.d.ts +1 -1
- package/dist/commands/spaces.d.ts.map +1 -1
- package/dist/commands/spaces.js +100 -43
- package/dist/commands/spaces.js.map +1 -1
- package/dist/commands/status.d.ts +17 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +314 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/users.d.ts +1 -1
- package/dist/commands/users.d.ts.map +1 -1
- package/dist/commands/users.js +80 -42
- package/dist/commands/users.js.map +1 -1
- package/dist/index.js +42 -14
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/__tests__/client.test.d.ts +5 -0
- package/dist/utils/__tests__/client.test.d.ts.map +1 -0
- package/dist/utils/__tests__/client.test.js +88 -0
- package/dist/utils/__tests__/client.test.js.map +1 -0
- package/dist/utils/__tests__/env-file.test.d.ts +7 -0
- package/dist/utils/__tests__/env-file.test.d.ts.map +1 -0
- package/dist/utils/__tests__/env-file.test.js +196 -0
- package/dist/utils/__tests__/env-file.test.js.map +1 -0
- package/dist/utils/__tests__/shell.test.d.ts +7 -0
- package/dist/utils/__tests__/shell.test.d.ts.map +1 -0
- package/dist/utils/__tests__/shell.test.js +89 -0
- package/dist/utils/__tests__/shell.test.js.map +1 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -39
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/deployment-selector.d.ts +50 -0
- package/dist/utils/deployment-selector.d.ts.map +1 -0
- package/dist/utils/deployment-selector.js +129 -0
- package/dist/utils/deployment-selector.js.map +1 -0
- package/dist/utils/init/convex-setup.d.ts +30 -0
- package/dist/utils/init/convex-setup.d.ts.map +1 -0
- package/dist/utils/init/convex-setup.js +225 -0
- package/dist/utils/init/convex-setup.js.map +1 -0
- package/dist/utils/init/env-generator.d.ts +32 -0
- package/dist/utils/init/env-generator.d.ts.map +1 -0
- package/dist/utils/init/env-generator.js +210 -0
- package/dist/utils/init/env-generator.js.map +1 -0
- package/dist/utils/init/file-operations.d.ts +22 -0
- package/dist/utils/init/file-operations.d.ts.map +1 -0
- package/dist/utils/init/file-operations.js +211 -0
- package/dist/utils/init/file-operations.js.map +1 -0
- package/dist/utils/init/graph-setup.d.ts +35 -0
- package/dist/utils/init/graph-setup.d.ts.map +1 -0
- package/dist/utils/init/graph-setup.js +413 -0
- package/dist/utils/init/graph-setup.js.map +1 -0
- package/dist/utils/init/index.d.ts +11 -0
- package/dist/utils/init/index.d.ts.map +1 -0
- package/dist/utils/init/index.js +11 -0
- package/dist/utils/init/index.js.map +1 -0
- package/dist/utils/init/types.d.ts +73 -0
- package/dist/utils/init/types.d.ts.map +1 -0
- package/dist/utils/init/types.js +5 -0
- package/dist/utils/init/types.js.map +1 -0
- package/dist/utils/shell.d.ts +60 -0
- package/dist/utils/shell.d.ts.map +1 -0
- package/dist/utils/shell.js +188 -0
- package/dist/utils/shell.js.map +1 -0
- package/package.json +25 -20
- package/templates/basic/README.md +105 -0
- package/templates/basic/dev-runner.mjs +215 -0
- package/templates/basic/package-lock.json +1263 -0
- package/templates/basic/package.json +22 -0
- package/templates/basic/src/index.ts +85 -0
- package/templates/basic/tsconfig.json +17 -0
package/dist/commands/dev.js
CHANGED
|
@@ -1,558 +1,779 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Interactive Dev Mode Command
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
4
|
+
* Expo-style interactive terminal with:
|
|
5
|
+
* - Multi-deployment support (all enabled deployments)
|
|
6
|
+
* - Live status dashboard
|
|
7
|
+
* - Aggregated streaming logs from all instances
|
|
8
|
+
* - Keyboard shortcuts for common actions
|
|
9
9
|
*/
|
|
10
|
-
import
|
|
11
|
-
import { withClient } from "../utils/client.js";
|
|
12
|
-
import { printSuccess, printError, printWarning, printSection, formatTimestamp, } from "../utils/formatting.js";
|
|
13
|
-
import { validateMemorySpaceId, validateMemoryId, validateSearchQuery, requireConfirmation, } from "../utils/validation.js";
|
|
10
|
+
import { spawn } from "child_process";
|
|
14
11
|
import pc from "picocolors";
|
|
12
|
+
import fs from "fs-extra";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { loadConfig } from "../utils/config.js";
|
|
15
|
+
import { commandExists, execCommand } from "../utils/shell.js";
|
|
16
|
+
import { runInitWizard } from "./init.js";
|
|
15
17
|
/**
|
|
16
|
-
*
|
|
18
|
+
* Register dev commands
|
|
17
19
|
*/
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
+
export function registerDevCommands(program, _config) {
|
|
21
|
+
program
|
|
22
|
+
.command("dev")
|
|
23
|
+
.description("Start interactive development mode (Expo-style)")
|
|
24
|
+
.option("-d, --deployment <name>", "Run specific deployment only")
|
|
25
|
+
.option("-l, --local", "Force local Convex instance for all deployments", false)
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
const config = await loadConfig();
|
|
28
|
+
await runInteractiveDevMode(config, options);
|
|
29
|
+
});
|
|
20
30
|
}
|
|
21
31
|
/**
|
|
22
|
-
*
|
|
32
|
+
* Collect deployments to run (same logic as start command)
|
|
23
33
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
34
|
+
async function getDeploymentsToRun(config, options) {
|
|
35
|
+
const deployments = [];
|
|
36
|
+
if (options.deployment) {
|
|
37
|
+
// Single deployment mode
|
|
38
|
+
const deployment = config.deployments[options.deployment];
|
|
39
|
+
if (!deployment) {
|
|
40
|
+
console.error(pc.red(`\n Deployment "${options.deployment}" not found`));
|
|
41
|
+
const names = Object.keys(config.deployments);
|
|
42
|
+
if (names.length > 0) {
|
|
43
|
+
console.log(pc.dim(` Available: ${names.join(", ")}`));
|
|
44
|
+
}
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const projectPath = deployment.projectPath || process.cwd();
|
|
48
|
+
if (deployment.projectPath && !fs.existsSync(projectPath)) {
|
|
49
|
+
console.error(pc.red(`\n Project path not found: ${projectPath}`));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
// Check for graph config
|
|
53
|
+
let graphType = null;
|
|
54
|
+
const dockerComposePath = path.join(projectPath, "docker-compose.graph.yml");
|
|
55
|
+
if (fs.existsSync(dockerComposePath)) {
|
|
56
|
+
const content = await fs.readFile(dockerComposePath, "utf-8");
|
|
57
|
+
graphType = content.includes("memgraph") ? "memgraph" : "neo4j";
|
|
58
|
+
}
|
|
59
|
+
deployments.push({
|
|
60
|
+
name: options.deployment,
|
|
61
|
+
url: deployment.url,
|
|
62
|
+
key: deployment.key,
|
|
63
|
+
projectPath,
|
|
64
|
+
isLocal: options.local || false,
|
|
65
|
+
process: null,
|
|
66
|
+
convexRunning: false,
|
|
67
|
+
graphRunning: false,
|
|
68
|
+
graphType,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// All enabled deployments
|
|
73
|
+
for (const [name, deployment] of Object.entries(config.deployments)) {
|
|
74
|
+
const isDefault = name === config.default;
|
|
75
|
+
const isEnabled = deployment.enabled === true || (deployment.enabled === undefined && isDefault);
|
|
76
|
+
if (!isEnabled)
|
|
77
|
+
continue;
|
|
78
|
+
if (!deployment.projectPath) {
|
|
79
|
+
console.log(pc.yellow(` Skipping "${name}" - no projectPath configured`));
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (!fs.existsSync(deployment.projectPath)) {
|
|
83
|
+
console.log(pc.yellow(` Skipping "${name}" - projectPath not found`));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// Check for graph config
|
|
87
|
+
let graphType = null;
|
|
88
|
+
const dockerComposePath = path.join(deployment.projectPath, "docker-compose.graph.yml");
|
|
89
|
+
if (fs.existsSync(dockerComposePath)) {
|
|
90
|
+
const content = await fs.readFile(dockerComposePath, "utf-8");
|
|
91
|
+
graphType = content.includes("memgraph") ? "memgraph" : "neo4j";
|
|
92
|
+
}
|
|
93
|
+
deployments.push({
|
|
94
|
+
name,
|
|
95
|
+
url: deployment.url,
|
|
96
|
+
key: deployment.key,
|
|
97
|
+
projectPath: deployment.projectPath,
|
|
98
|
+
isLocal: options.local || false,
|
|
99
|
+
process: null,
|
|
100
|
+
convexRunning: false,
|
|
101
|
+
graphRunning: false,
|
|
102
|
+
graphType,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return deployments;
|
|
107
|
+
}
|
|
90
108
|
/**
|
|
91
|
-
*
|
|
109
|
+
* Show prompt when no deployments configured
|
|
92
110
|
*/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const prefix = options.prefix;
|
|
115
|
-
if (!options.yes) {
|
|
116
|
-
console.log();
|
|
117
|
-
console.log(pc.bold("This will create:"));
|
|
118
|
-
console.log(` • ${numUsers} test users`);
|
|
119
|
-
console.log(` • ${numSpaces} memory spaces`);
|
|
120
|
-
console.log(` • ${numMemories * numSpaces} memories`);
|
|
121
|
-
console.log(` • ${numConversations * numSpaces} conversations`);
|
|
122
|
-
console.log();
|
|
123
|
-
const confirmed = await requireConfirmation("Proceed with seeding test data?", config);
|
|
124
|
-
if (!confirmed) {
|
|
125
|
-
printWarning("Seeding cancelled");
|
|
126
|
-
return;
|
|
111
|
+
async function showNotConfiguredPrompt() {
|
|
112
|
+
console.clear();
|
|
113
|
+
console.log(pc.bold(pc.yellow("\n ⚠ No Deployments Configured\n")));
|
|
114
|
+
console.log(pc.dim(" No enabled deployments found with valid project paths.\n"));
|
|
115
|
+
console.log(pc.dim(" This could mean:"));
|
|
116
|
+
console.log(pc.dim(" • No deployments are enabled"));
|
|
117
|
+
console.log(pc.dim(" • Deployments don't have projectPath set"));
|
|
118
|
+
console.log(pc.dim(" • Project paths don't exist\n"));
|
|
119
|
+
console.log(` Press ${pc.bold(pc.green("i"))} to run ${pc.cyan("cortex init")} and set up a project`);
|
|
120
|
+
console.log(` Press ${pc.bold(pc.red("q"))} to quit\n`);
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
if (process.stdin.isTTY) {
|
|
123
|
+
process.stdin.setRawMode(true);
|
|
124
|
+
process.stdin.resume();
|
|
125
|
+
process.stdin.setEncoding("utf8");
|
|
126
|
+
}
|
|
127
|
+
const handler = async (key) => {
|
|
128
|
+
if (key === "i" || key === "I") {
|
|
129
|
+
process.stdin.removeListener("data", handler);
|
|
130
|
+
if (process.stdin.isTTY) {
|
|
131
|
+
process.stdin.setRawMode(false);
|
|
127
132
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
users: 0,
|
|
133
|
-
spaces: 0,
|
|
134
|
-
memories: 0,
|
|
135
|
-
conversations: 0,
|
|
136
|
-
facts: 0,
|
|
137
|
-
};
|
|
138
|
-
// Create test users
|
|
139
|
-
spinner.text = "Creating test users...";
|
|
140
|
-
for (let i = 0; i < numUsers; i++) {
|
|
141
|
-
const userId = `${prefix}-user-${i + 1}`;
|
|
142
|
-
try {
|
|
143
|
-
await client.users.update(userId, {
|
|
144
|
-
displayName: `Test User ${i + 1}`,
|
|
145
|
-
email: `user${i + 1}@test.example`,
|
|
146
|
-
createdBy: "cortex-cli-seed",
|
|
147
|
-
});
|
|
148
|
-
created.users++;
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
// Skip if exists
|
|
152
|
-
}
|
|
133
|
+
console.clear();
|
|
134
|
+
try {
|
|
135
|
+
await runInitWizard(process.cwd(), { start: false });
|
|
136
|
+
resolve(true);
|
|
153
137
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
for (let i = 0; i < numSpaces; i++) {
|
|
158
|
-
const spaceId = `${prefix}-space-${i + 1}`;
|
|
159
|
-
try {
|
|
160
|
-
await client.memorySpaces.register({
|
|
161
|
-
memorySpaceId: spaceId,
|
|
162
|
-
name: `Test Space ${i + 1}`,
|
|
163
|
-
type: "project",
|
|
164
|
-
metadata: { createdBy: "cortex-cli-seed" },
|
|
165
|
-
});
|
|
166
|
-
spaceIds.push(spaceId);
|
|
167
|
-
created.spaces++;
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (error instanceof Error && error.message === "Setup cancelled") {
|
|
140
|
+
console.log(pc.yellow("\n Setup cancelled.\n"));
|
|
168
141
|
}
|
|
169
|
-
|
|
170
|
-
|
|
142
|
+
else {
|
|
143
|
+
console.error(pc.red("\n Setup failed:"), error);
|
|
171
144
|
}
|
|
145
|
+
resolve(false);
|
|
172
146
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const conversationId = generateId(`${prefix}-conv`);
|
|
179
|
-
const userId = `${prefix}-user-${(i % numUsers) + 1}`;
|
|
180
|
-
try {
|
|
181
|
-
await client.memory.remember({
|
|
182
|
-
memorySpaceId: spaceId,
|
|
183
|
-
conversationId,
|
|
184
|
-
userMessage: `Test message ${i + 1} from user`,
|
|
185
|
-
agentResponse: `Test response ${i + 1} from agent`,
|
|
186
|
-
userId,
|
|
187
|
-
userName: `Test User ${(i % numUsers) + 1}`,
|
|
188
|
-
});
|
|
189
|
-
created.conversations++;
|
|
190
|
-
created.memories += 2; // User + agent message
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
// Skip on error
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
// Create additional standalone memories
|
|
197
|
-
for (let i = 0; i < numMemories; i++) {
|
|
198
|
-
try {
|
|
199
|
-
await client.vector.store(spaceId, {
|
|
200
|
-
content: `Test memory content ${i + 1} for space ${spaceId}`,
|
|
201
|
-
contentType: "raw",
|
|
202
|
-
source: {
|
|
203
|
-
type: "system",
|
|
204
|
-
timestamp: Date.now(),
|
|
205
|
-
},
|
|
206
|
-
metadata: {
|
|
207
|
-
importance: Math.floor(Math.random() * 100),
|
|
208
|
-
tags: ["test", "seed", `batch-${i + 1}`],
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
created.memories++;
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
// Skip on error
|
|
215
|
-
}
|
|
216
|
-
}
|
|
147
|
+
}
|
|
148
|
+
else if (key === "q" || key === "Q" || key === "\u0003") {
|
|
149
|
+
process.stdin.removeListener("data", handler);
|
|
150
|
+
if (process.stdin.isTTY) {
|
|
151
|
+
process.stdin.setRawMode(false);
|
|
217
152
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
153
|
+
console.log(pc.dim("\n Goodbye!\n"));
|
|
154
|
+
resolve(false);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
process.stdin.on("data", handler);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Run the interactive dev mode
|
|
162
|
+
*/
|
|
163
|
+
async function runInteractiveDevMode(config, options) {
|
|
164
|
+
// Get deployments to run
|
|
165
|
+
let deploymentsList = await getDeploymentsToRun(config, options);
|
|
166
|
+
if (deploymentsList.length === 0) {
|
|
167
|
+
const shouldContinue = await showNotConfiguredPrompt();
|
|
168
|
+
if (!shouldContinue) {
|
|
169
|
+
process.exit(0);
|
|
227
170
|
}
|
|
228
|
-
|
|
229
|
-
|
|
171
|
+
// Reload config and try again
|
|
172
|
+
const newConfig = await loadConfig();
|
|
173
|
+
deploymentsList = await getDeploymentsToRun(newConfig, options);
|
|
174
|
+
if (deploymentsList.length === 0) {
|
|
175
|
+
console.log(pc.red("\n Still no deployments configured. Please run 'cortex init' manually.\n"));
|
|
230
176
|
process.exit(1);
|
|
231
177
|
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
178
|
+
}
|
|
179
|
+
const state = {
|
|
180
|
+
deployments: new Map(deploymentsList.map(d => [d.name, d])),
|
|
181
|
+
logs: [],
|
|
182
|
+
maxLogs: 100,
|
|
183
|
+
lastStatus: new Date(),
|
|
184
|
+
};
|
|
185
|
+
// Setup stdin for raw mode
|
|
186
|
+
if (process.stdin.isTTY) {
|
|
187
|
+
process.stdin.setRawMode(true);
|
|
188
|
+
process.stdin.resume();
|
|
189
|
+
process.stdin.setEncoding("utf8");
|
|
190
|
+
}
|
|
191
|
+
// Clear screen and show initial status
|
|
192
|
+
console.clear();
|
|
193
|
+
await refreshStatus(state);
|
|
194
|
+
showHelp();
|
|
195
|
+
console.log(pc.dim(" Logs streaming below. Press 'c' to clear, '?' for help.\n"));
|
|
196
|
+
// Start all services
|
|
197
|
+
await startAllServices(state);
|
|
198
|
+
// Track shutdown state
|
|
199
|
+
let shuttingDown = false;
|
|
200
|
+
let shutdownInProgress = false;
|
|
201
|
+
// Handle keyboard input
|
|
202
|
+
process.stdin.on("data", async (key) => {
|
|
203
|
+
switch (key) {
|
|
204
|
+
case "c":
|
|
205
|
+
// Clear screen
|
|
206
|
+
state.logs = [];
|
|
207
|
+
console.clear();
|
|
208
|
+
await refreshStatus(state);
|
|
209
|
+
showHelp();
|
|
210
|
+
console.log(pc.dim(" Logs cleared.\n"));
|
|
211
|
+
break;
|
|
212
|
+
case "r":
|
|
213
|
+
// Restart all services
|
|
214
|
+
console.log(pc.yellow("\n Restarting all services...\n"));
|
|
215
|
+
await stopAllServices(state, false);
|
|
216
|
+
state.logs = [];
|
|
217
|
+
console.clear();
|
|
218
|
+
await refreshStatus(state);
|
|
219
|
+
showHelp();
|
|
220
|
+
console.log(pc.dim(" Restarting...\n"));
|
|
221
|
+
await startAllServices(state);
|
|
222
|
+
break;
|
|
223
|
+
case "g":
|
|
224
|
+
// Toggle graph - show menu if multiple deployments
|
|
225
|
+
await handleGraphToggle(state);
|
|
226
|
+
break;
|
|
227
|
+
case "s":
|
|
228
|
+
// Show status
|
|
229
|
+
console.clear();
|
|
230
|
+
await refreshStatus(state);
|
|
231
|
+
showHelp();
|
|
232
|
+
console.log(pc.dim(" Logs streaming below...\n"));
|
|
233
|
+
// Re-print recent logs
|
|
234
|
+
for (const log of state.logs.slice(-20)) {
|
|
235
|
+
console.log(` ${log}`);
|
|
248
236
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Find and delete test spaces
|
|
257
|
-
spinner.text = "Finding test memory spaces...";
|
|
258
|
-
const spaces = await client.memorySpaces.list({ limit: 1000 });
|
|
259
|
-
const testSpaces = spaces.filter((s) => s.memorySpaceId.startsWith(prefix));
|
|
260
|
-
for (const space of testSpaces) {
|
|
261
|
-
try {
|
|
262
|
-
await client.memorySpaces.delete(space.memorySpaceId, {
|
|
263
|
-
cascade: true,
|
|
264
|
-
});
|
|
265
|
-
deleted.spaces++;
|
|
266
|
-
}
|
|
267
|
-
catch {
|
|
268
|
-
// Continue on error
|
|
269
|
-
}
|
|
237
|
+
break;
|
|
238
|
+
case "q":
|
|
239
|
+
if (!shutdownInProgress) {
|
|
240
|
+
shuttingDown = true;
|
|
241
|
+
shutdownInProgress = true;
|
|
242
|
+
console.log(pc.yellow("\n\n Shutting down...\n"));
|
|
243
|
+
await performShutdown(state, false);
|
|
270
244
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
for (const user of testUsers) {
|
|
276
|
-
try {
|
|
277
|
-
await client.users.delete(user.id, { cascade: true });
|
|
278
|
-
deleted.users++;
|
|
279
|
-
}
|
|
280
|
-
catch {
|
|
281
|
-
// Continue on error
|
|
282
|
-
}
|
|
245
|
+
break;
|
|
246
|
+
case "\u0003": // Ctrl+C
|
|
247
|
+
if (shutdownInProgress) {
|
|
248
|
+
break;
|
|
283
249
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
250
|
+
if (shuttingDown) {
|
|
251
|
+
shutdownInProgress = true;
|
|
252
|
+
console.log(pc.red("\n\n Force killing...\n"));
|
|
253
|
+
await performShutdown(state, true);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
shuttingDown = true;
|
|
257
|
+
shutdownInProgress = true;
|
|
258
|
+
console.log(pc.yellow("\n\n Shutting down... (Ctrl+C again to force)\n"));
|
|
259
|
+
await performShutdown(state, false);
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
case "?":
|
|
263
|
+
case "h":
|
|
264
|
+
console.clear();
|
|
265
|
+
showDetailedHelp();
|
|
266
|
+
console.log(pc.dim("\nPress any key to return to logs"));
|
|
267
|
+
break;
|
|
268
|
+
case "k":
|
|
269
|
+
// Kill menu
|
|
270
|
+
console.clear();
|
|
271
|
+
await showKillMenu(state);
|
|
272
|
+
break;
|
|
291
273
|
}
|
|
292
|
-
|
|
293
|
-
|
|
274
|
+
});
|
|
275
|
+
// Handle SIGINT
|
|
276
|
+
process.on("SIGINT", async () => {
|
|
277
|
+
if (shutdownInProgress) {
|
|
278
|
+
console.log(pc.red("\n\n Force killing...\n"));
|
|
294
279
|
process.exit(1);
|
|
295
280
|
}
|
|
281
|
+
else if (!shuttingDown) {
|
|
282
|
+
shuttingDown = true;
|
|
283
|
+
shutdownInProgress = true;
|
|
284
|
+
console.log(pc.yellow("\n\n Shutting down... (Ctrl+C again to force)\n"));
|
|
285
|
+
await performShutdown(state, false);
|
|
286
|
+
}
|
|
296
287
|
});
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
if (
|
|
310
|
-
|
|
311
|
-
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Start all services for all deployments
|
|
291
|
+
*/
|
|
292
|
+
async function startAllServices(state) {
|
|
293
|
+
const hasConvex = await commandExists("convex");
|
|
294
|
+
for (const [name, dep] of state.deployments) {
|
|
295
|
+
// Start graph if configured
|
|
296
|
+
if (dep.graphType) {
|
|
297
|
+
addLog(state, name, `Starting ${dep.graphType}...`);
|
|
298
|
+
const started = await toggleGraph(dep.projectPath, dep.graphType, true);
|
|
299
|
+
dep.graphRunning = started;
|
|
300
|
+
if (started) {
|
|
301
|
+
addLog(state, name, pc.green(`${dep.graphType} started`));
|
|
312
302
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
303
|
+
else {
|
|
304
|
+
addLog(state, name, pc.yellow(`${dep.graphType} failed to start`));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Deploy to production first for cloud deployments with key
|
|
308
|
+
if (dep.key && !dep.isLocal) {
|
|
309
|
+
addLog(state, name, "Deploying functions to production...");
|
|
310
|
+
try {
|
|
311
|
+
const deployCmd = hasConvex ? "convex" : "npx";
|
|
312
|
+
const deployArgs = hasConvex
|
|
313
|
+
? ["deploy", "--cmd", "echo deployed"]
|
|
314
|
+
: ["convex", "deploy", "--cmd", "echo deployed"];
|
|
315
|
+
const env = { ...process.env };
|
|
316
|
+
env.CONVEX_URL = dep.url;
|
|
317
|
+
env.CONVEX_DEPLOY_KEY = dep.key;
|
|
318
|
+
await new Promise((resolve, reject) => {
|
|
319
|
+
const child = spawn(deployCmd, deployArgs, {
|
|
320
|
+
cwd: dep.projectPath,
|
|
321
|
+
stdio: "pipe",
|
|
322
|
+
env,
|
|
324
323
|
});
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
spinner.text = "Creating demo user...";
|
|
331
|
-
const userId = options.user;
|
|
332
|
-
try {
|
|
333
|
-
await client.users.update(userId, {
|
|
334
|
-
displayName: "Demo User",
|
|
335
|
-
template: templateName,
|
|
324
|
+
child.on("close", (code) => {
|
|
325
|
+
if (code === 0)
|
|
326
|
+
resolve();
|
|
327
|
+
else
|
|
328
|
+
reject(new Error(`Deploy failed with code ${code}`));
|
|
336
329
|
});
|
|
337
|
-
|
|
338
|
-
catch {
|
|
339
|
-
// User may already exist
|
|
340
|
-
}
|
|
341
|
-
// Create conversations
|
|
342
|
-
spinner.text = "Creating conversations...";
|
|
343
|
-
let conversationCount = 0;
|
|
344
|
-
for (const conv of template.conversations) {
|
|
345
|
-
const conversationId = generateId("demo-conv");
|
|
346
|
-
try {
|
|
347
|
-
await client.memory.remember({
|
|
348
|
-
memorySpaceId: spaceId,
|
|
349
|
-
conversationId,
|
|
350
|
-
userMessage: conv.user,
|
|
351
|
-
agentResponse: conv.agent,
|
|
352
|
-
userId,
|
|
353
|
-
userName: "Demo User",
|
|
354
|
-
});
|
|
355
|
-
conversationCount++;
|
|
356
|
-
}
|
|
357
|
-
catch {
|
|
358
|
-
// Skip on error
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
// Create facts
|
|
362
|
-
spinner.text = "Creating facts...";
|
|
363
|
-
let factCount = 0;
|
|
364
|
-
for (const factData of template.facts) {
|
|
365
|
-
try {
|
|
366
|
-
await client.facts.store({
|
|
367
|
-
memorySpaceId: spaceId,
|
|
368
|
-
fact: factData.fact,
|
|
369
|
-
factType: factData.type,
|
|
370
|
-
confidence: 80,
|
|
371
|
-
sourceType: "system",
|
|
372
|
-
tags: ["demo", templateName],
|
|
373
|
-
});
|
|
374
|
-
factCount++;
|
|
375
|
-
}
|
|
376
|
-
catch {
|
|
377
|
-
// Skip on error
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
spinner.stop();
|
|
381
|
-
printSuccess(`Generated ${template.name} demo data`);
|
|
382
|
-
printSection("Created", {
|
|
383
|
-
"Memory Space": spaceId,
|
|
384
|
-
User: userId,
|
|
385
|
-
Conversations: conversationCount,
|
|
386
|
-
Facts: factCount,
|
|
330
|
+
child.on("error", reject);
|
|
387
331
|
});
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
332
|
+
addLog(state, name, pc.green("Functions deployed"));
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
addLog(state, name, pc.yellow("Deploy failed, continuing..."));
|
|
336
|
+
}
|
|
393
337
|
}
|
|
338
|
+
// Start Convex dev
|
|
339
|
+
addLog(state, name, "Starting Convex...");
|
|
340
|
+
await startConvexProcess(state, name, dep, hasConvex);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Start Convex dev process for a deployment
|
|
345
|
+
*/
|
|
346
|
+
async function startConvexProcess(state, name, dep, hasConvex) {
|
|
347
|
+
const command = hasConvex ? "convex" : "npx";
|
|
348
|
+
const args = hasConvex ? ["dev"] : ["convex", "dev"];
|
|
349
|
+
if (dep.isLocal)
|
|
350
|
+
args.push("--local");
|
|
351
|
+
const env = { ...process.env };
|
|
352
|
+
env.CONVEX_URL = dep.url;
|
|
353
|
+
if (dep.key)
|
|
354
|
+
env.CONVEX_DEPLOY_KEY = dep.key;
|
|
355
|
+
const child = spawn(command, args, {
|
|
356
|
+
cwd: dep.projectPath,
|
|
357
|
+
env,
|
|
358
|
+
detached: true,
|
|
394
359
|
});
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
.requiredOption("-s, --space <id>", "Memory space ID")
|
|
402
|
-
.option("-l, --limit <number>", "Number of results", "5")
|
|
403
|
-
.option("--verbose", "Show detailed results", false)
|
|
404
|
-
.action(async (query, options) => {
|
|
405
|
-
const globalOpts = program.opts();
|
|
406
|
-
try {
|
|
407
|
-
validateSearchQuery(query);
|
|
408
|
-
validateMemorySpaceId(options.space);
|
|
409
|
-
const spinner = ora("Performing vector search...").start();
|
|
410
|
-
const startTime = Date.now();
|
|
411
|
-
await withClient(config, globalOpts, async (client) => {
|
|
412
|
-
const results = await client.memory.search(options.space, query, {
|
|
413
|
-
limit: parseInt(options.limit, 10),
|
|
414
|
-
});
|
|
415
|
-
const duration = Date.now() - startTime;
|
|
416
|
-
spinner.stop();
|
|
417
|
-
console.log();
|
|
418
|
-
printSection("Search Debug", {
|
|
419
|
-
Query: query,
|
|
420
|
-
Space: options.space,
|
|
421
|
-
Results: results.length,
|
|
422
|
-
Duration: `${duration}ms`,
|
|
423
|
-
});
|
|
424
|
-
if (results.length > 0) {
|
|
425
|
-
console.log("\n Results:");
|
|
426
|
-
for (let i = 0; i < results.length; i++) {
|
|
427
|
-
const result = results[i];
|
|
428
|
-
// Type guard for EnrichedMemory vs MemoryEntry
|
|
429
|
-
const memory = result && typeof result === "object" && "memory" in result
|
|
430
|
-
? result.memory
|
|
431
|
-
: result;
|
|
432
|
-
console.log(`\n ${pc.cyan(`[${i + 1}]`)} ${memory.memoryId}`);
|
|
433
|
-
console.log(` ${pc.dim("Content:")} ${memory.content.substring(0, 100)}...`);
|
|
434
|
-
console.log(` ${pc.dim("Type:")} ${memory.contentType}`);
|
|
435
|
-
console.log(` ${pc.dim("Importance:")} ${memory.importance}`);
|
|
436
|
-
if (options.verbose) {
|
|
437
|
-
console.log(` ${pc.dim("Created:")} ${formatTimestamp(memory.createdAt)}`);
|
|
438
|
-
console.log(` ${pc.dim("Tags:")} ${memory.tags.join(", ") || "-"}`);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
else {
|
|
443
|
-
printWarning("No results found");
|
|
444
|
-
}
|
|
445
|
-
});
|
|
360
|
+
dep.process = child;
|
|
361
|
+
// Handle stdout
|
|
362
|
+
child.stdout?.on("data", (data) => {
|
|
363
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
364
|
+
for (const line of lines) {
|
|
365
|
+
addLog(state, name, line);
|
|
446
366
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
367
|
+
});
|
|
368
|
+
// Handle stderr (Convex outputs here)
|
|
369
|
+
child.stderr?.on("data", (data) => {
|
|
370
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
371
|
+
for (const line of lines) {
|
|
372
|
+
addLog(state, name, line);
|
|
373
|
+
// Detect ready state
|
|
374
|
+
if (line.includes("Convex functions ready") && !dep.convexRunning) {
|
|
375
|
+
dep.convexRunning = true;
|
|
376
|
+
printStatusUpdate(state);
|
|
377
|
+
}
|
|
450
378
|
}
|
|
451
379
|
});
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
.
|
|
455
|
-
.
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const globalOpts = program.opts();
|
|
459
|
-
try {
|
|
460
|
-
validateMemoryId(memoryId);
|
|
461
|
-
validateMemorySpaceId(options.space);
|
|
462
|
-
const spinner = ora("Loading memory...").start();
|
|
463
|
-
await withClient(config, globalOpts, async (client) => {
|
|
464
|
-
const result = await client.memory.get(options.space, memoryId, {
|
|
465
|
-
includeConversation: true,
|
|
466
|
-
});
|
|
467
|
-
spinner.stop();
|
|
468
|
-
if (!result) {
|
|
469
|
-
printError("Memory not found");
|
|
470
|
-
process.exit(1);
|
|
471
|
-
}
|
|
472
|
-
const memory = "memory" in result ? result.memory : result;
|
|
473
|
-
console.log();
|
|
474
|
-
console.log(pc.bold(pc.cyan("Memory Inspection")));
|
|
475
|
-
console.log(pc.dim("─".repeat(50)));
|
|
476
|
-
console.log();
|
|
477
|
-
// Basic info
|
|
478
|
-
console.log(pc.bold("Basic Info:"));
|
|
479
|
-
console.log(` ID: ${memory.memoryId}`);
|
|
480
|
-
console.log(` Space: ${memory.memorySpaceId}`);
|
|
481
|
-
console.log(` Version: ${memory.version}`);
|
|
482
|
-
console.log();
|
|
483
|
-
// Content
|
|
484
|
-
console.log(pc.bold("Content:"));
|
|
485
|
-
console.log(` Type: ${memory.contentType}`);
|
|
486
|
-
console.log(` Length: ${memory.content.length} characters`);
|
|
487
|
-
console.log(` Content:\n ${memory.content}`);
|
|
488
|
-
console.log();
|
|
489
|
-
// Source
|
|
490
|
-
console.log(pc.bold("Source:"));
|
|
491
|
-
console.log(` Type: ${memory.sourceType}`);
|
|
492
|
-
console.log(` User: ${memory.userId ?? "-"}`);
|
|
493
|
-
console.log(` Timestamp: ${formatTimestamp(memory.sourceTimestamp)}`);
|
|
494
|
-
console.log();
|
|
495
|
-
// Metadata
|
|
496
|
-
console.log(pc.bold("Metadata:"));
|
|
497
|
-
console.log(` Importance: ${memory.importance}`);
|
|
498
|
-
console.log(` Tags: ${memory.tags.join(", ") || "-"}`);
|
|
499
|
-
console.log(` Access Count: ${memory.accessCount}`);
|
|
500
|
-
console.log();
|
|
501
|
-
// Embedding
|
|
502
|
-
console.log(pc.bold("Embedding:"));
|
|
503
|
-
if (memory.embedding) {
|
|
504
|
-
console.log(` Dimensions: ${memory.embedding.length}`);
|
|
505
|
-
console.log(` Sample: [${memory.embedding
|
|
506
|
-
.slice(0, 5)
|
|
507
|
-
.map((n) => n.toFixed(4))
|
|
508
|
-
.join(", ")}, ...]`);
|
|
509
|
-
}
|
|
510
|
-
else {
|
|
511
|
-
console.log(" No embedding stored");
|
|
512
|
-
}
|
|
513
|
-
console.log();
|
|
514
|
-
// Timestamps
|
|
515
|
-
console.log(pc.bold("Timestamps:"));
|
|
516
|
-
console.log(` Created: ${formatTimestamp(memory.createdAt)}`);
|
|
517
|
-
console.log(` Updated: ${formatTimestamp(memory.updatedAt)}`);
|
|
518
|
-
if (memory.lastAccessed) {
|
|
519
|
-
console.log(` Last Accessed: ${formatTimestamp(memory.lastAccessed)}`);
|
|
520
|
-
}
|
|
521
|
-
// Version history
|
|
522
|
-
if (memory.previousVersions && memory.previousVersions.length > 0) {
|
|
523
|
-
console.log();
|
|
524
|
-
console.log(pc.bold(`Version History: ${memory.previousVersions.length} previous versions`));
|
|
525
|
-
}
|
|
526
|
-
});
|
|
380
|
+
child.on("exit", (code) => {
|
|
381
|
+
const wasRunning = dep.convexRunning;
|
|
382
|
+
dep.convexRunning = false;
|
|
383
|
+
dep.process = null;
|
|
384
|
+
if (code !== 0 && code !== null) {
|
|
385
|
+
addLog(state, name, pc.red(`Convex exited with code ${code}`));
|
|
527
386
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
process.exit(1);
|
|
387
|
+
if (wasRunning) {
|
|
388
|
+
printStatusUpdate(state);
|
|
531
389
|
}
|
|
532
390
|
});
|
|
533
|
-
//
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
391
|
+
// Wait a moment for startup
|
|
392
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Stop all services
|
|
396
|
+
*/
|
|
397
|
+
async function stopAllServices(state, force) {
|
|
398
|
+
const signal = force ? "SIGKILL" : "SIGTERM";
|
|
399
|
+
for (const dep of state.deployments.values()) {
|
|
400
|
+
// Stop Convex
|
|
401
|
+
if (dep.process && dep.process.pid) {
|
|
402
|
+
try {
|
|
403
|
+
process.kill(-dep.process.pid, signal);
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// Already dead
|
|
407
|
+
}
|
|
408
|
+
dep.process = null;
|
|
409
|
+
dep.convexRunning = false;
|
|
410
|
+
}
|
|
411
|
+
// Stop graph
|
|
412
|
+
if (dep.graphRunning && dep.graphType) {
|
|
413
|
+
await toggleGraph(dep.projectPath, dep.graphType, false);
|
|
414
|
+
dep.graphRunning = false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (!force) {
|
|
418
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Perform full shutdown
|
|
423
|
+
*/
|
|
424
|
+
async function performShutdown(state, force) {
|
|
425
|
+
console.log(pc.dim(" Stopping all services..."));
|
|
426
|
+
await stopAllServices(state, force);
|
|
427
|
+
console.log(pc.green(" ✓ All services stopped"));
|
|
428
|
+
if (process.stdin.isTTY) {
|
|
429
|
+
process.stdin.setRawMode(false);
|
|
430
|
+
}
|
|
431
|
+
process.exit(0);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Handle graph toggle (with menu for multiple deployments)
|
|
435
|
+
*/
|
|
436
|
+
async function handleGraphToggle(state) {
|
|
437
|
+
// Find deployments with graph configured
|
|
438
|
+
const withGraph = Array.from(state.deployments.values()).filter(d => d.graphType);
|
|
439
|
+
if (withGraph.length === 0) {
|
|
440
|
+
printLog("No graph databases configured");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (withGraph.length === 1) {
|
|
444
|
+
// Single deployment - toggle directly
|
|
445
|
+
const dep = withGraph[0];
|
|
446
|
+
if (dep.graphRunning) {
|
|
447
|
+
printLog(`Stopping ${dep.graphType} for ${dep.name}...`);
|
|
448
|
+
const stopped = await toggleGraph(dep.projectPath, dep.graphType, false);
|
|
449
|
+
dep.graphRunning = !stopped;
|
|
450
|
+
printLog(stopped ? pc.green("Graph stopped") : pc.red("Failed to stop graph"));
|
|
552
451
|
}
|
|
553
452
|
else {
|
|
554
|
-
|
|
453
|
+
printLog(`Starting ${dep.graphType} for ${dep.name}...`);
|
|
454
|
+
const started = await toggleGraph(dep.projectPath, dep.graphType, true);
|
|
455
|
+
dep.graphRunning = started;
|
|
456
|
+
printLog(started ? pc.green("Graph started") : pc.red("Failed to start graph"));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
// Multiple - show menu
|
|
461
|
+
console.log("\n" + pc.bold(" Select deployment to toggle graph:"));
|
|
462
|
+
withGraph.forEach((dep, i) => {
|
|
463
|
+
const status = dep.graphRunning ? pc.green("running") : pc.yellow("stopped");
|
|
464
|
+
console.log(` ${pc.cyan(String(i + 1))} ${dep.name} (${dep.graphType}) - ${status}`);
|
|
465
|
+
});
|
|
466
|
+
console.log(` ${pc.dim("Press 1-9 or any other key to cancel")}\n`);
|
|
467
|
+
const key = await waitForKey();
|
|
468
|
+
const num = parseInt(key);
|
|
469
|
+
if (num >= 1 && num <= withGraph.length) {
|
|
470
|
+
const dep = withGraph[num - 1];
|
|
471
|
+
if (dep.graphRunning) {
|
|
472
|
+
printLog(`Stopping ${dep.graphType} for ${dep.name}...`);
|
|
473
|
+
const stopped = await toggleGraph(dep.projectPath, dep.graphType, false);
|
|
474
|
+
dep.graphRunning = !stopped;
|
|
475
|
+
printLog(stopped ? pc.green("Graph stopped") : pc.red("Failed to stop graph"));
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
printLog(`Starting ${dep.graphType} for ${dep.name}...`);
|
|
479
|
+
const started = await toggleGraph(dep.projectPath, dep.graphType, true);
|
|
480
|
+
dep.graphRunning = started;
|
|
481
|
+
printLog(started ? pc.green("Graph started") : pc.red("Failed to start graph"));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
printStatusUpdate(state);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Toggle graph database
|
|
489
|
+
*/
|
|
490
|
+
async function toggleGraph(projectPath, graphType, start) {
|
|
491
|
+
const dockerComposePath = path.join(projectPath, "docker-compose.graph.yml");
|
|
492
|
+
const containerName = `cortex-${graphType}`;
|
|
493
|
+
if (!fs.existsSync(dockerComposePath)) {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
if (start) {
|
|
498
|
+
// Check if already running
|
|
499
|
+
const checkResult = await execCommand("docker", ["ps", "--filter", `name=${containerName}`, "--format", "{{.Status}}"], { quiet: true });
|
|
500
|
+
if (checkResult.stdout.includes("Up")) {
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
// Check if container exists but stopped
|
|
504
|
+
const existsResult = await execCommand("docker", ["ps", "-a", "--filter", `name=${containerName}`, "--format", "{{.Names}}"], { quiet: true });
|
|
505
|
+
if (existsResult.stdout.includes(containerName)) {
|
|
506
|
+
const startResult = await execCommand("docker", ["start", containerName], { quiet: true });
|
|
507
|
+
if (startResult.code === 0) {
|
|
508
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
await execCommand("docker", ["rm", "-f", containerName], { quiet: true });
|
|
512
|
+
}
|
|
513
|
+
// Start via docker-compose
|
|
514
|
+
const result = await execCommand("docker", ["compose", "-f", "docker-compose.graph.yml", "up", "-d"], { cwd: projectPath, quiet: true });
|
|
515
|
+
if (result.code === 0) {
|
|
516
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
const result = await execCommand("docker", ["stop", containerName], { quiet: true });
|
|
523
|
+
return result.code === 0;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Add a log entry with deployment prefix
|
|
532
|
+
*/
|
|
533
|
+
function addLog(state, deploymentName, message) {
|
|
534
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
535
|
+
const prefix = state.deployments.size > 1
|
|
536
|
+
? pc.cyan(`[${deploymentName}]`.padEnd(18))
|
|
537
|
+
: "";
|
|
538
|
+
const logLine = `${pc.dim(timestamp)} ${prefix}${message}`;
|
|
539
|
+
state.logs.push(logLine);
|
|
540
|
+
if (state.logs.length > state.maxLogs) {
|
|
541
|
+
state.logs = state.logs.slice(-state.maxLogs);
|
|
542
|
+
}
|
|
543
|
+
console.log(` ${logLine}`);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Print a log without deployment prefix
|
|
547
|
+
*/
|
|
548
|
+
function printLog(message) {
|
|
549
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
550
|
+
console.log(` ${pc.dim(timestamp)} ${message}`);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Print inline status update
|
|
554
|
+
*/
|
|
555
|
+
function printStatusUpdate(state) {
|
|
556
|
+
const statuses = [];
|
|
557
|
+
for (const [name, dep] of state.deployments) {
|
|
558
|
+
const convexIcon = dep.convexRunning ? pc.green("●") : pc.yellow("○");
|
|
559
|
+
let status = `${convexIcon} ${name}`;
|
|
560
|
+
if (dep.graphType) {
|
|
561
|
+
const graphIcon = dep.graphRunning ? pc.green("●") : pc.yellow("○");
|
|
562
|
+
status += ` ${graphIcon}${dep.graphType}`;
|
|
563
|
+
}
|
|
564
|
+
statuses.push(status);
|
|
565
|
+
}
|
|
566
|
+
console.log();
|
|
567
|
+
console.log(pc.cyan(" ══════════════════════════════════════════════════════════════"));
|
|
568
|
+
console.log(` ${pc.bold("Status:")} ${statuses.join(" │ ")}`);
|
|
569
|
+
console.log(pc.cyan(" ══════════════════════════════════════════════════════════════"));
|
|
570
|
+
console.log();
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Refresh and display full status
|
|
574
|
+
*/
|
|
575
|
+
async function refreshStatus(state) {
|
|
576
|
+
const width = 66;
|
|
577
|
+
const line = "═".repeat(width);
|
|
578
|
+
const thinLine = "─".repeat(width);
|
|
579
|
+
const count = state.deployments.size;
|
|
580
|
+
console.log();
|
|
581
|
+
console.log(pc.cyan("╔" + line + "╗"));
|
|
582
|
+
console.log(pc.cyan("║") +
|
|
583
|
+
pc.bold(` Cortex Dev Mode (${count} deployment${count !== 1 ? "s" : ""})`).padEnd(width) +
|
|
584
|
+
pc.cyan("║"));
|
|
585
|
+
console.log(pc.cyan("╚" + line + "╝"));
|
|
586
|
+
console.log();
|
|
587
|
+
console.log(pc.bold(pc.white(" Services")));
|
|
588
|
+
console.log(pc.dim(" " + thinLine));
|
|
589
|
+
for (const [name, dep] of state.deployments) {
|
|
590
|
+
// Convex status
|
|
591
|
+
const convexStatus = dep.convexRunning
|
|
592
|
+
? pc.green("Running")
|
|
593
|
+
: pc.yellow("Starting...");
|
|
594
|
+
const convexIcon = dep.convexRunning ? pc.green("●") : pc.yellow("○");
|
|
595
|
+
// Graph status
|
|
596
|
+
let graphStatus = pc.dim("N/A");
|
|
597
|
+
if (dep.graphType) {
|
|
598
|
+
graphStatus = dep.graphRunning
|
|
599
|
+
? pc.green(`Running (${dep.graphType})`)
|
|
600
|
+
: pc.yellow(`Stopped (${dep.graphType})`);
|
|
555
601
|
}
|
|
602
|
+
console.log(` ${convexIcon} ${pc.cyan(name.padEnd(16))} Convex: ${convexStatus.padEnd(20)} Graph: ${graphStatus}`);
|
|
603
|
+
// Show URLs for running services
|
|
604
|
+
if (dep.convexRunning) {
|
|
605
|
+
const isLocal = dep.url.includes("localhost") || dep.url.includes("127.0.0.1");
|
|
606
|
+
if (isLocal) {
|
|
607
|
+
console.log(pc.dim(` Dashboard: http://127.0.0.1:3210`));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (dep.graphRunning && dep.graphType) {
|
|
611
|
+
const url = dep.graphType === "neo4j"
|
|
612
|
+
? "http://localhost:7474"
|
|
613
|
+
: "http://localhost:3000";
|
|
614
|
+
console.log(pc.dim(` ${dep.graphType}: ${url}`));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
console.log();
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Show keyboard shortcut help
|
|
621
|
+
*/
|
|
622
|
+
function showHelp() {
|
|
623
|
+
console.log(pc.dim(" ──────────────────────────────────────────────────────────────────"));
|
|
624
|
+
console.log(pc.dim(" ") +
|
|
625
|
+
pc.cyan("c") + pc.dim(" clear ") +
|
|
626
|
+
pc.cyan("s") + pc.dim(" status ") +
|
|
627
|
+
pc.cyan("r") + pc.dim(" restart ") +
|
|
628
|
+
pc.cyan("g") + pc.dim(" graph ") +
|
|
629
|
+
pc.cyan("k") + pc.dim(" kill ") +
|
|
630
|
+
pc.cyan("q") + pc.dim(" quit ") +
|
|
631
|
+
pc.cyan("?") + pc.dim(" help"));
|
|
632
|
+
console.log();
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Show detailed help
|
|
636
|
+
*/
|
|
637
|
+
function showDetailedHelp() {
|
|
638
|
+
const width = 66;
|
|
639
|
+
const line = "═".repeat(width);
|
|
640
|
+
console.log();
|
|
641
|
+
console.log(pc.cyan("╔" + line + "╗"));
|
|
642
|
+
console.log(pc.cyan("║") +
|
|
643
|
+
pc.bold(" Keyboard Shortcuts").padEnd(width) +
|
|
644
|
+
pc.cyan("║"));
|
|
645
|
+
console.log(pc.cyan("╚" + line + "╝"));
|
|
646
|
+
console.log();
|
|
647
|
+
console.log(` ${pc.cyan("c")} Clear screen & show status`);
|
|
648
|
+
console.log(` ${pc.cyan("s")} Show status dashboard`);
|
|
649
|
+
console.log(` ${pc.cyan("r")} Restart all services`);
|
|
650
|
+
console.log(` ${pc.cyan("g")} Toggle graph database (select deployment if multiple)`);
|
|
651
|
+
console.log(` ${pc.cyan("k")} Kill Convex instances (port conflicts)`);
|
|
652
|
+
console.log(` ${pc.cyan("q")} Quit (Ctrl+C)`);
|
|
653
|
+
console.log(` ${pc.cyan("?")} Show this help`);
|
|
654
|
+
console.log();
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Show kill menu
|
|
658
|
+
*/
|
|
659
|
+
async function showKillMenu(state) {
|
|
660
|
+
const width = 66;
|
|
661
|
+
const line = "═".repeat(width);
|
|
662
|
+
console.log();
|
|
663
|
+
console.log(pc.cyan("╔" + line + "╗"));
|
|
664
|
+
console.log(pc.cyan("║") +
|
|
665
|
+
pc.bold(" Kill Convex Instances").padEnd(width) +
|
|
666
|
+
pc.cyan("║"));
|
|
667
|
+
console.log(pc.cyan("╚" + line + "╝"));
|
|
668
|
+
console.log();
|
|
669
|
+
console.log(pc.dim(" Scanning for running Convex instances..."));
|
|
670
|
+
const instances = await findConvexInstances();
|
|
671
|
+
console.clear();
|
|
672
|
+
console.log();
|
|
673
|
+
console.log(pc.cyan("╔" + line + "╗"));
|
|
674
|
+
console.log(pc.cyan("║") +
|
|
675
|
+
pc.bold(" Kill Convex Instances").padEnd(width) +
|
|
676
|
+
pc.cyan("║"));
|
|
677
|
+
console.log(pc.cyan("╚" + line + "╝"));
|
|
678
|
+
console.log();
|
|
679
|
+
if (instances.length === 0) {
|
|
680
|
+
console.log(pc.green(" No Convex instances found on common ports."));
|
|
681
|
+
console.log(pc.dim("\n Press any key to return..."));
|
|
682
|
+
await waitForKey();
|
|
683
|
+
console.clear();
|
|
684
|
+
await refreshStatus(state);
|
|
685
|
+
showHelp();
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
console.log(pc.bold(pc.white(" Running Instances")));
|
|
689
|
+
console.log(pc.dim(" " + "─".repeat(60)));
|
|
690
|
+
for (let i = 0; i < instances.length; i++) {
|
|
691
|
+
const inst = instances[i];
|
|
692
|
+
console.log(` ${pc.cyan(`${i + 1}`)} Port ${pc.yellow(inst.port)} PID ${inst.pid} ${pc.dim(inst.name)}`);
|
|
693
|
+
}
|
|
694
|
+
console.log();
|
|
695
|
+
console.log(pc.dim(" Press 1-9 to kill, 'a' to kill all, or any other key to cancel"));
|
|
696
|
+
console.log();
|
|
697
|
+
const key = await waitForKey();
|
|
698
|
+
if (key === "a") {
|
|
699
|
+
console.log(pc.yellow("\n Killing all instances..."));
|
|
700
|
+
for (const inst of instances) {
|
|
701
|
+
try {
|
|
702
|
+
process.kill(parseInt(inst.pid), "SIGTERM");
|
|
703
|
+
console.log(pc.green(` ✓ Killed PID ${inst.pid} (port ${inst.port})`));
|
|
704
|
+
}
|
|
705
|
+
catch {
|
|
706
|
+
console.log(pc.red(` ✗ Failed to kill PID ${inst.pid}`));
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
const num = parseInt(key);
|
|
713
|
+
if (num >= 1 && num <= instances.length) {
|
|
714
|
+
const inst = instances[num - 1];
|
|
715
|
+
console.log(pc.yellow(`\n Killing PID ${inst.pid} on port ${inst.port}...`));
|
|
716
|
+
try {
|
|
717
|
+
process.kill(parseInt(inst.pid), "SIGTERM");
|
|
718
|
+
console.log(pc.green(` ✓ Killed successfully`));
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
console.log(pc.red(` ✗ Failed to kill`));
|
|
722
|
+
}
|
|
723
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
console.clear();
|
|
727
|
+
await refreshStatus(state);
|
|
728
|
+
showHelp();
|
|
729
|
+
console.log(pc.dim(" Logs streaming below...\n"));
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Find running Convex instances
|
|
733
|
+
*/
|
|
734
|
+
async function findConvexInstances() {
|
|
735
|
+
const instances = [];
|
|
736
|
+
const ports = ["3210", "3211", "3212", "3213", "3214", "3215"];
|
|
737
|
+
const seenPids = new Set();
|
|
738
|
+
for (const port of ports) {
|
|
739
|
+
try {
|
|
740
|
+
const result = await execCommand("lsof", ["-i", `:${port}`, "-sTCP:LISTEN", "-t"], { quiet: true });
|
|
741
|
+
if (result.code === 0 && result.stdout.trim()) {
|
|
742
|
+
const pids = result.stdout.trim().split("\n");
|
|
743
|
+
for (const pid of pids) {
|
|
744
|
+
if (pid && !seenPids.has(pid)) {
|
|
745
|
+
seenPids.add(pid);
|
|
746
|
+
const nameResult = await execCommand("ps", ["-p", pid, "-o", "comm="], { quiet: true });
|
|
747
|
+
let name = nameResult.stdout.trim() || "unknown";
|
|
748
|
+
const cmdResult = await execCommand("ps", ["-p", pid, "-o", "args="], { quiet: true });
|
|
749
|
+
const cmdLine = cmdResult.stdout.trim();
|
|
750
|
+
if (cmdLine.includes("convex")) {
|
|
751
|
+
name = "convex";
|
|
752
|
+
}
|
|
753
|
+
else if (cmdLine.includes("local-backend")) {
|
|
754
|
+
name = "convex-local-backend";
|
|
755
|
+
}
|
|
756
|
+
instances.push({ pid, port, name });
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
// Port not in use
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return instances;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Wait for a single keypress
|
|
769
|
+
*/
|
|
770
|
+
function waitForKey() {
|
|
771
|
+
return new Promise((resolve) => {
|
|
772
|
+
const handler = (data) => {
|
|
773
|
+
process.stdin.removeListener("data", handler);
|
|
774
|
+
resolve(data);
|
|
775
|
+
};
|
|
776
|
+
process.stdin.once("data", handler);
|
|
556
777
|
});
|
|
557
778
|
}
|
|
558
779
|
//# sourceMappingURL=dev.js.map
|