@guanmu/ccprofile 0.1.4
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/index.js +205 -0
- package/package.json +25 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
const PROFILES_DIR = path.join(process.env.HOME, ".ccx", "profiles");
|
|
6
|
+
function ensureProfilesDir() {
|
|
7
|
+
fs.mkdirSync(PROFILES_DIR, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
function profilePath(name) {
|
|
10
|
+
return path.join(PROFILES_DIR, `${name}.json`);
|
|
11
|
+
}
|
|
12
|
+
function readProfile(name) {
|
|
13
|
+
const file = profilePath(name);
|
|
14
|
+
if (!fs.existsSync(file)) {
|
|
15
|
+
console.error(`Profile "${name}" not found.`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
return JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
19
|
+
}
|
|
20
|
+
function writeProfile(name, data) {
|
|
21
|
+
ensureProfilesDir();
|
|
22
|
+
fs.writeFileSync(profilePath(name), JSON.stringify(data, null, 2) + "\n");
|
|
23
|
+
}
|
|
24
|
+
function addProfile(name) {
|
|
25
|
+
const file = profilePath(name);
|
|
26
|
+
if (fs.existsSync(file)) {
|
|
27
|
+
console.error(`Profile "${name}" already exists.`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
writeProfile(name, { name, plugins: [] });
|
|
31
|
+
console.log(`Created profile "${name}".`);
|
|
32
|
+
}
|
|
33
|
+
function removeProfile(name) {
|
|
34
|
+
const file = profilePath(name);
|
|
35
|
+
if (!fs.existsSync(file)) {
|
|
36
|
+
console.error(`Profile "${name}" not found.`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
fs.unlinkSync(file);
|
|
40
|
+
console.log(`Removed profile "${name}".`);
|
|
41
|
+
}
|
|
42
|
+
function listProfiles() {
|
|
43
|
+
ensureProfilesDir();
|
|
44
|
+
const files = fs.readdirSync(PROFILES_DIR).filter((f) => f.endsWith(".json"));
|
|
45
|
+
if (files.length === 0) {
|
|
46
|
+
console.log("No profiles found.");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const f of files) {
|
|
50
|
+
const data = JSON.parse(fs.readFileSync(path.join(PROFILES_DIR, f), "utf-8"));
|
|
51
|
+
console.log(` ${data.name} (${data.plugins.length} plugins)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function addPlugin(profileName, plugin) {
|
|
55
|
+
const data = readProfile(profileName);
|
|
56
|
+
if (data.plugins.includes(plugin)) {
|
|
57
|
+
console.error(`Plugin "${plugin}" already in profile "${profileName}".`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
data.plugins.push(plugin);
|
|
61
|
+
writeProfile(profileName, data);
|
|
62
|
+
console.log(`Added "${plugin}" to profile "${profileName}".`);
|
|
63
|
+
}
|
|
64
|
+
function removePlugin(profileName, plugin) {
|
|
65
|
+
const data = readProfile(profileName);
|
|
66
|
+
const idx = data.plugins.indexOf(plugin);
|
|
67
|
+
if (idx === -1) {
|
|
68
|
+
console.error(`Plugin "${plugin}" not found in profile "${profileName}".`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
data.plugins.splice(idx, 1);
|
|
72
|
+
writeProfile(profileName, data);
|
|
73
|
+
console.log(`Removed "${plugin}" from profile "${profileName}".`);
|
|
74
|
+
}
|
|
75
|
+
function listPlugins(profileName) {
|
|
76
|
+
const data = readProfile(profileName);
|
|
77
|
+
if (data.plugins.length === 0) {
|
|
78
|
+
console.log(`No plugins in profile "${profileName}".`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
for (const p of data.plugins) {
|
|
82
|
+
console.log(` ${p}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function executeProfile(profileName) {
|
|
86
|
+
const data = readProfile(profileName);
|
|
87
|
+
if (data.plugins.length === 0) {
|
|
88
|
+
console.log(`No plugins to install in profile "${profileName}".`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
console.log(`Installing ${data.plugins.length} plugins from profile "${profileName}"...`);
|
|
92
|
+
for (const plugin of data.plugins) {
|
|
93
|
+
console.log(` Installing ${plugin}...`);
|
|
94
|
+
try {
|
|
95
|
+
execSync(`claude plugin install ${plugin} --scope project`, {
|
|
96
|
+
stdio: "inherit",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
console.error(` Failed to install ${plugin}.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.log("Done.");
|
|
104
|
+
}
|
|
105
|
+
function searchPlugins(keyword) {
|
|
106
|
+
const marketplacesDir = path.join(process.env.HOME, ".claude", "plugins", "marketplaces");
|
|
107
|
+
if (!fs.existsSync(marketplacesDir)) {
|
|
108
|
+
console.log("No marketplaces found.");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const lower = keyword.toLowerCase();
|
|
112
|
+
const dirs = fs.readdirSync(marketplacesDir);
|
|
113
|
+
let found = 0;
|
|
114
|
+
for (const dir of dirs) {
|
|
115
|
+
const file = path.join(marketplacesDir, dir, ".claude-plugin", "marketplace.json");
|
|
116
|
+
if (!fs.existsSync(file))
|
|
117
|
+
continue;
|
|
118
|
+
const data = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
119
|
+
for (const p of data.plugins || []) {
|
|
120
|
+
if (p.name?.toLowerCase().includes(lower) ||
|
|
121
|
+
p.description?.toLowerCase().includes(lower) ||
|
|
122
|
+
p.category?.toLowerCase().includes(lower)) {
|
|
123
|
+
found++;
|
|
124
|
+
console.log(` ${p.name} (${data.name}) ${p.description?.slice(0, 80) || ""}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (found === 0) {
|
|
129
|
+
console.log(`No plugins matching "${keyword}".`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function printHelp() {
|
|
133
|
+
console.log(`Usage:
|
|
134
|
+
ccx add <profile> Create a new profile
|
|
135
|
+
ccx remove <profile> Remove a profile
|
|
136
|
+
ccx list List all profiles
|
|
137
|
+
ccx search <keyword> Search plugins in all marketplaces
|
|
138
|
+
ccx <profile> Install all plugins from profile
|
|
139
|
+
ccx <profile> add <plugin|url> Add plugin to profile
|
|
140
|
+
ccx <profile> remove <plugin|url> Remove plugin from profile
|
|
141
|
+
ccx <profile> list List plugins in profile`);
|
|
142
|
+
}
|
|
143
|
+
const args = process.argv.slice(2);
|
|
144
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
145
|
+
printHelp();
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
const cmd = args[0];
|
|
149
|
+
switch (cmd) {
|
|
150
|
+
case "add":
|
|
151
|
+
if (!args[1]) {
|
|
152
|
+
console.error("Usage: ccx add <profile>");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
addProfile(args[1]);
|
|
156
|
+
break;
|
|
157
|
+
case "remove":
|
|
158
|
+
if (!args[1]) {
|
|
159
|
+
console.error("Usage: ccx remove <profile>");
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
removeProfile(args[1]);
|
|
163
|
+
break;
|
|
164
|
+
case "list":
|
|
165
|
+
listProfiles();
|
|
166
|
+
break;
|
|
167
|
+
case "search":
|
|
168
|
+
if (!args[1]) {
|
|
169
|
+
console.error("Usage: ccx search <keyword>");
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
searchPlugins(args[1]);
|
|
173
|
+
break;
|
|
174
|
+
default: {
|
|
175
|
+
// cmd is a profile name
|
|
176
|
+
const profileName = cmd;
|
|
177
|
+
const sub = args[1];
|
|
178
|
+
if (!sub) {
|
|
179
|
+
executeProfile(profileName);
|
|
180
|
+
}
|
|
181
|
+
else if (sub === "add") {
|
|
182
|
+
if (!args[2]) {
|
|
183
|
+
console.error("Usage: ccx <profile> add <plugin|url>");
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
addPlugin(profileName, args[2]);
|
|
187
|
+
}
|
|
188
|
+
else if (sub === "remove") {
|
|
189
|
+
if (!args[2]) {
|
|
190
|
+
console.error("Usage: ccx <profile> remove <plugin|url>");
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
removePlugin(profileName, args[2]);
|
|
194
|
+
}
|
|
195
|
+
else if (sub === "list") {
|
|
196
|
+
listPlugins(profileName);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.error(`Unknown command: ccx ${args.join(" ")}`);
|
|
200
|
+
printHelp();
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@guanmu/ccprofile",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Agent Profile Manager for Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ccx": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/ZeroZ-lab/ccpr"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "bun run src/index.ts",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^22.0.0",
|
|
23
|
+
"typescript": "^5.7.0"
|
|
24
|
+
}
|
|
25
|
+
}
|