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,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchCommand = searchCommand;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
const client_1 = require("../api/client");
|
|
6
|
+
async function searchCommand(query, options) {
|
|
7
|
+
try {
|
|
8
|
+
const config = await (0, config_1.getConfig)();
|
|
9
|
+
if (!config.token) {
|
|
10
|
+
console.error('❌ Not logged in. Please run `kitstore login` first.');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
if (!query || query.trim() === '') {
|
|
14
|
+
console.error('❌ Search query is required.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const { rulesApi } = await (0, client_1.createApi)({ server: config.server });
|
|
18
|
+
const queryParams = {
|
|
19
|
+
name: query, // Search by name using the name filter
|
|
20
|
+
};
|
|
21
|
+
if (options.type)
|
|
22
|
+
queryParams.type = options.type;
|
|
23
|
+
if (options.tag)
|
|
24
|
+
queryParams.tag = options.tag;
|
|
25
|
+
if (options.limit)
|
|
26
|
+
queryParams.limit = parseInt(options.limit);
|
|
27
|
+
const response = await rulesApi.rulesControllerFindAll(queryParams);
|
|
28
|
+
if (response.data && response.data.length > 0) {
|
|
29
|
+
console.log(`🔍 Search results for "${query}":`);
|
|
30
|
+
console.log(`📋 Found ${response.data.length} matching rules/commands:`);
|
|
31
|
+
console.log('');
|
|
32
|
+
response.data.forEach((rule) => {
|
|
33
|
+
console.log(`📄 ${rule.name}`);
|
|
34
|
+
console.log(` ID: ${rule.id}`);
|
|
35
|
+
console.log(` Tag: ${rule.tag}`);
|
|
36
|
+
console.log(` Type: ${rule.type}`);
|
|
37
|
+
console.log(` Version: ${rule.version}`);
|
|
38
|
+
console.log(` Author: ${rule.author.username}`);
|
|
39
|
+
if (rule.description) {
|
|
40
|
+
console.log(` Description: ${rule.description}`);
|
|
41
|
+
}
|
|
42
|
+
console.log('');
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log(`🔍 No rules or commands found matching "${query}".`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (err && typeof err === 'object' && 'response' in err) {
|
|
51
|
+
const axiosError = err;
|
|
52
|
+
console.error('❌ Search failed:', axiosError.response?.data?.error || axiosError.message);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.error('❌ Search failed:', err);
|
|
56
|
+
}
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.uploadCommand = uploadCommand;
|
|
40
|
+
const axios_1 = __importDefault(require("axios"));
|
|
41
|
+
const fs = __importStar(require("fs-extra"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
44
|
+
const config_1 = require("../config");
|
|
45
|
+
/**
|
|
46
|
+
* @requirement TC-UNIT-CLI-004, TC-UNIT-CLI-005, TC-E2E-CLI-003
|
|
47
|
+
*/
|
|
48
|
+
async function uploadCommand(filePath, options) {
|
|
49
|
+
try {
|
|
50
|
+
const config = await (0, config_1.getConfig)();
|
|
51
|
+
if (!config.token) {
|
|
52
|
+
console.error('❌ Not logged in. Please run: kitstore login');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
// Validate file exists
|
|
56
|
+
if (!(await fs.pathExists(filePath))) {
|
|
57
|
+
console.error(`❌ File not found: ${filePath}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
// Validate file size (max 10MB)
|
|
61
|
+
const stats = await fs.stat(filePath);
|
|
62
|
+
const fileSizeInMB = stats.size / (1024 * 1024);
|
|
63
|
+
const maxSizeInMB = 10;
|
|
64
|
+
if (fileSizeInMB > maxSizeInMB) {
|
|
65
|
+
console.error(`❌ File size exceeds limit (${fileSizeInMB.toFixed(2)} MB > ${maxSizeInMB} MB)`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
// Validate file type
|
|
69
|
+
const allowedExtensions = ['.md', '.txt', '.json', '.cursorrules'];
|
|
70
|
+
const fileExtension = path.extname(filePath).toLowerCase();
|
|
71
|
+
if (!allowedExtensions.includes(fileExtension)) {
|
|
72
|
+
console.error(`❌ Invalid file type. Allowed types: ${allowedExtensions.join(', ')}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
// Auto-detect type from path if not provided
|
|
76
|
+
let type = options.type;
|
|
77
|
+
if (!type) {
|
|
78
|
+
const normalizedPath = path.normalize(filePath);
|
|
79
|
+
if (normalizedPath.includes('.cursor/rules') || normalizedPath.includes('.cursorrules')) {
|
|
80
|
+
type = 'rule';
|
|
81
|
+
}
|
|
82
|
+
else if (normalizedPath.includes('.cursor/commands')) {
|
|
83
|
+
type = 'command';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!type) {
|
|
87
|
+
console.error('❌ Type is required. Use --type rule or --type command');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
if (!options.tag) {
|
|
91
|
+
console.error('❌ Tag is required. Use --tag <tag>');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
// Prepare form data
|
|
95
|
+
const form = new form_data_1.default();
|
|
96
|
+
form.append('file', fs.createReadStream(filePath));
|
|
97
|
+
form.append('name', options.name || path.basename(filePath));
|
|
98
|
+
form.append('tag', options.tag);
|
|
99
|
+
form.append('type', type);
|
|
100
|
+
if (options.description)
|
|
101
|
+
form.append('description', options.description);
|
|
102
|
+
form.append('version', options.version || '1.0.0');
|
|
103
|
+
console.log('Uploading...');
|
|
104
|
+
const response = await axios_1.default.post(`${config.server}/api/rules`, form, {
|
|
105
|
+
headers: {
|
|
106
|
+
...form.getHeaders(),
|
|
107
|
+
Authorization: `Bearer ${config.token}`,
|
|
108
|
+
},
|
|
109
|
+
maxContentLength: Infinity,
|
|
110
|
+
maxBodyLength: Infinity,
|
|
111
|
+
});
|
|
112
|
+
if (response.data && response.data.id) {
|
|
113
|
+
console.log(`✅ Uploaded successfully!`);
|
|
114
|
+
console.log(` Name: ${response.data.name}`);
|
|
115
|
+
console.log(` Tag: ${response.data.tag}`);
|
|
116
|
+
console.log(` Type: ${response.data.type}`);
|
|
117
|
+
console.log(` Version: ${response.data.version}`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.error('❌ Upload failed');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
if (axios_1.default.isAxiosError(err)) {
|
|
126
|
+
const errorData = err.response?.data;
|
|
127
|
+
const errorMessage = errorData?.message || errorData?.error || err.message;
|
|
128
|
+
console.error('❌ Upload failed:', errorMessage);
|
|
129
|
+
if (errorData && typeof errorData === 'object') {
|
|
130
|
+
console.error(' Details:', JSON.stringify(errorData, null, 2));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.error('❌ Upload failed:', err);
|
|
135
|
+
}
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getConfig = getConfig;
|
|
37
|
+
exports.saveConfig = saveConfig;
|
|
38
|
+
exports.getInstallPaths = getInstallPaths;
|
|
39
|
+
/**
|
|
40
|
+
* @requirement TC-E2E-CLI-001
|
|
41
|
+
*/
|
|
42
|
+
const fs = __importStar(require("fs-extra"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const CONFIG_DIR = process.env.KITSTORE_CONFIG_DIR || path.join(os.homedir(), '.kitstore');
|
|
46
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
47
|
+
async function getConfig() {
|
|
48
|
+
try {
|
|
49
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
50
|
+
if (await fs.pathExists(CONFIG_FILE)) {
|
|
51
|
+
const content = await fs.readJson(CONFIG_FILE);
|
|
52
|
+
// Environment variable takes precedence over config file, fallback to localhost for development
|
|
53
|
+
const server = process.env.AGENTKIT_BACKEND_URL || content.server || 'http://localhost:3000';
|
|
54
|
+
return { ...content, server };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// Ignore errors
|
|
59
|
+
}
|
|
60
|
+
// Use production backend URL if available, fallback to localhost for development
|
|
61
|
+
const server = process.env.AGENTKIT_BACKEND_URL || 'http://localhost:3000';
|
|
62
|
+
return { server };
|
|
63
|
+
}
|
|
64
|
+
async function saveConfig(config) {
|
|
65
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
66
|
+
await fs.writeJson(CONFIG_FILE, config, { spaces: 2 });
|
|
67
|
+
}
|
|
68
|
+
function getInstallPaths() {
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
const home = os.homedir();
|
|
71
|
+
// Check for .cursor in current directory first
|
|
72
|
+
const cwdCursor = path.join(cwd, '.cursor');
|
|
73
|
+
if (fs.existsSync(cwdCursor)) {
|
|
74
|
+
return {
|
|
75
|
+
rules: path.join(cwdCursor, 'rules'),
|
|
76
|
+
commands: path.join(cwdCursor, 'commands'),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// Fallback to home directory
|
|
80
|
+
return {
|
|
81
|
+
rules: path.join(home, '.cursor', 'rules'),
|
|
82
|
+
commands: path.join(home, '.cursor', 'commands'),
|
|
83
|
+
};
|
|
84
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const login_1 = require("./commands/login");
|
|
9
|
+
const install_1 = require("./commands/install");
|
|
10
|
+
const upload_1 = require("./commands/upload");
|
|
11
|
+
const list_1 = require("./commands/list");
|
|
12
|
+
const search_1 = require("./commands/search");
|
|
13
|
+
const init_1 = require("./commands/init");
|
|
14
|
+
const rule_check_1 = require("./commands/rule-check");
|
|
15
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
16
|
+
const program = new commander_1.Command();
|
|
17
|
+
program
|
|
18
|
+
.name('kitstore')
|
|
19
|
+
.description('CLI tool for managing Cursor rules and commands')
|
|
20
|
+
.version(package_json_1.default.version);
|
|
21
|
+
program
|
|
22
|
+
.command('init')
|
|
23
|
+
.description('Initialize Cursor Kit in the current directory')
|
|
24
|
+
.action(init_1.initCommand);
|
|
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(rule_check_1.ruleCheckCommand);
|
|
30
|
+
program
|
|
31
|
+
.command('login')
|
|
32
|
+
.description('Login to Cursor Kit backend')
|
|
33
|
+
.option('-e, --email <email>', 'Email address')
|
|
34
|
+
.option('-p, --password <password>', 'Password')
|
|
35
|
+
.option('-s, --server <url>', 'Backend server URL', 'http://localhost:3000')
|
|
36
|
+
.action(login_1.loginCommand);
|
|
37
|
+
program
|
|
38
|
+
.command('install')
|
|
39
|
+
.description('Install rules or commands')
|
|
40
|
+
.option('-t, --tag <tag>', 'Install by tag')
|
|
41
|
+
.option('-n, --name <name>', 'Install by name')
|
|
42
|
+
.option('--type <type>', 'Filter by type (rule|command)')
|
|
43
|
+
.option('-f, --force', 'Overwrite existing files')
|
|
44
|
+
.action(install_1.installCommand);
|
|
45
|
+
program
|
|
46
|
+
.command('upload')
|
|
47
|
+
.description('Upload a rule or command file')
|
|
48
|
+
.argument('<file-path>', 'Path to the file to upload')
|
|
49
|
+
.option('-n, --name <name>', 'Name of the rule/command')
|
|
50
|
+
.option('-t, --tag <tag>', 'Tag (required)')
|
|
51
|
+
.option('--type <type>', 'Type: rule or command (required)')
|
|
52
|
+
.option('-d, --description <desc>', 'Description')
|
|
53
|
+
.option('--rule-version <version>', 'Version', '1.0.0')
|
|
54
|
+
.action(upload_1.uploadCommand);
|
|
55
|
+
program
|
|
56
|
+
.command('list')
|
|
57
|
+
.description('List available rules and commands')
|
|
58
|
+
.option('--type <type>', 'Filter by type (rule|command)')
|
|
59
|
+
.option('-t, --tag <tag>', 'Filter by tag')
|
|
60
|
+
.option('-n, --name <name>', 'Filter by name')
|
|
61
|
+
.option('-l, --limit <number>', 'Limit results', '20')
|
|
62
|
+
.action(list_1.listCommand);
|
|
63
|
+
program
|
|
64
|
+
.command('search')
|
|
65
|
+
.description('Search for rules and commands')
|
|
66
|
+
.argument('<query>', 'Search query')
|
|
67
|
+
.option('--type <type>', 'Filter by type (rule|command)')
|
|
68
|
+
.option('-t, --tag <tag>', 'Filter by tag')
|
|
69
|
+
.option('-l, --limit <number>', 'Limit results', '20')
|
|
70
|
+
.action(search_1.searchCommand);
|
|
71
|
+
program.parse();
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, unlinkSync, readFileSync, mkdirSync, rmSync } from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
// TC-E2E-CLI-002: Install rule flow
|
|
6
|
+
// TC-E2E-CLI-005: List rules
|
|
7
|
+
// TC-E2E-CLI-011: Search rules
|
|
8
|
+
describe('CLI Install E2E Tests', () => {
|
|
9
|
+
const tempHome = path.join(process.cwd(), 'temp-home-install');
|
|
10
|
+
const configDir = path.join(tempHome, '.kitstore');
|
|
11
|
+
const configPath = path.join(configDir, 'config.json');
|
|
12
|
+
const installDir = path.join(tempHome, '.cursor');
|
|
13
|
+
const rulesDir = path.join(installDir, 'rules');
|
|
14
|
+
const cliPath = path.join(process.cwd(), 'dist', 'index.js');
|
|
15
|
+
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
if (!existsSync(tempHome)) {
|
|
18
|
+
mkdirSync(tempHome, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
// Setup mock config for authenticated user
|
|
24
|
+
const config = {
|
|
25
|
+
server: 'http://localhost:3000',
|
|
26
|
+
token: 'test-token',
|
|
27
|
+
user: { id: 'test-user-id', username: 'testuser' },
|
|
28
|
+
lastLogin: new Date().toISOString(),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Ensure config directory exists
|
|
32
|
+
if (!existsSync(configDir)) {
|
|
33
|
+
mkdirSync(configDir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
require('fs').writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterAll(() => {
|
|
39
|
+
// Clean up temp home
|
|
40
|
+
if (existsSync(tempHome)) {
|
|
41
|
+
rmSync(tempHome, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
// Clean up config and installed rules
|
|
47
|
+
if (existsSync(configPath)) {
|
|
48
|
+
unlinkSync(configPath);
|
|
49
|
+
}
|
|
50
|
+
if (existsSync(rulesDir)) {
|
|
51
|
+
rmSync(rulesDir, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// TC-E2E-CLI-005: List rules
|
|
56
|
+
test('should list rules successfully', () => {
|
|
57
|
+
// Skip if CLI is not built
|
|
58
|
+
if (!existsSync(cliPath)) {
|
|
59
|
+
console.warn('Skipping test: CLI not built');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Run list command
|
|
65
|
+
const result = execSync(`node ${cliPath} list`, {
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
timeout: 10000,
|
|
68
|
+
env: {
|
|
69
|
+
...process.env,
|
|
70
|
+
NODE_ENV: 'test',
|
|
71
|
+
AGENTKIT_CONFIG_DIR: configDir,
|
|
72
|
+
HOME: tempHome,
|
|
73
|
+
USERPROFILE: tempHome,
|
|
74
|
+
HOMEDRIVE: tempHome.substring(0, 2),
|
|
75
|
+
HOMEPATH: tempHome.substring(2)
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Should show found rules or empty message
|
|
80
|
+
expect(result).toMatch(/📋 Found \d+ rules\/commands|📭 No rules or commands found/);
|
|
81
|
+
|
|
82
|
+
} catch (error: any) {
|
|
83
|
+
// If backend not running, skip test
|
|
84
|
+
const output = (error.stdout || '') + (error.stderr || '');
|
|
85
|
+
if (
|
|
86
|
+
output.includes('ECONNREFUSED') ||
|
|
87
|
+
output.includes('ENOTFOUND') ||
|
|
88
|
+
output.includes('Not logged in') ||
|
|
89
|
+
output.includes('Failed to list rules') ||
|
|
90
|
+
output.includes('failed: Error')
|
|
91
|
+
) {
|
|
92
|
+
console.warn('Skipping test: Backend server not running or unauthorized');
|
|
93
|
+
return;
|
|
94
|
+
} else {
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// TC-E2E-CLI-011: Search rules
|
|
101
|
+
test('should search rules successfully', () => {
|
|
102
|
+
// Skip if CLI is not built
|
|
103
|
+
if (!existsSync(cliPath)) {
|
|
104
|
+
console.warn('Skipping test: CLI not built');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// Run search command
|
|
110
|
+
const result = execSync(`node ${cliPath} search "test"`, {
|
|
111
|
+
encoding: 'utf8',
|
|
112
|
+
timeout: 10000,
|
|
113
|
+
env: {
|
|
114
|
+
...process.env,
|
|
115
|
+
NODE_ENV: 'test',
|
|
116
|
+
AGENTKIT_CONFIG_DIR: configDir,
|
|
117
|
+
HOME: tempHome,
|
|
118
|
+
USERPROFILE: tempHome,
|
|
119
|
+
HOMEDRIVE: tempHome.substring(0, 2),
|
|
120
|
+
HOMEPATH: tempHome.substring(2)
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Should show search results
|
|
125
|
+
expect(result).toMatch(/🔍 Search results for "test"|🔍 No rules or commands found matching "test"/);
|
|
126
|
+
|
|
127
|
+
} catch (error: any) {
|
|
128
|
+
// If backend not running, skip test
|
|
129
|
+
const output = (error.stdout || '') + (error.stderr || '');
|
|
130
|
+
if (
|
|
131
|
+
output.includes('ECONNREFUSED') ||
|
|
132
|
+
output.includes('ENOTFOUND') ||
|
|
133
|
+
output.includes('Not logged in') ||
|
|
134
|
+
output.includes('Failed to list rules') ||
|
|
135
|
+
output.includes('failed: Error')
|
|
136
|
+
) {
|
|
137
|
+
console.warn('Skipping test: Backend server not running or unauthorized');
|
|
138
|
+
return;
|
|
139
|
+
} else {
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// TC-E2E-CLI-012: List with filters
|
|
146
|
+
test('should list rules with tag filter', () => {
|
|
147
|
+
// Skip if CLI is not built
|
|
148
|
+
if (!existsSync(cliPath)) {
|
|
149
|
+
console.warn('Skipping test: CLI not built');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// Run list command with tag filter
|
|
155
|
+
const result = execSync(`node ${cliPath} list --tag test`, {
|
|
156
|
+
encoding: 'utf8',
|
|
157
|
+
timeout: 10000,
|
|
158
|
+
env: {
|
|
159
|
+
...process.env,
|
|
160
|
+
NODE_ENV: 'test',
|
|
161
|
+
AGENTKIT_CONFIG_DIR: configDir,
|
|
162
|
+
HOME: tempHome,
|
|
163
|
+
USERPROFILE: tempHome,
|
|
164
|
+
HOMEDRIVE: tempHome.substring(0, 2),
|
|
165
|
+
HOMEPATH: tempHome.substring(2)
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Should show filtered results
|
|
170
|
+
expect(result).toMatch(/📋 Found \d+ rules\/commands|📭 No rules or commands found/);
|
|
171
|
+
|
|
172
|
+
} catch (error: any) {
|
|
173
|
+
// If backend not running, skip test
|
|
174
|
+
const output = (error.stdout || '') + (error.stderr || '');
|
|
175
|
+
if (
|
|
176
|
+
output.includes('ECONNREFUSED') ||
|
|
177
|
+
output.includes('ENOTFOUND') ||
|
|
178
|
+
output.includes('Not logged in') ||
|
|
179
|
+
output.includes('Failed to list rules') ||
|
|
180
|
+
output.includes('failed: Error')
|
|
181
|
+
) {
|
|
182
|
+
console.warn('Skipping test: Backend server not running or unauthorized');
|
|
183
|
+
return;
|
|
184
|
+
} else {
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// TC-E2E-CLI-013: List with type filter
|
|
191
|
+
test('should list rules with type filter', () => {
|
|
192
|
+
// Skip if CLI is not built
|
|
193
|
+
if (!existsSync(cliPath)) {
|
|
194
|
+
console.warn('Skipping test: CLI not built');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
// Run list command with type filter
|
|
200
|
+
const result = execSync(`node ${cliPath} list --type rule`, {
|
|
201
|
+
encoding: 'utf8',
|
|
202
|
+
timeout: 10000,
|
|
203
|
+
env: {
|
|
204
|
+
...process.env,
|
|
205
|
+
NODE_ENV: 'test',
|
|
206
|
+
AGENTKIT_CONFIG_DIR: configDir,
|
|
207
|
+
HOME: tempHome,
|
|
208
|
+
USERPROFILE: tempHome,
|
|
209
|
+
HOMEDRIVE: tempHome.substring(0, 2),
|
|
210
|
+
HOMEPATH: tempHome.substring(2)
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Should show filtered results
|
|
215
|
+
expect(result).toMatch(/📋 Found \d+ rules\/commands|📭 No rules or commands found/);
|
|
216
|
+
|
|
217
|
+
} catch (error: any) {
|
|
218
|
+
// If backend not running, skip test
|
|
219
|
+
const output = (error.stdout || '') + (error.stderr || '');
|
|
220
|
+
if (
|
|
221
|
+
output.includes('ECONNREFUSED') ||
|
|
222
|
+
output.includes('ENOTFOUND') ||
|
|
223
|
+
output.includes('Not logged in') ||
|
|
224
|
+
output.includes('Failed to list rules') ||
|
|
225
|
+
output.includes('failed: Error')
|
|
226
|
+
) {
|
|
227
|
+
console.warn('Skipping test: Backend server not running or unauthorized');
|
|
228
|
+
return;
|
|
229
|
+
} else {
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|