@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.
Files changed (2) hide show
  1. package/dist/index.js +148 -108
  2. 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 pc from "picocolors";
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
- console.error(pc.red(`Profile "${name}" not found.`));
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 p of data.plugins || []) {
44
+ for (const pl of data.plugins || []) {
46
45
  plugins.push({
47
- name: p.name,
48
- description: p.description || "",
49
- category: p.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 input({ message: "Profile name:" });
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
- console.error(pc.red(`Profile "${name}" already exists.`));
64
- process.exit(1);
66
+ p.log.error(`Profile "${name}" already exists.`);
67
+ return;
65
68
  }
66
69
  writeProfile(name, { name, plugins: [] });
67
- console.log(pc.green(`Created profile "${name}".`));
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
- console.log(pc.yellow("No profiles found."));
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
- choices: names.map((n) => ({ name: n, value: n })),
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
- console.error(pc.red(`Profile "${name}" not found.`));
84
- process.exit(1);
88
+ p.log.error(`Profile "${name}" not found.`);
89
+ return;
85
90
  }
86
91
  fs.unlinkSync(file);
87
- console.log(pc.green(`Removed profile "${name}".`));
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
- console.log(pc.yellow("No profiles found."));
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
- console.log(` ${pc.cyan(data.name)} ${pc.dim(`(${data.plugins.length} plugins)`)}`);
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((p) => !data.plugins.includes(p.name));
112
+ const available = allPlugins.filter((pl) => !data.plugins.includes(pl.name));
109
113
  if (available.length === 0) {
110
- console.log(pc.yellow("All available plugins already added."));
114
+ p.log.warn("All available plugins already added.");
111
115
  return;
112
116
  }
113
- const choices = [
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
- choices,
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 input({ message: "Plugin URL or name:" });
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
- console.error(pc.yellow(`Plugin "${plugin}" already in profile.`));
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
- console.log(pc.green(`Added "${plugin}" to profile "${profileName}".`));
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
- console.log(pc.yellow("No plugins in this profile."));
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
- choices: data.plugins.map((p) => ({ name: p, value: p })),
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
- console.error(pc.red(`Plugin "${plugin}" not found.`));
155
- process.exit(1);
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
- console.log(pc.green(`Removed "${plugin}" from profile "${profileName}".`));
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
- console.log(pc.yellow(`No plugins in profile "${profileName}".`));
178
+ p.log.warn(`No plugins in profile "${profileName}".`);
165
179
  return;
166
180
  }
167
- console.log(pc.bold(`Plugins in "${profileName}":`));
168
- for (const p of data.plugins) {
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 input({ message: "Search plugins:" });
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((p) => p.name.toLowerCase().includes(lower) ||
179
- p.description.toLowerCase().includes(lower) ||
180
- (p.category || "").toLowerCase().includes(lower));
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
- console.log(pc.yellow(`No plugins matching "${keyword}".`));
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 p of results) {
189
- const list = grouped.get(p.marketplace) || [];
190
- list.push(p);
191
- grouped.set(p.marketplace, list);
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(pc.bold(pc.dim(` [${marketplace}]`)));
195
- for (const p of plugins) {
196
- const desc = p.description.length > 70
197
- ? p.description.slice(0, 67) + "..."
198
- : p.description;
199
- console.log(` ${pc.cyan(p.name.padEnd(24))} ${pc.dim(desc)}`);
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
- console.log(pc.yellow(`No plugins to install in profile "${profileName}".`));
223
+ p.log.warn(`No plugins to install in profile "${profileName}".`);
208
224
  return;
209
225
  }
210
- console.log(pc.bold(`Installing ${data.plugins.length} plugin(s) from profile "${profileName}"...\n`));
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
- const spinner = ora(`Installing ${pc.cyan(plugin)}...`).start();
231
+ s.message(`Installing ${plugin}...`);
213
232
  try {
214
233
  execSync(`claude plugin install ${plugin} --scope project`, {
215
234
  stdio: "pipe",
216
235
  });
217
- spinner.succeed(`Installed ${pc.cyan(plugin)}`);
236
+ installed++;
218
237
  }
219
238
  catch {
220
- spinner.fail(`Failed to install ${pc.red(plugin)}`);
239
+ failed++;
240
+ p.log.error(`Failed to install ${plugin}`);
221
241
  }
222
242
  }
223
- console.log(pc.green("\nDone."));
243
+ s.stop(`${installed} installed${failed > 0 ? `, ${failed} failed` : ""}`);
244
+ p.log.success("Done.");
224
245
  }
225
246
  async function interactiveMode() {
226
- const action = (await select({
247
+ p.intro("ccx Agent Profile Manager");
248
+ const action = await p.select({
227
249
  message: "What do you want to do?",
228
- choices: [
229
- { name: "Install profile plugins", value: "install", description: "Run a profile to install its plugins" },
230
- { name: "Add plugin to profile", value: "plugin-add", description: "Add a plugin to an existing profile" },
231
- { name: "Remove plugin from profile", value: "plugin-remove", description: "Remove a plugin from a profile" },
232
- { name: "List profile plugins", value: "plugin-list", description: "Show plugins in a profile" },
233
- { name: "Create new profile", value: "add", description: "Create a new empty profile" },
234
- { name: "Delete profile", value: "remove", description: "Remove a profile" },
235
- { name: "List all profiles", value: "list", description: "Show all profiles" },
236
- { name: "Search plugins", value: "search", description: "Search plugins in marketplaces" },
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
- console.log(pc.yellow("No profiles found."));
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
- choices: names.map((n) => ({ name: n, value: n })),
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
- console.log(pc.yellow("No profiles found. Create one first."));
282
+ p.log.warn("No profiles found. Create one first.");
257
283
  return;
258
284
  }
259
- const name = (await select({
285
+ const name = await p.select({
260
286
  message: "Select profile:",
261
- choices: names.map((n) => ({ name: n, value: n })),
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
- console.log(pc.yellow("No profiles found."));
297
+ p.log.warn("No profiles found.");
270
298
  return;
271
299
  }
272
- const name = (await select({
300
+ const name = await p.select({
273
301
  message: "Select profile:",
274
- choices: names.map((n) => ({ name: n, value: n })),
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
- console.log(pc.yellow("No profiles found."));
312
+ p.log.warn("No profiles found.");
283
313
  return;
284
314
  }
285
- const name = (await select({
315
+ const name = await p.select({
286
316
  message: "Select profile:",
287
- choices: names.map((n) => ({ name: n, value: n })),
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(`${pc.bold("ccx")} — Agent Profile Manager for Claude Code
340
+ console.log(`ccx — Agent Profile Manager for Claude Code
308
341
 
309
- ${pc.bold("Usage:")}
310
- ${pc.cyan("ccx")} Interactive mode
311
- ${pc.cyan("ccx")} <profile> Install all plugins from profile
312
- ${pc.cyan("ccx add")} <name> Create a new profile
313
- ${pc.cyan("ccx remove")} <name> Remove a profile
314
- ${pc.cyan("ccx list")} List all profiles
315
- ${pc.cyan("ccx search")} <keyword> Search plugins in marketplaces
316
- ${pc.cyan("ccx <profile> add")} [plugin] Add plugin to profile
317
- ${pc.cyan("ccx <profile> remove")} [plugin] Remove plugin from profile
318
- ${pc.cyan("ccx <profile> list")} List plugins in profile`);
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(pc.red(`Unknown command: ccx ${args.join(" ")}`));
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.5",
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
- "@inquirer/prompts": "^7.0.0",
22
+ "@clack/prompts": "^1.4.0",
23
23
  "ora": "^8.0.0",
24
24
  "picocolors": "^1.1.0"
25
25
  },