claude-manager 1.5.1 → 1.5.4

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 +20 -2
  2. package/dist/cli.js +768 -275
  3. package/package.json +6 -3
package/dist/cli.js CHANGED
@@ -1,74 +1,119 @@
1
1
  #!/usr/bin/env node
2
- import React, { useState, useEffect } from "react";
2
+ // src/cli.js
3
+ import React, { useState, useEffect, useMemo } from "react";
3
4
  import { render, Box, Text, useInput, useApp } from "ink";
4
5
  import SelectInput from "ink-select-input";
5
6
  import TextInput from "ink-text-input";
6
- import fs from "fs";
7
- import path from "path";
8
- import os from "os";
9
- import { execSync, spawnSync } from "child_process";
10
- const VERSION = "1.5.1";
11
- const PROFILES_DIR = path.join(os.homedir(), ".claude", "profiles");
12
- const SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
13
- const CLAUDE_JSON_PATH = path.join(os.homedir(), ".claude.json");
14
- const LAST_PROFILE_PATH = path.join(os.homedir(), ".claude", ".last-profile");
15
- const MCP_REGISTRY_URL = "https://registry.modelcontextprotocol.io/v0/servers";
16
- const args = process.argv.slice(2);
17
- const cmd = args[0];
18
- if (!fs.existsSync(PROFILES_DIR)) fs.mkdirSync(PROFILES_DIR, { recursive: true });
19
- if (args.includes("-v") || args.includes("--version")) {
20
- console.log(`cm v${VERSION}`);
21
- process.exit(0);
22
- }
23
- if (args.includes("-h") || args.includes("--help")) {
24
- console.log(`cm v${VERSION} - Claude Settings Manager
7
+ import { spawnSync, execSync as execSync2 } from "child_process";
8
+ import fs2 from "fs";
9
+ import path3 from "path";
10
+ import Fuse from "fuse.js";
25
11
 
26
- Usage: cm [command] [options]
27
-
28
- Commands:
29
- (none) Select profile interactively
30
- new Create a new profile
31
- edit <n> Edit profile (by name or number)
32
- delete <n> Delete profile (by name or number)
33
- status Show current settings
34
- list List all profiles
35
- mcp [query] Search and add MCP servers
36
- skills Browse and add Anthropic skills
12
+ // src/constants.js
13
+ import os from "os";
14
+ import path from "path";
15
+ var VERSION = "1.5.4";
16
+ var LOGO = `\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
17
+ \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
18
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557
19
+ \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D
20
+ \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
21
+ \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D`;
22
+ var PROFILES_DIR = path.join(os.homedir(), ".claude", "profiles");
23
+ var SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
24
+ var CLAUDE_JSON_PATH = path.join(os.homedir(), ".claude.json");
25
+ var LAST_PROFILE_PATH = path.join(os.homedir(), ".claude", ".last-profile");
26
+ var SKILLS_DIR = path.join(os.homedir(), ".claude", "skills");
27
+ var MCP_REGISTRY_URL = "https://registry.modelcontextprotocol.io/v0/servers";
28
+ var SKILL_SOURCES = [
29
+ { url: "https://api.github.com/repos/anthropics/skills/contents/skills", base: "https://github.com/anthropics/skills/tree/main/skills" },
30
+ { url: "https://api.github.com/repos/Prat011/awesome-llm-skills/contents/skills", base: "https://github.com/Prat011/awesome-llm-skills/tree/main/skills" },
31
+ { url: "https://api.github.com/repos/skillcreatorai/Ai-Agent-Skills/contents/skills", base: "https://github.com/skillcreatorai/Ai-Agent-Skills/tree/main/skills" }
32
+ ];
33
+ var PROVIDERS = [
34
+ { label: "Anthropic (Direct)", value: "anthropic", url: "", needsKey: true },
35
+ { label: "Amazon Bedrock", value: "bedrock", url: "", needsKey: false },
36
+ { label: "Z.AI", value: "zai", url: "https://api.z.ai/api/anthropic", needsKey: true },
37
+ { label: "MiniMax", value: "minimax", url: "https://api.minimax.io/anthropic", needsKey: true },
38
+ { label: "Custom", value: "custom", url: "", needsKey: true }
39
+ ];
40
+ var FETCH_TIMEOUT = 1e4;
41
+ var NPM_OUTDATED_TIMEOUT = 5e3;
42
+ var GIT_CLONE_TIMEOUT = 3e4;
43
+ var GIT_SPARSE_TIMEOUT = 1e4;
44
+ var GIT_MOVE_TIMEOUT = 5e3;
45
+ var GIT_CLEANUP_TIMEOUT = 5e3;
46
+ var MCP_PAGE_SIZE = 50;
47
+ var SKILLS_PAGE_SIZE = 50;
48
+ var DEFAULT_SETTINGS = {
49
+ env: {},
50
+ model: "opus",
51
+ alwaysThinkingEnabled: true,
52
+ defaultMode: "bypassPermissions"
53
+ };
54
+ var API_TIMEOUT_MS = "3000000";
55
+ var FUSE_THRESHOLD = 0.3;
37
56
 
38
- Options:
39
- --last, -l Use last profile without menu
40
- --skip-update Skip update check
41
- --yolo Run claude with --dangerously-skip-permissions
42
- -v, --version Show version
43
- -h, --help Show help`);
44
- process.exit(0);
45
- }
46
- const skipUpdate = args.includes("--skip-update");
47
- const useLast = args.includes("--last") || args.includes("-l");
48
- const dangerMode = args.includes("--dangerously-skip-permissions") || args.includes("--yolo");
49
- const loadProfiles = () => {
57
+ // src/utils.js
58
+ import fs from "fs";
59
+ import path2 from "path";
60
+ import { execSync } from "child_process";
61
+ import { createInterface } from "readline";
62
+ var ensureProfilesDir = () => {
63
+ if (!fs.existsSync(PROFILES_DIR)) {
64
+ fs.mkdirSync(PROFILES_DIR, { recursive: true });
65
+ }
66
+ };
67
+ var logError = (context, error) => {
68
+ if (process.env.DEBUG || process.env.CM_DEBUG) {
69
+ console.error(`[${context}]`, error?.message || error);
70
+ }
71
+ };
72
+ var safeParseInt = (value, defaultValue = null) => {
73
+ const parsed = parseInt(value, 10);
74
+ return Number.isNaN(parsed) ? defaultValue : parsed;
75
+ };
76
+ var sanitizeProfileName = (name) => {
77
+ return name.toLowerCase().replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
78
+ };
79
+ var sanitizeFilePath = (filename, baseDir) => {
80
+ const sanitized = path2.basename(filename);
81
+ const resolved = path2.resolve(baseDir, sanitized);
82
+ if (!resolved.startsWith(baseDir)) {
83
+ return null;
84
+ }
85
+ return sanitized;
86
+ };
87
+ var loadProfiles = () => {
50
88
  const profiles = [];
51
- if (fs.existsSync(PROFILES_DIR)) {
52
- for (const file of fs.readdirSync(PROFILES_DIR).sort()) {
53
- if (file.endsWith(".json")) {
54
- try {
55
- const content = JSON.parse(fs.readFileSync(path.join(PROFILES_DIR, file), "utf8"));
56
- profiles.push({
57
- label: content.name || file.replace(".json", ""),
58
- value: file,
59
- key: file,
60
- group: content.group || null,
61
- data: content
62
- });
63
- } catch {
64
- }
65
- }
89
+ ensureProfilesDir();
90
+ if (!fs.existsSync(PROFILES_DIR)) {
91
+ return profiles;
92
+ }
93
+ const files = fs.readdirSync(PROFILES_DIR).sort();
94
+ for (const file of files) {
95
+ if (!file.endsWith(".json")) continue;
96
+ const filePath = path2.join(PROFILES_DIR, file);
97
+ try {
98
+ const content = JSON.parse(fs.readFileSync(filePath, "utf8"));
99
+ profiles.push({
100
+ label: content.name || file.replace(".json", ""),
101
+ value: file,
102
+ key: file,
103
+ group: content.group || null,
104
+ data: content
105
+ });
106
+ } catch (error) {
107
+ logError("loadProfiles", error);
66
108
  }
67
109
  }
68
110
  return profiles;
69
111
  };
70
- const applyProfile = (filename) => {
71
- const profilePath = path.join(PROFILES_DIR, filename);
112
+ var applyProfile = (filename) => {
113
+ const profilePath = path2.join(PROFILES_DIR, filename);
114
+ if (!fs.existsSync(profilePath)) {
115
+ throw new Error(`Profile not found: ${filename}`);
116
+ }
72
117
  const profile = JSON.parse(fs.readFileSync(profilePath, "utf8"));
73
118
  const { name, group, mcpServers, ...settings } = profile;
74
119
  fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
@@ -77,65 +122,356 @@ const applyProfile = (filename) => {
77
122
  const claudeJson = fs.existsSync(CLAUDE_JSON_PATH) ? JSON.parse(fs.readFileSync(CLAUDE_JSON_PATH, "utf8")) : {};
78
123
  claudeJson.mcpServers = mcpServers;
79
124
  fs.writeFileSync(CLAUDE_JSON_PATH, JSON.stringify(claudeJson, null, 2));
80
- } catch {
125
+ } catch (error) {
126
+ logError("applyProfile-mcp", error);
81
127
  }
82
128
  }
83
129
  fs.writeFileSync(LAST_PROFILE_PATH, filename);
84
130
  return name || filename;
85
131
  };
86
- const getLastProfile = () => {
132
+ var getLastProfile = () => {
87
133
  try {
88
- return fs.readFileSync(LAST_PROFILE_PATH, "utf8").trim();
89
- } catch {
134
+ const content = fs.readFileSync(LAST_PROFILE_PATH, "utf8");
135
+ return content.trim() || null;
136
+ } catch (error) {
90
137
  return null;
91
138
  }
92
139
  };
93
- const checkProjectProfile = () => {
94
- const localProfile = path.join(process.cwd(), ".claude-profile");
140
+ var checkProjectProfile = () => {
141
+ const localProfile = path2.join(process.cwd(), ".claude-profile");
95
142
  if (fs.existsSync(localProfile)) {
96
- return fs.readFileSync(localProfile, "utf8").trim();
143
+ try {
144
+ return fs.readFileSync(localProfile, "utf8").trim();
145
+ } catch (error) {
146
+ logError("checkProjectProfile", error);
147
+ }
97
148
  }
98
149
  return null;
99
150
  };
100
- const checkForUpdate = () => {
101
- if (skipUpdate) return { needsUpdate: false };
151
+ var confirm = async (message) => {
152
+ const rl = createInterface({
153
+ input: process.stdin,
154
+ output: process.stdout
155
+ });
156
+ return new Promise((resolve) => {
157
+ rl.question(`${message} (y/N): `, (answer) => {
158
+ rl.close();
159
+ const normalized = answer.toLowerCase().trim();
160
+ resolve(normalized === "y" || normalized === "yes");
161
+ });
162
+ });
163
+ };
164
+ var validateProfile = (profile) => {
165
+ const errors = [];
166
+ if (!profile.name || profile.name.trim().length === 0) {
167
+ errors.push("Profile name is required");
168
+ }
169
+ if (profile.env?.ANTHROPIC_AUTH_TOKEN) {
170
+ const key = profile.env.ANTHROPIC_AUTH_TOKEN;
171
+ if (!key.startsWith("sk-ant-")) {
172
+ errors.push('API key should start with "sk-ant-"');
173
+ }
174
+ if (key.length < 20) {
175
+ errors.push("API key appears too short");
176
+ }
177
+ }
178
+ if (profile.env?.ANTHROPIC_MODEL) {
179
+ const model = profile.env.ANTHROPIC_MODEL;
180
+ const validPatterns = [
181
+ /^claude-\d+(\.\d+)?(-\d+)?$/,
182
+ /^glm-/,
183
+ /^minimax-/,
184
+ /^anthropic\.claude-/
185
+ ];
186
+ if (!validPatterns.some((p) => p.test(model))) {
187
+ errors.push(`Model format looks invalid: ${model}`);
188
+ }
189
+ }
190
+ if (profile.env?.ANTHROPIC_BASE_URL) {
191
+ try {
192
+ new URL(profile.env.ANTHROPIC_BASE_URL);
193
+ } catch {
194
+ errors.push("Base URL is not a valid URL");
195
+ }
196
+ }
197
+ return { valid: errors.length === 0, errors };
198
+ };
199
+ var getInstalledSkills = () => {
200
+ if (!fs.existsSync(SKILLS_DIR)) return [];
102
201
  try {
103
- const current = execSync("claude --version 2>/dev/null", { encoding: "utf8" }).match(/(\d+\.\d+\.\d+)/)?.[1];
104
- const output = execSync("brew outdated claude-code 2>&1 || true", { encoding: "utf8" }).trim();
105
- return { current, needsUpdate: output.includes("claude-code") };
106
- } catch {
202
+ return fs.readdirSync(SKILLS_DIR).filter((f) => {
203
+ const p = path2.join(SKILLS_DIR, f);
204
+ try {
205
+ return fs.statSync(p).isDirectory() && !f.startsWith(".");
206
+ } catch {
207
+ return false;
208
+ }
209
+ });
210
+ } catch (error) {
211
+ logError("getInstalledSkills", error);
212
+ return [];
213
+ }
214
+ };
215
+ var removeSkill = (skillName) => {
216
+ const skillPath = path2.join(SKILLS_DIR, skillName);
217
+ if (!fs.existsSync(skillPath)) {
218
+ return { success: false, message: "Skill not found" };
219
+ }
220
+ try {
221
+ fs.rmSync(skillPath, { recursive: true, force: true });
222
+ return { success: true };
223
+ } catch (error) {
224
+ logError("removeSkill", error);
225
+ return { success: false, message: "Failed to remove skill" };
226
+ }
227
+ };
228
+ var checkForUpdate = async (skipUpdate2) => {
229
+ if (skipUpdate2) return { needsUpdate: false };
230
+ const { exec } = await import("child_process");
231
+ const { promisify } = await import("util");
232
+ const execAsync = promisify(exec);
233
+ try {
234
+ const versionResult = await execAsync("claude --version 2>/dev/null").catch(() => ({ stdout: "" }));
235
+ const current = versionResult.stdout.match(/(\d+\.\d+\.\d+)/)?.[1];
236
+ if (!current) return { needsUpdate: false };
237
+ let needsUpdate = false;
238
+ if (process.platform === "darwin") {
239
+ const outdatedResult = await execAsync("brew outdated claude-code 2>&1 || true").catch(() => ({ stdout: "" }));
240
+ needsUpdate = outdatedResult.stdout.includes("claude-code");
241
+ }
242
+ if (!needsUpdate) {
243
+ const npmListResult = await execAsync("npm list -g @anthropic-ai/claude-code 2>/dev/null").catch(() => ({ stdout: "" }));
244
+ if (npmListResult.stdout.includes("@anthropic-ai/claude-code")) {
245
+ try {
246
+ const npmOutdated = await execAsync("npm outdated -g @anthropic-ai/claude-code --json 2>/dev/null || true", { timeout: NPM_OUTDATED_TIMEOUT });
247
+ needsUpdate = npmOutdated.stdout.length > 0;
248
+ } catch {
249
+ needsUpdate = true;
250
+ }
251
+ }
252
+ }
253
+ return { current, needsUpdate };
254
+ } catch (error) {
255
+ logError("checkForUpdate", error);
107
256
  return { needsUpdate: false };
108
257
  }
109
258
  };
110
- const launchClaude = () => {
259
+ var launchClaude = (dangerMode2) => {
111
260
  try {
112
- const claudeArgs = dangerMode ? "--dangerously-skip-permissions" : "";
261
+ const claudeArgs = dangerMode2 ? "--dangerously-skip-permissions" : "";
113
262
  execSync(`claude ${claudeArgs}`, { stdio: "inherit" });
114
263
  } catch (e) {
115
264
  process.exit(e.status || 1);
116
265
  }
117
266
  process.exit(0);
118
267
  };
268
+ var searchMcpServers = async (query, offset = 0) => {
269
+ const controller = new AbortController();
270
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
271
+ try {
272
+ const res = await fetch(`${MCP_REGISTRY_URL}?limit=200`, { signal: controller.signal });
273
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
274
+ const data = await res.json();
275
+ const seen = /* @__PURE__ */ new Set();
276
+ const filtered = data.servers.filter((s) => {
277
+ if (seen.has(s.server.name)) return false;
278
+ seen.add(s.server.name);
279
+ const isLatest = s._meta?.["io.modelcontextprotocol.registry/official"]?.isLatest !== false;
280
+ const matchesQuery = !query || s.server.name.toLowerCase().includes(query.toLowerCase()) || s.server.description?.toLowerCase().includes(query.toLowerCase());
281
+ return isLatest && matchesQuery;
282
+ });
283
+ const MCP_PAGE_SIZE2 = 50;
284
+ return {
285
+ servers: filtered.slice(offset, offset + MCP_PAGE_SIZE2),
286
+ total: filtered.length,
287
+ hasMore: offset + MCP_PAGE_SIZE2 < filtered.length,
288
+ offset
289
+ };
290
+ } catch (error) {
291
+ logError("searchMcpServers", error);
292
+ return { servers: [], total: 0, hasMore: false, offset: 0 };
293
+ } finally {
294
+ clearTimeout(timeout);
295
+ }
296
+ };
297
+ var addMcpToProfile = (server, profileFile) => {
298
+ const sanitizedFile = sanitizeFilePath(profileFile, PROFILES_DIR);
299
+ if (!sanitizedFile) {
300
+ throw new Error("Invalid profile file");
301
+ }
302
+ const profilePath = path2.join(PROFILES_DIR, sanitizedFile);
303
+ const profile = JSON.parse(fs.readFileSync(profilePath, "utf8"));
304
+ if (!profile.mcpServers) profile.mcpServers = {};
305
+ const s = server.server;
306
+ const name = s.name.split("/").pop();
307
+ if (s.remotes?.[0]) {
308
+ const remote = s.remotes[0];
309
+ profile.mcpServers[name] = {
310
+ type: remote.type === "streamable-http" ? "http" : remote.type,
311
+ url: remote.url
312
+ };
313
+ } else if (s.packages?.[0]) {
314
+ const pkg = s.packages[0];
315
+ if (pkg.registryType === "npm") {
316
+ profile.mcpServers[name] = {
317
+ type: "stdio",
318
+ command: "npx",
319
+ args: ["-y", pkg.identifier]
320
+ };
321
+ } else if (pkg.registryType === "pypi") {
322
+ profile.mcpServers[name] = {
323
+ type: "stdio",
324
+ command: "uvx",
325
+ args: [pkg.identifier]
326
+ };
327
+ }
328
+ }
329
+ fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
330
+ return name;
331
+ };
332
+ var fetchSkills = async () => {
333
+ const seen = /* @__PURE__ */ new Set();
334
+ const skills = [];
335
+ const promises = SKILL_SOURCES.map(async (source) => {
336
+ const controller = new AbortController();
337
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
338
+ try {
339
+ const res = await fetch(source.url, {
340
+ signal: controller.signal,
341
+ headers: { "Accept": "application/vnd.github.v3+json" }
342
+ });
343
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
344
+ const data = await res.json();
345
+ if (Array.isArray(data)) {
346
+ for (const s of data.filter((s2) => s2.type === "dir")) {
347
+ if (!seen.has(s.name)) {
348
+ seen.add(s.name);
349
+ skills.push({
350
+ label: s.name,
351
+ value: `${source.base}/${s.name}`,
352
+ key: s.name
353
+ });
354
+ }
355
+ }
356
+ }
357
+ } catch (error) {
358
+ logError(`fetchSkills(${source.url})`, error);
359
+ } finally {
360
+ clearTimeout(timeout);
361
+ }
362
+ });
363
+ await Promise.all(promises);
364
+ return skills.sort((a, b) => a.label.localeCompare(b.label));
365
+ };
366
+ var addSkillToClaudeJson = (skillName, skillUrl) => {
367
+ try {
368
+ if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
369
+ const skillPath = path2.join(SKILLS_DIR, skillName);
370
+ if (fs.existsSync(skillPath)) {
371
+ return { success: false, message: "Skill already installed" };
372
+ }
373
+ const match = skillUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)\/(.+)/);
374
+ if (!match) return { success: false, message: "Invalid skill URL" };
375
+ const [, owner, repo, branch, skillSubPath] = match;
376
+ const tempDir = `/tmp/skill-clone-${Date.now()}`;
377
+ const sanitizedTempDir = sanitizeFilePath(`skill-clone-${Date.now()}`, "/tmp");
378
+ const finalTempDir = path2.join("/tmp", sanitizedTempDir || "skill-clone");
379
+ execSync(`git clone --depth 1 --filter=blob:none --sparse "https://github.com/${owner}/${repo}.git" "${finalTempDir}" 2>/dev/null`, { timeout: GIT_CLONE_TIMEOUT });
380
+ execSync(`cd "${finalTempDir}" && git sparse-checkout set "${skillSubPath}" 2>/dev/null`, { timeout: GIT_SPARSE_TIMEOUT });
381
+ const sourcePath = path2.join(finalTempDir, skillSubPath);
382
+ if (fs.existsSync(sourcePath)) {
383
+ execSync(`mv "${sourcePath}" "${skillPath}"`, { timeout: GIT_MOVE_TIMEOUT });
384
+ }
385
+ execSync(`rm -rf "${finalTempDir}"`, { timeout: GIT_CLEANUP_TIMEOUT });
386
+ return { success: true };
387
+ } catch (e) {
388
+ logError("addSkillToClaudeJson", e);
389
+ return { success: false, message: "Failed to download skill" };
390
+ }
391
+ };
392
+ var createDefaultSettings = () => {
393
+ if (!fs.existsSync(SETTINGS_PATH)) {
394
+ fs.writeFileSync(SETTINGS_PATH, JSON.stringify(DEFAULT_SETTINGS, null, 2));
395
+ }
396
+ };
397
+ var buildProfileData = (name, provider, apiKey, model, group, providers) => {
398
+ const prov = providers.find((p) => p.value === provider);
399
+ return {
400
+ name,
401
+ group: group || void 0,
402
+ env: {
403
+ ...apiKey && { ANTHROPIC_AUTH_TOKEN: apiKey },
404
+ ...model && { ANTHROPIC_MODEL: model },
405
+ ...prov?.url && { ANTHROPIC_BASE_URL: prov.url },
406
+ API_TIMEOUT_MS
407
+ },
408
+ model: "opus",
409
+ alwaysThinkingEnabled: true,
410
+ defaultMode: "bypassPermissions"
411
+ };
412
+ };
413
+
414
+ // src/cli.js
415
+ ensureProfilesDir();
416
+ var args = process.argv.slice(2);
417
+ var cmd = args[0];
418
+ if (args.includes("-v") || args.includes("--version")) {
419
+ console.log(`cm v${VERSION}`);
420
+ process.exit(0);
421
+ }
422
+ if (args.includes("-h") || args.includes("--help")) {
423
+ console.log(`cm v${VERSION} - Claude Settings Manager
424
+
425
+ Usage: cm [command] [options]
426
+
427
+ Commands:
428
+ (none) Select profile interactively
429
+ new Create a new profile
430
+ edit <n> Edit profile (by name or number)
431
+ copy <n> <new> Copy/duplicate a profile
432
+ delete <n> Delete profile (by name or number)
433
+ status Show current settings
434
+ list List all profiles
435
+ config Open Claude settings.json in editor
436
+ mcp [query] Search and add MCP servers
437
+ mcp remove Remove MCP server from profile
438
+ skills Browse and add Anthropic skills
439
+ skills list List installed skills
440
+ skills remove Remove an installed skill
441
+
442
+ Options:
443
+ --last, -l Use last profile without menu
444
+ --skip-update Skip update check
445
+ --yolo Run claude with --dangerously-skip-permissions
446
+ --force, -f Skip confirmation prompts (e.g., for delete)
447
+ -v, --version Show version
448
+ -h, --help Show help`);
449
+ process.exit(0);
450
+ }
451
+ var skipUpdate = args.includes("--skip-update");
452
+ var useLast = args.includes("--last") || args.includes("-l");
453
+ var dangerMode = args.includes("--dangerously-skip-permissions") || args.includes("--yolo");
119
454
  if (useLast) {
120
455
  const last = getLastProfile();
121
- if (last && fs.existsSync(path.join(PROFILES_DIR, last))) {
456
+ const lastPath = last ? path3.join(PROFILES_DIR, last) : null;
457
+ if (last && lastPath && fs2.existsSync(lastPath)) {
122
458
  const name = applyProfile(last);
123
459
  console.log(`\x1B[32m\u2713\x1B[0m Applied: ${name}
124
460
  `);
125
- launchClaude();
461
+ launchClaude(dangerMode);
126
462
  } else {
127
463
  console.log("\x1B[31mNo last profile found\x1B[0m");
128
464
  process.exit(1);
129
465
  }
130
466
  }
131
- const projectProfile = checkProjectProfile();
467
+ var projectProfile = checkProjectProfile();
132
468
  if (projectProfile && !cmd) {
133
469
  const profiles = loadProfiles();
134
470
  const match = profiles.find((p) => p.label === projectProfile || p.value === projectProfile + ".json");
135
471
  if (match) {
136
472
  console.log(`\x1B[36mUsing project profile: ${match.label}\x1B[0m`);
137
473
  applyProfile(match.value);
138
- launchClaude();
474
+ launchClaude(dangerMode);
139
475
  }
140
476
  }
141
477
  if (cmd === "status") {
@@ -157,36 +493,29 @@ Profile MCP Servers (${Object.keys(mcpServers).length}):`);
157
493
  } else {
158
494
  console.log("No profile active");
159
495
  }
160
- const skillsDir = path.join(os.homedir(), ".claude", "skills");
161
- try {
162
- if (fs.existsSync(skillsDir)) {
163
- const installedSkills = fs.readdirSync(skillsDir).filter((f) => {
164
- const p = path.join(skillsDir, f);
165
- return fs.statSync(p).isDirectory() && !f.startsWith(".");
166
- });
167
- if (installedSkills.length > 0) {
168
- console.log(`
496
+ const installedSkills = getInstalledSkills();
497
+ if (installedSkills.length > 0) {
498
+ console.log(`
169
499
  Installed Skills (${installedSkills.length}):`);
170
- installedSkills.forEach((s) => console.log(` - ${s}`));
171
- }
172
- }
173
- } catch {
500
+ installedSkills.forEach((s) => console.log(` - ${s}`));
174
501
  }
175
502
  try {
176
- const claudeJson = JSON.parse(fs.readFileSync(CLAUDE_JSON_PATH, "utf8"));
503
+ const claudeJson = JSON.parse(fs2.readFileSync(CLAUDE_JSON_PATH, "utf8"));
177
504
  const globalMcp = claudeJson.mcpServers || {};
178
505
  if (Object.keys(globalMcp).length > 0) {
179
506
  console.log(`
180
507
  Global MCP Servers (${Object.keys(globalMcp).length}):`);
181
508
  Object.keys(globalMcp).forEach((s) => console.log(` - ${s}`));
182
509
  }
183
- } catch {
510
+ } catch (error) {
511
+ logError("status-mcp", error);
184
512
  }
185
513
  try {
186
- const ver = execSync("claude --version 2>/dev/null", { encoding: "utf8" }).trim();
514
+ const ver = execSync2("claude --version 2>/dev/null", { encoding: "utf8" }).trim();
187
515
  console.log(`
188
516
  Claude: ${ver}`);
189
- } catch {
517
+ } catch (error) {
518
+ logError("status-version", error);
190
519
  }
191
520
  process.exit(0);
192
521
  }
@@ -200,104 +529,145 @@ if (cmd === "list") {
200
529
  });
201
530
  process.exit(0);
202
531
  }
532
+ if (cmd === "config") {
533
+ const editor = process.env.EDITOR || "nano";
534
+ createDefaultSettings();
535
+ console.log(`Opening ${SETTINGS_PATH} in ${editor}...`);
536
+ spawnSync(editor, [SETTINGS_PATH], { stdio: "inherit" });
537
+ process.exit(0);
538
+ }
203
539
  if (cmd === "delete") {
540
+ const forceDelete = args.includes("--force") || args.includes("-f");
204
541
  const profiles = loadProfiles();
205
542
  const target = args[1];
206
- const idx = parseInt(target) - 1;
207
- const match = profiles[idx] || profiles.find((p) => p.label.toLowerCase() === target?.toLowerCase());
208
- if (match) {
209
- fs.unlinkSync(path.join(PROFILES_DIR, match.value));
210
- console.log(`\x1B[32m\u2713\x1B[0m Deleted: ${match.label}`);
211
- } else {
543
+ if (!target) {
544
+ console.log("\x1B[31mUsage: cm delete <profile>\x1B[0m");
545
+ console.log(" profile: Profile name or number");
546
+ process.exit(1);
547
+ }
548
+ const idx = safeParseInt(target, -1);
549
+ const match = idx > 0 && idx <= profiles.length ? profiles[idx - 1] : profiles.find((p) => p.label.toLowerCase() === target?.toLowerCase());
550
+ if (!match) {
212
551
  console.log(`\x1B[31mProfile not found: ${target}\x1B[0m`);
552
+ process.exit(1);
553
+ }
554
+ const shouldDelete = forceDelete || await confirm(`Delete profile "${match.label}"?`);
555
+ if (shouldDelete) {
556
+ const filePath = path3.join(PROFILES_DIR, match.value);
557
+ if (fs2.existsSync(filePath)) {
558
+ fs2.unlinkSync(filePath);
559
+ console.log(`\x1B[32m\u2713\x1B[0m Deleted: ${match.label}`);
560
+ } else {
561
+ console.log(`\x1B[31mProfile file not found: ${match.value}\x1B[0m`);
562
+ }
563
+ } else {
564
+ console.log("\x1B[33mCancelled\x1B[0m");
213
565
  }
214
566
  process.exit(0);
215
567
  }
216
568
  if (cmd === "edit") {
217
569
  const profiles = loadProfiles();
218
570
  const target = args[1];
219
- const idx = parseInt(target) - 1;
220
- const match = profiles[idx] || profiles.find((p) => p.label.toLowerCase() === target?.toLowerCase());
571
+ if (!target) {
572
+ console.log("\x1B[31mUsage: cm edit <profile>\x1B[0m");
573
+ console.log(" profile: Profile name or number");
574
+ process.exit(1);
575
+ }
576
+ const idx = safeParseInt(target, -1);
577
+ const match = idx > 0 && idx <= profiles.length ? profiles[idx - 1] : profiles.find((p) => p.label.toLowerCase() === target?.toLowerCase());
221
578
  if (match) {
222
579
  const editor = process.env.EDITOR || "nano";
223
- spawnSync(editor, [path.join(PROFILES_DIR, match.value)], { stdio: "inherit" });
580
+ const filePath = path3.join(PROFILES_DIR, match.value);
581
+ if (fs2.existsSync(filePath)) {
582
+ spawnSync(editor, [filePath], { stdio: "inherit" });
583
+ } else {
584
+ console.log(`\x1B[31mProfile file not found: ${match.value}\x1B[0m`);
585
+ }
224
586
  } else {
225
587
  console.log(`\x1B[31mProfile not found: ${target}\x1B[0m`);
226
588
  }
227
589
  process.exit(0);
228
590
  }
229
- const searchMcpServers = (query) => {
230
- try {
231
- const res = execSync(`curl -s "${MCP_REGISTRY_URL}?limit=100"`, { encoding: "utf8", timeout: 1e4 });
232
- const data = JSON.parse(res);
233
- const seen = /* @__PURE__ */ new Set();
234
- return data.servers.filter((s) => {
235
- if (seen.has(s.server.name)) return false;
236
- seen.add(s.server.name);
237
- const isLatest = s._meta?.["io.modelcontextprotocol.registry/official"]?.isLatest !== false;
238
- const matchesQuery = !query || s.server.name.toLowerCase().includes(query.toLowerCase()) || s.server.description?.toLowerCase().includes(query.toLowerCase());
239
- return isLatest && matchesQuery;
240
- }).slice(0, 15);
241
- } catch {
242
- return [];
591
+ if (cmd === "copy") {
592
+ const profiles = loadProfiles();
593
+ const target = args[1];
594
+ const newName = args[2];
595
+ if (!newName) {
596
+ console.log("\x1B[31mUsage: cm copy <source> <new-name>\x1B[0m");
597
+ console.log(" source: Profile name or number");
598
+ console.log(" new-name: Name for the copied profile");
599
+ process.exit(1);
243
600
  }
244
- };
245
- const addMcpToProfile = (server, profileFile) => {
246
- const profilePath = path.join(PROFILES_DIR, profileFile);
247
- const profile = JSON.parse(fs.readFileSync(profilePath, "utf8"));
248
- if (!profile.mcpServers) profile.mcpServers = {};
249
- const s = server.server;
250
- const name = s.name.split("/").pop();
251
- if (s.remotes?.[0]) {
252
- const remote = s.remotes[0];
253
- profile.mcpServers[name] = {
254
- type: remote.type === "streamable-http" ? "http" : remote.type,
255
- url: remote.url
256
- };
257
- } else if (s.packages?.[0]) {
258
- const pkg = s.packages[0];
259
- if (pkg.registryType === "npm") {
260
- profile.mcpServers[name] = {
261
- type: "stdio",
262
- command: "npx",
263
- args: ["-y", pkg.identifier]
264
- };
265
- } else if (pkg.registryType === "pypi") {
266
- profile.mcpServers[name] = {
267
- type: "stdio",
268
- command: "uvx",
269
- args: [pkg.identifier]
270
- };
601
+ const idx = safeParseInt(target, -1);
602
+ const match = idx > 0 && idx <= profiles.length ? profiles[idx - 1] : profiles.find((p) => p.label.toLowerCase() === target?.toLowerCase());
603
+ if (!match) {
604
+ console.log(`\x1B[31mProfile not found: ${target}\x1B[0m`);
605
+ process.exit(1);
606
+ }
607
+ const sourcePath = path3.join(PROFILES_DIR, match.value);
608
+ const profile = JSON.parse(fs2.readFileSync(sourcePath, "utf8"));
609
+ profile.name = newName;
610
+ const newFilename = sanitizeProfileName(newName) + ".json";
611
+ const destPath = path3.join(PROFILES_DIR, newFilename);
612
+ if (fs2.existsSync(destPath)) {
613
+ const shouldOverwrite = await confirm(`Profile "${newName}" already exists. Overwrite?`);
614
+ if (!shouldOverwrite) {
615
+ console.log("\x1B[33mCancelled\x1B[0m");
616
+ process.exit(0);
271
617
  }
272
618
  }
273
- fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
274
- return name;
275
- };
276
- const McpSearch = () => {
619
+ fs2.writeFileSync(destPath, JSON.stringify(profile, null, 2));
620
+ console.log(`\x1B[32m\u2713\x1B[0m Copied "${match.label}" to "${newName}"`);
621
+ process.exit(0);
622
+ }
623
+ var McpSearch = () => {
277
624
  const { exit } = useApp();
278
625
  const [step, setStep] = useState(args[1] ? "loading" : "search");
279
626
  const [query, setQuery] = useState(args[1] || "");
280
- const [servers, setServers] = useState([]);
627
+ const [searchResults, setSearchResults] = useState({ servers: [], total: 0, hasMore: false, offset: 0 });
281
628
  const [selectedServer, setSelectedServer] = useState(null);
282
629
  const profiles = loadProfiles();
283
630
  useEffect(() => {
284
- if (args[1] && step === "loading") {
285
- const results = searchMcpServers(args[1]);
286
- setServers(results);
287
- setStep("results");
288
- }
631
+ const loadInitialResults = async () => {
632
+ if (args[1] && step === "loading") {
633
+ const results = await searchMcpServers(args[1]);
634
+ setSearchResults(results);
635
+ setStep("results");
636
+ }
637
+ };
638
+ loadInitialResults();
289
639
  }, []);
290
- const doSearch = () => {
291
- const results = searchMcpServers(query);
292
- setServers(results);
640
+ const doSearch = async () => {
641
+ setStep("loading");
642
+ const results = await searchMcpServers(query, 0);
643
+ setSearchResults(results);
293
644
  setStep("results");
294
645
  };
295
- const serverItems = servers.map((s) => ({
646
+ const nextPage = async () => {
647
+ const results = await searchMcpServers(query, searchResults.offset + MCP_PAGE_SIZE);
648
+ setSearchResults(results);
649
+ };
650
+ const prevPage = async () => {
651
+ const results = await searchMcpServers(query, Math.max(0, searchResults.offset - MCP_PAGE_SIZE));
652
+ setSearchResults(results);
653
+ };
654
+ const serverItems = searchResults.servers.map((s) => ({
296
655
  label: `${s.server.name} - ${s.server.description?.slice(0, 50) || ""}`,
297
656
  value: s,
298
657
  key: s.server.name + s.server.version
299
658
  }));
300
659
  const profileItems = profiles.map((p) => ({ label: p.label, value: p.value, key: p.key }));
660
+ useInput((input, key) => {
661
+ if (step === "results") {
662
+ if (key.return && !selectedServer) return;
663
+ if ((input === "n" || key.rightArrow) && searchResults.hasMore) {
664
+ nextPage();
665
+ }
666
+ if ((input === "p" || key.leftArrow) && searchResults.offset > 0) {
667
+ prevPage();
668
+ }
669
+ }
670
+ });
301
671
  if (step === "search") {
302
672
  return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "MCP Server Search"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Search: "), /* @__PURE__ */ React.createElement(TextInput, { value: query, onChange: setQuery, onSubmit: doSearch })));
303
673
  }
@@ -305,10 +675,12 @@ const McpSearch = () => {
305
675
  return /* @__PURE__ */ React.createElement(Box, { padding: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Searching MCP registry..."));
306
676
  }
307
677
  if (step === "results") {
308
- if (servers.length === 0) {
678
+ if (searchResults.servers.length === 0) {
309
679
  return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, 'No servers found for "', query, '"'));
310
680
  }
311
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "MCP Servers"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Found ", servers.length, " servers"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React.createElement(
681
+ const start = searchResults.offset + 1;
682
+ const end = Math.min(searchResults.offset + MCP_PAGE_SIZE, searchResults.total);
683
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "MCP Servers"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Showing ", start, "-", end, " of ", searchResults.total, " results"), /* @__PURE__ */ React.createElement(Text, { dimColor: true, color: "gray" }, "Navigation: n/\u2192 next page, p/\u2190 prev page"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React.createElement(
312
684
  SelectInput,
313
685
  {
314
686
  items: serverItems,
@@ -326,9 +698,14 @@ const McpSearch = () => {
326
698
  {
327
699
  items: profileItems,
328
700
  onSelect: (item) => {
329
- const name = addMcpToProfile(selectedServer, item.value);
330
- console.log(`
701
+ try {
702
+ const name = addMcpToProfile(selectedServer, item.value);
703
+ console.log(`
331
704
  \x1B[32m\u2713\x1B[0m Added ${name} to ${item.label}`);
705
+ } catch (error) {
706
+ console.log(`
707
+ \x1B[31m\u2717\x1B[0m ${error.message}`);
708
+ }
332
709
  exit();
333
710
  }
334
711
  }
@@ -336,75 +713,43 @@ const McpSearch = () => {
336
713
  }
337
714
  return null;
338
715
  };
339
- const SKILL_SOURCES = [
340
- { url: "https://api.github.com/repos/anthropics/skills/contents/skills", base: "https://github.com/anthropics/skills/tree/main/skills" },
341
- { url: "https://api.github.com/repos/Prat011/awesome-llm-skills/contents/skills", base: "https://github.com/Prat011/awesome-llm-skills/tree/main/skills" },
342
- { url: "https://api.github.com/repos/skillcreatorai/Ai-Agent-Skills/contents/skills", base: "https://github.com/skillcreatorai/Ai-Agent-Skills/tree/main/skills" }
343
- ];
344
- const fetchSkills = () => {
345
- const seen = /* @__PURE__ */ new Set();
346
- const skills = [];
347
- for (const source of SKILL_SOURCES) {
348
- try {
349
- const res = execSync(`curl -s "${source.url}"`, { encoding: "utf8", timeout: 1e4 });
350
- const data = JSON.parse(res);
351
- if (Array.isArray(data)) {
352
- for (const s of data.filter((s2) => s2.type === "dir")) {
353
- if (!seen.has(s.name)) {
354
- seen.add(s.name);
355
- skills.push({
356
- label: s.name,
357
- value: `${source.base}/${s.name}`,
358
- key: s.name
359
- });
360
- }
361
- }
362
- }
363
- } catch {
364
- }
365
- }
366
- return skills.sort((a, b) => a.label.localeCompare(b.label));
367
- };
368
- const SKILLS_DIR = path.join(os.homedir(), ".claude", "skills");
369
- const addSkillToClaudeJson = (skillName, skillUrl) => {
370
- try {
371
- if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
372
- const skillPath = path.join(SKILLS_DIR, skillName);
373
- if (fs.existsSync(skillPath)) {
374
- return { success: false, message: "Skill already installed" };
375
- }
376
- const match = skillUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)\/(.+)/);
377
- if (!match) return { success: false, message: "Invalid skill URL" };
378
- const [, owner, repo, branch, skillSubPath] = match;
379
- const tempDir = `/tmp/skill-clone-${Date.now()}`;
380
- execSync(`git clone --depth 1 --filter=blob:none --sparse "https://github.com/${owner}/${repo}.git" "${tempDir}" 2>/dev/null`, { timeout: 3e4 });
381
- execSync(`cd "${tempDir}" && git sparse-checkout set "${skillSubPath}" 2>/dev/null`, { timeout: 1e4 });
382
- execSync(`mv "${tempDir}/${skillSubPath}" "${skillPath}"`, { timeout: 5e3 });
383
- execSync(`rm -rf "${tempDir}"`, { timeout: 5e3 });
384
- return { success: true };
385
- } catch (e) {
386
- return { success: false, message: "Failed to download skill" };
387
- }
388
- };
389
- const SkillsBrowser = () => {
716
+ var SkillsBrowser = () => {
390
717
  const { exit } = useApp();
391
- const [skills, setSkills] = useState([]);
718
+ const [allSkills, setAllSkills] = useState([]);
392
719
  const [loading, setLoading] = useState(true);
720
+ const [offset, setOffset] = useState(0);
393
721
  useEffect(() => {
394
- const s = fetchSkills();
395
- setSkills(s);
396
- setLoading(false);
722
+ const loadSkills = async () => {
723
+ const s = await fetchSkills();
724
+ setAllSkills(s);
725
+ setLoading(false);
726
+ };
727
+ loadSkills();
397
728
  }, []);
729
+ const paginatedSkills = allSkills.slice(offset, offset + SKILLS_PAGE_SIZE);
730
+ const hasMore = offset + SKILLS_PAGE_SIZE < allSkills.length;
731
+ useInput((input, key) => {
732
+ if (!loading) {
733
+ if ((input === "n" || key.rightArrow) && hasMore) {
734
+ setOffset(offset + SKILLS_PAGE_SIZE);
735
+ }
736
+ if ((input === "p" || key.leftArrow) && offset > 0) {
737
+ setOffset(Math.max(0, offset - SKILLS_PAGE_SIZE));
738
+ }
739
+ }
740
+ });
398
741
  if (loading) {
399
742
  return /* @__PURE__ */ React.createElement(Box, { padding: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Loading skills..."));
400
743
  }
401
- if (skills.length === 0) {
744
+ if (allSkills.length === 0) {
402
745
  return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Could not fetch skills"));
403
746
  }
404
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "Anthropic Skills"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Found ", skills.length, " skills"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React.createElement(
747
+ const start = offset + 1;
748
+ const end = Math.min(offset + SKILLS_PAGE_SIZE, allSkills.length);
749
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "Anthropic Skills"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Showing ", start, "-", end, " of ", allSkills.length, " skills"), /* @__PURE__ */ React.createElement(Text, { dimColor: true, color: "gray" }, "Navigation: n/\u2192 next page, p/\u2190 prev page"), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React.createElement(
405
750
  SelectInput,
406
751
  {
407
- items: skills,
752
+ items: paginatedSkills,
408
753
  onSelect: (item) => {
409
754
  const result = addSkillToClaudeJson(item.label, item.value);
410
755
  if (result.success) {
@@ -421,8 +766,95 @@ const SkillsBrowser = () => {
421
766
  )));
422
767
  };
423
768
  if (cmd === "skills") {
769
+ const subCommand = args[1];
770
+ if (subCommand === "list") {
771
+ const installed = getInstalledSkills();
772
+ console.log(`\x1B[1m\x1B[36mInstalled Skills\x1B[0m (${installed.length})`);
773
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
774
+ if (installed.length === 0) {
775
+ console.log("No skills installed");
776
+ } else {
777
+ installed.forEach((s, i) => console.log(`${i + 1}. ${s}`));
778
+ }
779
+ process.exit(0);
780
+ }
781
+ if (subCommand === "remove") {
782
+ const target = args[2];
783
+ if (!target) {
784
+ console.log("\x1B[31mUsage: cm skills remove <skill-name>\x1B[0m");
785
+ process.exit(1);
786
+ }
787
+ const installed = getInstalledSkills();
788
+ const idx = safeParseInt(target, -1);
789
+ const match = idx > 0 && idx <= installed.length ? installed[idx - 1] : installed.find((s) => s.toLowerCase() === target?.toLowerCase());
790
+ if (!match) {
791
+ console.log(`\x1B[31mSkill not found: ${target}\x1B[0m`);
792
+ console.log('Run "cm skills list" to see installed skills');
793
+ process.exit(1);
794
+ }
795
+ const shouldRemove = await confirm(`Remove skill "${match}"?`);
796
+ if (shouldRemove) {
797
+ const result = removeSkill(match);
798
+ if (result.success) {
799
+ console.log(`\x1B[32m\u2713\x1B[0m Removed skill: ${match}`);
800
+ } else {
801
+ console.log(`\x1B[31m\u2717\x1B[0m ${result.message}`);
802
+ }
803
+ } else {
804
+ console.log("\x1B[33mCancelled\x1B[0m");
805
+ }
806
+ process.exit(0);
807
+ }
424
808
  render(/* @__PURE__ */ React.createElement(SkillsBrowser, null));
425
809
  } else if (cmd === "mcp") {
810
+ const subCommand = args[1];
811
+ if (subCommand === "remove") {
812
+ const profiles = loadProfiles();
813
+ if (profiles.length === 0) {
814
+ console.log("\x1B[31mNo profiles found\x1B[0m");
815
+ process.exit(1);
816
+ }
817
+ const serverName = args[2];
818
+ const targetProfile = args[3];
819
+ if (!targetProfile) {
820
+ console.log("\x1B[31mUsage: cm mcp remove <server-name> <profile>\x1B[0m");
821
+ console.log(" server-name: MCP server name to remove");
822
+ console.log(" profile: Profile name or number");
823
+ process.exit(1);
824
+ }
825
+ const idx = safeParseInt(targetProfile, -1);
826
+ const profileMatch = idx > 0 && idx <= profiles.length ? profiles[idx - 1] : profiles.find((p) => p.label.toLowerCase() === targetProfile?.toLowerCase());
827
+ if (!profileMatch) {
828
+ console.log(`\x1B[31mProfile not found: ${targetProfile}\x1B[0m`);
829
+ process.exit(1);
830
+ }
831
+ const profilePath = path3.join(PROFILES_DIR, profileMatch.value);
832
+ if (!fs2.existsSync(profilePath)) {
833
+ console.log(`\x1B[31mProfile file not found: ${profileMatch.value}\x1B[0m`);
834
+ process.exit(1);
835
+ }
836
+ const profile = JSON.parse(fs2.readFileSync(profilePath, "utf8"));
837
+ const mcpServers = profile.mcpServers || {};
838
+ if (Object.keys(mcpServers).length === 0) {
839
+ console.log(`\x1B[33mNo MCP servers configured in "${profileMatch.label}"\x1B[0m`);
840
+ process.exit(0);
841
+ }
842
+ if (!mcpServers[serverName]) {
843
+ console.log(`\x1B[31mMCP server not found: ${serverName}\x1B[0m`);
844
+ console.log(`Available servers: ${Object.keys(mcpServers).join(", ")}`);
845
+ process.exit(1);
846
+ }
847
+ const shouldRemove = await confirm(`Remove "${serverName}" from "${profileMatch.label}"?`);
848
+ if (shouldRemove) {
849
+ delete mcpServers[serverName];
850
+ profile.mcpServers = mcpServers;
851
+ fs2.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
852
+ console.log(`\x1B[32m\u2713\x1B[0m Removed "${serverName}" from "${profileMatch.label}"`);
853
+ } else {
854
+ console.log("\x1B[33mCancelled\x1B[0m");
855
+ }
856
+ process.exit(0);
857
+ }
426
858
  render(/* @__PURE__ */ React.createElement(McpSearch, null));
427
859
  } else if (cmd === "new") {
428
860
  const NewProfileWizard = () => {
@@ -433,40 +865,33 @@ if (cmd === "skills") {
433
865
  const [apiKey, setApiKey] = useState("");
434
866
  const [model, setModel] = useState("");
435
867
  const [group, setGroup] = useState("");
436
- const providers = [
437
- { label: "Anthropic (Direct)", value: "anthropic", url: "", needsKey: true },
438
- { label: "Amazon Bedrock", value: "bedrock", url: "", needsKey: false },
439
- { label: "Z.AI", value: "zai", url: "https://api.z.ai/api/anthropic", needsKey: true },
440
- { label: "MiniMax", value: "minimax", url: "https://api.minimax.io/anthropic", needsKey: true },
441
- { label: "Custom", value: "custom", url: "", needsKey: true }
442
- ];
868
+ const [validationErrors, setValidationErrors] = useState([]);
443
869
  const handleSave = () => {
444
- const prov = providers.find((p) => p.value === provider);
445
- const profile = {
446
- name,
447
- group: group || void 0,
448
- env: {
449
- ...apiKey && { ANTHROPIC_AUTH_TOKEN: apiKey },
450
- ...model && { ANTHROPIC_MODEL: model },
451
- ...prov?.url && { ANTHROPIC_BASE_URL: prov.url },
452
- API_TIMEOUT_MS: "3000000"
453
- },
454
- model: "opus",
455
- alwaysThinkingEnabled: true,
456
- defaultMode: "bypassPermissions"
457
- };
458
- const filename = name.toLowerCase().replace(/\s+/g, "-") + ".json";
459
- fs.writeFileSync(path.join(PROFILES_DIR, filename), JSON.stringify(profile, null, 2));
870
+ const profile = buildProfileData(name, provider, apiKey, model, group, PROVIDERS);
871
+ const validation = validateProfile(profile);
872
+ if (!validation.valid) {
873
+ setStep("error");
874
+ setValidationErrors(validation.errors);
875
+ return;
876
+ }
877
+ const filename = sanitizeProfileName(name) + ".json";
878
+ fs2.writeFileSync(path3.join(PROFILES_DIR, filename), JSON.stringify(profile, null, 2));
460
879
  console.log(`
461
880
  \x1B[32m\u2713\x1B[0m Created: ${name}`);
462
881
  exit();
463
882
  };
464
883
  const handleProviderSelect = (item) => {
465
884
  setProvider(item.value);
466
- const prov = providers.find((p) => p.value === item.value);
885
+ const prov = PROVIDERS.find((p) => p.value === item.value);
467
886
  setStep(prov.needsKey ? "apikey" : "model");
468
887
  };
469
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "New Profile"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), step === "name" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Name: "), /* @__PURE__ */ React.createElement(TextInput, { value: name, onChange: setName, onSubmit: () => setStep("provider") })), step === "provider" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Provider:"), /* @__PURE__ */ React.createElement(SelectInput, { items: providers, onSelect: handleProviderSelect })), step === "apikey" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "API Key: "), /* @__PURE__ */ React.createElement(TextInput, { value: apiKey, onChange: setApiKey, onSubmit: () => setStep("model"), mask: "*" })), step === "model" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Model ID (optional): "), /* @__PURE__ */ React.createElement(TextInput, { value: model, onChange: setModel, onSubmit: () => setStep("group") })), step === "group" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Group (optional): "), /* @__PURE__ */ React.createElement(TextInput, { value: group, onChange: setGroup, onSubmit: handleSave })));
888
+ useInput((input, key) => {
889
+ if (step === "error") {
890
+ setStep("group");
891
+ setValidationErrors([]);
892
+ }
893
+ });
894
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "New Profile"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), step === "name" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Name: "), /* @__PURE__ */ React.createElement(TextInput, { value: name, onChange: setName, onSubmit: () => setStep("provider") })), step === "provider" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Provider:"), /* @__PURE__ */ React.createElement(SelectInput, { items: PROVIDERS, onSelect: handleProviderSelect })), step === "apikey" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "API Key: "), /* @__PURE__ */ React.createElement(TextInput, { value: apiKey, onChange: setApiKey, onSubmit: () => setStep("model"), mask: "*" })), step === "model" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Model ID (optional): "), /* @__PURE__ */ React.createElement(TextInput, { value: model, onChange: setModel, onSubmit: () => setStep("group") })), step === "group" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Group (optional): "), /* @__PURE__ */ React.createElement(TextInput, { value: group, onChange: setGroup, onSubmit: handleSave })), step === "error" && /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "red" }, "Validation errors:"), validationErrors.map((err, i) => /* @__PURE__ */ React.createElement(Text, { key: i, color: "yellow" }, " \u2022 ", err)), /* @__PURE__ */ React.createElement(Text, { marginTop: 1 }, "Press any key to go back and fix...")));
470
895
  };
471
896
  render(/* @__PURE__ */ React.createElement(NewProfileWizard, null));
472
897
  } else {
@@ -486,48 +911,111 @@ if (cmd === "skills") {
486
911
  clearInterval(colorInterval);
487
912
  };
488
913
  }, []);
489
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: colors[colorIdx] }, `\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
490
- \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
491
- \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557
492
- \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D
493
- \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
494
- \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D`), /* @__PURE__ */ React.createElement(Text, { bold: true, color: colors[(colorIdx + 3) % colors.length] }, "MANAGER v", VERSION), /* @__PURE__ */ React.createElement(Text, { color: "yellow", marginTop: 1 }, message, dots));
914
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: colors[colorIdx] }, LOGO), /* @__PURE__ */ React.createElement(Text, { bold: true, color: colors[(colorIdx + 3) % colors.length] }, "MANAGER v", VERSION), /* @__PURE__ */ React.createElement(Text, { color: "yellow", marginTop: 1 }, message, dots));
495
915
  };
496
916
  const App = () => {
497
- const [step, setStep] = useState("select");
917
+ const [step, setStep] = useState("loading");
498
918
  const [updateInfo, setUpdateInfo] = useState(null);
499
919
  const [filter, setFilter] = useState("");
920
+ const [showHelp, setShowHelp] = useState(false);
921
+ const [showCommandPalette, setShowCommandPalette] = useState(false);
922
+ const [commandInput, setCommandInput] = useState("");
500
923
  const profiles = loadProfiles();
924
+ const commands = [
925
+ { label: "/skills", description: "Browse and install skills", action: () => render(/* @__PURE__ */ React.createElement(SkillsBrowser, null)) },
926
+ { label: "/mcp", description: "Search and add MCP servers", action: () => render(/* @__PURE__ */ React.createElement(McpSearch, null)) },
927
+ { label: "/new", description: "Create new profile", action: () => setStep("newProfile") },
928
+ { label: "/list", description: "List all profiles", action: () => execSync2("cm list", { stdio: "inherit" }) },
929
+ { label: "/status", description: "Show current settings", action: () => execSync2("cm status", { stdio: "inherit" }) },
930
+ { label: "/config", description: "Edit Claude settings", action: () => execSync2("cm config", { stdio: "inherit" }) },
931
+ { label: "/help", description: "Show keyboard shortcuts", action: () => setShowHelp(true) },
932
+ { label: "/quit", description: "Exit cm", action: () => process.exit(0) }
933
+ ];
934
+ const filteredProfiles = useMemo(() => {
935
+ if (!filter) return profiles;
936
+ const fuse = new Fuse(profiles, {
937
+ keys: ["label", "group"],
938
+ threshold: FUSE_THRESHOLD,
939
+ ignoreLocation: true,
940
+ includeScore: true
941
+ });
942
+ return fuse.search(filter).map((r) => r.item);
943
+ }, [profiles, filter]);
944
+ const filteredCommands = useMemo(() => {
945
+ if (!commandInput) return commands;
946
+ const search = commandInput.toLowerCase().replace(/^\//, "");
947
+ const fuse = new Fuse(commands, {
948
+ keys: ["label", "description"],
949
+ threshold: FUSE_THRESHOLD,
950
+ ignoreLocation: true
951
+ });
952
+ return fuse.search(search).map((r) => r.item);
953
+ }, [commands, commandInput]);
501
954
  useEffect(() => {
502
955
  setTimeout(() => setStep("select"), 1500);
503
956
  if (!skipUpdate) {
504
- Promise.resolve().then(() => {
505
- const info = checkForUpdate();
506
- setUpdateInfo(info);
507
- });
957
+ checkForUpdate(skipUpdate).then(setUpdateInfo);
508
958
  }
509
959
  }, []);
510
960
  useInput((input, key) => {
961
+ if (showCommandPalette) {
962
+ if (key.escape) {
963
+ setShowCommandPalette(false);
964
+ setCommandInput("");
965
+ return;
966
+ }
967
+ if (key.return) {
968
+ const matchedCommand = commandInput.startsWith("/") ? commands.find((c) => c.label === commandInput) : filteredCommands[0];
969
+ if (matchedCommand) {
970
+ setShowCommandPalette(false);
971
+ setCommandInput("");
972
+ matchedCommand.action();
973
+ }
974
+ return;
975
+ }
976
+ if (key.backspace || key.delete) {
977
+ setCommandInput((c) => c.slice(0, -1));
978
+ if (commandInput.length <= 1) {
979
+ setShowCommandPalette(false);
980
+ }
981
+ return;
982
+ }
983
+ if (input && !key.ctrl && !key.meta) {
984
+ setCommandInput((c) => c + input);
985
+ }
986
+ return;
987
+ }
511
988
  if (step === "select") {
512
- const num = parseInt(input);
989
+ const num = safeParseInt(input, -1);
513
990
  if (num >= 1 && num <= 9 && num <= filteredProfiles.length) {
514
991
  const profile = filteredProfiles[num - 1];
515
992
  applyProfile(profile.value);
516
993
  console.log(`
517
994
  \x1B[32m\u2713\x1B[0m Applied: ${profile.label}
518
995
  `);
519
- launchClaude();
996
+ launchClaude(dangerMode);
520
997
  }
521
998
  if (input === "u" && updateInfo?.needsUpdate) {
522
999
  console.log("\n\x1B[33mUpdating Claude...\x1B[0m\n");
523
1000
  try {
524
- execSync("brew upgrade claude-code", { stdio: "inherit" });
525
- console.log("\n\x1B[32m\u2713 Updated!\x1B[0m\n");
1001
+ if (process.platform === "darwin") {
1002
+ execSync2("brew upgrade claude-code", { stdio: "inherit" });
1003
+ } else {
1004
+ execSync2("npm update -g @anthropic-ai/claude-code", { stdio: "inherit" });
1005
+ }
1006
+ console.log("\x1B[32m\u2713 Updated!\x1B[0m\n");
526
1007
  setUpdateInfo({ ...updateInfo, needsUpdate: false });
527
- } catch {
1008
+ } catch (error) {
1009
+ console.log("\x1B[31m\u2717 Update failed\x1B[0m\n");
1010
+ logError("update", error);
528
1011
  }
529
1012
  }
530
- if (input.match(/^[a-zA-Z]$/) && input !== "u") {
1013
+ if (input === "/" && !showHelp) {
1014
+ setShowCommandPalette(true);
1015
+ setCommandInput("/");
1016
+ return;
1017
+ }
1018
+ if (input.match(/^[a-zA-Z]$/) && input !== "u" && input !== "c" && input !== "?" && input !== "/") {
531
1019
  setFilter((f) => f + input);
532
1020
  }
533
1021
  if (key.backspace || key.delete) {
@@ -536,11 +1024,21 @@ if (cmd === "skills") {
536
1024
  if (key.escape) {
537
1025
  setFilter("");
538
1026
  }
1027
+ if (input === "?") {
1028
+ setShowHelp(true);
1029
+ }
1030
+ if (input === "c") {
1031
+ const editor = process.env.EDITOR || "nano";
1032
+ createDefaultSettings();
1033
+ console.clear();
1034
+ spawnSync(editor, [SETTINGS_PATH], { stdio: "inherit" });
1035
+ console.log("\n\x1B[36mConfig edited. Press Enter to continue...\x1B[0m");
1036
+ }
1037
+ }
1038
+ if (showHelp && (input === "q" || input === "?" || key.escape || key.return)) {
1039
+ setShowHelp(false);
539
1040
  }
540
1041
  });
541
- const filteredProfiles = profiles.filter(
542
- (p) => !filter || p.label.toLowerCase().includes(filter.toLowerCase())
543
- );
544
1042
  const groupedItems = [];
545
1043
  const groups = [...new Set(filteredProfiles.map((p) => p.group).filter(Boolean))];
546
1044
  if (groups.length > 0) {
@@ -570,21 +1068,16 @@ if (cmd === "skills") {
570
1068
  console.log(`
571
1069
  \x1B[32m\u2713\x1B[0m Applied: ${item.label.replace(/^\d+\.\s*/, "")}
572
1070
  `);
573
- launchClaude();
1071
+ launchClaude(dangerMode);
574
1072
  };
575
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, `\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
576
- \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
577
- \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557
578
- \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D
579
- \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
580
- \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D`), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta" }, "MANAGER v", VERSION), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), updateInfo?.current && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Claude v", updateInfo.current), updateInfo?.needsUpdate && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "\u26A0 Update available! Press 'u' to upgrade"), filter && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Filter: ", filter), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Select Profile: ", /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "(1-9 quick select, type to filter", updateInfo?.needsUpdate ? ", u to update" : "", ")")), /* @__PURE__ */ React.createElement(
1073
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, LOGO), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta" }, "MANAGER v", VERSION), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), updateInfo?.current && /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Claude v", updateInfo.current), updateInfo?.needsUpdate && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Update available! Press 'u' to upgrade"), filter && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Filter: ", filter), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Select Profile: ", /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "(1-9 select, / commands, ? help, c config", updateInfo?.needsUpdate ? ", u update" : "", ")")), /* @__PURE__ */ React.createElement(
581
1074
  SelectInput,
582
1075
  {
583
1076
  items: groupedItems,
584
1077
  onSelect: handleSelect,
585
1078
  itemComponent: ({ isSelected, label, disabled }) => /* @__PURE__ */ React.createElement(Text, { color: disabled ? "gray" : isSelected ? "cyan" : "white", dimColor: disabled }, disabled ? label : (isSelected ? "\u276F " : " ") + label)
586
1079
  }
587
- )));
1080
+ )), showCommandPalette && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1, marginTop: 1, borderStyle: "double", borderColor: "magenta" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta" }, "Command Palette"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, ">"), /* @__PURE__ */ React.createElement(Text, { color: "white" }, commandInput)), /* @__PURE__ */ React.createElement(Text, { dimColor: true, marginTop: 1 }, "Available commands:"), filteredCommands.map((cmd2, i) => /* @__PURE__ */ React.createElement(Text, { key: cmd2.label }, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, cmd2.label), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " - "), /* @__PURE__ */ React.createElement(Text, { color: "gray" }, cmd2.description))), /* @__PURE__ */ React.createElement(Text, { dimColor: true, marginTop: 1 }, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Enter to execute \u2022 Esc to close")), showHelp && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", padding: 1, marginTop: 1, borderStyle: "single", borderColor: "cyan" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "Keyboard Shortcuts"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta" }, "Navigation"), /* @__PURE__ */ React.createElement(Text, null, " ", /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "1-9"), " Quick select profile"), /* @__PURE__ */ React.createElement(Text, null, " ", /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "\u2191/\u2193"), " Navigate list"), /* @__PURE__ */ React.createElement(Text, null, " ", /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Enter"), " Select profile"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta", marginTop: 1 }, "Search"), /* @__PURE__ */ React.createElement(Text, null, " ", /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "a-z"), " Fuzzy filter profiles"), /* @__PURE__ */ React.createElement(Text, null, " ", /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Backspace"), " Delete filter character"), /* @__PURE__ */ React.createElement(Text, null, " ", /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "Escape"), " Clear filter"), updateInfo?.needsUpdate && /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta", marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "u"), " Update Claude"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta", marginTop: 1 }, "Help"), /* @__PURE__ */ React.createElement(Text, null, " ", /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "?"), " Toggle this help"), /* @__PURE__ */ React.createElement(Text, null, " ", /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "q"), " Close help"), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "magenta", marginTop: 1 }, "CLI Commands"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " cm new Create new profile"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " cm config Edit Claude settings"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " cm status Show current settings"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " cm --help Show all commands")));
588
1081
  };
589
1082
  render(/* @__PURE__ */ React.createElement(App, null));
590
1083
  }