@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,143 @@
1
+ import chalk from 'chalk';
2
+
3
+ export class CLIError extends Error {
4
+ constructor(
5
+ message: string,
6
+ public exitCode: number = 1,
7
+ public details?: unknown
8
+ ) {
9
+ super(message);
10
+ this.name = 'CLIError';
11
+ }
12
+ }
13
+
14
+ export function handleError(error: unknown): never {
15
+ if (error instanceof CLIError) {
16
+ console.error(chalk.red(`Error: ${error.message}`));
17
+ if (error.details && process.env.DEBUG) {
18
+ console.error(chalk.gray(JSON.stringify(error.details, null, 2)));
19
+ }
20
+ process.exit(error.exitCode);
21
+ }
22
+
23
+ if (isAxiosError(error)) {
24
+ handleApiError(error);
25
+ }
26
+
27
+ // Unknown error
28
+ console.error(chalk.red('An unexpected error occurred'));
29
+ if (error instanceof Error) {
30
+ console.error(chalk.gray(error.message));
31
+ if (process.env.DEBUG && error.stack) {
32
+ console.error(chalk.gray(error.stack));
33
+ }
34
+ }
35
+ process.exit(1);
36
+ }
37
+
38
+ interface AxiosError {
39
+ isAxiosError: boolean;
40
+ response?: {
41
+ status: number;
42
+ statusText: string;
43
+ data: { error?: string; message?: string };
44
+ };
45
+ }
46
+
47
+ function isAxiosError(error: unknown): error is AxiosError {
48
+ return (error as AxiosError).isAxiosError === true;
49
+ }
50
+
51
+ function handleApiError(error: AxiosError): never {
52
+ if (!error.response) {
53
+ console.error(chalk.red('❌ Network Error: Unable to connect to API'));
54
+ console.log(chalk.yellow('\n💡 Troubleshooting steps:'));
55
+ console.log(chalk.yellow(' 1. Check your internet connection'));
56
+ console.log(chalk.yellow(' 2. Verify API URL: mx config get apiUrl'));
57
+ console.log(chalk.yellow(' 3. Test API connectivity: curl <api-url>/health'));
58
+ console.log(chalk.yellow(' 4. Update API URL if needed: mx config set apiUrl <url>'));
59
+ process.exit(4);
60
+ }
61
+
62
+ const status = error.response.status;
63
+ const data = error.response.data;
64
+
65
+ // Each case calls process.exit() which never returns, so break statements are unnecessary
66
+ // eslint-disable-next-line no-fallthrough
67
+ switch (status) {
68
+ case 401:
69
+ console.error(chalk.red('❌ Authentication Error: Invalid or missing API key'));
70
+ console.log(chalk.yellow('\n💡 To fix this:'));
71
+ console.log(chalk.yellow(' 1. Check auth status: mx auth status'));
72
+ console.log(
73
+ chalk.yellow(' 2. Login with your API key: mx auth login --api-key <your-key>')
74
+ );
75
+ console.log(chalk.yellow(' 3. Or use interactive mode: mx auth login --interactive'));
76
+ process.exit(3);
77
+ // eslint-disable-next-line no-fallthrough
78
+ case 403:
79
+ console.error(chalk.red('❌ Authorization Error: Insufficient permissions'));
80
+ console.log(chalk.yellow('\n💡 To fix this:'));
81
+ console.log(chalk.yellow(' 1. Verify your API key has the required permissions'));
82
+ console.log(chalk.yellow(' 2. Contact your administrator if you need additional access'));
83
+ console.log(chalk.yellow(' 3. Check your current auth status: mx auth status'));
84
+ process.exit(3);
85
+ // eslint-disable-next-line no-fallthrough
86
+ case 404:
87
+ console.error(chalk.red('❌ Not Found: Resource does not exist'));
88
+ console.log(chalk.yellow('\n💡 To fix this:'));
89
+ console.log(chalk.yellow(' 1. Verify the resource ID is correct'));
90
+ console.log(chalk.yellow(' 2. List available resources to find the correct ID'));
91
+ console.log(chalk.yellow(' 3. Check if the resource was deleted'));
92
+ process.exit(1);
93
+ // eslint-disable-next-line no-fallthrough
94
+ case 422:
95
+ console.error(chalk.red('❌ Validation Error: Invalid input'));
96
+ if (data.message) {
97
+ console.error(chalk.yellow(`\n${data.message}`));
98
+ }
99
+ console.log(chalk.yellow('\n💡 To fix this:'));
100
+ console.log(chalk.yellow(' 1. Check the command syntax: mx <command> --help'));
101
+ console.log(chalk.yellow(' 2. Verify all required fields are provided'));
102
+ console.log(chalk.yellow(' 3. Use --interactive mode for guided input'));
103
+ process.exit(2);
104
+ // eslint-disable-next-line no-fallthrough
105
+ case 429:
106
+ console.error(chalk.red('❌ Rate Limit Exceeded: Too many requests'));
107
+ console.log(chalk.yellow('\n💡 To fix this:'));
108
+ console.log(chalk.yellow(' 1. Wait a few moments before retrying'));
109
+ console.log(chalk.yellow(' 2. Reduce the frequency of your requests'));
110
+ console.log(chalk.yellow(' 3. Contact support if you need a higher rate limit'));
111
+ process.exit(5);
112
+ // eslint-disable-next-line no-fallthrough
113
+ case 500:
114
+ console.error(chalk.red('❌ Server Error: Internal server error'));
115
+ console.log(chalk.yellow('\n💡 To fix this:'));
116
+ console.log(chalk.yellow(' 1. Try again in a few moments'));
117
+ console.log(chalk.yellow(' 2. Check API status: mx system health'));
118
+ console.log(chalk.yellow(' 3. Contact support if the issue persists'));
119
+ if (data.error || data.message) {
120
+ console.error(chalk.gray(`\nDetails: ${data.error || data.message}`));
121
+ }
122
+ process.exit(5);
123
+ // eslint-disable-next-line no-fallthrough
124
+ case 503:
125
+ console.error(chalk.red('❌ Service Unavailable: API is temporarily unavailable'));
126
+ console.log(chalk.yellow('\n💡 To fix this:'));
127
+ console.log(chalk.yellow(' 1. The service may be undergoing maintenance'));
128
+ console.log(chalk.yellow(' 2. Try again in a few minutes'));
129
+ console.log(chalk.yellow(' 3. Check API status: mx system health'));
130
+ process.exit(5);
131
+ // eslint-disable-next-line no-fallthrough
132
+ default:
133
+ console.error(chalk.red(`❌ API Error: ${status} ${error.response.statusText}`));
134
+ if (data.error || data.message) {
135
+ console.error(chalk.yellow(`\n${data.error || data.message}`));
136
+ }
137
+ console.log(chalk.yellow('\n💡 For help:'));
138
+ console.log(chalk.yellow(' 1. Run with DEBUG=1 for more details'));
139
+ console.log(chalk.yellow(' 2. Check command syntax: mx <command> --help'));
140
+ console.log(chalk.yellow(' 3. Contact support if the issue persists'));
141
+ process.exit(5);
142
+ }
143
+ }
@@ -0,0 +1,123 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import yaml from 'js-yaml';
4
+ import { config } from './config';
5
+ import { OutputFormat } from '../types';
6
+
7
+ export function formatOutput(
8
+ data: unknown,
9
+ format?: OutputFormat,
10
+ tableOptions?: TableOptions
11
+ ): string {
12
+ const outputFormat = format || config.getFormat();
13
+
14
+ switch (outputFormat) {
15
+ case 'json':
16
+ return formatJson(data);
17
+ case 'yaml':
18
+ return formatYaml(data);
19
+ case 'table':
20
+ return formatTable(data, tableOptions);
21
+ default:
22
+ return formatJson(data);
23
+ }
24
+ }
25
+
26
+ export function formatJson(data: unknown): string {
27
+ return JSON.stringify(data, null, 2);
28
+ }
29
+
30
+ export function formatYaml(data: unknown): string {
31
+ return yaml.dump(data, { indent: 2, lineWidth: 120 });
32
+ }
33
+
34
+ interface TableOptions {
35
+ columns?: string[];
36
+ maxColumnWidth?: number;
37
+ }
38
+
39
+ export function formatTable(data: unknown, options?: TableOptions): string {
40
+ if (!data) {
41
+ return 'No data';
42
+ }
43
+
44
+ // Single object
45
+ if (typeof data === 'object' && !Array.isArray(data)) {
46
+ return formatObjectAsTable(data as Record<string, unknown>);
47
+ }
48
+
49
+ // Array of objects
50
+ if (Array.isArray(data)) {
51
+ if (data.length === 0) {
52
+ return 'No results';
53
+ }
54
+ return formatArrayAsTable(data, options);
55
+ }
56
+
57
+ // Primitive value
58
+ return String(data);
59
+ }
60
+
61
+ function formatObjectAsTable(obj: Record<string, unknown>): string {
62
+ const table = new Table({
63
+ head: [chalk.cyan('Property'), chalk.cyan('Value')],
64
+ colWidths: [30, 70],
65
+ wordWrap: true,
66
+ });
67
+
68
+ Object.entries(obj).forEach(([key, value]) => {
69
+ table.push([key, formatValue(value)]);
70
+ });
71
+
72
+ return table.toString();
73
+ }
74
+
75
+ function formatArrayAsTable(data: unknown[], options?: TableOptions): string {
76
+ const firstItem = data[0];
77
+ if (typeof firstItem !== 'object' || firstItem === null) {
78
+ // Array of primitives
79
+ const table = new Table({ head: [chalk.cyan('Value')] });
80
+ data.forEach((item) => table.push([formatValue(item)]));
81
+ return table.toString();
82
+ }
83
+
84
+ // Array of objects - use keys from first object
85
+ const keys = options?.columns || Object.keys(firstItem as Record<string, unknown>);
86
+ const table = new Table({
87
+ head: keys.map((k) => chalk.cyan(k)),
88
+ wordWrap: true,
89
+ colWidths: keys.map(() => options?.maxColumnWidth || 30),
90
+ });
91
+
92
+ data.forEach((item) => {
93
+ const row = keys.map((key) => {
94
+ const value = (item as Record<string, unknown>)[key];
95
+ return formatValue(value);
96
+ });
97
+ table.push(row);
98
+ });
99
+
100
+ return table.toString();
101
+ }
102
+
103
+ function formatValue(value: unknown): string {
104
+ if (value === null || value === undefined) {
105
+ return chalk.gray('null');
106
+ }
107
+ if (typeof value === 'boolean') {
108
+ return value ? chalk.green('true') : chalk.red('false');
109
+ }
110
+ if (typeof value === 'object') {
111
+ return JSON.stringify(value);
112
+ }
113
+ return String(value);
114
+ }
115
+
116
+ export function printOutput(
117
+ data: unknown,
118
+ format?: OutputFormat,
119
+ tableOptions?: TableOptions
120
+ ): void {
121
+ const output = formatOutput(data, format, tableOptions);
122
+ console.log(output);
123
+ }
@@ -0,0 +1,113 @@
1
+ import ora, { Ora } from 'ora';
2
+ import { OutputFormat } from '../types';
3
+
4
+ /**
5
+ * Conditional spinner that only shows when output format is not JSON
6
+ * This prevents spinners from interfering with JSON output that may be piped to other tools
7
+ */
8
+ export class ConditionalSpinner {
9
+ private spinner: Ora | null = null;
10
+ private enabled: boolean;
11
+
12
+ constructor(text: string, format?: OutputFormat | string) {
13
+ // Disable spinner for JSON output format
14
+ this.enabled = format !== 'json';
15
+
16
+ if (this.enabled) {
17
+ this.spinner = ora(text);
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Start the spinner
23
+ */
24
+ start(): this {
25
+ if (this.enabled && this.spinner) {
26
+ this.spinner.start();
27
+ }
28
+ return this;
29
+ }
30
+
31
+ /**
32
+ * Stop the spinner and show success message
33
+ */
34
+ succeed(text?: string): this {
35
+ if (this.enabled && this.spinner) {
36
+ this.spinner.succeed(text);
37
+ }
38
+ return this;
39
+ }
40
+
41
+ /**
42
+ * Stop the spinner and show failure message
43
+ */
44
+ fail(text?: string): this {
45
+ if (this.enabled && this.spinner) {
46
+ this.spinner.fail(text);
47
+ }
48
+ return this;
49
+ }
50
+
51
+ /**
52
+ * Stop the spinner and show warning message
53
+ */
54
+ warn(text?: string): this {
55
+ if (this.enabled && this.spinner) {
56
+ this.spinner.warn(text);
57
+ }
58
+ return this;
59
+ }
60
+
61
+ /**
62
+ * Stop the spinner and show info message
63
+ */
64
+ info(text?: string): this {
65
+ if (this.enabled && this.spinner) {
66
+ this.spinner.info(text);
67
+ }
68
+ return this;
69
+ }
70
+
71
+ /**
72
+ * Stop the spinner
73
+ */
74
+ stop(): this {
75
+ if (this.enabled && this.spinner) {
76
+ this.spinner.stop();
77
+ }
78
+ return this;
79
+ }
80
+
81
+ /**
82
+ * Update spinner text
83
+ */
84
+ set text(value: string) {
85
+ if (this.enabled && this.spinner) {
86
+ this.spinner.text = value;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Get spinner text
92
+ */
93
+ get text(): string {
94
+ return this.enabled && this.spinner ? this.spinner.text : '';
95
+ }
96
+
97
+ /**
98
+ * Check if spinner is currently spinning
99
+ */
100
+ get isSpinning(): boolean {
101
+ return this.enabled && this.spinner ? this.spinner.isSpinning : false;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Create a conditional spinner that respects output format
107
+ * @param text - Initial spinner text
108
+ * @param format - Output format (spinner disabled for 'json')
109
+ * @returns ConditionalSpinner instance
110
+ */
111
+ export function createSpinner(text: string, format?: OutputFormat | string): ConditionalSpinner {
112
+ return new ConditionalSpinner(text, format);
113
+ }
@@ -0,0 +1,302 @@
1
+ import chalk from 'chalk';
2
+
3
+ /**
4
+ * Validation result type
5
+ */
6
+ export interface ValidationResult {
7
+ valid: boolean;
8
+ error?: string;
9
+ }
10
+
11
+ /**
12
+ * Validate URL format
13
+ * @param url - URL string to validate
14
+ * @returns Validation result with helpful error message
15
+ */
16
+ export function validateUrl(url: string): ValidationResult {
17
+ if (!url || url.trim().length === 0) {
18
+ return {
19
+ valid: false,
20
+ error: 'URL cannot be empty',
21
+ };
22
+ }
23
+
24
+ try {
25
+ const urlObj = new URL(url);
26
+
27
+ // Check for valid protocol
28
+ if (!['http:', 'https:'].includes(urlObj.protocol)) {
29
+ return {
30
+ valid: false,
31
+ error: `Invalid protocol "${urlObj.protocol}". Must be http: or https:\nExample: https://api.memnexus.ai`,
32
+ };
33
+ }
34
+
35
+ return { valid: true };
36
+ } catch (error) {
37
+ return {
38
+ valid: false,
39
+ error: `Invalid URL format\nExample: https://api.memnexus.ai\nProvided: ${url}`,
40
+ };
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Validate number within range
46
+ * @param value - Value to validate
47
+ * @param min - Minimum value (inclusive)
48
+ * @param max - Maximum value (inclusive)
49
+ * @param fieldName - Name of the field for error messages
50
+ * @returns Validation result with helpful error message
51
+ */
52
+ export function validateNumber(
53
+ value: any,
54
+ min?: number,
55
+ max?: number,
56
+ fieldName: string = 'Value'
57
+ ): ValidationResult {
58
+ const num = typeof value === 'string' ? parseFloat(value) : value;
59
+
60
+ if (isNaN(num)) {
61
+ return {
62
+ valid: false,
63
+ error: `${fieldName} must be a valid number\nProvided: ${value}`,
64
+ };
65
+ }
66
+
67
+ if (min !== undefined && num < min) {
68
+ return {
69
+ valid: false,
70
+ error: `${fieldName} must be at least ${min}\nProvided: ${num}`,
71
+ };
72
+ }
73
+
74
+ if (max !== undefined && num > max) {
75
+ return {
76
+ valid: false,
77
+ error: `${fieldName} must be at most ${max}\nProvided: ${num}`,
78
+ };
79
+ }
80
+
81
+ return { valid: true };
82
+ }
83
+
84
+ /**
85
+ * Validate integer within range
86
+ * @param value - Value to validate
87
+ * @param min - Minimum value (inclusive)
88
+ * @param max - Maximum value (inclusive)
89
+ * @param fieldName - Name of the field for error messages
90
+ * @returns Validation result with helpful error message
91
+ */
92
+ export function validateInteger(
93
+ value: any,
94
+ min?: number,
95
+ max?: number,
96
+ fieldName: string = 'Value'
97
+ ): ValidationResult {
98
+ const num = typeof value === 'string' ? parseInt(value, 10) : value;
99
+
100
+ if (isNaN(num) || !Number.isInteger(num)) {
101
+ return {
102
+ valid: false,
103
+ error: `${fieldName} must be a valid integer\nProvided: ${value}`,
104
+ };
105
+ }
106
+
107
+ if (min !== undefined && num < min) {
108
+ return {
109
+ valid: false,
110
+ error: `${fieldName} must be at least ${min}\nProvided: ${num}`,
111
+ };
112
+ }
113
+
114
+ if (max !== undefined && num > max) {
115
+ return {
116
+ valid: false,
117
+ error: `${fieldName} must be at most ${max}\nProvided: ${num}`,
118
+ };
119
+ }
120
+
121
+ return { valid: true };
122
+ }
123
+
124
+ /**
125
+ * Validate confidence score (0-1)
126
+ * @param value - Confidence score to validate
127
+ * @returns Validation result with helpful error message
128
+ */
129
+ export function validateConfidence(value: any): ValidationResult {
130
+ const result = validateNumber(value, 0, 1, 'Confidence score');
131
+
132
+ if (!result.valid) {
133
+ return {
134
+ valid: false,
135
+ error: `${result.error}\nConfidence must be between 0 and 1 (e.g., 0.85 for 85% confidence)`,
136
+ };
137
+ }
138
+
139
+ return { valid: true };
140
+ }
141
+
142
+ /**
143
+ * Validate JSON string
144
+ * @param jsonString - JSON string to validate
145
+ * @param fieldName - Name of the field for error messages
146
+ * @returns Validation result with helpful error message and parsed object
147
+ */
148
+ export function validateJson(
149
+ jsonString: string,
150
+ fieldName: string = 'JSON'
151
+ ): ValidationResult & { parsed?: any } {
152
+ if (!jsonString || jsonString.trim().length === 0) {
153
+ return {
154
+ valid: false,
155
+ error: `${fieldName} cannot be empty`,
156
+ };
157
+ }
158
+
159
+ try {
160
+ const parsed = JSON.parse(jsonString);
161
+ return { valid: true, parsed };
162
+ } catch (error) {
163
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
164
+ return {
165
+ valid: false,
166
+ error: `Invalid ${fieldName} format: ${errorMessage}\nExample: {"key": "value"}`,
167
+ };
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Validate output format
173
+ * @param format - Output format to validate
174
+ * @returns Validation result with helpful error message
175
+ */
176
+ export function validateOutputFormat(format: string): ValidationResult {
177
+ const validFormats = ['json', 'table', 'yaml'];
178
+
179
+ if (!format || format.trim().length === 0) {
180
+ return {
181
+ valid: false,
182
+ error: 'Output format cannot be empty',
183
+ };
184
+ }
185
+
186
+ const normalizedFormat = format.toLowerCase();
187
+
188
+ if (!validFormats.includes(normalizedFormat)) {
189
+ return {
190
+ valid: false,
191
+ error: `Invalid output format: ${format}\nValid formats: ${validFormats.join(', ')}`,
192
+ };
193
+ }
194
+
195
+ return { valid: true };
196
+ }
197
+
198
+ /**
199
+ * Validate page number
200
+ * @param page - Page number to validate
201
+ * @returns Validation result with helpful error message
202
+ */
203
+ export function validatePage(page: any): ValidationResult {
204
+ return validateInteger(page, 0, undefined, 'Page number');
205
+ }
206
+
207
+ /**
208
+ * Validate limit/page size
209
+ * @param limit - Limit to validate
210
+ * @returns Validation result with helpful error message
211
+ */
212
+ export function validateLimit(limit: any): ValidationResult {
213
+ return validateInteger(limit, 1, 1000, 'Limit');
214
+ }
215
+
216
+ /**
217
+ * Validate required string field
218
+ * @param value - Value to validate
219
+ * @param fieldName - Name of the field for error messages
220
+ * @param minLength - Minimum length (default: 1)
221
+ * @returns Validation result with helpful error message
222
+ */
223
+ export function validateRequiredString(
224
+ value: any,
225
+ fieldName: string,
226
+ minLength: number = 1
227
+ ): ValidationResult {
228
+ if (value === undefined || value === null) {
229
+ return {
230
+ valid: false,
231
+ error: `${fieldName} is required`,
232
+ };
233
+ }
234
+
235
+ const str = String(value).trim();
236
+
237
+ if (str.length === 0) {
238
+ return {
239
+ valid: false,
240
+ error: `${fieldName} cannot be empty`,
241
+ };
242
+ }
243
+
244
+ if (str.length < minLength) {
245
+ return {
246
+ valid: false,
247
+ error: `${fieldName} must be at least ${minLength} characters\nProvided: ${str.length} characters`,
248
+ };
249
+ }
250
+
251
+ return { valid: true };
252
+ }
253
+
254
+ /**
255
+ * Validate enum value
256
+ * @param value - Value to validate
257
+ * @param allowedValues - Array of allowed values
258
+ * @param fieldName - Name of the field for error messages
259
+ * @returns Validation result with helpful error message
260
+ */
261
+ export function validateEnum(
262
+ value: any,
263
+ allowedValues: string[],
264
+ fieldName: string
265
+ ): ValidationResult {
266
+ if (!value || value.trim().length === 0) {
267
+ return {
268
+ valid: false,
269
+ error: `${fieldName} is required\nAllowed values: ${allowedValues.join(', ')}`,
270
+ };
271
+ }
272
+
273
+ if (!allowedValues.includes(value)) {
274
+ return {
275
+ valid: false,
276
+ error: `Invalid ${fieldName}: ${value}\nAllowed values: ${allowedValues.join(', ')}`,
277
+ };
278
+ }
279
+
280
+ return { valid: true };
281
+ }
282
+
283
+ /**
284
+ * Print validation error with formatting
285
+ * @param error - Error message to print
286
+ */
287
+ export function printValidationError(error: string): void {
288
+ console.log(chalk.red('❌ Validation Error:'));
289
+ console.log(chalk.yellow(error));
290
+ }
291
+
292
+ /**
293
+ * Validate and throw if invalid
294
+ * @param result - Validation result
295
+ * @throws Error if validation fails
296
+ */
297
+ export function assertValid(result: ValidationResult): void {
298
+ if (!result.valid && result.error) {
299
+ printValidationError(result.error);
300
+ process.exit(1);
301
+ }
302
+ }
@@ -0,0 +1,17 @@
1
+ // Re-export types from SDK
2
+ export type * from '@memnexus-ai/mx-typescript-sdk';
3
+
4
+ // CLI-specific types
5
+ export interface CLIConfig {
6
+ apiUrl: string;
7
+ apiKey?: string;
8
+ defaultFormat: 'json' | 'table' | 'yaml';
9
+ defaultPageSize: number;
10
+ }
11
+
12
+ export interface CommandOptions {
13
+ format?: 'json' | 'table' | 'yaml';
14
+ verbose?: boolean;
15
+ }
16
+
17
+ export type OutputFormat = 'json' | 'table' | 'yaml';
@@ -0,0 +1,16 @@
1
+ // Mock chalk for Jest tests
2
+ const createChalk = (color: string) => (text: string) => text;
3
+
4
+ const chalk = {
5
+ red: createChalk('red'),
6
+ green: createChalk('green'),
7
+ yellow: createChalk('yellow'),
8
+ blue: createChalk('blue'),
9
+ cyan: createChalk('cyan'),
10
+ gray: createChalk('gray'),
11
+ grey: createChalk('grey'),
12
+ white: createChalk('white'),
13
+ black: createChalk('black'),
14
+ };
15
+
16
+ export default chalk;