@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
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Setup and Configuration Commands
2
+ * Configuration Commands
3
3
  *
4
- * Commands for setting up and configuring the CLI:
5
- * - setup: Interactive setup wizard
4
+ * Commands for managing CLI configuration:
6
5
  * - config: Configuration management
7
6
  */
8
7
  import prompts from "prompts";
@@ -12,141 +11,20 @@ import { loadConfig, saveUserConfig, getUserConfigPath, getProjectConfigPath, li
12
11
  import { testConnection } from "../utils/client.js";
13
12
  import { printSuccess, printError, printWarning, printInfo, printSection, formatOutput, } from "../utils/formatting.js";
14
13
  import { validateUrl } from "../utils/validation.js";
14
+ import { addDeploymentToEnv, removeDeploymentFromEnv, getDeploymentEnvKeys, } from "../utils/env-file.js";
15
+ import { getCurrentDeployment, setCurrentDeployment, clearCurrentDeployment, } from "../utils/deployment-selector.js";
15
16
  import { existsSync } from "fs";
17
+ import { join, resolve } from "path";
16
18
  /**
17
- * Register setup and config commands
19
+ * Register config commands
18
20
  */
19
- export function registerSetupCommands(program, _config) {
20
- // setup command
21
- program
22
- .command("setup")
23
- .description("Interactive setup wizard")
24
- .option("--auto", "Auto-configure from environment variables", false)
25
- .option("--local", "Set up local Convex development", false)
26
- .option("--cloud", "Set up cloud Convex deployment", false)
27
- .action(async (options) => {
28
- console.log();
29
- console.log(pc.bold(pc.cyan("🧠 Cortex CLI Setup")));
30
- console.log(pc.dim("Configure your Cortex Memory deployment\n"));
31
- try {
32
- let config = await loadConfig();
33
- if (options.auto) {
34
- // Auto mode: configure from environment variables
35
- await autoSetup(config);
36
- return;
37
- }
38
- // Interactive setup
39
- const setupMode = await prompts({
40
- type: "select",
41
- name: "mode",
42
- message: "What would you like to set up?",
43
- choices: [
44
- {
45
- title: "Local development",
46
- description: "Configure local Convex instance",
47
- value: "local",
48
- },
49
- {
50
- title: "Cloud deployment",
51
- description: "Configure Convex cloud deployment",
52
- value: "cloud",
53
- },
54
- {
55
- title: "Both",
56
- description: "Configure local and cloud deployments",
57
- value: "both",
58
- },
59
- {
60
- title: "View current configuration",
61
- description: "Show existing configuration",
62
- value: "view",
63
- },
64
- ],
65
- initial: options.local ? 0 : options.cloud ? 1 : 2,
66
- });
67
- if (!setupMode.mode) {
68
- printWarning("Setup cancelled");
69
- return;
70
- }
71
- if (setupMode.mode === "view") {
72
- await showConfiguration(config);
73
- return;
74
- }
75
- // Set up local deployment
76
- if (setupMode.mode === "local" || setupMode.mode === "both") {
77
- config = await setupLocalDeployment(config);
78
- }
79
- // Set up cloud deployment
80
- if (setupMode.mode === "cloud" || setupMode.mode === "both") {
81
- config = await setupCloudDeployment(config);
82
- }
83
- // Set default deployment
84
- if (setupMode.mode === "both") {
85
- const defaultChoice = await prompts({
86
- type: "select",
87
- name: "default",
88
- message: "Which deployment should be the default?",
89
- choices: [
90
- { title: "Local", value: "local" },
91
- { title: "Cloud", value: "cloud" },
92
- ],
93
- });
94
- if (defaultChoice.default) {
95
- config.default = defaultChoice.default;
96
- }
97
- }
98
- else {
99
- config.default = setupMode.mode;
100
- }
101
- // Set output format
102
- const formatChoice = await prompts({
103
- type: "select",
104
- name: "format",
105
- message: "Preferred output format?",
106
- choices: [
107
- { title: "Table (human-readable)", value: "table" },
108
- { title: "JSON (machine-readable)", value: "json" },
109
- ],
110
- initial: 0,
111
- });
112
- if (formatChoice.format) {
113
- config.format = formatChoice.format;
114
- }
115
- // Confirm dangerous operations
116
- const confirmChoice = await prompts({
117
- type: "confirm",
118
- name: "confirm",
119
- message: "Require confirmation for dangerous operations (delete, clear)?",
120
- initial: true,
121
- });
122
- config.confirmDangerous = confirmChoice.confirm ?? true;
123
- // Save configuration
124
- await saveUserConfig(config);
125
- console.log();
126
- printSuccess(`Configuration saved to ${getUserConfigPath()}`);
127
- // Test connection
128
- const testChoice = await prompts({
129
- type: "confirm",
130
- name: "test",
131
- message: "Would you like to test the connection?",
132
- initial: true,
133
- });
134
- if (testChoice.test) {
135
- await testAndShowConnection(config, config.default);
136
- }
137
- console.log();
138
- printSuccess("Setup complete! 🎉");
139
- printInfo("Run 'cortex --help' to see available commands");
140
- }
141
- catch (error) {
142
- printError(error instanceof Error ? error.message : "Setup failed");
143
- process.exit(1);
144
- }
145
- });
21
+ export function registerConfigCommands(program, _config) {
146
22
  // config command group
147
23
  const configCmd = program
148
24
  .command("config")
149
- .description("Manage CLI configuration");
25
+ .description("Manage CLI configuration")
26
+ .enablePositionalOptions()
27
+ .passThroughOptions();
150
28
  // config show
151
29
  configCmd
152
30
  .command("show")
@@ -162,6 +40,75 @@ export function registerSetupCommands(program, _config) {
162
40
  process.exit(1);
163
41
  }
164
42
  });
43
+ // config list - Table view of deployments
44
+ configCmd
45
+ .command("list")
46
+ .description("List all deployments in table format")
47
+ .action(async () => {
48
+ try {
49
+ const config = await loadConfig();
50
+ const deployments = Object.entries(config.deployments);
51
+ if (deployments.length === 0) {
52
+ console.log(pc.yellow("\n No deployments configured\n"));
53
+ printInfo("To get started:");
54
+ console.log(pc.dim(" • Run 'cortex init' to create a new project"));
55
+ console.log(pc.dim(" • Run 'cortex config add-deployment' to add an existing deployment"));
56
+ return;
57
+ }
58
+ console.log();
59
+ // Table header
60
+ const nameWidth = 20;
61
+ const statusWidth = 10;
62
+ const urlWidth = 40;
63
+ const keyWidth = 6;
64
+ const pathWidth = 35;
65
+ console.log(pc.bold(" " +
66
+ "NAME".padEnd(nameWidth) +
67
+ "STATUS".padEnd(statusWidth) +
68
+ "URL".padEnd(urlWidth) +
69
+ "KEY".padEnd(keyWidth) +
70
+ "PROJECT PATH"));
71
+ console.log(pc.dim(" " + "─".repeat(nameWidth + statusWidth + urlWidth + keyWidth + pathWidth)));
72
+ for (const [name, deployment] of deployments) {
73
+ const isDefault = name === config.default;
74
+ // Default deployment is implicitly enabled; others check enabled field
75
+ const isEnabled = deployment.enabled === true || (deployment.enabled === undefined && isDefault);
76
+ const prefix = isDefault ? pc.green("→ ") : " ";
77
+ // Pad BEFORE applying color to avoid ANSI escape code length issues
78
+ const namePadded = name.padEnd(nameWidth - 2);
79
+ const nameDisplay = isDefault ? pc.cyan(namePadded) : namePadded;
80
+ const statusText = isEnabled ? "enabled" : "disabled";
81
+ const statusPadded = statusText.padEnd(statusWidth);
82
+ const statusDisplay = isEnabled ? pc.green(statusPadded) : pc.dim(statusPadded);
83
+ const keyText = deployment.key ? "yes" : "no";
84
+ const keyPadded = keyText.padEnd(keyWidth);
85
+ const keyStatus = deployment.key ? pc.green(keyPadded) : pc.dim(keyPadded);
86
+ const urlDisplay = (deployment.url.length > urlWidth - 2
87
+ ? deployment.url.substring(0, urlWidth - 5) + "..."
88
+ : deployment.url).padEnd(urlWidth);
89
+ const pathDisplay = deployment.projectPath
90
+ ? (deployment.projectPath.length > pathWidth - 2
91
+ ? "..." + deployment.projectPath.slice(-(pathWidth - 5))
92
+ : deployment.projectPath)
93
+ : pc.dim("--");
94
+ console.log(prefix +
95
+ nameDisplay +
96
+ statusDisplay +
97
+ urlDisplay +
98
+ keyStatus +
99
+ pathDisplay);
100
+ }
101
+ console.log();
102
+ if (config.default) {
103
+ console.log(pc.dim(` Default: ${config.default} (→) • Enabled deployments started with 'cortex start'`));
104
+ }
105
+ console.log();
106
+ }
107
+ catch (error) {
108
+ printError(error instanceof Error ? error.message : "Failed to load config");
109
+ process.exit(1);
110
+ }
111
+ });
165
112
  // config set
166
113
  configCmd
167
114
  .command("set <key> <value>")
@@ -192,6 +139,101 @@ export function registerSetupCommands(program, _config) {
192
139
  process.exit(1);
193
140
  }
194
141
  });
