@guanmu/ccprofile 0.1.5 → 0.1.6

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 +140 -108
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2,9 +2,7 @@
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { execSync } from "node:child_process";
5
- import pc from "picocolors";
6
- import ora from "ora";
7
- import { select, input } from "@inquirer/prompts";
5
+ import * as p from "@clack/prompts";
8
6
  const PROFILES_DIR = path.join(process.env.HOME, ".ccx", "profiles");
9
7
  const MARKETPLACES_DIR = path.join(process.env.HOME, ".claude", "plugins", "marketplaces");
10
8
  function ensureProfilesDir() {
@@ -16,7 +14,7 @@ function profilePath(name) {
16
14
  function readProfile(name) {
17
15
  const file = profilePath(name);
18
16
  if (!fs.existsSync(file)) {
19
- console.error(pc.red(`Profile "${name}" not found.`));
17
+ p.log.error(`Profile "${name}" not found.`);
20
18
  process.exit(1);
21
19
  }
22
20
  return JSON.parse(fs.readFileSync(file, "utf-8"));
@@ -42,11 +40,11 @@ function getAllPlugins() {
42
40
  if (!fs.existsSync(file))
43
41
  continue;
44
42
  const data = JSON.parse(fs.readFileSync(file, "utf-8"));
45
- for (const p of data.plugins || []) {
43
+ for (const pl of data.plugins || []) {
46
44
  plugins.push({
47
- name: p.name,
48
- description: p.description || "",
49
- category: p.category,
45
+ name: pl.name,
46
+ description: pl.description || "",
47
+ category: pl.category,
50
48
  marketplace: data.name,
51
49
  });
52
50
  }
@@ -56,35 +54,41 @@ function getAllPlugins() {
56
54
  // ── Commands ──────────────────────────────────────────────
57
55
  async function addProfile(name) {
58
56
  if (!name) {
59
- name = await input({ message: "Profile name:" });
57
+ name = (await p.text({
58
+ message: "Profile name:",
59
+ }));
60
+ if (p.isCancel(name))
61
+ return;
60
62
  }
61
63
  const file = profilePath(name);
62
64
  if (fs.existsSync(file)) {
63
- console.error(pc.red(`Profile "${name}" already exists.`));
64
- process.exit(1);
65
+ p.log.error(`Profile "${name}" already exists.`);
66
+ return;
65
67
  }
66
68
  writeProfile(name, { name, plugins: [] });
67
- console.log(pc.green(`Created profile "${name}".`));
69
+ p.log.success(`Created profile "${name}".`);
68
70
  }
69
71
  async function removeProfile(name) {
70
72
  const names = getProfileNames();
71
73
  if (names.length === 0) {
72
- console.log(pc.yellow("No profiles found."));
74
+ p.log.warn("No profiles found.");
73
75
  return;
74
76
  }
75
77
  if (!name) {
76
- name = (await select({
78
+ name = (await p.select({
77
79
  message: "Select profile to remove:",
78
- choices: names.map((n) => ({ name: n, value: n })),
80
+ options: names.map((n) => ({ value: n, label: n })),
79
81
  }));
82
+ if (p.isCancel(name))
83
+ return;
80
84
  }
81
85
  const file = profilePath(name);
82
86
  if (!fs.existsSync(file)) {
83
- console.error(pc.red(`Profile "${name}" not found.`));
84
- process.exit(1);
87
+ p.log.error(`Profile "${name}" not found.`);
88
+ return;
85
89
  }
86
90
  fs.unlinkSync(file);
87
- console.log(pc.green(`Removed profile "${name}".`));
91
+ p.log.success(`Removed profile "${name}".`);
88
92
  }
89
93
  async function listProfiles() {
90
94
  ensureProfilesDir();
@@ -92,111 +96,122 @@ async function listProfiles() {
92
96
  .readdirSync(PROFILES_DIR)
93
97
  .filter((f) => f.endsWith(".json"));
94
98
  if (files.length === 0) {
95
- console.log(pc.yellow("No profiles found."));
99
+ p.log.warn("No profiles found.");
96
100
  return;
97
101
  }
98
- console.log(pc.bold("Profiles:"));
99
102
  for (const f of files) {
100
103
  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)`)}`);
104
+ p.log.success(`${data.name} (${data.plugins.length} plugins)`);
102
105
  }
103
106
  }
104
107
  async function addPlugin(profileName, plugin) {
105
108
  const data = readProfile(profileName);
106
109
  if (!plugin) {
107
110
  const allPlugins = getAllPlugins();
108
- const available = allPlugins.filter((p) => !data.plugins.includes(p.name));
111
+ const available = allPlugins.filter((pl) => !data.plugins.includes(pl.name));
109
112
  if (available.length === 0) {
110
- console.log(pc.yellow("All available plugins already added."));
113
+ p.log.warn("All available plugins already added.");
111
114
  return;
112
115
  }
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({
116
+ const selected = await p.select({
122
117
  message: `Add plugin to "${profileName}":`,
123
- choices,
124
- }));
118
+ options: [
119
+ ...available.map((pl) => ({
120
+ value: pl.name,
121
+ label: pl.name,
122
+ hint: pl.description.slice(0, 60),
123
+ })),
124
+ { value: "__url__", label: "Enter URL manually...", hint: "Input a GitHub URL or plugin name" },
125
+ ],
126
+ });
127
+ if (p.isCancel(selected))
128
+ return;
125
129
  if (selected === "__url__") {
126
- plugin = await input({ message: "Plugin URL or name:" });
130
+ plugin = (await p.text({
131
+ message: "Plugin URL or name:",
132
+ }));
133
+ if (p.isCancel(plugin))
134
+ return;
127
135
  }
128
136
  else {
129
137
  plugin = selected;
130
138
  }
131
139
  }
132
140
  if (data.plugins.includes(plugin)) {
133
- console.error(pc.yellow(`Plugin "${plugin}" already in profile.`));
141
+ p.log.warn(`Plugin "${plugin}" already in profile.`);
134
142
  return;
135
143
  }
136
144
  data.plugins.push(plugin);
137
145
  writeProfile(profileName, data);
138
- console.log(pc.green(`Added "${plugin}" to profile "${profileName}".`));
146
+ p.log.success(`Added "${plugin}" to profile "${profileName}".`);
139
147
  }
140
148
  async function removePlugin(profileName, plugin) {
141
149
  const data = readProfile(profileName);
142
150
  if (data.plugins.length === 0) {
143
- console.log(pc.yellow("No plugins in this profile."));
151
+ p.log.warn("No plugins in this profile.");
144
152
  return;
145
153
  }
146
154
  if (!plugin) {
147
- plugin = (await select({
155
+ plugin = (await p.select({
148
156
  message: `Remove plugin from "${profileName}":`,
149
- choices: data.plugins.map((p) => ({ name: p, value: p })),
157
+ options: data.plugins.map((pl) => ({
158
+ value: pl,
159
+ label: pl,
160
+ })),
150
161
  }));
162
+ if (p.isCancel(plugin))
163
+ return;
151
164
  }
152
165
  const idx = data.plugins.indexOf(plugin);
153
166
  if (idx === -1) {
154
- console.error(pc.red(`Plugin "${plugin}" not found.`));
155
- process.exit(1);
167
+ p.log.error(`Plugin "${plugin}" not found.`);
168
+ return;
156
169
  }
157
170
  data.plugins.splice(idx, 1);
158
171
  writeProfile(profileName, data);
159
- console.log(pc.green(`Removed "${plugin}" from profile "${profileName}".`));
172
+ p.log.success(`Removed "${plugin}" from profile "${profileName}".`);
160
173
  }
161
174
  async function listPlugins(profileName) {
162
175
  const data = readProfile(profileName);
163
176
  if (data.plugins.length === 0) {
164
- console.log(pc.yellow(`No plugins in profile "${profileName}".`));
177
+ p.log.warn(`No plugins in profile "${profileName}".`);
165
178
  return;
166
179
  }
167
- console.log(pc.bold(`Plugins in "${profileName}":`));
168
- for (const p of data.plugins) {
169
- console.log(` ${pc.cyan(p)}`);
180
+ for (const pl of data.plugins) {
181
+ p.log.success(pl);
170
182
  }
171
183
  }
172
184
  async function searchPlugins(keyword) {
173
185
  if (!keyword) {
174
- keyword = await input({ message: "Search plugins:" });
186
+ keyword = (await p.text({
187
+ message: "Search plugins:",
188
+ }));
189
+ if (p.isCancel(keyword))
190
+ return;
175
191
  }
176
192
  const allPlugins = getAllPlugins();
177
193
  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));
194
+ const results = allPlugins.filter((pl) => pl.name.toLowerCase().includes(lower) ||
195
+ pl.description.toLowerCase().includes(lower) ||
196
+ (pl.category || "").toLowerCase().includes(lower));
181
197
  if (results.length === 0) {
182
- console.log(pc.yellow(`No plugins matching "${keyword}".`));
198
+ p.log.warn(`No plugins matching "${keyword}".`);
183
199
  return;
184
200
  }
185
- console.log(pc.bold(`Found ${results.length} plugin(s) for "${keyword}":\n`));
186
- // Group by marketplace
187
201
  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);
202
+ for (const pl of results) {
203
+ const list = grouped.get(pl.marketplace) || [];
204
+ list.push(pl);
205
+ grouped.set(pl.marketplace, list);
192
206
  }
207
+ console.log(`\n Found ${results.length} plugin(s) for "${keyword}":\n`);
193
208
  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)}`);
209
+ console.log(` [${marketplace}]`);
210
+ for (const pl of plugins) {
211
+ const desc = pl.description.length > 70
212
+ ? pl.description.slice(0, 67) + "..."
213
+ : pl.description;
214
+ console.log(` ${pl.name.padEnd(24)} ${desc}`);
200
215
  }
201
216
  console.log();
202
217
  }
@@ -204,88 +219,104 @@ async function searchPlugins(keyword) {
204
219
  async function executeProfile(profileName) {
205
220
  const data = readProfile(profileName);
206
221
  if (data.plugins.length === 0) {
207
- console.log(pc.yellow(`No plugins to install in profile "${profileName}".`));
222
+ p.log.warn(`No plugins to install in profile "${profileName}".`);
208
223
  return;
209
224
  }
210
- console.log(pc.bold(`Installing ${data.plugins.length} plugin(s) from profile "${profileName}"...\n`));
225
+ const s = p.spinner();
226
+ s.start(`Installing ${data.plugins.length} plugin(s) from "${profileName}"...`);
227
+ let installed = 0;
228
+ let failed = 0;
211
229
  for (const plugin of data.plugins) {
212
- const spinner = ora(`Installing ${pc.cyan(plugin)}...`).start();
230
+ s.message(`Installing ${plugin}...`);
213
231
  try {
214
232
  execSync(`claude plugin install ${plugin} --scope project`, {
215
233
  stdio: "pipe",
216
234
  });
217
- spinner.succeed(`Installed ${pc.cyan(plugin)}`);
235
+ installed++;
218
236
  }
219
237
  catch {
220
- spinner.fail(`Failed to install ${pc.red(plugin)}`);
238
+ failed++;
239
+ p.log.error(`Failed to install ${plugin}`);
221
240
  }
222
241
  }
223
- console.log(pc.green("\nDone."));
242
+ s.stop(`${installed} installed${failed > 0 ? `, ${failed} failed` : ""}`);
243
+ p.log.success("Done.");
224
244
  }
225
245
  async function interactiveMode() {
226
- const action = (await select({
246
+ p.intro("ccx Agent Profile Manager");
247
+ const action = await p.select({
227
248
  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" },
249
+ options: [
250
+ { value: "install", label: "Install profile plugins", hint: "Run a profile to install its plugins" },
251
+ { value: "plugin-add", label: "Add plugin to profile", hint: "Add a plugin to an existing profile" },
252
+ { value: "plugin-remove", label: "Remove plugin from profile", hint: "Remove a plugin from a profile" },
253
+ { value: "plugin-list", label: "List profile plugins", hint: "Show plugins in a profile" },
254
+ { value: "add", label: "Create new profile", hint: "Create a new empty profile" },
255
+ { value: "remove", label: "Delete profile", hint: "Remove a profile" },
256
+ { value: "list", label: "List all profiles", hint: "Show all profiles" },
257
+ { value: "search", label: "Search plugins", hint: "Search plugins in marketplaces" },
237
258
  ],
238
- }));
259
+ });
260
+ if (p.isCancel(action))
261
+ return;
239
262
  switch (action) {
240
263
  case "install": {
241
264
  const names = getProfileNames();
242
265
  if (names.length === 0) {
243
- console.log(pc.yellow("No profiles found."));
266
+ p.log.warn("No profiles found.");
244
267
  return;
245
268
  }
246
- const name = await select({
269
+ const name = await p.select({
247
270
  message: "Select profile to install:",
248
- choices: names.map((n) => ({ name: n, value: n })),
271
+ options: names.map((n) => ({ value: n, label: n })),
249
272
  });
273
+ if (p.isCancel(name))
274
+ return;
250
275
  await executeProfile(name);
251
276
  break;
252
277
  }
253
278
  case "plugin-add": {
254
279
  const names = getProfileNames();
255
280
  if (names.length === 0) {
256
- console.log(pc.yellow("No profiles found. Create one first."));
281
+ p.log.warn("No profiles found. Create one first.");
257
282
  return;
258
283
  }
259
- const name = (await select({
284
+ const name = await p.select({
260
285
  message: "Select profile:",
261
- choices: names.map((n) => ({ name: n, value: n })),
262
- }));
286
+ options: names.map((n) => ({ value: n, label: n })),
287
+ });
288
+ if (p.isCancel(name))
289
+ return;
263
290
  await addPlugin(name);
264
291
  break;
265
292
  }
266
293
  case "plugin-remove": {
267
294
  const names = getProfileNames();
268
295
  if (names.length === 0) {
269
- console.log(pc.yellow("No profiles found."));
296
+ p.log.warn("No profiles found.");
270
297
  return;
271
298
  }
272
- const name = (await select({
299
+ const name = await p.select({
273
300
  message: "Select profile:",
274
- choices: names.map((n) => ({ name: n, value: n })),
275
- }));
301
+ options: names.map((n) => ({ value: n, label: n })),
302
+ });
303
+ if (p.isCancel(name))
304
+ return;
276
305
  await removePlugin(name);
277
306
  break;
278
307
  }
279
308
  case "plugin-list": {
280
309
  const names = getProfileNames();
281
310
  if (names.length === 0) {
282
- console.log(pc.yellow("No profiles found."));
311
+ p.log.warn("No profiles found.");
283
312
  return;
284
313
  }
285
- const name = (await select({
314
+ const name = await p.select({
286
315
  message: "Select profile:",
287
- choices: names.map((n) => ({ name: n, value: n })),
288
- }));
316
+ options: names.map((n) => ({ value: n, label: n })),
317
+ });
318
+ if (p.isCancel(name))
319
+ return;
289
320
  await listPlugins(name);
290
321
  break;
291
322
  }
@@ -302,20 +333,21 @@ async function interactiveMode() {
302
333
  await searchPlugins();
303
334
  break;
304
335
  }
336
+ p.outro("Done.");
305
337
  }
306
338
  function printHelp() {
307
- console.log(`${pc.bold("ccx")} — Agent Profile Manager for Claude Code
339
+ console.log(`ccx — Agent Profile Manager for Claude Code
308
340
 
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`);
341
+ Usage:
342
+ ccx Interactive mode
343
+ ccx <profile> Install all plugins from profile
344
+ ccx add <name> Create a new profile
345
+ ccx remove <name> Remove a profile
346
+ ccx list List all profiles
347
+ ccx search <keyword> Search plugins in marketplaces
348
+ ccx <profile> add [plugin] Add plugin to profile
349
+ ccx <profile> remove [plugin] Remove plugin from profile
350
+ ccx <profile> list List plugins in profile`);
319
351
  }
320
352
  // ── Main ──────────────────────────────────────────────────
321
353
  const args = process.argv.slice(2);
@@ -357,7 +389,7 @@ switch (cmd) {
357
389
  listPlugins(profileName);
358
390
  }
359
391
  else {
360
- console.error(pc.red(`Unknown command: ccx ${args.join(" ")}`));
392
+ console.error(`Unknown command: ccx ${args.join(" ")}`);
361
393
  printHelp();
362
394
  process.exit(1);
363
395
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanmu/ccprofile",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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
  },