@cc-claw/code 0.6.48 → 0.6.49

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 (4) hide show
  1. package/bin/cc-code +19 -19
  2. package/install.js +308 -308
  3. package/install.test.js +125 -125
  4. package/package.json +43 -43
package/bin/cc-code CHANGED
@@ -1,19 +1,19 @@
1
- #!/usr/bin/env node
2
-
3
- const { existsSync } = require("fs");
4
- const { join, dirname } = require("path");
5
- const { execFileSync } = require("child_process");
6
-
7
- const dir = dirname(__filename);
8
- const binPath = existsSync(join(dir, "cc-code-bin"))
9
- ? join(dir, "cc-code-bin")
10
- : join(dir, "cc-code.exe");
11
-
12
- try {
13
- execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });
14
- } catch (err) {
15
- if (err.status !== null) {
16
- process.exit(err.status);
17
- }
18
- throw err;
19
- }
1
+ #!/usr/bin/env node
2
+
3
+ const { existsSync } = require("fs");
4
+ const { join, dirname } = require("path");
5
+ const { execFileSync } = require("child_process");
6
+
7
+ const dir = dirname(__filename);
8
+ const binPath = existsSync(join(dir, "cc-code-bin"))
9
+ ? join(dir, "cc-code-bin")
10
+ : join(dir, "cc-code.exe");
11
+
12
+ try {
13
+ execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });
14
+ } catch (err) {
15
+ if (err.status !== null) {
16
+ process.exit(err.status);
17
+ }
18
+ throw err;
19
+ }
package/install.js CHANGED
@@ -1,308 +1,308 @@
1
- #!/usr/bin/env node
2
-
3
- const { createWriteStream, mkdirSync, chmodSync, existsSync, renameSync, unlinkSync, writeFileSync } = require("fs");
4
- const { join } = require("path");
5
- const { execSync } = require("child_process");
6
-
7
- const { homedir } = require("os");
8
-
9
- const VERSION = require("./package.json").version;
10
- const REPO = "cc-claws/cc-code";
11
- const BASE_URL = `https://github.com/${REPO}/releases/download/npm-v${VERSION}`;
12
-
13
- const PLATFORMS = {
14
- "linux-x64": { os: "linux", arch: "x64", suffix: "linux-x86_64", ext: "tar.gz" },
15
- "linux-arm64": { os: "linux", arch: "arm64", suffix: "linux-aarch64", ext: "tar.gz" },
16
- "darwin-x64": { os: "darwin", arch: "x64", suffix: "macos-x86_64", ext: "tar.gz" },
17
- "darwin-arm64": { os: "darwin", arch: "arm64", suffix: "macos-aarch64", ext: "tar.gz" },
18
- "win32-x64": { os: "win32", arch: "x64", suffix: "windows-x86_64", ext: "zip" },
19
- };
20
-
21
- function getPlatformKey() {
22
- const key = `${process.platform}-${process.arch}`;
23
- if (!PLATFORMS[key]) {
24
- throw new Error(`Unsupported platform: ${key}. Supported: ${Object.keys(PLATFORMS).join(", ")}`);
25
- }
26
- return key;
27
- }
28
-
29
- function getProxyUrl() {
30
- // Check common proxy environment variables
31
- const proxy = process.env.HTTPS_PROXY || process.env.https_proxy
32
- || process.env.HTTP_PROXY || process.env.http_proxy
33
- || process.env.ALL_PROXY || process.env.all_proxy;
34
- return proxy || null;
35
- }
36
-
37
- function download(url) {
38
- const proxyUrl = getProxyUrl();
39
-
40
- if (proxyUrl) {
41
- return downloadViaProxy(url, proxyUrl);
42
- }
43
- return downloadDirect(url);
44
- }
45
-
46
- function downloadDirect(url) {
47
- const { get } = require("https");
48
- return new Promise((resolve, reject) => {
49
- get(url, (res) => {
50
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
51
- downloadDirect(res.headers.location).then(resolve, reject);
52
- return;
53
- }
54
- if (res.statusCode !== 200) {
55
- reject(new Error(`Download failed: HTTP ${res.statusCode} for ${url}`));
56
- return;
57
- }
58
- const chunks = [];
59
- res.on("data", (chunk) => chunks.push(chunk));
60
- res.on("end", () => resolve(Buffer.concat(chunks)));
61
- res.on("error", reject);
62
- }).on("error", reject);
63
- });
64
- }
65
-
66
- function downloadViaProxy(url, proxyUrl) {
67
- const { URL } = require("url");
68
- const target = new URL(url);
69
- const proxy = new URL(proxyUrl);
70
-
71
- const isHttps = proxy.protocol === "https:" || proxy.protocol === "HTTPS:";
72
- const proxyModule = isHttps ? require("https") : require("http");
73
-
74
- const proxyOpts = {
75
- hostname: proxy.hostname,
76
- port: proxy.port || (isHttps ? 443 : 80),
77
- path: url,
78
- method: "GET",
79
- headers: { "Host": target.hostname, "User-Agent": "cc-code-installer" },
80
- };
81
-
82
- // Support proxy auth
83
- if (proxy.username) {
84
- const auth = decodeURIComponent(`${proxy.username}:${proxy.password || ""}`);
85
- proxyOpts.headers["Proxy-Authorization"] = `Basic ${Buffer.from(auth).toString("base64")}`;
86
- }
87
-
88
- console.log(` Using proxy: ${proxy.hostname}:${proxy.port || (isHttps ? 443 : 80)}`);
89
-
90
- return new Promise((resolve, reject) => {
91
- const req = proxyModule.request(proxyOpts, (res) => {
92
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
93
- download(res.headers.location).then(resolve, reject);
94
- return;
95
- }
96
- if (res.statusCode !== 200) {
97
- reject(new Error(`Download failed: HTTP ${res.statusCode} for ${url}`));
98
- return;
99
- }
100
- const chunks = [];
101
- res.on("data", (chunk) => chunks.push(chunk));
102
- res.on("end", () => resolve(Buffer.concat(chunks)));
103
- res.on("error", reject);
104
- });
105
- req.on("error", reject);
106
- req.end();
107
- });
108
- }
109
-
110
- function extractTarGz(buffer, dest) {
111
- const tmpFile = join(dest, "cc-code.tar.gz");
112
- writeFileSync(tmpFile, buffer);
113
- execSync(`tar -xzf "${tmpFile}" -C "${dest}"`, { stdio: "ignore" });
114
- unlinkSync(tmpFile);
115
- }
116
-
117
- function extractZip(buffer, dest) {
118
- const AdmZip = require("adm-zip");
119
- const zip = new AdmZip(buffer);
120
- zip.extractAllTo(dest, true);
121
- }
122
-
123
- function migrateFromClaudeCode(home = homedir()) {
124
- const claudeSettingsPath = join(home, ".claude", "settings.json");
125
- const ccCodeDir = join(home, ".cc-code");
126
- const ccCodeSettingsPath = join(ccCodeDir, "settings.json");
127
-
128
- // 已有 cc-code 配置,跳过
129
- if (existsSync(ccCodeSettingsPath)) {
130
- return true;
131
- }
132
-
133
- // 无 Claude Code 配置
134
- if (!existsSync(claudeSettingsPath)) {
135
- return false;
136
- }
137
-
138
- let claudeSettings;
139
- try {
140
- claudeSettings = JSON.parse(require("fs").readFileSync(claudeSettingsPath, "utf-8"));
141
- } catch {
142
- return false;
143
- }
144
-
145
- const env = claudeSettings.env || {};
146
- const providers = [];
147
-
148
- // 检测 Anthropic
149
- const anthropicKey = env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN || "";
150
- const anthropicBaseUrl = env.ANTHROPIC_BASE_URL || "";
151
- if (anthropicKey || anthropicBaseUrl) {
152
- const models = {};
153
- if (env.ANTHROPIC_DEFAULT_OPUS_MODEL) models.opus = env.ANTHROPIC_DEFAULT_OPUS_MODEL;
154
- if (env.ANTHROPIC_DEFAULT_SONNET_MODEL) models.sonnet = env.ANTHROPIC_DEFAULT_SONNET_MODEL;
155
- if (env.ANTHROPIC_DEFAULT_HAIKU_MODEL) models.haiku = env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
156
- const p = {
157
- id: "anthropic",
158
- type: "anthropic",
159
- apiKey: anthropicKey,
160
- };
161
- if (anthropicBaseUrl) p.baseUrl = anthropicBaseUrl;
162
- if (Object.keys(models).length > 0) p.models = models;
163
- providers.push(p);
164
- }
165
-
166
- // 检测 OpenAI 兼容
167
- const openaiKey = env.OPENAI_API_KEY || env.CODEX_API_KEY || "";
168
- const openaiBaseUrl = env.OPENAI_BASE_URL || env.OPENAI_API_BASE || "";
169
- if (openaiKey || openaiBaseUrl) {
170
- const models = {};
171
- if (env.OPENAI_MODEL) models.sonnet = env.OPENAI_MODEL;
172
- const p = {
173
- id: "openai",
174
- type: "openai",
175
- apiKey: openaiKey,
176
- };
177
- if (openaiBaseUrl) p.baseUrl = openaiBaseUrl;
178
- if (Object.keys(models).length > 0) p.models = models;
179
- providers.push(p);
180
- }
181
-
182
- if (providers.length === 0) {
183
- return false;
184
- }
185
-
186
- if (!existsSync(ccCodeDir)) {
187
- mkdirSync(ccCodeDir, { recursive: true });
188
- }
189
-
190
- // 根据第一个 provider 的可用模型决定默认激活别名
191
- const firstProvider = providers[0];
192
- let activeAlias = "opus";
193
- if (firstProvider.models) {
194
- if (firstProvider.models.opus) activeAlias = "opus";
195
- else if (firstProvider.models.sonnet) activeAlias = "sonnet";
196
- else if (firstProvider.models.haiku) activeAlias = "haiku";
197
- }
198
-
199
- const ccCodeSettings = {
200
- config: {
201
- active_alias: activeAlias,
202
- active_provider_id: firstProvider.id,
203
- providers,
204
- },
205
- };
206
- writeFileSync(ccCodeSettingsPath, JSON.stringify(ccCodeSettings, null, 2) + "\n");
207
- console.log("");
208
- console.log(" Migrated ~/.claude/settings.json -> ~/.cc-code/settings.json");
209
- console.log(` Found ${providers.length} provider(s): ${providers.map(p => p.type).join(", ")}`);
210
- return true;
211
- }
212
-
213
- async function main() {
214
- const key = getPlatformKey();
215
- const platform = PLATFORMS[key];
216
- const fileName = `cc-code-${platform.suffix}.${platform.ext}`;
217
- const url = `${BASE_URL}/${fileName}`;
218
- const binDir = join(__dirname, "bin");
219
-
220
- if (!existsSync(binDir)) {
221
- mkdirSync(binDir, { recursive: true });
222
- }
223
-
224
- console.log(`Downloading cc-code ${VERSION} for ${platform.os}-${platform.arch}...`);
225
- console.log(` URL: ${url}`);
226
-
227
- const buffer = await download(url);
228
-
229
- if (platform.ext === "tar.gz") {
230
- extractTarGz(buffer, binDir);
231
- } else {
232
- extractZip(buffer, binDir);
233
- }
234
-
235
- const extractedName = platform.os === "win32"
236
- ? `cc-code-${platform.suffix}.exe`
237
- : `cc-code-${platform.suffix}`;
238
- const finalName = platform.os === "win32" ? "cc-code.exe" : "cc-code-bin";
239
- const extractedPath = join(binDir, extractedName);
240
- const finalPath = join(binDir, finalName);
241
-
242
- if (existsSync(extractedPath)) {
243
- if (existsSync(finalPath)) unlinkSync(finalPath);
244
- renameSync(extractedPath, finalPath);
245
- }
246
-
247
- if (platform.os !== "win32") {
248
- chmodSync(finalPath, 0o755);
249
- const wrapperPath = join(__dirname, "bin", "cc-code");
250
- if (existsSync(wrapperPath)) chmodSync(wrapperPath, 0o755);
251
- } else {
252
- // Generate Windows batch wrapper so npm's cc-code.cmd/ps1 can invoke it
253
- const binDirPath = join(__dirname, "bin");
254
- writeFileSync(join(binDirPath, "cc-code.cmd"), `@echo off\r\n"%~dp0cc-code.exe" %*\r\n`);
255
- writeFileSync(join(binDirPath, "cc-code.ps1"), `$basedir = Split-Path $MyInvocation.MyCommand.Definition -Parent\r\n& "$basedir\\cc-code.exe" @args\r\nexit $LASTEXITCODE\r\n`);
256
- }
257
-
258
- console.log(`cc-code ${VERSION} installed successfully.`);
259
-
260
- const migrated = migrateFromClaudeCode();
261
-
262
- if (!migrated) {
263
- console.log("");
264
- console.log("─── Quick Start ───");
265
- console.log("");
266
- console.log(" Set your API key (pick one):");
267
- console.log("");
268
- console.log(" # DeepSeek");
269
- console.log(" export OPENAI_API_KEY=sk-xxx");
270
- console.log(" export OPENAI_BASE_URL=https://api.deepseek.com/v1");
271
- console.log(" export OPENAI_MODEL=deepseek-chat");
272
- console.log("");
273
- console.log(" # Anthropic");
274
- console.log(" export ANTHROPIC_API_KEY=sk-ant-xxx");
275
- console.log("");
276
- console.log(" Or create config file:");
277
- console.log("");
278
- console.log(" mkdir -p ~/.cc-code");
279
- console.log(' cat > ~/.cc-code/settings.json << \'EOF\'');
280
- console.log(" {");
281
- console.log(' "config": {');
282
- console.log(' "providers": [');
283
- console.log(" {");
284
- console.log(' "type": "openai",');
285
- console.log(' "apiKey": "sk-xxx",');
286
- console.log(' "baseUrl": "https://api.deepseek.com/v1",');
287
- console.log(' "models": { "sonnet": "deepseek-chat" }');
288
- console.log(" }");
289
- console.log(" ]");
290
- console.log(" }");
291
- console.log(" }");
292
- console.log(" EOF");
293
- }
294
-
295
- console.log("");
296
- console.log(" Launch: cc-code");
297
- console.log(" Docs: https://github.com/cc-claws/cc-code");
298
- console.log("");
299
- }
300
-
301
- if (require.main === module) {
302
- main().catch((err) => {
303
- console.error("Failed to install cc-code:", err.message);
304
- process.exit(1);
305
- });
306
- }
307
-
308
- module.exports = { migrateFromClaudeCode };
1
+ #!/usr/bin/env node
2
+
3
+ const { createWriteStream, mkdirSync, chmodSync, existsSync, renameSync, unlinkSync, writeFileSync } = require("fs");
4
+ const { join } = require("path");
5
+ const { execSync } = require("child_process");
6
+
7
+ const { homedir } = require("os");
8
+
9
+ const VERSION = require("./package.json").version;
10
+ const REPO = "cc-claws/cc-code";
11
+ const BASE_URL = `https://github.com/${REPO}/releases/download/npm-v${VERSION}`;
12
+
13
+ const PLATFORMS = {
14
+ "linux-x64": { os: "linux", arch: "x64", suffix: "linux-x86_64", ext: "tar.gz" },
15
+ "linux-arm64": { os: "linux", arch: "arm64", suffix: "linux-aarch64", ext: "tar.gz" },
16
+ "darwin-x64": { os: "darwin", arch: "x64", suffix: "macos-x86_64", ext: "tar.gz" },
17
+ "darwin-arm64": { os: "darwin", arch: "arm64", suffix: "macos-aarch64", ext: "tar.gz" },
18
+ "win32-x64": { os: "win32", arch: "x64", suffix: "windows-x86_64", ext: "zip" },
19
+ };
20
+
21
+ function getPlatformKey() {
22
+ const key = `${process.platform}-${process.arch}`;
23
+ if (!PLATFORMS[key]) {
24
+ throw new Error(`Unsupported platform: ${key}. Supported: ${Object.keys(PLATFORMS).join(", ")}`);
25
+ }
26
+ return key;
27
+ }
28
+
29
+ function getProxyUrl() {
30
+ // Check common proxy environment variables
31
+ const proxy = process.env.HTTPS_PROXY || process.env.https_proxy
32
+ || process.env.HTTP_PROXY || process.env.http_proxy
33
+ || process.env.ALL_PROXY || process.env.all_proxy;
34
+ return proxy || null;
35
+ }
36
+
37
+ function download(url) {
38
+ const proxyUrl = getProxyUrl();
39
+
40
+ if (proxyUrl) {
41
+ return downloadViaProxy(url, proxyUrl);
42
+ }
43
+ return downloadDirect(url);
44
+ }
45
+
46
+ function downloadDirect(url) {
47
+ const { get } = require("https");
48
+ return new Promise((resolve, reject) => {
49
+ get(url, (res) => {
50
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
51
+ downloadDirect(res.headers.location).then(resolve, reject);
52
+ return;
53
+ }
54
+ if (res.statusCode !== 200) {
55
+ reject(new Error(`Download failed: HTTP ${res.statusCode} for ${url}`));
56
+ return;
57
+ }
58
+ const chunks = [];
59
+ res.on("data", (chunk) => chunks.push(chunk));
60
+ res.on("end", () => resolve(Buffer.concat(chunks)));
61
+ res.on("error", reject);
62
+ }).on("error", reject);
63
+ });
64
+ }
65
+
66
+ function downloadViaProxy(url, proxyUrl) {
67
+ const { URL } = require("url");
68
+ const target = new URL(url);
69
+ const proxy = new URL(proxyUrl);
70
+
71
+ const isHttps = proxy.protocol === "https:" || proxy.protocol === "HTTPS:";
72
+ const proxyModule = isHttps ? require("https") : require("http");
73
+
74
+ const proxyOpts = {
75
+ hostname: proxy.hostname,
76
+ port: proxy.port || (isHttps ? 443 : 80),
77
+ path: url,
78
+ method: "GET",
79
+ headers: { "Host": target.hostname, "User-Agent": "cc-code-installer" },
80
+ };
81
+
82
+ // Support proxy auth
83
+ if (proxy.username) {
84
+ const auth = decodeURIComponent(`${proxy.username}:${proxy.password || ""}`);
85
+ proxyOpts.headers["Proxy-Authorization"] = `Basic ${Buffer.from(auth).toString("base64")}`;
86
+ }
87
+
88
+ console.log(` Using proxy: ${proxy.hostname}:${proxy.port || (isHttps ? 443 : 80)}`);
89
+
90
+ return new Promise((resolve, reject) => {
91
+ const req = proxyModule.request(proxyOpts, (res) => {
92
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
93
+ download(res.headers.location).then(resolve, reject);
94
+ return;
95
+ }
96
+ if (res.statusCode !== 200) {
97
+ reject(new Error(`Download failed: HTTP ${res.statusCode} for ${url}`));
98
+ return;
99
+ }
100
+ const chunks = [];
101
+ res.on("data", (chunk) => chunks.push(chunk));
102
+ res.on("end", () => resolve(Buffer.concat(chunks)));
103
+ res.on("error", reject);
104
+ });
105
+ req.on("error", reject);
106
+ req.end();
107
+ });
108
+ }
109
+
110
+ function extractTarGz(buffer, dest) {
111
+ const tmpFile = join(dest, "cc-code.tar.gz");
112
+ writeFileSync(tmpFile, buffer);
113
+ execSync(`tar -xzf "${tmpFile}" -C "${dest}"`, { stdio: "ignore" });
114
+ unlinkSync(tmpFile);
115
+ }
116
+
117
+ function extractZip(buffer, dest) {
118
+ const AdmZip = require("adm-zip");
119
+ const zip = new AdmZip(buffer);
120
+ zip.extractAllTo(dest, true);
121
+ }
122
+
123
+ function migrateFromClaudeCode(home = homedir()) {
124
+ const claudeSettingsPath = join(home, ".claude", "settings.json");
125
+ const ccCodeDir = join(home, ".cc-code");
126
+ const ccCodeSettingsPath = join(ccCodeDir, "settings.json");
127
+
128
+ // 已有 cc-code 配置,跳过
129
+ if (existsSync(ccCodeSettingsPath)) {
130
+ return true;
131
+ }
132
+
133
+ // 无 Claude Code 配置
134
+ if (!existsSync(claudeSettingsPath)) {
135
+ return false;
136
+ }
137
+
138
+ let claudeSettings;
139
+ try {
140
+ claudeSettings = JSON.parse(require("fs").readFileSync(claudeSettingsPath, "utf-8"));
141
+ } catch {
142
+ return false;
143
+ }
144
+
145
+ const env = claudeSettings.env || {};
146
+ const providers = [];
147
+
148
+ // 检测 Anthropic
149
+ const anthropicKey = env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN || "";
150
+ const anthropicBaseUrl = env.ANTHROPIC_BASE_URL || "";
151
+ if (anthropicKey || anthropicBaseUrl) {
152
+ const models = {};
153
+ if (env.ANTHROPIC_DEFAULT_OPUS_MODEL) models.opus = env.ANTHROPIC_DEFAULT_OPUS_MODEL;
154
+ if (env.ANTHROPIC_DEFAULT_SONNET_MODEL) models.sonnet = env.ANTHROPIC_DEFAULT_SONNET_MODEL;
155
+ if (env.ANTHROPIC_DEFAULT_HAIKU_MODEL) models.haiku = env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
156
+ const p = {
157
+ id: "anthropic",
158
+ type: "anthropic",
159
+ apiKey: anthropicKey,
160
+ };
161
+ if (anthropicBaseUrl) p.baseUrl = anthropicBaseUrl;
162
+ if (Object.keys(models).length > 0) p.models = models;
163
+ providers.push(p);
164
+ }
165
+
166
+ // 检测 OpenAI 兼容
167
+ const openaiKey = env.OPENAI_API_KEY || env.CODEX_API_KEY || "";
168
+ const openaiBaseUrl = env.OPENAI_BASE_URL || env.OPENAI_API_BASE || "";
169
+ if (openaiKey || openaiBaseUrl) {
170
+ const models = {};
171
+ if (env.OPENAI_MODEL) models.sonnet = env.OPENAI_MODEL;
172
+ const p = {
173
+ id: "openai",
174
+ type: "openai",
175
+ apiKey: openaiKey,
176
+ };
177
+ if (openaiBaseUrl) p.baseUrl = openaiBaseUrl;
178
+ if (Object.keys(models).length > 0) p.models = models;
179
+ providers.push(p);
180
+ }
181
+
182
+ if (providers.length === 0) {
183
+ return false;
184
+ }
185
+
186
+ if (!existsSync(ccCodeDir)) {
187
+ mkdirSync(ccCodeDir, { recursive: true });
188
+ }
189
+
190
+ // 根据第一个 provider 的可用模型决定默认激活别名
191
+ const firstProvider = providers[0];
192
+ let activeAlias = "opus";
193
+ if (firstProvider.models) {
194
+ if (firstProvider.models.opus) activeAlias = "opus";
195
+ else if (firstProvider.models.sonnet) activeAlias = "sonnet";
196
+ else if (firstProvider.models.haiku) activeAlias = "haiku";
197
+ }
198
+
199
+ const ccCodeSettings = {
200
+ config: {
201
+ active_alias: activeAlias,
202
+ active_provider_id: firstProvider.id,
203
+ providers,
204
+ },
205
+ };
206
+ writeFileSync(ccCodeSettingsPath, JSON.stringify(ccCodeSettings, null, 2) + "\n");
207
+ console.log("");
208
+ console.log(" Migrated ~/.claude/settings.json -> ~/.cc-code/settings.json");
209
+ console.log(` Found ${providers.length} provider(s): ${providers.map(p => p.type).join(", ")}`);
210
+ return true;
211
+ }
212
+
213
+ async function main() {
214
+ const key = getPlatformKey();
215
+ const platform = PLATFORMS[key];
216
+ const fileName = `cc-code-${platform.suffix}.${platform.ext}`;
217
+ const url = `${BASE_URL}/${fileName}`;
218
+ const binDir = join(__dirname, "bin");
219
+
220
+ if (!existsSync(binDir)) {
221
+ mkdirSync(binDir, { recursive: true });
222
+ }
223
+
224
+ console.log(`Downloading cc-code ${VERSION} for ${platform.os}-${platform.arch}...`);
225
+ console.log(` URL: ${url}`);
226
+
227
+ const buffer = await download(url);
228
+
229
+ if (platform.ext === "tar.gz") {
230
+ extractTarGz(buffer, binDir);
231
+ } else {
232
+ extractZip(buffer, binDir);
233
+ }
234
+
235
+ const extractedName = platform.os === "win32"
236
+ ? `cc-code-${platform.suffix}.exe`
237
+ : `cc-code-${platform.suffix}`;
238
+ const finalName = platform.os === "win32" ? "cc-code.exe" : "cc-code-bin";
239
+ const extractedPath = join(binDir, extractedName);
240
+ const finalPath = join(binDir, finalName);
241
+
242
+ if (existsSync(extractedPath)) {
243
+ if (existsSync(finalPath)) unlinkSync(finalPath);
244
+ renameSync(extractedPath, finalPath);
245
+ }
246
+
247
+ if (platform.os !== "win32") {
248
+ chmodSync(finalPath, 0o755);
249
+ const wrapperPath = join(__dirname, "bin", "cc-code");
250
+ if (existsSync(wrapperPath)) chmodSync(wrapperPath, 0o755);
251
+ } else {
252
+ // Generate Windows batch wrapper so npm's cc-code.cmd/ps1 can invoke it
253
+ const binDirPath = join(__dirname, "bin");
254
+ writeFileSync(join(binDirPath, "cc-code.cmd"), `@echo off\r\n"%~dp0cc-code.exe" %*\r\n`);
255
+ writeFileSync(join(binDirPath, "cc-code.ps1"), `$basedir = Split-Path $MyInvocation.MyCommand.Definition -Parent\r\n& "$basedir\\cc-code.exe" @args\r\nexit $LASTEXITCODE\r\n`);
256
+ }
257
+
258
+ console.log(`cc-code ${VERSION} installed successfully.`);
259
+
260
+ const migrated = migrateFromClaudeCode();
261
+
262
+ if (!migrated) {
263
+ console.log("");
264
+ console.log("─── Quick Start ───");
265
+ console.log("");
266
+ console.log(" Set your API key (pick one):");
267
+ console.log("");
268
+ console.log(" # DeepSeek");
269
+ console.log(" export OPENAI_API_KEY=sk-xxx");
270
+ console.log(" export OPENAI_BASE_URL=https://api.deepseek.com/v1");
271
+ console.log(" export OPENAI_MODEL=deepseek-chat");
272
+ console.log("");
273
+ console.log(" # Anthropic");
274
+ console.log(" export ANTHROPIC_API_KEY=sk-ant-xxx");
275
+ console.log("");
276
+ console.log(" Or create config file:");
277
+ console.log("");
278
+ console.log(" mkdir -p ~/.cc-code");
279
+ console.log(' cat > ~/.cc-code/settings.json << \'EOF\'');
280
+ console.log(" {");
281
+ console.log(' "config": {');
282
+ console.log(' "providers": [');
283
+ console.log(" {");
284
+ console.log(' "type": "openai",');
285
+ console.log(' "apiKey": "sk-xxx",');
286
+ console.log(' "baseUrl": "https://api.deepseek.com/v1",');
287
+ console.log(' "models": { "sonnet": "deepseek-chat" }');
288
+ console.log(" }");
289
+ console.log(" ]");
290
+ console.log(" }");
291
+ console.log(" }");
292
+ console.log(" EOF");
293
+ }
294
+
295
+ console.log("");
296
+ console.log(" Launch: cc-code");
297
+ console.log(" Docs: https://github.com/cc-claws/cc-code");
298
+ console.log("");
299
+ }
300
+
301
+ if (require.main === module) {
302
+ main().catch((err) => {
303
+ console.error("Failed to install cc-code:", err.message);
304
+ process.exit(1);
305
+ });
306
+ }
307
+
308
+ module.exports = { migrateFromClaudeCode };
package/install.test.js CHANGED
@@ -1,125 +1,125 @@
1
- const { migrateFromClaudeCode } = require("./install");
2
-
3
- const fs = require("fs");
4
- const { join } = require("path");
5
- const os = require("os");
6
-
7
- function makeTempDir() {
8
- return fs.mkdtempSync(join(os.tmpdir(), "cc-code-install-test-"));
9
- }
10
-
11
- function cleanup(dir) {
12
- fs.rmSync(dir, { recursive: true, force: true });
13
- }
14
-
15
- function writeClaudeSettings(home, content) {
16
- const claudeDir = join(home, ".claude");
17
- fs.mkdirSync(claudeDir, { recursive: true });
18
- fs.writeFileSync(join(claudeDir, "settings.json"), JSON.stringify(content, null, 2));
19
- }
20
-
21
- function readCcCodeSettings(home) {
22
- return JSON.parse(fs.readFileSync(join(home, ".cc-code", "settings.json"), "utf-8"));
23
- }
24
-
25
- function test(name, fn) {
26
- const home = makeTempDir();
27
- try {
28
- fn(home);
29
- console.log(` ✓ ${name}`);
30
- } catch (e) {
31
- console.error(` ✗ ${name}`);
32
- console.error(e.message);
33
- process.exitCode = 1;
34
- } finally {
35
- cleanup(home);
36
- }
37
- }
38
-
39
- console.log("npm/install.js tests");
40
-
41
- test("migrateFromClaudeCode returns false when no claude settings exist", (home) => {
42
- const result = migrateFromClaudeCode(home);
43
- if (result !== false) throw new Error("expected false");
44
- });
45
-
46
- test("migrateFromClaudeCode skips when cc-code settings already exist", (home) => {
47
- const ccCodeDir = join(home, ".cc-code");
48
- fs.mkdirSync(ccCodeDir, { recursive: true });
49
- fs.writeFileSync(join(ccCodeDir, "settings.json"), JSON.stringify({ config: {} }));
50
- writeClaudeSettings(home, { env: { ANTHROPIC_API_KEY: "sk-ant" } });
51
- const result = migrateFromClaudeCode(home);
52
- if (result !== true) throw new Error("expected true");
53
- const content = fs.readFileSync(join(ccCodeDir, "settings.json"), "utf-8");
54
- if (content !== JSON.stringify({ config: {} })) {
55
- throw new Error("should not overwrite existing settings");
56
- }
57
- });
58
-
59
- test("migrateFromClaudeCode produces camelCase provider config for Anthropic", (home) => {
60
- writeClaudeSettings(home, {
61
- env: {
62
- ANTHROPIC_API_KEY: "sk-ant-xxx",
63
- ANTHROPIC_BASE_URL: "https://api.anthropic.com",
64
- ANTHROPIC_DEFAULT_OPUS_MODEL: "claude-opus-4-7",
65
- ANTHROPIC_DEFAULT_SONNET_MODEL: "claude-sonnet-4-6",
66
- ANTHROPIC_DEFAULT_HAIKU_MODEL: "claude-haiku-4-5",
67
- },
68
- });
69
- const result = migrateFromClaudeCode(home);
70
- if (result !== true) throw new Error("expected true");
71
- const cfg = readCcCodeSettings(home);
72
- if (cfg.config.active_alias !== "opus") throw new Error(`expected active_alias opus, got ${cfg.config.active_alias}`);
73
- if (cfg.config.active_provider_id !== "anthropic") throw new Error("expected active_provider_id anthropic");
74
- const p = cfg.config.providers[0];
75
- if (p.id !== "anthropic") throw new Error("expected provider id anthropic");
76
- if (p.type !== "anthropic") throw new Error("expected provider type anthropic");
77
- if (p.apiKey !== "sk-ant-xxx") throw new Error("expected apiKey");
78
- if (p.baseUrl !== "https://api.anthropic.com") throw new Error("expected baseUrl");
79
- if (p.provider_type !== undefined) throw new Error("provider_type (snake_case) should not exist");
80
- if (p.api_key !== undefined) throw new Error("api_key (snake_case) should not exist");
81
- if (p.base_url !== undefined) throw new Error("base_url (snake_case) should not exist");
82
- if (p.models.opus !== "claude-opus-4-7") throw new Error("expected models.opus");
83
- if (p.models.sonnet !== "claude-sonnet-4-6") throw new Error("expected models.sonnet");
84
- if (p.models.haiku !== "claude-haiku-4-5") throw new Error("expected models.haiku");
85
- });
86
-
87
- test("migrateFromClaudeCode produces camelCase provider config for OpenAI", (home) => {
88
- writeClaudeSettings(home, {
89
- env: {
90
- OPENAI_API_KEY: "sk-openai-xxx",
91
- OPENAI_BASE_URL: "https://api.deepseek.com/v1",
92
- OPENAI_MODEL: "deepseek-chat",
93
- },
94
- });
95
- const result = migrateFromClaudeCode(home);
96
- if (result !== true) throw new Error("expected true");
97
- const cfg = readCcCodeSettings(home);
98
- if (cfg.config.active_alias !== "sonnet") throw new Error(`expected active_alias sonnet, got ${cfg.config.active_alias}`);
99
- if (cfg.config.active_provider_id !== "openai") throw new Error("expected active_provider_id openai");
100
- const p = cfg.config.providers[0];
101
- if (p.id !== "openai") throw new Error("expected provider id openai");
102
- if (p.type !== "openai") throw new Error("expected provider type openai");
103
- if (p.apiKey !== "sk-openai-xxx") throw new Error("expected apiKey");
104
- if (p.baseUrl !== "https://api.deepseek.com/v1") throw new Error("expected baseUrl");
105
- if (p.models.sonnet !== "deepseek-chat") throw new Error("expected models.sonnet");
106
- });
107
-
108
- test("migrateFromClaudeCode supports CODEX_API_KEY fallback", (home) => {
109
- writeClaudeSettings(home, {
110
- env: {
111
- CODEX_API_KEY: "sk-codex-xxx",
112
- },
113
- });
114
- const result = migrateFromClaudeCode(home);
115
- if (result !== true) throw new Error("expected true");
116
- const cfg = readCcCodeSettings(home);
117
- if (cfg.config.providers[0].apiKey !== "sk-codex-xxx") throw new Error("expected CODEX_API_KEY to be used");
118
- });
119
-
120
- console.log("");
121
- if (process.exitCode) {
122
- console.log("Some tests failed.");
123
- } else {
124
- console.log("All tests passed.");
125
- }
1
+ const { migrateFromClaudeCode } = require("./install");
2
+
3
+ const fs = require("fs");
4
+ const { join } = require("path");
5
+ const os = require("os");
6
+
7
+ function makeTempDir() {
8
+ return fs.mkdtempSync(join(os.tmpdir(), "cc-code-install-test-"));
9
+ }
10
+
11
+ function cleanup(dir) {
12
+ fs.rmSync(dir, { recursive: true, force: true });
13
+ }
14
+
15
+ function writeClaudeSettings(home, content) {
16
+ const claudeDir = join(home, ".claude");
17
+ fs.mkdirSync(claudeDir, { recursive: true });
18
+ fs.writeFileSync(join(claudeDir, "settings.json"), JSON.stringify(content, null, 2));
19
+ }
20
+
21
+ function readCcCodeSettings(home) {
22
+ return JSON.parse(fs.readFileSync(join(home, ".cc-code", "settings.json"), "utf-8"));
23
+ }
24
+
25
+ function test(name, fn) {
26
+ const home = makeTempDir();
27
+ try {
28
+ fn(home);
29
+ console.log(` ✓ ${name}`);
30
+ } catch (e) {
31
+ console.error(` ✗ ${name}`);
32
+ console.error(e.message);
33
+ process.exitCode = 1;
34
+ } finally {
35
+ cleanup(home);
36
+ }
37
+ }
38
+
39
+ console.log("npm/install.js tests");
40
+
41
+ test("migrateFromClaudeCode returns false when no claude settings exist", (home) => {
42
+ const result = migrateFromClaudeCode(home);
43
+ if (result !== false) throw new Error("expected false");
44
+ });
45
+
46
+ test("migrateFromClaudeCode skips when cc-code settings already exist", (home) => {
47
+ const ccCodeDir = join(home, ".cc-code");
48
+ fs.mkdirSync(ccCodeDir, { recursive: true });
49
+ fs.writeFileSync(join(ccCodeDir, "settings.json"), JSON.stringify({ config: {} }));
50
+ writeClaudeSettings(home, { env: { ANTHROPIC_API_KEY: "sk-ant" } });
51
+ const result = migrateFromClaudeCode(home);
52
+ if (result !== true) throw new Error("expected true");
53
+ const content = fs.readFileSync(join(ccCodeDir, "settings.json"), "utf-8");
54
+ if (content !== JSON.stringify({ config: {} })) {
55
+ throw new Error("should not overwrite existing settings");
56
+ }
57
+ });
58
+
59
+ test("migrateFromClaudeCode produces camelCase provider config for Anthropic", (home) => {
60
+ writeClaudeSettings(home, {
61
+ env: {
62
+ ANTHROPIC_API_KEY: "sk-ant-xxx",
63
+ ANTHROPIC_BASE_URL: "https://api.anthropic.com",
64
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "claude-opus-4-7",
65
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "claude-sonnet-4-6",
66
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "claude-haiku-4-5",
67
+ },
68
+ });
69
+ const result = migrateFromClaudeCode(home);
70
+ if (result !== true) throw new Error("expected true");
71
+ const cfg = readCcCodeSettings(home);
72
+ if (cfg.config.active_alias !== "opus") throw new Error(`expected active_alias opus, got ${cfg.config.active_alias}`);
73
+ if (cfg.config.active_provider_id !== "anthropic") throw new Error("expected active_provider_id anthropic");
74
+ const p = cfg.config.providers[0];
75
+ if (p.id !== "anthropic") throw new Error("expected provider id anthropic");
76
+ if (p.type !== "anthropic") throw new Error("expected provider type anthropic");
77
+ if (p.apiKey !== "sk-ant-xxx") throw new Error("expected apiKey");
78
+ if (p.baseUrl !== "https://api.anthropic.com") throw new Error("expected baseUrl");
79
+ if (p.provider_type !== undefined) throw new Error("provider_type (snake_case) should not exist");
80
+ if (p.api_key !== undefined) throw new Error("api_key (snake_case) should not exist");
81
+ if (p.base_url !== undefined) throw new Error("base_url (snake_case) should not exist");
82
+ if (p.models.opus !== "claude-opus-4-7") throw new Error("expected models.opus");
83
+ if (p.models.sonnet !== "claude-sonnet-4-6") throw new Error("expected models.sonnet");
84
+ if (p.models.haiku !== "claude-haiku-4-5") throw new Error("expected models.haiku");
85
+ });
86
+
87
+ test("migrateFromClaudeCode produces camelCase provider config for OpenAI", (home) => {
88
+ writeClaudeSettings(home, {
89
+ env: {
90
+ OPENAI_API_KEY: "sk-openai-xxx",
91
+ OPENAI_BASE_URL: "https://api.deepseek.com/v1",
92
+ OPENAI_MODEL: "deepseek-chat",
93
+ },
94
+ });
95
+ const result = migrateFromClaudeCode(home);
96
+ if (result !== true) throw new Error("expected true");
97
+ const cfg = readCcCodeSettings(home);
98
+ if (cfg.config.active_alias !== "sonnet") throw new Error(`expected active_alias sonnet, got ${cfg.config.active_alias}`);
99
+ if (cfg.config.active_provider_id !== "openai") throw new Error("expected active_provider_id openai");
100
+ const p = cfg.config.providers[0];
101
+ if (p.id !== "openai") throw new Error("expected provider id openai");
102
+ if (p.type !== "openai") throw new Error("expected provider type openai");
103
+ if (p.apiKey !== "sk-openai-xxx") throw new Error("expected apiKey");
104
+ if (p.baseUrl !== "https://api.deepseek.com/v1") throw new Error("expected baseUrl");
105
+ if (p.models.sonnet !== "deepseek-chat") throw new Error("expected models.sonnet");
106
+ });
107
+
108
+ test("migrateFromClaudeCode supports CODEX_API_KEY fallback", (home) => {
109
+ writeClaudeSettings(home, {
110
+ env: {
111
+ CODEX_API_KEY: "sk-codex-xxx",
112
+ },
113
+ });
114
+ const result = migrateFromClaudeCode(home);
115
+ if (result !== true) throw new Error("expected true");
116
+ const cfg = readCcCodeSettings(home);
117
+ if (cfg.config.providers[0].apiKey !== "sk-codex-xxx") throw new Error("expected CODEX_API_KEY to be used");
118
+ });
119
+
120
+ console.log("");
121
+ if (process.exitCode) {
122
+ console.log("Some tests failed.");
123
+ } else {
124
+ console.log("All tests passed.");
125
+ }
package/package.json CHANGED
@@ -1,43 +1,43 @@
1
- {
2
- "name": "@cc-claw/code",
3
- "version": "0.6.48",
4
- "description": "cc-code \u2014 Terminal coding agent powered by open-source models \u2014 Rust-built, Claude Code compatible",
5
- "keywords": [
6
- "agent",
7
- "coding",
8
- "terminal",
9
- "rust",
10
- "llm",
11
- "claude-code"
12
- ],
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/cc-claws/cc-code.git"
16
- },
17
- "homepage": "https://github.com/cc-claws/cc-code",
18
- "bugs": {
19
- "url": "https://github.com/cc-claws/cc-code/issues"
20
- },
21
- "license": "Apache-2.0",
22
- "bin": {
23
- "cc-code": "bin/cc-code"
24
- },
25
- "scripts": {
26
- "postinstall": "node install.js"
27
- },
28
- "os": [
29
- "linux",
30
- "darwin",
31
- "win32"
32
- ],
33
- "cpu": [
34
- "x64",
35
- "arm64"
36
- ],
37
- "engines": {
38
- "node": ">=16"
39
- },
40
- "dependencies": {
41
- "adm-zip": "^0.5.16"
42
- }
43
- }
1
+ {
2
+ "name": "@cc-claw/code",
3
+ "version": "0.6.49",
4
+ "description": "cc-code Terminal coding agent powered by open-source models Rust-built, Claude Code compatible",
5
+ "keywords": [
6
+ "agent",
7
+ "coding",
8
+ "terminal",
9
+ "rust",
10
+ "llm",
11
+ "claude-code"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/cc-claws/cc-code.git"
16
+ },
17
+ "homepage": "https://github.com/cc-claws/cc-code",
18
+ "bugs": {
19
+ "url": "https://github.com/cc-claws/cc-code/issues"
20
+ },
21
+ "license": "Apache-2.0",
22
+ "bin": {
23
+ "cc-code": "bin/cc-code"
24
+ },
25
+ "scripts": {
26
+ "postinstall": "node install.js"
27
+ },
28
+ "os": [
29
+ "linux",
30
+ "darwin",
31
+ "win32"
32
+ ],
33
+ "cpu": [
34
+ "x64",
35
+ "arm64"
36
+ ],
37
+ "engines": {
38
+ "node": ">=16"
39
+ },
40
+ "dependencies": {
41
+ "adm-zip": "^0.5.16"
42
+ }
43
+ }