@guanmu/ccprofile 0.1.5 → 0.1.7
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 +148 -108
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { execSync } from "node:child_process";
|
|
5
|
-
import
|
|
6
|
-
import ora from "ora";
|
|
7
|
-
import { select, input } from "@inquirer/prompts";
|
|
6
|
+
import * as p from "@clack/prompts";
|
|
8
7
|
const PROFILES_DIR = path.join(process.env.HOME, ".ccx", "profiles");
|
|
9
8
|
const MARKETPLACES_DIR = path.join(process.env.HOME, ".claude", "plugins", "marketplaces");
|
|
10
9
|
function ensureProfilesDir() {
|
|
@@ -16,7 +15,7 @@ function profilePath(name) {
|
|
|
16
15
|
function readProfile(name) {
|
|
17
16
|
const file = profilePath(name);
|
|
18
17
|
if (!fs.existsSync(file)) {
|
|
19
|
-
|
|
18
|
+
p.log.error(`Profile "${name}" not found.`);
|
|
20
19
|
process.exit(1);
|
|
21
20
|
}
|
|
22
21
|
return JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
@@ -42,11 +41,11 @@ function getAllPlugins() {
|
|
|
42
41
|
if (!fs.existsSync(file))
|
|
43
42
|
continue;
|
|
44
43
|
const data = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
45
|
-
for (const
|
|
44
|
+
for (const pl of data.plugins || []) {
|
|
46
45
|
plugins.push({
|
|
47
|
-
name:
|
|
48
|
-
description:
|
|
49
|
-
category:
|
|
46
|
+
name: pl.name,
|
|
47
|
+
description: pl.description || "",
|
|
48
|
+
category: pl.category,
|
|
50
49
|
marketplace: data.name,
|
|
51
50
|
});
|
|
52
51
|
}
|
|
@@ -56,35 +55,41 @@ function getAllPlugins() {
|
|
|
56
55
|
// ── Commands ──────────────────────────────────────────────
|
|
57
56
|
async function addProfile(name) {
|
|
58
57
|
if (!name) {
|
|
59
|
-
name = await
|
|
58
|
+
name = (await p.text({
|
|
59
|
+
message: "Profile name:",
|
|
60
|
+
}));
|
|
61
|
+
if (p.isCancel(name))
|
|
62
|
+
return;
|
|
60
63
|
}
|
|
61
64
|
const file = profilePath(name);
|
|
62
65
|
if (fs.existsSync(file)) {
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
p.log.error(`Profile "${name}" already exists.`);
|
|
67
|
+
return;
|
|
65
68
|
}
|
|
66
69
|
writeProfile(name, { name, plugins: [] });
|
|
67
|
-
|
|
70
|
+
p.log.success(`Created profile "${name}".`);
|
|
68
71
|
}
|
|
69
72
|
async function removeProfile(name) {
|
|
70
73
|
const names = getProfileNames();
|
|
71
74
|
if (names.length === 0) {
|
|
72
|
-
|
|
75
|
+
p.log.warn("No profiles found.");
|
|
73
76
|
return;
|
|
74
77
|
}
|
|
75
78
|
if (!name) {
|
|
76
|
-
name = (await select({
|
|
79
|
+
name = (await p.select({
|
|
77
80
|
message: "Select profile to remove:",
|
|
78
|
-
|
|
81
|
+
options: names.map((n) => ({ value: n, label: n })),
|
|
79
82
|
}));
|
|
83
|
+
if (p.isCancel(name))
|
|
84
|
+
return;
|
|
80
85
|
}
|
|
81
86
|
const file = profilePath(name);
|
|
82
87
|
if (!fs.existsSync(file)) {
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
p.log.error(`Profile "${name}" not found.`);
|
|
89
|
+
return;
|
|
85
90
|
}
|
|
86
91
|
fs.unlinkSync(file);
|
|
87
|
-
|
|
92
|
+
p.log.success(`Removed profile "${name}".`);
|
|
88
93
|
}
|
|
89
94
|
async function listProfiles() {
|
|
90
95
|
ensureProfilesDir();
|
|
@@ -92,111 +97,122 @@ async function listProfiles() {
|
|
|
92
97
|
.readdirSync(PROFILES_DIR)
|
|
93
98
|
.filter((f) => f.endsWith(".json"));
|
|
94
99
|
if (files.length === 0) {
|
|
95
|
-
|
|
100
|
+
p.log.warn("No profiles found.");
|
|
96
101
|
return;
|
|
97
102
|
}
|
|
98
|
-
console.log(pc.bold("Profiles:"));
|
|
99
103
|
for (const f of files) {
|
|
100
104
|
const data = JSON.parse(fs.readFileSync(path.join(PROFILES_DIR, f), "utf-8"));
|
|
101
|
-
|
|
105
|
+
p.log.success(`${data.name} (${data.plugins.length} plugins)`);
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
async function addPlugin(profileName, plugin) {
|
|
105
109
|
const data = readProfile(profileName);
|
|
106
110
|
if (!plugin) {
|
|
107
111
|
const allPlugins = getAllPlugins();
|
|
108
|
-
const available = allPlugins.filter((
|
|
112
|
+
const available = allPlugins.filter((pl) => !data.plugins.includes(pl.name));
|
|
109
113
|
if (available.length === 0) {
|
|
110
|
-
|
|
114
|
+
p.log.warn("All available plugins already added.");
|
|
111
115
|
return;
|
|
112
116
|
}
|
|
113
|
-
const
|
|
114
|
-
...available.map((p) => ({
|
|
115
|
-
name: `${pc.cyan(p.name)} ${pc.dim(p.description.slice(0, 60))} ${pc.dim(`[${p.marketplace}]`)}`,
|
|
116
|
-
value: p.name,
|
|
117
|
-
description: p.description,
|
|
118
|
-
})),
|
|
119
|
-
{ name: pc.yellow("Enter URL manually..."), value: "__url__" },
|
|
120
|
-
];
|
|
121
|
-
const selected = (await select({
|
|
117
|
+
const selected = await p.select({
|
|
122
118
|
message: `Add plugin to "${profileName}":`,
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
options: [
|
|
120
|
+
...available.map((pl) => ({
|
|
121
|
+
value: pl.name,
|
|
122
|
+
label: pl.name,
|
|
123
|
+
hint: pl.description.slice(0, 60),
|
|
124
|
+
})),
|
|
125
|
+
{ value: "__url__", label: "Enter URL manually...", hint: "Input a GitHub URL or plugin name" },
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
if (p.isCancel(selected))
|
|
129
|
+
return;
|
|
125
130
|
if (selected === "__url__") {
|
|
126
|
-
plugin = await
|
|
131
|
+
plugin = (await p.text({
|
|
132
|
+
message: "Plugin URL or name:",
|
|
133
|
+
}));
|
|
134
|
+
if (p.isCancel(plugin))
|
|
135
|
+
return;
|
|
127
136
|
}
|
|
128
137
|
else {
|
|
129
138
|
plugin = selected;
|
|
130
139
|
}
|
|
131
140
|
}
|
|
132
141
|
if (data.plugins.includes(plugin)) {
|
|
133
|
-
|
|
142
|
+
p.log.warn(`Plugin "${plugin}" already in profile.`);
|
|
134
143
|
return;
|
|
135
144
|
}
|
|
136
145
|
data.plugins.push(plugin);
|
|
137
146
|
writeProfile(profileName, data);
|
|
138
|
-
|
|
147
|
+
p.log.success(`Added "${plugin}" to profile "${profileName}".`);
|
|
139
148
|
}
|
|
140
149
|
async function removePlugin(profileName, plugin) {
|
|
141
150
|
const data = readProfile(profileName);
|
|
142
151
|
if (data.plugins.length === 0) {
|
|
143
|
-
|
|
152
|
+
p.log.warn("No plugins in this profile.");
|
|
144
153
|
return;
|
|
145
154
|
}
|
|
146
155
|
if (!plugin) {
|
|
147
|
-
plugin = (await select({
|
|
156
|
+
plugin = (await p.select({
|
|
148
157
|
message: `Remove plugin from "${profileName}":`,
|
|
149
|
-
|
|
158
|
+
options: data.plugins.map((pl) => ({
|
|
159
|
+
value: pl,
|
|
160
|
+
label: pl,
|
|
161
|
+
})),
|
|
150
162
|
}));
|
|
163
|
+
if (p.isCancel(plugin))
|
|
164
|
+
return;
|
|
151
165
|
}
|
|
152
166
|
const idx = data.plugins.indexOf(plugin);
|
|
153
167
|
if (idx === -1) {
|
|
154
|
-
|
|
155
|
-
|
|
168
|
+
p.log.error(`Plugin "${plugin}" not found.`);
|
|
169
|
+
return;
|
|
156
170
|
}
|
|
157
171
|
data.plugins.splice(idx, 1);
|
|
158
172
|
writeProfile(profileName, data);
|
|
159
|
-
|
|
173
|
+
p.log.success(`Removed "${plugin}" from profile "${profileName}".`);
|
|
160
174
|
}
|
|
161
175
|
async function listPlugins(profileName) {
|
|
162
176
|
const data = readProfile(profileName);
|
|
163
177
|
if (data.plugins.length === 0) {
|
|
164
|
-
|
|
178
|
+
p.log.warn(`No plugins in profile "${profileName}".`);
|
|
165
179
|
return;
|
|
166
180
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
console.log(` ${pc.cyan(p)}`);
|
|
181
|
+
for (const pl of data.plugins) {
|
|
182
|
+
p.log.success(pl);
|
|
170
183
|
}
|
|
171
184
|
}
|
|
172
185
|
async function searchPlugins(keyword) {
|
|
173
186
|
if (!keyword) {
|
|
174
|
-
keyword = await
|
|
187
|
+
keyword = (await p.text({
|
|
188
|
+
message: "Search plugins:",
|
|
189
|
+
}));
|
|
190
|
+
if (p.isCancel(keyword))
|
|
191
|
+
return;
|
|
175
192
|
}
|
|
176
193
|
const allPlugins = getAllPlugins();
|
|
177
194
|
const lower = keyword.toLowerCase();
|
|
178
|
-
const results = allPlugins.filter((
|
|
179
|
-
|
|
180
|
-
(
|
|
195
|
+
const results = allPlugins.filter((pl) => pl.name.toLowerCase().includes(lower) ||
|
|
196
|
+
pl.description.toLowerCase().includes(lower) ||
|
|
197
|
+
(pl.category || "").toLowerCase().includes(lower));
|
|
181
198
|
if (results.length === 0) {
|
|
182
|
-
|
|
199
|
+
p.log.warn(`No plugins matching "${keyword}".`);
|
|
183
200
|
return;
|
|
184
201
|
}
|
|
185
|
-
console.log(pc.bold(`Found ${results.length} plugin(s) for "${keyword}":\n`));
|
|
186
|
-
// Group by marketplace
|
|
187
202
|
const grouped = new Map();
|
|
188
|
-
for (const
|
|
189
|
-
const list = grouped.get(
|
|
190
|
-
list.push(
|
|
191
|
-
grouped.set(
|
|
203
|
+
for (const pl of results) {
|
|
204
|
+
const list = grouped.get(pl.marketplace) || [];
|
|
205
|
+
list.push(pl);
|
|
206
|
+
grouped.set(pl.marketplace, list);
|
|
192
207
|
}
|
|
208
|
+
console.log(`\n Found ${results.length} plugin(s) for "${keyword}":\n`);
|
|
193
209
|
for (const [marketplace, plugins] of grouped) {
|
|
194
|
-
console.log(
|
|
195
|
-
for (const
|
|
196
|
-
const desc =
|
|
197
|
-
?
|
|
198
|
-
:
|
|
199
|
-
console.log(` ${
|
|
210
|
+
console.log(` [${marketplace}]`);
|
|
211
|
+
for (const pl of plugins) {
|
|
212
|
+
const desc = pl.description.length > 70
|
|
213
|
+
? pl.description.slice(0, 67) + "..."
|
|
214
|
+
: pl.description;
|
|
215
|
+
console.log(` ${pl.name.padEnd(24)} ${desc}`);
|
|
200
216
|
}
|
|
201
217
|
console.log();
|
|
202
218
|
}
|
|
@@ -204,88 +220,104 @@ async function searchPlugins(keyword) {
|
|
|
204
220
|
async function executeProfile(profileName) {
|
|
205
221
|
const data = readProfile(profileName);
|
|
206
222
|
if (data.plugins.length === 0) {
|
|
207
|
-
|
|
223
|
+
p.log.warn(`No plugins to install in profile "${profileName}".`);
|
|
208
224
|
return;
|
|
209
225
|
}
|
|
210
|
-
|
|
226
|
+
const s = p.spinner();
|
|
227
|
+
s.start(`Installing ${data.plugins.length} plugin(s) from "${profileName}"...`);
|
|
228
|
+
let installed = 0;
|
|
229
|
+
let failed = 0;
|
|
211
230
|
for (const plugin of data.plugins) {
|
|
212
|
-
|
|
231
|
+
s.message(`Installing ${plugin}...`);
|
|
213
232
|
try {
|
|
214
233
|
execSync(`claude plugin install ${plugin} --scope project`, {
|
|
215
234
|
stdio: "pipe",
|
|
216
235
|
});
|
|
217
|
-
|
|
236
|
+
installed++;
|
|
218
237
|
}
|
|
219
238
|
catch {
|
|
220
|
-
|
|
239
|
+
failed++;
|
|
240
|
+
p.log.error(`Failed to install ${plugin}`);
|
|
221
241
|
}
|
|
222
242
|
}
|
|
223
|
-
|
|
243
|
+
s.stop(`${installed} installed${failed > 0 ? `, ${failed} failed` : ""}`);
|
|
244
|
+
p.log.success("Done.");
|
|
224
245
|
}
|
|
225
246
|
async function interactiveMode() {
|
|
226
|
-
|
|
247
|
+
p.intro("ccx — Agent Profile Manager");
|
|
248
|
+
const action = await p.select({
|
|
227
249
|
message: "What do you want to do?",
|
|
228
|
-
|
|
229
|
-
{
|
|
230
|
-
{
|
|
231
|
-
{
|
|
232
|
-
{
|
|
233
|
-
{
|
|
234
|
-
{
|
|
235
|
-
{
|
|
236
|
-
{
|
|
250
|
+
options: [
|
|
251
|
+
{ value: "install", label: "Install profile plugins", hint: "Run a profile to install its plugins" },
|
|
252
|
+
{ value: "plugin-add", label: "Add plugin to profile", hint: "Add a plugin to an existing profile" },
|
|
253
|
+
{ value: "plugin-remove", label: "Remove plugin from profile", hint: "Remove a plugin from a profile" },
|
|
254
|
+
{ value: "plugin-list", label: "List profile plugins", hint: "Show plugins in a profile" },
|
|
255
|
+
{ value: "add", label: "Create new profile", hint: "Create a new empty profile" },
|
|
256
|
+
{ value: "remove", label: "Delete profile", hint: "Remove a profile" },
|
|
257
|
+
{ value: "list", label: "List all profiles", hint: "Show all profiles" },
|
|
258
|
+
{ value: "search", label: "Search plugins", hint: "Search plugins in marketplaces" },
|
|
237
259
|
],
|
|
238
|
-
})
|
|
260
|
+
});
|
|
261
|
+
if (p.isCancel(action))
|
|
262
|
+
return;
|
|
239
263
|
switch (action) {
|
|
240
264
|
case "install": {
|
|
241
265
|
const names = getProfileNames();
|
|
242
266
|
if (names.length === 0) {
|
|
243
|
-
|
|
267
|
+
p.log.warn("No profiles found.");
|
|
244
268
|
return;
|
|
245
269
|
}
|
|
246
|
-
const name = await select({
|
|
270
|
+
const name = await p.select({
|
|
247
271
|
message: "Select profile to install:",
|
|
248
|
-
|
|
272
|
+
options: names.map((n) => ({ value: n, label: n })),
|
|
249
273
|
});
|
|
274
|
+
if (p.isCancel(name))
|
|
275
|
+
return;
|
|
250
276
|
await executeProfile(name);
|
|
251
277
|
break;
|
|
252
278
|
}
|
|
253
279
|
case "plugin-add": {
|
|
254
280
|
const names = getProfileNames();
|
|
255
281
|
if (names.length === 0) {
|
|
256
|
-
|
|
282
|
+
p.log.warn("No profiles found. Create one first.");
|
|
257
283
|
return;
|
|
258
284
|
}
|
|
259
|
-
const name =
|
|
285
|
+
const name = await p.select({
|
|
260
286
|
message: "Select profile:",
|
|
261
|
-
|
|
262
|
-
})
|
|
287
|
+
options: names.map((n) => ({ value: n, label: n })),
|
|
288
|
+
});
|
|
289
|
+
if (p.isCancel(name))
|
|
290
|
+
return;
|
|
263
291
|
await addPlugin(name);
|
|
264
292
|
break;
|
|
265
293
|
}
|
|
266
294
|
case "plugin-remove": {
|
|
267
295
|
const names = getProfileNames();
|
|
268
296
|
if (names.length === 0) {
|
|
269
|
-
|
|
297
|
+
p.log.warn("No profiles found.");
|
|
270
298
|
return;
|
|
271
299
|
}
|
|
272
|
-
const name =
|
|
300
|
+
const name = await p.select({
|
|
273
301
|
message: "Select profile:",
|
|
274
|
-
|
|
275
|
-
})
|
|
302
|
+
options: names.map((n) => ({ value: n, label: n })),
|
|
303
|
+
});
|
|
304
|
+
if (p.isCancel(name))
|
|
305
|
+
return;
|
|
276
306
|
await removePlugin(name);
|
|
277
307
|
break;
|
|
278
308
|
}
|
|
279
309
|
case "plugin-list": {
|
|
280
310
|
const names = getProfileNames();
|
|
281
311
|
if (names.length === 0) {
|
|
282
|
-
|
|
312
|
+
p.log.warn("No profiles found.");
|
|
283
313
|
return;
|
|
284
314
|
}
|
|
285
|
-
const name =
|
|
315
|
+
const name = await p.select({
|
|
286
316
|
message: "Select profile:",
|
|
287
|
-
|
|
288
|
-
})
|
|
317
|
+
options: names.map((n) => ({ value: n, label: n })),
|
|
318
|
+
});
|
|
319
|
+
if (p.isCancel(name))
|
|
320
|
+
return;
|
|
289
321
|
await listPlugins(name);
|
|
290
322
|
break;
|
|
291
323
|
}
|
|
@@ -302,20 +334,22 @@ async function interactiveMode() {
|
|
|
302
334
|
await searchPlugins();
|
|
303
335
|
break;
|
|
304
336
|
}
|
|
337
|
+
p.outro("Done.");
|
|
305
338
|
}
|
|
306
339
|
function printHelp() {
|
|
307
|
-
console.log(
|
|
340
|
+
console.log(`ccx — Agent Profile Manager for Claude Code
|
|
308
341
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
342
|
+
Usage:
|
|
343
|
+
ccx Interactive mode
|
|
344
|
+
ccx <profile> Install all plugins from profile
|
|
345
|
+
ccx add <name> Create a new profile
|
|
346
|
+
ccx remove <name> Remove a profile
|
|
347
|
+
ccx list List all profiles
|
|
348
|
+
ccx search <keyword> Search plugins in marketplaces
|
|
349
|
+
ccx <profile> add [plugin] Add plugin to profile
|
|
350
|
+
ccx <profile> remove [plugin] Remove plugin from profile
|
|
351
|
+
ccx <profile> list List plugins in profile
|
|
352
|
+
ccx -v, --version Show version`);
|
|
319
353
|
}
|
|
320
354
|
// ── Main ──────────────────────────────────────────────────
|
|
321
355
|
const args = process.argv.slice(2);
|
|
@@ -327,6 +361,12 @@ if (args[0] === "--help" || args[0] === "-h") {
|
|
|
327
361
|
printHelp();
|
|
328
362
|
process.exit(0);
|
|
329
363
|
}
|
|
364
|
+
if (args[0] === "--version" || args[0] === "-v" || args[0] === "-V") {
|
|
365
|
+
const require = createRequire(import.meta.url);
|
|
366
|
+
const pkg = require("../package.json");
|
|
367
|
+
console.log(`ccx v${pkg.version}`);
|
|
368
|
+
process.exit(0);
|
|
369
|
+
}
|
|
330
370
|
const cmd = args[0];
|
|
331
371
|
switch (cmd) {
|
|
332
372
|
case "add":
|
|
@@ -357,7 +397,7 @@ switch (cmd) {
|
|
|
357
397
|
listPlugins(profileName);
|
|
358
398
|
}
|
|
359
399
|
else {
|
|
360
|
-
console.error(
|
|
400
|
+
console.error(`Unknown command: ccx ${args.join(" ")}`);
|
|
361
401
|
printHelp();
|
|
362
402
|
process.exit(1);
|
|
363
403
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guanmu/ccprofile",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Agent Profile Manager for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"prepublishOnly": "npm run build"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@
|
|
22
|
+
"@clack/prompts": "^1.4.0",
|
|
23
23
|
"ora": "^8.0.0",
|
|
24
24
|
"picocolors": "^1.1.0"
|
|
25
25
|
},
|