claude-manager 1.5.3 → 1.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -9
- package/dist/cli.js +438 -331
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A powerful terminal app for managing Claude Code settings, profiles, MCP servers, and skills. Switch between different AI providers, models, and configurations with a single command.
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
## Features
|
|
@@ -19,12 +19,27 @@ A powerful terminal app for managing Claude Code settings, profiles, MCP servers
|
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
22
|
+
### Option 1: Homebrew (macOS)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
brew tap faisalnazir/claude-manager
|
|
26
|
+
brew install claude-manager
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Option 2: npm
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g claude-manager
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Option 3: curl
|
|
36
|
+
|
|
22
37
|
```bash
|
|
23
38
|
curl -fsSL https://raw.githubusercontent.com/faisalnazir/claude-manager/main/install.sh | bash
|
|
24
39
|
```
|
|
25
40
|
|
|
26
41
|
### Requirements
|
|
27
|
-
- [
|
|
42
|
+
- [Node.js 18+](https://nodejs.org) (for npm install)
|
|
28
43
|
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
|
29
44
|
|
|
30
45
|
## Quick Start
|
|
@@ -129,13 +144,15 @@ Profiles are stored in `~/.claude/profiles/*.json`
|
|
|
129
144
|
|
|
130
145
|
Pre-configured in `cm new`:
|
|
131
146
|
|
|
132
|
-
| Provider | Base URL |
|
|
133
|
-
|
|
134
|
-
| Anthropic (Direct) | Default |
|
|
135
|
-
| Amazon Bedrock | Default |
|
|
136
|
-
| Z.AI | `https://api.z.ai/api/anthropic` |
|
|
137
|
-
| MiniMax | `https://api.minimax.io/anthropic` |
|
|
138
|
-
| Custom | Your URL |
|
|
147
|
+
| Provider | Base URL | Notes |
|
|
148
|
+
|----------|----------|-------|
|
|
149
|
+
| Anthropic (Direct) | Default | Standard `sk-ant-` keys |
|
|
150
|
+
| Amazon Bedrock | Default | No API key needed |
|
|
151
|
+
| Z.AI | `https://api.z.ai/api/anthropic` | Standard `sk-ant-` keys |
|
|
152
|
+
| MiniMax | `https://api.minimax.io/anthropic` | Supports both `sk-ant-` and `sk-cp-` (coding plan) keys |
|
|
153
|
+
| Custom | Your URL | Depends on provider |
|
|
154
|
+
|
|
155
|
+
**MiniMax Coding Plan Keys**: If you have a MiniMax coding plan subscription, get your `sk-cp-` key from the [Account/Coding Plan](https://platform.minimax.io/user-center/payment/coding-plan) page. Regular platform keys (`sk-ant-`) are available from the [API Keys](https://platform.minimax.io/user-center/basic-information/interface-key) page.
|
|
139
156
|
|
|
140
157
|
## Per-Project Profiles
|
|
141
158
|
|
|
@@ -171,6 +188,9 @@ cm edit 1
|
|
|
171
188
|
|
|
172
189
|
# Check what's installed
|
|
173
190
|
cm status
|
|
191
|
+
|
|
192
|
+
# Update cm via npm
|
|
193
|
+
npm update -g claude-manager
|
|
174
194
|
```
|
|
175
195
|
|
|
176
196
|
## How It Works
|
package/dist/cli.js
CHANGED
|
@@ -1,88 +1,119 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// src/cli.js
|
|
2
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
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { execSync, spawnSync } from "child_process";
|
|
10
|
-
import { createInterface } from "readline";
|
|
7
|
+
import { spawnSync, execSync as execSync2 } from "child_process";
|
|
8
|
+
import fs2 from "fs";
|
|
9
|
+
import path3 from "path";
|
|
11
10
|
import Fuse from "fuse.js";
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
|
|
12
|
+
// src/constants.js
|
|
13
|
+
import os from "os";
|
|
14
|
+
import path from "path";
|
|
15
|
+
var VERSION = "1.5.5";
|
|
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
|
|
14
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
|
|
15
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
|
|
16
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
|
|
17
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
|
|
18
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`;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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;
|
|
50
56
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 = () => {
|
|
64
88
|
const profiles = [];
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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);
|
|
80
108
|
}
|
|
81
109
|
}
|
|
82
110
|
return profiles;
|
|
83
111
|
};
|
|
84
|
-
|
|
85
|
-
const profilePath =
|
|
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
|
+
}
|
|
86
117
|
const profile = JSON.parse(fs.readFileSync(profilePath, "utf8"));
|
|
87
118
|
const { name, group, mcpServers, ...settings } = profile;
|
|
88
119
|
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
@@ -91,32 +122,33 @@ const applyProfile = (filename) => {
|
|
|
91
122
|
const claudeJson = fs.existsSync(CLAUDE_JSON_PATH) ? JSON.parse(fs.readFileSync(CLAUDE_JSON_PATH, "utf8")) : {};
|
|
92
123
|
claudeJson.mcpServers = mcpServers;
|
|
93
124
|
fs.writeFileSync(CLAUDE_JSON_PATH, JSON.stringify(claudeJson, null, 2));
|
|
94
|
-
} catch {
|
|
125
|
+
} catch (error) {
|
|
126
|
+
logError("applyProfile-mcp", error);
|
|
95
127
|
}
|
|
96
128
|
}
|
|
97
129
|
fs.writeFileSync(LAST_PROFILE_PATH, filename);
|
|
98
130
|
return name || filename;
|
|
99
131
|
};
|
|
100
|
-
|
|
132
|
+
var getLastProfile = () => {
|
|
101
133
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
134
|
+
const content = fs.readFileSync(LAST_PROFILE_PATH, "utf8");
|
|
135
|
+
return content.trim() || null;
|
|
136
|
+
} catch (error) {
|
|
104
137
|
return null;
|
|
105
138
|
}
|
|
106
139
|
};
|
|
107
|
-
|
|
108
|
-
const localProfile =
|
|
140
|
+
var checkProjectProfile = () => {
|
|
141
|
+
const localProfile = path2.join(process.cwd(), ".claude-profile");
|
|
109
142
|
if (fs.existsSync(localProfile)) {
|
|
110
|
-
|
|
143
|
+
try {
|
|
144
|
+
return fs.readFileSync(localProfile, "utf8").trim();
|
|
145
|
+
} catch (error) {
|
|
146
|
+
logError("checkProjectProfile", error);
|
|
147
|
+
}
|
|
111
148
|
}
|
|
112
149
|
return null;
|
|
113
150
|
};
|
|
114
|
-
|
|
115
|
-
if (process.env.DEBUG || process.env.CM_DEBUG) {
|
|
116
|
-
console.error(`[${context}]`, error?.message || error);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
const confirm = async (message) => {
|
|
151
|
+
var confirm = async (message) => {
|
|
120
152
|
const rl = createInterface({
|
|
121
153
|
input: process.stdin,
|
|
122
154
|
output: process.stdout
|
|
@@ -124,19 +156,28 @@ const confirm = async (message) => {
|
|
|
124
156
|
return new Promise((resolve) => {
|
|
125
157
|
rl.question(`${message} (y/N): `, (answer) => {
|
|
126
158
|
rl.close();
|
|
127
|
-
|
|
159
|
+
const normalized = answer.toLowerCase().trim();
|
|
160
|
+
resolve(normalized === "y" || normalized === "yes");
|
|
128
161
|
});
|
|
129
162
|
});
|
|
130
163
|
};
|
|
131
|
-
|
|
164
|
+
var validateProfile = (profile) => {
|
|
132
165
|
const errors = [];
|
|
133
166
|
if (!profile.name || profile.name.trim().length === 0) {
|
|
134
167
|
errors.push("Profile name is required");
|
|
135
168
|
}
|
|
136
169
|
if (profile.env?.ANTHROPIC_AUTH_TOKEN) {
|
|
137
170
|
const key = profile.env.ANTHROPIC_AUTH_TOKEN;
|
|
138
|
-
|
|
139
|
-
|
|
171
|
+
const baseUrl = profile.env?.ANTHROPIC_BASE_URL || "";
|
|
172
|
+
const isMiniMax = baseUrl.includes("minimax.io") || baseUrl.includes("minimaxi.com");
|
|
173
|
+
if (isMiniMax) {
|
|
174
|
+
if (!key.startsWith("sk-ant-") && !key.startsWith("sk-cp-")) {
|
|
175
|
+
errors.push('MiniMax API key should start with "sk-ant-" or "sk-cp-" (for coding plans)');
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
if (!key.startsWith("sk-ant-")) {
|
|
179
|
+
errors.push('API key should start with "sk-ant-"');
|
|
180
|
+
}
|
|
140
181
|
}
|
|
141
182
|
if (key.length < 20) {
|
|
142
183
|
errors.push("API key appears too short");
|
|
@@ -148,6 +189,7 @@ const validateProfile = (profile) => {
|
|
|
148
189
|
/^claude-\d+(\.\d+)?(-\d+)?$/,
|
|
149
190
|
/^glm-/,
|
|
150
191
|
/^minimax-/,
|
|
192
|
+
/^MiniMax-M\d+(\.\d+)?$/,
|
|
151
193
|
/^anthropic\.claude-/
|
|
152
194
|
];
|
|
153
195
|
if (!validPatterns.some((p) => p.test(model))) {
|
|
@@ -163,28 +205,41 @@ const validateProfile = (profile) => {
|
|
|
163
205
|
}
|
|
164
206
|
return { valid: errors.length === 0, errors };
|
|
165
207
|
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
208
|
+
var getInstalledSkills = () => {
|
|
209
|
+
if (!fs.existsSync(SKILLS_DIR)) return [];
|
|
210
|
+
try {
|
|
211
|
+
return fs.readdirSync(SKILLS_DIR).filter((f) => {
|
|
212
|
+
const p = path2.join(SKILLS_DIR, f);
|
|
213
|
+
try {
|
|
214
|
+
return fs.statSync(p).isDirectory() && !f.startsWith(".");
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
} catch (error) {
|
|
220
|
+
logError("getInstalledSkills", error);
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
173
223
|
};
|
|
174
|
-
|
|
175
|
-
const skillPath =
|
|
224
|
+
var removeSkill = (skillName) => {
|
|
225
|
+
const skillPath = path2.join(SKILLS_DIR, skillName);
|
|
176
226
|
if (!fs.existsSync(skillPath)) {
|
|
177
227
|
return { success: false, message: "Skill not found" };
|
|
178
228
|
}
|
|
179
|
-
|
|
180
|
-
|
|
229
|
+
try {
|
|
230
|
+
fs.rmSync(skillPath, { recursive: true, force: true });
|
|
231
|
+
return { success: true };
|
|
232
|
+
} catch (error) {
|
|
233
|
+
logError("removeSkill", error);
|
|
234
|
+
return { success: false, message: "Failed to remove skill" };
|
|
235
|
+
}
|
|
181
236
|
};
|
|
182
|
-
|
|
183
|
-
if (
|
|
237
|
+
var checkForUpdate = async (skipUpdate2) => {
|
|
238
|
+
if (skipUpdate2) return { needsUpdate: false };
|
|
239
|
+
const { exec } = await import("child_process");
|
|
240
|
+
const { promisify } = await import("util");
|
|
241
|
+
const execAsync = promisify(exec);
|
|
184
242
|
try {
|
|
185
|
-
const { exec } = await import("child_process");
|
|
186
|
-
const { promisify } = await import("util");
|
|
187
|
-
const execAsync = promisify(exec);
|
|
188
243
|
const versionResult = await execAsync("claude --version 2>/dev/null").catch(() => ({ stdout: "" }));
|
|
189
244
|
const current = versionResult.stdout.match(/(\d+\.\d+\.\d+)/)?.[1];
|
|
190
245
|
if (!current) return { needsUpdate: false };
|
|
@@ -197,7 +252,7 @@ const checkForUpdate = async () => {
|
|
|
197
252
|
const npmListResult = await execAsync("npm list -g @anthropic-ai/claude-code 2>/dev/null").catch(() => ({ stdout: "" }));
|
|
198
253
|
if (npmListResult.stdout.includes("@anthropic-ai/claude-code")) {
|
|
199
254
|
try {
|
|
200
|
-
const npmOutdated = await execAsync("npm outdated -g @anthropic-ai/claude-code --json 2>/dev/null || true", { timeout:
|
|
255
|
+
const npmOutdated = await execAsync("npm outdated -g @anthropic-ai/claude-code --json 2>/dev/null || true", { timeout: NPM_OUTDATED_TIMEOUT });
|
|
201
256
|
needsUpdate = npmOutdated.stdout.length > 0;
|
|
202
257
|
} catch {
|
|
203
258
|
needsUpdate = true;
|
|
@@ -210,35 +265,222 @@ const checkForUpdate = async () => {
|
|
|
210
265
|
return { needsUpdate: false };
|
|
211
266
|
}
|
|
212
267
|
};
|
|
213
|
-
|
|
268
|
+
var launchClaude = (dangerMode2) => {
|
|
214
269
|
try {
|
|
215
|
-
const claudeArgs =
|
|
270
|
+
const claudeArgs = dangerMode2 ? "--dangerously-skip-permissions" : "";
|
|
216
271
|
execSync(`claude ${claudeArgs}`, { stdio: "inherit" });
|
|
217
272
|
} catch (e) {
|
|
218
273
|
process.exit(e.status || 1);
|
|
219
274
|
}
|
|
220
275
|
process.exit(0);
|
|
221
276
|
};
|
|
277
|
+
var searchMcpServers = async (query, offset = 0) => {
|
|
278
|
+
const controller = new AbortController();
|
|
279
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
|
280
|
+
try {
|
|
281
|
+
const res = await fetch(`${MCP_REGISTRY_URL}?limit=200`, { signal: controller.signal });
|
|
282
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
283
|
+
const data = await res.json();
|
|
284
|
+
const seen = /* @__PURE__ */ new Set();
|
|
285
|
+
const filtered = data.servers.filter((s) => {
|
|
286
|
+
if (seen.has(s.server.name)) return false;
|
|
287
|
+
seen.add(s.server.name);
|
|
288
|
+
const isLatest = s._meta?.["io.modelcontextprotocol.registry/official"]?.isLatest !== false;
|
|
289
|
+
const matchesQuery = !query || s.server.name.toLowerCase().includes(query.toLowerCase()) || s.server.description?.toLowerCase().includes(query.toLowerCase());
|
|
290
|
+
return isLatest && matchesQuery;
|
|
291
|
+
});
|
|
292
|
+
const MCP_PAGE_SIZE2 = 50;
|
|
293
|
+
return {
|
|
294
|
+
servers: filtered.slice(offset, offset + MCP_PAGE_SIZE2),
|
|
295
|
+
total: filtered.length,
|
|
296
|
+
hasMore: offset + MCP_PAGE_SIZE2 < filtered.length,
|
|
297
|
+
offset
|
|
298
|
+
};
|
|
299
|
+
} catch (error) {
|
|
300
|
+
logError("searchMcpServers", error);
|
|
301
|
+
return { servers: [], total: 0, hasMore: false, offset: 0 };
|
|
302
|
+
} finally {
|
|
303
|
+
clearTimeout(timeout);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
var addMcpToProfile = (server, profileFile) => {
|
|
307
|
+
const sanitizedFile = sanitizeFilePath(profileFile, PROFILES_DIR);
|
|
308
|
+
if (!sanitizedFile) {
|
|
309
|
+
throw new Error("Invalid profile file");
|
|
310
|
+
}
|
|
311
|
+
const profilePath = path2.join(PROFILES_DIR, sanitizedFile);
|
|
312
|
+
const profile = JSON.parse(fs.readFileSync(profilePath, "utf8"));
|
|
313
|
+
if (!profile.mcpServers) profile.mcpServers = {};
|
|
314
|
+
const s = server.server;
|
|
315
|
+
const name = s.name.split("/").pop();
|
|
316
|
+
if (s.remotes?.[0]) {
|
|
317
|
+
const remote = s.remotes[0];
|
|
318
|
+
profile.mcpServers[name] = {
|
|
319
|
+
type: remote.type === "streamable-http" ? "http" : remote.type,
|
|
320
|
+
url: remote.url
|
|
321
|
+
};
|
|
322
|
+
} else if (s.packages?.[0]) {
|
|
323
|
+
const pkg = s.packages[0];
|
|
324
|
+
if (pkg.registryType === "npm") {
|
|
325
|
+
profile.mcpServers[name] = {
|
|
326
|
+
type: "stdio",
|
|
327
|
+
command: "npx",
|
|
328
|
+
args: ["-y", pkg.identifier]
|
|
329
|
+
};
|
|
330
|
+
} else if (pkg.registryType === "pypi") {
|
|
331
|
+
profile.mcpServers[name] = {
|
|
332
|
+
type: "stdio",
|
|
333
|
+
command: "uvx",
|
|
334
|
+
args: [pkg.identifier]
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
|
|
339
|
+
return name;
|
|
340
|
+
};
|
|
341
|
+
var fetchSkills = async () => {
|
|
342
|
+
const seen = /* @__PURE__ */ new Set();
|
|
343
|
+
const skills = [];
|
|
344
|
+
const promises = SKILL_SOURCES.map(async (source) => {
|
|
345
|
+
const controller = new AbortController();
|
|
346
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
|
347
|
+
try {
|
|
348
|
+
const res = await fetch(source.url, {
|
|
349
|
+
signal: controller.signal,
|
|
350
|
+
headers: { "Accept": "application/vnd.github.v3+json" }
|
|
351
|
+
});
|
|
352
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
353
|
+
const data = await res.json();
|
|
354
|
+
if (Array.isArray(data)) {
|
|
355
|
+
for (const s of data.filter((s2) => s2.type === "dir")) {
|
|
356
|
+
if (!seen.has(s.name)) {
|
|
357
|
+
seen.add(s.name);
|
|
358
|
+
skills.push({
|
|
359
|
+
label: s.name,
|
|
360
|
+
value: `${source.base}/${s.name}`,
|
|
361
|
+
key: s.name
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
} catch (error) {
|
|
367
|
+
logError(`fetchSkills(${source.url})`, error);
|
|
368
|
+
} finally {
|
|
369
|
+
clearTimeout(timeout);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
await Promise.all(promises);
|
|
373
|
+
return skills.sort((a, b) => a.label.localeCompare(b.label));
|
|
374
|
+
};
|
|
375
|
+
var addSkillToClaudeJson = (skillName, skillUrl) => {
|
|
376
|
+
try {
|
|
377
|
+
if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
378
|
+
const skillPath = path2.join(SKILLS_DIR, skillName);
|
|
379
|
+
if (fs.existsSync(skillPath)) {
|
|
380
|
+
return { success: false, message: "Skill already installed" };
|
|
381
|
+
}
|
|
382
|
+
const match = skillUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)\/(.+)/);
|
|
383
|
+
if (!match) return { success: false, message: "Invalid skill URL" };
|
|
384
|
+
const [, owner, repo, branch, skillSubPath] = match;
|
|
385
|
+
const tempDir = `/tmp/skill-clone-${Date.now()}`;
|
|
386
|
+
const sanitizedTempDir = sanitizeFilePath(`skill-clone-${Date.now()}`, "/tmp");
|
|
387
|
+
const finalTempDir = path2.join("/tmp", sanitizedTempDir || "skill-clone");
|
|
388
|
+
execSync(`git clone --depth 1 --filter=blob:none --sparse "https://github.com/${owner}/${repo}.git" "${finalTempDir}" 2>/dev/null`, { timeout: GIT_CLONE_TIMEOUT });
|
|
389
|
+
execSync(`cd "${finalTempDir}" && git sparse-checkout set "${skillSubPath}" 2>/dev/null`, { timeout: GIT_SPARSE_TIMEOUT });
|
|
390
|
+
const sourcePath = path2.join(finalTempDir, skillSubPath);
|
|
391
|
+
if (fs.existsSync(sourcePath)) {
|
|
392
|
+
execSync(`mv "${sourcePath}" "${skillPath}"`, { timeout: GIT_MOVE_TIMEOUT });
|
|
393
|
+
}
|
|
394
|
+
execSync(`rm -rf "${finalTempDir}"`, { timeout: GIT_CLEANUP_TIMEOUT });
|
|
395
|
+
return { success: true };
|
|
396
|
+
} catch (e) {
|
|
397
|
+
logError("addSkillToClaudeJson", e);
|
|
398
|
+
return { success: false, message: "Failed to download skill" };
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
var createDefaultSettings = () => {
|
|
402
|
+
if (!fs.existsSync(SETTINGS_PATH)) {
|
|
403
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(DEFAULT_SETTINGS, null, 2));
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
var buildProfileData = (name, provider, apiKey, model, group, providers) => {
|
|
407
|
+
const prov = providers.find((p) => p.value === provider);
|
|
408
|
+
return {
|
|
409
|
+
name,
|
|
410
|
+
group: group || void 0,
|
|
411
|
+
env: {
|
|
412
|
+
...apiKey && { ANTHROPIC_AUTH_TOKEN: apiKey },
|
|
413
|
+
...model && { ANTHROPIC_MODEL: model },
|
|
414
|
+
...prov?.url && { ANTHROPIC_BASE_URL: prov.url },
|
|
415
|
+
API_TIMEOUT_MS
|
|
416
|
+
},
|
|
417
|
+
model: "opus",
|
|
418
|
+
alwaysThinkingEnabled: true,
|
|
419
|
+
defaultMode: "bypassPermissions"
|
|
420
|
+
};
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// src/cli.js
|
|
424
|
+
ensureProfilesDir();
|
|
425
|
+
var args = process.argv.slice(2);
|
|
426
|
+
var cmd = args[0];
|
|
427
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
428
|
+
console.log(`cm v${VERSION}`);
|
|
429
|
+
process.exit(0);
|
|
430
|
+
}
|
|
431
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
432
|
+
console.log(`cm v${VERSION} - Claude Settings Manager
|
|
433
|
+
|
|
434
|
+
Usage: cm [command] [options]
|
|
435
|
+
|
|
436
|
+
Commands:
|
|
437
|
+
(none) Select profile interactively
|
|
438
|
+
new Create a new profile
|
|
439
|
+
edit <n> Edit profile (by name or number)
|
|
440
|
+
copy <n> <new> Copy/duplicate a profile
|
|
441
|
+
delete <n> Delete profile (by name or number)
|
|
442
|
+
status Show current settings
|
|
443
|
+
list List all profiles
|
|
444
|
+
config Open Claude settings.json in editor
|
|
445
|
+
mcp [query] Search and add MCP servers
|
|
446
|
+
mcp remove Remove MCP server from profile
|
|
447
|
+
skills Browse and add Anthropic skills
|
|
448
|
+
skills list List installed skills
|
|
449
|
+
skills remove Remove an installed skill
|
|
450
|
+
|
|
451
|
+
Options:
|
|
452
|
+
--last, -l Use last profile without menu
|
|
453
|
+
--skip-update Skip update check
|
|
454
|
+
--yolo Run claude with --dangerously-skip-permissions
|
|
455
|
+
--force, -f Skip confirmation prompts (e.g., for delete)
|
|
456
|
+
-v, --version Show version
|
|
457
|
+
-h, --help Show help`);
|
|
458
|
+
process.exit(0);
|
|
459
|
+
}
|
|
460
|
+
var skipUpdate = args.includes("--skip-update");
|
|
461
|
+
var useLast = args.includes("--last") || args.includes("-l");
|
|
462
|
+
var dangerMode = args.includes("--dangerously-skip-permissions") || args.includes("--yolo");
|
|
222
463
|
if (useLast) {
|
|
223
464
|
const last = getLastProfile();
|
|
224
|
-
|
|
465
|
+
const lastPath = last ? path3.join(PROFILES_DIR, last) : null;
|
|
466
|
+
if (last && lastPath && fs2.existsSync(lastPath)) {
|
|
225
467
|
const name = applyProfile(last);
|
|
226
468
|
console.log(`\x1B[32m\u2713\x1B[0m Applied: ${name}
|
|
227
469
|
`);
|
|
228
|
-
launchClaude();
|
|
470
|
+
launchClaude(dangerMode);
|
|
229
471
|
} else {
|
|
230
472
|
console.log("\x1B[31mNo last profile found\x1B[0m");
|
|
231
473
|
process.exit(1);
|
|
232
474
|
}
|
|
233
475
|
}
|
|
234
|
-
|
|
476
|
+
var projectProfile = checkProjectProfile();
|
|
235
477
|
if (projectProfile && !cmd) {
|
|
236
478
|
const profiles = loadProfiles();
|
|
237
479
|
const match = profiles.find((p) => p.label === projectProfile || p.value === projectProfile + ".json");
|
|
238
480
|
if (match) {
|
|
239
481
|
console.log(`\x1B[36mUsing project profile: ${match.label}\x1B[0m`);
|
|
240
482
|
applyProfile(match.value);
|
|
241
|
-
launchClaude();
|
|
483
|
+
launchClaude(dangerMode);
|
|
242
484
|
}
|
|
243
485
|
}
|
|
244
486
|
if (cmd === "status") {
|
|
@@ -260,36 +502,29 @@ Profile MCP Servers (${Object.keys(mcpServers).length}):`);
|
|
|
260
502
|
} else {
|
|
261
503
|
console.log("No profile active");
|
|
262
504
|
}
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const installedSkills = fs.readdirSync(skillsDir).filter((f) => {
|
|
267
|
-
const p = path.join(skillsDir, f);
|
|
268
|
-
return fs.statSync(p).isDirectory() && !f.startsWith(".");
|
|
269
|
-
});
|
|
270
|
-
if (installedSkills.length > 0) {
|
|
271
|
-
console.log(`
|
|
505
|
+
const installedSkills = getInstalledSkills();
|
|
506
|
+
if (installedSkills.length > 0) {
|
|
507
|
+
console.log(`
|
|
272
508
|
Installed Skills (${installedSkills.length}):`);
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
} catch {
|
|
509
|
+
installedSkills.forEach((s) => console.log(` - ${s}`));
|
|
277
510
|
}
|
|
278
511
|
try {
|
|
279
|
-
const claudeJson = JSON.parse(
|
|
512
|
+
const claudeJson = JSON.parse(fs2.readFileSync(CLAUDE_JSON_PATH, "utf8"));
|
|
280
513
|
const globalMcp = claudeJson.mcpServers || {};
|
|
281
514
|
if (Object.keys(globalMcp).length > 0) {
|
|
282
515
|
console.log(`
|
|
283
516
|
Global MCP Servers (${Object.keys(globalMcp).length}):`);
|
|
284
517
|
Object.keys(globalMcp).forEach((s) => console.log(` - ${s}`));
|
|
285
518
|
}
|
|
286
|
-
} catch {
|
|
519
|
+
} catch (error) {
|
|
520
|
+
logError("status-mcp", error);
|
|
287
521
|
}
|
|
288
522
|
try {
|
|
289
|
-
const ver =
|
|
523
|
+
const ver = execSync2("claude --version 2>/dev/null", { encoding: "utf8" }).trim();
|
|
290
524
|
console.log(`
|
|
291
525
|
Claude: ${ver}`);
|
|
292
|
-
} catch {
|
|
526
|
+
} catch (error) {
|
|
527
|
+
logError("status-version", error);
|
|
293
528
|
}
|
|
294
529
|
process.exit(0);
|
|
295
530
|
}
|
|
@@ -305,34 +540,35 @@ if (cmd === "list") {
|
|
|
305
540
|
}
|
|
306
541
|
if (cmd === "config") {
|
|
307
542
|
const editor = process.env.EDITOR || "nano";
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
fs.writeFileSync(configPath, JSON.stringify({
|
|
312
|
-
env: {},
|
|
313
|
-
model: "opus",
|
|
314
|
-
alwaysThinkingEnabled: true,
|
|
315
|
-
defaultMode: "bypassPermissions"
|
|
316
|
-
}, null, 2));
|
|
317
|
-
}
|
|
318
|
-
console.log(`Opening ${configPath} in ${editor}...`);
|
|
319
|
-
spawnSync(editor, [configPath], { stdio: "inherit" });
|
|
543
|
+
createDefaultSettings();
|
|
544
|
+
console.log(`Opening ${SETTINGS_PATH} in ${editor}...`);
|
|
545
|
+
spawnSync(editor, [SETTINGS_PATH], { stdio: "inherit" });
|
|
320
546
|
process.exit(0);
|
|
321
547
|
}
|
|
322
548
|
if (cmd === "delete") {
|
|
323
549
|
const forceDelete = args.includes("--force") || args.includes("-f");
|
|
324
550
|
const profiles = loadProfiles();
|
|
325
551
|
const target = args[1];
|
|
326
|
-
|
|
327
|
-
|
|
552
|
+
if (!target) {
|
|
553
|
+
console.log("\x1B[31mUsage: cm delete <profile>\x1B[0m");
|
|
554
|
+
console.log(" profile: Profile name or number");
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
const idx = safeParseInt(target, -1);
|
|
558
|
+
const match = idx > 0 && idx <= profiles.length ? profiles[idx - 1] : profiles.find((p) => p.label.toLowerCase() === target?.toLowerCase());
|
|
328
559
|
if (!match) {
|
|
329
560
|
console.log(`\x1B[31mProfile not found: ${target}\x1B[0m`);
|
|
330
561
|
process.exit(1);
|
|
331
562
|
}
|
|
332
563
|
const shouldDelete = forceDelete || await confirm(`Delete profile "${match.label}"?`);
|
|
333
564
|
if (shouldDelete) {
|
|
334
|
-
|
|
335
|
-
|
|
565
|
+
const filePath = path3.join(PROFILES_DIR, match.value);
|
|
566
|
+
if (fs2.existsSync(filePath)) {
|
|
567
|
+
fs2.unlinkSync(filePath);
|
|
568
|
+
console.log(`\x1B[32m\u2713\x1B[0m Deleted: ${match.label}`);
|
|
569
|
+
} else {
|
|
570
|
+
console.log(`\x1B[31mProfile file not found: ${match.value}\x1B[0m`);
|
|
571
|
+
}
|
|
336
572
|
} else {
|
|
337
573
|
console.log("\x1B[33mCancelled\x1B[0m");
|
|
338
574
|
}
|
|
@@ -341,11 +577,21 @@ if (cmd === "delete") {
|
|
|
341
577
|
if (cmd === "edit") {
|
|
342
578
|
const profiles = loadProfiles();
|
|
343
579
|
const target = args[1];
|
|
344
|
-
|
|
345
|
-
|
|
580
|
+
if (!target) {
|
|
581
|
+
console.log("\x1B[31mUsage: cm edit <profile>\x1B[0m");
|
|
582
|
+
console.log(" profile: Profile name or number");
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
const idx = safeParseInt(target, -1);
|
|
586
|
+
const match = idx > 0 && idx <= profiles.length ? profiles[idx - 1] : profiles.find((p) => p.label.toLowerCase() === target?.toLowerCase());
|
|
346
587
|
if (match) {
|
|
347
588
|
const editor = process.env.EDITOR || "nano";
|
|
348
|
-
|
|
589
|
+
const filePath = path3.join(PROFILES_DIR, match.value);
|
|
590
|
+
if (fs2.existsSync(filePath)) {
|
|
591
|
+
spawnSync(editor, [filePath], { stdio: "inherit" });
|
|
592
|
+
} else {
|
|
593
|
+
console.log(`\x1B[31mProfile file not found: ${match.value}\x1B[0m`);
|
|
594
|
+
}
|
|
349
595
|
} else {
|
|
350
596
|
console.log(`\x1B[31mProfile not found: ${target}\x1B[0m`);
|
|
351
597
|
}
|
|
@@ -361,87 +607,29 @@ if (cmd === "copy") {
|
|
|
361
607
|
console.log(" new-name: Name for the copied profile");
|
|
362
608
|
process.exit(1);
|
|
363
609
|
}
|
|
364
|
-
const idx =
|
|
365
|
-
const match = profiles[idx]
|
|
610
|
+
const idx = safeParseInt(target, -1);
|
|
611
|
+
const match = idx > 0 && idx <= profiles.length ? profiles[idx - 1] : profiles.find((p) => p.label.toLowerCase() === target?.toLowerCase());
|
|
366
612
|
if (!match) {
|
|
367
613
|
console.log(`\x1B[31mProfile not found: ${target}\x1B[0m`);
|
|
368
614
|
process.exit(1);
|
|
369
615
|
}
|
|
370
|
-
const
|
|
616
|
+
const sourcePath = path3.join(PROFILES_DIR, match.value);
|
|
617
|
+
const profile = JSON.parse(fs2.readFileSync(sourcePath, "utf8"));
|
|
371
618
|
profile.name = newName;
|
|
372
|
-
const newFilename = newName
|
|
373
|
-
|
|
619
|
+
const newFilename = sanitizeProfileName(newName) + ".json";
|
|
620
|
+
const destPath = path3.join(PROFILES_DIR, newFilename);
|
|
621
|
+
if (fs2.existsSync(destPath)) {
|
|
374
622
|
const shouldOverwrite = await confirm(`Profile "${newName}" already exists. Overwrite?`);
|
|
375
623
|
if (!shouldOverwrite) {
|
|
376
624
|
console.log("\x1B[33mCancelled\x1B[0m");
|
|
377
625
|
process.exit(0);
|
|
378
626
|
}
|
|
379
627
|
}
|
|
380
|
-
|
|
628
|
+
fs2.writeFileSync(destPath, JSON.stringify(profile, null, 2));
|
|
381
629
|
console.log(`\x1B[32m\u2713\x1B[0m Copied "${match.label}" to "${newName}"`);
|
|
382
630
|
process.exit(0);
|
|
383
631
|
}
|
|
384
|
-
|
|
385
|
-
const searchMcpServers = async (query, offset = 0) => {
|
|
386
|
-
const controller = new AbortController();
|
|
387
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
388
|
-
try {
|
|
389
|
-
const res = await fetch(`${MCP_REGISTRY_URL}?limit=200`, { signal: controller.signal });
|
|
390
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
391
|
-
const data = await res.json();
|
|
392
|
-
const seen = /* @__PURE__ */ new Set();
|
|
393
|
-
const filtered = data.servers.filter((s) => {
|
|
394
|
-
if (seen.has(s.server.name)) return false;
|
|
395
|
-
seen.add(s.server.name);
|
|
396
|
-
const isLatest = s._meta?.["io.modelcontextprotocol.registry/official"]?.isLatest !== false;
|
|
397
|
-
const matchesQuery = !query || s.server.name.toLowerCase().includes(query.toLowerCase()) || s.server.description?.toLowerCase().includes(query.toLowerCase());
|
|
398
|
-
return isLatest && matchesQuery;
|
|
399
|
-
});
|
|
400
|
-
return {
|
|
401
|
-
servers: filtered.slice(offset, offset + MCP_PAGE_SIZE),
|
|
402
|
-
total: filtered.length,
|
|
403
|
-
hasMore: offset + MCP_PAGE_SIZE < filtered.length,
|
|
404
|
-
offset
|
|
405
|
-
};
|
|
406
|
-
} catch (error) {
|
|
407
|
-
logError("searchMcpServers", error);
|
|
408
|
-
return { servers: [], total: 0, hasMore: false, offset: 0 };
|
|
409
|
-
} finally {
|
|
410
|
-
clearTimeout(timeout);
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
const addMcpToProfile = (server, profileFile) => {
|
|
414
|
-
const profilePath = path.join(PROFILES_DIR, profileFile);
|
|
415
|
-
const profile = JSON.parse(fs.readFileSync(profilePath, "utf8"));
|
|
416
|
-
if (!profile.mcpServers) profile.mcpServers = {};
|
|
417
|
-
const s = server.server;
|
|
418
|
-
const name = s.name.split("/").pop();
|
|
419
|
-
if (s.remotes?.[0]) {
|
|
420
|
-
const remote = s.remotes[0];
|
|
421
|
-
profile.mcpServers[name] = {
|
|
422
|
-
type: remote.type === "streamable-http" ? "http" : remote.type,
|
|
423
|
-
url: remote.url
|
|
424
|
-
};
|
|
425
|
-
} else if (s.packages?.[0]) {
|
|
426
|
-
const pkg = s.packages[0];
|
|
427
|
-
if (pkg.registryType === "npm") {
|
|
428
|
-
profile.mcpServers[name] = {
|
|
429
|
-
type: "stdio",
|
|
430
|
-
command: "npx",
|
|
431
|
-
args: ["-y", pkg.identifier]
|
|
432
|
-
};
|
|
433
|
-
} else if (pkg.registryType === "pypi") {
|
|
434
|
-
profile.mcpServers[name] = {
|
|
435
|
-
type: "stdio",
|
|
436
|
-
command: "uvx",
|
|
437
|
-
args: [pkg.identifier]
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
|
|
442
|
-
return name;
|
|
443
|
-
};
|
|
444
|
-
const McpSearch = () => {
|
|
632
|
+
var McpSearch = () => {
|
|
445
633
|
const { exit } = useApp();
|
|
446
634
|
const [step, setStep] = useState(args[1] ? "loading" : "search");
|
|
447
635
|
const [query, setQuery] = useState(args[1] || "");
|
|
@@ -519,9 +707,14 @@ const McpSearch = () => {
|
|
|
519
707
|
{
|
|
520
708
|
items: profileItems,
|
|
521
709
|
onSelect: (item) => {
|
|
522
|
-
|
|
523
|
-
|
|
710
|
+
try {
|
|
711
|
+
const name = addMcpToProfile(selectedServer, item.value);
|
|
712
|
+
console.log(`
|
|
524
713
|
\x1B[32m\u2713\x1B[0m Added ${name} to ${item.label}`);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
console.log(`
|
|
716
|
+
\x1B[31m\u2717\x1B[0m ${error.message}`);
|
|
717
|
+
}
|
|
525
718
|
exit();
|
|
526
719
|
}
|
|
527
720
|
}
|
|
@@ -529,72 +722,11 @@ const McpSearch = () => {
|
|
|
529
722
|
}
|
|
530
723
|
return null;
|
|
531
724
|
};
|
|
532
|
-
|
|
533
|
-
{ url: "https://api.github.com/repos/anthropics/skills/contents/skills", base: "https://github.com/anthropics/skills/tree/main/skills" },
|
|
534
|
-
{ url: "https://api.github.com/repos/Prat011/awesome-llm-skills/contents/skills", base: "https://github.com/Prat011/awesome-llm-skills/tree/main/skills" },
|
|
535
|
-
{ url: "https://api.github.com/repos/skillcreatorai/Ai-Agent-Skills/contents/skills", base: "https://github.com/skillcreatorai/Ai-Agent-Skills/tree/main/skills" }
|
|
536
|
-
];
|
|
537
|
-
const fetchSkills = async () => {
|
|
538
|
-
const seen = /* @__PURE__ */ new Set();
|
|
539
|
-
const skills = [];
|
|
540
|
-
const promises = SKILL_SOURCES.map(async (source) => {
|
|
541
|
-
const controller = new AbortController();
|
|
542
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
543
|
-
try {
|
|
544
|
-
const res = await fetch(source.url, {
|
|
545
|
-
signal: controller.signal,
|
|
546
|
-
headers: { "Accept": "application/vnd.github.v3+json" }
|
|
547
|
-
});
|
|
548
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
549
|
-
const data = await res.json();
|
|
550
|
-
if (Array.isArray(data)) {
|
|
551
|
-
for (const s of data.filter((s2) => s2.type === "dir")) {
|
|
552
|
-
if (!seen.has(s.name)) {
|
|
553
|
-
seen.add(s.name);
|
|
554
|
-
skills.push({
|
|
555
|
-
label: s.name,
|
|
556
|
-
value: `${source.base}/${s.name}`,
|
|
557
|
-
key: s.name
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
} catch (error) {
|
|
563
|
-
logError(`fetchSkills(${source.url})`, error);
|
|
564
|
-
} finally {
|
|
565
|
-
clearTimeout(timeout);
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
await Promise.all(promises);
|
|
569
|
-
return skills.sort((a, b) => a.label.localeCompare(b.label));
|
|
570
|
-
};
|
|
571
|
-
const SKILLS_DIR = path.join(os.homedir(), ".claude", "skills");
|
|
572
|
-
const addSkillToClaudeJson = (skillName, skillUrl) => {
|
|
573
|
-
try {
|
|
574
|
-
if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
575
|
-
const skillPath = path.join(SKILLS_DIR, skillName);
|
|
576
|
-
if (fs.existsSync(skillPath)) {
|
|
577
|
-
return { success: false, message: "Skill already installed" };
|
|
578
|
-
}
|
|
579
|
-
const match = skillUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)\/(.+)/);
|
|
580
|
-
if (!match) return { success: false, message: "Invalid skill URL" };
|
|
581
|
-
const [, owner, repo, branch, skillSubPath] = match;
|
|
582
|
-
const tempDir = `/tmp/skill-clone-${Date.now()}`;
|
|
583
|
-
execSync(`git clone --depth 1 --filter=blob:none --sparse "https://github.com/${owner}/${repo}.git" "${tempDir}" 2>/dev/null`, { timeout: 3e4 });
|
|
584
|
-
execSync(`cd "${tempDir}" && git sparse-checkout set "${skillSubPath}" 2>/dev/null`, { timeout: 1e4 });
|
|
585
|
-
execSync(`mv "${tempDir}/${skillSubPath}" "${skillPath}"`, { timeout: 5e3 });
|
|
586
|
-
execSync(`rm -rf "${tempDir}"`, { timeout: 5e3 });
|
|
587
|
-
return { success: true };
|
|
588
|
-
} catch (e) {
|
|
589
|
-
return { success: false, message: "Failed to download skill" };
|
|
590
|
-
}
|
|
591
|
-
};
|
|
592
|
-
const SkillsBrowser = () => {
|
|
725
|
+
var SkillsBrowser = () => {
|
|
593
726
|
const { exit } = useApp();
|
|
594
727
|
const [allSkills, setAllSkills] = useState([]);
|
|
595
728
|
const [loading, setLoading] = useState(true);
|
|
596
729
|
const [offset, setOffset] = useState(0);
|
|
597
|
-
const SKILLS_PAGE_SIZE = 50;
|
|
598
730
|
useEffect(() => {
|
|
599
731
|
const loadSkills = async () => {
|
|
600
732
|
const s = await fetchSkills();
|
|
@@ -662,8 +794,8 @@ if (cmd === "skills") {
|
|
|
662
794
|
process.exit(1);
|
|
663
795
|
}
|
|
664
796
|
const installed = getInstalledSkills();
|
|
665
|
-
const idx =
|
|
666
|
-
const match = installed[idx]
|
|
797
|
+
const idx = safeParseInt(target, -1);
|
|
798
|
+
const match = idx > 0 && idx <= installed.length ? installed[idx - 1] : installed.find((s) => s.toLowerCase() === target?.toLowerCase());
|
|
667
799
|
if (!match) {
|
|
668
800
|
console.log(`\x1B[31mSkill not found: ${target}\x1B[0m`);
|
|
669
801
|
console.log('Run "cm skills list" to see installed skills');
|
|
@@ -699,14 +831,18 @@ if (cmd === "skills") {
|
|
|
699
831
|
console.log(" profile: Profile name or number");
|
|
700
832
|
process.exit(1);
|
|
701
833
|
}
|
|
702
|
-
const idx =
|
|
703
|
-
const profileMatch = profiles[idx]
|
|
834
|
+
const idx = safeParseInt(targetProfile, -1);
|
|
835
|
+
const profileMatch = idx > 0 && idx <= profiles.length ? profiles[idx - 1] : profiles.find((p) => p.label.toLowerCase() === targetProfile?.toLowerCase());
|
|
704
836
|
if (!profileMatch) {
|
|
705
837
|
console.log(`\x1B[31mProfile not found: ${targetProfile}\x1B[0m`);
|
|
706
838
|
process.exit(1);
|
|
707
839
|
}
|
|
708
|
-
const profilePath =
|
|
709
|
-
|
|
840
|
+
const profilePath = path3.join(PROFILES_DIR, profileMatch.value);
|
|
841
|
+
if (!fs2.existsSync(profilePath)) {
|
|
842
|
+
console.log(`\x1B[31mProfile file not found: ${profileMatch.value}\x1B[0m`);
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
const profile = JSON.parse(fs2.readFileSync(profilePath, "utf8"));
|
|
710
846
|
const mcpServers = profile.mcpServers || {};
|
|
711
847
|
if (Object.keys(mcpServers).length === 0) {
|
|
712
848
|
console.log(`\x1B[33mNo MCP servers configured in "${profileMatch.label}"\x1B[0m`);
|
|
@@ -721,7 +857,7 @@ if (cmd === "skills") {
|
|
|
721
857
|
if (shouldRemove) {
|
|
722
858
|
delete mcpServers[serverName];
|
|
723
859
|
profile.mcpServers = mcpServers;
|
|
724
|
-
|
|
860
|
+
fs2.writeFileSync(profilePath, JSON.stringify(profile, null, 2));
|
|
725
861
|
console.log(`\x1B[32m\u2713\x1B[0m Removed "${serverName}" from "${profileMatch.label}"`);
|
|
726
862
|
} else {
|
|
727
863
|
console.log("\x1B[33mCancelled\x1B[0m");
|
|
@@ -739,43 +875,23 @@ if (cmd === "skills") {
|
|
|
739
875
|
const [model, setModel] = useState("");
|
|
740
876
|
const [group, setGroup] = useState("");
|
|
741
877
|
const [validationErrors, setValidationErrors] = useState([]);
|
|
742
|
-
const providers = [
|
|
743
|
-
{ label: "Anthropic (Direct)", value: "anthropic", url: "", needsKey: true },
|
|
744
|
-
{ label: "Amazon Bedrock", value: "bedrock", url: "", needsKey: false },
|
|
745
|
-
{ label: "Z.AI", value: "zai", url: "https://api.z.ai/api/anthropic", needsKey: true },
|
|
746
|
-
{ label: "MiniMax", value: "minimax", url: "https://api.minimax.io/anthropic", needsKey: true },
|
|
747
|
-
{ label: "Custom", value: "custom", url: "", needsKey: true }
|
|
748
|
-
];
|
|
749
878
|
const handleSave = () => {
|
|
750
|
-
const
|
|
751
|
-
const profile = {
|
|
752
|
-
name,
|
|
753
|
-
group: group || void 0,
|
|
754
|
-
env: {
|
|
755
|
-
...apiKey && { ANTHROPIC_AUTH_TOKEN: apiKey },
|
|
756
|
-
...model && { ANTHROPIC_MODEL: model },
|
|
757
|
-
...prov?.url && { ANTHROPIC_BASE_URL: prov.url },
|
|
758
|
-
API_TIMEOUT_MS: "3000000"
|
|
759
|
-
},
|
|
760
|
-
model: "opus",
|
|
761
|
-
alwaysThinkingEnabled: true,
|
|
762
|
-
defaultMode: "bypassPermissions"
|
|
763
|
-
};
|
|
879
|
+
const profile = buildProfileData(name, provider, apiKey, model, group, PROVIDERS);
|
|
764
880
|
const validation = validateProfile(profile);
|
|
765
881
|
if (!validation.valid) {
|
|
766
882
|
setStep("error");
|
|
767
883
|
setValidationErrors(validation.errors);
|
|
768
884
|
return;
|
|
769
885
|
}
|
|
770
|
-
const filename = name
|
|
771
|
-
|
|
886
|
+
const filename = sanitizeProfileName(name) + ".json";
|
|
887
|
+
fs2.writeFileSync(path3.join(PROFILES_DIR, filename), JSON.stringify(profile, null, 2));
|
|
772
888
|
console.log(`
|
|
773
889
|
\x1B[32m\u2713\x1B[0m Created: ${name}`);
|
|
774
890
|
exit();
|
|
775
891
|
};
|
|
776
892
|
const handleProviderSelect = (item) => {
|
|
777
893
|
setProvider(item.value);
|
|
778
|
-
const prov =
|
|
894
|
+
const prov = PROVIDERS.find((p) => p.value === item.value);
|
|
779
895
|
setStep(prov.needsKey ? "apikey" : "model");
|
|
780
896
|
};
|
|
781
897
|
useInput((input, key) => {
|
|
@@ -784,7 +900,7 @@ if (cmd === "skills") {
|
|
|
784
900
|
setValidationErrors([]);
|
|
785
901
|
}
|
|
786
902
|
});
|
|
787
|
-
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:
|
|
903
|
+
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...")));
|
|
788
904
|
};
|
|
789
905
|
render(/* @__PURE__ */ React.createElement(NewProfileWizard, null));
|
|
790
906
|
} else {
|
|
@@ -818,9 +934,9 @@ if (cmd === "skills") {
|
|
|
818
934
|
{ label: "/skills", description: "Browse and install skills", action: () => render(/* @__PURE__ */ React.createElement(SkillsBrowser, null)) },
|
|
819
935
|
{ label: "/mcp", description: "Search and add MCP servers", action: () => render(/* @__PURE__ */ React.createElement(McpSearch, null)) },
|
|
820
936
|
{ label: "/new", description: "Create new profile", action: () => setStep("newProfile") },
|
|
821
|
-
{ label: "/list", description: "List all profiles", action: () =>
|
|
822
|
-
{ label: "/status", description: "Show current settings", action: () =>
|
|
823
|
-
{ label: "/config", description: "Edit Claude settings", action: () =>
|
|
937
|
+
{ label: "/list", description: "List all profiles", action: () => execSync2("cm list", { stdio: "inherit" }) },
|
|
938
|
+
{ label: "/status", description: "Show current settings", action: () => execSync2("cm status", { stdio: "inherit" }) },
|
|
939
|
+
{ label: "/config", description: "Edit Claude settings", action: () => execSync2("cm config", { stdio: "inherit" }) },
|
|
824
940
|
{ label: "/help", description: "Show keyboard shortcuts", action: () => setShowHelp(true) },
|
|
825
941
|
{ label: "/quit", description: "Exit cm", action: () => process.exit(0) }
|
|
826
942
|
];
|
|
@@ -828,8 +944,7 @@ if (cmd === "skills") {
|
|
|
828
944
|
if (!filter) return profiles;
|
|
829
945
|
const fuse = new Fuse(profiles, {
|
|
830
946
|
keys: ["label", "group"],
|
|
831
|
-
threshold:
|
|
832
|
-
// Lower = more strict matching
|
|
947
|
+
threshold: FUSE_THRESHOLD,
|
|
833
948
|
ignoreLocation: true,
|
|
834
949
|
includeScore: true
|
|
835
950
|
});
|
|
@@ -840,7 +955,7 @@ if (cmd === "skills") {
|
|
|
840
955
|
const search = commandInput.toLowerCase().replace(/^\//, "");
|
|
841
956
|
const fuse = new Fuse(commands, {
|
|
842
957
|
keys: ["label", "description"],
|
|
843
|
-
threshold:
|
|
958
|
+
threshold: FUSE_THRESHOLD,
|
|
844
959
|
ignoreLocation: true
|
|
845
960
|
});
|
|
846
961
|
return fuse.search(search).map((r) => r.item);
|
|
@@ -848,7 +963,7 @@ if (cmd === "skills") {
|
|
|
848
963
|
useEffect(() => {
|
|
849
964
|
setTimeout(() => setStep("select"), 1500);
|
|
850
965
|
if (!skipUpdate) {
|
|
851
|
-
checkForUpdate().then(setUpdateInfo);
|
|
966
|
+
checkForUpdate(skipUpdate).then(setUpdateInfo);
|
|
852
967
|
}
|
|
853
968
|
}, []);
|
|
854
969
|
useInput((input, key) => {
|
|
@@ -880,24 +995,24 @@ if (cmd === "skills") {
|
|
|
880
995
|
return;
|
|
881
996
|
}
|
|
882
997
|
if (step === "select") {
|
|
883
|
-
const num =
|
|
998
|
+
const num = safeParseInt(input, -1);
|
|
884
999
|
if (num >= 1 && num <= 9 && num <= filteredProfiles.length) {
|
|
885
1000
|
const profile = filteredProfiles[num - 1];
|
|
886
1001
|
applyProfile(profile.value);
|
|
887
1002
|
console.log(`
|
|
888
1003
|
\x1B[32m\u2713\x1B[0m Applied: ${profile.label}
|
|
889
1004
|
`);
|
|
890
|
-
launchClaude();
|
|
1005
|
+
launchClaude(dangerMode);
|
|
891
1006
|
}
|
|
892
1007
|
if (input === "u" && updateInfo?.needsUpdate) {
|
|
893
1008
|
console.log("\n\x1B[33mUpdating Claude...\x1B[0m\n");
|
|
894
1009
|
try {
|
|
895
1010
|
if (process.platform === "darwin") {
|
|
896
|
-
|
|
1011
|
+
execSync2("brew upgrade claude-code", { stdio: "inherit" });
|
|
897
1012
|
} else {
|
|
898
|
-
|
|
1013
|
+
execSync2("npm update -g @anthropic-ai/claude-code", { stdio: "inherit" });
|
|
899
1014
|
}
|
|
900
|
-
console.log("\
|
|
1015
|
+
console.log("\x1B[32m\u2713 Updated!\x1B[0m\n");
|
|
901
1016
|
setUpdateInfo({ ...updateInfo, needsUpdate: false });
|
|
902
1017
|
} catch (error) {
|
|
903
1018
|
console.log("\x1B[31m\u2717 Update failed\x1B[0m\n");
|
|
@@ -923,17 +1038,9 @@ if (cmd === "skills") {
|
|
|
923
1038
|
}
|
|
924
1039
|
if (input === "c") {
|
|
925
1040
|
const editor = process.env.EDITOR || "nano";
|
|
926
|
-
|
|
927
|
-
if (!fs.existsSync(configPath)) {
|
|
928
|
-
fs.writeFileSync(configPath, JSON.stringify({
|
|
929
|
-
env: {},
|
|
930
|
-
model: "opus",
|
|
931
|
-
alwaysThinkingEnabled: true,
|
|
932
|
-
defaultMode: "bypassPermissions"
|
|
933
|
-
}, null, 2));
|
|
934
|
-
}
|
|
1041
|
+
createDefaultSettings();
|
|
935
1042
|
console.clear();
|
|
936
|
-
spawnSync(editor, [
|
|
1043
|
+
spawnSync(editor, [SETTINGS_PATH], { stdio: "inherit" });
|
|
937
1044
|
console.log("\n\x1B[36mConfig edited. Press Enter to continue...\x1B[0m");
|
|
938
1045
|
}
|
|
939
1046
|
}
|
|
@@ -970,9 +1077,9 @@ if (cmd === "skills") {
|
|
|
970
1077
|
console.log(`
|
|
971
1078
|
\x1B[32m\u2713\x1B[0m Applied: ${item.label.replace(/^\d+\.\s*/, "")}
|
|
972
1079
|
`);
|
|
973
|
-
launchClaude();
|
|
1080
|
+
launchClaude(dangerMode);
|
|
974
1081
|
};
|
|
975
|
-
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" }, "
|
|
1082
|
+
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(
|
|
976
1083
|
SelectInput,
|
|
977
1084
|
{
|
|
978
1085
|
items: groupedItems,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-manager",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
4
4
|
"description": "Terminal app for managing Claude Code settings, profiles, MCP servers, and skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"claude-manager": "./dist/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "esbuild src/cli.js --platform=node --format=esm --loader:.js=jsx --outfile=dist/cli.js --packages=external && echo '#!/usr/bin/env node' | cat - dist/cli.js > dist/tmp && mv dist/tmp dist/cli.js && chmod +x dist/cli.js",
|
|
11
|
+
"build": "esbuild src/cli.js --platform=node --format=esm --loader:.js=jsx --bundle --outfile=dist/cli.js --packages=external && echo '#!/usr/bin/env node' | cat - dist/cli.js > dist/tmp && mv dist/tmp dist/cli.js && chmod +x dist/cli.js",
|
|
12
12
|
"prepublishOnly": "npm run build"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|