claude-scionos 2.2.0 → 3.0.2

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/1.png ADDED
Binary file
package/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.0.2] - 2026-01-11
9
+
10
+ ### Added
11
+ - **Robust Proxy**: Integrated `undici` library for advanced HTTP agent control in the local proxy.
12
+ - **SSL Bypass**: Added support for internal/self-signed certificates (`rejectUnauthorized: false`) when using the proxy.
13
+
14
+ ### Fixed
15
+ - **Proxy Connectivity**: Fixed `fetch failed` and protocol errors by cleaning conflicting headers (`Host`, `Content-Length`) before upstream forwarding.
16
+ - **Code Quality**: Removed unused variables and dead code for cleaner execution.
17
+
18
+ ## [3.0.1] - 2026-01-11
19
+
20
+ ### Added
21
+ - **Model Mapping & Proxy**: Integrated local proxy to transparently map Claude models to **GLM-4.7** or **MiniMax-M2.1**.
22
+ - **Active Token Validation**: Now validates the `ANTHROPIC_AUTH_TOKEN` against the `routerlab.ch` API in real-time before launching.
23
+ - **Interactive Menu**: Added a selection menu at startup to choose the model strategy (Default vs Mapped).
24
+ - **Pro Branding**: New professional "ScioNos ✕ Claude Code" banner with corporate colors.
25
+
26
+ ### Improved
27
+ - **Error Handling**: Better distinction between missing executable (`ENOENT`) and permission errors (`EACCES`).
28
+ - **User Interface**: Clearer validation steps and visual feedback.
29
+
8
30
  ## [2.2.0] - 2026-01-06
9
31
 
10
32
  ### Added
package/README.fr.md CHANGED
@@ -41,11 +41,11 @@ L'objectif est d'offrir une couche d'exécution propre, isolée et professionnel
41
41
  ### 📌 Points clés
42
42
 
43
43
  - 🔒 **Isolation du jeton** — Le jeton d'authentification n'est jamais écrit sur le disque
44
+ - 🔄 **Mapping de Modèles** — Redirection transparente vers **GLM-4.7** ou **MiniMax-M2.1** via proxy local
44
45
  - 💾 **Zéro persistance** — Aucun fichier temporaire ni configuration locale stockés
45
46
  - 🧩 **Compatibilité totale** — Fonctionne parfaitement avec la CLI officielle Claude Code
46
47
  - 🔐 **Stockage en mémoire uniquement** — Toutes les informations d'identification sont détruites à la fin du processus
47
48
  - 🚀 **Démarrage rapide** — Exécution en une seule commande via `npx`
48
- - 🧪 **Prêt pour CI/CD** — Adapté aux workflows automatisés et pipelines
49
49
 
50
50
  ---
51
51
 
@@ -99,9 +99,13 @@ npx claude-scionos
99
99
  **Ce qui se passe :**
100
100
 
101
101
  1. L'outil vérifie si la CLI Claude Code est installée (si non, propose l'**installation automatique**)
102
- 2. Vous invite à saisir votre `ANTHROPIC_AUTH_TOKEN`
103
- 3. Lance Claude Code avec le jeton stocké **uniquement en mémoire**
104
- 4. Nettoie automatiquement les informations d'identification à la sortie
102
+ 2. Vous invite à saisir votre `ANTHROPIC_AUTH_TOKEN` et le valide instantanément
103
+ 3. **Menu de Sélection** : Vous choisissez la stratégie de modèle :
104
+ - *Default* : Utilise les modèles Anthropic (Opus/Sonnet/Haiku)
105
+ - *Force GLM-4.7* : Mappe toutes les requêtes vers GLM-4.7
106
+ - *Force MiniMax-M2.1* : Mappe toutes les requêtes vers MiniMax-M2.1
107
+ 4. Lance Claude Code (avec un proxy local transparent si un mapping est choisi)
108
+ 5. Nettoie automatiquement les informations d'identification à la sortie
105
109
 
106
110
  #### Débogage
107
111
 
@@ -148,7 +152,7 @@ Pour une liste complète des flags et commandes disponibles, consultez la [docum
148
152
  ### 🔍 Fonctionnement
149
153
 
150
154
  1. **Vérification** : Vérifie que la commande `claude` est disponible dans votre PATH
151
- 2. **Saisie du jeton** : Demande de manière sécurisée votre jeton d'authentification (entrée masquée)
155
+ 2. **Validation du jeton** : Demande et valide votre jeton en temps réel via l'API (assurant qu'il est fonctionnel avant le lancement)
152
156
  3. **Configuration de l'environnement** : Crée des variables d'environnement isolées :
