kitstore-cli 1.0.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.test +4 -0
- package/.eslintrc.js +29 -0
- package/dist/__tests__/commands/init.test.js +76 -0
- package/dist/__tests__/commands/install.test.js +422 -0
- package/dist/__tests__/commands/list.test.js +173 -0
- package/dist/__tests__/commands/login.test.js +281 -0
- package/dist/__tests__/commands/rule-check.test.js +72 -0
- package/dist/__tests__/commands/search.test.js +175 -0
- package/dist/__tests__/commands/upload.test.js +367 -0
- package/dist/__tests__/config.test.js +179 -0
- package/dist/__tests__/setup.js +8 -0
- package/dist/api/client.js +18 -0
- package/dist/api/generated/api.js +912 -0
- package/dist/api/generated/base.js +48 -0
- package/dist/api/generated/common.js +108 -0
- package/dist/api/generated/configuration.js +48 -0
- package/dist/api/generated/index.js +31 -0
- package/dist/commands/init.js +79 -0
- package/dist/commands/install.js +150 -0
- package/dist/commands/list.js +70 -0
- package/dist/commands/login.js +64 -0
- package/dist/commands/rule-check.js +81 -0
- package/dist/commands/search.js +59 -0
- package/dist/commands/upload.js +138 -0
- package/dist/config.js +84 -0
- package/dist/index.js +71 -0
- package/e2e/install.e2e.test.ts +237 -0
- package/e2e/integration.e2e.test.ts +346 -0
- package/e2e/login.e2e.test.ts +188 -0
- package/jest.config.js +24 -0
- package/openapitools.json +7 -0
- package/package.json +41 -0
- package/src/__tests__/commands/init.test.ts +52 -0
- package/src/__tests__/commands/install.test.ts +449 -0
- package/src/__tests__/commands/list.test.ts +164 -0
- package/src/__tests__/commands/login.test.ts +293 -0
- package/src/__tests__/commands/rule-check.test.ts +52 -0
- package/src/__tests__/commands/search.test.ts +168 -0
- package/src/__tests__/commands/upload.test.ts +404 -0
- package/src/__tests__/config.test.ts +181 -0
- package/src/__tests__/setup.ts +11 -0
- package/src/api/client.ts +20 -0
- package/src/api/generated/.openapi-generator/FILES +17 -0
- package/src/api/generated/.openapi-generator/VERSION +1 -0
- package/src/api/generated/.openapi-generator-ignore +23 -0
- package/src/api/generated/api.ts +1171 -0
- package/src/api/generated/base.ts +62 -0
- package/src/api/generated/common.ts +113 -0
- package/src/api/generated/configuration.ts +121 -0
- package/src/api/generated/docs/AuthApi.md +158 -0
- package/src/api/generated/docs/AuthResponseDto.md +22 -0
- package/src/api/generated/docs/AuthUserDto.md +24 -0
- package/src/api/generated/docs/HealthApi.md +183 -0
- package/src/api/generated/docs/LoginDto.md +22 -0
- package/src/api/generated/docs/RegisterDto.md +24 -0
- package/src/api/generated/docs/RuleAuthorDto.md +22 -0
- package/src/api/generated/docs/RuleResponseDto.md +36 -0
- package/src/api/generated/docs/RulesApi.md +289 -0
- package/src/api/generated/git_push.sh +57 -0
- package/src/api/generated/index.ts +18 -0
- package/src/commands/init.ts +46 -0
- package/src/commands/install.ts +129 -0
- package/src/commands/list.ts +71 -0
- package/src/commands/login.ts +65 -0
- package/src/commands/rule-check.ts +49 -0
- package/src/commands/search.ts +66 -0
- package/src/commands/upload.ts +117 -0
- package/src/config.ts +66 -0
- package/src/index.ts +79 -0
- package/test-cli-config.js +118 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { getConfig, saveConfig } from '../config';
|
|
4
|
+
import { createApi } from '../api/client';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @requirement TC-UNIT-CLI-001, TC-UNIT-CLI-002, TC-E2E-CLI-001
|
|
8
|
+
*/
|
|
9
|
+
export async function loginCommand(options: {
|
|
10
|
+
email?: string;
|
|
11
|
+
password?: string;
|
|
12
|
+
server?: string;
|
|
13
|
+
}) {
|
|
14
|
+
try {
|
|
15
|
+
const config = await getConfig();
|
|
16
|
+
const server = options.server || config.server;
|
|
17
|
+
|
|
18
|
+
let email = options.email;
|
|
19
|
+
let password = options.password;
|
|
20
|
+
|
|
21
|
+
if (!email) {
|
|
22
|
+
const answer = await inquirer.prompt([
|
|
23
|
+
{ type: 'input', name: 'email', message: 'Email:' }
|
|
24
|
+
]);
|
|
25
|
+
email = answer.email;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!password) {
|
|
29
|
+
const answer = await inquirer.prompt([
|
|
30
|
+
{ type: 'password', name: 'password', message: 'Password:' }
|
|
31
|
+
]);
|
|
32
|
+
password = answer.password;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { authApi } = await createApi({ server });
|
|
36
|
+
if (!email || !password) {
|
|
37
|
+
throw new Error('Email and password are required');
|
|
38
|
+
}
|
|
39
|
+
const response = await authApi.authControllerLogin({ email, password });
|
|
40
|
+
const data = response.data;
|
|
41
|
+
|
|
42
|
+
if ((data as any).token) {
|
|
43
|
+
await saveConfig({
|
|
44
|
+
token: (data as any).token,
|
|
45
|
+
server,
|
|
46
|
+
user: (data as any).user,
|
|
47
|
+
lastLogin: new Date().toISOString(),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log(`โ
Logged in as ${(data as any).user.username}`);
|
|
51
|
+
} else {
|
|
52
|
+
console.error('โ Login failed');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
} catch (err: unknown) {
|
|
56
|
+
if (axios.isAxiosError(err)) {
|
|
57
|
+
console.error(`โ Login failed: ${err.response?.data?.error || err.message}`);
|
|
58
|
+
} else if (err instanceof Error) {
|
|
59
|
+
console.error(`โ Login failed: ${err.message}`);
|
|
60
|
+
} else {
|
|
61
|
+
console.error(`โ Login failed: ${err}`);
|
|
62
|
+
}
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as fs from 'fs-extra';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @requirement TC-UNIT-CLI-CHECK-001
|
|
6
|
+
*/
|
|
7
|
+
export async function ruleCheckCommand(filePath: string) {
|
|
8
|
+
try {
|
|
9
|
+
if (!(await fs.pathExists(filePath))) {
|
|
10
|
+
console.error(chalk.red(`โ File not found: ${filePath}`));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
15
|
+
const lines = content.split('\n');
|
|
16
|
+
|
|
17
|
+
let errors = 0;
|
|
18
|
+
let warnings = 0;
|
|
19
|
+
|
|
20
|
+
console.log(chalk.blue(`Checking rule: ${filePath}...`));
|
|
21
|
+
|
|
22
|
+
// Basic checks
|
|
23
|
+
if (content.length === 0) {
|
|
24
|
+
console.error(chalk.red(' - Error: File is empty'));
|
|
25
|
+
errors++;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!content.includes('@requirement')) {
|
|
29
|
+
console.warn(chalk.yellow(' - Warning: Missing @requirement tag'));
|
|
30
|
+
warnings++;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (lines.length > 500) {
|
|
34
|
+
console.warn(chalk.yellow(' - Warning: File is very long (>500 lines)'));
|
|
35
|
+
warnings++;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (errors === 0 && warnings === 0) {
|
|
39
|
+
console.log(chalk.green('โ
Rule validation passed!'));
|
|
40
|
+
} else {
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log(`Validation finished with ${chalk.red(errors + ' errors')} and ${chalk.yellow(warnings + ' warnings')}.`);
|
|
43
|
+
}
|
|
44
|
+
} catch (err: any) {
|
|
45
|
+
console.error(chalk.red(`โ Check failed: ${err.message}`));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getConfig } from '../config';
|
|
2
|
+
import { createApi } from '../api/client';
|
|
3
|
+
|
|
4
|
+
export async function searchCommand(
|
|
5
|
+
query: string,
|
|
6
|
+
options: {
|
|
7
|
+
type?: string;
|
|
8
|
+
tag?: string;
|
|
9
|
+
limit?: string;
|
|
10
|
+
}
|
|
11
|
+
) {
|
|
12
|
+
try {
|
|
13
|
+
const config = await getConfig();
|
|
14
|
+
|
|
15
|
+
if (!config.token) {
|
|
16
|
+
console.error('โ Not logged in. Please run `kitstore login` first.');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!query || query.trim() === '') {
|
|
21
|
+
console.error('โ Search query is required.');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { rulesApi } = await createApi({ server: config.server });
|
|
26
|
+
|
|
27
|
+
const queryParams: any = {
|
|
28
|
+
name: query, // Search by name using the name filter
|
|
29
|
+
};
|
|
30
|
+
if (options.type) queryParams.type = options.type;
|
|
31
|
+
if (options.tag) queryParams.tag = options.tag;
|
|
32
|
+
if (options.limit) queryParams.limit = parseInt(options.limit);
|
|
33
|
+
|
|
34
|
+
const response = await rulesApi.rulesControllerFindAll(queryParams);
|
|
35
|
+
|
|
36
|
+
if (response.data && response.data.length > 0) {
|
|
37
|
+
console.log(`๐ Search results for "${query}":`);
|
|
38
|
+
console.log(`๐ Found ${response.data.length} matching rules/commands:`);
|
|
39
|
+
console.log('');
|
|
40
|
+
|
|
41
|
+
response.data.forEach((rule: any) => {
|
|
42
|
+
console.log(`๐ ${rule.name}`);
|
|
43
|
+
console.log(` ID: ${rule.id}`);
|
|
44
|
+
console.log(` Tag: ${rule.tag}`);
|
|
45
|
+
console.log(` Type: ${rule.type}`);
|
|
46
|
+
console.log(` Version: ${rule.version}`);
|
|
47
|
+
console.log(` Author: ${rule.author.username}`);
|
|
48
|
+
if (rule.description) {
|
|
49
|
+
console.log(` Description: ${rule.description}`);
|
|
50
|
+
}
|
|
51
|
+
console.log('');
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
console.log(`๐ No rules or commands found matching "${query}".`);
|
|
55
|
+
}
|
|
56
|
+
} catch (err: unknown) {
|
|
57
|
+
if (err && typeof err === 'object' && 'response' in err) {
|
|
58
|
+
const axiosError = err as any;
|
|
59
|
+
console.error('โ Search failed:', axiosError.response?.data?.error || axiosError.message);
|
|
60
|
+
} else {
|
|
61
|
+
console.error('โ Search failed:', err);
|
|
62
|
+
}
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import * as fs from 'fs-extra';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import FormData from 'form-data';
|
|
5
|
+
import { getConfig } from '../config';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @requirement TC-UNIT-CLI-004, TC-UNIT-CLI-005, TC-E2E-CLI-003
|
|
9
|
+
*/
|
|
10
|
+
export async function uploadCommand(
|
|
11
|
+
filePath: string,
|
|
12
|
+
options: {
|
|
13
|
+
name?: string;
|
|
14
|
+
tag?: string;
|
|
15
|
+
type?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
}
|
|
19
|
+
) {
|
|
20
|
+
try {
|
|
21
|
+
const config = await getConfig();
|
|
22
|
+
|
|
23
|
+
if (!config.token) {
|
|
24
|
+
console.error('โ Not logged in. Please run: kitstore login');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Validate file exists
|
|
29
|
+
if (!(await fs.pathExists(filePath))) {
|
|
30
|
+
console.error(`โ File not found: ${filePath}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Validate file size (max 10MB)
|
|
35
|
+
const stats = await fs.stat(filePath);
|
|
36
|
+
const fileSizeInMB = stats.size / (1024 * 1024);
|
|
37
|
+
const maxSizeInMB = 10;
|
|
38
|
+
if (fileSizeInMB > maxSizeInMB) {
|
|
39
|
+
console.error(`โ File size exceeds limit (${fileSizeInMB.toFixed(2)} MB > ${maxSizeInMB} MB)`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Validate file type
|
|
44
|
+
const allowedExtensions = ['.md', '.txt', '.json', '.cursorrules'];
|
|
45
|
+
const fileExtension = path.extname(filePath).toLowerCase();
|
|
46
|
+
if (!allowedExtensions.includes(fileExtension)) {
|
|
47
|
+
console.error(`โ Invalid file type. Allowed types: ${allowedExtensions.join(', ')}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Auto-detect type from path if not provided
|
|
52
|
+
let type = options.type;
|
|
53
|
+
if (!type) {
|
|
54
|
+
const normalizedPath = path.normalize(filePath);
|
|
55
|
+
if (normalizedPath.includes('.cursor/rules') || normalizedPath.includes('.cursorrules')) {
|
|
56
|
+
type = 'rule';
|
|
57
|
+
} else if (normalizedPath.includes('.cursor/commands')) {
|
|
58
|
+
type = 'command';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!type) {
|
|
63
|
+
console.error('โ Type is required. Use --type rule or --type command');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!options.tag) {
|
|
68
|
+
console.error('โ Tag is required. Use --tag <tag>');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Prepare form data
|
|
73
|
+
const form = new FormData();
|
|
74
|
+
form.append('file', fs.createReadStream(filePath));
|
|
75
|
+
form.append('name', options.name || path.basename(filePath));
|
|
76
|
+
form.append('tag', options.tag);
|
|
77
|
+
form.append('type', type);
|
|
78
|
+
if (options.description) form.append('description', options.description);
|
|
79
|
+
form.append('version', options.version || '1.0.0');
|
|
80
|
+
|
|
81
|
+
console.log('Uploading...');
|
|
82
|
+
|
|
83
|
+
const response = await axios.post(`${config.server}/api/rules`, form, {
|
|
84
|
+
headers: {
|
|
85
|
+
...form.getHeaders(),
|
|
86
|
+
Authorization: `Bearer ${config.token}`,
|
|
87
|
+
},
|
|
88
|
+
maxContentLength: Infinity,
|
|
89
|
+
maxBodyLength: Infinity,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (response.data && response.data.id) {
|
|
93
|
+
console.log(`โ
Uploaded successfully!`);
|
|
94
|
+
console.log(` Name: ${response.data.name}`);
|
|
95
|
+
console.log(` Tag: ${response.data.tag}`);
|
|
96
|
+
console.log(` Type: ${response.data.type}`);
|
|
97
|
+
console.log(` Version: ${response.data.version}`);
|
|
98
|
+
} else {
|
|
99
|
+
console.error('โ Upload failed');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
} catch (err: unknown) {
|
|
103
|
+
if (axios.isAxiosError(err)) {
|
|
104
|
+
const errorData = err.response?.data;
|
|
105
|
+
const errorMessage = errorData?.message || errorData?.error || err.message;
|
|
106
|
+
console.error('โ Upload failed:', errorMessage);
|
|
107
|
+
if (errorData && typeof errorData === 'object') {
|
|
108
|
+
console.error(' Details:', JSON.stringify(errorData, null, 2));
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
console.error('โ Upload failed:', err);
|
|
112
|
+
}
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @requirement TC-E2E-CLI-001
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs-extra';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
|
|
8
|
+
const CONFIG_DIR = process.env.KITSTORE_CONFIG_DIR || path.join(os.homedir(), '.kitstore');
|
|
9
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
10
|
+
|
|
11
|
+
export interface Config {
|
|
12
|
+
token?: string;
|
|
13
|
+
server: string;
|
|
14
|
+
user?: {
|
|
15
|
+
id: string;
|
|
16
|
+
username: string;
|
|
17
|
+
email?: string;
|
|
18
|
+
};
|
|
19
|
+
lastLogin?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function getConfig(): Promise<Config> {
|
|
23
|
+
try {
|
|
24
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
25
|
+
if (await fs.pathExists(CONFIG_FILE)) {
|
|
26
|
+
const content = await fs.readJson(CONFIG_FILE);
|
|
27
|
+
// Environment variable takes precedence over config file, fallback to localhost for development
|
|
28
|
+
const server = process.env.AGENTKIT_BACKEND_URL || content.server || 'http://localhost:3000';
|
|
29
|
+
return { ...content, server };
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
// Ignore errors
|
|
33
|
+
}
|
|
34
|
+
// Use production backend URL if available, fallback to localhost for development
|
|
35
|
+
const server = process.env.AGENTKIT_BACKEND_URL || 'http://localhost:3000';
|
|
36
|
+
return { server };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function saveConfig(config: Config): Promise<void> {
|
|
40
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
41
|
+
await fs.writeJson(CONFIG_FILE, config, { spaces: 2 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getInstallPaths(): { rules: string; commands: string } {
|
|
45
|
+
const cwd = process.cwd();
|
|
46
|
+
const home = os.homedir();
|
|
47
|
+
|
|
48
|
+
// Check for .cursor in current directory first
|
|
49
|
+
const cwdCursor = path.join(cwd, '.cursor');
|
|
50
|
+
if (fs.existsSync(cwdCursor)) {
|
|
51
|
+
return {
|
|
52
|
+
rules: path.join(cwdCursor, 'rules'),
|
|
53
|
+
commands: path.join(cwdCursor, 'commands'),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback to home directory
|
|
58
|
+
return {
|
|
59
|
+
rules: path.join(home, '.cursor', 'rules'),
|
|
60
|
+
commands: path.join(home, '.cursor', 'commands'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { loginCommand } from './commands/login';
|
|
5
|
+
import { installCommand } from './commands/install';
|
|
6
|
+
import { uploadCommand } from './commands/upload';
|
|
7
|
+
import { listCommand } from './commands/list';
|
|
8
|
+
import { searchCommand } from './commands/search';
|
|
9
|
+
import { initCommand } from './commands/init';
|
|
10
|
+
import { ruleCheckCommand } from './commands/rule-check';
|
|
11
|
+
import pkg from '../package.json';
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name('kitstore')
|
|
17
|
+
.description('CLI tool for managing Cursor rules and commands')
|
|
18
|
+
.version(pkg.version);
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command('init')
|
|
22
|
+
.description('Initialize Cursor Kit in the current directory')
|
|
23
|
+
.action(initCommand);
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.command('rule-check')
|
|
27
|
+
.description('Check and validate a rule file')
|
|
28
|
+
.argument('<file-path>', 'Path to the rule file')
|
|
29
|
+
.action(ruleCheckCommand);
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.command('login')
|
|
33
|
+
.description('Login to Cursor Kit backend')
|
|
34
|
+
.option('-e, --email <email>', 'Email address')
|
|
35
|
+
.option('-p, --password <password>', 'Password')
|
|
36
|
+
.option('-s, --server <url>', 'Backend server URL', 'http://localhost:3000')
|
|
37
|
+
.action(loginCommand);
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command('install')
|
|
41
|
+
.description('Install rules or commands')
|
|
42
|
+
.option('-t, --tag <tag>', 'Install by tag')
|
|
43
|
+
.option('-n, --name <name>', 'Install by name')
|
|
44
|
+
.option('--type <type>', 'Filter by type (rule|command)')
|
|
45
|
+
.option('-f, --force', 'Overwrite existing files')
|
|
46
|
+
.action(installCommand);
|
|
47
|
+
|
|
48
|
+
program
|
|
49
|
+
.command('upload')
|
|
50
|
+
.description('Upload a rule or command file')
|
|
51
|
+
.argument('<file-path>', 'Path to the file to upload')
|
|
52
|
+
.option('-n, --name <name>', 'Name of the rule/command')
|
|
53
|
+
.option('-t, --tag <tag>', 'Tag (required)')
|
|
54
|
+
.option('--type <type>', 'Type: rule or command (required)')
|
|
55
|
+
.option('-d, --description <desc>', 'Description')
|
|
56
|
+
.option('--rule-version <version>', 'Version', '1.0.0')
|
|
57
|
+
.action(uploadCommand);
|
|
58
|
+
|
|
59
|
+
program
|
|
60
|
+
.command('list')
|
|
61
|
+
.description('List available rules and commands')
|
|
62
|
+
.option('--type <type>', 'Filter by type (rule|command)')
|
|
63
|
+
.option('-t, --tag <tag>', 'Filter by tag')
|
|
64
|
+
.option('-n, --name <name>', 'Filter by name')
|
|
65
|
+
.option('-l, --limit <number>', 'Limit results', '20')
|
|
66
|
+
.action(listCommand);
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.command('search')
|
|
70
|
+
.description('Search for rules and commands')
|
|
71
|
+
.argument('<query>', 'Search query')
|
|
72
|
+
.option('--type <type>', 'Filter by type (rule|command)')
|
|
73
|
+
.option('-t, --tag <tag>', 'Filter by tag')
|
|
74
|
+
.option('-l, --limit <number>', 'Limit results', '20')
|
|
75
|
+
.action(searchCommand);
|
|
76
|
+
|
|
77
|
+
program.parse();
|
|
78
|
+
|
|
79
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Test script to verify CLI backend URL configuration mechanism
|
|
4
|
+
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
console.log('๐งช Testing CLI Backend URL Configuration Mechanism...\n');
|
|
11
|
+
|
|
12
|
+
// Test different configuration scenarios
|
|
13
|
+
const testCases = [
|
|
14
|
+
{
|
|
15
|
+
name: 'Default configuration (no env var, no config file)',
|
|
16
|
+
env: {},
|
|
17
|
+
expectedServer: 'http://localhost:3000'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'Environment variable override',
|
|
21
|
+
env: { AGENTKIT_BACKEND_URL: 'https://test-env.example.com' },
|
|
22
|
+
expectedServer: 'https://test-env.example.com'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'Config file override',
|
|
26
|
+
env: {},
|
|
27
|
+
config: { server: 'https://test-config.example.com' },
|
|
28
|
+
expectedServer: 'https://test-config.example.com'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'Environment variable takes precedence over config file',
|
|
32
|
+
env: { AGENTKIT_BACKEND_URL: 'https://test-env-precedence.example.com' },
|
|
33
|
+
config: { server: 'https://test-config-precedence.example.com' },
|
|
34
|
+
expectedServer: 'https://test-env-precedence.example.com'
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const configDir = path.join(os.homedir(), '.agentkit');
|
|
39
|
+
const configFile = path.join(configDir, 'config.json');
|
|
40
|
+
|
|
41
|
+
// Helper function to run CLI config test
|
|
42
|
+
async function testConfig(testCase) {
|
|
43
|
+
console.log(`๐ Testing: ${testCase.name}`);
|
|
44
|
+
|
|
45
|
+
// Set environment variables
|
|
46
|
+
const originalEnv = { ...process.env };
|
|
47
|
+
Object.assign(process.env, testCase.env);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Create config file if specified
|
|
51
|
+
if (testCase.config) {
|
|
52
|
+
fs.ensureDirSync(configDir);
|
|
53
|
+
fs.writeJsonSync(configFile, testCase.config);
|
|
54
|
+
} else {
|
|
55
|
+
// Remove config file
|
|
56
|
+
if (fs.existsSync(configFile)) {
|
|
57
|
+
fs.removeSync(configFile);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Import and test the config module
|
|
62
|
+
delete require.cache[require.resolve('./dist/config.js')];
|
|
63
|
+
const config = require('./dist/config.js');
|
|
64
|
+
|
|
65
|
+
// Test getConfig function (async)
|
|
66
|
+
const result = await config.getConfig();
|
|
67
|
+
console.log(` Expected: ${testCase.expectedServer}`);
|
|
68
|
+
console.log(` Got: ${result.server}`);
|
|
69
|
+
|
|
70
|
+
if (result.server === testCase.expectedServer) {
|
|
71
|
+
console.log(' โ
PASS\n');
|
|
72
|
+
return true;
|
|
73
|
+
} else {
|
|
74
|
+
console.log(' โ FAIL\n');
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.log(` โ ERROR: ${error.message}\n`);
|
|
80
|
+
return false;
|
|
81
|
+
} finally {
|
|
82
|
+
// Restore environment
|
|
83
|
+
process.env = originalEnv;
|
|
84
|
+
|
|
85
|
+
// Clean up config file
|
|
86
|
+
if (fs.existsSync(configFile)) {
|
|
87
|
+
fs.removeSync(configFile);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Run all test cases
|
|
93
|
+
async function runTests() {
|
|
94
|
+
let passed = 0;
|
|
95
|
+
let total = testCases.length;
|
|
96
|
+
|
|
97
|
+
for (const testCase of testCases) {
|
|
98
|
+
if (await testConfig(testCase)) {
|
|
99
|
+
passed++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(`๐ฏ Test Results: ${passed}/${total} passed`);
|
|
104
|
+
|
|
105
|
+
if (passed === total) {
|
|
106
|
+
console.log('โ
All backend URL configuration tests passed!');
|
|
107
|
+
console.log('๐ Configuration precedence verified:');
|
|
108
|
+
console.log(' 1. AGENTKIT_BACKEND_URL environment variable');
|
|
109
|
+
console.log(' 2. Configuration file (~/.agentkit/config.json)');
|
|
110
|
+
console.log(' 3. Default localhost:3000');
|
|
111
|
+
} else {
|
|
112
|
+
console.log('โ Some configuration tests failed!');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Run the tests
|
|
118
|
+
runTests().catch(console.error);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"moduleResolution": "node"
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|