@memnexus-ai/cli 0.1.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 (148) hide show
  1. package/.env.example +13 -0
  2. package/.eslintrc.js +24 -0
  3. package/.github/ISSUE_TEMPLATE/phase-1-foundation.md +1078 -0
  4. package/.github/workflows/publish.yml +277 -0
  5. package/.github/workflows/test-app-token.yml +54 -0
  6. package/.npmrc.backup +3 -0
  7. package/.npmrc.example +6 -0
  8. package/.prettierignore +4 -0
  9. package/.prettierrc +8 -0
  10. package/CHANGELOG.md +138 -0
  11. package/PLATFORM_TESTING.md +243 -0
  12. package/README.md +986 -0
  13. package/RELEASE.md +428 -0
  14. package/RELEASE_READINESS.md +253 -0
  15. package/USAGE.md +1373 -0
  16. package/bin/mx.js +2 -0
  17. package/dist/commands/apikeys.d.ts +7 -0
  18. package/dist/commands/apikeys.d.ts.map +1 -0
  19. package/dist/commands/apikeys.js +133 -0
  20. package/dist/commands/apikeys.js.map +1 -0
  21. package/dist/commands/artifacts.d.ts +7 -0
  22. package/dist/commands/artifacts.d.ts.map +1 -0
  23. package/dist/commands/artifacts.js +277 -0
  24. package/dist/commands/artifacts.js.map +1 -0
  25. package/dist/commands/auth.d.ts +7 -0
  26. package/dist/commands/auth.d.ts.map +1 -0
  27. package/dist/commands/auth.js +119 -0
  28. package/dist/commands/auth.js.map +1 -0
  29. package/dist/commands/communities.d.ts +7 -0
  30. package/dist/commands/communities.d.ts.map +1 -0
  31. package/dist/commands/communities.js +137 -0
  32. package/dist/commands/communities.js.map +1 -0
  33. package/dist/commands/config.d.ts +7 -0
  34. package/dist/commands/config.d.ts.map +1 -0
  35. package/dist/commands/config.js +138 -0
  36. package/dist/commands/config.js.map +1 -0
  37. package/dist/commands/conversations.d.ts +7 -0
  38. package/dist/commands/conversations.d.ts.map +1 -0
  39. package/dist/commands/conversations.js +160 -0
  40. package/dist/commands/conversations.js.map +1 -0
  41. package/dist/commands/facts.d.ts +7 -0
  42. package/dist/commands/facts.d.ts.map +1 -0
  43. package/dist/commands/facts.js +298 -0
  44. package/dist/commands/facts.js.map +1 -0
  45. package/dist/commands/graphrag.d.ts +7 -0
  46. package/dist/commands/graphrag.d.ts.map +1 -0
  47. package/dist/commands/graphrag.js +139 -0
  48. package/dist/commands/graphrag.js.map +1 -0
  49. package/dist/commands/memories.d.ts +7 -0
  50. package/dist/commands/memories.d.ts.map +1 -0
  51. package/dist/commands/memories.js +304 -0
  52. package/dist/commands/memories.js.map +1 -0
  53. package/dist/commands/patterns.d.ts +7 -0
  54. package/dist/commands/patterns.d.ts.map +1 -0
  55. package/dist/commands/patterns.js +227 -0
  56. package/dist/commands/patterns.js.map +1 -0
  57. package/dist/commands/system.d.ts +7 -0
  58. package/dist/commands/system.d.ts.map +1 -0
  59. package/dist/commands/system.js +97 -0
  60. package/dist/commands/system.js.map +1 -0
  61. package/dist/commands/topics.d.ts +7 -0
  62. package/dist/commands/topics.d.ts.map +1 -0
  63. package/dist/commands/topics.js +314 -0
  64. package/dist/commands/topics.js.map +1 -0
  65. package/dist/index.d.ts +3 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +44 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/lib/api-client.d.ts +29 -0
  70. package/dist/lib/api-client.d.ts.map +1 -0
  71. package/dist/lib/api-client.js +64 -0
  72. package/dist/lib/api-client.js.map +1 -0
  73. package/dist/lib/auth.d.ts +10 -0
  74. package/dist/lib/auth.d.ts.map +1 -0
  75. package/dist/lib/auth.js +47 -0
  76. package/dist/lib/auth.js.map +1 -0
  77. package/dist/lib/config.d.ts +19 -0
  78. package/dist/lib/config.d.ts.map +1 -0
  79. package/dist/lib/config.js +59 -0
  80. package/dist/lib/config.js.map +1 -0
  81. package/dist/lib/errors.d.ts +7 -0
  82. package/dist/lib/errors.d.ts.map +1 -0
  83. package/dist/lib/errors.js +133 -0
  84. package/dist/lib/errors.js.map +1 -0
  85. package/dist/lib/formatters.d.ts +12 -0
  86. package/dist/lib/formatters.d.ts.map +1 -0
  87. package/dist/lib/formatters.js +103 -0
  88. package/dist/lib/formatters.js.map +1 -0
  89. package/dist/lib/spinner.d.ts +54 -0
  90. package/dist/lib/spinner.d.ts.map +1 -0
  91. package/dist/lib/spinner.js +108 -0
  92. package/dist/lib/spinner.js.map +1 -0
  93. package/dist/lib/validators.d.ts +92 -0
  94. package/dist/lib/validators.d.ts.map +1 -0
  95. package/dist/lib/validators.js +257 -0
  96. package/dist/lib/validators.js.map +1 -0
  97. package/dist/types/index.d.ts +13 -0
  98. package/dist/types/index.d.ts.map +1 -0
  99. package/dist/types/index.js +3 -0
  100. package/dist/types/index.js.map +1 -0
  101. package/docs/README.md +219 -0
  102. package/docs/code-generation-strategy.md +560 -0
  103. package/docs/prd.md +748 -0
  104. package/docs/sync-strategy.md +533 -0
  105. package/jest.config.js +30 -0
  106. package/package.json +67 -0
  107. package/scripts/install-deps.sh +38 -0
  108. package/src/commands/apikeys.ts +144 -0
  109. package/src/commands/artifacts.ts +296 -0
  110. package/src/commands/auth.ts +122 -0
  111. package/src/commands/communities.ts +153 -0
  112. package/src/commands/config.ts +144 -0
  113. package/src/commands/conversations.ts +176 -0
  114. package/src/commands/facts.ts +320 -0
  115. package/src/commands/graphrag.ts +149 -0
  116. package/src/commands/memories.ts +332 -0
  117. package/src/commands/patterns.ts +251 -0
  118. package/src/commands/system.ts +102 -0
  119. package/src/commands/topics.ts +354 -0
  120. package/src/index.ts +43 -0
  121. package/src/lib/api-client.ts +68 -0
  122. package/src/lib/auth.ts +42 -0
  123. package/src/lib/config.ts +68 -0
  124. package/src/lib/errors.ts +143 -0
  125. package/src/lib/formatters.ts +123 -0
  126. package/src/lib/spinner.ts +113 -0
  127. package/src/lib/validators.ts +302 -0
  128. package/src/types/index.ts +17 -0
  129. package/tests/__mocks__/chalk.ts +16 -0
  130. package/tests/__mocks__/cli-table3.ts +37 -0
  131. package/tests/__mocks__/configstore.ts +38 -0
  132. package/tests/commands/apikeys.test.ts +179 -0
  133. package/tests/commands/artifacts.test.ts +194 -0
  134. package/tests/commands/auth.test.ts +120 -0
  135. package/tests/commands/communities.test.ts +154 -0
  136. package/tests/commands/config.test.ts +154 -0
  137. package/tests/commands/conversations.test.ts +136 -0
  138. package/tests/commands/facts.test.ts +210 -0
  139. package/tests/commands/graphrag.test.ts +194 -0
  140. package/tests/commands/memories.test.ts +215 -0
  141. package/tests/commands/patterns.test.ts +201 -0
  142. package/tests/commands/system.test.ts +172 -0
  143. package/tests/commands/topics.test.ts +274 -0
  144. package/tests/lib/auth.test.ts +77 -0
  145. package/tests/lib/config.test.ts +50 -0
  146. package/tests/lib/errors.test.ts +126 -0
  147. package/tests/lib/formatters.test.ts +87 -0
  148. package/tsconfig.json +20 -0