153
157
  - `ANTHROPIC_BASE_URL` → `https://routerlab.ch`
154
158
  - `ANTHROPIC_AUTH_TOKEN` → Votre jeton (mémoire uniquement)
package/README.md CHANGED
@@ -41,11 +41,11 @@ The goal is to offer a clean, isolated, and professional execution layer fully c
41
41
  ### 📌 Key Features
42
42
 
43
43
  - 🔒 **Token Isolation** — Authentication token never written to disk
44
+ - 🔄 **Model Mapping** — Transparently route requests to **GLM-4.7** or **MiniMax-M2.1** via local proxy
44
45
  - 💾 **Zero Persistence** — No temporary files or local configuration stored
45
46
  - 🧩 **Full Compatibility** — Works seamlessly with the official Claude Code CLI
46
47
  - 🔐 **Memory-Only Storage** — All credentials destroyed on process exit
47
48
  - 🚀 **Quick Start** — Single command execution via `npx`
48
- - 🧪 **CI/CD Ready** — Suitable for automated workflows and pipelines
49
49
 
50
50
  ---
51
51
 
@@ -99,9 +99,13 @@ npx claude-scionos
99
99
  **What happens:**
100
100
 
101
101
  1. Checks if Claude Code CLI is installed (if not, offers **automatic installation**)
102
- 2. Prompts for your `ANTHROPIC_AUTH_TOKEN`
103
- 3. Launches Claude Code with token stored **in memory only**
104
- 4. Automatically cleans credentials on exit
102
+ 2. Prompts for your `ANTHROPIC_AUTH_TOKEN` and validates it instantly
103
+ 3. **Selection Menu**: Choose your model strategy:
104
+ - *Default*: Use standard Anthropic models (Opus/Sonnet/Haiku)
105
+ - *Force GLM-4.7*: Maps all requests to GLM-4.7
106
+ - *Force MiniMax-M2.1*: Maps all requests to MiniMax-M2.1
107
+ 4. Launches Claude Code (starting a transparent local proxy if needed)
108
+ 5. Automatically cleans credentials on exit
105
109
 
106
110
  #### Debugging
107
111
 
