@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.
Files changed (3) hide show
  1. package/README.md +146 -0
  2. package/dist/index.js +143 -86
  3. 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
- const action = await p.select({
387
- message: "What do you want to do?",
388
- options: [
389
- { value: "install", label: "Install profile plugins", hint: "Run a profile to install its plugins" },
390
- { value: "plugin-add", label: "Add plugin to profile", hint: "Add a plugin to an existing profile" },
391
- { value: "plugin-remove", label: "Remove plugin from profile", hint: "Remove a plugin from a profile" },
392
- { value: "plugin-list", label: "List profile plugins", hint: "Show plugins in a profile" },
393
- { value: "add", label: "Create new profile", hint: "Create a new empty profile" },
394
- { value: "remove", label: "Delete profile", hint: "Remove a profile" },
395
- { value: "list", label: "List all profiles", hint: "Show all profiles" },
396
- { value: "search", label: "Search plugins", hint: "Search plugins in marketplaces" },
397
- ],
398
- });
399
- if (p.isCancel(action))
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
- case "add":
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanmu/ccprofile",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Agent Profile Manager for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {