cc-wrapper 0.1.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 +294 -0
  2. package/dist/cli.js +329 -0
  3. package/package.json +53 -0
package/README.md ADDED
@@ -0,0 +1,294 @@
1
+ # cc-wrapper
2
+
3
+ > Cross-platform CLI wrapper for [Claude Code](https://claude.ai/code) with named environment profiles, arg passthrough, and zero config friction.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/cc-wrapper)](https://www.npmjs.com/package/cc-wrapper)
6
+ [![npm downloads](https://img.shields.io/npm/dm/cc-wrapper)](https://www.npmjs.com/package/cc-wrapper)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+ [![Node.js >= 20](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
9
+ [![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-blue)](#)
10
+
11
+ Switch between Claude environments — local proxies, OpenRouter, AWS Bedrock, Google Vertex AI — with a single command instead of exporting env vars every time.
12
+
13
+ ```bash
14
+ # Before cc-wrapper
15
+ ANTHROPIC_BASE_URL=http://localhost:8787 ANTHROPIC_AUTH_TOKEN=my-token claude
16
+
17
+ # After cc-wrapper
18
+ cc-wrapper claude
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Table of Contents
24
+
25
+ - [Features](#features)
26
+ - [Requirements](#requirements)
27
+ - [Installation](#installation)
28
+ - [Quick Start](#quick-start)
29
+ - [Commands](#commands)
30
+ - [Configuration](#configuration)
31
+ - [Supported Environment Variables](#supported-environment-variables)
32
+ - [Security](#security)
33
+ - [Contributing](#contributing)
34
+ - [License](#license)
35
+
36
+ ---
37
+
38
+ ## Features
39
+
40
+ - **Named profiles** — save multiple Claude environments (local, openrouter, vertex, bedrock, etc.)
41
+ - **Interactive setup** — searchable env var selector with descriptions and example values
42
+ - **Auto `--dangerously-skip-permissions`** — injected by default, disable with `--dd`
43
+ - **Full arg passthrough** — all extra args forwarded to `claude` unchanged
44
+ - **Cross-platform** — Windows, macOS, Linux; config stored at correct OS path automatically
45
+ - **Secret masking** — API keys and tokens masked in all log output
46
+ - **Live sync** — fetch latest Claude env vars from official docs with `npm run sync-envs`
47
+
48
+ ---
49
+
50
+ ## Requirements
51
+
52
+ - [Node.js](https://nodejs.org) >= 20
53
+ - [Claude Code CLI](https://claude.ai/code) installed and accessible as `claude` in PATH
54
+
55
+ ---
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ npm install -g cc-wrapper
61
+ ```
62
+
63
+ Verify:
64
+
65
+ ```bash
66
+ cc-wrapper --version
67
+ cc-wrapper --help
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Quick Start
73
+
74
+ **1. Create your first profile:**
75
+
76
+ ```bash
77
+ cc-wrapper new
78
+ ```
79
+
80
+ You will be prompted for:
81
+ - Profile name (e.g. `local`, `openrouter`, `vertex`)
82
+ - Which Claude env vars to set (searchable, numbered list with descriptions)
83
+ - Value for each selected env var (with examples shown inline)
84
+ - Default extra args to pass to `claude`
85
+
86
+ **2. Run Claude with the profile:**
87
+
88
+ ```bash
89
+ cc-wrapper claude
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Commands
95
+
96
+ ### `cc-wrapper new`
97
+
98
+ Create a new named profile interactively.
99
+
100
+ ```bash
101
+ cc-wrapper new
102
+ ```
103
+
104
+ Flow:
105
+ 1. Enter profile name
106
+ 2. Multi-select env vars from numbered list (SPACE to toggle, ENTER to confirm)
107
+ 3. Enter value for each selected var (example shown inline)
108
+ 4. Enter default claude args
109
+ 5. Profile saved — set as default if it's the first one
110
+
111
+ ---
112
+
113
+ ### `cc-wrapper list`
114
+
115
+ List all saved profiles. Default profile is highlighted.
116
+
117
+ ```bash
118
+ cc-wrapper list
119
+ ```
120
+
121
+ ```
122
+ ● local (default)
123
+ ○ openrouter
124
+ ○ vertex
125
+ ```
126
+
127
+ ---
128
+
129
+ ### `cc-wrapper default <name>`
130
+
131
+ Set the default profile used by `cc-wrapper claude`.
132
+
133
+ ```bash
134
+ cc-wrapper default openrouter
135
+ ```
136
+
137
+ ---
138
+
139
+ ### `cc-wrapper edit <name>`
140
+
141
+ Interactively edit an existing profile. Current values are pre-filled and masked.
142
+
143
+ ```bash
144
+ cc-wrapper edit local
145
+ ```
146
+
147
+ ---
148
+
149
+ ### `cc-wrapper delete <name>`
150
+
151
+ Delete a profile (confirmation prompt required).
152
+
153
+ ```bash
154
+ cc-wrapper delete vertex
155
+ ```
156
+
157
+ If the deleted profile was the default, the next available profile becomes the new default automatically.
158
+
159
+ ---
160
+
161
+ ### `cc-wrapper claude [args...]`
162
+
163
+ Launch `claude` with the default profile's environment variables injected.
164
+
165
+ ```bash
166
+ cc-wrapper claude
167
+ ```
168
+
169
+ **Pass extra args directly to claude:**
170
+
171
+ ```bash
172
+ cc-wrapper claude --print "explain this codebase"
173
+ cc-wrapper claude --model claude-opus-4-7-20250514
174
+ cc-wrapper claude --no-stream
175
+ ```
176
+
177
+ **Disable automatic `--dangerously-skip-permissions` injection:**
178
+
179
+ ```bash
180
+ cc-wrapper claude --dd
181
+ ```
182
+
183
+ **Use a specific profile instead of default:**
184
+
185
+ ```bash
186
+ cc-wrapper claude --profile openrouter
187
+ cc-wrapper claude -p vertex --print "hello"
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Configuration
193
+
194
+ Config is stored as a single JSON file at the OS-appropriate path:
195
+
196
+ | OS | Path |
197
+ |---|---|
198
+ | Linux | `~/.config/cc-wrapper/config.json` |
199
+ | macOS | `~/Library/Preferences/cc-wrapper/config.json` |
200
+ | Windows | `%APPDATA%\cc-wrapper\config.json` |
201
+
202
+ ### Config schema
203
+
204
+ ```json
205
+ {
206
+ "default": "local",
207
+ "configs": {
208
+ "local": {
209
+ "env": {
210
+ "ANTHROPIC_BASE_URL": "http://localhost:8787",
211
+ "ANTHROPIC_AUTH_TOKEN": "my-token"
212
+ },
213
+ "args": []
214
+ },
215
+ "openrouter": {
216
+ "env": {
217
+ "ANTHROPIC_BASE_URL": "https://openrouter.ai/api/v1",
218
+ "ANTHROPIC_API_KEY": "sk-or-..."
219
+ },
220
+ "args": ["--model", "anthropic/claude-opus-4"]
221
+ },
222
+ "vertex": {
223
+ "env": {
224
+ "CLAUDE_CODE_USE_VERTEX": "1",
225
+ "ANTHROPIC_VERTEX_PROJECT_ID": "my-gcp-project",
226
+ "CLOUD_ML_REGION": "us-central1"
227
+ },
228
+ "args": []
229
+ }
230
+ }
231
+ }
232
+ ```
233
+
234
+ You can edit this file directly or use `cc-wrapper edit <name>`.
235
+
236
+ ---
237
+
238
+ ## Supported Environment Variables
239
+
240
+ cc-wrapper supports all official Claude Code environment variables. The list is stored in `src/data/env-vars.json` and can be refreshed from the official docs:
241
+
242
+ ```bash
243
+ npm run sync-envs
244
+ ```
245
+
246
+ Key variables include:
247
+
248
+ | Variable | Description |
249
+ |---|---|
250
+ | `ANTHROPIC_API_KEY` | Anthropic API key |
251
+ | `ANTHROPIC_BASE_URL` | Custom API base URL (local proxy, OpenRouter, etc.) |
252
+ | `ANTHROPIC_AUTH_TOKEN` | Bearer token for custom auth |
253
+ | `ANTHROPIC_MODEL` | Override default model |
254
+ | `CLAUDE_CODE_USE_VERTEX` | Use Google Vertex AI |
255
+ | `CLAUDE_CODE_USE_BEDROCK` | Use AWS Bedrock |
256
+ | `AWS_REGION` | AWS region for Bedrock |
257
+ | `CLOUD_ML_REGION` | GCP region for Vertex |
258
+ | `HTTP_PROXY` / `HTTPS_PROXY` | Proxy settings |
259
+ | `CLAUDE_CODE_MAX_OUTPUT_TOKENS` | Override max output tokens |
260
+ | `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` | Disable telemetry |
261
+
262
+ Full list: run `cc-wrapper new` and browse the interactive selector.
263
+
264
+ ---
265
+
266
+ ## Security
267
+
268
+ - API keys and tokens are **never printed** in logs — always masked (e.g. `sk-a*****`)
269
+ - Config file lives in your user data directory, not in project folders
270
+ - No values are sent anywhere other than the `claude` process environment
271
+
272
+ ---
273
+
274
+ ## Contributing
275
+
276
+ ```bash
277
+ git clone https://github.com/aminechraibi/cc-wrapper
278
+ cd cc-wrapper
279
+ npm install
280
+ npm test
281
+ npm run build
282
+ ```
283
+
284
+ Refresh Claude env var list from official docs:
285
+
286
+ ```bash
287
+ npm run sync-envs
288
+ ```
289
+
290
+ ---
291
+
292
+ ## License
293
+
294
+ MIT — [Amine Chraibi](https://github.com/aminechraibi)
package/dist/cli.js ADDED
@@ -0,0 +1,329 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { cac } from "cac";
5
+ import chalk7 from "chalk";
6
+
7
+ // src/commands/new.ts
8
+ import { input as input2 } from "@inquirer/prompts";
9
+ import chalk from "chalk";
10
+
11
+ // src/config/store.ts
12
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
13
+ import { dirname } from "path";
14
+
15
+ // src/config/schema.ts
16
+ import { z } from "zod";
17
+ var ProfileSchema = z.object({
18
+ env: z.record(z.string(), z.string()),
19
+ args: z.array(z.string())
20
+ });
21
+ var ConfigSchema = z.object({
22
+ default: z.string().nullable(),
23
+ configs: z.record(z.string(), ProfileSchema)
24
+ });
25
+
26
+ // src/config/store.ts
27
+ var EMPTY_CONFIG = { default: null, configs: {} };
28
+ async function readConfig(path) {
29
+ if (!existsSync(path)) return structuredClone(EMPTY_CONFIG);
30
+ const raw = readFileSync(path, "utf-8");
31
+ const parsed = JSON.parse(raw);
32
+ return ConfigSchema.parse(parsed);
33
+ }
34
+ async function writeConfig(path, config) {
35
+ mkdirSync(dirname(path), { recursive: true });
36
+ writeFileSync(path, JSON.stringify(config, null, 2), "utf-8");
37
+ }
38
+
39
+ // src/config/paths.ts
40
+ import envPaths from "env-paths";
41
+ import { join } from "path";
42
+ var paths = envPaths("cc-wrapper", { suffix: "" });
43
+ function getConfigPath() {
44
+ if (process.env["CC_WRAPPER_CONFIG_PATH"]) {
45
+ return process.env["CC_WRAPPER_CONFIG_PATH"];
46
+ }
47
+ return join(paths.config, "config.json");
48
+ }
49
+
50
+ // src/prompts/env-selector.ts
51
+ import { checkbox, input } from "@inquirer/prompts";
52
+
53
+ // src/utils/mask.ts
54
+ var SECRET_PATTERN = /key|token|secret|password|credential/i;
55
+ function maskValue(value) {
56
+ if (value.length <= 4) return "*****";
57
+ return value.slice(0, 4) + "*****";
58
+ }
59
+ function maskEnv(env) {
60
+ return Object.fromEntries(
61
+ Object.entries(env).map(([k, v]) => [k, SECRET_PATTERN.test(k) ? maskValue(v) : v])
62
+ );
63
+ }
64
+
65
+ // src/prompts/env-selector.ts
66
+ var ENV_EXAMPLES = {
67
+ ANTHROPIC_API_KEY: "sk-ant-api03-...",
68
+ ANTHROPIC_BASE_URL: "http://localhost:8787",
69
+ ANTHROPIC_AUTH_TOKEN: "Bearer sk-ant-...",
70
+ ANTHROPIC_MODEL: "claude-opus-4-7-20250514",
71
+ ANTHROPIC_SMALL_FAST_MODEL: "claude-haiku-4-5-20251001",
72
+ ANTHROPIC_VERTEX_PROJECT_ID: "my-gcp-project-123",
73
+ CLOUD_ML_REGION: "us-central1",
74
+ AWS_REGION: "us-east-1",
75
+ AWS_ACCESS_KEY_ID: "AKIAIOSFODNN7EXAMPLE",
76
+ AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
77
+ HTTP_PROXY: "http://proxy.corp.com:3128",
78
+ HTTPS_PROXY: "http://proxy.corp.com:3128",
79
+ NO_PROXY: "localhost,127.0.0.1",
80
+ BASH_DEFAULT_TIMEOUT_MS: "30000",
81
+ BASH_MAX_TIMEOUT_MS: "120000",
82
+ BASH_MAX_OUTPUT_LENGTH: "50000",
83
+ CLAUDE_CODE_MAX_OUTPUT_TOKENS: "8192",
84
+ MCP_TIMEOUT: "10000",
85
+ MCP_TOOL_TIMEOUT: "60000"
86
+ };
87
+ async function promptEnvSelection(envVars, existing = {}) {
88
+ const selected = await checkbox({
89
+ message: "Select env vars (SPACE to toggle, ENTER to confirm):",
90
+ choices: envVars.map((v, i) => ({
91
+ name: `${String(i + 1).padStart(2)}. ${v.name}${existing[v.name] ? ` [${maskValue(existing[v.name])}]` : ""}`,
92
+ value: v.name,
93
+ checked: v.name in existing,
94
+ description: v.description
95
+ })),
96
+ pageSize: 15
97
+ });
98
+ const result = {};
99
+ for (const name of selected) {
100
+ const current = existing[name];
101
+ const example = ENV_EXAMPLES[name];
102
+ let hint = "";
103
+ if (current) hint = ` (current: ${maskValue(current)}, leave blank to keep)`;
104
+ else if (example) hint = ` (e.g. ${example})`;
105
+ const value = await input({
106
+ message: `${name}${hint}:`,
107
+ required: !current
108
+ });
109
+ result[name] = value || current || "";
110
+ }
111
+ return result;
112
+ }
113
+ async function promptArgs(existing = []) {
114
+ const raw = await input({
115
+ message: "Default claude args (space-separated, e.g. --print --model claude-opus-4-7):",
116
+ default: existing.join(" ")
117
+ });
118
+ return raw.trim() ? raw.trim().split(/\s+/) : [];
119
+ }
120
+
121
+ // src/data/env-vars.json
122
+ var env_vars_default = [
123
+ { name: "ANTHROPIC_API_KEY", description: "Anthropic API key for authentication" },
124
+ { name: "ANTHROPIC_BASE_URL", description: "Custom base URL for Anthropic API (e.g. local proxy)" },
125
+ { name: "ANTHROPIC_AUTH_TOKEN", description: "Bearer token for custom auth header" },
126
+ { name: "CLAUDE_CODE_USE_VERTEX", description: "Use Google Cloud Vertex AI instead of Anthropic API" },
127
+ { name: "ANTHROPIC_VERTEX_PROJECT_ID", description: "Google Cloud project ID for Vertex AI" },
128
+ { name: "CLOUD_ML_REGION", description: "Google Cloud region for Vertex AI" },
129
+ { name: "CLAUDE_CODE_USE_BEDROCK", description: "Use AWS Bedrock instead of Anthropic API" },
130
+ { name: "AWS_REGION", description: "AWS region for Bedrock" },
131
+ { name: "AWS_ACCESS_KEY_ID", description: "AWS access key ID for Bedrock auth" },
132
+ { name: "AWS_SECRET_ACCESS_KEY", description: "AWS secret access key for Bedrock auth" },
133
+ { name: "AWS_SESSION_TOKEN", description: "AWS session token for Bedrock auth" },
134
+ { name: "HTTP_PROXY", description: "HTTP proxy URL" },
135
+ { name: "HTTPS_PROXY", description: "HTTPS proxy URL" },
136
+ { name: "NO_PROXY", description: "Comma-separated hosts to bypass proxy" },
137
+ { name: "CLAUDE_CODE_MAX_OUTPUT_TOKENS", description: "Max output tokens for Claude responses" },
138
+ { name: "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", description: "Disable telemetry and version checks" },
139
+ { name: "ANTHROPIC_MODEL", description: "Override default Claude model" },
140
+ { name: "ANTHROPIC_SMALL_FAST_MODEL", description: "Override background task model" },
141
+ { name: "BASH_DEFAULT_TIMEOUT_MS", description: "Default bash command timeout in milliseconds" },
142
+ { name: "BASH_MAX_TIMEOUT_MS", description: "Maximum bash command timeout in milliseconds" },
143
+ { name: "BASH_MAX_OUTPUT_LENGTH", description: "Max characters of bash output before truncation" },
144
+ { name: "CLAUDE_CODE_API_KEY_HELPER_TTL_MS", description: "TTL for API key helper cache" },
145
+ { name: "CLAUDE_CODE_SKIP_BEDROCK_AUTH", description: "Skip Bedrock auth validation" },
146
+ { name: "CLAUDE_CODE_SKIP_VERTEX_AUTH", description: "Skip Vertex auth validation" },
147
+ { name: "MCP_TIMEOUT", description: "Timeout in ms for MCP server startup" },
148
+ { name: "MCP_TOOL_TIMEOUT", description: "Timeout in ms for individual MCP tool calls" }
149
+ ];
150
+
151
+ // src/commands/new.ts
152
+ async function newCommand() {
153
+ const configPath = getConfigPath();
154
+ const config = await readConfig(configPath);
155
+ const name = await input2({
156
+ message: "Profile name (e.g. local, openrouter, vertex, bedrock):",
157
+ validate: (v) => {
158
+ if (!v.trim()) return "Name required";
159
+ if (config.configs[v.trim()]) return `Profile "${v.trim()}" already exists`;
160
+ return true;
161
+ }
162
+ });
163
+ const profileName = name.trim();
164
+ const env = await promptEnvSelection(env_vars_default);
165
+ const args = await promptArgs();
166
+ config.configs[profileName] = { env, args };
167
+ if (!config.default) {
168
+ config.default = profileName;
169
+ console.log(chalk.dim(`Set "${profileName}" as default (first profile)`));
170
+ }
171
+ await writeConfig(configPath, config);
172
+ console.log(chalk.green(`\u2713 Profile "${profileName}" saved`));
173
+ }
174
+
175
+ // src/commands/list.ts
176
+ import chalk2 from "chalk";
177
+ async function listCommand() {
178
+ const config = await readConfig(getConfigPath());
179
+ const names = Object.keys(config.configs);
180
+ if (names.length === 0) {
181
+ console.log(chalk2.dim("No profiles. Run `cc-wrapper new` to create one."));
182
+ return;
183
+ }
184
+ for (const name of names) {
185
+ const isDefault = name === config.default;
186
+ const bullet = isDefault ? chalk2.green("\u25CF") : chalk2.dim("\u25CB");
187
+ const label = isDefault ? chalk2.bold(name) + chalk2.dim(" (default)") : name;
188
+ console.log(`${bullet} ${label}`);
189
+ }
190
+ }
191
+
192
+ // src/commands/default.ts
193
+ import chalk3 from "chalk";
194
+ async function defaultCommand(name) {
195
+ const configPath = getConfigPath();
196
+ const config = await readConfig(configPath);
197
+ if (!config.configs[name]) {
198
+ console.error(chalk3.red(`Profile "${name}" not found`));
199
+ process.exit(1);
200
+ }
201
+ config.default = name;
202
+ await writeConfig(configPath, config);
203
+ console.log(chalk3.green(`\u2713 Default set to "${name}"`));
204
+ }
205
+
206
+ // src/commands/edit.ts
207
+ import chalk4 from "chalk";
208
+ async function editCommand(name) {
209
+ const configPath = getConfigPath();
210
+ const config = await readConfig(configPath);
211
+ if (!config.configs[name]) {
212
+ console.error(chalk4.red(`Profile "${name}" not found`));
213
+ process.exit(1);
214
+ }
215
+ const profile = config.configs[name];
216
+ const env = await promptEnvSelection(env_vars_default, profile.env);
217
+ const args = await promptArgs(profile.args);
218
+ config.configs[name] = { env, args };
219
+ await writeConfig(configPath, config);
220
+ console.log(chalk4.green(`\u2713 Profile "${name}" updated`));
221
+ }
222
+
223
+ // src/commands/delete.ts
224
+ import { confirm } from "@inquirer/prompts";
225
+ import chalk5 from "chalk";
226
+ async function deleteCommand(name) {
227
+ const configPath = getConfigPath();
228
+ const config = await readConfig(configPath);
229
+ if (!config.configs[name]) {
230
+ console.error(chalk5.red(`Profile "${name}" not found`));
231
+ process.exit(1);
232
+ }
233
+ const ok = await confirm({ message: `Delete profile "${name}"?`, default: false });
234
+ if (!ok) {
235
+ console.log(chalk5.dim("Cancelled"));
236
+ return;
237
+ }
238
+ delete config.configs[name];
239
+ if (config.default === name) {
240
+ const remaining = Object.keys(config.configs);
241
+ config.default = remaining[0] ?? null;
242
+ if (config.default) {
243
+ console.log(chalk5.dim(`Default changed to "${config.default}"`));
244
+ }
245
+ }
246
+ await writeConfig(configPath, config);
247
+ console.log(chalk5.green(`\u2713 Profile "${name}" deleted`));
248
+ }
249
+
250
+ // src/commands/claude.ts
251
+ import chalk6 from "chalk";
252
+ import ora from "ora";
253
+
254
+ // src/utils/spawn.ts
255
+ import spawn from "cross-spawn";
256
+ function spawnClaude(args, env) {
257
+ return new Promise((resolve, reject) => {
258
+ const child = spawn("claude", args, {
259
+ stdio: "inherit",
260
+ shell: true,
261
+ env
262
+ });
263
+ child.on("close", (code) => resolve(code ?? 0));
264
+ child.on("error", reject);
265
+ });
266
+ }
267
+
268
+ // src/commands/claude.ts
269
+ var DANGEROUS = "--dangerously-skip-permissions";
270
+ function buildClaudeArgs(profileArgs, extraArgs, disableDangerous) {
271
+ const base = profileArgs.filter((a) => a !== DANGEROUS);
272
+ if (!disableDangerous) base.unshift(DANGEROUS);
273
+ return [...base, ...extraArgs];
274
+ }
275
+ async function claudeCommand(extraArgs, options) {
276
+ const configPath = getConfigPath();
277
+ const config = await readConfig(configPath);
278
+ const profileName = options.profile ?? config.default;
279
+ if (!profileName) {
280
+ console.error(chalk6.red("No default profile set. Run `cc-wrapper new` or `cc-wrapper default <name>`."));
281
+ process.exit(1);
282
+ }
283
+ const profile = config.configs[profileName];
284
+ if (!profile) {
285
+ console.error(chalk6.red(`Profile "${profileName}" not found`));
286
+ process.exit(1);
287
+ }
288
+ const args = buildClaudeArgs(profile.args, extraArgs, !!options.dd);
289
+ const env = { ...process.env, ...profile.env };
290
+ const spinner = ora(`Launching claude (profile: ${profileName})`).start();
291
+ console.log(chalk6.dim("env: " + JSON.stringify(maskEnv(profile.env))));
292
+ spinner.stop();
293
+ const code = await spawnClaude(args, env);
294
+ process.exit(code);
295
+ }
296
+
297
+ // src/cli.ts
298
+ process.on("SIGINT", () => {
299
+ process.stdout.write("\n");
300
+ process.exit(0);
301
+ });
302
+ process.on("uncaughtException", (err) => {
303
+ if (err.name === "ExitPromptError") {
304
+ process.stdout.write("\n");
305
+ process.exit(0);
306
+ }
307
+ console.error(chalk7.red(`Error: ${err.message}`));
308
+ process.exit(1);
309
+ });
310
+ process.on("unhandledRejection", (reason) => {
311
+ if (reason instanceof Error && reason.name === "ExitPromptError") {
312
+ process.stdout.write("\n");
313
+ process.exit(0);
314
+ }
315
+ console.error(chalk7.red(`Error: ${String(reason)}`));
316
+ process.exit(1);
317
+ });
318
+ var cli = cac("cc-wrapper");
319
+ cli.command("new", "Create a new profile").action(newCommand);
320
+ cli.command("list", "List all profiles").action(listCommand);
321
+ cli.command("default <name>", "Set default profile").action(defaultCommand);
322
+ cli.command("edit <name>", "Edit a profile interactively").action(editCommand);
323
+ cli.command("delete <name>", "Delete a profile").action(deleteCommand);
324
+ cli.command("claude [...args]", "Run claude with the default profile").option("--dd", "Disable --dangerously-skip-permissions injection").option("-p, --profile <name>", "Use a specific profile instead of default").action((args, options) => {
325
+ return claudeCommand(args, options);
326
+ });
327
+ cli.help();
328
+ cli.version("0.1.0");
329
+ cli.parse();
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "cc-wrapper",
3
+ "version": "0.1.0",
4
+ "description": "Cross-platform CLI wrapper for claude with reusable environment profiles",
5
+ "type": "module",
6
+ "bin": {
7
+ "cc-wrapper": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/cli.js",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "dev": "tsup --watch",
16
+ "start": "node dist/cli.js",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "sync-envs": "npx tsx scripts/sync-envs.ts",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": ["claude", "anthropic", "cli", "wrapper", "profiles"],
23
+ "author": "Amine Chraibi <aminechraibi2020@gmail.com>",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/aminechraibi/cc-wrapper"
28
+ },
29
+ "homepage": "https://github.com/aminechraibi/cc-wrapper#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/aminechraibi/cc-wrapper/issues"
32
+ },
33
+ "engines": {
34
+ "node": ">=20"
35
+ },
36
+ "dependencies": {
37
+ "@inquirer/prompts": "^7.5.0",
38
+ "cac": "^6.7.14",
39
+ "chalk": "^5.4.1",
40
+ "cross-spawn": "^7.0.6",
41
+ "env-paths": "^3.0.0",
42
+ "ora": "^8.2.0",
43
+ "zod": "^3.24.3"
44
+ },
45
+ "devDependencies": {
46
+ "@types/cross-spawn": "^6.0.6",
47
+ "@types/node": "^20.0.0",
48
+ "tsx": "^4.19.4",
49
+ "tsup": "^8.5.0",
50
+ "typescript": "^5.8.3",
51
+ "vitest": "^3.1.4"
52
+ }
53
+ }