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 +0 -0
- package/CHANGELOG.md +22 -0
- package/README.fr.md +9 -5
- package/README.md +9 -5
- package/index.js +298 -103
- package/package.json +2 -1
- package/src/detectors/claude-only.js +10 -2
- package/CLAUDE.md +0 -90
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.
|
|
104
|
-
|
|
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. **
|
|
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.
|
|
104
|
-
|
|
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
|
|
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
|
|
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.
|
|
27
|
-
|
|
216
|
+
// 1. Show Banner
|
|
217
|
+
showBanner();
|
|
28
218
|
|
|
29
|
-
//
|
|
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.
|
|
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
|
|
53
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
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.
|
|
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.
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
},
|
|
128
|
-
mask: '*'
|
|
305
|
+
]
|
|
129
306
|
});
|
|
130
307
|
|
|
131
|
-
//
|
|
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:
|
|
327
|
+
ANTHROPIC_BASE_URL: finalBaseUrl,
|
|
135
328
|
ANTHROPIC_AUTH_TOKEN: token,
|
|
136
|
-
ANTHROPIC_API_KEY: "" // Force empty
|
|
329
|
+
ANTHROPIC_API_KEY: "" // Force empty
|
|
137
330
|
};
|
|
138
331
|
|
|
139
|
-
//
|
|
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(`
|
|
146
|
-
console.log(chalk.gray(`
|
|
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
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
|
|
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(
|
|
371
|
+
process.exit(1);
|
|
176
372
|
});
|
|
177
373
|
|
|
178
|
-
|
|
179
|
-
if
|
|
180
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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": "
|
|
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 {{
|
|
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
|
|
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)
|