claude-scionos 2.2.0 → 3.0.1

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,18 @@ 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.1] - 2026-01-11
9
+
10
+ ### Added
11
+ - **Model Mapping & Proxy**: Integrated local proxy to transparently map Claude models to **GLM-4.7** or **MiniMax-M2.1**.
12
+ - **Active Token Validation**: Now validates the `ANTHROPIC_AUTH_TOKEN` against the `routerlab.ch` API in real-time before launching.
13
+ - **Interactive Menu**: Added a selection menu at startup to choose the model strategy (Default vs Mapped).
14
+ - **Pro Branding**: New professional "ScioNos ✕ Claude Code" banner with corporate colors.
15
+
16
+ ### Improved
17
+ - **Error Handling**: Better distinction between missing executable (`ENOENT`) and permission errors (`EACCES`).
18
+ - **User Interface**: Clearer validation steps and visual feedback.
19
+
8
20
  ## [2.2.0] - 2026-01-06
9
21
 
10
22
  ### 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,177 @@ 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 (e) {
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
+ const upstreamRes = await fetch(`${BASE_URL}${req.url}`, {
119
+ method: 'POST',
120
+ headers: {
121
+ ...req.headers,
122
+ 'host': new URL(BASE_URL).host,
123
+ 'x-api-key': validToken // Ensure we use the validated token
124
+ },
125
+ body: bodyJson ? JSON.stringify(bodyJson) : bodyBuffer
126
+ });
127
+
128
+ // Pipe response back
129
+ res.writeHead(upstreamRes.status, upstreamRes.headers);
130
+ if (upstreamRes.body) {
131
+ // @ts-ignore - Node fetch body is iterable
132
+ for await (const chunk of upstreamRes.body) {
133
+ res.write(chunk);
134
+ }
135
+ }
136
+ res.end();
137
+
138
+ } catch (error) {
139
+ console.error(chalk.red(`[Proxy Error] ${error.message}`));
140
+ res.writeHead(500);
141
+ res.end(JSON.stringify({ error: { message: "Scionos Proxy Error" } }));
142
+ }
143
+ });
144
+ } else {
145
+ // Passthrough for other endpoints (like /models potentially)
146
+ // In a simple CLI usage, direct passthrough might be enough,
147
+ // but since we changed the BASE_URL, we must forward everything.
148
+ // For simplicity in this script, we assume most traffic is POST /messages.
149
+ // If Claude Code calls GET /models, we should forward it too.
150
+
151
+ // Simple Redirect implementation for non-body requests
152
+ try {
153
+ const upstreamRes = await fetch(`${BASE_URL}${req.url}`, {
154
+ method: req.method,
155
+ headers: {
156
+ ...req.headers,
157
+ 'host': new URL(BASE_URL).host,
158
+ 'x-api-key': validToken
159
+ }
160
+ });
161
+ res.writeHead(upstreamRes.status, upstreamRes.headers);
162
+ if (upstreamRes.body) {
163
+ // @ts-ignore
164
+ for await (const chunk of upstreamRes.body) {
165
+ res.write(chunk);
166
+ }
167
+ }
168
+ res.end();
169
+ } catch (e) {
170
+ res.writeHead(502);
171
+ res.end();
172
+ }
173
+ }
174
+ });
175
+
176
+ // Listen on random port
177
+ server.listen(0, '127.0.0.1', () => {
178
+ const address = server.address();
179
+ const port = address.port;
180
+ resolve({ server, url: `http://127.0.0.1:${port}` });
181
+ });
182
+
183
+ server.on('error', (err) => reject(err));
184
+ });
185
+ }
186
+
187
+ // --- MAIN EXECUTION ---
188
+
18
189
  // 0. Handle Flags