142
+ // config set-path - Set project path for a deployment
143
+ configCmd
144
+ .command("set-path <deployment> [path]")
145
+ .description("Set project path for a deployment (enables 'cortex start -d <name>' from anywhere)")
146
+ .action(async (deploymentName, pathArg) => {
147
+ try {
148
+ const config = await loadConfig();
149
+ if (!config.deployments[deploymentName]) {
150
+ printError(`Deployment "${deploymentName}" not found`);
151
+ const names = Object.keys(config.deployments);
152
+ if (names.length > 0) {
153
+ printInfo(`Available deployments: ${names.join(", ")}`);
154
+ }
155
+ process.exit(1);
156
+ }
157
+ // If no path provided, use current directory
158
+ const projectPath = pathArg ? resolve(pathArg) : process.cwd();
159
+ // Verify path exists
160
+ if (!existsSync(projectPath)) {
161
+ printError(`Path does not exist: ${projectPath}`);
162
+ process.exit(1);
163
+ }
164
+ // Update deployment with projectPath
165
+ config.deployments[deploymentName] = {
166
+ ...config.deployments[deploymentName],
167
+ projectPath,
168
+ };
169
+ await saveUserConfig(config);
170
+ printSuccess(`Set project path for "${deploymentName}"`);
171
+ console.log(pc.dim(` Path: ${projectPath}`));
172
+ console.log();
173
+ printInfo(`You can now run: cortex start -d ${deploymentName}`);
174
+ console.log(pc.dim(" This will work from any directory"));
175
+ }
176
+ catch (error) {
177
+ printError(error instanceof Error ? error.message : "Failed to set project path");
178
+ process.exit(1);
179
+ }
180
+ });
181
+ // config enable - Enable a deployment for `cortex start`
182
+ configCmd
183
+ .command("enable <deployment>")
184
+ .description("Enable a deployment (will be started with 'cortex start')")
185
+ .action(async (deploymentName) => {
186
+ try {
187
+ const config = await loadConfig();
188
+ if (!config.deployments[deploymentName]) {
189
+ printError(`Deployment "${deploymentName}" not found`);
190
+ const names = Object.keys(config.deployments);
191
+ if (names.length > 0) {
192
+ printInfo(`Available deployments: ${names.join(", ")}`);
193
+ }
194
+ process.exit(1);
195
+ }
196
+ config.deployments[deploymentName] = {
197
+ ...config.deployments[deploymentName],
198
+ enabled: true,
199
+ };
200
+ await saveUserConfig(config);
201
+ printSuccess(`Enabled deployment "${deploymentName}"`);
202
+ console.log(pc.dim(" Will be started with 'cortex start'"));
203
+ }
204
+ catch (error) {
205
+ printError(error instanceof Error ? error.message : "Failed to enable deployment");
206
+ process.exit(1);
207
+ }
208
+ });
209
+ // config disable - Disable a deployment
210
+ configCmd
211
+ .command("disable <deployment>")
212
+ .description("Disable a deployment (will not be started with 'cortex start')")
213
+ .action(async (deploymentName) => {
214
+ try {
215
+ const config = await loadConfig();
216
+ if (!config.deployments[deploymentName]) {
217
+ printError(`Deployment "${deploymentName}" not found`);
218
+ const names = Object.keys(config.deployments);
219
+ if (names.length > 0) {
220
+ printInfo(`Available deployments: ${names.join(", ")}`);
221
+ }
222
+ process.exit(1);
223
+ }
224
+ config.deployments[deploymentName] = {
225
+ ...config.deployments[deploymentName],
226
+ enabled: false,
227
+ };
228
+ await saveUserConfig(config);
229
+ printSuccess(`Disabled deployment "${deploymentName}"`);
230
+ console.log(pc.dim(" Will NOT be started with 'cortex start'"));
231
+ }
232
+ catch (error) {
233
+ printError(error instanceof Error ? error.message : "Failed to disable deployment");
234
+ process.exit(1);
235
+ }
236
+ });
195
237
  // config deployments
