ccjk 11.1.2 → 12.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/dist/chunks/agent-teams.mjs +1 -1
- package/dist/chunks/agent.mjs +1 -1
- package/dist/chunks/api-cli.mjs +118 -0
- package/dist/chunks/api-providers.mjs +1 -1
- package/dist/chunks/api.mjs +8 -759
- package/dist/chunks/auto-bootstrap.mjs +1 -1
- package/dist/chunks/auto-init.mjs +1 -1
- package/dist/chunks/auto-updater.mjs +1 -1
- package/dist/chunks/banner.mjs +1 -1
- package/dist/chunks/boost.mjs +1 -1
- package/dist/chunks/ccjk-agents.mjs +1 -1
- package/dist/chunks/ccjk-all.mjs +1 -1
- package/dist/chunks/ccjk-config.mjs +1 -1
- package/dist/chunks/ccjk-hooks.mjs +1 -1
- package/dist/chunks/ccjk-mcp.mjs +1 -1
- package/dist/chunks/ccjk-setup.mjs +1 -1
- package/dist/chunks/ccjk-skills.mjs +1 -1
- package/dist/chunks/ccr.mjs +6 -6
- package/dist/chunks/ccu.mjs +1 -1
- package/dist/chunks/check-updates.mjs +2 -2
- package/dist/chunks/claude-code-config-manager.mjs +2 -2
- package/dist/chunks/claude-code-incremental-manager.mjs +2 -2
- package/dist/chunks/claude-config.mjs +1 -1
- package/dist/chunks/claude-wrapper.mjs +1 -1
- package/dist/chunks/codex-config-switch.mjs +1 -1
- package/dist/chunks/codex-provider-manager.mjs +1 -1
- package/dist/chunks/codex-uninstaller.mjs +1 -1
- package/dist/chunks/codex.mjs +1 -1
- package/dist/chunks/commands.mjs +1 -1
- package/dist/chunks/completion.mjs +1 -1
- package/dist/chunks/config-consolidator.mjs +1 -1
- package/dist/chunks/config-switch.mjs +2 -2
- package/dist/chunks/config.mjs +1 -1
- package/dist/chunks/config2.mjs +1 -1
- package/dist/chunks/config3.mjs +1 -1
- package/dist/chunks/constants.mjs +1 -1
- package/dist/chunks/dashboard.mjs +1 -1
- package/dist/chunks/doctor.mjs +31 -7
- package/dist/chunks/evolution.mjs +1 -1
- package/dist/chunks/features.mjs +5 -5
- package/dist/chunks/index.mjs +7 -7
- package/dist/chunks/index2.mjs +10 -177
- package/dist/chunks/index3.mjs +168 -1162
- package/dist/chunks/index4.mjs +1076 -910
- package/dist/chunks/index5.mjs +947 -137
- package/dist/chunks/index6.mjs +167 -635
- package/dist/chunks/index7.mjs +663 -0
- package/dist/chunks/init.mjs +17 -17
- package/dist/chunks/installer.mjs +1 -1
- package/dist/chunks/interview.mjs +2 -2
- package/dist/chunks/marketplace.mjs +1 -1
- package/dist/chunks/mcp-cli.mjs +191 -0
- package/dist/chunks/mcp.mjs +2 -2
- package/dist/chunks/menu.mjs +3 -3
- package/dist/chunks/migrator.mjs +1 -1
- package/dist/chunks/monitor.mjs +1 -1
- package/dist/chunks/notification.mjs +1 -1
- package/dist/chunks/onboarding.mjs +1 -1
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/permission-manager.mjs +1 -1
- package/dist/chunks/permissions.mjs +1 -1
- package/dist/chunks/persistence-manager.mjs +1 -1
- package/dist/chunks/plugin.mjs +1 -1
- package/dist/chunks/prompts.mjs +1 -1
- package/dist/chunks/providers.mjs +1 -1
- package/dist/chunks/quick-actions.mjs +1 -1
- package/dist/chunks/quick-setup.mjs +5 -5
- package/dist/chunks/remote.mjs +2 -2
- package/dist/chunks/silent-updater.mjs +1 -1
- package/dist/chunks/simple-config.mjs +1 -1
- package/dist/chunks/skill.mjs +1 -1
- package/dist/chunks/skills-sync.mjs +1 -1
- package/dist/chunks/skills.mjs +1 -1
- package/dist/chunks/slash-commands.mjs +2 -2
- package/dist/chunks/startup.mjs +1 -1
- package/dist/chunks/stats.mjs +1 -1
- package/dist/chunks/status.mjs +1 -1
- package/dist/chunks/team.mjs +1 -1
- package/dist/chunks/thinking.mjs +2 -2
- package/dist/chunks/uninstall.mjs +3 -3
- package/dist/chunks/update.mjs +4 -4
- package/dist/chunks/upgrade-manager.mjs +1 -1
- package/dist/chunks/version-checker.mjs +1 -1
- package/dist/chunks/vim.mjs +1 -1
- package/dist/chunks/zero-config.mjs +1 -1
- package/dist/cli.mjs +76 -16
- package/dist/index.d.mts +37 -10
- package/dist/index.d.ts +37 -10
- package/dist/index.mjs +2 -2
- package/dist/shared/{ccjk.DB2UYcq0.mjs → ccjk.BOIUTf5z.mjs} +2 -2
- package/dist/shared/ccjk.CL4Yat0G.mjs +303 -0
- package/dist/shared/{ccjk.CxtuJxaS.mjs → ccjk.CN0edl87.mjs} +1 -1
- package/dist/shared/{ccjk.BKoi8-Hy.mjs → ccjk.CrB6OYHv.mjs} +1 -1
- package/dist/shared/{ccjk.DVBW2wxp.mjs → ccjk.Dk1HDseQ.mjs} +1 -1
- package/dist/shared/ccjk.bhFAMRyc.mjs +460 -0
- package/dist/shared/ccjk.cChAaGgT.mjs +88 -0
- package/dist/shared/{ccjk.DrMygfCF.mjs → ccjk.j4uut26D.mjs} +1 -1
- package/package.json +2 -3
- package/dist/shared/ccjk.Cu_R2MbQ.mjs +0 -75
package/dist/chunks/index5.mjs
CHANGED
|
@@ -1,195 +1,1005 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import 'node:child_process';
|
|
7
|
-
import 'node:
|
|
8
|
-
import 'node:util';
|
|
9
|
-
import './index2.mjs';
|
|
10
|
-
import 'node:url';
|
|
11
|
-
import 'i18next';
|
|
12
|
-
import 'i18next-fs-backend';
|
|
1
|
+
import ansis from 'ansis';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import os__default from 'node:os';
|
|
5
|
+
import path__default from 'node:path';
|
|
6
|
+
import { exec } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"git-worktrees"
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
function getSuperpowersDir$1() {
|
|
23
|
-
return join(homedir(), ".claude", "plugins", "superpowers");
|
|
9
|
+
function getCcjkConfigDir() {
|
|
10
|
+
if (process.env.CCJK_CONFIG_DIR) {
|
|
11
|
+
return process.env.CCJK_CONFIG_DIR;
|
|
12
|
+
}
|
|
13
|
+
return path__default.join(os__default.homedir(), ".ccjk");
|
|
24
14
|
}
|
|
25
|
-
function
|
|
26
|
-
const
|
|
27
|
-
|
|
15
|
+
function getInstallPath(pluginType, name) {
|
|
16
|
+
const configDir = getCcjkConfigDir();
|
|
17
|
+
const typeDir = {
|
|
18
|
+
skill: "skills",
|
|
19
|
+
mcp: "mcp-servers",
|
|
20
|
+
agent: "agents",
|
|
21
|
+
hook: "hooks"
|
|
22
|
+
}[pluginType];
|
|
23
|
+
return path__default.join(configDir, typeDir, name);
|
|
28
24
|
}
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
async function copyDirectory(src, dest) {
|
|
26
|
+
await fs.mkdir(dest, { recursive: true });
|
|
27
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
const srcPath = path__default.join(src, entry.name);
|
|
30
|
+
const destPath = path__default.join(dest, entry.name);
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
await copyDirectory(srcPath, destPath);
|
|
33
|
+
} else {
|
|
34
|
+
await fs.copyFile(srcPath, destPath);
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
|
-
return true;
|
|
41
37
|
}
|
|
42
|
-
async function
|
|
38
|
+
async function downloadFile(url, destPath) {
|
|
39
|
+
const response = await fetch(url, {
|
|
40
|
+
headers: {
|
|
41
|
+
"User-Agent": "ccjk-cli"
|
|
42
|
+
},
|
|
43
|
+
redirect: "follow"
|
|
44
|
+
});
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new Error(`Failed to download: ${response.statusText}`);
|
|
47
|
+
}
|
|
48
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
49
|
+
await fs.writeFile(destPath, buffer);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function installFromGitHub(sourceInfo, pluginType, options = {}) {
|
|
53
|
+
const { force = false, dryRun = false } = options;
|
|
54
|
+
const { owner, repo, ref = "main", subpath } = sourceInfo;
|
|
43
55
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
const repoInfo = await fetchRepoInfo(owner, repo);
|
|
57
|
+
const defaultBranch = repoInfo.default_branch || "main";
|
|
58
|
+
const actualRef = ref || defaultBranch;
|
|
59
|
+
const installPath = getInstallPath(pluginType, repo);
|
|
60
|
+
if (!force) {
|
|
61
|
+
try {
|
|
62
|
+
await fs.access(installPath);
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
source: sourceInfo.originalUrl,
|
|
66
|
+
sourceType: "github",
|
|
67
|
+
pluginType,
|
|
68
|
+
error: `Plugin already exists at ${installPath}. Use --force to overwrite.`
|
|
69
|
+
};
|
|
70
|
+
} catch {
|
|
55
71
|
}
|
|
56
|
-
return false;
|
|
57
72
|
}
|
|
58
|
-
|
|
73
|
+
if (dryRun) {
|
|
74
|
+
const files = await listRepoFiles(owner, repo, actualRef, subpath);
|
|
75
|
+
return {
|
|
76
|
+
success: true,
|
|
77
|
+
source: sourceInfo.originalUrl,
|
|
78
|
+
sourceType: "github",
|
|
79
|
+
pluginType,
|
|
80
|
+
installedPath: installPath,
|
|
81
|
+
details: {
|
|
82
|
+
name: repo,
|
|
83
|
+
version: actualRef,
|
|
84
|
+
description: repoInfo.description || void 0,
|
|
85
|
+
files: files.slice(0, 20)
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const tempDir = await downloadRepo(owner, repo, actualRef);
|
|
90
|
+
const sourcePath = subpath ? path__default.join(tempDir, subpath) : tempDir;
|
|
91
|
+
await fs.mkdir(path__default.dirname(installPath), { recursive: true });
|
|
92
|
+
await copyDirectory(sourcePath, installPath);
|
|
93
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
94
|
+
const installedFiles = await listInstalledFiles$1(installPath);
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
source: sourceInfo.originalUrl,
|
|
98
|
+
sourceType: "github",
|
|
99
|
+
pluginType,
|
|
100
|
+
installedPath: installPath,
|
|
101
|
+
details: {
|
|
102
|
+
name: repo,
|
|
103
|
+
version: actualRef,
|
|
104
|
+
description: repoInfo.description || void 0,
|
|
105
|
+
files: installedFiles
|
|
106
|
+
}
|
|
107
|
+
};
|
|
59
108
|
} catch (error) {
|
|
60
|
-
|
|
61
|
-
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
source: sourceInfo.originalUrl,
|
|
112
|
+
sourceType: "github",
|
|
113
|
+
pluginType,
|
|
114
|
+
error: error instanceof Error ? error.message : String(error)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function fetchRepoInfo(owner, repo) {
|
|
119
|
+
const url = `https://api.github.com/repos/${owner}/${repo}`;
|
|
120
|
+
const response = await fetch(url, {
|
|
121
|
+
headers: {
|
|
122
|
+
"Accept": "application/vnd.github.v3+json",
|
|
123
|
+
"User-Agent": "ccjk-cli"
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
if (response.status === 404) {
|
|
128
|
+
throw new Error(`Repository not found: ${owner}/${repo}`);
|
|
62
129
|
}
|
|
63
|
-
|
|
130
|
+
throw new Error(`Failed to fetch repository info: ${response.statusText}`);
|
|
64
131
|
}
|
|
132
|
+
return response.json();
|
|
65
133
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
134
|
+
async function listRepoFiles(owner, repo, ref, subpath) {
|
|
135
|
+
const treePath = subpath || "";
|
|
136
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`;
|
|
137
|
+
const response = await fetch(url, {
|
|
138
|
+
headers: {
|
|
139
|
+
"Accept": "application/vnd.github.v3+json",
|
|
140
|
+
"User-Agent": "ccjk-cli"
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const data = await response.json();
|
|
147
|
+
const files = (data.tree || []).filter((item) => item.type === "blob").map((item) => item.path).filter((filePath) => !treePath || filePath.startsWith(treePath));
|
|
148
|
+
return files;
|
|
149
|
+
}
|
|
150
|
+
async function downloadRepo(owner, repo, ref) {
|
|
151
|
+
const zipUrl = `https://github.com/${owner}/${repo}/archive/${ref}.zip`;
|
|
152
|
+
const tempDir = path__default.join(os__default.tmpdir(), `ccjk-${repo}-${Date.now()}`);
|
|
153
|
+
const zipPath = path__default.join(tempDir, "repo.zip");
|
|
154
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
155
|
+
await downloadFile(zipUrl, zipPath);
|
|
156
|
+
const extractedDir = await extractZip(zipPath, tempDir);
|
|
157
|
+
await fs.unlink(zipPath);
|
|
158
|
+
return extractedDir;
|
|
69
159
|
}
|
|
70
|
-
function
|
|
71
|
-
const
|
|
72
|
-
|
|
160
|
+
async function extractZip(zipPath, destDir) {
|
|
161
|
+
const { exec } = await import('node:child_process');
|
|
162
|
+
const { promisify } = await import('node:util');
|
|
163
|
+
const execAsync = promisify(exec);
|
|
164
|
+
try {
|
|
165
|
+
await execAsync(`unzip -q "${zipPath}" -d "${destDir}"`);
|
|
166
|
+
} catch {
|
|
167
|
+
try {
|
|
168
|
+
await execAsync(`tar -xf "${zipPath}" -C "${destDir}"`);
|
|
169
|
+
} catch {
|
|
170
|
+
throw new Error("Failed to extract archive. Please ensure unzip or tar is installed.");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const entries = await fs.readdir(destDir);
|
|
174
|
+
const extractedDir = entries.find((entry) => !entry.endsWith(".zip"));
|
|
175
|
+
if (!extractedDir) {
|
|
176
|
+
throw new Error("Failed to find extracted directory");
|
|
177
|
+
}
|
|
178
|
+
return path__default.join(destDir, extractedDir);
|
|
179
|
+
}
|
|
180
|
+
async function listInstalledFiles$1(dir) {
|
|
181
|
+
const files = [];
|
|
182
|
+
async function walk(currentDir, prefix = "") {
|
|
183
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
186
|
+
if (entry.isDirectory()) {
|
|
187
|
+
await walk(path__default.join(currentDir, entry.name), relativePath);
|
|
188
|
+
} else {
|
|
189
|
+
files.push(relativePath);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
await walk(dir);
|
|
194
|
+
return files;
|
|
73
195
|
}
|
|
74
|
-
|
|
196
|
+
|
|
197
|
+
async function installFromLocal(sourceInfo, pluginType, options = {}) {
|
|
198
|
+
const { force = false, dryRun = false } = options;
|
|
199
|
+
const { absolutePath, originalPath } = sourceInfo;
|
|
75
200
|
try {
|
|
76
|
-
|
|
201
|
+
const stat = await fs.stat(absolutePath);
|
|
202
|
+
const isFile = stat.isFile();
|
|
203
|
+
const isDirectory = stat.isDirectory();
|
|
204
|
+
if (!isFile && !isDirectory) {
|
|
77
205
|
return {
|
|
78
|
-
skill: skillName,
|
|
79
206
|
success: false,
|
|
80
|
-
|
|
207
|
+
source: originalPath,
|
|
208
|
+
sourceType: "local",
|
|
209
|
+
pluginType,
|
|
210
|
+
error: `Source path is neither a file nor a directory: ${absolutePath}`
|
|
81
211
|
};
|
|
82
212
|
}
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
if (!
|
|
213
|
+
const pluginName = path__default.basename(absolutePath, path__default.extname(absolutePath));
|
|
214
|
+
const installPath = getInstallPath(pluginType, pluginName);
|
|
215
|
+
if (!force) {
|
|
216
|
+
try {
|
|
217
|
+
await fs.access(installPath);
|
|
218
|
+
return {
|
|
219
|
+
success: false,
|
|
220
|
+
source: originalPath,
|
|
221
|
+
sourceType: "local",
|
|
222
|
+
pluginType,
|
|
223
|
+
error: `Plugin already exists at ${installPath}. Use --force to overwrite.`
|
|
224
|
+
};
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const files = isFile ? [path__default.basename(absolutePath)] : await listFiles(absolutePath);
|
|
229
|
+
const pluginInfo = await readPluginInfo(absolutePath, isFile);
|
|
230
|
+
if (dryRun) {
|
|
86
231
|
return {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
232
|
+
success: true,
|
|
233
|
+
source: originalPath,
|
|
234
|
+
sourceType: "local",
|
|
235
|
+
pluginType,
|
|
236
|
+
installedPath: installPath,
|
|
237
|
+
details: {
|
|
238
|
+
name: pluginInfo.name || pluginName,
|
|
239
|
+
version: pluginInfo.version,
|
|
240
|
+
description: pluginInfo.description,
|
|
241
|
+
files
|
|
242
|
+
}
|
|
90
243
|
};
|
|
91
244
|
}
|
|
245
|
+
if (isFile) {
|
|
246
|
+
await fs.mkdir(path__default.dirname(installPath), { recursive: true });
|
|
247
|
+
if (absolutePath.endsWith(".md")) {
|
|
248
|
+
await fs.copyFile(absolutePath, installPath);
|
|
249
|
+
} else {
|
|
250
|
+
await fs.mkdir(installPath, { recursive: true });
|
|
251
|
+
await fs.copyFile(
|
|
252
|
+
absolutePath,
|
|
253
|
+
path__default.join(installPath, path__default.basename(absolutePath))
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
await fs.mkdir(path__default.dirname(installPath), { recursive: true });
|
|
258
|
+
await copyDirectory(absolutePath, installPath);
|
|
259
|
+
}
|
|
260
|
+
const installedFiles = isFile ? [path__default.basename(absolutePath)] : await listFiles(installPath);
|
|
92
261
|
return {
|
|
93
|
-
|
|
94
|
-
|
|
262
|
+
success: true,
|
|
263
|
+
source: originalPath,
|
|
264
|
+
sourceType: "local",
|
|
265
|
+
pluginType,
|
|
266
|
+
installedPath: installPath,
|
|
267
|
+
details: {
|
|
268
|
+
name: pluginInfo.name || pluginName,
|
|
269
|
+
version: pluginInfo.version,
|
|
270
|
+
description: pluginInfo.description,
|
|
271
|
+
files: installedFiles
|
|
272
|
+
}
|
|
95
273
|
};
|
|
96
274
|
} catch (error) {
|
|
97
275
|
return {
|
|
98
|
-
skill: skillName,
|
|
99
276
|
success: false,
|
|
100
|
-
|
|
277
|
+
source: originalPath,
|
|
278
|
+
sourceType: "local",
|
|
279
|
+
pluginType,
|
|
280
|
+
error: error instanceof Error ? error.message : String(error)
|
|
101
281
|
};
|
|
102
282
|
}
|
|
103
283
|
}
|
|
104
|
-
async function
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
284
|
+
async function listFiles(dir) {
|
|
285
|
+
const files = [];
|
|
286
|
+
async function walk(currentDir, prefix = "") {
|
|
287
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
288
|
+
for (const entry of entries) {
|
|
289
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
293
|
+
if (entry.isDirectory()) {
|
|
294
|
+
await walk(path__default.join(currentDir, entry.name), relativePath);
|
|
295
|
+
} else {
|
|
296
|
+
files.push(relativePath);
|
|
297
|
+
}
|
|
114
298
|
}
|
|
115
299
|
}
|
|
116
|
-
|
|
300
|
+
await walk(dir);
|
|
301
|
+
return files;
|
|
117
302
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
303
|
+
async function readPluginInfo(sourcePath, isFile) {
|
|
304
|
+
if (isFile) {
|
|
305
|
+
const name = path__default.basename(sourcePath, path__default.extname(sourcePath));
|
|
306
|
+
return { name };
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const packageJsonPath = path__default.join(sourcePath, "package.json");
|
|
310
|
+
const content = await fs.readFile(packageJsonPath, "utf-8");
|
|
311
|
+
const packageJson = JSON.parse(content);
|
|
312
|
+
return {
|
|
313
|
+
name: packageJson.name,
|
|
314
|
+
version: packageJson.version,
|
|
315
|
+
description: packageJson.description
|
|
316
|
+
};
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
const metaFiles = ["SKILL.md", "skill.md", "README.md", "readme.md"];
|
|
320
|
+
for (const metaFile of metaFiles) {
|
|
321
|
+
try {
|
|
322
|
+
const metaPath = path__default.join(sourcePath, metaFile);
|
|
323
|
+
const content = await fs.readFile(metaPath, "utf-8");
|
|
324
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
325
|
+
if (titleMatch) {
|
|
326
|
+
return { name: titleMatch[1].trim() };
|
|
327
|
+
}
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return {};
|
|
124
332
|
}
|
|
125
|
-
|
|
333
|
+
|
|
334
|
+
const execAsync = promisify(exec);
|
|
335
|
+
async function installFromNpm(sourceInfo, pluginType, options = {}) {
|
|
336
|
+
const { force = false, dryRun = false } = options;
|
|
337
|
+
const { packageName, version } = sourceInfo;
|
|
126
338
|
try {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
339
|
+
const packageInfo = await fetchPackageInfo(packageName, version);
|
|
340
|
+
const packageVersion = version || packageInfo.version;
|
|
341
|
+
const shortName = getShortName(packageName);
|
|
342
|
+
const installPath = getInstallPath(pluginType, shortName);
|
|
343
|
+
if (!force) {
|
|
344
|
+
try {
|
|
345
|
+
await fs.access(installPath);
|
|
346
|
+
return {
|
|
347
|
+
success: false,
|
|
348
|
+
source: packageName,
|
|
349
|
+
sourceType: "npm",
|
|
350
|
+
pluginType,
|
|
351
|
+
error: `Plugin already exists at ${installPath}. Use --force to overwrite.`
|
|
352
|
+
};
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
130
355
|
}
|
|
131
|
-
|
|
132
|
-
|
|
356
|
+
if (dryRun) {
|
|
357
|
+
const files = packageInfo.files || ["(package files)"];
|
|
358
|
+
return {
|
|
359
|
+
success: true,
|
|
360
|
+
source: packageName,
|
|
361
|
+
sourceType: "npm",
|
|
362
|
+
pluginType,
|
|
363
|
+
installedPath: installPath,
|
|
364
|
+
details: {
|
|
365
|
+
name: packageName,
|
|
366
|
+
version: packageVersion,
|
|
367
|
+
description: packageInfo.description,
|
|
368
|
+
files
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (pluginType === "mcp") {
|
|
373
|
+
await installMcpPackage(packageName, packageVersion, installPath);
|
|
374
|
+
} else {
|
|
375
|
+
await installGenericPackage(packageName, packageVersion, installPath);
|
|
376
|
+
}
|
|
377
|
+
const installedFiles = await listInstalledFiles(installPath);
|
|
378
|
+
return {
|
|
379
|
+
success: true,
|
|
380
|
+
source: packageName,
|
|
381
|
+
sourceType: "npm",
|
|
382
|
+
pluginType,
|
|
383
|
+
installedPath: installPath,
|
|
384
|
+
details: {
|
|
385
|
+
name: packageName,
|
|
386
|
+
version: packageVersion,
|
|
387
|
+
description: packageInfo.description,
|
|
388
|
+
files: installedFiles
|
|
389
|
+
}
|
|
390
|
+
};
|
|
133
391
|
} catch (error) {
|
|
134
|
-
|
|
135
|
-
|
|
392
|
+
return {
|
|
393
|
+
success: false,
|
|
394
|
+
source: packageName,
|
|
395
|
+
sourceType: "npm",
|
|
396
|
+
pluginType,
|
|
397
|
+
error: error instanceof Error ? error.message : String(error)
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async function fetchPackageInfo(packageName, version) {
|
|
402
|
+
const url = version ? `https://registry.npmjs.org/${packageName}/${version}` : `https://registry.npmjs.org/${packageName}/latest`;
|
|
403
|
+
const response = await fetch(url);
|
|
404
|
+
if (!response.ok) {
|
|
405
|
+
if (response.status === 404) {
|
|
406
|
+
throw new Error(`Package not found: ${packageName}`);
|
|
136
407
|
}
|
|
137
|
-
|
|
408
|
+
throw new Error(`Failed to fetch package info: ${response.statusText}`);
|
|
138
409
|
}
|
|
410
|
+
const data = await response.json();
|
|
411
|
+
return {
|
|
412
|
+
version: data.version,
|
|
413
|
+
description: data.description,
|
|
414
|
+
files: data.files
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function getShortName(packageName) {
|
|
418
|
+
if (packageName.startsWith("@")) {
|
|
419
|
+
const parts = packageName.split("/");
|
|
420
|
+
return parts[1] || packageName;
|
|
421
|
+
}
|
|
422
|
+
return packageName;
|
|
139
423
|
}
|
|
140
|
-
function
|
|
424
|
+
async function installMcpPackage(packageName, version, installPath) {
|
|
425
|
+
await fs.mkdir(installPath, { recursive: true });
|
|
426
|
+
const packageJson = {
|
|
427
|
+
name: `ccjk-mcp-${getShortName(packageName)}`,
|
|
428
|
+
version: "1.0.0",
|
|
429
|
+
private: true,
|
|
430
|
+
dependencies: {
|
|
431
|
+
[packageName]: version
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
await fs.writeFile(
|
|
435
|
+
path__default.join(installPath, "package.json"),
|
|
436
|
+
JSON.stringify(packageJson, null, 2)
|
|
437
|
+
);
|
|
141
438
|
try {
|
|
142
|
-
|
|
143
|
-
writeFileSync(statePath, JSON.stringify(status, null, 2), "utf-8");
|
|
439
|
+
await execAsync("npm install", { cwd: installPath });
|
|
144
440
|
} catch (error) {
|
|
145
|
-
|
|
146
|
-
|
|
441
|
+
try {
|
|
442
|
+
await execAsync("pnpm install", { cwd: installPath });
|
|
443
|
+
} catch {
|
|
444
|
+
throw new Error(
|
|
445
|
+
`Failed to install package. Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
446
|
+
);
|
|
147
447
|
}
|
|
148
448
|
}
|
|
149
449
|
}
|
|
150
|
-
function
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
154
|
-
|
|
450
|
+
async function installGenericPackage(packageName, version, installPath) {
|
|
451
|
+
const registryUrl = `https://registry.npmjs.org/${packageName}/${version}`;
|
|
452
|
+
const response = await fetch(registryUrl);
|
|
453
|
+
if (!response.ok) {
|
|
454
|
+
throw new Error(`Failed to fetch package info: ${response.statusText}`);
|
|
455
|
+
}
|
|
456
|
+
const data = await response.json();
|
|
457
|
+
const tarballUrl = data.dist?.tarball;
|
|
458
|
+
if (!tarballUrl) {
|
|
459
|
+
throw new Error("Failed to get tarball URL");
|
|
460
|
+
}
|
|
461
|
+
const tarballResponse = await fetch(tarballUrl);
|
|
462
|
+
if (!tarballResponse.ok) {
|
|
463
|
+
throw new Error(`Failed to download tarball: ${tarballResponse.statusText}`);
|
|
464
|
+
}
|
|
465
|
+
await fs.mkdir(installPath, { recursive: true });
|
|
466
|
+
const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
|
|
467
|
+
const tarballPath = path__default.join(installPath, "package.tgz");
|
|
468
|
+
await fs.writeFile(tarballPath, tarballBuffer);
|
|
469
|
+
try {
|
|
470
|
+
await execAsync(`tar -xzf package.tgz --strip-components=1`, { cwd: installPath });
|
|
471
|
+
} finally {
|
|
472
|
+
await fs.unlink(tarballPath).catch(() => {
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async function listInstalledFiles(dir) {
|
|
477
|
+
const files = [];
|
|
478
|
+
async function walk(currentDir, prefix = "") {
|
|
479
|
+
try {
|
|
480
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
481
|
+
for (const entry of entries) {
|
|
482
|
+
if (entry.name === "node_modules")
|
|
483
|
+
continue;
|
|
484
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
485
|
+
if (entry.isDirectory()) {
|
|
486
|
+
await walk(path__default.join(currentDir, entry.name), relativePath);
|
|
487
|
+
} else {
|
|
488
|
+
files.push(relativePath);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
} catch {
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
await walk(dir);
|
|
495
|
+
return files;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function parseSource(source) {
|
|
499
|
+
const trimmed = source.trim();
|
|
500
|
+
const githubInfo = parseGitHubSource(trimmed);
|
|
501
|
+
if (githubInfo) {
|
|
502
|
+
return githubInfo;
|
|
503
|
+
}
|
|
504
|
+
const localInfo = parseLocalSource(trimmed);
|
|
505
|
+
if (localInfo) {
|
|
506
|
+
return localInfo;
|
|
507
|
+
}
|
|
508
|
+
return parseNpmSource(trimmed);
|
|
509
|
+
}
|
|
510
|
+
function parseGitHubSource(source) {
|
|
511
|
+
const prefixMatch = source.match(/^(?:github|gh):([^/]+)\/([^#@/]+)(?:#(.+))?$/);
|
|
512
|
+
if (prefixMatch) {
|
|
513
|
+
return {
|
|
514
|
+
type: "github",
|
|
515
|
+
owner: prefixMatch[1],
|
|
516
|
+
repo: prefixMatch[2],
|
|
517
|
+
ref: prefixMatch[3],
|
|
518
|
+
originalUrl: source
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
const urlMatch = source.match(
|
|
522
|
+
/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+))?(?:\/(.+))?$/
|
|
523
|
+
);
|
|
524
|
+
if (urlMatch) {
|
|
525
|
+
return {
|
|
526
|
+
type: "github",
|
|
527
|
+
owner: urlMatch[1],
|
|
528
|
+
repo: urlMatch[2],
|
|
529
|
+
ref: urlMatch[3],
|
|
530
|
+
subpath: urlMatch[4],
|
|
531
|
+
originalUrl: source
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const shortMatch = source.match(/^([\w-]+)\/([\w.-]+)(?:#(.+))?$/);
|
|
535
|
+
if (shortMatch && !source.startsWith(".") && !source.startsWith("/") && !source.startsWith("~")) {
|
|
536
|
+
return {
|
|
537
|
+
type: "github",
|
|
538
|
+
owner: shortMatch[1],
|
|
539
|
+
repo: shortMatch[2],
|
|
540
|
+
ref: shortMatch[3],
|
|
541
|
+
originalUrl: source
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
function parseLocalSource(source) {
|
|
547
|
+
if (source.startsWith("./") || source.startsWith("../") || source.startsWith("/") || source.startsWith("~/")) {
|
|
548
|
+
let absolutePath;
|
|
549
|
+
if (source.startsWith("~/")) {
|
|
550
|
+
absolutePath = path__default.join(os__default.homedir(), source.slice(2));
|
|
551
|
+
} else if (path__default.isAbsolute(source)) {
|
|
552
|
+
absolutePath = source;
|
|
553
|
+
} else {
|
|
554
|
+
absolutePath = path__default.resolve(process.cwd(), source);
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
type: "local",
|
|
558
|
+
absolutePath,
|
|
559
|
+
originalPath: source
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
if (/^[a-z]:[\\/]/i.test(source)) {
|
|
563
|
+
return {
|
|
564
|
+
type: "local",
|
|
565
|
+
absolutePath: path__default.resolve(source),
|
|
566
|
+
originalPath: source
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
function parseNpmSource(source) {
|
|
572
|
+
const npmPrefixMatch = source.match(/^npm:(.+)$/);
|
|
573
|
+
const packageStr = npmPrefixMatch ? npmPrefixMatch[1] : source;
|
|
574
|
+
const scopedMatch = packageStr.match(/^(@[^/]+)\/([^@]+)(?:@(.+))?$/);
|
|
575
|
+
if (scopedMatch) {
|
|
576
|
+
return {
|
|
577
|
+
type: "npm",
|
|
578
|
+
scope: scopedMatch[1],
|
|
579
|
+
packageName: `${scopedMatch[1]}/${scopedMatch[2]}`,
|
|
580
|
+
version: scopedMatch[3]
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
const versionMatch = packageStr.match(/^([^@]+)@(.+)$/);
|
|
584
|
+
if (versionMatch) {
|
|
585
|
+
return {
|
|
586
|
+
type: "npm",
|
|
587
|
+
packageName: versionMatch[1],
|
|
588
|
+
version: versionMatch[2]
|
|
589
|
+
};
|
|
155
590
|
}
|
|
156
591
|
return {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
loadedSkills: [],
|
|
160
|
-
needsActivation: true,
|
|
161
|
-
lastActivation: void 0
|
|
592
|
+
type: "npm",
|
|
593
|
+
packageName: packageStr
|
|
162
594
|
};
|
|
163
595
|
}
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
596
|
+
function buildGitHubRawUrl(info, filePath) {
|
|
597
|
+
const ref = info.ref || "main";
|
|
598
|
+
return `https://raw.githubusercontent.com/${info.owner}/${info.repo}/${ref}/${filePath}`;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function detectPluginType(sourceInfo) {
|
|
602
|
+
const result = await detectPluginTypeWithConfidence(sourceInfo);
|
|
603
|
+
return result.type;
|
|
604
|
+
}
|
|
605
|
+
async function detectPluginTypeWithConfidence(sourceInfo) {
|
|
606
|
+
switch (sourceInfo.type) {
|
|
607
|
+
case "github":
|
|
608
|
+
return detectFromGitHub(sourceInfo);
|
|
609
|
+
case "npm":
|
|
610
|
+
return detectFromNpm(sourceInfo);
|
|
611
|
+
case "local":
|
|
612
|
+
return detectFromLocal(sourceInfo);
|
|
613
|
+
default:
|
|
614
|
+
return { type: "skill", confidence: "low", reason: "Unknown source type" };
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function detectFromGitHub(info) {
|
|
618
|
+
const repoNameResult = detectFromRepoName(info.repo);
|
|
619
|
+
if (repoNameResult.confidence === "high") {
|
|
620
|
+
return repoNameResult;
|
|
168
621
|
}
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
622
|
+
try {
|
|
623
|
+
const packageJsonUrl = buildGitHubRawUrl(info, "package.json");
|
|
624
|
+
const response = await fetch(packageJsonUrl);
|
|
625
|
+
if (response.ok) {
|
|
626
|
+
const packageJson = await response.json();
|
|
627
|
+
const pkgResult = detectFromPackageJson(packageJson);
|
|
628
|
+
if (pkgResult.confidence !== "low") {
|
|
629
|
+
return pkgResult;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
const filePatterns = [
|
|
635
|
+
{ file: "SKILL.md", type: "skill" },
|
|
636
|
+
{ file: "skill.md", type: "skill" },
|
|
637
|
+
{ file: "AGENT.md", type: "agent" },
|
|
638
|
+
{ file: "agent.md", type: "agent" },
|
|
639
|
+
{ file: "mcp.json", type: "mcp" },
|
|
640
|
+
{ file: "hook.json", type: "hook" }
|
|
641
|
+
];
|
|
642
|
+
for (const pattern of filePatterns) {
|
|
643
|
+
try {
|
|
644
|
+
const fileUrl = buildGitHubRawUrl(info, pattern.file);
|
|
645
|
+
const response = await fetch(fileUrl, { method: "HEAD" });
|
|
646
|
+
if (response.ok) {
|
|
647
|
+
return {
|
|
648
|
+
type: pattern.type,
|
|
649
|
+
confidence: "high",
|
|
650
|
+
reason: `Found ${pattern.file}`
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
} catch {
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return repoNameResult.confidence !== "low" ? repoNameResult : { type: "skill", confidence: "low", reason: "Default type" };
|
|
657
|
+
}
|
|
658
|
+
async function detectFromNpm(info) {
|
|
659
|
+
const nameResult = detectFromPackageName(info.packageName);
|
|
660
|
+
if (nameResult.confidence === "high") {
|
|
661
|
+
return nameResult;
|
|
662
|
+
}
|
|
663
|
+
try {
|
|
664
|
+
const registryUrl = `https://registry.npmjs.org/${info.packageName}`;
|
|
665
|
+
const response = await fetch(registryUrl);
|
|
666
|
+
if (response.ok) {
|
|
667
|
+
const data = await response.json();
|
|
668
|
+
const latestVersion = data["dist-tags"]?.latest;
|
|
669
|
+
if (latestVersion && data.versions?.[latestVersion]) {
|
|
670
|
+
const packageJson = data.versions[latestVersion];
|
|
671
|
+
const pkgResult = detectFromPackageJson(packageJson);
|
|
672
|
+
if (pkgResult.confidence !== "low") {
|
|
673
|
+
return pkgResult;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
679
|
+
return nameResult.confidence !== "low" ? nameResult : { type: "skill", confidence: "low", reason: "Default type" };
|
|
680
|
+
}
|
|
681
|
+
async function detectFromLocal(info) {
|
|
682
|
+
const { absolutePath } = info;
|
|
683
|
+
const dirName = path__default.basename(absolutePath);
|
|
684
|
+
const dirResult = detectFromRepoName(dirName);
|
|
685
|
+
if (dirResult.confidence === "high") {
|
|
686
|
+
return dirResult;
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
const packageJsonPath = path__default.join(absolutePath, "package.json");
|
|
690
|
+
const content = await fs.readFile(packageJsonPath, "utf-8");
|
|
691
|
+
const packageJson = JSON.parse(content);
|
|
692
|
+
const pkgResult = detectFromPackageJson(packageJson);
|
|
693
|
+
if (pkgResult.confidence !== "low") {
|
|
694
|
+
return pkgResult;
|
|
695
|
+
}
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
const filePatterns = [
|
|
699
|
+
{ file: "SKILL.md", type: "skill" },
|
|
700
|
+
{ file: "skill.md", type: "skill" },
|
|
701
|
+
{ file: "AGENT.md", type: "agent" },
|
|
702
|
+
{ file: "agent.md", type: "agent" },
|
|
703
|
+
{ file: "mcp.json", type: "mcp" },
|
|
704
|
+
{ file: "hook.json", type: "hook" }
|
|
705
|
+
];
|
|
706
|
+
for (const pattern of filePatterns) {
|
|
707
|
+
try {
|
|
708
|
+
const filePath = path__default.join(absolutePath, pattern.file);
|
|
709
|
+
await fs.access(filePath);
|
|
172
710
|
return {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
needsActivation: true,
|
|
177
|
-
lastActivation: void 0
|
|
711
|
+
type: pattern.type,
|
|
712
|
+
confidence: "high",
|
|
713
|
+
reason: `Found ${pattern.file}`
|
|
178
714
|
};
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
try {
|
|
719
|
+
const stat = await fs.stat(absolutePath);
|
|
720
|
+
if (stat.isFile() && absolutePath.endsWith(".md")) {
|
|
721
|
+
return {
|
|
722
|
+
type: "skill",
|
|
723
|
+
confidence: "high",
|
|
724
|
+
reason: "Single markdown file"
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
} catch {
|
|
728
|
+
}
|
|
729
|
+
return dirResult.confidence !== "low" ? dirResult : { type: "skill", confidence: "low", reason: "Default type" };
|
|
730
|
+
}
|
|
731
|
+
function detectFromRepoName(name) {
|
|
732
|
+
const lowerName = name.toLowerCase();
|
|
733
|
+
if (lowerName.includes("mcp-server") || lowerName.includes("mcp_server") || lowerName.startsWith("mcp-") || lowerName.endsWith("-mcp")) {
|
|
734
|
+
return { type: "mcp", confidence: "high", reason: `Name contains MCP pattern: ${name}` };
|
|
735
|
+
}
|
|
736
|
+
if (lowerName.includes("agent") || lowerName.includes("-agent") || lowerName.endsWith("-agent")) {
|
|
737
|
+
return { type: "agent", confidence: "medium", reason: `Name contains agent pattern: ${name}` };
|
|
738
|
+
}
|
|
739
|
+
if (lowerName.includes("hook") || lowerName.includes("-hook") || lowerName.endsWith("-hook")) {
|
|
740
|
+
return { type: "hook", confidence: "medium", reason: `Name contains hook pattern: ${name}` };
|
|
741
|
+
}
|
|
742
|
+
if (lowerName.includes("skill") || lowerName.includes("-skill") || lowerName.endsWith("-skill")) {
|
|
743
|
+
return { type: "skill", confidence: "medium", reason: `Name contains skill pattern: ${name}` };
|
|
744
|
+
}
|
|
745
|
+
return { type: "skill", confidence: "low", reason: "No pattern matched" };
|
|
746
|
+
}
|
|
747
|
+
function detectFromPackageName(packageName) {
|
|
748
|
+
const lowerName = packageName.toLowerCase();
|
|
749
|
+
if (lowerName.startsWith("@modelcontextprotocol/")) {
|
|
750
|
+
return { type: "mcp", confidence: "high", reason: "MCP official scope" };
|
|
751
|
+
}
|
|
752
|
+
if (lowerName.includes("mcp-server") || lowerName.includes("mcp_server")) {
|
|
753
|
+
return { type: "mcp", confidence: "high", reason: "MCP server pattern in name" };
|
|
754
|
+
}
|
|
755
|
+
return detectFromRepoName(packageName);
|
|
756
|
+
}
|
|
757
|
+
function detectFromPackageJson(packageJson) {
|
|
758
|
+
if (packageJson.ccjk && typeof packageJson.ccjk === "object") {
|
|
759
|
+
const ccjk = packageJson.ccjk;
|
|
760
|
+
if (ccjk.type && typeof ccjk.type === "string") {
|
|
761
|
+
const type = ccjk.type;
|
|
762
|
+
if (["skill", "mcp", "agent", "hook"].includes(type)) {
|
|
763
|
+
return { type, confidence: "high", reason: "Explicit ccjk.type field" };
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (Array.isArray(packageJson.keywords)) {
|
|
768
|
+
const keywords = packageJson.keywords;
|
|
769
|
+
const keywordMap = {
|
|
770
|
+
"mcp-server": "mcp",
|
|
771
|
+
"mcp": "mcp",
|
|
772
|
+
"model-context-protocol": "mcp",
|
|
773
|
+
"ccjk-skill": "skill",
|
|
774
|
+
"claude-skill": "skill",
|
|
775
|
+
"ccjk-agent": "agent",
|
|
776
|
+
"ccjk-hook": "hook"
|
|
777
|
+
};
|
|
778
|
+
for (const keyword of keywords) {
|
|
779
|
+
const type = keywordMap[keyword.toLowerCase()];
|
|
780
|
+
if (type) {
|
|
781
|
+
return { type, confidence: "high", reason: `Keyword: ${keyword}` };
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (packageJson.bin) {
|
|
786
|
+
const binKeys = Object.keys(packageJson.bin);
|
|
787
|
+
for (const key of binKeys) {
|
|
788
|
+
if (key.includes("mcp") || key.includes("server")) {
|
|
789
|
+
return { type: "mcp", confidence: "medium", reason: `Binary name: ${key}` };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (typeof packageJson.main === "string") {
|
|
794
|
+
const main = packageJson.main.toLowerCase();
|
|
795
|
+
if (main.includes("server") || main.includes("mcp")) {
|
|
796
|
+
return { type: "mcp", confidence: "low", reason: `Main file: ${packageJson.main}` };
|
|
179
797
|
}
|
|
180
798
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
799
|
+
return { type: "skill", confidence: "low", reason: "No specific indicators" };
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async function addCommand(source, options = {}) {
|
|
803
|
+
const { force = false, dryRun = false, json = false, lang = "en" } = options;
|
|
804
|
+
const i18n = getI18n(lang);
|
|
805
|
+
const spinner = json ? null : ora(i18n.parsing).start();
|
|
806
|
+
let sourceInfo;
|
|
807
|
+
try {
|
|
808
|
+
sourceInfo = parseSource(source);
|
|
809
|
+
spinner?.succeed(i18n.parsed(sourceInfo.type));
|
|
810
|
+
} catch (error) {
|
|
811
|
+
spinner?.fail(i18n.parseFailed);
|
|
812
|
+
const result = {
|
|
813
|
+
success: false,
|
|
814
|
+
source,
|
|
815
|
+
sourceType: "local",
|
|
816
|
+
pluginType: "skill",
|
|
817
|
+
error: error instanceof Error ? error.message : String(error)
|
|
818
|
+
};
|
|
819
|
+
if (json) {
|
|
820
|
+
console.log(JSON.stringify(result, null, 2));
|
|
821
|
+
} else {
|
|
822
|
+
console.error(ansis.red(`
|
|
823
|
+
${i18n.error}: ${result.error}
|
|
824
|
+
`));
|
|
825
|
+
}
|
|
826
|
+
return result;
|
|
827
|
+
}
|
|
828
|
+
let pluginType = options.type;
|
|
829
|
+
if (!pluginType) {
|
|
830
|
+
spinner?.start(i18n.detectingType);
|
|
831
|
+
try {
|
|
832
|
+
pluginType = await detectPluginType(sourceInfo);
|
|
833
|
+
spinner?.succeed(i18n.detectedType(pluginType));
|
|
834
|
+
} catch {
|
|
835
|
+
spinner?.info(i18n.defaultType);
|
|
836
|
+
pluginType = "skill";
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
spinner?.start(dryRun ? i18n.previewInstall : i18n.installing);
|
|
840
|
+
try {
|
|
841
|
+
let result;
|
|
842
|
+
switch (sourceInfo.type) {
|
|
843
|
+
case "github":
|
|
844
|
+
result = await installFromGitHub(sourceInfo, pluginType, { force, dryRun });
|
|
845
|
+
break;
|
|
846
|
+
case "npm":
|
|
847
|
+
result = await installFromNpm(sourceInfo, pluginType, { force, dryRun });
|
|
848
|
+
break;
|
|
849
|
+
case "local":
|
|
850
|
+
result = await installFromLocal(sourceInfo, pluginType, { force, dryRun });
|
|
851
|
+
break;
|
|
852
|
+
default:
|
|
853
|
+
throw new Error(`Unsupported source type: ${sourceInfo.type}`);
|
|
854
|
+
}
|
|
855
|
+
if (result.success) {
|
|
856
|
+
spinner?.succeed(dryRun ? i18n.previewSuccess : i18n.installSuccess);
|
|
857
|
+
} else {
|
|
858
|
+
spinner?.fail(i18n.installFailed);
|
|
859
|
+
}
|
|
860
|
+
if (json) {
|
|
861
|
+
console.log(JSON.stringify(result, null, 2));
|
|
862
|
+
} else if (result.success && !dryRun) {
|
|
863
|
+
printSuccessMessage(result, i18n);
|
|
864
|
+
} else if (result.success && dryRun) {
|
|
865
|
+
printPreviewMessage(result, i18n);
|
|
866
|
+
} else {
|
|
867
|
+
console.error(ansis.red(`
|
|
868
|
+
${i18n.error}: ${result.error}
|
|
869
|
+
`));
|
|
870
|
+
}
|
|
871
|
+
return result;
|
|
872
|
+
} catch (error) {
|
|
873
|
+
spinner?.fail(i18n.installFailed);
|
|
874
|
+
const result = {
|
|
875
|
+
success: false,
|
|
876
|
+
source,
|
|
877
|
+
sourceType: sourceInfo.type,
|
|
878
|
+
pluginType,
|
|
879
|
+
error: error instanceof Error ? error.message : String(error)
|
|
880
|
+
};
|
|
881
|
+
if (json) {
|
|
882
|
+
console.log(JSON.stringify(result, null, 2));
|
|
883
|
+
} else {
|
|
884
|
+
console.error(ansis.red(`
|
|
885
|
+
${i18n.error}: ${result.error}
|
|
886
|
+
`));
|
|
887
|
+
}
|
|
888
|
+
return result;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function printSuccessMessage(result, i18n) {
|
|
892
|
+
console.log();
|
|
893
|
+
console.log(ansis.green.bold(`\u2713 ${i18n.installed}`));
|
|
894
|
+
console.log();
|
|
895
|
+
console.log(` ${ansis.gray(i18n.name)}: ${result.details?.name || result.source}`);
|
|
896
|
+
if (result.details?.version) {
|
|
897
|
+
console.log(` ${ansis.gray(i18n.version)}: ${result.details.version}`);
|
|
898
|
+
}
|
|
899
|
+
console.log(` ${ansis.gray(i18n.type)}: ${result.pluginType}`);
|
|
900
|
+
console.log(` ${ansis.gray(i18n.path)}: ${result.installedPath}`);
|
|
901
|
+
console.log();
|
|
902
|
+
console.log(ansis.cyan(i18n.nextSteps));
|
|
903
|
+
switch (result.pluginType) {
|
|
904
|
+
case "skill":
|
|
905
|
+
console.log(` ${ansis.gray("\u2022")} ${i18n.skillHint}`);
|
|
906
|
+
break;
|
|
907
|
+
case "mcp":
|
|
908
|
+
console.log(` ${ansis.gray("\u2022")} ${i18n.mcpHint}`);
|
|
909
|
+
break;
|
|
910
|
+
case "agent":
|
|
911
|
+
console.log(` ${ansis.gray("\u2022")} ${i18n.agentHint}`);
|
|
912
|
+
break;
|
|
913
|
+
case "hook":
|
|
914
|
+
console.log(` ${ansis.gray("\u2022")} ${i18n.hookHint}`);
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
console.log();
|
|
918
|
+
}
|
|
919
|
+
function printPreviewMessage(result, i18n) {
|
|
920
|
+
console.log();
|
|
921
|
+
console.log(ansis.yellow.bold(`\u26A1 ${i18n.preview}`));
|
|
922
|
+
console.log();
|
|
923
|
+
console.log(` ${ansis.gray(i18n.source)}: ${result.source}`);
|
|
924
|
+
console.log(` ${ansis.gray(i18n.type)}: ${result.pluginType}`);
|
|
925
|
+
console.log(` ${ansis.gray(i18n.target)}: ${result.installedPath}`);
|
|
926
|
+
if (result.details?.files?.length) {
|
|
927
|
+
console.log(` ${ansis.gray(i18n.files)}:`);
|
|
928
|
+
for (const file of result.details.files.slice(0, 5)) {
|
|
929
|
+
console.log(` ${ansis.gray("\u2022")} ${file}`);
|
|
930
|
+
}
|
|
931
|
+
if (result.details.files.length > 5) {
|
|
932
|
+
console.log(` ${ansis.gray("...")} ${i18n.andMore(result.details.files.length - 5)}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
console.log();
|
|
936
|
+
console.log(ansis.gray(i18n.dryRunNote));
|
|
937
|
+
console.log();
|
|
938
|
+
}
|
|
939
|
+
function getI18n(lang) {
|
|
940
|
+
const texts = {
|
|
941
|
+
"zh-CN": {
|
|
942
|
+
parsing: "\u89E3\u6790\u6765\u6E90...",
|
|
943
|
+
parsed: (type) => `\u6765\u6E90\u7C7B\u578B: ${type}`,
|
|
944
|
+
parseFailed: "\u89E3\u6790\u6765\u6E90\u5931\u8D25",
|
|
945
|
+
detectingType: "\u68C0\u6D4B\u63D2\u4EF6\u7C7B\u578B...",
|
|
946
|
+
detectedType: (type) => `\u68C0\u6D4B\u5230\u7C7B\u578B: ${type}`,
|
|
947
|
+
defaultType: "\u4F7F\u7528\u9ED8\u8BA4\u7C7B\u578B: skill",
|
|
948
|
+
installing: "\u5B89\u88C5\u4E2D...",
|
|
949
|
+
previewInstall: "\u9884\u89C8\u5B89\u88C5...",
|
|
950
|
+
installSuccess: "\u5B89\u88C5\u6210\u529F",
|
|
951
|
+
previewSuccess: "\u9884\u89C8\u5B8C\u6210",
|
|
952
|
+
installFailed: "\u5B89\u88C5\u5931\u8D25",
|
|
953
|
+
error: "\u9519\u8BEF",
|
|
954
|
+
installed: "\u63D2\u4EF6\u5DF2\u5B89\u88C5",
|
|
955
|
+
preview: "\u5B89\u88C5\u9884\u89C8 (dry-run)",
|
|
956
|
+
name: "\u540D\u79F0",
|
|
957
|
+
version: "\u7248\u672C",
|
|
958
|
+
type: "\u7C7B\u578B",
|
|
959
|
+
path: "\u8DEF\u5F84",
|
|
960
|
+
source: "\u6765\u6E90",
|
|
961
|
+
target: "\u76EE\u6807",
|
|
962
|
+
files: "\u6587\u4EF6",
|
|
963
|
+
nextSteps: "\u4E0B\u4E00\u6B65:",
|
|
964
|
+
skillHint: "\u4F7F\u7528 /skill-name \u5728 Claude Code \u4E2D\u8C03\u7528",
|
|
965
|
+
mcpHint: "\u8FD0\u884C ccjk mcp status \u67E5\u770B\u72B6\u6001",
|
|
966
|
+
agentHint: "\u4F7F\u7528 ccjk agent list \u67E5\u770B\u5DF2\u5B89\u88C5\u7684 agent",
|
|
967
|
+
hookHint: "\u8FD0\u884C ccjk hooks list \u67E5\u770B\u5DF2\u5B89\u88C5\u7684 hook",
|
|
968
|
+
dryRunNote: "\u8FD9\u662F\u9884\u89C8\u6A21\u5F0F\uFF0C\u672A\u5B9E\u9645\u5B89\u88C5\u3002\u79FB\u9664 --dry-run \u6267\u884C\u5B89\u88C5\u3002",
|
|
969
|
+
andMore: (n) => `\u8FD8\u6709 ${n} \u4E2A\u6587\u4EF6`
|
|
970
|
+
},
|
|
971
|
+
"en": {
|
|
972
|
+
parsing: "Parsing source...",
|
|
973
|
+
parsed: (type) => `Source type: ${type}`,
|
|
974
|
+
parseFailed: "Failed to parse source",
|
|
975
|
+
detectingType: "Detecting plugin type...",
|
|
976
|
+
detectedType: (type) => `Detected type: ${type}`,
|
|
977
|
+
defaultType: "Using default type: skill",
|
|
978
|
+
installing: "Installing...",
|
|
979
|
+
previewInstall: "Previewing installation...",
|
|
980
|
+
installSuccess: "Installation successful",
|
|
981
|
+
previewSuccess: "Preview complete",
|
|
982
|
+
installFailed: "Installation failed",
|
|
983
|
+
error: "Error",
|
|
984
|
+
installed: "Plugin installed",
|
|
985
|
+
preview: "Installation preview (dry-run)",
|
|
986
|
+
name: "Name",
|
|
987
|
+
version: "Version",
|
|
988
|
+
type: "Type",
|
|
989
|
+
path: "Path",
|
|
990
|
+
source: "Source",
|
|
991
|
+
target: "Target",
|
|
992
|
+
files: "Files",
|
|
993
|
+
nextSteps: "Next steps:",
|
|
994
|
+
skillHint: "Use /skill-name in Claude Code to invoke",
|
|
995
|
+
mcpHint: "Run ccjk mcp status to check status",
|
|
996
|
+
agentHint: "Use ccjk agent list to see installed agents",
|
|
997
|
+
hookHint: "Run ccjk hooks list to see installed hooks",
|
|
998
|
+
dryRunNote: "This is preview mode, nothing was installed. Remove --dry-run to install.",
|
|
999
|
+
andMore: (n) => `and ${n} more files`
|
|
1000
|
+
}
|
|
190
1001
|
};
|
|
191
|
-
|
|
192
|
-
return newStatus;
|
|
1002
|
+
return texts[lang];
|
|
193
1003
|
}
|
|
194
1004
|
|
|
195
|
-
export {
|
|
1005
|
+
export { addCommand, parseSource };
|