@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.
- package/.env.example +13 -0
- package/.eslintrc.js +24 -0
- package/.github/ISSUE_TEMPLATE/phase-1-foundation.md +1078 -0
- package/.github/workflows/publish.yml +277 -0
- package/.github/workflows/test-app-token.yml +54 -0
- package/.npmrc.backup +3 -0
- package/.npmrc.example +6 -0
- package/.prettierignore +4 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +138 -0
- package/PLATFORM_TESTING.md +243 -0
- package/README.md +986 -0
- package/RELEASE.md +428 -0
- package/RELEASE_READINESS.md +253 -0
- package/USAGE.md +1373 -0
- package/bin/mx.js +2 -0
- package/dist/commands/apikeys.d.ts +7 -0
- package/dist/commands/apikeys.d.ts.map +1 -0
- package/dist/commands/apikeys.js +133 -0
- package/dist/commands/apikeys.js.map +1 -0
- package/dist/commands/artifacts.d.ts +7 -0
- package/dist/commands/artifacts.d.ts.map +1 -0
- package/dist/commands/artifacts.js +277 -0
- package/dist/commands/artifacts.js.map +1 -0
- package/dist/commands/auth.d.ts +7 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +119 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/communities.d.ts +7 -0
- package/dist/commands/communities.d.ts.map +1 -0
- package/dist/commands/communities.js +137 -0
- package/dist/commands/communities.js.map +1 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +138 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/conversations.d.ts +7 -0
- package/dist/commands/conversations.d.ts.map +1 -0
- package/dist/commands/conversations.js +160 -0
- package/dist/commands/conversations.js.map +1 -0
- package/dist/commands/facts.d.ts +7 -0
- package/dist/commands/facts.d.ts.map +1 -0
- package/dist/commands/facts.js +298 -0
- package/dist/commands/facts.js.map +1 -0
- package/dist/commands/graphrag.d.ts +7 -0
- package/dist/commands/graphrag.d.ts.map +1 -0
- package/dist/commands/graphrag.js +139 -0
- package/dist/commands/graphrag.js.map +1 -0
- package/dist/commands/memories.d.ts +7 -0
- package/dist/commands/memories.d.ts.map +1 -0
- package/dist/commands/memories.js +304 -0
- package/dist/commands/memories.js.map +1 -0
- package/dist/commands/patterns.d.ts +7 -0
- package/dist/commands/patterns.d.ts.map +1 -0
- package/dist/commands/patterns.js +227 -0
- package/dist/commands/patterns.js.map +1 -0
- package/dist/commands/system.d.ts +7 -0
- package/dist/commands/system.d.ts.map +1 -0
- package/dist/commands/system.js +97 -0
- package/dist/commands/system.js.map +1 -0
- package/dist/commands/topics.d.ts +7 -0
- package/dist/commands/topics.d.ts.map +1 -0
- package/dist/commands/topics.js +314 -0
- package/dist/commands/topics.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api-client.d.ts +29 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +64 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/auth.d.ts +10 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +47 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +19 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +59 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/errors.d.ts +7 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +133 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/formatters.d.ts +12 -0
- package/dist/lib/formatters.d.ts.map +1 -0
- package/dist/lib/formatters.js +103 -0
- package/dist/lib/formatters.js.map +1 -0
- package/dist/lib/spinner.d.ts +54 -0
- package/dist/lib/spinner.d.ts.map +1 -0
- package/dist/lib/spinner.js +108 -0
- package/dist/lib/spinner.js.map +1 -0
- package/dist/lib/validators.d.ts +92 -0
- package/dist/lib/validators.d.ts.map +1 -0
- package/dist/lib/validators.js +257 -0
- package/dist/lib/validators.js.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/docs/README.md +219 -0
- package/docs/code-generation-strategy.md +560 -0
- package/docs/prd.md +748 -0
- package/docs/sync-strategy.md +533 -0
- package/jest.config.js +30 -0
- package/package.json +67 -0
- package/scripts/install-deps.sh +38 -0
- package/src/commands/apikeys.ts +144 -0
- package/src/commands/artifacts.ts +296 -0
- package/src/commands/auth.ts +122 -0
- package/src/commands/communities.ts +153 -0
- package/src/commands/config.ts +144 -0
- package/src/commands/conversations.ts +176 -0
- package/src/commands/facts.ts +320 -0
- package/src/commands/graphrag.ts +149 -0
- package/src/commands/memories.ts +332 -0
- package/src/commands/patterns.ts +251 -0
- package/src/commands/system.ts +102 -0
- package/src/commands/topics.ts +354 -0
- package/src/index.ts +43 -0
- package/src/lib/api-client.ts +68 -0
- package/src/lib/auth.ts +42 -0
- package/src/lib/config.ts +68 -0
- package/src/lib/errors.ts +143 -0
- package/src/lib/formatters.ts +123 -0
- package/src/lib/spinner.ts +113 -0
- package/src/lib/validators.ts +302 -0
- package/src/types/index.ts +17 -0
- package/tests/__mocks__/chalk.ts +16 -0
- package/tests/__mocks__/cli-table3.ts +37 -0
- package/tests/__mocks__/configstore.ts +38 -0
- package/tests/commands/apikeys.test.ts +179 -0
- package/tests/commands/artifacts.test.ts +194 -0
- package/tests/commands/auth.test.ts +120 -0
- package/tests/commands/communities.test.ts +154 -0
- package/tests/commands/config.test.ts +154 -0
- package/tests/commands/conversations.test.ts +136 -0
- package/tests/commands/facts.test.ts +210 -0
- package/tests/commands/graphrag.test.ts +194 -0
- package/tests/commands/memories.test.ts +215 -0
- package/tests/commands/patterns.test.ts +201 -0
- package/tests/commands/system.test.ts +172 -0
- package/tests/commands/topics.test.ts +274 -0
- package/tests/lib/auth.test.ts +77 -0
- package/tests/lib/config.test.ts +50 -0
- package/tests/lib/errors.test.ts +126 -0
- package/tests/lib/formatters.test.ts +87 -0
- 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;
|