@kaitranntt/ccs 4.3.10 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -42
- package/VERSION +1 -1
- package/config/base-agy.settings.json +10 -0
- package/config/base-codex.settings.json +10 -0
- package/config/base-gemini.settings.json +10 -0
- package/dist/auth/auth-commands.d.ts +52 -0
- package/dist/auth/auth-commands.d.ts.map +1 -0
- package/dist/auth/auth-commands.js +479 -0
- package/dist/auth/auth-commands.js.map +1 -0
- package/dist/auth/profile-detector.d.ts +68 -0
- package/dist/auth/profile-detector.d.ts.map +1 -0
- package/dist/auth/profile-detector.js +209 -0
- package/dist/auth/profile-detector.js.map +1 -0
- package/dist/auth/profile-registry.d.ts +60 -0
- package/dist/auth/profile-registry.d.ts.map +1 -0
- package/dist/auth/profile-registry.js +188 -0
- package/dist/auth/profile-registry.js.map +1 -0
- package/dist/ccs.d.ts +10 -0
- package/dist/ccs.d.ts.map +1 -0
- package/dist/ccs.js +320 -0
- package/dist/ccs.js.map +1 -0
- package/dist/cliproxy/auth-handler.d.ts +93 -0
- package/dist/cliproxy/auth-handler.d.ts.map +1 -0
- package/dist/cliproxy/auth-handler.js +402 -0
- package/dist/cliproxy/auth-handler.js.map +1 -0
- package/dist/cliproxy/base-config-loader.d.ts +42 -0
- package/dist/cliproxy/base-config-loader.d.ts.map +1 -0
- package/dist/cliproxy/base-config-loader.js +123 -0
- package/dist/cliproxy/base-config-loader.js.map +1 -0
- package/dist/cliproxy/binary-manager.d.ts +104 -0
- package/dist/cliproxy/binary-manager.d.ts.map +1 -0
- package/dist/cliproxy/binary-manager.js +567 -0
- package/dist/cliproxy/binary-manager.js.map +1 -0
- package/dist/cliproxy/cliproxy-executor.d.ts +33 -0
- package/dist/cliproxy/cliproxy-executor.d.ts.map +1 -0
- package/dist/cliproxy/cliproxy-executor.js +297 -0
- package/dist/cliproxy/cliproxy-executor.js.map +1 -0
- package/dist/cliproxy/config-generator.d.ts +89 -0
- package/dist/cliproxy/config-generator.d.ts.map +1 -0
- package/dist/cliproxy/config-generator.js +263 -0
- package/dist/cliproxy/config-generator.js.map +1 -0
- package/dist/cliproxy/index.d.ts +13 -0
- package/dist/cliproxy/index.d.ts.map +1 -0
- package/dist/cliproxy/index.js +62 -0
- package/dist/cliproxy/index.js.map +1 -0
- package/dist/cliproxy/platform-detector.d.ts +48 -0
- package/dist/cliproxy/platform-detector.d.ts.map +1 -0
- package/dist/cliproxy/platform-detector.js +118 -0
- package/dist/cliproxy/platform-detector.js.map +1 -0
- package/dist/cliproxy/types.d.ts +169 -0
- package/dist/cliproxy/types.d.ts.map +1 -0
- package/dist/cliproxy/types.js +7 -0
- package/dist/cliproxy/types.js.map +1 -0
- package/dist/commands/doctor-command.d.ts +10 -0
- package/dist/commands/doctor-command.d.ts.map +1 -0
- package/dist/commands/doctor-command.js +44 -0
- package/dist/commands/doctor-command.js.map +1 -0
- package/dist/commands/help-command.d.ts +5 -0
- package/dist/commands/help-command.d.ts.map +1 -0
- package/dist/commands/help-command.js +104 -0
- package/dist/commands/help-command.js.map +1 -0
- package/dist/commands/install-command.d.ts +14 -0
- package/dist/commands/install-command.d.ts.map +1 -0
- package/dist/commands/install-command.js +39 -0
- package/dist/commands/install-command.js.map +1 -0
- package/dist/commands/shell-completion-command.d.ts +10 -0
- package/dist/commands/shell-completion-command.d.ts.map +1 -0
- package/dist/commands/shell-completion-command.js +85 -0
- package/dist/commands/shell-completion-command.js.map +1 -0
- package/dist/commands/sync-command.d.ts +10 -0
- package/dist/commands/sync-command.d.ts.map +1 -0
- package/dist/commands/sync-command.js +59 -0
- package/dist/commands/sync-command.js.map +1 -0
- package/dist/commands/update-command.d.ts +12 -0
- package/dist/commands/update-command.d.ts.map +1 -0
- package/dist/commands/update-command.js +295 -0
- package/dist/commands/update-command.js.map +1 -0
- package/dist/commands/version-command.d.ts +10 -0
- package/dist/commands/version-command.d.ts.map +1 -0
- package/dist/commands/version-command.js +100 -0
- package/dist/commands/version-command.js.map +1 -0
- package/dist/delegation/delegation-handler.d.ts +60 -0
- package/dist/delegation/delegation-handler.d.ts.map +1 -0
- package/dist/delegation/delegation-handler.js +174 -0
- package/dist/delegation/delegation-handler.js.map +1 -0
- package/dist/delegation/headless-executor.d.ts +114 -0
- package/dist/delegation/headless-executor.d.ts.map +1 -0
- package/dist/delegation/headless-executor.js +562 -0
- package/dist/delegation/headless-executor.js.map +1 -0
- package/dist/delegation/result-formatter.d.ts +108 -0
- package/dist/delegation/result-formatter.d.ts.map +1 -0
- package/dist/delegation/result-formatter.js +391 -0
- package/dist/delegation/result-formatter.js.map +1 -0
- package/dist/delegation/session-manager.d.ts +58 -0
- package/dist/delegation/session-manager.d.ts.map +1 -0
- package/dist/delegation/session-manager.js +153 -0
- package/dist/delegation/session-manager.js.map +1 -0
- package/dist/delegation/settings-parser.d.ts +31 -0
- package/dist/delegation/settings-parser.d.ts.map +1 -0
- package/dist/delegation/settings-parser.js +107 -0
- package/dist/delegation/settings-parser.js.map +1 -0
- package/dist/glmt/delta-accumulator.d.ts +210 -0
- package/dist/glmt/delta-accumulator.d.ts.map +1 -0
- package/dist/glmt/delta-accumulator.js +351 -0
- package/dist/glmt/delta-accumulator.js.map +1 -0
- package/dist/glmt/glmt-proxy.d.ts +72 -0
- package/dist/glmt/glmt-proxy.d.ts.map +1 -0
- package/dist/glmt/glmt-proxy.js +427 -0
- package/dist/glmt/glmt-proxy.js.map +1 -0
- package/dist/glmt/glmt-transformer.d.ts +265 -0
- package/dist/glmt/glmt-transformer.d.ts.map +1 -0
- package/dist/glmt/glmt-transformer.js +832 -0
- package/dist/glmt/glmt-transformer.js.map +1 -0
- package/dist/glmt/locale-enforcer.d.ts +38 -0
- package/dist/glmt/locale-enforcer.d.ts.map +1 -0
- package/dist/glmt/locale-enforcer.js +69 -0
- package/dist/glmt/locale-enforcer.js.map +1 -0
- package/dist/glmt/reasoning-enforcer.d.ts +52 -0
- package/dist/glmt/reasoning-enforcer.d.ts.map +1 -0
- package/dist/glmt/reasoning-enforcer.js +151 -0
- package/dist/glmt/reasoning-enforcer.js.map +1 -0
- package/dist/glmt/sse-parser.d.ts +47 -0
- package/dist/glmt/sse-parser.d.ts.map +1 -0
- package/dist/glmt/sse-parser.js +93 -0
- package/dist/glmt/sse-parser.js.map +1 -0
- package/dist/management/doctor.d.ts +104 -0
- package/dist/management/doctor.d.ts.map +1 -0
- package/dist/management/doctor.js +673 -0
- package/dist/management/doctor.js.map +1 -0
- package/dist/management/instance-manager.d.ts +57 -0
- package/dist/management/instance-manager.d.ts.map +1 -0
- package/dist/management/instance-manager.js +195 -0
- package/dist/management/instance-manager.js.map +1 -0
- package/dist/management/recovery-manager.d.ts +39 -0
- package/dist/management/recovery-manager.d.ts.map +1 -0
- package/dist/management/recovery-manager.js +141 -0
- package/dist/management/recovery-manager.js.map +1 -0
- package/dist/management/shared-manager.d.ts +47 -0
- package/dist/management/shared-manager.d.ts.map +1 -0
- package/dist/management/shared-manager.js +388 -0
- package/dist/management/shared-manager.js.map +1 -0
- package/dist/types/cli.d.ts +50 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +16 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/config.d.ts +51 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +26 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/delegation.d.ts +61 -0
- package/dist/types/delegation.d.ts.map +1 -0
- package/dist/types/delegation.js +6 -0
- package/dist/types/delegation.js.map +1 -0
- package/dist/types/glmt.d.ts +95 -0
- package/dist/types/glmt.d.ts.map +1 -0
- package/dist/types/glmt.js +7 -0
- package/dist/types/glmt.js.map +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/utils.d.ts +36 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/types/utils.js +22 -0
- package/dist/types/utils.js.map +1 -0
- package/dist/utils/claude-detector.d.ts +14 -0
- package/dist/utils/claude-detector.d.ts.map +1 -0
- package/dist/utils/claude-detector.js +112 -0
- package/dist/utils/claude-detector.js.map +1 -0
- package/dist/utils/claude-dir-installer.d.ts +46 -0
- package/dist/utils/claude-dir-installer.d.ts.map +1 -0
- package/dist/utils/claude-dir-installer.js +289 -0
- package/dist/utils/claude-dir-installer.js.map +1 -0
- package/dist/utils/claude-symlink-manager.d.ts +61 -0
- package/dist/utils/claude-symlink-manager.d.ts.map +1 -0
- package/dist/utils/claude-symlink-manager.js +291 -0
- package/dist/utils/claude-symlink-manager.js.map +1 -0
- package/dist/utils/config-manager.d.ts +32 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +143 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/delegation-validator.d.ts +39 -0
- package/dist/utils/delegation-validator.d.ts.map +1 -0
- package/dist/utils/delegation-validator.js +161 -0
- package/dist/utils/delegation-validator.js.map +1 -0
- package/dist/utils/error-codes.d.ts +36 -0
- package/dist/utils/error-codes.d.ts.map +1 -0
- package/dist/utils/error-codes.js +63 -0
- package/dist/utils/error-codes.js.map +1 -0
- package/dist/utils/error-manager.d.ts +59 -0
- package/dist/utils/error-manager.d.ts.map +1 -0
- package/dist/utils/error-manager.js +228 -0
- package/dist/utils/error-manager.js.map +1 -0
- package/dist/utils/helpers.d.ts +27 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +150 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/package-manager-detector.d.ts +14 -0
- package/dist/utils/package-manager-detector.d.ts.map +1 -0
- package/dist/utils/package-manager-detector.js +162 -0
- package/dist/utils/package-manager-detector.js.map +1 -0
- package/dist/utils/progress-indicator.d.ts +52 -0
- package/dist/utils/progress-indicator.d.ts.map +1 -0
- package/dist/utils/progress-indicator.js +102 -0
- package/dist/utils/progress-indicator.js.map +1 -0
- package/dist/utils/prompt.d.ts +29 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +116 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/shell-completion.d.ts +52 -0
- package/dist/utils/shell-completion.d.ts.map +1 -0
- package/dist/utils/shell-completion.js +231 -0
- package/dist/utils/shell-completion.js.map +1 -0
- package/dist/utils/shell-executor.d.ts +15 -0
- package/dist/utils/shell-executor.d.ts.map +1 -0
- package/dist/utils/shell-executor.js +57 -0
- package/dist/utils/shell-executor.js.map +1 -0
- package/dist/utils/update-checker.d.ts +48 -0
- package/dist/utils/update-checker.d.ts.map +1 -0
- package/dist/utils/update-checker.js +241 -0
- package/dist/utils/update-checker.js.map +1 -0
- package/lib/ccs +21 -1907
- package/lib/ccs.ps1 +26 -1800
- package/lib/error-codes.ps1 +2 -1
- package/lib/prompt.ps1 +2 -2
- package/package.json +31 -11
- package/scripts/add-shebang.js +39 -0
- package/scripts/bump-version.sh +25 -37
- package/scripts/dev-install.sh +32 -11
- package/scripts/postinstall.js +32 -29
- package/README.ja.md +0 -649
- package/README.vi.md +0 -649
- package/bin/auth/auth-commands.js +0 -499
- package/bin/auth/profile-detector.js +0 -204
- package/bin/auth/profile-registry.js +0 -225
- package/bin/ccs.js +0 -1034
- package/bin/delegation/README.md +0 -191
- package/bin/delegation/delegation-handler.js +0 -212
- package/bin/delegation/headless-executor.js +0 -618
- package/bin/delegation/result-formatter.js +0 -485
- package/bin/delegation/session-manager.js +0 -157
- package/bin/delegation/settings-parser.js +0 -109
- package/bin/glmt/delta-accumulator.js +0 -276
- package/bin/glmt/glmt-proxy.js +0 -495
- package/bin/glmt/glmt-transformer.js +0 -999
- package/bin/glmt/locale-enforcer.js +0 -72
- package/bin/glmt/reasoning-enforcer.js +0 -173
- package/bin/glmt/sse-parser.js +0 -96
- package/bin/management/doctor.js +0 -599
- package/bin/management/instance-manager.js +0 -207
- package/bin/management/recovery-manager.js +0 -135
- package/bin/management/shared-manager.js +0 -285
- package/bin/utils/claude-detector.js +0 -73
- package/bin/utils/claude-dir-installer.js +0 -283
- package/bin/utils/claude-symlink-manager.js +0 -289
- package/bin/utils/config-manager.js +0 -103
- package/bin/utils/delegation-validator.js +0 -154
- package/bin/utils/error-codes.js +0 -59
- package/bin/utils/error-manager.js +0 -165
- package/bin/utils/helpers.js +0 -136
- package/bin/utils/progress-indicator.js +0 -111
- package/bin/utils/prompt.js +0 -134
- package/bin/utils/shell-completion.js +0 -256
- package/bin/utils/update-checker.js +0 -243
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const SharedManager = require('./shared-manager');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Instance Manager (Simplified)
|
|
10
|
-
*
|
|
11
|
-
* Manages isolated Claude CLI instances per profile for concurrent sessions.
|
|
12
|
-
* Each instance is an isolated CLAUDE_CONFIG_DIR where users login directly.
|
|
13
|
-
* No credential copying/encryption - Claude manages credentials per instance.
|
|
14
|
-
*/
|
|
15
|
-
class InstanceManager {
|
|
16
|
-
constructor() {
|
|
17
|
-
this.instancesDir = path.join(os.homedir(), '.ccs', 'instances');
|
|
18
|
-
this.sharedManager = new SharedManager();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Ensure instance exists for profile (lazy init only)
|
|
23
|
-
* @param {string} profileName - Profile name
|
|
24
|
-
* @returns {string} Instance path
|
|
25
|
-
*/
|
|
26
|
-
ensureInstance(profileName) {
|
|
27
|
-
const instancePath = this.getInstancePath(profileName);
|
|
28
|
-
|
|
29
|
-
// Lazy initialization
|
|
30
|
-
if (!fs.existsSync(instancePath)) {
|
|
31
|
-
this.initializeInstance(profileName, instancePath);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Validate structure (auto-fix missing dirs)
|
|
35
|
-
this.validateInstance(instancePath);
|
|
36
|
-
|
|
37
|
-
return instancePath;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get instance path for profile
|
|
42
|
-
* @param {string} profileName - Profile name
|
|
43
|
-
* @returns {string} Instance directory path
|
|
44
|
-
*/
|
|
45
|
-
getInstancePath(profileName) {
|
|
46
|
-
const safeName = this._sanitizeName(profileName);
|
|
47
|
-
return path.join(this.instancesDir, safeName);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Initialize new instance directory
|
|
52
|
-
* @param {string} profileName - Profile name
|
|
53
|
-
* @param {string} instancePath - Instance directory path
|
|
54
|
-
* @throws {Error} If initialization fails
|
|
55
|
-
*/
|
|
56
|
-
initializeInstance(profileName, instancePath) {
|
|
57
|
-
try {
|
|
58
|
-
// Create base directory
|
|
59
|
-
fs.mkdirSync(instancePath, { recursive: true, mode: 0o700 });
|
|
60
|
-
|
|
61
|
-
// Create Claude-expected subdirectories (profile-specific only)
|
|
62
|
-
const subdirs = [
|
|
63
|
-
'session-env',
|
|
64
|
-
'todos',
|
|
65
|
-
'logs',
|
|
66
|
-
'file-history',
|
|
67
|
-
'shell-snapshots',
|
|
68
|
-
'debug',
|
|
69
|
-
'.anthropic'
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
subdirs.forEach(dir => {
|
|
73
|
-
const dirPath = path.join(instancePath, dir);
|
|
74
|
-
if (!fs.existsSync(dirPath)) {
|
|
75
|
-
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Symlink shared directories (Phase 1: commands, skills)
|
|
80
|
-
this.sharedManager.linkSharedDirectories(instancePath);
|
|
81
|
-
|
|
82
|
-
// Copy global configs if exist (settings.json only)
|
|
83
|
-
this._copyGlobalConfigs(instancePath);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
throw new Error(`Failed to initialize instance for ${profileName}: ${error.message}`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Validate instance directory structure (auto-fix missing directories)
|
|
91
|
-
* @param {string} instancePath - Instance path
|
|
92
|
-
*/
|
|
93
|
-
validateInstance(instancePath) {
|
|
94
|
-
// Check required directories (auto-create if missing for migration)
|
|
95
|
-
const requiredDirs = [
|
|
96
|
-
'session-env',
|
|
97
|
-
'todos',
|
|
98
|
-
'logs',
|
|
99
|
-
'file-history',
|
|
100
|
-
'shell-snapshots',
|
|
101
|
-
'debug',
|
|
102
|
-
'.anthropic'
|
|
103
|
-
];
|
|
104
|
-
|
|
105
|
-
for (const dir of requiredDirs) {
|
|
106
|
-
const dirPath = path.join(instancePath, dir);
|
|
107
|
-
if (!fs.existsSync(dirPath)) {
|
|
108
|
-
// Auto-create missing directory (migration from older versions)
|
|
109
|
-
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Note: Credentials managed by Claude CLI in instance (no validation needed)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Delete instance for profile
|
|
118
|
-
* @param {string} profileName - Profile name
|
|
119
|
-
*/
|
|
120
|
-
deleteInstance(profileName) {
|
|
121
|
-
const instancePath = this.getInstancePath(profileName);
|
|
122
|
-
|
|
123
|
-
if (!fs.existsSync(instancePath)) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Recursive delete
|
|
128
|
-
fs.rmSync(instancePath, { recursive: true, force: true });
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* List all instance names
|
|
133
|
-
* @returns {Array<string>} Instance names
|
|
134
|
-
*/
|
|
135
|
-
listInstances() {
|
|
136
|
-
if (!fs.existsSync(this.instancesDir)) {
|
|
137
|
-
return [];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return fs.readdirSync(this.instancesDir)
|
|
141
|
-
.filter(name => {
|
|
142
|
-
const instancePath = path.join(this.instancesDir, name);
|
|
143
|
-
return fs.statSync(instancePath).isDirectory();
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Check if instance exists for profile
|
|
149
|
-
* @param {string} profileName - Profile name
|
|
150
|
-
* @returns {boolean} True if exists
|
|
151
|
-
*/
|
|
152
|
-
hasInstance(profileName) {
|
|
153
|
-
const instancePath = this.getInstancePath(profileName);
|
|
154
|
-
return fs.existsSync(instancePath);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Copy global configs to instance (optional)
|
|
159
|
-
* @param {string} instancePath - Instance path
|
|
160
|
-
*/
|
|
161
|
-
_copyGlobalConfigs(instancePath) {
|
|
162
|
-
const globalConfigDir = path.join(os.homedir(), '.claude');
|
|
163
|
-
|
|
164
|
-
// Copy settings.json only (commands/skills are now symlinked to shared/)
|
|
165
|
-
const globalSettings = path.join(globalConfigDir, 'settings.json');
|
|
166
|
-
if (fs.existsSync(globalSettings)) {
|
|
167
|
-
const instanceSettings = path.join(instancePath, 'settings.json');
|
|
168
|
-
fs.copyFileSync(globalSettings, instanceSettings);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Copy directory recursively
|
|
174
|
-
* @param {string} src - Source directory
|
|
175
|
-
* @param {string} dest - Destination directory
|
|
176
|
-
*/
|
|
177
|
-
_copyDirectory(src, dest) {
|
|
178
|
-
if (!fs.existsSync(dest)) {
|
|
179
|
-
fs.mkdirSync(dest, { recursive: true, mode: 0o700 });
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
183
|
-
|
|
184
|
-
for (const entry of entries) {
|
|
185
|
-
const srcPath = path.join(src, entry.name);
|
|
186
|
-
const destPath = path.join(dest, entry.name);
|
|
187
|
-
|
|
188
|
-
if (entry.isDirectory()) {
|
|
189
|
-
this._copyDirectory(srcPath, destPath);
|
|
190
|
-
} else {
|
|
191
|
-
fs.copyFileSync(srcPath, destPath);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Sanitize profile name for filesystem
|
|
198
|
-
* @param {string} name - Profile name
|
|
199
|
-
* @returns {string} Safe name
|
|
200
|
-
*/
|
|
201
|
-
_sanitizeName(name) {
|
|
202
|
-
// Replace unsafe characters with dash
|
|
203
|
-
return name.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
module.exports = InstanceManager;
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Auto-recovery for missing or corrupted configuration
|
|
9
|
-
*/
|
|
10
|
-
class RecoveryManager {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.homedir = os.homedir();
|
|
13
|
-
this.ccsDir = path.join(this.homedir, '.ccs');
|
|
14
|
-
this.claudeDir = path.join(this.homedir, '.claude');
|
|
15
|
-
this.recovered = [];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Ensure ~/.ccs/ directory exists
|
|
20
|
-
*/
|
|
21
|
-
ensureCcsDirectory() {
|
|
22
|
-
if (!fs.existsSync(this.ccsDir)) {
|
|
23
|
-
fs.mkdirSync(this.ccsDir, { recursive: true, mode: 0o755 });
|
|
24
|
-
this.recovered.push('Created ~/.ccs/ directory');
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Ensure ~/.ccs/config.json exists with defaults
|
|
32
|
-
*/
|
|
33
|
-
ensureConfigJson() {
|
|
34
|
-
const configPath = path.join(this.ccsDir, 'config.json');
|
|
35
|
-
|
|
36
|
-
// Check if exists and valid
|
|
37
|
-
if (fs.existsSync(configPath)) {
|
|
38
|
-
try {
|
|
39
|
-
const content = fs.readFileSync(configPath, 'utf8');
|
|
40
|
-
JSON.parse(content); // Validate JSON
|
|
41
|
-
return false; // No recovery needed
|
|
42
|
-
} catch (e) {
|
|
43
|
-
// Corrupted - backup and recreate
|
|
44
|
-
const backupPath = `${configPath}.backup.${Date.now()}`;
|
|
45
|
-
fs.renameSync(configPath, backupPath);
|
|
46
|
-
this.recovered.push(`Backed up corrupted config.json to ${path.basename(backupPath)}`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Create default config
|
|
51
|
-
const defaultConfig = {
|
|
52
|
-
profiles: {
|
|
53
|
-
glm: '~/.ccs/glm.settings.json',
|
|
54
|
-
kimi: '~/.ccs/kimi.settings.json',
|
|
55
|
-
default: '~/.claude/settings.json'
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const tmpPath = `${configPath}.tmp`;
|
|
60
|
-
fs.writeFileSync(tmpPath, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf8');
|
|
61
|
-
fs.renameSync(tmpPath, configPath);
|
|
62
|
-
|
|
63
|
-
this.recovered.push('Created ~/.ccs/config.json');
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Ensure ~/.claude/settings.json exists
|
|
69
|
-
*/
|
|
70
|
-
ensureClaudeSettings() {
|
|
71
|
-
const claudeSettingsPath = path.join(this.claudeDir, 'settings.json');
|
|
72
|
-
|
|
73
|
-
// Create ~/.claude/ if missing
|
|
74
|
-
if (!fs.existsSync(this.claudeDir)) {
|
|
75
|
-
fs.mkdirSync(this.claudeDir, { recursive: true, mode: 0o755 });
|
|
76
|
-
this.recovered.push('Created ~/.claude/ directory');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Create settings.json if missing
|
|
80
|
-
if (!fs.existsSync(claudeSettingsPath)) {
|
|
81
|
-
const tmpPath = `${claudeSettingsPath}.tmp`;
|
|
82
|
-
fs.writeFileSync(tmpPath, '{}\n', 'utf8');
|
|
83
|
-
fs.renameSync(tmpPath, claudeSettingsPath);
|
|
84
|
-
|
|
85
|
-
this.recovered.push('Created ~/.claude/settings.json');
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Run all recovery operations
|
|
94
|
-
* @returns {boolean} True if any recovery performed
|
|
95
|
-
*/
|
|
96
|
-
recoverAll() {
|
|
97
|
-
this.recovered = [];
|
|
98
|
-
|
|
99
|
-
this.ensureCcsDirectory();
|
|
100
|
-
this.ensureConfigJson();
|
|
101
|
-
this.ensureClaudeSettings();
|
|
102
|
-
|
|
103
|
-
return this.recovered.length > 0;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Get recovery summary
|
|
108
|
-
* @returns {string[]} List of recovery actions performed
|
|
109
|
-
*/
|
|
110
|
-
getRecoverySummary() {
|
|
111
|
-
return this.recovered;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Show recovery hints
|
|
116
|
-
*/
|
|
117
|
-
showRecoveryHints() {
|
|
118
|
-
if (this.recovered.length === 0) return;
|
|
119
|
-
|
|
120
|
-
console.log('');
|
|
121
|
-
console.log('[i] Auto-recovery completed:');
|
|
122
|
-
this.recovered.forEach(msg => console.log(` - ${msg}`));
|
|
123
|
-
|
|
124
|
-
// Show login hint if created Claude settings
|
|
125
|
-
if (this.recovered.some(msg => msg.includes('settings.json'))) {
|
|
126
|
-
console.log('');
|
|
127
|
-
console.log('[i] Next step: Login to Claude CLI');
|
|
128
|
-
console.log(' Run: claude /login');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
console.log('');
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
module.exports = RecoveryManager;
|
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* SharedManager - Manages symlinked shared directories for CCS
|
|
9
|
-
* v3.2.0: Symlink-based architecture
|
|
10
|
-
*
|
|
11
|
-
* Purpose: Eliminates duplication by symlinking:
|
|
12
|
-
* ~/.claude/ ← ~/.ccs/shared/ ← instance/
|
|
13
|
-
*/
|
|
14
|
-
class SharedManager {
|
|
15
|
-
constructor() {
|
|
16
|
-
this.homeDir = os.homedir();
|
|
17
|
-
this.sharedDir = path.join(this.homeDir, '.ccs', 'shared');
|
|
18
|
-
this.claudeDir = path.join(this.homeDir, '.claude');
|
|
19
|
-
this.instancesDir = path.join(this.homeDir, '.ccs', 'instances');
|
|
20
|
-
this.sharedDirs = ['commands', 'skills', 'agents', 'plugins'];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Detect circular symlink before creation
|
|
25
|
-
* @param {string} target - Target path to link to
|
|
26
|
-
* @param {string} linkPath - Path where symlink will be created
|
|
27
|
-
* @returns {boolean} True if circular
|
|
28
|
-
* @private
|
|
29
|
-
*/
|
|
30
|
-
_detectCircularSymlink(target, linkPath) {
|
|
31
|
-
// Check if target exists and is symlink
|
|
32
|
-
if (!fs.existsSync(target)) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const stats = fs.lstatSync(target);
|
|
38
|
-
if (!stats.isSymbolicLink()) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Resolve target's link
|
|
43
|
-
const targetLink = fs.readlinkSync(target);
|
|
44
|
-
const resolvedTarget = path.resolve(path.dirname(target), targetLink);
|
|
45
|
-
|
|
46
|
-
// Check if target points back to our shared dir or link path
|
|
47
|
-
const sharedDir = path.join(this.homeDir, '.ccs', 'shared');
|
|
48
|
-
if (resolvedTarget.startsWith(sharedDir) || resolvedTarget === linkPath) {
|
|
49
|
-
console.log(`[!] Circular symlink detected: ${target} → ${resolvedTarget}`);
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
} catch (err) {
|
|
53
|
-
// If can't read, assume not circular
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Ensure shared directories exist as symlinks to ~/.claude/
|
|
62
|
-
* Creates ~/.claude/ structure if missing
|
|
63
|
-
*/
|
|
64
|
-
ensureSharedDirectories() {
|
|
65
|
-
// Create ~/.claude/ if missing
|
|
66
|
-
if (!fs.existsSync(this.claudeDir)) {
|
|
67
|
-
console.log('[i] Creating ~/.claude/ directory structure');
|
|
68
|
-
fs.mkdirSync(this.claudeDir, { recursive: true, mode: 0o700 });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Create shared directory
|
|
72
|
-
if (!fs.existsSync(this.sharedDir)) {
|
|
73
|
-
fs.mkdirSync(this.sharedDir, { recursive: true, mode: 0o700 });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Create symlinks ~/.ccs/shared/* → ~/.claude/*
|
|
77
|
-
for (const dir of this.sharedDirs) {
|
|
78
|
-
const claudePath = path.join(this.claudeDir, dir);
|
|
79
|
-
const sharedPath = path.join(this.sharedDir, dir);
|
|
80
|
-
|
|
81
|
-
// Create directory in ~/.claude/ if missing
|
|
82
|
-
if (!fs.existsSync(claudePath)) {
|
|
83
|
-
fs.mkdirSync(claudePath, { recursive: true, mode: 0o700 });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check for circular symlink
|
|
87
|
-
if (this._detectCircularSymlink(claudePath, sharedPath)) {
|
|
88
|
-
console.log(`[!] Skipping ${dir}: circular symlink detected`);
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// If already a symlink pointing to correct target, skip
|
|
93
|
-
if (fs.existsSync(sharedPath)) {
|
|
94
|
-
try {
|
|
95
|
-
const stats = fs.lstatSync(sharedPath);
|
|
96
|
-
if (stats.isSymbolicLink()) {
|
|
97
|
-
const currentTarget = fs.readlinkSync(sharedPath);
|
|
98
|
-
const resolvedTarget = path.resolve(path.dirname(sharedPath), currentTarget);
|
|
99
|
-
if (resolvedTarget === claudePath) {
|
|
100
|
-
continue; // Already correct
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
} catch (err) {
|
|
104
|
-
// Continue to recreate
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Remove existing directory/link
|
|
108
|
-
fs.rmSync(sharedPath, { recursive: true, force: true });
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Create symlink
|
|
112
|
-
try {
|
|
113
|
-
fs.symlinkSync(claudePath, sharedPath, 'dir');
|
|
114
|
-
} catch (err) {
|
|
115
|
-
// Windows fallback: copy directory
|
|
116
|
-
if (process.platform === 'win32') {
|
|
117
|
-
this._copyDirectoryFallback(claudePath, sharedPath);
|
|
118
|
-
console.log(`[!] Symlink failed for ${dir}, copied instead (enable Developer Mode)`);
|
|
119
|
-
} else {
|
|
120
|
-
throw err;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Link shared directories to instance
|
|
128
|
-
* @param {string} instancePath - Path to instance directory
|
|
129
|
-
*/
|
|
130
|
-
linkSharedDirectories(instancePath) {
|
|
131
|
-
this.ensureSharedDirectories();
|
|
132
|
-
|
|
133
|
-
for (const dir of this.sharedDirs) {
|
|
134
|
-
const linkPath = path.join(instancePath, dir);
|
|
135
|
-
const targetPath = path.join(this.sharedDir, dir);
|
|
136
|
-
|
|
137
|
-
// Remove existing directory/link
|
|
138
|
-
if (fs.existsSync(linkPath)) {
|
|
139
|
-
fs.rmSync(linkPath, { recursive: true, force: true });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Create symlink
|
|
143
|
-
try {
|
|
144
|
-
fs.symlinkSync(targetPath, linkPath, 'dir');
|
|
145
|
-
} catch (err) {
|
|
146
|
-
// Windows fallback
|
|
147
|
-
if (process.platform === 'win32') {
|
|
148
|
-
this._copyDirectoryFallback(targetPath, linkPath);
|
|
149
|
-
console.log(`[!] Symlink failed for ${dir}, copied instead (enable Developer Mode)`);
|
|
150
|
-
} else {
|
|
151
|
-
throw err;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Migrate from v3.1.1 (copied data in ~/.ccs/shared/) to v3.2.0 (symlinks to ~/.claude/)
|
|
159
|
-
* Runs once on upgrade
|
|
160
|
-
*/
|
|
161
|
-
migrateFromV311() {
|
|
162
|
-
// Check if migration already done (shared dirs are symlinks)
|
|
163
|
-
const commandsPath = path.join(this.sharedDir, 'commands');
|
|
164
|
-
if (fs.existsSync(commandsPath)) {
|
|
165
|
-
try {
|
|
166
|
-
if (fs.lstatSync(commandsPath).isSymbolicLink()) {
|
|
167
|
-
return; // Already migrated
|
|
168
|
-
}
|
|
169
|
-
} catch (err) {
|
|
170
|
-
// Continue with migration
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
console.log('[i] Migrating from v3.1.1 to v3.2.0...');
|
|
175
|
-
|
|
176
|
-
// Ensure ~/.claude/ exists
|
|
177
|
-
if (!fs.existsSync(this.claudeDir)) {
|
|
178
|
-
fs.mkdirSync(this.claudeDir, { recursive: true, mode: 0o700 });
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Copy user modifications from ~/.ccs/shared/ to ~/.claude/
|
|
182
|
-
for (const dir of this.sharedDirs) {
|
|
183
|
-
const sharedPath = path.join(this.sharedDir, dir);
|
|
184
|
-
const claudePath = path.join(this.claudeDir, dir);
|
|
185
|
-
|
|
186
|
-
if (!fs.existsSync(sharedPath)) continue;
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
const stats = fs.lstatSync(sharedPath);
|
|
190
|
-
if (!stats.isDirectory()) continue;
|
|
191
|
-
} catch (err) {
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Create claude dir if missing
|
|
196
|
-
if (!fs.existsSync(claudePath)) {
|
|
197
|
-
fs.mkdirSync(claudePath, { recursive: true, mode: 0o700 });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Copy files from shared to claude (preserve user modifications)
|
|
201
|
-
try {
|
|
202
|
-
const entries = fs.readdirSync(sharedPath, { withFileTypes: true });
|
|
203
|
-
let copied = 0;
|
|
204
|
-
|
|
205
|
-
for (const entry of entries) {
|
|
206
|
-
const src = path.join(sharedPath, entry.name);
|
|
207
|
-
const dest = path.join(claudePath, entry.name);
|
|
208
|
-
|
|
209
|
-
// Skip if already exists in claude
|
|
210
|
-
if (fs.existsSync(dest)) continue;
|
|
211
|
-
|
|
212
|
-
if (entry.isDirectory()) {
|
|
213
|
-
fs.cpSync(src, dest, { recursive: true });
|
|
214
|
-
} else {
|
|
215
|
-
fs.copyFileSync(src, dest);
|
|
216
|
-
}
|
|
217
|
-
copied++;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (copied > 0) {
|
|
221
|
-
console.log(`[OK] Migrated ${copied} ${dir} to ~/.claude/${dir}`);
|
|
222
|
-
}
|
|
223
|
-
} catch (err) {
|
|
224
|
-
console.log(`[!] Failed to migrate ${dir}: ${err.message}`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Now run ensureSharedDirectories to create symlinks
|
|
229
|
-
this.ensureSharedDirectories();
|
|
230
|
-
|
|
231
|
-
// Update all instances to use new symlinks
|
|
232
|
-
if (fs.existsSync(this.instancesDir)) {
|
|
233
|
-
try {
|
|
234
|
-
const instances = fs.readdirSync(this.instancesDir);
|
|
235
|
-
|
|
236
|
-
for (const instance of instances) {
|
|
237
|
-
const instancePath = path.join(this.instancesDir, instance);
|
|
238
|
-
try {
|
|
239
|
-
if (fs.statSync(instancePath).isDirectory()) {
|
|
240
|
-
this.linkSharedDirectories(instancePath);
|
|
241
|
-
}
|
|
242
|
-
} catch (err) {
|
|
243
|
-
console.log(`[!] Failed to update instance ${instance}: ${err.message}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
} catch (err) {
|
|
247
|
-
// No instances to update
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
console.log('[OK] Migration to v3.2.0 complete');
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Copy directory as fallback (Windows without Developer Mode)
|
|
256
|
-
* @param {string} src - Source directory
|
|
257
|
-
* @param {string} dest - Destination directory
|
|
258
|
-
* @private
|
|
259
|
-
*/
|
|
260
|
-
_copyDirectoryFallback(src, dest) {
|
|
261
|
-
if (!fs.existsSync(src)) {
|
|
262
|
-
fs.mkdirSync(src, { recursive: true, mode: 0o700 });
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (!fs.existsSync(dest)) {
|
|
267
|
-
fs.mkdirSync(dest, { recursive: true, mode: 0o700 });
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
271
|
-
|
|
272
|
-
for (const entry of entries) {
|
|
273
|
-
const srcPath = path.join(src, entry.name);
|
|
274
|
-
const destPath = path.join(dest, entry.name);
|
|
275
|
-
|
|
276
|
-
if (entry.isDirectory()) {
|
|
277
|
-
this._copyDirectoryFallback(srcPath, destPath);
|
|
278
|
-
} else {
|
|
279
|
-
fs.copyFileSync(srcPath, destPath);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
module.exports = SharedManager;
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const { execSync } = require('child_process');
|
|
5
|
-
const { expandPath } = require('./helpers');
|
|
6
|
-
|
|
7
|
-
// Detect Claude CLI executable
|
|
8
|
-
function detectClaudeCli() {
|
|
9
|
-
// Priority 1: CCS_CLAUDE_PATH environment variable (if user wants custom path)
|
|
10
|
-
if (process.env.CCS_CLAUDE_PATH) {
|
|
11
|
-
const ccsPath = expandPath(process.env.CCS_CLAUDE_PATH);
|
|
12
|
-
// Basic validation: file exists
|
|
13
|
-
if (fs.existsSync(ccsPath)) {
|
|
14
|
-
return ccsPath;
|
|
15
|
-
}
|
|
16
|
-
// Invalid CCS_CLAUDE_PATH - show warning and fall back to PATH
|
|
17
|
-
console.warn('[!] Warning: CCS_CLAUDE_PATH is set but file not found:', ccsPath);
|
|
18
|
-
console.warn(' Falling back to system PATH lookup...');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Priority 2: Resolve 'claude' from PATH using which/where.exe
|
|
22
|
-
// This fixes Windows npm installation where spawn() can't resolve bare command names
|
|
23
|
-
// SECURITY: Commands are hardcoded literals with no user input - safe from injection
|
|
24
|
-
const isWindows = process.platform === 'win32';
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const cmd = isWindows ? 'where.exe claude' : 'which claude';
|
|
28
|
-
const result = execSync(cmd, {
|
|
29
|
-
encoding: 'utf8',
|
|
30
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
31
|
-
timeout: 5000 // 5 second timeout to prevent hangs
|
|
32
|
-
}).trim();
|
|
33
|
-
|
|
34
|
-
// where.exe may return multiple lines (all matches in PATH order)
|
|
35
|
-
const matches = result.split('\n').map(p => p.trim()).filter(p => p);
|
|
36
|
-
|
|
37
|
-
if (isWindows) {
|
|
38
|
-
// On Windows, prefer executables with extensions (.exe, .cmd, .bat)
|
|
39
|
-
// where.exe often returns file without extension first, then the actual .cmd wrapper
|
|
40
|
-
const withExtension = matches.find(p => /\.(exe|cmd|bat|ps1)$/i.test(p));
|
|
41
|
-
const claudePath = withExtension || matches[0];
|
|
42
|
-
|
|
43
|
-
if (claudePath && fs.existsSync(claudePath)) {
|
|
44
|
-
return claudePath;
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
// On Unix, first match is fine
|
|
48
|
-
const claudePath = matches[0];
|
|
49
|
-
|
|
50
|
-
if (claudePath && fs.existsSync(claudePath)) {
|
|
51
|
-
return claudePath;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} catch (err) {
|
|
55
|
-
// Command failed - claude not in PATH
|
|
56
|
-
// Fall through to return null
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Priority 3: Claude not found
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Show Claude not found error
|
|
64
|
-
function showClaudeNotFoundError() {
|
|
65
|
-
console.error('ERROR: Claude CLI not found in PATH');
|
|
66
|
-
console.error('Install from: https://docs.claude.com/en/docs/claude-code/installation');
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
module.exports = {
|
|
71
|
-
detectClaudeCli,
|
|
72
|
-
showClaudeNotFoundError
|
|
73
|
-
};
|