196
238
  configCmd
197
239
  .command("deployments")
@@ -226,24 +268,126 @@ export function registerSetupCommands(program, _config) {
226
268
  });
227
269
  // config add-deployment
228
270
  configCmd
229
- .command("add-deployment <name>")
230
- .description("Add a new deployment configuration")
231
- .requiredOption("-u, --url <url>", "Convex deployment URL")
271
+ .command("add-deployment [name]")
272
+ .description("Add a new deployment configuration\n\nExample: cortex config add-deployment cloud -u https://my-app.convex.cloud")
273
+ .option("-u, --url <url>", "Convex deployment URL")
232
274
  .option("-k, --key <key>", "Convex deploy key")
233
275
  .option("--default", "Set as default deployment", false)
234
- .action(async (name, options) => {
276
+ .option("--json-only", "Only save to ~/.cortexrc (skip .env.local)", false)
277
+ .action(async (nameArg, options) => {
235
278
  try {
236
- validateUrl(options.url);
279
+ // Prompt for missing values interactively
280
+ let name = nameArg;
281
+ let url = options.url;
282
+ let key = options.key;
283
+ if (!name) {
284
+ const response = await prompts({
285
+ type: "select",
286
+ name: "name",
287
+ message: "Deployment name:",
288
+ choices: [
289
+ {
290
+ title: "local",
291
+ description: "Local development",
292
+ value: "local",
293
+ },
294
+ {
295
+ title: "cloud",
296
+ description: "Cloud/production",
297
+ value: "cloud",
298
+ },
299
+ {
300
+ title: "staging",
301
+ description: "Staging environment",
302
+ value: "staging",
303
+ },
304
+ {
305
+ title: "custom",
306
+ description: "Enter custom name",
307
+ value: "__custom__",
308
+ },
309
+ ],
310
+ });
311
+ if (!response.name) {
312
+ printWarning("Cancelled");
313
+ return;
314
+ }
315
+ if (response.name === "__custom__") {
316
+ const customResponse = await prompts({
317
+ type: "text",
318
+ name: "name",
319
+ message: "Custom deployment name:",
320
+ validate: (v) => v.length > 0 || "Name is required",
321
+ });
322
+ if (!customResponse.name) {
323
+ printWarning("Cancelled");
324
+ return;
325
+ }
326
+ name = customResponse.name;
327
+ }
328
+ else {
329
+ name = response.name;
330
+ }
331
+ }
332
+ if (!url) {
333
+ const isLocal = name.toLowerCase() === "local";
334
+ const response = await prompts({
335
+ type: "text",
336
+ name: "url",
337
+ message: "Convex deployment URL:",
338
+ initial: isLocal
339
+ ? "http://127.0.0.1:3210"
340
+ : "https://your-app.convex.cloud",
341
+ validate: (v) => {
342
+ try {
343
+ new URL(v);
344
+ return true;
345
+ }
346
+ catch {
347
+ return "Please enter a valid URL";
348
+ }
349
+ },
350
+ });
351
+ if (!response.url) {
352
+ printWarning("Cancelled");
353
+ return;
354
+ }
355
+ url = response.url;
356
+ }
357
+ validateUrl(url);
358
+ // Only prompt for key if not local and not already provided
359
+ const isLocal = name.toLowerCase() === "local";
360
+ if (!key && !isLocal) {
361
+ const response = await prompts({
362
+ type: "password",
363
+ name: "key",
364
+ message: "Convex deploy key (optional, press Enter to skip):",
365
+ });
366
+ key = response.key || undefined;
367
+ }
237
368
  const deployment = {
238
- url: options.url,
239
- key: options.key,
369
+ url,
370
+ key,
240
371
  };
372
+ // Save to user config (~/.cortexrc)
241
373
  const config = await updateDeployment(name, deployment);
242
374
  if (options.default) {
243
375
  config.default = name;
244
376
  await saveUserConfig(config);
245
377
  }
246
- printSuccess(`Added deployment "${name}"`);
378
+ // Also save to .env.local (unless --json-only)
379
+ if (!options.jsonOnly) {
380
+ await addDeploymentToEnv(name, url, key);
381
+ const envKeys = getDeploymentEnvKeys(name);
382
+ printSuccess(`Added deployment "${name}"`);
383
+ printInfo(`Updated .env.local: ${envKeys.urlKey}=${url}`);
384
+ if (key) {
385
+ printInfo(`Updated .env.local: ${envKeys.keyKey}=***`);
386
+ }
387
+ }
388
+ else {
389
+ printSuccess(`Added deployment "${name}" to ~/.cortexrc`);
390
+ }
247
391
  if (options.default) {
248
392
  printInfo(`Set as default deployment`);
249
393
  }
@@ -255,11 +399,36 @@ export function registerSetupCommands(program, _config) {
255
399
  });
256
400
  // config remove-deployment
257
401
  configCmd
258
- .command("remove-deployment <name>")
402
+ .command("remove-deployment [name]")
259
403
  .description("Remove a deployment configuration")
260
- .action(async (name) => {
404
+ .option("--json-only", "Only remove from ~/.cortexrc (skip .env.local)", false)
405
+ .action(async (nameArg, options) => {
261
406
  try {
262
407
  const config = await loadConfig();
408
+ let name = nameArg;
409
+ // If no name provided, show interactive selection
410
+ if (!name) {
411
+ const deploymentNames = Object.keys(config.deployments).filter((n) => n !== config.default);
412
+ if (deploymentNames.length === 0) {
413
+ printWarning("No removable deployments found (cannot remove default)");
414
+ return;
415
+ }
416
+ const response = await prompts({
417
+ type: "select",
418
+ name: "name",
419
+ message: "Select deployment to remove:",
420
+ choices: deploymentNames.map((n) => ({
421
+ title: n,
422
+ description: config.deployments[n].url,
423
+ value: n,
424
+ })),
425
+ });
426
+ if (!response.name) {
427
+ printWarning("Cancelled");
428
+ return;
429
+ }
430
+ name = response.name;
431
+ }
263
432
  if (!config.deployments[name]) {
264
433
  printError(`Deployment "${name}" not found`);
265
434
  process.exit(1);
@@ -268,9 +437,30 @@ export function registerSetupCommands(program, _config) {
268
437
  printError(`Cannot remove default deployment. Set a different default first.`);
269
438
  process.exit(1);
270
439
  }
440
+ // Confirm removal
441
+ const confirm = await prompts({
442
+ type: "confirm",
443
+ name: "value",
444
+ message: `Remove deployment "${name}" (${config.deployments[name].url})?`,
445
+ initial: false,
446
+ });
447
+ if (!confirm.value) {
448
+ printWarning("Cancelled");
449
+ return;
450
+ }
451
+ // Remove from user config (~/.cortexrc)
271
452
  delete config.deployments[name];
272
453
  await saveUserConfig(config);
273
- printSuccess(`Removed deployment "${name}"`);
454
+ // Also remove from .env.local (unless --json-only)
455
+ if (!options.jsonOnly) {
456
+ const envKeys = getDeploymentEnvKeys(name);
457
+ await removeDeploymentFromEnv(name);
458
+ printSuccess(`Removed deployment "${name}"`);
459
+ printInfo(`Removed from .env.local: ${envKeys.urlKey}, ${envKeys.keyKey}`);
460
+ }
461
+ else {
462
+ printSuccess(`Removed deployment "${name}" from ~/.cortexrc`);
463
+ }
274
464
  }
275
465
  catch (error) {
276
466
  printError(error instanceof Error
@@ -279,20 +469,208 @@ export function registerSetupCommands(program, _config) {
279
469
  process.exit(1);
280
470
  }
281
471
  });
472
+ // config set-key
473
+ configCmd
474
+ .command("set-key [deployment]")
475
+ .description("Set or update the deploy key for a deployment")
476
+ .option("-k, --key <key>", "Deploy key (will prompt if not provided)")
477
+ .option("--json-only", "Only update ~/.cortexrc (skip .env.local)", false)
478
+ .action(async (deploymentArg, options) => {
479
+ try {
480
+ const config = await loadConfig();
481
+ let name = deploymentArg;
482
+ // If no name provided, show interactive selection
483
+ if (!name) {
484
+ const deploymentNames = Object.keys(config.deployments);
485
+ if (deploymentNames.length === 0) {
486
+ printWarning("No deployments configured");
487
+ console.log(pc.dim(" Run 'cortex config add-deployment' to add one\n"));
488
+ return;
489
+ }
490
+ const response = await prompts({
491
+ type: "select",
492
+ name: "name",
493
+ message: "Select deployment to set key for:",
494
+ choices: deploymentNames.map((n) => {
495
+ const dep = config.deployments[n];
496
+ const keyStatus = dep.key ? pc.green("(key set)") : pc.dim("(no key)");
497
+ return {
498
+ title: `${n} ${keyStatus}`,
499
+ description: dep.url,
500
+ value: n,
501
+ };
502
+ }),
503
+ });
504
+ if (!response.name) {
505
+ printWarning("Cancelled");
506
+ return;
507
+ }
508
+ name = response.name;
509
+ }
510
+ if (!config.deployments[name]) {
511
+ printError(`Deployment "${name}" not found`);
512
+ console.log(pc.dim(" Run 'cortex config list' to see available deployments\n"));
513
+ process.exit(1);
514
+ }
515
+ // Get key from option or prompt
516
+ let key = options.key;
517
+ if (!key) {
518
+ const response = await prompts({
519
+ type: "password",
520
+ name: "key",
521
+ message: `Enter deploy key for "${name}":`,
522
+ validate: (v) => v.length > 0 || "Key cannot be empty",
523
+ });
524
+ if (!response.key) {
525
+ printWarning("Cancelled");
526
+ return;
527
+ }
528
+ key = response.key;
529
+ }
530
+ // Update the deployment with the new key
531
+ const deployment = config.deployments[name];
532
+ deployment.key = key;
533
+ await updateDeployment(name, deployment);
534
+ // Also update .env.local (unless --json-only)
535
+ if (!options.jsonOnly) {
536
+ await addDeploymentToEnv(name, deployment.url, key);
537
+ const envKeys = getDeploymentEnvKeys(name);
538
+ printSuccess(`Set deploy key for "${name}"`);
539
+ printInfo(`Updated .env.local: ${envKeys.keyKey}=***`);
540
+ }
541
+ else {
542
+ printSuccess(`Set deploy key for "${name}" in ~/.cortexrc`);
543
+ }
544
+ }
545
+ catch (error) {
546
+ printError(error instanceof Error ? error.message : "Failed to set key");
547
+ process.exit(1);
548
+ }
549
+ });
550
+ // config set-url
551
+ configCmd
552
+ .command("set-url [deployment]")
553
+ .description("Set or update the URL for a deployment")
554
+ .option("-u, --url <url>", "Deployment URL (will prompt if not provided)")
555
+ .option("--json-only", "Only update ~/.cortexrc (skip .env.local)", false)
556
+ .action(async (deploymentArg, options) => {
557
+ try {
558
+ const config = await loadConfig();
559
+ let name = deploymentArg;
560
+ // If no name provided, show interactive selection
561
+ if (!name) {
562
+ const deploymentNames = Object.keys(config.deployments);
563
+ if (deploymentNames.length === 0) {
564
+ printWarning("No deployments configured");
565
+ console.log(pc.dim(" Run 'cortex config add-deployment' to add one\n"));
566
+ return;
567
+ }
568
+ const response = await prompts({
569
+ type: "select",
570
+ name: "name",
571
+ message: "Select deployment to set URL for:",
572
+ choices: deploymentNames.map((n) => {
573
+ const dep = config.deployments[n];
574
+ return {
575
+ title: n,
576
+ description: dep.url,
577
+ value: n,
578
+ };
579
+ }),
580
+ });
581
+ if (!response.name) {
582
+ printWarning("Cancelled");
583
+ return;
584
+ }
585
+ name = response.name;
586
+ }
587
+ if (!config.deployments[name]) {
588
+ printError(`Deployment "${name}" not found`);
589
+ console.log(pc.dim(" Run 'cortex config list' to see available deployments\n"));
590
+ process.exit(1);
591
+ }
592
+ // Get URL from option or prompt
593
+ let url = options.url;
594
+ if (!url) {
595
+ const currentUrl = config.deployments[name].url;
596
+ const response = await prompts({
597
+ type: "text",
598
+ name: "url",
599
+ message: `Enter URL for "${name}":`,
600
+ initial: currentUrl,
601
+ validate: (v) => {
602
+ try {
603
+ new URL(v);
604
+ return true;
605
+ }
606
+ catch {
607
+ return "Please enter a valid URL";
608
+ }
609
+ },
610
+ });
611
+ if (!response.url) {
612
+ printWarning("Cancelled");
613
+ return;
614
+ }
615
+ url = response.url;
616
+ }
617
+ // Validate URL
618
+ validateUrl(url);
619
+ // Update the deployment with the new URL
620
+ const deployment = config.deployments[name];
621
+ const oldUrl = deployment.url;
622
+ deployment.url = url;
623
+ await updateDeployment(name, deployment);
624
+ // Also update .env.local (unless --json-only)
625
+ if (!options.jsonOnly) {
626
+ await addDeploymentToEnv(name, url, deployment.key);
627
+ const envKeys = getDeploymentEnvKeys(name);
628
+ printSuccess(`Updated URL for "${name}"`);
629
+ printInfo(`${oldUrl} → ${url}`);
630
+ printInfo(`Updated .env.local: ${envKeys.urlKey}=${url}`);
631
+ }
632
+ else {
633
+ printSuccess(`Updated URL for "${name}" in ~/.cortexrc`);
634
+ printInfo(`${oldUrl} → ${url}`);
635
+ }
636
+ }
637
+ catch (error) {
638
+ printError(error instanceof Error ? error.message : "Failed to set URL");
639
+ process.exit(1);
640
+ }
641
+ });
282
642
  // config path
283
643
  configCmd
284
644
  .command("path")
285
645
  .description("Show configuration file paths")
286
646
  .action(async () => {
287
647
  const userPath = getUserConfigPath();
288
- const projectPath = getProjectConfigPath();
648
+ const projectJsonPath = getProjectConfigPath();
649
+ const projectEnvPath = join(process.cwd(), ".env.local");
289
650
  console.log();
290
651
  printSection("Configuration Paths", {
291
- "User config": userPath,
652
+ "User config (~/.cortexrc)": userPath,
292
653
  "User config exists": existsSync(userPath) ? "Yes" : "No",
293
- "Project config": projectPath,
294
- "Project config exists": existsSync(projectPath) ? "Yes" : "No",
654
+ "Project JSON config": projectJsonPath,
655
+ "Project JSON exists": existsSync(projectJsonPath) ? "Yes" : "No",
656
+ "Project env config": projectEnvPath,
657
+ "Project env exists": existsSync(projectEnvPath) ? "Yes" : "No",
295
658
  });
659
+ // Show which env vars are currently set
660
+ const envVars = {
661
+ LOCAL_CONVEX_URL: process.env.LOCAL_CONVEX_URL,
662
+ LOCAL_CONVEX_DEPLOYMENT: process.env.LOCAL_CONVEX_DEPLOYMENT,
663
+ CLOUD_CONVEX_URL: process.env.CLOUD_CONVEX_URL,
664
+ CLOUD_CONVEX_DEPLOY_KEY: process.env.CLOUD_CONVEX_DEPLOY_KEY
665
+ ? "***"
666
+ : undefined,
667
+ CONVEX_URL: process.env.CONVEX_URL,
668
+ CONVEX_DEPLOY_KEY: process.env.CONVEX_DEPLOY_KEY ? "***" : undefined,
669
+ };
670
+ const setVars = Object.entries(envVars).filter(([, v]) => v);
671
+ if (setVars.length > 0) {
672
+ printSection("Environment Variables", Object.fromEntries(setVars));
673
+ }
296
674
  });
297
675
  // config reset
298
676
  configCmd
@@ -314,134 +692,88 @@ export function registerSetupCommands(program, _config) {
314
692
  }
315
693
  }
316
694
  const defaultConfig = {
317
- deployments: {
318
- local: {
319
- url: "http://127.0.0.1:3210",
320
- deployment: "anonymous:anonymous-cortex-sdk-local",
321
- },
322
- },
323
- default: "local",
695
+ deployments: {},
696
+ default: "",
324
697
  format: "table",
325
698
  confirmDangerous: true,
326
699
  };
327
700
  await saveUserConfig(defaultConfig);
328
701
  printSuccess("Configuration reset to defaults");
702
+ console.log();
703
+ printInfo("No deployments configured. To get started:");
704
+ console.log(pc.dim(" • Run 'cortex init' to create a new project"));
705
+ console.log(pc.dim(" • Run 'cortex config add-deployment' to add an existing deployment"));
329
706
  }
330
707
  catch (error) {
331
708
  printError(error instanceof Error ? error.message : "Reset failed");
332
709
  process.exit(1);
333
710
  }
334
711
  });
335
- }
336
- /**
337
- * Auto-configure from environment variables
338
- */
339
- async function autoSetup(config) {
340
- const spinner = ora("Auto-configuring from environment...").start();
341
- let hasChanges = false;
342
- // Check for local config
343
- const localUrl = process.env.LOCAL_CONVEX_URL;
344
- const localDeployment = process.env.LOCAL_CONVEX_DEPLOYMENT;
345
- if (localUrl) {
346
- config.deployments.local = {
347
- url: localUrl,
348
- deployment: localDeployment,
349
- };
350
- hasChanges = true;
351
- }
352
- // Check for cloud config
353
- const cloudUrl = process.env.CLOUD_CONVEX_URL ?? process.env.CONVEX_URL;
354
- const cloudKey = process.env.CLOUD_CONVEX_DEPLOY_KEY ?? process.env.CONVEX_DEPLOY_KEY;
355
- if (cloudUrl &&
356
- !cloudUrl.includes("localhost") &&
357
- !cloudUrl.includes("127.0.0.1")) {
358
- config.deployments.cloud = {
359
- url: cloudUrl,
360
- key: cloudKey,
361
- };
362
- hasChanges = true;
363
- }
364
- spinner.stop();
365
- if (!hasChanges) {
366
- printWarning("No environment variables found to configure");
367
- printInfo("Set LOCAL_CONVEX_URL or CONVEX_URL environment variables");
368
- return;
369
- }
370
- await saveUserConfig(config);
371
- printSuccess("Auto-configured from environment variables");
372
- await showConfiguration(config);
373
- }
374
- /**
375
- * Set up local deployment
376
- */
377
- async function setupLocalDeployment(config) {
378
- console.log();
379
- console.log(pc.bold("Local Development Setup"));
380
- console.log(pc.dim("Configure connection to local Convex instance\n"));
381
- const urlPrompt = await prompts({
382
- type: "text",
383
- name: "url",
384
- message: "Local Convex URL:",
385
- initial: config.deployments.local?.url ?? "http://127.0.0.1:3210",
386
- validate: (value) => {
387
- try {
388
- new URL(value);
389
- return true;
390
- }
391
- catch {
392
- return "Please enter a valid URL";
393
- }
394
- },
395
- });
396
- const deploymentPrompt = await prompts({
397
- type: "text",
398
- name: "deployment",
399
- message: "Local deployment name (optional):",
400
- initial: config.deployments.local?.deployment ??
401
- "anonymous:anonymous-cortex-sdk-local",
402
- });
403
- config.deployments.local = {
404
- url: urlPrompt.url,
405
- deployment: deploymentPrompt.deployment || undefined,
406
- };
407
- return config;
408
- }
409
- /**
410
- * Set up cloud deployment
411
- */
412
- async function setupCloudDeployment(config) {
413
- console.log();
414
- console.log(pc.bold("Cloud Deployment Setup"));
415
- console.log(pc.dim("Configure connection to Convex cloud\n"));
416
- const urlPrompt = await prompts({
417
- type: "text",
418
- name: "url",
419
- message: "Convex cloud URL:",
420
- initial: config.deployments.cloud?.url ?? "https://your-deployment.convex.cloud",
421
- validate: (value) => {
422
- try {
423
- const url = new URL(value);
424
- if (!url.hostname.includes("convex")) {
425
- return "URL should be a Convex cloud URL";
712
+ // Top-level 'use' command for quick deployment switching
713
+ program
714
+ .command("use [deployment]")
715
+ .description("Set current deployment for all commands (session context)")
716
+ .option("--clear", "Clear the current deployment setting")
717
+ .action(async (deploymentName, options) => {
718
+ try {
719
+ const config = await loadConfig();
720
+ const deployments = Object.keys(config.deployments);
721
+ // Handle --clear flag
722
+ if (options.clear) {
723
+ await clearCurrentDeployment();
724
+ printSuccess("Cleared current deployment");
725
+ console.log(pc.dim(" Commands will now prompt for deployment selection\n"));
726
+ return;
727
+ }
728
+ // If no deployment specified, show current and list available
729
+ if (!deploymentName) {
730
+ const current = await getCurrentDeployment();
731
+ console.log();
732
+ if (current && config.deployments[current]) {
733
+ console.log(pc.bold(" Current deployment: ") + pc.cyan(current));
734
+ }
735
+ else if (current) {
736
+ console.log(pc.yellow(` Current deployment "${current}" no longer exists`));
737
+ }
738
+ else {
739
+ console.log(pc.dim(" No current deployment set"));
740
+ }
741
+ console.log();
742
+ console.log(pc.bold(" Available deployments:"));
743
+ if (deployments.length === 0) {
744
+ console.log(pc.yellow(" No deployments configured"));
745
+ }
746
+ else {
747
+ for (const name of deployments) {
748
+ const isCurrent = name === current;
749
+ const isDefault = name === config.default;
750
+ const prefix = isCurrent ? pc.green("→") : " ";
751
+ const suffix = isDefault ? pc.dim(" (default)") : "";
752
+ console.log(` ${prefix} ${pc.cyan(name)}${suffix}`);
753
+ }
426
754
  }
427
- return true;
755
+ console.log();
756
+ console.log(pc.dim(" Usage: cortex use <deployment>"));
757
+ console.log(pc.dim(" cortex use --clear"));
758
+ console.log();
759
+ return;
428
760
  }
429
- catch {
430
- return "Please enter a valid URL";
761
+ // Validate deployment exists
762
+ if (!config.deployments[deploymentName]) {
763
+ printError(`Deployment "${deploymentName}" not found`);
764
+ console.log(pc.dim(` Available: ${deployments.join(", ")}\n`));
765
+ process.exit(1);
431
766
  }
432
- },
433
- });
434
- const keyPrompt = await prompts({
435
- type: "password",
436
- name: "key",
437
- message: "Convex deploy key (optional):",
438
- initial: config.deployments.cloud?.key ?? "",
767
+ // Set the current deployment
768
+ await setCurrentDeployment(deploymentName);
769
+ printSuccess(`Now using: ${deploymentName}`);
770
+ console.log(pc.dim(` All commands will target this deployment until changed\n`));
771
+ }
772
+ catch (error) {
773
+ printError(error instanceof Error ? error.message : "Failed to set deployment");
774
+ process.exit(1);
775
+ }
439
776
  });
440
- config.deployments.cloud = {
441
- url: urlPrompt.url,
442
- key: keyPrompt.key || undefined,
443
- };
444
- return config;
445
777
  }
446
778
  /**
447
779
  * Show current configuration
@@ -467,11 +799,27 @@ async function showConfiguration(config, format) {
467
799
  "Confirm dangerous ops": config.confirmDangerous ? "Yes" : "No",
468
800
  });
469
801
  console.log(" Deployments:");
470
- for (const [name, deployment] of Object.entries(config.deployments)) {
471
- const isDefault = name === config.default;
472
- const prefix = isDefault ? pc.green("→") : " ";
473
- const keyStatus = deployment.key ? pc.green("(key set)") : "";
474
- console.log(` ${prefix} ${pc.cyan(name)}: ${deployment.url} ${keyStatus}`);
802
+ const deploymentEntries = Object.entries(config.deployments);
803
+ if (deploymentEntries.length === 0) {
804
+ console.log(pc.yellow(" No deployments configured"));
805
+ console.log();
806
+ printInfo("To get started:");
807
+ console.log(pc.dim(" • Run 'cortex init' to create a new project"));
808
+ console.log(pc.dim(" • Run 'cortex config add-deployment' to add an existing deployment"));
809
+ }
810
+ else {
811
+ for (const [name, deployment] of deploymentEntries) {
812
+ const isDefault = name === config.default;
813
+ // Default deployment is implicitly enabled; others check enabled field
814
+ const isEnabled = deployment.enabled === true || (deployment.enabled === undefined && isDefault);
815
+ const prefix = isDefault ? pc.green("→") : " ";
816
+ const statusBadge = isEnabled ? pc.green("[enabled]") : pc.dim("[disabled]");
817
+ const keyStatus = deployment.key ? pc.green("(key set)") : "";
818
+ console.log(` ${prefix} ${pc.cyan(name)}: ${deployment.url} ${statusBadge} ${keyStatus}`);
819
+ if (deployment.projectPath) {
820
+ console.log(pc.dim(` Project: ${deployment.projectPath}`));
821
+ }
822
+ }
475
823
  }
476
824
  console.log();
477
825
  }