@guanmu/ccprofile 0.1.16 → 0.1.17
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 +149 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import * as p from "@clack/prompts";
|
|
|
7
7
|
import pc from "picocolors";
|
|
8
8
|
const PROFILES_DIR = path.join(process.env.HOME, ".ccx", "profiles");
|
|
9
9
|
const MARKETPLACES_DIR = path.join(process.env.HOME, ".claude", "plugins", "marketplaces");
|
|
10
|
+
const PROJECT_CONFIG_FILE = ".ccx.json";
|
|
10
11
|
function ensureProfilesDir() {
|
|
11
12
|
fs.mkdirSync(PROFILES_DIR, { recursive: true });
|
|
12
13
|
}
|
|
@@ -78,6 +79,36 @@ function writeProfile(name, data) {
|
|
|
78
79
|
ensureProfilesDir();
|
|
79
80
|
fs.writeFileSync(profilePath(normalized), JSON.stringify({ ...data, name: normalized }, null, 2) + "\n");
|
|
80
81
|
}
|
|
82
|
+
function readProjectConfig() {
|
|
83
|
+
const file = path.join(process.cwd(), PROJECT_CONFIG_FILE);
|
|
84
|
+
if (!fs.existsSync(file)) {
|
|
85
|
+
p.log.error(`No ${PROJECT_CONFIG_FILE} found. Run \`ccx init\` to create one.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
let data;
|
|
89
|
+
try {
|
|
90
|
+
data = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
p.log.error(`Invalid ${PROJECT_CONFIG_FILE}. Expected valid JSON.`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
if (!data ||
|
|
97
|
+
typeof data !== "object" ||
|
|
98
|
+
!Array.isArray(data.plugins)) {
|
|
99
|
+
p.log.error(`Invalid ${PROJECT_CONFIG_FILE}. Expected a plugins array.`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
const plugins = data.plugins.map((plugin) => typeof plugin === "string" ? plugin.trim() : undefined);
|
|
103
|
+
if (plugins.some((plugin) => !plugin)) {
|
|
104
|
+
p.log.error(`Invalid ${PROJECT_CONFIG_FILE}. Plugin entries must be non-empty strings.`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
return plugins;
|
|
108
|
+
}
|
|
109
|
+
function writeProjectConfig(plugins) {
|
|
110
|
+
fs.writeFileSync(path.join(process.cwd(), PROJECT_CONFIG_FILE), JSON.stringify({ plugins }, null, 2) + "\n");
|
|
111
|
+
}
|
|
81
112
|
function getProfileNames() {
|
|
82
113
|
ensureProfilesDir();
|
|
83
114
|
return fs
|
|
@@ -411,20 +442,12 @@ async function browsePlugins(currentProfile) {
|
|
|
411
442
|
}
|
|
412
443
|
return [];
|
|
413
444
|
}
|
|
414
|
-
async function
|
|
415
|
-
const normalizedProfileName = normalizeProfileName(profileName);
|
|
416
|
-
if (!normalizedProfileName)
|
|
417
|
-
return;
|
|
418
|
-
const data = readProfile(normalizedProfileName);
|
|
419
|
-
if (data.plugins.length === 0) {
|
|
420
|
-
p.log.warn(`No plugins to install in profile "${normalizedProfileName}".`);
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
445
|
+
async function installPlugins(plugins, label) {
|
|
423
446
|
const s = p.spinner();
|
|
424
|
-
s.start(`Installing ${
|
|
447
|
+
s.start(`Installing ${plugins.length} plugin(s) from "${label}"...`);
|
|
425
448
|
let installed = 0;
|
|
426
449
|
let failed = 0;
|
|
427
|
-
for (const plugin of
|
|
450
|
+
for (const plugin of plugins) {
|
|
428
451
|
s.message(`Installing ${plugin}...`);
|
|
429
452
|
try {
|
|
430
453
|
execFileSync("claude", ["plugin", "install", plugin, "--scope", "project"], {
|
|
@@ -440,6 +463,111 @@ async function executeProfile(profileName) {
|
|
|
440
463
|
s.stop(`${installed} installed${failed > 0 ? `, ${failed} failed` : ""}`);
|
|
441
464
|
p.log.success("Done.");
|
|
442
465
|
}
|
|
466
|
+
async function executeProfile(profileName) {
|
|
467
|
+
const normalizedProfileName = normalizeProfileName(profileName);
|
|
468
|
+
if (!normalizedProfileName)
|
|
469
|
+
return;
|
|
470
|
+
const data = readProfile(normalizedProfileName);
|
|
471
|
+
if (data.plugins.length === 0) {
|
|
472
|
+
p.log.warn(`No plugins to install in profile "${normalizedProfileName}".`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
await installPlugins(data.plugins, normalizedProfileName);
|
|
476
|
+
}
|
|
477
|
+
async function executeProjectConfig() {
|
|
478
|
+
const file = path.join(process.cwd(), PROJECT_CONFIG_FILE);
|
|
479
|
+
if (!fs.existsSync(file)) {
|
|
480
|
+
p.log.error(`No ${PROJECT_CONFIG_FILE} found. Run \`ccx init\` to create one.`);
|
|
481
|
+
process.exitCode = 1;
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const plugins = readProjectConfig();
|
|
485
|
+
if (plugins.length === 0) {
|
|
486
|
+
p.log.warn(`No plugins in ${PROJECT_CONFIG_FILE}.`);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
await installPlugins(plugins, PROJECT_CONFIG_FILE);
|
|
490
|
+
}
|
|
491
|
+
async function initProjectConfig() {
|
|
492
|
+
if (!canPrompt()) {
|
|
493
|
+
console.error("ccx init requires a TTY.");
|
|
494
|
+
process.exitCode = 1;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const file = path.join(process.cwd(), PROJECT_CONFIG_FILE);
|
|
498
|
+
if (fs.existsSync(file)) {
|
|
499
|
+
const overwrite = await p.confirm({
|
|
500
|
+
message: `${PROJECT_CONFIG_FILE} already exists. Overwrite?`,
|
|
501
|
+
initialValue: false,
|
|
502
|
+
});
|
|
503
|
+
if (p.isCancel(overwrite) || !overwrite)
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const allPlugins = getAllPlugins();
|
|
507
|
+
let selected;
|
|
508
|
+
if (allPlugins.length === 0) {
|
|
509
|
+
const input = (await p.text({
|
|
510
|
+
message: "No marketplace plugins found. Enter plugin names (comma-separated):",
|
|
511
|
+
placeholder: "plugin-a, plugin-b",
|
|
512
|
+
}));
|
|
513
|
+
if (p.isCancel(input))
|
|
514
|
+
return;
|
|
515
|
+
selected = input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
let filtered = allPlugins;
|
|
519
|
+
if (allPlugins.length > 10) {
|
|
520
|
+
const query = (await p.text({
|
|
521
|
+
message: "Search plugins (leave empty to list all):",
|
|
522
|
+
}));
|
|
523
|
+
if (p.isCancel(query))
|
|
524
|
+
return;
|
|
525
|
+
const q = query.trim().toLowerCase();
|
|
526
|
+
if (q) {
|
|
527
|
+
filtered = allPlugins.filter((pl) => pl.name.toLowerCase().includes(q) ||
|
|
528
|
+
pl.description.toLowerCase().includes(q) ||
|
|
529
|
+
(pl.category && pl.category.toLowerCase().includes(q)));
|
|
530
|
+
if (filtered.length === 0) {
|
|
531
|
+
p.log.warn(`No plugins matching "${query.trim()}".`);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const picked = await p.multiselect({
|
|
537
|
+
message: "Select plugins for this project:",
|
|
538
|
+
options: filtered.map((pl) => ({
|
|
539
|
+
value: pl.name,
|
|
540
|
+
label: pl.name,
|
|
541
|
+
hint: pl.description.slice(0, 50),
|
|
542
|
+
})),
|
|
543
|
+
required: false,
|
|
544
|
+
});
|
|
545
|
+
if (p.isCancel(picked))
|
|
546
|
+
return;
|
|
547
|
+
selected = picked;
|
|
548
|
+
}
|
|
549
|
+
writeProjectConfig(selected);
|
|
550
|
+
p.log.success(`Created ${PROJECT_CONFIG_FILE} with ${selected.length} plugin(s).`);
|
|
551
|
+
}
|
|
552
|
+
function syncProjectConfig() {
|
|
553
|
+
const pluginsDir = path.join(process.cwd(), ".claude", "plugins");
|
|
554
|
+
if (!fs.existsSync(pluginsDir)) {
|
|
555
|
+
p.log.warn("No project plugins found (.claude/plugins/).");
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const entries = fs.readdirSync(pluginsDir, { withFileTypes: true });
|
|
559
|
+
const plugins = entries
|
|
560
|
+
.filter((e) => e.isDirectory())
|
|
561
|
+
.map((e) => e.name)
|
|
562
|
+
.filter((name) => !name.startsWith("."))
|
|
563
|
+
.sort();
|
|
564
|
+
if (plugins.length === 0) {
|
|
565
|
+
p.log.warn("No project plugins found (.claude/plugins/).");
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
writeProjectConfig(plugins);
|
|
569
|
+
p.log.success(`Synced ${plugins.length} plugin(s) to ${PROJECT_CONFIG_FILE}.`);
|
|
570
|
+
}
|
|
443
571
|
function printBanner() {
|
|
444
572
|
const require = createRequire(import.meta.url);
|
|
445
573
|
const pkg = require("../package.json");
|
|
@@ -522,6 +650,9 @@ function printHelp() {
|
|
|
522
650
|
Usage:
|
|
523
651
|
ccx Interactive mode (TTY only)
|
|
524
652
|
ccx ui Interactive mode (TTY only)
|
|
653
|
+
ccx init Create .ccx.json for current project
|
|
654
|
+
ccx sync Sync installed plugins to .ccx.json
|
|
655
|
+
ccx install Install plugins from .ccx.json
|
|
525
656
|
ccx install <profile> Install all plugins from profile
|
|
526
657
|
ccx create <name> Create a new profile
|
|
527
658
|
ccx delete <name> Remove a profile
|
|
@@ -557,6 +688,12 @@ async function main(args) {
|
|
|
557
688
|
}
|
|
558
689
|
const cmd = args[0];
|
|
559
690
|
switch (cmd) {
|
|
691
|
+
case "init":
|
|
692
|
+
await initProjectConfig();
|
|
693
|
+
break;
|
|
694
|
+
case "sync":
|
|
695
|
+
syncProjectConfig();
|
|
696
|
+
break;
|
|
560
697
|
case "ui":
|
|
561
698
|
case "tui":
|
|
562
699
|
case "interactive":
|
|
@@ -564,7 +701,7 @@ async function main(args) {
|
|
|
564
701
|
break;
|
|
565
702
|
case "install":
|
|
566
703
|
if (!args[1]) {
|
|
567
|
-
|
|
704
|
+
await executeProjectConfig();
|
|
568
705
|
return;
|
|
569
706
|
}
|
|
570
707
|
await executeProfile(args[1]);
|