19
190
  if (process.argv.includes('--version') || process.argv.includes('-v')) {
20
191
  console.log(pkg.version);
@@ -23,166 +194,173 @@ if (process.argv.includes('--version') || process.argv.includes('-v')) {
23
194
 
24
195
  const isDebug = process.argv.includes('--scionos-debug');
25
196
 
26
- // 1. Enhanced System Detection
27
- console.log(chalk.cyan('🔍 Checking system configuration...'));
197
+ // 1. Show Banner
198
+ showBanner();
28
199
 
29
- // Detect OS and environment
200
+ // 2. System Check
201
+ if (isDebug) console.log(chalk.cyan('🔍 Checking system configuration...'));
30
202
  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
203
  let claudeStatus = isClaudeCodeInstalled();
36
204
 
205
+ // Check Claude Code installation
37
206
  if (!claudeStatus.installed) {
38
207
  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
208
  const shouldInstall = await confirm({
46
- message: 'Claude Code CLI is not installed. Do you want to install it via npm now?',
209
+ message: 'Claude Code CLI is not installed. Install globally via npm?',
47
210
  default: true
48
211
  });
49
212
 
50
213
  if (shouldInstall) {
51
214
  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
215
+ console.log(chalk.cyan('\n📦 Installing @anthropic-ai/claude-code...'));
216
+ spawn.sync('npm', ['install', '-g', '@anthropic-ai/claude-code'], { stdio: 'inherit' });
57
217
  claudeStatus = isClaudeCodeInstalled();
58
-
59
218
  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.'));
219
+ console.warn(chalk.yellow('⚠ Installation finished, but executable not found immediately. Restart terminal recommended.'));
63
220
  process.exit(0);
64
221
  }
65
222
  } catch (e) {
66
223
  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));
224
+ console.error(chalk.cyan(getInstallationInstructions(osInfo)));
71
225
  process.exit(1);
72
226
  }
73
227
  } else {
74
- // User declined install
75
- const instructions = getInstallationInstructions(osInfo, claudeStatus);
76
- console.error(chalk.cyan(instructions));
228
+ console.error(chalk.cyan(getInstallationInstructions(osInfo)));
77
229
  process.exit(1);
78
230
  }
79
231
  }
80
232
 
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)
233
+ // Check Git Bash on Windows
86
234
  let gitBashPath = null;
87
235
  if (process.platform === 'win32') {
88
- console.log(chalk.cyan('\n🔍 Checking Git Bash availability...'));
89
236
  const gitBashStatus = checkGitBashOnWindows();
90
-
91
237
  if (!gitBashStatus.available) {
92
238
  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'));
239
+ console.log(chalk.cyan('Please install Git for Windows: https://git-scm.com/downloads/win'));
102
240
  process.exit(1);
241
+ }
242
+ gitBashPath = gitBashStatus.path;
243
+ }
244
+
245
+ // 3. Token Loop
246
+ let token = "";
247
+ while (true) {
248
+ console.log(chalk.blueBright("To retrieve your token, visit: https://routerlab.ch/keys"));
249
+ token = await password({
250
+ message: "Please enter your ANTHROPIC_AUTH_TOKEN:",
251
+ mask: '*'
252
+ });
253
+
254
+ console.log(chalk.gray("Validating token..."));
255
+ const validation = await validateToken(token);
256
+
257
+ if (validation.valid) {
258
+ console.log(chalk.green("✓ Token validated."));
259
+ break;
260
+ } else if (validation.reason === 'auth_failed') {
261
+ console.log(chalk.red("❌ Invalid token (401/403). Try again."));
103
262
  } else {
104
- console.log(chalk.green('✓ Git Bash available'));
105
- console.log(chalk.gray(gitBashStatus.message));
106
- gitBashPath = gitBashStatus.path;
263
+ console.log(chalk.yellow(`⚠ Validation warning: ${validation.message || validation.status}`));
264
+ const ignore = await confirm({ message: "Continue anyway?", default: false });
265
+ if (ignore) break;
107
266
  }
108
267
  }
109
268
 
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.";
269
+ // 4. Model Selection
270
+ const modelChoice = await select({
271
+ message: 'Select Model Strategy:',
272
+ choices: [
273
+ {
274
+ name: 'Default (Use Claude Opus/Sonnet/Haiku natively)',
275
+ value: 'default',
276
+ description: 'Standard behavior. Claude decides which model to use.'
277
+ },
278
+ {
279
+ name: 'Force GLM-4.7 (Map all models to GLM-4.7)',
280
+ value: 'glm-4.7',
281
+ description: 'Intercepts traffic and routes everything to GLM-4.7'
282
+ },
283
+ {
284
+ name: 'Force MiniMax-M2.1 (Map all models to MiniMax)',
285
+ value: 'minimax-m2.1',
286
+ description: 'Intercepts traffic and routes everything to MiniMax-M2.1'
125
287
  }
126
- return true;
127
- },
128
- mask: '*'
288
+ ]
129
289
  });