@@ -136,7 +140,7 @@ For a complete list of available flags and commands, see the [official Claude Co
136
140
  ### 🔍 How It Works
137
141
 
138
142
  1. **Verification**: Checks if `claude` command is available in your PATH
139
- 2. **Token Input**: Securely prompts for your authentication token (masked input)
143
+ 2. **Token Validation**: Prompts for and validates your token in real-time via the API (ensuring it works before launch)
140
144
  3. **Environment Setup**: Creates isolated environment variables:
141
145
  - `ANTHROPIC_BASE_URL` → `https://routerlab.ch`
142
146
  - `ANTHROPIC_AUTH_TOKEN` → Your token (memory only)
package/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import chalk from 'chalk';
4
- import { password, confirm } from '@inquirer/prompts';
4
+ import { password, confirm, select } from '@inquirer/prompts';
5
5
  import spawn from 'cross-spawn';
6
6
  import updateNotifier from 'update-notifier';
7
7
  import process from 'node:process';
8
- import { execSync } from 'node:child_process';
8
+ import http from 'node:http';
9
9
  import { createRequire } from 'node:module';
10
10
  import { isClaudeCodeInstalled, detectOS, checkGitBashOnWindows, getInstallationInstructions } from './src/detectors/claude-only.js';
11
11
 
@@ -15,6 +15,196 @@ const pkg = require('./package.json');
15
15
  // Initialize update notifier
16
16
  updateNotifier({ pkg }).notify();
17
17
 
18
+ // --- CONFIGURATION ---
19
+ const BASE_URL = "https://routerlab.ch";
20
+
21
+ // --- UTILS ---
22
+
23
+ /**
24
+ * Displays the application banner
25
+ */
26
+ function showBanner() {
27
+ console.clear();
28
+ const p = chalk.hex('#3b82f6'); // Primary (Scio)
29
+ const s = chalk.hex('#a855f7'); // Secondary (Nos)
30
+ const c = chalk.hex('#D97757'); // Claude Orange
31
+ const w = chalk.white;
32
+ const g = chalk.gray;
33
+ const border = g; // Gray border
34
+
35
+ console.log("");
36
+ console.log(border(" ┌──────────────────────────────────────────────────────────┐"));
37
+ console.log(border(" │ │"));
38
+ console.log(border(" │ ") + p.bold("Scio") + s.bold("Nos") + w.bold(" ✕ ") + c.bold("Claude Code") + border(" │"));
39
+ console.log(border(" │ │"));
40
+ console.log(border(" └──────────────────────────────────────────────────────────┘"));
41
+ console.log(g(` v${pkg.version}`));
42
+ console.log("");
43
+ }
44
+
45
+ /**
46
+ * Validates the API Token against RouterLab
47
+ */
48
+ async function validateToken(apiKey) {
49
+ try {
50
+ const response = await fetch(`${BASE_URL}/v1/models`, {
51
+ method: 'GET',
52
+ headers: {
53
+ 'x-api-key': apiKey,
54
+ 'anthropic-version': '2023-06-01'
55
+ }
56
+ });
57
+
58
+ if (response.ok) {
59
+ return { valid: true };
60
+ } else if (response.status === 401 || response.status === 403) {
61
+ return { valid: false, reason: 'auth_failed' };
62
+ } else {
63
+ return { valid: false, reason: 'server_error', status: response.status, statusText: response.statusText };
64
+ }
65
+ } catch (error) {
66
+ return { valid: false, reason: 'network_error', message: error.message };
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Starts a local proxy server to map models
72
+ * @param {string} targetModel - The model ID to map to (e.g., 'glm-4.7')
73
+ * @param {string} validToken - The validated Auth Token
74
+ * @returns {Promise<string>} - The local URL (e.g., http://127.0.0.1:45321)
75
+ */
76
+ function startProxyServer(targetModel, validToken) {
77
+ return new Promise((resolve, reject) => {
78
+ const server = http.createServer(async (req, res) => {
79
+ // Handle CORS preflight if necessary (usually strict for CLI tools but good practice)
80
+ if (req.method === 'OPTIONS') {
81
+ res.writeHead(200, {
82
+ 'Access-Control-Allow-Origin': '*',
83
+ 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
84
+ 'Access-Control-Allow-Headers': '*'
85
+ });
86
+ res.end();
87
+ return;
88
+ }
89
+
90
+ // We only care about interception if it's a message generation request
91
+ // Claude Code uses /v1/messages
92
+ if (req.method === 'POST' && req.url.includes('/messages')) {
93
+ const chunks = [];
94
+ req.on('data', chunk => chunks.push(chunk));
95
+ req.on('end', async () => {
96
+ try {
97
+ const bodyBuffer = Buffer.concat(chunks);
98
+ let bodyJson;
99
+ try {
100
+ bodyJson = JSON.parse(bodyBuffer.toString());
101
+ } catch {
102
+ // If not JSON, forward as is
103
+ bodyJson = null;
104
+ }
105
+
106
+ // THE MAGIC: Swap the model
107
+ if (bodyJson && bodyJson.model) {
108
+ // Map any Claude model to our target
109
+ // Claude Code usually requests 'claude-3-opus-...' or 'claude-3-5-sonnet...'
110
+ // We force the target.
111
+ if (process.argv.includes('--scionos-debug')) {
112
+ console.log(chalk.yellow(`[Proxy] Swapping model ${bodyJson.model} -> ${targetModel}`));
113
+ }
114
+ bodyJson.model = targetModel;
115
+ }
116
+
117
+ // Prepare upstream request
118
+ // 1. Create an agent that ignores SSL errors (CRITICAL for internal/testing environments)
119
+ const { Agent } = await import('undici');
120
+ const dispatcher = new Agent({
121
+ connect: {
122
+ rejectUnauthorized: false // WARNING: Ignores SSL certificate errors
123
+ }
124
+ });
125
+
126
+ // 2. Remove problematic headers that fetch handles automatically
127
+ const upstreamHeaders = { ...req.headers };
128
+ delete upstreamHeaders['host']; // Let fetch set the correct Host based on URL
129
+ delete upstreamHeaders['content-length']; // Let fetch calculate length based on body
130
+ upstreamHeaders['x-api-key'] = validToken;
131
+
132
+ // 3. Execute request with the permissive dispatcher
133
+ const upstreamRes = await fetch(`${BASE_URL}${req.url}`, {
134
+ method: 'POST',
135
+ headers: upstreamHeaders,
136
+ body: bodyJson ? JSON.stringify(bodyJson) : bodyBuffer,
137
+ dispatcher: dispatcher // <--- Apply the custom agent here
138
+ });
139
+
140
+ // Pipe response back
141
+ res.writeHead(upstreamRes.status, upstreamRes.headers);
142
+ if (upstreamRes.body) {
143
+ // @ts-ignore - Node fetch body is iterable
144
+ for await (const chunk of upstreamRes.body) {
145
+ res.write(chunk);
146
+ }
147
+ }
148
+ res.end();
149
+
150
+ } catch (error) {
151
+ console.error(chalk.red(`[Proxy Error] ${error.message}`));
152
+ res.writeHead(500);
153
+ res.end(JSON.stringify({ error: { message: "Scionos Proxy Error" } }));
154
+ }
155
+ });
156
+ } else {
157
+ // Passthrough for other endpoints (like /models potentially)
158
+ // In a simple CLI usage, direct passthrough might be enough,
159
+ // but since we changed the BASE_URL, we must forward everything.
160
+ // For simplicity in this script, we assume most traffic is POST /messages.
161
+ // If Claude Code calls GET /models, we should forward it too.
162
+
163
+ // Simple Redirect implementation for non-body requests
164
+ try {
165
+ // 1. Create agent (SSL bypass)
166
+ const { Agent } = await import('undici');
167
+ const dispatcher = new Agent({ connect: { rejectUnauthorized: false } });
168
+
169
+ // 2. Clean headers
170
+ const upstreamHeaders = { ...req.headers };
171
+ delete upstreamHeaders['host'];
172
+ delete upstreamHeaders['content-length'];
173
+ upstreamHeaders['x-api-key'] = validToken;
174
+
175
+ const upstreamRes = await fetch(`${BASE_URL}${req.url}`, {
176
+ method: req.method,
177
+ headers: upstreamHeaders,
178
+ dispatcher: dispatcher
179
+ });
180
+ res.writeHead(upstreamRes.status, upstreamRes.headers);
181
+ if (upstreamRes.body) {
182
+ // @ts-ignore
183
+ for await (const chunk of upstreamRes.body) {
184
+ res.write(chunk);
185
+ }
186
+ }
187
+ res.end();
188
+ } catch {
189
+ res.writeHead(502);
190
+ res.end();
191
+ }
192
+ }
193
+ });
194
+
195
+ // Listen on random port
196
+ server.listen(0, '127.0.0.1', () => {
197
+ const address = server.address();
198
+ const port = address.port;
199
+ resolve({ server, url: `http://127.0.0.1:${port}` });
200
+ });
201
+
202
+ server.on('error', (err) => reject(err));
203
+ });
204
+ }
205
+
206
+ // --- MAIN EXECUTION ---
207
+
18
208
  // 0. Handle Flags
19
209
  if (process.argv.includes('--version') || process.argv.includes('-v')) {
20
210
  console.log(pkg.version);
@@ -23,166 +213,171 @@ if (process.argv.includes('--version') || process.argv.includes('-v')) {
23
213
 
24
214
  const isDebug = process.argv.includes('--scionos-debug');
25
215
 
26
- // 1. Enhanced System Detection
27
- console.log(chalk.cyan('🔍 Checking system configuration...'));
216
+ // 1. Show Banner
217
+ showBanner();
28
218
 
29
- // Detect OS and environment
219
+ // 2. System Check
220
+ if (isDebug) console.log(chalk.cyan('🔍 Checking system configuration...'));
30
221
  const osInfo = detectOS();
31
- console.log(chalk.gray(`✓ OS: ${osInfo.type} (${osInfo.arch})`));
32
- console.log(chalk.gray(`✓ Shell: ${osInfo.shell}`));
33
-
34
- // Check Claude Code installation
35
222
  let claudeStatus = isClaudeCodeInstalled();
36
223
 
224
+ // Check Claude Code installation
37
225
  if (!claudeStatus.installed) {
38
226
  console.error(chalk.redBright('\n❌ Claude Code CLI not found'));
39
-
40
- // Show detailed detection info
41
- console.error(chalk.yellow('\nDetection Details:'));
42
- console.error(chalk.gray(claudeStatus.details));
43
- console.log(''); // spacer
44
-
45
227
  const shouldInstall = await confirm({
46
- message: 'Claude Code CLI is not installed. Do you want to install it via npm now?',
228
+ message: 'Claude Code CLI is not installed. Install globally via npm?',
47
229
  default: true
48
230
  });
49
231
 
50
232
  if (shouldInstall) {
51
233
  try {
52
- console.log(chalk.cyan('\n📦 Installing @anthropic-ai/claude-code globally...'));
53
- execSync('npm install -g @anthropic-ai/claude-code', { stdio: 'inherit' });
54
- console.log(chalk.green('✓ Installation successful!'));
55
-
56
- // Re-detect to get the new path
234
+ console.log(chalk.cyan('\n📦 Installing @anthropic-ai/claude-code...'));
235
+ spawn.sync('npm', ['install', '-g', '@anthropic-ai/claude-code'], { stdio: 'inherit' });
57
236
  claudeStatus = isClaudeCodeInstalled();
58
-
59
237
  if (!claudeStatus.installed) {
60
- // Fallback if install succeeded but path isn't picked up immediately
61
- console.warn(chalk.yellow('⚠ Installation finished, but the executable was not found in the standard paths immediately.'));
62
- console.warn(chalk.yellow('You may need to restart your terminal.'));
238
+ console.warn(chalk.yellow('⚠ Installation finished, but executable not found immediately. Restart terminal recommended.'));
63
239
  process.exit(0);
64
240
  }
65
241
  } catch (e) {
66
242
  console.error(chalk.red(`\n❌ Installation failed: ${e.message}`));
67
- console.error(chalk.yellow('Please try installing manually using the instructions below.'));
68
-
69
- const instructions = getInstallationInstructions(osInfo, claudeStatus);
70
- console.error(chalk.cyan(instructions));
243
+ console.error(chalk.cyan(getInstallationInstructions(osInfo)));
71
244
  process.exit(1);
72
245
  }
73
246
  } else {
74
- // User declined install
75
- const instructions = getInstallationInstructions(osInfo, claudeStatus);
76
- console.error(chalk.cyan(instructions));
247
+ console.error(chalk.cyan(getInstallationInstructions(osInfo)));
77
248
  process.exit(1);
78
249
  }
79
250
  }
80
251
 
81
- // Show Claude Code status
82
- console.log(chalk.green('\n✓ Claude Code detected'));
83
- console.log(chalk.gray(claudeStatus.details));
84
-
85
- // 2. Check Git Bash on Windows (if needed)
86
- let gitBashPath = null;
252
+ // Check Git Bash on Windows
87
253
  if (process.platform === 'win32') {
88
- console.log(chalk.cyan('\n🔍 Checking Git Bash availability...'));
89
254
  const gitBashStatus = checkGitBashOnWindows();
90
-
91
255
  if (!gitBashStatus.available) {
92
256
  console.log(chalk.red('\n❌ Git Bash is required on Windows'));
93
- console.log(chalk.gray(gitBashStatus.message));
94
-
95
- // Show Git Bash installation instructions
96
- console.log(chalk.cyan('\n📥 Install Git for Windows:'));
97
- console.log(chalk.white(' https://git-scm.com/downloads/win\n'));
98
- console.log(chalk.cyan('⚙️ Or set the path manually:'));
99
- console.log(chalk.white(' set CLAUDE_CODE_GIT_BASH_PATH=C:\\Program Files\\Git\\bin\\bash.exe'));
100
- console.log(chalk.white(' (PowerShell: $env:CLAUDE_CODE_GIT_BASH_PATH="C:\\Program Files\\Git\\bin\\bash.exe")\n'));
101
- console.log(chalk.yellow('💡 After installation, restart your terminal and try again.\n'));
257
+ console.log(chalk.cyan('Please install Git for Windows: https://git-scm.com/downloads/win'));
102
258
  process.exit(1);
259
+ }
260
+ }
261
+
262
+ // 3. Token Loop
263
+ let token = "";
264
+ while (true) {
265
+ console.log(chalk.blueBright("To retrieve your token, visit: https://routerlab.ch/keys"));
266
+ token = await password({
267
+ message: "Please enter your ANTHROPIC_AUTH_TOKEN:",
268
+ mask: '*'
269
+ });
270
+
271
+ console.log(chalk.gray("Validating token..."));
272
+ const validation = await validateToken(token);
273
+
274
+ if (validation.valid) {
275
+ console.log(chalk.green("✓ Token validated."));
276
+ break;
277
+ } else if (validation.reason === 'auth_failed') {
278
+ console.log(chalk.red("❌ Invalid token (401/403). Try again."));
103
279
  } else {
104
- console.log(chalk.green('✓ Git Bash available'));
105
- console.log(chalk.gray(gitBashStatus.message));
106
- gitBashPath = gitBashStatus.path;
280
+ console.log(chalk.yellow(`⚠ Validation warning: ${validation.message || validation.status}`));
281
+ const ignore = await confirm({ message: "Continue anyway?", default: false });
282
+ if (ignore) break;
107
283
  }
108
284
  }
109
285
 
110
- // 3. Intro
111
- console.clear();
112
- console.log(chalk.cyan.bold("Claude Code (via ScioNos)"));
113
- console.log(chalk.gray(`Running on ${osInfo.type} with ${osInfo.shell}`));
114
- if (isDebug) console.log(chalk.yellow("🐞 Debug Mode Active"));
115
-
116
- // 4. Token info
117
- console.log(chalk.blueBright("To retrieve your token, visit: https://routerlab.ch/keys"));
118
-
119
- // 5. Token input
120
- const token = await password({
121
- message: "Please enter your ANTHROPIC_AUTH_TOKEN:",
122
- validate: (input) => {
123
- if (!input || input.trim() === '') {
124
- return "Token cannot be empty.";
286
+ // 4. Model Selection
287
+ const modelChoice = await select({
288
+ message: 'Select Model Strategy:',
289
+ choices: [
290
+ {
291
+ name: 'Default (Use Claude Opus/Sonnet/Haiku natively)',
292
+ value: 'default',
293
+ description: 'Standard behavior. Claude decides which model to use.'
294
+ },
295
+ {
296
+ name: 'Force GLM-4.7 (Map all models to GLM-4.7)',
297
+ value: 'glm-4.7',
298
+ description: 'Intercepts traffic and routes everything to GLM-4.7'
299
+ },
300
+ {
301
+ name: 'Force MiniMax-M2.1 (Map all models to MiniMax)',
302
+ value: 'minimax-m2.1',
303
+ description: 'Intercepts traffic and routes everything to MiniMax-M2.1'
125
304
  }
126
- return true;
127
- },
128
- mask: '*'
305
+ ]
129
306
  });
130
307
 
131
- // 6. Environment configuration
308
+ // 5. Setup Environment & Proxy
309
+ let finalBaseUrl = BASE_URL;
310
+ let proxyServer = null;
311
+
312
+ if (modelChoice !== 'default') {
313
+ console.log(chalk.magenta(`\n🔮 Starting Local Proxy to map models to ${chalk.bold(modelChoice)}...`));
314
+ try {
315
+ const proxyInfo = await startProxyServer(modelChoice, token);
316
+ proxyServer = proxyInfo.server;
317
+ finalBaseUrl = proxyInfo.url; // e.g. http://127.0.0.1:54321
318
+ if (isDebug) console.log(chalk.gray(`✓ Proxy listening on ${finalBaseUrl}`));
319
+ } catch (err) {
320
+ console.error(chalk.red(`Failed to start proxy: ${err.message}`));
321
+ process.exit(1);
322
+ }
323
+ }
324
+
132
325
  const env = {
133
326
  ...process.env,
134
- ANTHROPIC_BASE_URL: "https://routerlab.ch",
327
+ ANTHROPIC_BASE_URL: finalBaseUrl,
135
328
  ANTHROPIC_AUTH_TOKEN: token,
136
- ANTHROPIC_API_KEY: "" // Force empty string
329
+ ANTHROPIC_API_KEY: "" // Force empty
137
330
  };
138
331
 
139
- // 7. Launch Claude Code
140
- // Filter out our internal flag before passing args to Claude
332
+ // 6. Launch Claude
141
333
  const args = process.argv.slice(2).filter(arg => arg !== '--scionos-debug');
142
-
143
334
  if (isDebug) {
144
335
  console.log(chalk.yellow('\n--- DEBUG INFO ---'));
145
- console.log(chalk.gray(`Platform: ${process.platform}`));
146
- console.log(chalk.gray(`Claude Command: claude ${args.join(' ')}`));
147
- console.log(chalk.gray(`Router URL: ${env.ANTHROPIC_BASE_URL}`));
148
- console.log(chalk.gray(`Token Length: ${token.length} chars`));
149
- if (gitBashPath) console.log(chalk.gray(`Git Bash: ${gitBashPath}`));
336
+ console.log(chalk.gray(`Endpoint: ${env.ANTHROPIC_BASE_URL}`));
337
+ console.log(chalk.gray(`Model Strategy: ${modelChoice}`));
150
338
  console.log(chalk.yellow('------------------\n'));
151
339
  }
152
340
 
341
+ console.log(chalk.green(`\n🚀 Launching Claude Code [${modelChoice}]...\n`));
342
+
153
343
  const child = spawn(claudeStatus.cliPath, args, {
154
344
  stdio: 'inherit',
155
345
  env: env
156
346
  });
157
347
 
158
- // Signal Handling
159
- // We intentionally ignore SIGINT in the parent process.
160
- // Because stdio is 'inherit', the child process (Claude) receives the Ctrl+C (SIGINT) directly from the TTY.
161
- process.on('SIGINT', () => {
162
- if (isDebug) {
163
- console.error(chalk.yellow('\n[Wrapper] Received SIGINT. Waiting for Claude to exit...'));
348
+ // 7. Cleanup Handlers
349
+ const cleanup = () => {
350
+ if (proxyServer) {
351
+ if (isDebug) console.log(chalk.gray('\nStopping proxy server...'));
352
+ proxyServer.close();
164
353
  }
354
+ };
355
+
356
+ child.on('exit', (code) => {
357
+ cleanup();
358
+ process.exit(code);
165
359
  });
166
360
 
167
- // Handle SIGTERM (e.g., Docker stop, kill)
168
- process.on('SIGTERM', () => {
169
- if (isDebug) {
170
- console.error(chalk.yellow('\n[Wrapper] Received SIGTERM. Forwarding to Claude...'));
171
- }
172
- if (child) {
173
- child.kill('SIGTERM');
361
+ child.on('error', (err) => {
362
+ cleanup();
363
+ console.error(chalk.red(`\n❌ Error launching Claude CLI:`));
364
+ if (err.code === 'ENOENT') {
365
+ console.error(chalk.yellow(` Executable not found. Try 'npm install -g @anthropic-ai/claude-code'`));
366
+ } else if (err.code === 'EACCES') {
367
+ console.error(chalk.yellow(` Permission denied.`));
368
+ } else {
369
+ console.error(chalk.yellow(` ${err.message}`));
174
370
  }
175
- process.exit(0);
371
+ process.exit(1);
176
372
  });
177
373
 
178
- child.on('close', (code) => {
179
- if (isDebug) {
180
- console.error(chalk.yellow(`\n[Wrapper] Child process exited with code ${code}`));
181
- }
182
- process.exit(code);
374
+ process.on('SIGINT', () => {
375
+ // Child handles SIGINT usually, but we ensure cleanup if wrapper is killed
376
+ // We don't exit here immediately to let Claude handle the interrupt
183
377
  });
184
378
 
185
- child.on('error', (err) => {
186
- console.error(chalk.red(`Error launching Claude at ${claudeStatus.cliPath}: ${err.message}`));
187
- process.exit(1);
379
+ process.on('SIGTERM', () => {
380
+ if (child) child.kill('SIGTERM');
381
+ cleanup();
382
+ process.exit(0);
188
383
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-scionos",
3
- "version": "2.2.0",
3
+ "version": "3.0.2",
4
4
  "description": "Ephemeral and secure runner for Claude Code CLI in ScioNos environment",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -38,6 +38,7 @@
38
38
  "@inquirer/prompts": "^8.1.0",
39
39
  "chalk": "^5.6.2",
40
40
  "cross-spawn": "^7.0.6",
41
+ "undici": "^7.18.2",
41
42
  "update-notifier": "^7.3.1",
42
43
  "which": "^6.0.0"
43
44
  },
@@ -6,7 +6,15 @@ import os from 'os';
6
6
 
7
7
  /**
8
8
  * Detects if Claude Code is installed and returns detailed status
9
- * @returns {{installed: boolean, path: string|null, configPath: string|null, cliAvailable: boolean, details: string}}
9
+ * @returns {{
10
+ * installed: boolean,
11
+ * path: string|null,
12
+ * configPath: string|null,
13
+ * cliAvailable: boolean,
14
+ * cliPath: string|null,
15
+ * details: string,
16
+ * configFound: boolean
17
+ * }}
10
18
  */
11
19
  function isClaudeCodeInstalled() {
12
20
  // Check for Claude Code directory in user home
@@ -50,7 +58,7 @@ function isClaudeCodeInstalled() {
50
58
  cliPath = foundPath;
51
59
  details.push(`✓ Found in PATH: ${foundPath}`);
52
60
  }
53
- } catch (e) {
61
+ } catch {
54
62
  // Ignore error if not found in PATH
55
63
  }
56
64
  }
package/CLAUDE.md DELETED
@@ -1,90 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Project Overview
6
-
7
- claude-scionos is a lightweight Node.js CLI wrapper that provides ephemeral and secure execution of the official Claude Code CLI. It's designed for the ScioNos environment and focuses on in-memory credential management with zero disk persistence.
8
-
9
- ## Architecture
10
-
11
- The project is a single-file Node.js application (`index.js`) that:
12
-
13
- 1. **Validates prerequisites**: Checks if Claude Code CLI is installed and available in PATH
14
- 2. **Windows-specific check**: Verifies Git Bash installation (required by Claude Code on Windows)
15
- 3. **Secure token collection**: Uses `@inquirer/prompts` for masked password input
16
- 4. **Environment isolation**: Creates isolated environment variables with:
17
- - `ANTHROPIC_BASE_URL`: Set to `https://routerlab.ch` (hardcoded ScioNos endpoint)
18
- - `ANTHROPIC_AUTH_TOKEN`: User-provided token (memory only)
19
- - `ANTHROPIC_API_KEY`: Explicitly set to empty string
20
- 5. **Process spawning**: Launches Claude Code CLI with inherited stdio and custom environment
21
-
22
- ## Key Dependencies
23
-
24
- - `@inquirer/prompts`: For secure password input with masking
25
- - `cross-spawn`: Platform-agnostic process spawning
26
- - `chalk`: Terminal colors for user-friendly output
27
- - `update-notifier`: Automatic npm update notifications
28
- - `which`: Command availability checking
29
-
30
- ## Development Commands
31
-
32
- ```bash
33
- # Run locally during development
34
- node index.js
35
-
36
- # Lint code
37
- npm run lint
38
-
39
- # Version bumping (automated commit messages)
40
- npm run release:patch # Bump patch version
41
- npm run release:minor # Bump minor version
42
- npm run release:major # Bump major version
43
-
44
- # Check for vulnerabilities
45
- npm audit
46
- ```
47
-
48
- ## Testing
49
-
50
- Currently no automated tests are configured. When implementing tests, focus on:
51
- - Version flag handling (`--version`, `-v`)
52
- - Claude Code CLI detection
53
- - Windows Git Bash detection logic
54
- - Token validation and environment setup
55
-
56
- ## Release Process
57
-
58
- 1. Update version in `package.json` if not using npm scripts
59
- 2. Update `SESSION_SUMMARY.md` with changes
60
- 3. Commit changes with descriptive message
61
- 4. Publish to npm: `npm publish`
62
- 5. Create GitHub release with changelog
63
-
64
- ## Platform-Specific Behavior
65
-
66
- ### Windows
67
- - Requires Git Bash for Claude Code CLI to function
68
- - Detects Git Bash in standard locations:
69
- - `C:\Program Files\Git\bin\bash.exe` (64-bit)
70
- - `C:\Program Files (x86)\Git\bin\bash.exe` (32-bit)
71
- - Custom path via `CLAUDE_CODE_GIT_BASH_PATH` environment variable
72
- - Provides clear error messages with installation guidance
73
-
74
- ### Unix (macOS/Linux)
75
- - No additional requirements beyond Claude Code CLI
76
- - Standard Unix process spawning behavior
77
-
78
- ## Important Constants
79
-
80
- - `ANTHROPIC_BASE_URL`: `"https://routerlab.ch"` (ScioNos-specific endpoint)
81
- - Minimum Node.js version: 22
82
- - Package entry point: `index.js` with shebang for direct execution
83
-
84
- ## Code Style Notes
85
-
86
- - ES6 modules throughout (`import`/`export`)
87
- - Async/await pattern for asynchronous operations
88
- - Colored terminal output using chalk
89
- - Clear user error messages with actionable guidance
90
- - Process exit codes: 0 (success), 1 (error)