@guanmu/ccprofile 0.1.7 → 0.1.10
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 +414 -170
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
6
|
import * as p from "@clack/prompts";
|
|
7
7
|
const PROFILES_DIR = path.join(process.env.HOME, ".ccx", "profiles");
|
|
8
8
|
const MARKETPLACES_DIR = path.join(process.env.HOME, ".claude", "plugins", "marketplaces");
|
|
@@ -12,24 +12,77 @@ function ensureProfilesDir() {
|
|
|
12
12
|
function profilePath(name) {
|
|
13
13
|
return path.join(PROFILES_DIR, `${name}.json`);
|
|
14
14
|
}
|
|
15
|
+
function isValidProfileName(name) {
|
|
16
|
+
return /^[A-Za-z0-9._-]+$/.test(name) && name !== "." && name !== "..";
|
|
17
|
+
}
|
|
18
|
+
function normalizeProfileName(name) {
|
|
19
|
+
if (!name)
|
|
20
|
+
return undefined;
|
|
21
|
+
const normalized = name.trim();
|
|
22
|
+
if (!isValidProfileName(normalized)) {
|
|
23
|
+
console.error("Invalid profile name. Use only letters, numbers, dots, underscores, and hyphens.");
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
function normalizePluginName(plugin) {
|
|
30
|
+
if (!plugin)
|
|
31
|
+
return undefined;
|
|
32
|
+
const normalized = plugin.trim();
|
|
33
|
+
if (!normalized) {
|
|
34
|
+
console.error("Plugin name is required.");
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return normalized;
|
|
39
|
+
}
|
|
15
40
|
function readProfile(name) {
|
|
16
|
-
const
|
|
41
|
+
const normalized = normalizeProfileName(name);
|
|
42
|
+
if (!normalized)
|
|
43
|
+
process.exit(1);
|
|
44
|
+
const file = profilePath(normalized);
|
|
17
45
|
if (!fs.existsSync(file)) {
|
|
18
|
-
p.log.error(`Profile "${
|
|
46
|
+
p.log.error(`Profile "${normalized}" not found.`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
let data;
|
|
50
|
+
try {
|
|
51
|
+
data = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
p.log.error(`Invalid profile file "${normalized}". Expected valid JSON.`);
|
|
19
55
|
process.exit(1);
|
|
20
56
|
}
|
|
21
|
-
|
|
57
|
+
if (!data ||
|
|
58
|
+
typeof data !== "object" ||
|
|
59
|
+
!Array.isArray(data.plugins)) {
|
|
60
|
+
p.log.error(`Invalid profile file "${normalized}". Expected a plugins array.`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const plugins = data.plugins.map((plugin) => typeof plugin === "string" ? plugin.trim() : undefined);
|
|
64
|
+
if (plugins.some((plugin) => !plugin)) {
|
|
65
|
+
p.log.error(`Invalid profile file "${normalized}". Plugin entries must be non-empty strings.`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
name: normalized,
|
|
70
|
+
plugins: plugins,
|
|
71
|
+
};
|
|
22
72
|
}
|
|
23
73
|
function writeProfile(name, data) {
|
|
74
|
+
const normalized = normalizeProfileName(name);
|
|
75
|
+
if (!normalized)
|
|
76
|
+
return;
|
|
24
77
|
ensureProfilesDir();
|
|
25
|
-
fs.writeFileSync(profilePath(
|
|
78
|
+
fs.writeFileSync(profilePath(normalized), JSON.stringify({ ...data, name: normalized }, null, 2) + "\n");
|
|
26
79
|
}
|
|
27
80
|
function getProfileNames() {
|
|
28
81
|
ensureProfilesDir();
|
|
29
82
|
return fs
|
|
30
83
|
.readdirSync(PROFILES_DIR)
|
|
31
84
|
.filter((f) => f.endsWith(".json"))
|
|
32
|
-
.map((f) => f.
|
|
85
|
+
.map((f) => f.slice(0, -".json".length));
|
|
33
86
|
}
|
|
34
87
|
function getAllPlugins() {
|
|
35
88
|
if (!fs.existsSync(MARKETPLACES_DIR))
|
|
@@ -40,27 +93,83 @@ function getAllPlugins() {
|
|
|
40
93
|
const file = path.join(MARKETPLACES_DIR, dir, ".claude-plugin", "marketplace.json");
|
|
41
94
|
if (!fs.existsSync(file))
|
|
42
95
|
continue;
|
|
43
|
-
|
|
44
|
-
|
|
96
|
+
let data;
|
|
97
|
+
try {
|
|
98
|
+
data = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
p.log.warn(`Skipping invalid marketplace "${dir}".`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!data ||
|
|
105
|
+
typeof data !== "object" ||
|
|
106
|
+
!Array.isArray(data.plugins)) {
|
|
107
|
+
p.log.warn(`Skipping invalid marketplace "${dir}".`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const marketplace = typeof data.name === "string"
|
|
111
|
+
? (data.name || dir)
|
|
112
|
+
: dir;
|
|
113
|
+
for (const pl of data.plugins) {
|
|
114
|
+
if (!pl || typeof pl !== "object")
|
|
115
|
+
continue;
|
|
116
|
+
const name = pl.name;
|
|
117
|
+
if (typeof name !== "string" || !name.trim())
|
|
118
|
+
continue;
|
|
119
|
+
const description = pl.description;
|
|
120
|
+
const category = pl.category;
|
|
45
121
|
plugins.push({
|
|
46
|
-
name:
|
|
47
|
-
description:
|
|
48
|
-
category:
|
|
49
|
-
marketplace
|
|
122
|
+
name: name.trim(),
|
|
123
|
+
description: typeof description === "string" ? description : "",
|
|
124
|
+
category: typeof category === "string" ? category : undefined,
|
|
125
|
+
marketplace,
|
|
50
126
|
});
|
|
51
127
|
}
|
|
52
128
|
}
|
|
53
129
|
return plugins;
|
|
54
130
|
}
|
|
131
|
+
function canPrompt() {
|
|
132
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
133
|
+
}
|
|
134
|
+
function printNonInteractiveHelp() {
|
|
135
|
+
console.error("Interactive mode requires a TTY. Run `ccx --help` for command usage.");
|
|
136
|
+
printHelp();
|
|
137
|
+
}
|
|
138
|
+
function missingArg(message, usage) {
|
|
139
|
+
console.error(message);
|
|
140
|
+
console.error(`Usage: ${usage}`);
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
}
|
|
143
|
+
async function selectProfile(message) {
|
|
144
|
+
const names = getProfileNames();
|
|
145
|
+
if (names.length === 0) {
|
|
146
|
+
p.log.warn("No profiles found.");
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const name = await p.select({
|
|
150
|
+
message,
|
|
151
|
+
options: names.map((n) => ({ value: n, label: n })),
|
|
152
|
+
});
|
|
153
|
+
if (p.isCancel(name))
|
|
154
|
+
return undefined;
|
|
155
|
+
return name;
|
|
156
|
+
}
|
|
55
157
|
// ── Commands ──────────────────────────────────────────────
|
|
56
158
|
async function addProfile(name) {
|
|
57
159
|
if (!name) {
|
|
160
|
+
if (!canPrompt()) {
|
|
161
|
+
missingArg("Profile name is required.", "ccx create <name>");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
58
164
|
name = (await p.text({
|
|
59
165
|
message: "Profile name:",
|
|
60
166
|
}));
|
|
61
167
|
if (p.isCancel(name))
|
|
62
168
|
return;
|
|
63
169
|
}
|
|
170
|
+
name = normalizeProfileName(name);
|
|
171
|
+
if (!name)
|
|
172
|
+
return;
|
|
64
173
|
const file = profilePath(name);
|
|
65
174
|
if (fs.existsSync(file)) {
|
|
66
175
|
p.log.error(`Profile "${name}" already exists.`);
|
|
@@ -70,12 +179,20 @@ async function addProfile(name) {
|
|
|
70
179
|
p.log.success(`Created profile "${name}".`);
|
|
71
180
|
}
|
|
72
181
|
async function removeProfile(name) {
|
|
182
|
+
if (!name && !canPrompt()) {
|
|
183
|
+
missingArg("Profile name is required.", "ccx delete <name>");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
73
186
|
const names = getProfileNames();
|
|
74
187
|
if (names.length === 0) {
|
|
75
188
|
p.log.warn("No profiles found.");
|
|
76
189
|
return;
|
|
77
190
|
}
|
|
78
191
|
if (!name) {
|
|
192
|
+
if (!canPrompt()) {
|
|
193
|
+
missingArg("Profile name is required.", "ccx delete <name>");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
79
196
|
name = (await p.select({
|
|
80
197
|
message: "Select profile to remove:",
|
|
81
198
|
options: names.map((n) => ({ value: n, label: n })),
|
|
@@ -83,6 +200,9 @@ async function removeProfile(name) {
|
|
|
83
200
|
if (p.isCancel(name))
|
|
84
201
|
return;
|
|
85
202
|
}
|
|
203
|
+
name = normalizeProfileName(name);
|
|
204
|
+
if (!name)
|
|
205
|
+
return;
|
|
86
206
|
const file = profilePath(name);
|
|
87
207
|
if (!fs.existsSync(file)) {
|
|
88
208
|
p.log.error(`Profile "${name}" not found.`);
|
|
@@ -92,22 +212,26 @@ async function removeProfile(name) {
|
|
|
92
212
|
p.log.success(`Removed profile "${name}".`);
|
|
93
213
|
}
|
|
94
214
|
async function listProfiles() {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.readdirSync(PROFILES_DIR)
|
|
98
|
-
.filter((f) => f.endsWith(".json"));
|
|
99
|
-
if (files.length === 0) {
|
|
215
|
+
const names = getProfileNames();
|
|
216
|
+
if (names.length === 0) {
|
|
100
217
|
p.log.warn("No profiles found.");
|
|
101
218
|
return;
|
|
102
219
|
}
|
|
103
|
-
for (const
|
|
104
|
-
const data =
|
|
220
|
+
for (const name of names) {
|
|
221
|
+
const data = readProfile(name);
|
|
105
222
|
p.log.success(`${data.name} (${data.plugins.length} plugins)`);
|
|
106
223
|
}
|
|
107
224
|
}
|
|
108
225
|
async function addPlugin(profileName, plugin) {
|
|
226
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
227
|
+
if (!normalizedProfileName)
|
|
228
|
+
return;
|
|
109
229
|
const data = readProfile(profileName);
|
|
110
230
|
if (!plugin) {
|
|
231
|
+
if (!canPrompt()) {
|
|
232
|
+
missingArg("Plugin name is required.", "ccx add <profile> <plugin>");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
111
235
|
const allPlugins = getAllPlugins();
|
|
112
236
|
const available = allPlugins.filter((pl) => !data.plugins.includes(pl.name));
|
|
113
237
|
if (available.length === 0) {
|
|
@@ -138,21 +262,31 @@ async function addPlugin(profileName, plugin) {
|
|
|
138
262
|
plugin = selected;
|
|
139
263
|
}
|
|
140
264
|
}
|
|
265
|
+
plugin = normalizePluginName(plugin);
|
|
266
|
+
if (!plugin)
|
|
267
|
+
return;
|
|
141
268
|
if (data.plugins.includes(plugin)) {
|
|
142
269
|
p.log.warn(`Plugin "${plugin}" already in profile.`);
|
|
143
270
|
return;
|
|
144
271
|
}
|
|
145
272
|
data.plugins.push(plugin);
|
|
146
|
-
writeProfile(
|
|
147
|
-
p.log.success(`Added "${plugin}" to profile "${
|
|
273
|
+
writeProfile(normalizedProfileName, data);
|
|
274
|
+
p.log.success(`Added "${plugin}" to profile "${normalizedProfileName}".`);
|
|
148
275
|
}
|
|
149
276
|
async function removePlugin(profileName, plugin) {
|
|
277
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
278
|
+
if (!normalizedProfileName)
|
|
279
|
+
return;
|
|
150
280
|
const data = readProfile(profileName);
|
|
151
281
|
if (data.plugins.length === 0) {
|
|
152
282
|
p.log.warn("No plugins in this profile.");
|
|
153
283
|
return;
|
|
154
284
|
}
|
|
155
285
|
if (!plugin) {
|
|
286
|
+
if (!canPrompt()) {
|
|
287
|
+
missingArg("Plugin name is required.", "ccx remove <profile> <plugin>");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
156
290
|
plugin = (await p.select({
|
|
157
291
|
message: `Remove plugin from "${profileName}":`,
|
|
158
292
|
options: data.plugins.map((pl) => ({
|
|
@@ -163,19 +297,25 @@ async function removePlugin(profileName, plugin) {
|
|
|
163
297
|
if (p.isCancel(plugin))
|
|
164
298
|
return;
|
|
165
299
|
}
|
|
300
|
+
plugin = normalizePluginName(plugin);
|
|
301
|
+
if (!plugin)
|
|
302
|
+
return;
|
|
166
303
|
const idx = data.plugins.indexOf(plugin);
|
|
167
304
|
if (idx === -1) {
|
|
168
305
|
p.log.error(`Plugin "${plugin}" not found.`);
|
|
169
306
|
return;
|
|
170
307
|
}
|
|
171
308
|
data.plugins.splice(idx, 1);
|
|
172
|
-
writeProfile(
|
|
173
|
-
p.log.success(`Removed "${plugin}" from profile "${
|
|
309
|
+
writeProfile(normalizedProfileName, data);
|
|
310
|
+
p.log.success(`Removed "${plugin}" from profile "${normalizedProfileName}".`);
|
|
174
311
|
}
|
|
175
312
|
async function listPlugins(profileName) {
|
|
176
|
-
const
|
|
313
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
314
|
+
if (!normalizedProfileName)
|
|
315
|
+
return;
|
|
316
|
+
const data = readProfile(normalizedProfileName);
|
|
177
317
|
if (data.plugins.length === 0) {
|
|
178
|
-
p.log.warn(`No plugins in profile "${
|
|
318
|
+
p.log.warn(`No plugins in profile "${normalizedProfileName}".`);
|
|
179
319
|
return;
|
|
180
320
|
}
|
|
181
321
|
for (const pl of data.plugins) {
|
|
@@ -184,6 +324,10 @@ async function listPlugins(profileName) {
|
|
|
184
324
|
}
|
|
185
325
|
async function searchPlugins(keyword) {
|
|
186
326
|
if (!keyword) {
|
|
327
|
+
if (!canPrompt()) {
|
|
328
|
+
missingArg("Search keyword is required.", "ccx search <keyword>");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
187
331
|
keyword = (await p.text({
|
|
188
332
|
message: "Search plugins:",
|
|
189
333
|
}));
|
|
@@ -218,19 +362,22 @@ async function searchPlugins(keyword) {
|
|
|
218
362
|
}
|
|
219
363
|
}
|
|
220
364
|
async function executeProfile(profileName) {
|
|
221
|
-
const
|
|
365
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
366
|
+
if (!normalizedProfileName)
|
|
367
|
+
return;
|
|
368
|
+
const data = readProfile(normalizedProfileName);
|
|
222
369
|
if (data.plugins.length === 0) {
|
|
223
|
-
p.log.warn(`No plugins to install in profile "${
|
|
370
|
+
p.log.warn(`No plugins to install in profile "${normalizedProfileName}".`);
|
|
224
371
|
return;
|
|
225
372
|
}
|
|
226
373
|
const s = p.spinner();
|
|
227
|
-
s.start(`Installing ${data.plugins.length} plugin(s) from "${
|
|
374
|
+
s.start(`Installing ${data.plugins.length} plugin(s) from "${normalizedProfileName}"...`);
|
|
228
375
|
let installed = 0;
|
|
229
376
|
let failed = 0;
|
|
230
377
|
for (const plugin of data.plugins) {
|
|
231
378
|
s.message(`Installing ${plugin}...`);
|
|
232
379
|
try {
|
|
233
|
-
|
|
380
|
+
execFileSync("claude", ["plugin", "install", plugin, "--scope", "project"], {
|
|
234
381
|
stdio: "pipe",
|
|
235
382
|
});
|
|
236
383
|
installed++;
|
|
@@ -243,164 +390,261 @@ async function executeProfile(profileName) {
|
|
|
243
390
|
s.stop(`${installed} installed${failed > 0 ? `, ${failed} failed` : ""}`);
|
|
244
391
|
p.log.success("Done.");
|
|
245
392
|
}
|
|
393
|
+
async function installWizard() {
|
|
394
|
+
while (true) {
|
|
395
|
+
const action = await p.select({
|
|
396
|
+
message: "Install",
|
|
397
|
+
options: [
|
|
398
|
+
{ value: "install", label: "Install profile plugins", hint: "Run a profile" },
|
|
399
|
+
{ value: "back", label: "Back" },
|
|
400
|
+
{ value: "exit", label: "Exit" },
|
|
401
|
+
],
|
|
402
|
+
});
|
|
403
|
+
if (p.isCancel(action) || action === "exit")
|
|
404
|
+
return "exit";
|
|
405
|
+
if (action === "back")
|
|
406
|
+
return "back";
|
|
407
|
+
const name = await selectProfile("Select profile to install:");
|
|
408
|
+
if (name)
|
|
409
|
+
await executeProfile(name);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async function profilesWizard() {
|
|
413
|
+
while (true) {
|
|
414
|
+
const action = await p.select({
|
|
415
|
+
message: "Profiles",
|
|
416
|
+
options: [
|
|
417
|
+
{ value: "create", label: "Create profile", hint: "Create a new empty profile" },
|
|
418
|
+
{ value: "list", label: "List profiles", hint: "Show all profiles" },
|
|
419
|
+
{ value: "delete", label: "Delete profile", hint: "Remove a profile" },
|
|
420
|
+
{ value: "back", label: "Back" },
|
|
421
|
+
{ value: "exit", label: "Exit" },
|
|
422
|
+
],
|
|
423
|
+
});
|
|
424
|
+
if (p.isCancel(action) || action === "exit")
|
|
425
|
+
return "exit";
|
|
426
|
+
if (action === "back")
|
|
427
|
+
return "back";
|
|
428
|
+
switch (action) {
|
|
429
|
+
case "create":
|
|
430
|
+
await addProfile();
|
|
431
|
+
break;
|
|
432
|
+
case "list":
|
|
433
|
+
await listProfiles();
|
|
434
|
+
break;
|
|
435
|
+
case "delete":
|
|
436
|
+
await removeProfile();
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function pluginsWizard() {
|
|
442
|
+
while (true) {
|
|
443
|
+
const action = await p.select({
|
|
444
|
+
message: "Plugins",
|
|
445
|
+
options: [
|
|
446
|
+
{ value: "add", label: "Add plugin to profile", hint: "Choose a profile, then a plugin" },
|
|
447
|
+
{ value: "remove", label: "Remove plugin from profile", hint: "Choose a profile, then a plugin" },
|
|
448
|
+
{ value: "list", label: "List profile plugins", hint: "Show plugins in a profile" },
|
|
449
|
+
{ value: "back", label: "Back" },
|
|
450
|
+
{ value: "exit", label: "Exit" },
|
|
451
|
+
],
|
|
452
|
+
});
|
|
453
|
+
if (p.isCancel(action) || action === "exit")
|
|
454
|
+
return "exit";
|
|
455
|
+
if (action === "back")
|
|
456
|
+
return "back";
|
|
457
|
+
const name = await selectProfile("Select profile:");
|
|
458
|
+
if (!name)
|
|
459
|
+
continue;
|
|
460
|
+
switch (action) {
|
|
461
|
+
case "add":
|
|
462
|
+
await addPlugin(name);
|
|
463
|
+
break;
|
|
464
|
+
case "remove":
|
|
465
|
+
await removePlugin(name);
|
|
466
|
+
break;
|
|
467
|
+
case "list":
|
|
468
|
+
await listPlugins(name);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async function marketplaceWizard() {
|
|
474
|
+
while (true) {
|
|
475
|
+
const action = await p.select({
|
|
476
|
+
message: "Marketplace",
|
|
477
|
+
options: [
|
|
478
|
+
{ value: "search", label: "Search plugins", hint: "Search installed marketplaces" },
|
|
479
|
+
{ value: "back", label: "Back" },
|
|
480
|
+
{ value: "exit", label: "Exit" },
|
|
481
|
+
],
|
|
482
|
+
});
|
|
483
|
+
if (p.isCancel(action) || action === "exit")
|
|
484
|
+
return "exit";
|
|
485
|
+
if (action === "back")
|
|
486
|
+
return "back";
|
|
487
|
+
await searchPlugins();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
246
490
|
async function interactiveMode() {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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" },
|
|
259
|
-
],
|
|
260
|
-
});
|
|
261
|
-
if (p.isCancel(action))
|
|
491
|
+
if (!canPrompt()) {
|
|
492
|
+
printNonInteractiveHelp();
|
|
493
|
+
process.exitCode = 1;
|
|
262
494
|
return;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
495
|
+
}
|
|
496
|
+
p.intro("ccx — Agent Profile Manager");
|
|
497
|
+
let shouldExit = false;
|
|
498
|
+
while (!shouldExit) {
|
|
499
|
+
const area = await p.select({
|
|
500
|
+
message: "Choose area:",
|
|
501
|
+
options: [
|
|
502
|
+
{ value: "install", label: "Install", hint: "Run a profile" },
|
|
503
|
+
{ value: "profiles", label: "Profiles", hint: "Create, list, or delete profiles" },
|
|
504
|
+
{ value: "plugins", label: "Plugins", hint: "Manage plugins inside profiles" },
|
|
505
|
+
{ value: "marketplace", label: "Marketplace", hint: "Search available plugins" },
|
|
506
|
+
{ value: "help", label: "Help", hint: "Show command usage" },
|
|
507
|
+
{ value: "exit", label: "Exit" },
|
|
508
|
+
],
|
|
509
|
+
});
|
|
510
|
+
if (p.isCancel(area) || area === "exit")
|
|
277
511
|
break;
|
|
512
|
+
let result = "back";
|
|
513
|
+
switch (area) {
|
|
514
|
+
case "install":
|
|
515
|
+
result = await installWizard();
|
|
516
|
+
break;
|
|
517
|
+
case "profiles":
|
|
518
|
+
result = await profilesWizard();
|
|
519
|
+
break;
|
|
520
|
+
case "plugins":
|
|
521
|
+
result = await pluginsWizard();
|
|
522
|
+
break;
|
|
523
|
+
case "marketplace":
|
|
524
|
+
result = await marketplaceWizard();
|
|
525
|
+
break;
|
|
526
|
+
case "help":
|
|
527
|
+
printHelp();
|
|
528
|
+
break;
|
|
278
529
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
530
|
+
shouldExit = result === "exit";
|
|
531
|
+
}
|
|
532
|
+
p.outro("Done.");
|
|
533
|
+
}
|
|
534
|
+
function printHelp() {
|
|
535
|
+
console.log(`ccx — Agent Profile Manager for Claude Code
|
|
536
|
+
|
|
537
|
+
Usage:
|
|
538
|
+
ccx Interactive mode (TTY only)
|
|
539
|
+
ccx ui Interactive mode (TTY only)
|
|
540
|
+
ccx install <profile> Install all plugins from profile
|
|
541
|
+
ccx create <name> Create a new profile
|
|
542
|
+
ccx delete <name> Remove a profile
|
|
543
|
+
ccx profiles List all profiles
|
|
544
|
+
ccx add <profile> <plugin> Add plugin to profile
|
|
545
|
+
ccx remove <profile> <plugin> Remove plugin from profile
|
|
546
|
+
ccx list <profile> List plugins in profile
|
|
547
|
+
ccx search <keyword> Search plugins in marketplaces
|
|
548
|
+
ccx <profile> Install all plugins from profile
|
|
549
|
+
ccx <profile> add [plugin] Add plugin to profile (legacy)
|
|
550
|
+
ccx <profile> remove [plugin] Remove plugin from profile (legacy)
|
|
551
|
+
ccx <profile> list List plugins in profile (legacy)
|
|
552
|
+
ccx add <name> Create a new profile (legacy)
|
|
553
|
+
ccx remove <name> Remove a profile (legacy)
|
|
554
|
+
ccx list List all profiles (legacy)
|
|
555
|
+
ccx -v, --version Show version`);
|
|
556
|
+
}
|
|
557
|
+
// ── Main ──────────────────────────────────────────────────
|
|
558
|
+
async function main(args) {
|
|
559
|
+
if (args.length === 0) {
|
|
560
|
+
await interactiveMode();
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
564
|
+
printHelp();
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (args[0] === "--version" || args[0] === "-v" || args[0] === "-V") {
|
|
568
|
+
const require = createRequire(import.meta.url);
|
|
569
|
+
const pkg = require("../package.json");
|
|
570
|
+
console.log(`ccx v${pkg.version}`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const cmd = args[0];
|
|
574
|
+
switch (cmd) {
|
|
575
|
+
case "ui":
|
|
576
|
+
case "tui":
|
|
577
|
+
case "interactive":
|
|
578
|
+
await interactiveMode();
|
|
292
579
|
break;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (names.length === 0) {
|
|
297
|
-
p.log.warn("No profiles found.");
|
|
580
|
+
case "install":
|
|
581
|
+
if (!args[1]) {
|
|
582
|
+
missingArg("Profile name is required.", "ccx install <profile>");
|
|
298
583
|
return;
|
|
299
584
|
}
|
|
300
|
-
|
|
301
|
-
message: "Select profile:",
|
|
302
|
-
options: names.map((n) => ({ value: n, label: n })),
|
|
303
|
-
});
|
|
304
|
-
if (p.isCancel(name))
|
|
305
|
-
return;
|
|
306
|
-
await removePlugin(name);
|
|
585
|
+
await executeProfile(args[1]);
|
|
307
586
|
break;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
message: "Select profile:",
|
|
317
|
-
options: names.map((n) => ({ value: n, label: n })),
|
|
318
|
-
});
|
|
319
|
-
if (p.isCancel(name))
|
|
320
|
-
return;
|
|
321
|
-
await listPlugins(name);
|
|
587
|
+
case "create":
|
|
588
|
+
await addProfile(args[1]);
|
|
589
|
+
break;
|
|
590
|
+
case "delete":
|
|
591
|
+
await removeProfile(args[1]);
|
|
592
|
+
break;
|
|
593
|
+
case "profiles":
|
|
594
|
+
await listProfiles();
|
|
322
595
|
break;
|
|
323
|
-
}
|
|
324
596
|
case "add":
|
|
325
|
-
|
|
597
|
+
if (args[2]) {
|
|
598
|
+
await addPlugin(args[1], args[2]);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
await addProfile(args[1]);
|
|
602
|
+
}
|
|
326
603
|
break;
|
|
327
604
|
case "remove":
|
|
328
|
-
|
|
605
|
+
if (args[2]) {
|
|
606
|
+
await removePlugin(args[1], args[2]);
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
await removeProfile(args[1]);
|
|
610
|
+
}
|
|
329
611
|
break;
|
|
330
612
|
case "list":
|
|
331
|
-
|
|
613
|
+
if (args[1]) {
|
|
614
|
+
await listPlugins(args[1]);
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
await listProfiles();
|
|
618
|
+
}
|
|
332
619
|
break;
|
|
333
620
|
case "search":
|
|
334
|
-
await searchPlugins();
|
|
621
|
+
await searchPlugins(args[1]);
|
|
622
|
+
break;
|
|
623
|
+
default: {
|
|
624
|
+
const profileName = cmd;
|
|
625
|
+
const sub = args[1];
|
|
626
|
+
if (!sub) {
|
|
627
|
+
await executeProfile(profileName);
|
|
628
|
+
}
|
|
629
|
+
else if (sub === "add") {
|
|
630
|
+
await addPlugin(profileName, args[2]);
|
|
631
|
+
}
|
|
632
|
+
else if (sub === "remove") {
|
|
633
|
+
await removePlugin(profileName, args[2]);
|
|
634
|
+
}
|
|
635
|
+
else if (sub === "list") {
|
|
636
|
+
await listPlugins(profileName);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
console.error(`Unknown command: ccx ${args.join(" ")}`);
|
|
640
|
+
printHelp();
|
|
641
|
+
process.exitCode = 1;
|
|
642
|
+
}
|
|
335
643
|
break;
|
|
336
|
-
}
|
|
337
|
-
p.outro("Done.");
|
|
338
|
-
}
|
|
339
|
-
function printHelp() {
|
|
340
|
-
console.log(`ccx — Agent Profile Manager for Claude Code
|
|
341
|
-
|
|
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`);
|
|
353
|
-
}
|
|
354
|
-
// ── Main ──────────────────────────────────────────────────
|
|
355
|
-
const args = process.argv.slice(2);
|
|
356
|
-
if (args.length === 0) {
|
|
357
|
-
interactiveMode();
|
|
358
|
-
process.exit(0);
|
|
359
|
-
}
|
|
360
|
-
if (args[0] === "--help" || args[0] === "-h") {
|
|
361
|
-
printHelp();
|
|
362
|
-
process.exit(0);
|
|
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
|
-
}
|
|
370
|
-
const cmd = args[0];
|
|
371
|
-
switch (cmd) {
|
|
372
|
-
case "add":
|
|
373
|
-
addProfile(args[1]);
|
|
374
|
-
break;
|
|
375
|
-
case "remove":
|
|
376
|
-
removeProfile(args[1]);
|
|
377
|
-
break;
|
|
378
|
-
case "list":
|
|
379
|
-
listProfiles();
|
|
380
|
-
break;
|
|
381
|
-
case "search":
|
|
382
|
-
searchPlugins(args[1]);
|
|
383
|
-
break;
|
|
384
|
-
default: {
|
|
385
|
-
const profileName = cmd;
|
|
386
|
-
const sub = args[1];
|
|
387
|
-
if (!sub) {
|
|
388
|
-
executeProfile(profileName);
|
|
389
|
-
}
|
|
390
|
-
else if (sub === "add") {
|
|
391
|
-
addPlugin(profileName, args[2]);
|
|
392
|
-
}
|
|
393
|
-
else if (sub === "remove") {
|
|
394
|
-
removePlugin(profileName, args[2]);
|
|
395
|
-
}
|
|
396
|
-
else if (sub === "list") {
|
|
397
|
-
listPlugins(profileName);
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
console.error(`Unknown command: ccx ${args.join(" ")}`);
|
|
401
|
-
printHelp();
|
|
402
|
-
process.exit(1);
|
|
403
644
|
}
|
|
404
|
-
break;
|
|
405
645
|
}
|
|
406
646
|
}
|
|
647
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
648
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
649
|
+
process.exitCode = 1;
|
|
650
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guanmu/ccprofile",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Agent Profile Manager for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsc",
|
|
18
18
|
"dev": "bun run src/index.ts",
|
|
19
|
+
"test": "pnpm run build && node test/cli.test.mjs",
|
|
19
20
|
"prepublishOnly": "npm run build"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|