claude-scionos 3.0.5 → 3.0.7
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/CHANGELOG.md +176 -143
- package/README.fr.md +3 -3
- package/README.md +3 -3
- package/index.js +121 -70
- package/package.json +55 -48
- package/.claude/settings.local.json +0 -9
- package/.gitattributes +0 -2
- package/1.png +0 -0
- package/eslint.config.js +0 -20
- package/tests/detectors.test.js +0 -166
package/CHANGELOG.md
CHANGED
|
@@ -1,143 +1,176 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [3.0.
|
|
9
|
-
|
|
10
|
-
###
|
|
11
|
-
- **
|
|
12
|
-
- **Model
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
### Changed
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- **
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- **
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
- **
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- **
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- **
|
|
65
|
-
- **
|
|
66
|
-
|
|
67
|
-
## [
|
|
68
|
-
|
|
69
|
-
### Added
|
|
70
|
-
- **
|
|
71
|
-
- **
|
|
72
|
-
- **
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- **
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- **
|
|
88
|
-
- **
|
|
89
|
-
- **
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
###
|
|
94
|
-
- **
|
|
95
|
-
- **
|
|
96
|
-
- **
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [3.0.7] - 2026-03-23
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Auto-Installation**: Replaced the broken `spawn.sync(...)` call with `spawnSync(...)` so Claude Code can be installed automatically when missing.
|
|
12
|
+
- **Model Compatibility**: Updated GLM-5 and MiniMax M2.5 mappings to use the RouterLab model IDs `claude-glm-5` and `claude-minimax-m2.5`.
|
|
13
|
+
- **Windows Warning**: Removed `shell: true` from child process launches to avoid the `DEP0190` deprecation warning.
|
|
14
|
+
- **Test Stability**: Fixed the Windows detector test so it no longer depends on `PSModulePath` leaking from the host environment.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **Packaging**: Limited published npm files to the runtime assets and removed `update-notifier` from runtime dependencies to keep the CLI ephemeral.
|
|
18
|
+
- **Documentation**: Updated the README files to match the current model strategy names and RouterLab identifiers.
|
|
19
|
+
|
|
20
|
+
## [3.0.6] - 2026-03-17
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **Proxy Connectivity**: Replaced `undici` with native `node:https` module to fix "Cannot find package 'undici'" and "fetch failed" errors.
|
|
24
|
+
- **Model Compatibility**: Updated GLM-5 and MiniMax to use the Anthropic-compatible proxy model IDs available at the time.
|
|
25
|
+
- **Error Handling**: Improved error logging with detailed error codes and causes for easier debugging.
|
|
26
|
+
|
|
27
|
+
### Improved
|
|
28
|
+
- **Timeouts**: Increased token validation timeout from 10s to 30s; added 60-120s timeouts for proxy requests.
|
|
29
|
+
- **Retry Logic**: Added exponential backoff retry for transient 502/503/504 errors.
|
|
30
|
+
- **Debug Mode**: Enhanced `--scionos-debug` output with request/response status logging.
|
|
31
|
+
|
|
32
|
+
## [3.0.5] - 2026-03-16
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **Dynamic AWS Models**: Integrated dynamic routing for AWS 50% discount models (`haiku`, `sonnet`, `opus`) in the local proxy.
|
|
36
|
+
- **Model Selection**: Cleaned up the initial prompt menu to offer 4 robust strategic choices including GLM-5 and MiniMax M2.5.
|
|
37
|
+
|
|
38
|
+
## [3.0.4] - 2026-03-16
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- **Dependencies**: Replaced `chalk`, `cross-spawn`, and `undici` with Node.js >= 22 native APIs (`util.styleText`, `child_process.spawn`, `fetch`).
|
|
42
|
+
- **Dependencies**: Updated development and production packages to the latest versions.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- **Code Quality**: Fixed `Token` ESLint assignment warnings and improved formatting compatibility.
|
|
46
|
+
|
|
47
|
+
## [3.0.3] - 2026-02-18
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- **Model**: Renamed `GLM-4.7` to `Kimi K2.5` (`kimi-k2.5`) in the model selection menu.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- **Token Validation**: Added a 10-second `AbortController` timeout on `validateToken()` to prevent the prompt from hanging on unresponsive network.
|
|
54
|
+
- **Proxy Memory**: Added a 100 MB cap on incoming request buffers; oversized requests now return HTTP 413 instead of causing a memory overflow.
|
|
55
|
+
- **CI/CD Compatibility**: `console.clear()` is now skipped when the `CI` environment variable is set or `--no-clear` is passed, preventing broken output in pipelines.
|
|
56
|
+
|
|
57
|
+
## [3.0.2] - 2026-01-11
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
- **Robust Proxy**: Integrated `undici` library for advanced HTTP agent control in the local proxy.
|
|
61
|
+
- **SSL Bypass**: Added support for internal/self-signed certificates (`rejectUnauthorized: false`) when using the proxy.
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
- **Proxy Connectivity**: Fixed `fetch failed` and protocol errors by cleaning conflicting headers (`Host`, `Content-Length`) before upstream forwarding.
|
|
65
|
+
- **Code Quality**: Removed unused variables and dead code for cleaner execution.
|
|
66
|
+
|
|
67
|
+
## [3.0.1] - 2026-01-11
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
- **Model Mapping & Proxy**: Integrated local proxy to transparently map Claude models to **GLM-4.7** or **MiniMax-M2.1**.
|
|
71
|
+
- **Active Token Validation**: Now validates the `ANTHROPIC_AUTH_TOKEN` against the `routerlab.ch` API in real-time before launching.
|
|
72
|
+
- **Interactive Menu**: Added a selection menu at startup to choose the model strategy (Default vs Mapped).
|
|
73
|
+
- **Pro Branding**: New professional "ScioNos ✕ Claude Code" banner with corporate colors.
|
|
74
|
+
|
|
75
|
+
### Improved
|
|
76
|
+
- **Error Handling**: Better distinction between missing executable (`ENOENT`) and permission errors (`EACCES`).
|
|
77
|
+
- **User Interface**: Clearer validation steps and visual feedback.
|
|
78
|
+
|
|
79
|
+
## [2.2.0] - 2026-01-06
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
- **Auto-Installation**: Prompts users to automatically install Claude Code CLI (`npm install -g`) if missing.
|
|
83
|
+
- **Native Path Detection**: Now detects Claude Code installations in native paths (`~/.local/bin`, Windows Apps, etc.) per official docs.
|
|
84
|
+
- **SIGTERM Support**: Added handling for `SIGTERM` signals (Docker, CI/CD) to cleanly stop the child process.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
- **Crash on Config-Only**: Fixed a critical bug where the wrapper would crash if a configuration file existed but the CLI executable was missing.
|
|
88
|
+
- **Recursion Safety**: Now launches the detected absolute path of the executable instead of the generic command name, preventing potential loop issues.
|
|
89
|
+
- **Error Logging**: Errors are now correctly sent to `stderr` instead of `stdout`.
|
|
90
|
+
|
|
91
|
+
## [2.1.0] - 2026-01-06
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
- **Debug Mode**: New `--scionos-debug` flag for detailed diagnostic output
|
|
95
|
+
- **Test Infrastructure**: Added Vitest test suite covering core detection logic
|
|
96
|
+
- **Linting**: Fixed development environment and linting rules
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
- **Windows Path Handling**: Fixed an issue where `where claude` returned multiple paths on Windows
|
|
100
|
+
- **Signal Handling**: Improved `SIGINT` (Ctrl+C) handling to prevent wrapper from killing Claude prematurely
|
|
101
|
+
|
|
102
|
+
## [2.0.0] - 2025-12-12
|
|
103
|
+
|
|
104
|
+
### ⚠️ BREAKING CHANGES
|
|
105
|
+
- Enhanced detection system replaces basic checks
|
|
106
|
+
- Improved error handling with detailed diagnostics
|
|
107
|
+
- Better cross-platform support and user guidance
|
|
108
|
+
|
|
109
|
+
### Added
|
|
110
|
+
- **Advanced System Detection**: Comprehensive OS, shell, and environment detection
|
|
111
|
+
- **Enhanced Claude Code Detection**: Checks both directory and CLI availability with detailed status
|
|
112
|
+
- **Improved Git Bash Detection**: Better Windows support with automatic path discovery
|
|
113
|
+
- **Detailed Error Messages**: Actionable troubleshooting information for users
|
|
114
|
+
- **System Information Display**: Shows detected OS and shell during startup
|
|
115
|
+
- **Modular Architecture**: New `src/detectors/` directory for extensible detection logic
|
|
116
|
+
|
|
117
|
+
### Improved
|
|
118
|
+
- **User Experience**: Much clearer feedback when something goes wrong
|
|
119
|
+
- **Cross-Platform Compatibility**: Better handling of Windows, macOS, and Linux
|
|
120
|
+
- **Installation Guidance**: OS-specific instructions when dependencies are missing
|
|
121
|
+
- **Code Organization**: Cleaner separation of concerns with dedicated detection modules
|
|
122
|
+
|
|
123
|
+
### Technical Changes
|
|
124
|
+
- Replaced basic `which()` checks with comprehensive detection system
|
|
125
|
+
- Added support for detecting Claude Code configuration files
|
|
126
|
+
- Enhanced shell detection (PowerShell, CMD, bash, zsh, fish)
|
|
127
|
+
- Improved error recovery and user guidance
|
|
128
|
+
|
|
129
|
+
## [1.0.1] - 2025-11-29
|
|
130
|
+
|
|
131
|
+
### Added
|
|
132
|
+
- Git Bash detection for Windows users
|
|
133
|
+
- Clear error message when Git Bash is missing on Windows
|
|
134
|
+
- Troubleshooting section for Git Bash in README.md
|
|
135
|
+
- Section de dépannage pour Git Bash dans README.fr.md
|
|
136
|
+
- Support for `CLAUDE_CODE_GIT_BASH_PATH` environment variable
|
|
137
|
+
|
|
138
|
+
### Fixed
|
|
139
|
+
- Windows users no longer blocked after entering token due to missing Git Bash
|
|
140
|
+
- Better error handling for Windows environment requirements
|
|
141
|
+
|
|
142
|
+
### Improved
|
|
143
|
+
- User experience for Windows beta testers
|
|
144
|
+
- Documentation clarity for Windows-specific requirements
|
|
145
|
+
- Error messages now provide actionable solutions
|
|
146
|
+
|
|
147
|
+
## [1.0.0] - 2025-11-28
|
|
148
|
+
|
|
149
|
+
### Added
|
|
150
|
+
- Initial release
|
|
151
|
+
- Ephemeral and secure token handling (memory-only storage)
|
|
152
|
+
- Support for ScioNos environment (`https://routerlab.ch`)
|
|
153
|
+
- Bilingual documentation (English and French)
|
|
154
|
+
- Command-line interface with `--version` flag
|
|
155
|
+
- Secure token input with masking
|
|
156
|
+
- Automatic cleanup on process exit
|
|
157
|
+
- Zero persistence (no files written to disk)
|
|
158
|
+
|
|
159
|
+
### Security
|
|
160
|
+
- Tokens stored only in memory
|
|
161
|
+
- No configuration files created
|
|
162
|
+
- Automatic credential cleanup on exit
|
|
163
|
+
- Environment variable isolation
|
|
164
|
+
|
|
165
|
+
[3.0.6]: https://github.com/ScioNos/claude-scionos/compare/v3.0.5...v3.0.6
|
|
166
|
+
[3.0.7]: https://github.com/ScioNos/claude-scionos/compare/v3.0.6...v3.0.7
|
|
167
|
+
[3.0.5]: https://github.com/ScioNos/claude-scionos/compare/v3.0.4...v3.0.5
|
|
168
|
+
[3.0.4]: https://github.com/ScioNos/claude-scionos/compare/v3.0.3...v3.0.4
|
|
169
|
+
[3.0.3]: https://github.com/ScioNos/claude-scionos/compare/v3.0.2...v3.0.3
|
|
170
|
+
[3.0.2]: https://github.com/ScioNos/claude-scionos/compare/v3.0.1...v3.0.2
|
|
171
|
+
[3.0.1]: https://github.com/ScioNos/claude-scionos/compare/v2.2.0...v3.0.1
|
|
172
|
+
[2.2.0]: https://github.com/ScioNos/claude-scionos/compare/v2.1.0...v2.2.0
|
|
173
|
+
[2.1.0]: https://github.com/ScioNos/claude-scionos/compare/v2.0.0...v2.1.0
|
|
174
|
+
[2.0.0]: https://github.com/ScioNos/claude-scionos/compare/v1.0.1...v2.0.0
|
|
175
|
+
[1.0.1]: https://github.com/ScioNos/claude-scionos/compare/v1.0.0...v1.0.1
|
|
176
|
+
[1.0.0]: https://github.com/ScioNos/claude-scionos/releases/tag/v1.0.0
|
package/README.fr.md
CHANGED
|
@@ -41,7 +41,7 @@ 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 **
|
|
44
|
+
- 🔄 **Mapping de Modèles** — Redirection transparente vers **claude-glm-5** ou **claude-minimax-m2.5** via proxy local
|
|
45
45
|
- 💾 **Zéro persistance** — Aucun fichier temporaire ni configuration locale stockés
|
|
46
46
|
- 🧩 **Compatibilité totale** — Fonctionne parfaitement avec la CLI officielle Claude Code
|
|
47
47
|
- 🔐 **Stockage en mémoire uniquement** — Toutes les informations d'identification sont détruites à la fin du processus
|
|
@@ -102,8 +102,8 @@ npx claude-scionos
|
|
|
102
102
|
2. Vous invite à saisir votre `ANTHROPIC_AUTH_TOKEN` et le valide instantanément
|
|
103
103
|
3. **Menu de Sélection** : Vous choisissez la stratégie de modèle :
|
|
104
104
|
- *Default* : Utilise les modèles Anthropic (Opus/Sonnet/Haiku)
|
|
105
|
-
- *
|
|
106
|
-
- *
|
|
105
|
+
- *GLM-5* : Mappe toutes les requêtes vers `claude-glm-5`
|
|
106
|
+
- *MiniMax M2.5* : Mappe toutes les requêtes vers `claude-minimax-m2.5`
|
|
107
107
|
4. Lance Claude Code (avec un proxy local transparent si un mapping est choisi)
|
|
108
108
|
5. Nettoie automatiquement les informations d'identification à la sortie
|
|
109
109
|
|
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ 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 **
|
|
44
|
+
- 🔄 **Model Mapping** — Transparently route requests to **claude-glm-5** or **claude-minimax-m2.5** via local proxy
|
|
45
45
|
- 💾 **Zero Persistence** — No temporary files or local configuration stored
|
|
46
46
|
- 🧩 **Full Compatibility** — Works seamlessly with the official Claude Code CLI
|
|
47
47
|
- 🔐 **Memory-Only Storage** — All credentials destroyed on process exit
|
|
@@ -102,8 +102,8 @@ npx claude-scionos
|
|
|
102
102
|
2. Prompts for your `ANTHROPIC_AUTH_TOKEN` and validates it instantly
|
|
103
103
|
3. **Selection Menu**: Choose your model strategy:
|
|
104
104
|
- *Default*: Use standard Anthropic models (Opus/Sonnet/Haiku)
|
|
105
|
-
- *
|
|
106
|
-
- *
|
|
105
|
+
- *GLM-5*: Maps all requests to `claude-glm-5`
|
|
106
|
+
- *MiniMax M2.5*: Maps all requests to `claude-minimax-m2.5`
|
|
107
107
|
4. Launches Claude Code (starting a transparent local proxy if needed)
|
|
108
108
|
5. Automatically cleans credentials on exit
|
|
109
109
|
|
package/index.js
CHANGED
|
@@ -20,8 +20,7 @@ const chalk = {
|
|
|
20
20
|
bold: (t) => styleText('bold', t)
|
|
21
21
|
};
|
|
22
22
|
import { password, confirm, select } from '@inquirer/prompts';
|
|
23
|
-
import { spawn } from 'node:child_process';
|
|
24
|
-
import updateNotifier from 'update-notifier';
|
|
23
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
25
24
|
import process from 'node:process';
|
|
26
25
|
import http from 'node:http';
|
|
27
26
|
import { createRequire } from 'node:module';
|
|
@@ -29,9 +28,7 @@ import { isClaudeCodeInstalled, detectOS, checkGitBashOnWindows, getInstallation
|
|
|
29
28
|
|
|
30
29
|
const require = createRequire(import.meta.url);
|
|
31
30
|
const pkg = require('./package.json');
|
|
32
|
-
|
|
33
|
-
// Initialize update notifier
|
|
34
|
-
updateNotifier({ pkg }).notify();
|
|
31
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
35
32
|
|
|
36
33
|
// --- CONFIGURATION ---
|
|
37
34
|
const BASE_URL = "https://routerlab.ch";
|
|
@@ -66,7 +63,7 @@ function showBanner() {
|
|
|
66
63
|
async function validateToken(apiKey) {
|
|
67
64
|
try {
|
|
68
65
|
const controller = new AbortController();
|
|
69
|
-
const timeoutId = setTimeout(() => controller.abort(),
|
|
66
|
+
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
|
|
70
67
|
const response = await fetch(`${BASE_URL}/v1/models`, {
|
|
71
68
|
method: 'GET',
|
|
72
69
|
headers: {
|
|
@@ -85,13 +82,16 @@ async function validateToken(apiKey) {
|
|
|
85
82
|
return { valid: false, reason: 'server_error', status: response.status, statusText: response.statusText };
|
|
86
83
|
}
|
|
87
84
|
} catch (error) {
|
|
85
|
+
if (error.name === 'AbortError') {
|
|
86
|
+
return { valid: false, reason: 'timeout', message: 'Request timed out after 30s' };
|
|
87
|
+
}
|
|
88
88
|
return { valid: false, reason: 'network_error', message: error.message };
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
93
|
* Starts a local proxy server to map models
|
|
94
|
-
* @param {string} targetModel - The model ID to map to (e.g., 'glm-
|
|
94
|
+
* @param {string} targetModel - The model ID to map to (e.g., 'claude-glm-5')
|
|
95
95
|
* @param {string} validToken - The validated Auth Token
|
|
96
96
|
* @returns {Promise<string>} - The local URL (e.g., http://127.0.0.1:45321)
|
|
97
97
|
*/
|
|
@@ -132,7 +132,6 @@ function startProxyServer(targetModel, validToken) {
|
|
|
132
132
|
try {
|
|
133
133
|
bodyJson = JSON.parse(bodyBuffer.toString());
|
|
134
134
|
} catch {
|
|
135
|
-
// If not JSON, forward as is
|
|
136
135
|
bodyJson = null;
|
|
137
136
|
}
|
|
138
137
|
|
|
@@ -145,54 +144,73 @@ function startProxyServer(targetModel, validToken) {
|
|
|
145
144
|
} else if (bodyJson.model.includes('opus')) {
|
|
146
145
|
newModel = 'aws-claude-opus-4-6';
|
|
147
146
|
} else {
|
|
148
|
-
// Default to sonnet for AWS if not haiku or opus
|
|
149
147
|
newModel = 'aws-claude-sonnet-4-6';
|
|
150
148
|
}
|
|
151
149
|
}
|
|
152
|
-
|
|
150
|
+
|
|
153
151
|
if (process.argv.includes('--scionos-debug')) {
|
|
154
152
|
console.log(chalk.yellow(`[Proxy] Swapping model ${bodyJson.model} -> ${newModel}`));
|
|
155
153
|
}
|
|
156
154
|
bodyJson.model = newModel;
|
|
157
155
|
}
|
|
158
156
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
// Forward request using https directly
|
|
158
|
+
const https = await import('node:https');
|
|
159
|
+
const url = new URL(`${BASE_URL}${req.url}`);
|
|
160
|
+
|
|
161
|
+
const options = {
|
|
162
|
+
hostname: url.hostname,
|
|
163
|
+
port: url.port || 443,
|
|
164
|
+
path: url.pathname + url.search,
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: {
|
|
167
|
+
'Content-Type': 'application/json',
|
|
168
|
+
'x-api-key': validToken,
|
|
169
|
+
'anthropic-version': '2023-06-01',
|
|
170
|
+
'Content-Length': bodyJson ? Buffer.byteLength(JSON.stringify(bodyJson)) : bodyBuffer.length
|
|
171
|
+
},
|
|
172
|
+
rejectUnauthorized: false,
|
|
173
|
+
timeout: 120000
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const proxyReq = https.request(options, (proxyRes) => {
|
|
177
|
+
if (process.argv.includes('--scionos-debug')) {
|
|
178
|
+
console.log(chalk.yellow(`[Proxy] Upstream response status: ${proxyRes.statusCode}`));
|
|
165
179
|
}
|
|
180
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
181
|
+
proxyRes.pipe(res);
|
|
166
182
|
});
|
|
167
183
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const upstreamRes = await fetch(`${BASE_URL}${req.url}`, {
|
|
176
|
-
method: 'POST',
|
|
177
|
-
headers: upstreamHeaders,
|
|
178
|
-
body: bodyJson ? JSON.stringify(bodyJson) : bodyBuffer,
|
|
179
|
-
dispatcher: dispatcher // <--- Apply the custom agent here
|
|
184
|
+
proxyReq.on('error', (error) => {
|
|
185
|
+
console.error(chalk.red(`[Proxy Error] POST /messages: ${error.message}`));
|
|
186
|
+
console.error(chalk.red(` Code: ${error.code}`));
|
|
187
|
+
if (!res.headersSent) {
|
|
188
|
+
res.writeHead(500);
|
|
189
|
+
res.end(JSON.stringify({ error: { message: "Proxy Error", details: error.message, code: error.code } }));
|
|
190
|
+
}
|
|
180
191
|
});
|
|
181
192
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
res.
|
|
193
|
+
proxyReq.on('timeout', () => {
|
|
194
|
+
console.error(chalk.red(`[Proxy] Request timeout`));
|
|
195
|
+
proxyReq.destroy();
|
|
196
|
+
if (!res.headersSent) {
|
|
197
|
+
res.writeHead(504);
|
|
198
|
+
res.end(JSON.stringify({ error: { message: "Gateway Timeout" } }));
|
|
188
199
|
}
|
|
189
|
-
}
|
|
190
|
-
res.end();
|
|
200
|
+
});
|
|
191
201
|
|
|
202
|
+
proxyReq.write(bodyJson ? JSON.stringify(bodyJson) : bodyBuffer);
|
|
203
|
+
proxyReq.end();
|
|
204
|
+
|
|
205
|
+
if (process.argv.includes('--scionos-debug')) {
|
|
206
|
+
console.log(chalk.yellow(`[Proxy] Request sent to upstream`));
|
|
207
|
+
}
|
|
192
208
|
} catch (error) {
|
|
193
|
-
console.error(chalk.red(`[Proxy Error] ${error.message}`));
|
|
194
|
-
res.
|
|
195
|
-
|
|
209
|
+
console.error(chalk.red(`[Proxy Error] POST /messages: ${error.message}`));
|
|
210
|
+
if (!res.headersSent) {
|
|
211
|
+
res.writeHead(500);
|
|
212
|
+
res.end(JSON.stringify({ error: { message: "Scionos Proxy Error", details: error.message } }));
|
|
213
|
+
}
|
|
196
214
|
}
|
|
197
215
|
});
|
|
198
216
|
} else {
|
|
@@ -204,32 +222,58 @@ function startProxyServer(targetModel, validToken) {
|
|
|
204
222
|
|
|
205
223
|
// Simple Redirect implementation for non-body requests
|
|
206
224
|
try {
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
delete upstreamHeaders['content-length'];
|
|
215
|
-
upstreamHeaders['x-api-key'] = validToken;
|
|
216
|
-
|
|
217
|
-
const upstreamRes = await fetch(`${BASE_URL}${req.url}`, {
|
|
225
|
+
const https = await import('node:https');
|
|
226
|
+
const url = new URL(`${BASE_URL}${req.url}`);
|
|
227
|
+
|
|
228
|
+
const options = {
|
|
229
|
+
hostname: url.hostname,
|
|
230
|
+
port: url.port || 443,
|
|
231
|
+
path: url.pathname + url.search,
|
|
218
232
|
method: req.method,
|
|
219
|
-
headers:
|
|
220
|
-
|
|
233
|
+
headers: {
|
|
234
|
+
'Content-Type': 'application/json',
|
|
235
|
+
'x-api-key': validToken,
|
|
236
|
+
'anthropic-version': '2023-06-01'
|
|
237
|
+
},
|
|
238
|
+
rejectUnauthorized: false,
|
|
239
|
+
timeout: 60000
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const proxyReq = https.request(options, (proxyRes) => {
|
|
243
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
244
|
+
proxyRes.pipe(res);
|
|
221
245
|
});
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
res.
|
|
246
|
+
|
|
247
|
+
proxyReq.on('error', (error) => {
|
|
248
|
+
console.error(chalk.red(`[Proxy Error] ${req.method} ${req.url}: ${error.message}`));
|
|
249
|
+
if (!res.headersSent) {
|
|
250
|
+
res.writeHead(502);
|
|
251
|
+
res.end(JSON.stringify({
|
|
252
|
+
error: {
|
|
253
|
+
message: "Scionos Proxy Error: Failed to connect to upstream",
|
|
254
|
+
details: error.message
|
|
255
|
+
}
|
|
256
|
+
}));
|
|
227
257
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Forward body for PUT/POST requests
|
|
261
|
+
if (req.method === 'POST' || req.method === 'PUT') {
|
|
262
|
+
req.pipe(proxyReq);
|
|
263
|
+
} else {
|
|
264
|
+
proxyReq.end();
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.error(chalk.red(`[Proxy Error] ${req.method} ${req.url}: ${error.message}`));
|
|
268
|
+
if (!res.headersSent) {
|
|
269
|
+
res.writeHead(502);
|
|
270
|
+
res.end(JSON.stringify({
|
|
271
|
+
error: {
|
|
272
|
+
message: "Scionos Proxy Error: Failed to connect to upstream",
|
|
273
|
+
details: error.message
|
|
274
|
+
}
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
233
277
|
}
|
|
234
278
|
}
|
|
235
279
|
});
|
|
@@ -274,7 +318,15 @@ if (!claudeStatus.installed) {
|
|
|
274
318
|
if (shouldInstall) {
|
|
275
319
|
try {
|
|
276
320
|
console.log(chalk.cyan('\n📦 Installing @anthropic-ai/claude-code...'));
|
|
277
|
-
|
|
321
|
+
const installResult = spawnSync(npmCommand, ['install', '-g', '@anthropic-ai/claude-code'], {
|
|
322
|
+
stdio: 'inherit'
|
|
323
|
+
});
|
|
324
|
+
if (installResult.error) {
|
|
325
|
+
throw installResult.error;
|
|
326
|
+
}
|
|
327
|
+
if (installResult.status !== 0) {
|
|
328
|
+
throw new Error(`npm install exited with code ${installResult.status}`);
|
|
329
|
+
}
|
|
278
330
|
claudeStatus = isClaudeCodeInstalled();
|
|
279
331
|
if (!claudeStatus.installed) {
|
|
280
332
|
console.warn(chalk.yellow('⚠ Installation finished, but executable not found immediately. Restart terminal recommended.'));
|
|
@@ -341,13 +393,13 @@ const modelChoice = await select({
|
|
|
341
393
|
},
|
|
342
394
|
{
|
|
343
395
|
name: 'GLM-5',
|
|
344
|
-
value: 'glm-5',
|
|
345
|
-
description: 'Remplace tous les modèles par glm-5'
|
|
396
|
+
value: 'claude-glm-5',
|
|
397
|
+
description: 'Remplace tous les modèles par claude-glm-5'
|
|
346
398
|
},
|
|
347
399
|
{
|
|
348
400
|
name: 'MiniMax M2.5',
|
|
349
|
-
value: 'minimax-m2.5',
|
|
350
|
-
description: 'Remplace tous les modèles par minimax-m2.5'
|
|
401
|
+
value: 'claude-minimax-m2.5',
|
|
402
|
+
description: 'Remplace tous les modèles par claude-minimax-m2.5'
|
|
351
403
|
}
|
|
352
404
|
]
|
|
353
405
|
});
|
|
@@ -389,8 +441,7 @@ console.log(chalk.green(`\n🚀 Launching Claude Code [${modelChoice}]...\n`));
|
|
|
389
441
|
|
|
390
442
|
const child = spawn(claudeStatus.cliPath, args, {
|
|
391
443
|
stdio: 'inherit',
|
|
392
|
-
env: env
|
|
393
|
-
shell: process.platform === 'win32'
|
|
444
|
+
env: env
|
|
394
445
|
});
|
|
395
446
|
|
|
396
447
|
// 7. Cleanup Handlers
|
|
@@ -428,4 +479,4 @@ process.on('SIGTERM', () => {
|
|
|
428
479
|
if (child) child.kill('SIGTERM');
|
|
429
480
|
cleanup();
|
|
430
481
|
process.exit(0);
|
|
431
|
-
});
|
|
482
|
+
});
|
package/package.json
CHANGED
|
@@ -1,48 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "claude-scionos",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "Ephemeral and secure runner for Claude Code CLI in ScioNos environment",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"claude-scionos": "index.js"
|
|
9
|
-
},
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-scionos",
|
|
3
|
+
"version": "3.0.7",
|
|
4
|
+
"description": "Ephemeral and secure runner for Claude Code CLI in ScioNos environment",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"claude-scionos": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"src/",
|
|
13
|
+
"README.md",
|
|
14
|
+
"README.fr.md",
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"release:patch": "npm version patch -m \"Chore: Bump version to %s\"",
|
|
22
|
+
"release:minor": "npm version minor -m \"Chore: Bump version to %s\"",
|
|
23
|
+
"release:major": "npm version major -m \"Chore: Bump version to %s\""
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"claude",
|
|
27
|
+
"scionos",
|
|
28
|
+
"cli",
|
|
29
|
+
"wrapper"
|
|
30
|
+
],
|
|
31
|
+
"author": "ScioNos",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"homepage": "https://scionos.ch",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/ScioNos/claude-scionos.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/ScioNos/claude-scionos/issues"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22"
|
|
43
|
+
},
|
|
44
|
+
"private": false,
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@inquirer/prompts": "^8.3.2",
|
|
47
|
+
"which": "^6.0.1"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@eslint/js": "^10.0.1",
|
|
51
|
+
"eslint": "^10.1.0",
|
|
52
|
+
"globals": "^17.4.0",
|
|
53
|
+
"vitest": "^4.1.0"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/.gitattributes
DELETED
package/1.png
DELETED
|
Binary file
|
package/eslint.config.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import globals from 'globals';
|
|
2
|
-
import pluginJs from '@eslint/js';
|
|
3
|
-
|
|
4
|
-
export default [
|
|
5
|
-
{
|
|
6
|
-
languageOptions: {
|
|
7
|
-
globals: {
|
|
8
|
-
...globals.node,
|
|
9
|
-
...globals.builtin,
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
pluginJs.configs.recommended,
|
|
14
|
-
{
|
|
15
|
-
rules: {
|
|
16
|
-
'no-console': 'off',
|
|
17
|
-
'no-process-exit': 'off',
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
];
|
package/tests/detectors.test.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import child_process from 'child_process';
|
|
6
|
-
import { isClaudeCodeInstalled, detectOS, checkGitBashOnWindows } from '../src/detectors/claude-only.js';
|
|
7
|
-
|
|
8
|
-
// Mock modules
|
|
9
|
-
vi.mock('fs');
|
|
10
|
-
vi.mock('os');
|
|
11
|
-
vi.mock('child_process');
|
|
12
|
-
|
|
13
|
-
describe('System Detectors', () => {
|
|
14
|
-
const originalPlatform = process.platform;
|
|
15
|
-
const originalEnv = process.env;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
vi.resetAllMocks();
|
|
19
|
-
process.env = { ...originalEnv };
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
24
|
-
process.env = originalEnv;
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('detectOS', () => {
|
|
28
|
-
it('should detect Windows', () => {
|
|
29
|
-
vi.mocked(os.platform).mockReturnValue('win32');
|
|
30
|
-
vi.mocked(os.arch).mockReturnValue('x64');
|
|
31
|
-
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
32
|
-
process.env.WINDIR = 'C:\\Windows'; // Simulate CMD
|
|
33
|
-
|
|
34
|
-
const result = detectOS();
|
|
35
|
-
expect(result.type).toBe('Windows');
|
|
36
|
-
expect(result.shell).toBe('Command Prompt (CMD)');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should detect macOS', () => {
|
|
40
|
-
vi.mocked(os.platform).mockReturnValue('darwin');
|
|
41
|
-
vi.mocked(os.arch).mockReturnValue('arm64');
|
|
42
|
-
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
43
|
-
process.env.SHELL = '/bin/zsh';
|
|
44
|
-
|
|
45
|
-
const result = detectOS();
|
|
46
|
-
expect(result.type).toBe('macOS');
|
|
47
|
-
expect(result.shell).toBe('Zsh');
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('checkGitBashOnWindows', () => {
|
|
52
|
-
it('should return available if Git Bash exists in standard path on Windows', () => {
|
|
53
|
-
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
54
|
-
vi.mocked(fs.existsSync).mockImplementation((p) => p === 'C:\\Program Files\\Git\\bin\\bash.exe');
|
|
55
|
-
|
|
56
|
-
const result = checkGitBashOnWindows();
|
|
57
|
-
expect(result.available).toBe(true);
|
|
58
|
-
expect(result.path).toBe('C:\\Program Files\\Git\\bin\\bash.exe');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should return not available if no Git Bash found on Windows', () => {
|
|
62
|
-
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
63
|
-
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
64
|
-
|
|
65
|
-
const result = checkGitBashOnWindows();
|
|
66
|
-
expect(result.available).toBe(false);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should not require Git Bash on non-Windows', () => {
|
|
70
|
-
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
71
|
-
|
|
72
|
-
const result = checkGitBashOnWindows();
|
|
73
|
-
expect(result.available).toBe(true);
|
|
74
|
-
expect(result.message).toContain('Not required');
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('isClaudeCodeInstalled', () => {
|
|
79
|
-
it('should detect Claude in PATH', () => {
|
|
80
|
-
// Mock homedir to avoid finding config file
|
|
81
|
-
vi.mocked(os.homedir).mockReturnValue('/home/user');
|
|
82
|
-
|
|
83
|
-
// Mock execSync to return a path
|
|
84
|
-
vi.mocked(child_process.execSync).mockImplementation((cmd) => {
|
|
85
|
-
if (cmd.includes('which claude') || cmd.includes('where claude')) {
|
|
86
|
-
return '/usr/local/bin/claude';
|
|
87
|
-
}
|
|
88
|
-
if (cmd.includes('--version')) {
|
|
89
|
-
return '0.0.1';
|
|
90
|
-
}
|
|
91
|
-
return '';
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Mock existsSync: false for config, true for CLI path
|
|
95
|
-
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
|
96
|
-
if (p === '/usr/local/bin/claude') return true;
|
|
97
|
-
return false;
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const result = isClaudeCodeInstalled();
|
|
101
|
-
expect(result.installed).toBe(true);
|
|
102
|
-
expect(result.cliAvailable).toBe(true);
|
|
103
|
-
expect(result.cliPath).toBe('/usr/local/bin/claude');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should detect Claude from config file ONLY if CLI is also found', () => {
|
|
107
|
-
vi.mocked(os.homedir).mockReturnValue('/home/user');
|
|
108
|
-
// Simulate .claude directory and settings.json existence
|
|
109
|
-
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
|
110
|
-
if (p.endsWith('.claude')) return true;
|
|
111
|
-
if (p.endsWith('settings.json')) return true;
|
|
112
|
-
return false;
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Mock reading config
|
|
116
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({
|
|
117
|
-
env: { ANTHROPIC_BASE_URL: 'https://api.anthropic.com' }
|
|
118
|
-
}));
|
|
119
|
-
|
|
120
|
-
// BUT execSync fails (CLI not found)
|
|
121
|
-
vi.mocked(child_process.execSync).mockImplementation(() => {
|
|
122
|
-
throw new Error('not found');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const result = isClaudeCodeInstalled();
|
|
126
|
-
// Updated logic: installed should be false if CLI is missing, even if config exists
|
|
127
|
-
expect(result.installed).toBe(false);
|
|
128
|
-
expect(result.configFound).toBe(true);
|
|
129
|
-
expect(result.cliAvailable).toBe(false);
|
|
130
|
-
expect(result.details).toContain('Executable \'claude\' not found');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('should detect Claude in native path (Linux/Mac)', () => {
|
|
134
|
-
// Force Linux platform to ensure implementation looks for 'claude' not 'claude.exe'
|
|
135
|
-
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
136
|
-
|
|
137
|
-
// We simulate logic but path.join will use OS separators.
|
|
138
|
-
// On Windows, path.join produces backslashes.
|
|
139
|
-
// The code under test uses path.join.
|
|
140
|
-
const mockHome = '/home/user';
|
|
141
|
-
const expectedPath = path.join(mockHome, '.local', 'bin', 'claude');
|
|
142
|
-
|
|
143
|
-
vi.mocked(os.homedir).mockReturnValue(mockHome);
|
|
144
|
-
|
|
145
|
-
// Simulate native path existence
|
|
146
|
-
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
|
147
|
-
return p === expectedPath;
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const result = isClaudeCodeInstalled();
|
|
151
|
-
expect(result.installed).toBe(true);
|
|
152
|
-
expect(result.cliPath).toBe(expectedPath);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should return false if neither CLI nor config found', () => {
|
|
156
|
-
vi.mocked(os.homedir).mockReturnValue('/home/user');
|
|
157
|
-
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
158
|
-
vi.mocked(child_process.execSync).mockImplementation(() => {
|
|
159
|
-
throw new Error('not found');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const result = isClaudeCodeInstalled();
|
|
163
|
-
expect(result.installed).toBe(false);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
});
|