@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,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
|
+
}
|