@guanmu/ccprofile 0.1.9 → 0.1.11
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/README.md +146 -0
- package/dist/index.js +143 -86
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# ccx
|
|
2
|
+
|
|
3
|
+
Agent Profile Manager for Claude Code.
|
|
4
|
+
|
|
5
|
+
`ccx` lets you save named Claude Code plugin profiles and install a whole profile into the current project with one command. It is designed as a command-first CLI, with a lightweight interactive wizard for manual use.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @guanmu/ccprofile
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or with Bun:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add -g @guanmu/ccprofile@latest
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Verify the installed version:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
ccx --version
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ccx create dev
|
|
29
|
+
ccx add dev cc-design
|
|
30
|
+
ccx add dev browser
|
|
31
|
+
ccx list dev
|
|
32
|
+
ccx install dev
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`ccx install dev` runs `claude plugin install <plugin> --scope project` for every plugin in the `dev` profile.
|
|
36
|
+
|
|
37
|
+
## Commands
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
ccx # Interactive wizard, TTY only
|
|
41
|
+
ccx ui # Interactive wizard, TTY only
|
|
42
|
+
|
|
43
|
+
ccx install <profile> # Install all plugins from a profile
|
|
44
|
+
ccx create <name> # Create a profile
|
|
45
|
+
ccx delete <name> # Delete a profile
|
|
46
|
+
ccx profiles # List all profiles
|
|
47
|
+
|
|
48
|
+
ccx add <profile> <plugin> # Add a plugin to a profile
|
|
49
|
+
ccx remove <profile> <plugin> # Remove a plugin from a profile
|
|
50
|
+
ccx list <profile> # List plugins in a profile
|
|
51
|
+
ccx search <keyword> # Search plugins in installed marketplaces
|
|
52
|
+
|
|
53
|
+
ccx --version # Show version
|
|
54
|
+
ccx --help # Show help
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Legacy aliases are still supported:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ccx <profile> # Same as ccx install <profile>
|
|
61
|
+
ccx <profile> add [plugin]
|
|
62
|
+
ccx <profile> remove [plugin]
|
|
63
|
+
ccx <profile> list
|
|
64
|
+
ccx add <name> # Same as ccx create <name>
|
|
65
|
+
ccx remove <name> # Same as ccx delete <name>
|
|
66
|
+
ccx list # Same as ccx profiles
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Interactive Wizard
|
|
70
|
+
|
|
71
|
+
Run:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ccx
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The wizard is grouped into lightweight pages:
|
|
78
|
+
|
|
79
|
+
- `Install` - install plugins from a profile
|
|
80
|
+
- `Profiles` - create, list, and delete profiles
|
|
81
|
+
- `Plugins` - add, remove, and list profile plugins
|
|
82
|
+
- `Marketplace` - search installed plugin marketplaces
|
|
83
|
+
- `Help` - print command usage
|
|
84
|
+
|
|
85
|
+
The wizard only runs in a real TTY. In scripts, CI, or agent execution environments, use the command form instead.
|
|
86
|
+
|
|
87
|
+
## Profiles
|
|
88
|
+
|
|
89
|
+
Profiles are stored as JSON files under:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
~/.ccx/profiles/
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Example profile:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"name": "dev",
|
|
100
|
+
"plugins": [
|
|
101
|
+
"cc-design",
|
|
102
|
+
"browser"
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Profile names may contain letters, numbers, dots, underscores, and hyphens.
|
|
108
|
+
|
|
109
|
+
## Marketplace Search
|
|
110
|
+
|
|
111
|
+
`ccx search <keyword>` reads Claude plugin marketplaces from:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
~/.claude/plugins/marketplaces/
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Search matches plugin name, description, or category. Invalid marketplace files are skipped with a warning.
|
|
118
|
+
|
|
119
|
+
## Requirements
|
|
120
|
+
|
|
121
|
+
- Node.js 20.12 or newer
|
|
122
|
+
- Claude Code CLI available as `claude`
|
|
123
|
+
- Claude plugin marketplaces installed if you want marketplace search
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pnpm install
|
|
129
|
+
pnpm run build
|
|
130
|
+
pnpm test
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Run from source:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pnpm run dev
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Release
|
|
140
|
+
|
|
141
|
+
This package is published to npm from GitHub Releases.
|
|
142
|
+
|
|
143
|
+
1. Bump `package.json`.
|
|
144
|
+
2. Commit and push to `master`.
|
|
145
|
+
3. Create a GitHub release tag like `v0.1.10`.
|
|
146
|
+
4. The `Publish to npm` workflow builds and publishes the package.
|
package/dist/index.js
CHANGED
|
@@ -140,6 +140,20 @@ function missingArg(message, usage) {
|
|
|
140
140
|
console.error(`Usage: ${usage}`);
|
|
141
141
|
process.exitCode = 1;
|
|
142
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
|
+
}
|
|
143
157
|
// ── Commands ──────────────────────────────────────────────
|
|
144
158
|
async function addProfile(name) {
|
|
145
159
|
if (!name) {
|
|
@@ -376,6 +390,103 @@ async function executeProfile(profileName) {
|
|
|
376
390
|
s.stop(`${installed} installed${failed > 0 ? `, ${failed} failed` : ""}`);
|
|
377
391
|
p.log.success("Done.");
|
|
378
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
|
+
}
|
|
379
490
|
async function interactiveMode() {
|
|
380
491
|
if (!canPrompt()) {
|
|
381
492
|
printNonInteractiveHelp();
|
|
@@ -383,94 +494,40 @@ async function interactiveMode() {
|
|
|
383
494
|
return;
|
|
384
495
|
}
|
|
385
496
|
p.intro("ccx — Agent Profile Manager");
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return;
|
|
401
|
-
switch (action) {
|
|
402
|
-
case "install": {
|
|
403
|
-
const names = getProfileNames();
|
|
404
|
-
if (names.length === 0) {
|
|
405
|
-
p.log.warn("No profiles found.");
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
const name = await p.select({
|
|
409
|
-
message: "Select profile to install:",
|
|
410
|
-
options: names.map((n) => ({ value: n, label: n })),
|
|
411
|
-
});
|
|
412
|
-
if (p.isCancel(name))
|
|
413
|
-
return;
|
|
414
|
-
await executeProfile(name);
|
|
415
|
-
break;
|
|
416
|
-
}
|
|
417
|
-
case "plugin-add": {
|
|
418
|
-
const names = getProfileNames();
|
|
419
|
-
if (names.length === 0) {
|
|
420
|
-
p.log.warn("No profiles found. Create one first.");
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
const name = await p.select({
|
|
424
|
-
message: "Select profile:",
|
|
425
|
-
options: names.map((n) => ({ value: n, label: n })),
|
|
426
|
-
});
|
|
427
|
-
if (p.isCancel(name))
|
|
428
|
-
return;
|
|
429
|
-
await addPlugin(name);
|
|
430
|
-
break;
|
|
431
|
-
}
|
|
432
|
-
case "plugin-remove": {
|
|
433
|
-
const names = getProfileNames();
|
|
434
|
-
if (names.length === 0) {
|
|
435
|
-
p.log.warn("No profiles found.");
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
const name = await p.select({
|
|
439
|
-
message: "Select profile:",
|
|
440
|
-
options: names.map((n) => ({ value: n, label: n })),
|
|
441
|
-
});
|
|
442
|
-
if (p.isCancel(name))
|
|
443
|
-
return;
|
|
444
|
-
await removePlugin(name);
|
|
445
|
-
break;
|
|
446
|
-
}
|
|
447
|
-
case "plugin-list": {
|
|
448
|
-
const names = getProfileNames();
|
|
449
|
-
if (names.length === 0) {
|
|
450
|
-
p.log.warn("No profiles found.");
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
const name = await p.select({
|
|
454
|
-
message: "Select profile:",
|
|
455
|
-
options: names.map((n) => ({ value: n, label: n })),
|
|
456
|
-
});
|
|
457
|
-
if (p.isCancel(name))
|
|
458
|
-
return;
|
|
459
|
-
await listPlugins(name);
|
|
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")
|
|
460
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;
|
|
461
529
|
}
|
|
462
|
-
|
|
463
|
-
await addProfile();
|
|
464
|
-
break;
|
|
465
|
-
case "remove":
|
|
466
|
-
await removeProfile();
|
|
467
|
-
break;
|
|
468
|
-
case "list":
|
|
469
|
-
await listProfiles();
|
|
470
|
-
break;
|
|
471
|
-
case "search":
|
|
472
|
-
await searchPlugins();
|
|
473
|
-
break;
|
|
530
|
+
shouldExit = result === "exit";
|
|
474
531
|
}
|
|
475
532
|
p.outro("Done.");
|
|
476
533
|
}
|