130
290
 
131
- // 6. Environment configuration
291
+ // 5. Setup Environment & Proxy
292
+ let finalBaseUrl = BASE_URL;
293
+ let proxyServer = null;
294
+
295
+ if (modelChoice !== 'default') {
296
+ console.log(chalk.magenta(`\n🔮 Starting Local Proxy to map models to ${chalk.bold(modelChoice)}...`));
297
+ try {
298
+ const proxyInfo = await startProxyServer(modelChoice, token);
299
+ proxyServer = proxyInfo.server;
300
+ finalBaseUrl = proxyInfo.url; // e.g. http://127.0.0.1:54321
301
+ if (isDebug) console.log(chalk.gray(`✓ Proxy listening on ${finalBaseUrl}`));
302
+ } catch (err) {
303
+ console.error(chalk.red(`Failed to start proxy: ${err.message}`));
304
+ process.exit(1);
305
+ }
306
+ }
307
+
132
308
  const env = {
133
309
  ...process.env,
134
- ANTHROPIC_BASE_URL: "https://routerlab.ch",
310
+ ANTHROPIC_BASE_URL: finalBaseUrl,
135
311
  ANTHROPIC_AUTH_TOKEN: token,
136
- ANTHROPIC_API_KEY: "" // Force empty string
312
+ ANTHROPIC_API_KEY: "" // Force empty
137
313
  };
138
314
 
139
- // 7. Launch Claude Code
140
- // Filter out our internal flag before passing args to Claude
315
+ // 6. Launch Claude
141
316
  const args = process.argv.slice(2).filter(arg => arg !== '--scionos-debug');
142
-
143
317
  if (isDebug) {
144
318
  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}`));
319
+ console.log(chalk.gray(`Endpoint: ${env.ANTHROPIC_BASE_URL}`));
320
+ console.log(chalk.gray(`Model Strategy: ${modelChoice}`));
150
321
  console.log(chalk.yellow('------------------\n'));
151
322
  }
152
323
 
324
+ console.log(chalk.green(`\n🚀 Launching Claude Code [${modelChoice}]...\n`));
325
+
153
326
  const child = spawn(claudeStatus.cliPath, args, {
154
327
  stdio: 'inherit',
155
328
  env: env
156
329
  });
157
330
 
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...'));
331
+ // 7. Cleanup Handlers
332
+ const cleanup = () => {
333
+ if (proxyServer) {
334
+ if (isDebug) console.log(chalk.gray('\nStopping proxy server...'));
335
+ proxyServer.close();
164
336
  }
337
+ };
338
+
339
+ child.on('exit', (code) => {
340
+ cleanup();
341
+ process.exit(code);
165
342
  });
166
343
 
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');
344
+ child.on('error', (err) => {
345
+ cleanup();
346
+ console.error(chalk.red(`\n❌ Error launching Claude CLI:`));
347
+ if (err.code === 'ENOENT') {
348
+ console.error(chalk.yellow(` Executable not found. Try 'npm install -g @anthropic-ai/claude-code'`));
349
+ } else if (err.code === 'EACCES') {
350
+ console.error(chalk.yellow(` Permission denied.`));
351
+ } else {
352
+ console.error(chalk.yellow(` ${err.message}`));
174
353
  }
175
- process.exit(0);
354
+ process.exit(1);
176
355
  });
177
356
 
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);
357
+ process.on('SIGINT', () => {
358
+ // Child handles SIGINT usually, but we ensure cleanup if wrapper is killed
359
+ // We don't exit here immediately to let Claude handle the interrupt
183
360
  });
184
361
 
185
- child.on('error', (err) => {
186
- console.error(chalk.red(`Error launching Claude at ${claudeStatus.cliPath}: ${err.message}`));
187
- process.exit(1);
362
+ process.on('SIGTERM', () => {
363
+ if (child) child.kill('SIGTERM');
364
+ cleanup();
365
+ process.exit(0);
188
366
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-scionos",
3
- "version": "2.2.0",
3
+ "version": "3.0.1",
4
4
  "description": "Ephemeral and secure runner for Claude Code CLI in ScioNos environment",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -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
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)