codemax-setup 1.0.0

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 +32 -0
  2. package/index.js +302 -0
  3. package/package.json +28 -0
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # codemax-setup
2
+
3
+ One-command setup for [CodeMax](https://codemax.pro) — configures Claude Code, MCP tools, and environment automatically.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx codemax-setup
9
+ ```
10
+
11
+ Or with your API key directly:
12
+
13
+ ```bash
14
+ npx codemax-setup sk-cp-your-key-here
15
+ ```
16
+
17
+ ## What it does
18
+
19
+ 1. Validates your CodeMax API key
20
+ 2. Installs `codemax-mcp` (MCP tools: web_search & understand_image)
21
+ 3. Configures `~/.claude/settings.json` (Claude Code environment)
22
+ 4. Configures `~/.claude.json` (MCP server)
23
+ 5. Installs Claude Code CLI if missing
24
+
25
+ ## Requirements
26
+
27
+ - [Node.js](https://nodejs.org) 18+
28
+
29
+ ## Links
30
+
31
+ - Website: [codemax.pro](https://codemax.pro)
32
+ - Docs: [codemax.pro/docs](https://codemax.pro/docs)
package/index.js ADDED
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { execSync } = require("child_process");
6
+ const readline = require("readline");
7
+
8
+ const API_ENDPOINT = "https://api.codemax.pro";
9
+ const MCP_URL = "https://api.codemax.pro";
10
+
11
+ // ── Colors ──
12
+ const c = {
13
+ reset: "\x1b[0m",
14
+ bold: "\x1b[1m",
15
+ dim: "\x1b[2m",
16
+ cyan: "\x1b[36m",
17
+ green: "\x1b[32m",
18
+ yellow: "\x1b[33m",
19
+ red: "\x1b[31m",
20
+ gray: "\x1b[90m",
21
+ white: "\x1b[97m",
22
+ };
23
+
24
+ const log = (msg) => console.log(msg);
25
+ const ok = (msg) => log(` ${c.green}✓ ${msg}${c.reset}`);
26
+ const warn = (msg) => log(` ${c.yellow}⚠ ${msg}${c.reset}`);
27
+ const fail = (msg) => log(` ${c.red}✗ ${msg}${c.reset}`);
28
+ const info = (msg) => log(` ${c.cyan}${msg}${c.reset}`);
29
+ const gray = (msg) => log(` ${c.gray}${msg}${c.reset}`);
30
+
31
+ function ask(question) {
32
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
33
+ return new Promise((resolve) => {
34
+ rl.question(question, (answer) => {
35
+ rl.close();
36
+ resolve(answer.trim());
37
+ });
38
+ });
39
+ }
40
+
41
+ function readJson(filePath) {
42
+ try {
43
+ if (fs.existsSync(filePath)) {
44
+ const raw = fs.readFileSync(filePath, "utf8").trim();
45
+ if (!raw) return {};
46
+ return JSON.parse(raw);
47
+ }
48
+ } catch {}
49
+ return {};
50
+ }
51
+
52
+ function writeJson(filePath, data) {
53
+ const dir = path.dirname(filePath);
54
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
55
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
56
+ }
57
+
58
+ function run(cmd) {
59
+ try {
60
+ return execSync(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ function which(cmd) {
67
+ try {
68
+ const r = execSync(process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`, {
69
+ encoding: "utf8",
70
+ stdio: "pipe",
71
+ }).trim();
72
+ return r ? true : false;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ async function validateKey(apiKey) {
79
+ const body = JSON.stringify({
80
+ model: "claude-opus-4-6-thinking",
81
+ max_tokens: 10,
82
+ messages: [{ role: "user", content: "Hi" }],
83
+ });
84
+
85
+ try {
86
+ const res = await fetch(`${API_ENDPOINT}/messages`, {
87
+ method: "POST",
88
+ headers: {
89
+ "Content-Type": "application/json",
90
+ "x-api-key": apiKey,
91
+ "anthropic-version": "2023-06-01",
92
+ },
93
+ body,
94
+ signal: AbortSignal.timeout(30000),
95
+ });
96
+
97
+ if (res.ok) return { valid: true };
98
+ if (res.status === 429) return { valid: true, rateLimit: true };
99
+ if (res.status === 401 || res.status === 403) return { valid: false, reason: "Invalid API key" };
100
+ if (res.status === 402) return { valid: false, reason: "No remaining balance" };
101
+ return { valid: false, reason: `Server returned ${res.status}` };
102
+ } catch (e) {
103
+ return { valid: false, reason: e.message, network: true };
104
+ }
105
+ }
106
+
107
+ async function main() {
108
+ log("");
109
+ log(` ${c.cyan}╔══════════════════════════════════════════╗${c.reset}`);
110
+ log(` ${c.cyan}║ ${c.bold}CodeMax Setup${c.reset}${c.cyan} ║${c.reset}`);
111
+ log(` ${c.cyan}╚══════════════════════════════════════════╝${c.reset}`);
112
+ log("");
113
+
114
+ const home = process.env.HOME || process.env.USERPROFILE;
115
+ if (!home) {
116
+ fail("Cannot determine home directory");
117
+ process.exit(1);
118
+ }
119
+
120
+ // ── Get API Key ──
121
+ let apiKey = process.argv[2] || "";
122
+ if (!apiKey) {
123
+ log(` ${c.white}Enter your CodeMax API key${c.reset}`);
124
+ gray("Get it from your admin or codemax.pro");
125
+ log("");
126
+ apiKey = await ask(` ${c.white}API Key: ${c.reset}`);
127
+ log("");
128
+ }
129
+
130
+ if (!apiKey || apiKey.length < 5) {
131
+ fail("No valid API key provided. Exiting.");
132
+ process.exit(1);
133
+ }
134
+
135
+ // ── [1/6] Validate API Key ──
136
+ info("[1/6] Validating API key...");
137
+ const result = await validateKey(apiKey);
138
+
139
+ if (result.valid) {
140
+ if (result.rateLimit) {
141
+ warn("API key valid but rate-limited. Continuing setup...");
142
+ } else {
143
+ ok("API key is valid!");
144
+ ok("Connected to CodeMax");
145
+ }
146
+ } else if (result.network) {
147
+ warn(`Could not reach server: ${result.reason}`);
148
+ const cont = await ask(` ${c.yellow}Continue anyway? (y/n): ${c.reset}`);
149
+ if (cont.toLowerCase() !== "y") process.exit(1);
150
+ } else {
151
+ fail(result.reason);
152
+ gray("Make sure you're using a valid CodeMax API key (sk-cp-...)");
153
+ process.exit(1);
154
+ }
155
+ log("");
156
+
157
+ // ── [2/6] Check Node.js ──
158
+ info("[2/6] Checking Node.js...");
159
+ const nodeVer = run("node --version");
160
+ if (nodeVer) {
161
+ ok(`Node.js ${nodeVer} found`);
162
+ } else {
163
+ fail("Node.js not found. Install from https://nodejs.org");
164
+ process.exit(1);
165
+ }
166
+ log("");
167
+
168
+ // ── [3/6] Install codemax-mcp ──
169
+ info("[3/6] Installing codemax-mcp...");
170
+ const mcpResult = run("npm install -g codemax-mcp");
171
+ if (mcpResult !== null) {
172
+ ok("codemax-mcp installed");
173
+ } else {
174
+ warn("Could not install globally, will use npx");
175
+ }
176
+ log("");
177
+
178
+ // ── [4/6] Configure Claude Code settings ──
179
+ info("[4/6] Configuring Claude Code settings...");
180
+ const settingsPath = path.join(home, ".claude", "settings.json");
181
+ try {
182
+ const settings = readJson(settingsPath);
183
+ if (!settings.env) settings.env = {};
184
+
185
+ Object.assign(settings.env, {
186
+ ANTHROPIC_AUTH_TOKEN: apiKey,
187
+ ANTHROPIC_BASE_URL: API_ENDPOINT,
188
+ ANTHROPIC_MODEL: "claude-opus-4-6-thinking",
189
+ ANTHROPIC_REASONING_MODEL: "Opus 4.6",
190
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "Sonnet 4.5",
191
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "Opus 4.6",
192
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "Haiku 4.5",
193
+ CLAUDE_CODE_SUBAGENT_MODEL: "claude-opus-4-6-thinking",
194
+ ENABLE_EXPERIMENTAL_MCP_CLI: "true",
195
+ });
196
+
197
+ settings.model = "opus[1m]";
198
+
199
+ writeJson(settingsPath, settings);
200
+ ok(settingsPath);
201
+ } catch (e) {
202
+ fail(`Failed: ${e.message}`);
203
+ }
204
+ log("");
205
+
206
+ // ── [5/6] Configure MCP server ──
207
+ info("[5/6] Configuring MCP server...");
208
+ const claudeJsonPath = path.join(home, ".claude.json");
209
+ try {
210
+ const claudeJson = readJson(claudeJsonPath);
211
+ if (!claudeJson.mcpServers) claudeJson.mcpServers = {};
212
+
213
+ claudeJson.mcpServers.CodeMax = {
214
+ command: "npx",
215
+ args: ["-y", "codemax-mcp"],
216
+ env: {
217
+ CodeMax_API_KEY: apiKey,
218
+ CodeMax_URL: MCP_URL,
219
+ },
220
+ };
221
+ claudeJson.hasCompletedOnboarding = true;
222
+
223
+ writeJson(claudeJsonPath, claudeJson);
224
+ ok(claudeJsonPath);
225
+ } catch (e) {
226
+ fail(`Failed: ${e.message}`);
227
+ }
228
+ log("");
229
+
230
+ // ── Clear stale env vars (Windows only) ──
231
+ if (process.platform === "win32") {
232
+ const staleVars = [
233
+ "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL",
234
+ "ANTHROPIC_MODEL", "ANTHROPIC_SMALL_FAST_MODEL",
235
+ "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL",
236
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL", "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC",
237
+ ];
238
+ for (const v of staleVars) {
239
+ run(`setx ${v} ""`);
240
+ }
241
+ }
242
+
243
+ // ── [6/6] Install Claude Code CLI ──
244
+ info("[6/6] Checking Claude Code CLI...");
245
+ if (which("claude")) {
246
+ ok("Claude Code CLI already installed");
247
+ } else {
248
+ info("Installing Claude Code CLI...");
249
+ const cliResult = run("npm install -g @anthropic-ai/claude-code");
250
+ if (cliResult !== null) {
251
+ ok("Claude Code CLI installed!");
252
+ } else {
253
+ warn("Auto-install failed. Run: npm install -g @anthropic-ai/claude-code");
254
+ }
255
+ }
256
+
257
+ // ── Done ──
258
+ log("");
259
+ log(` ${c.green}╔══════════════════════════════════════════╗${c.reset}`);
260
+ log(` ${c.green}║ ${c.bold}✓ Setup complete!${c.reset}${c.green} ║${c.reset}`);
261
+ log(` ${c.green}╚══════════════════════════════════════════╝${c.reset}`);
262
+ log("");
263
+ log(` ${c.white}What was configured:${c.reset}`);
264
+ gray(` • API key validated ✓`);
265
+ gray(` • Claude Code settings (~/.claude/settings.json)`);
266
+ gray(` • MCP server with web_search & understand_image (~/.claude.json)`);
267
+ gray(` • Claude Code CLI`);
268
+ log("");
269
+ log(` ${c.gray}╭───────────────────────────────────────╮${c.reset}`);
270
+ log(` ${c.gray}│ Base URL: api.codemax.pro │${c.reset}`);
271
+ log(` ${c.gray}│ Key: ${apiKey.substring(0, 8)}... │${c.reset}`);
272
+ log(` ${c.gray}│ MCP: CodeMax (web_search + │${c.reset}`);
273
+ log(` ${c.gray}│ understand_image) │${c.reset}`);
274
+ log(` ${c.gray}╰───────────────────────────────────────╯${c.reset}`);
275
+ log("");
276
+ gray("Restart Claude Code / VS Code to apply.");
277
+ gray("Re-run anytime to update your key.");
278
+ log("");
279
+
280
+ // Ask to launch
281
+ if (which("claude")) {
282
+ const launch = await ask(` ${c.white}Launch Claude Code now? (y/n): ${c.reset}`);
283
+ if (launch.toLowerCase() === "y" || launch === "") {
284
+ log("");
285
+ info("🚀 Launching Claude Code...");
286
+ log(` ${c.gray}════════════════════════════════════${c.reset}`);
287
+ log("");
288
+ try {
289
+ execSync("claude", { stdio: "inherit" });
290
+ } catch {}
291
+ } else {
292
+ log("");
293
+ log(` ${c.white}To start later, open any terminal and type: claude${c.reset}`);
294
+ log("");
295
+ }
296
+ }
297
+ }
298
+
299
+ main().catch((e) => {
300
+ fail(`Unexpected error: ${e.message}`);
301
+ process.exit(1);
302
+ });
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "codemax-setup",
3
+ "version": "1.0.0",
4
+ "description": "One-command setup for CodeMax — configures Claude Code, MCP tools, and environment",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "codemax-setup": "index.js"
8
+ },
9
+ "keywords": [
10
+ "codemax",
11
+ "claude",
12
+ "claude-code",
13
+ "setup",
14
+ "anthropic",
15
+ "mcp",
16
+ "ai",
17
+ "coding-assistant"
18
+ ],
19
+ "author": "CodeMax",
20
+ "license": "MIT",
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "files": [
25
+ "index.js",
26
+ "README.md"
27
+ ]
28
+ }