@@ -0,0 +1,144 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { listApiKeys, createApiKey, deleteApiKey } from '@memnexus-ai/mx-typescript-sdk';
6
+ import { getApiOptions } from '../lib/api-client';
7
+ import { handleError } from '../lib/errors';
8
+ import { printOutput } from '../lib/formatters';
9
+ import { OutputFormat } from '../types';
10
+
11
+ /**
12
+ * Register API key management commands
13
+ * @param program - Commander program instance
14
+ */
15
+ export function registerApikeyCommands(program: Command): void {
16
+ const apikeys = program.command('apikeys').description('Manage API keys');
17
+
18
+ /**
19
+ * mx apikeys list [options]
20
+ * List API keys (metadata only, never show actual keys)
21
+ */
22
+ apikeys
23
+ .command('list')
24
+ .description('List API keys')
25
+ .option('--page <number>', 'Page number', '0')
26
+ .option('--limit <number>', 'Results per page', '20')
27
+ .option('--format <format>', 'Output format (json|table|yaml)')
28
+ .action(async (options) => {
29
+ try {
30
+ const result = await listApiKeys({
31
+ ...getApiOptions(),
32
+ });
33
+
34
+ const apiKeys = (result.data as any)?.data || (result.data as any) || [];
35
+
36
+ if (options.format === 'table' || !options.format) {
37
+ const tableData = (Array.isArray(apiKeys) ? apiKeys : []).map((k: any) => ({
38
+ id: k.id,
39
+ label: k.label || k.name || 'N/A',
40
+ status: k.status || 'active',
41
+ createdAt: k.createdAt ? new Date(k.createdAt).toLocaleDateString() : 'N/A',
42
+ lastUsed: k.lastUsed ? new Date(k.lastUsed).toLocaleDateString() : 'Never',
43
+ }));
44
+
45
+ printOutput(tableData, options.format as OutputFormat, {
46
+ columns: ['id', 'label', 'status', 'createdAt', 'lastUsed'],
47
+ maxColumnWidth: 20,
48
+ });
49
+
50
+ console.log(chalk.gray(`\nTotal: ${tableData.length} API keys`));
51
+ } else {
52
+ printOutput(result.data, options.format as OutputFormat);
53
+ }
54
+ } catch (error) {
55
+ handleError(error);
56
+ }
57
+ });
58
+
59
+ // Note: There's no getApiKeyById endpoint in the API, only list
60
+ // Removed the 'get <id>' command as it's not supported by the API
61
+
62
+ /**
63
+ * mx apikeys create [options]
64
+ * Create a new API key (show value ONCE on creation)
65
+ */
66
+ apikeys
67
+ .command('create')
68
+ .description('Create API key')
69
+ .option('--label <label>', 'Key label (optional)')
70
+ .option('--expires-at <date>', 'Expiration date (ISO 8601 format, optional)')
71
+ .action(async (options) => {
72
+ try {
73
+ const spinner = ora('Creating API key...').start();
74
+
75
+ const result = await createApiKey({
76
+ ...getApiOptions(),
77
+ body: {
78
+ label: options.label,
79
+ expiresAt: options.expiresAt,
80
+ },
81
+ });
82
+
83
+ spinner.succeed('API key created successfully');
84
+
85
+ const apiKey = result.data as any;
86
+ if (apiKey) {
87
+ console.log(chalk.green(`✓ Created API key`));
88
+ console.log(
89
+ chalk.yellow(
90
+ '\n⚠️ IMPORTANT: Save this key securely. You will not be able to see it again.\n'
91
+ )
92
+ );
93
+ console.log(chalk.cyan(`Key: ${apiKey.key || apiKey.value || apiKey.id}`));
94
+ console.log(chalk.gray(`\nMetadata:`));
95
+ const metadata = { ...apiKey };
96
+ delete metadata.key;
97
+ delete metadata.value;
98
+ printOutput(metadata, 'json');
99
+ }
100
+ } catch (error) {
101
+ handleError(error);
102
+ }
103
+ });
104
+
105
+ /**
106
+ * mx apikeys delete <id> [options]
107
+ * Delete an API key
108
+ */
109
+ apikeys
110
+ .command('delete <id>')
111
+ .description('Delete API key')
112
+ .option('--force', 'Skip confirmation')
113
+ .action(async (id: string, options) => {
114
+ try {
115
+ if (!options.force) {
116
+ const answer = await inquirer.prompt([
117
+ {
118
+ type: 'confirm',
119
+ name: 'confirm',
120
+ message: chalk.red(`Delete API key ${id}? This cannot be undone.`),
121
+ default: false,
122
+ },
123
+ ]);
124
+
125
+ if (!answer.confirm) {
126
+ console.log(chalk.gray('Delete cancelled'));
127
+ return;
128
+ }
129
+ }
130
+
131
+ const spinner = ora('Deleting API key...').start();
132
+
133
+ await deleteApiKey({
134
+ ...getApiOptions(),
135
+ path: { id },
136
+ });
137
+
138
+ spinner.succeed('API key deleted successfully');
139
+ console.log(chalk.green(`✓ Deleted API key: ${id}`));
140
+ } catch (error) {
141
+ handleError(error);
142
+ }
143
+ });
144
+ }
@@ -0,0 +1,296 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import {
6
+ listArtifacts,
7
+ getArtifactById,
8
+ createArtifact,
9
+ updateArtifact,
10
+ deleteArtifact,
11
+ } from '@memnexus-ai/mx-typescript-sdk';
12
+ import { getApiOptions } from '../lib/api-client';
13
+ import { handleError } from '../lib/errors';
14
+ import { printOutput } from '../lib/formatters';
15
+ import { OutputFormat } from '../types';
16
+
17
+ /**
18
+ * Register artifact management commands
19
+ * @param program - Commander program instance
20
+ */
21
+ export function registerArtifactsCommands(program: Command): void {
22
+ const artifacts = program.command('artifacts').description('Manage artifacts');
23
+
24
+ /**
25
+ * mx artifacts list [options]
26
+ * List artifacts with pagination
27
+ */
28
+ artifacts
29
+ .command('list')
30
+ .description('List artifacts')
31
+ .option('--page <number>', 'Page number', '0')
32
+ .option('--limit <number>', 'Results per page', '20')
33
+ .option('--context-id <id>', 'Filter by context')
34
+ .option('--type <type>', 'Filter by artifact type')
35
+ .option('--format <format>', 'Output format (json|table|yaml)')
36
+ .action(async (options) => {
37
+ try {
38
+ const page = parseInt(options.page, 10);
39
+ const limit = parseInt(options.limit, 10);
40
+ const offset = page * limit;
41
+
42
+ const result = await listArtifacts({
43
+ ...getApiOptions(),
44
+ query: {
45
+ limit,
46
+ offset,
47
+ type: options.type,
48
+ },
49
+ });
50
+
51
+ const responseData = result.data as any;
52
+
53
+ if (options.format === 'table' || !options.format) {
54
+ const tableData = (responseData?.data || []).map((a: any) => ({
55
+ id: a.id,
56
+ name: a.name || 'N/A',
57
+ type: a.type || 'N/A',
58
+ size: a.size || 0,
59
+ createdAt: new Date(a.createdAt).toLocaleDateString(),
60
+ }));
61
+
62
+ printOutput(tableData, options.format as OutputFormat, {
63
+ columns: ['id', 'name', 'type', 'size', 'createdAt'],
64
+ maxColumnWidth: 20,
65
+ });
66
+
67
+ const pagination = responseData?.pagination;
68
+ if (pagination) {
69
+ console.log(
70
+ chalk.gray(
71
+ `\nShowing ${pagination.offset + 1}-${
72
+ pagination.offset + (responseData?.data?.length || 0)
73
+ } (Page ${page + 1})`
74
+ )
75
+ );
76
+ }
77
+ } else {
78
+ printOutput(responseData, options.format as OutputFormat);
79
+ }
80
+ } catch (error) {
81
+ handleError(error);
82
+ }
83
+ });
84
+
85
+ /**
86
+ * mx artifacts get <id>
87
+ * Get artifact details
88
+ */
89
+ artifacts
90
+ .command('get <id>')
91
+ .description('Get artifact details')
92
+ .option('--format <format>', 'Output format (json|table|yaml)')
93
+ .action(async (id: string, options) => {
94
+ try {
95
+ const result = await getArtifactById({
96
+ ...getApiOptions(),
97
+ path: { id },
98
+ });
99
+
100
+ printOutput(result.data, options.format as OutputFormat);
101
+ } catch (error) {
102
+ handleError(error);
103
+ }
104
+ });
105
+
106
+ /**
107
+ * mx artifacts create [options]
108
+ * Create a new artifact
109
+ */
110
+ artifacts
111
+ .command('create')
112
+ .description('Create artifact')
113
+ .option('--name <name>', 'Artifact name')
114
+ .option('--type <type>', 'Artifact type')
115
+ .option('--context-id <id>', 'Context ID')
116
+ .option('--data <json>', 'Artifact data as JSON')
117
+ .option('--metadata <json>', 'Artifact metadata as JSON')
118
+ .option('--interactive', 'Interactive mode')
119
+ .option('--format <format>', 'Output format (json|table|yaml)')
120
+ .action(async (options) => {
121
+ try {
122
+ let payload: any = {};
123
+
124
+ if (options.interactive || !options.name || !options.type) {
125
+ // Interactive mode
126
+ const answers = await inquirer.prompt([
127
+ {
128
+ type: 'input',
129
+ name: 'name',
130
+ message: 'Artifact name:',
131
+ default: options.name,
132
+ validate: (input: string) => input.trim().length > 0 || 'Name is required',
133
+ },
134
+ {
135
+ type: 'input',
136
+ name: 'type',
137
+ message: 'Artifact type:',
138
+ default: options.type,
139
+ validate: (input: string) => input.trim().length > 0 || 'Type is required',
140
+ },
141
+ {
142
+ type: 'input',
143
+ name: 'contextId',
144
+ message: 'Context ID (optional):',
145
+ default: options.contextId,
146
+ },
147
+ {
148
+ type: 'input',
149
+ name: 'data',
150
+ message: 'Artifact data as JSON (optional):',
151
+ default: options.data,
152
+ validate: (input: string) => {
153
+ if (!input || input.trim().length === 0) return true;
154
+ try {
155
+ JSON.parse(input);
156
+ return true;
157
+ } catch (e) {
158
+ return 'Invalid JSON format';
159
+ }
160
+ },
161
+ },
162
+ {
163
+ type: 'input',
164
+ name: 'metadata',
165
+ message: 'Artifact metadata as JSON (optional):',
166
+ default: options.metadata,
167
+ validate: (input: string) => {
168
+ if (!input || input.trim().length === 0) return true;
169
+ try {
170
+ JSON.parse(input);
171
+ return true;
172
+ } catch (e) {
173
+ return 'Invalid JSON format';
174
+ }
175
+ },
176
+ },
177
+ ]);
178
+
179
+ payload = {
180
+ name: answers.name,
181
+ type: answers.type,
182
+ contextId: answers.contextId || undefined,
183
+ };
184
+
185
+ if (answers.data && answers.data.trim().length > 0) {
186
+ payload.data = JSON.parse(answers.data);
187
+ }
188
+ if (answers.metadata && answers.metadata.trim().length > 0) {
189
+ payload.metadata = JSON.parse(answers.metadata);
190
+ }
191
+ } else {
192
+ // Direct mode
193
+ payload = {
194
+ name: options.name,
195
+ type: options.type,
196
+ contextId: options.contextId,
197
+ };
198
+
199
+ if (options.data) payload.data = JSON.parse(options.data);
200
+ if (options.metadata) payload.metadata = JSON.parse(options.metadata);
201
+ }
202
+
203
+ const spinner = ora('Creating artifact...').start();
204
+
205
+ const result = await createArtifact({
206
+ ...getApiOptions(),
207
+ body: payload,
208
+ });
209
+
210
+ spinner.succeed('Artifact created successfully');
211
+
212
+ const artifactData = result.data as any;
213
+ console.log(chalk.green(`✓ Created artifact: ${artifactData?.id}`));
214
+ printOutput(artifactData, options.format as OutputFormat);
215
+ } catch (error) {
216
+ handleError(error);
217
+ }
218
+ });
219
+
220
+ /**
221
+ * mx artifacts update <id> [options]
222
+ * Update an artifact
223
+ */
224
+ artifacts
225
+ .command('update <id>')
226
+ .description('Update artifact')
227
+ .option('--name <name>', 'Updated name')
228
+ .option('--data <json>', 'Updated data')
229
+ .option('--metadata <json>', 'Updated metadata')
230
+ .option('--format <format>', 'Output format (json|table|yaml)')
231
+ .action(async (id: string, options) => {
232
+ try {
233
+ if (!options.name && !options.data && !options.metadata) {
234
+ console.log(chalk.red('Error: At least one option is required'));
235
+ return;
236
+ }
237
+
238
+ const updates: any = {};
239
+
240
+ if (options.name) updates.name = options.name;
241
+ if (options.data) updates.data = JSON.parse(options.data);
242
+ if (options.metadata) updates.metadata = JSON.parse(options.metadata);
243
+
244
+ const result = await updateArtifact({
245
+ ...getApiOptions(),
246
+ path: { id },
247
+ body: updates,
248
+ });
249
+
250
+ console.log(chalk.green('✓ Artifact updated successfully'));
251
+ printOutput(result.data, options.format as OutputFormat);
252
+ } catch (error) {
253
+ handleError(error);
254
+ }
255
+ });
256
+
257
+ /**
258
+ * mx artifacts delete <id> [options]
259
+ * Delete an artifact
260
+ */
261
+ artifacts
262
+ .command('delete <id>')
263
+ .description('Delete artifact')
264
+ .option('--force', 'Skip confirmation')
265
+ .action(async (id: string, options) => {
266
+ try {
267
+ if (!options.force) {
268
+ const answer = await inquirer.prompt([
269
+ {
270
+ type: 'confirm',
271
+ name: 'confirm',
272
+ message: `Delete artifact ${id}?`,
273
+ default: false,
274
+ },
275
+ ]);
276
+
277
+ if (!answer.confirm) {
278
+ console.log(chalk.gray('Delete cancelled'));
279
+ return;
280
+ }
281
+ }
282
+
283
+ const spinner = ora('Deleting artifact...').start();
284
+
285
+ await deleteArtifact({
286
+ ...getApiOptions(),
287
+ path: { id },
288
+ });
289
+
290
+ spinner.succeed('Artifact deleted successfully');
291
+ console.log(chalk.green(`✓ Deleted artifact: ${id}`));
292
+ } catch (error) {
293
+ handleError(error);
294
+ }
295
+ });
296
+ }
@@ -0,0 +1,122 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import chalk from 'chalk';
4
+ import Table from 'cli-table3';
5
+ import { storeApiKey, removeApiKey, getApiKeyStatus } from '../lib/auth';
6
+ import { config } from '../lib/config';
7
+ import { handleError } from '../lib/errors';
8
+
9
+ /**
10
+ * Register authentication commands
11
+ * @param program - Commander program instance
12
+ */
13
+ export function registerAuthCommands(program: Command): void {
14
+ const auth = program.command('auth').description('Manage authentication and API keys');
15
+
16
+ /**
17
+ * mx auth login
18
+ * Configure API key for authentication
19
+ */
20
+ auth
21
+ .command('login')
22
+ .description('Configure API key for authentication')
23
+ .option('--api-key <key>', 'API key to store')
24
+ .option('--interactive', 'Prompt for API key (mask input)')
25
+ .action(async (options) => {
26
+ try {
27
+ let apiKey: string;
28
+
29
+ if (options.apiKey) {
30
+ // Direct API key provided
31
+ apiKey = options.apiKey;
32
+ } else {
33
+ // Interactive mode - prompt for API key
34
+ const answers = await inquirer.prompt([
35
+ {
36
+ type: 'password',
37
+ name: 'apiKey',
38
+ message: 'Enter your API key:',
39
+ mask: '*',
40
+ validate: (input: string) => {
41
+ if (!input || input.trim().length === 0) {
42
+ return 'API key cannot be empty';
43
+ }
44
+ if (input.length < 10) {
45
+ return 'API key seems too short. Please check and try again.';
46
+ }
47
+ return true;
48
+ },
49
+ },
50
+ ]);
51
+ apiKey = answers.apiKey;
52
+ }
53
+
54
+ // Store the API key
55
+ storeApiKey(apiKey);
56
+ } catch (error) {
57
+ handleError(error);
58
+ }
59
+ });
60
+
61
+ /**
62
+ * mx auth logout
63
+ * Remove stored credentials
64
+ */
65
+ auth
66
+ .command('logout')
67
+ .description('Remove stored credentials')
68
+ .action(async () => {
69
+ try {
70
+ removeApiKey();
71
+ } catch (error) {
72
+ handleError(error);
73
+ }
74
+ });
75
+
76
+ /**
77
+ * mx auth status
78
+ * Show current authentication status
79
+ */
80
+ auth
81
+ .command('status')
82
+ .description('Show current authentication status')
83
+ .action(async () => {
84
+ try {
85
+ const status = getApiKeyStatus();
86
+ const apiUrl = config.getApiUrl();
87
+ const outputFormat = config.getFormat();
88
+
89
+ const table = new Table({
90
+ head: [chalk.cyan('Property'), chalk.cyan('Value')],
91
+ colWidths: [30, 70],
92
+ wordWrap: true,
93
+ });
94
+
95
+ // Authentication status
96
+ let authStatus: string;
97
+ if (status.configured) {
98
+ if (status.source === 'env') {
99
+ authStatus = chalk.green('✓ Configured (via environment)');
100
+ } else {
101
+ authStatus = chalk.green('✓ Configured (via config file)');
102
+ }
103
+ } else {
104
+ authStatus = chalk.red('✗ Not configured');
105
+ }
106
+
107
+ table.push(
108
+ ['Authentication', authStatus],
109
+ ['API URL', apiUrl],
110
+ ['Output Format', outputFormat]
111
+ );
112
+
113
+ console.log(table.toString());
114
+
115
+ if (!status.configured) {
116
+ console.log(chalk.yellow('\nTo configure authentication, run: mx auth login'));
117
+ }
118
+ } catch (error) {
119
+ handleError(error);
120
+ }
121
+ });
122
+ }