@kaitranntt/ccs 2.4.4 → 2.4.5

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 CHANGED
@@ -108,7 +108,7 @@ export CCS_CLAUDE_PATH="/path/to/claude" # Unix
108
108
  $env:CCS_CLAUDE_PATH = "D:\Tools\Claude\claude.exe" # Windows
109
109
  ```
110
110
 
111
- **See [Troubleshooting Guide](./docs/troubleshooting.md#claude-cli-in-non-standard-location) for detailed setup instructions.**
111
+ **See [Troubleshooting Guide](./docs/en/troubleshooting.md#claude-cli-in-non-standard-location) for detailed setup instructions.**
112
112
 
113
113
  ---
114
114
 
@@ -235,7 +235,9 @@ ccs --uninstall # Remove CCS commands and skills from ~/.claude/
235
235
 
236
236
  ---
237
237
 
238
- ### 🗑️ Uninstall
238
+ ### 🗑️ Official Uninstall
239
+
240
+ **The recommended way to completely remove CCS:**
239
241
 
240
242
  **macOS / Linux**:
241
243
  ```bash
@@ -247,6 +249,16 @@ curl -fsSL ccs.kaitran.ca/uninstall | bash
247
249
  irm ccs.kaitran.ca/uninstall | iex
248
250
  ```
249
251
 
252
+ > 💡 **Why use the official uninstaller?**
253
+ > - Removes all CCS files and configurations
254
+ > - Cleans up PATH modifications
255
+ > - Removes Claude CLI commands/skills
256
+ > - Handles edge cases we've tested
257
+
258
+ **Alternative methods** (if official uninstaller fails):
259
+ - **npm**: `npm uninstall -g @kaitranntt/ccs`
260
+ - **Manual**: See [troubleshooting guide](./docs/en/troubleshooting.md#manual-uninstall)
261
+
250
262
  ---
251
263
 
252
264
  ## 🎯 Philosophy
@@ -260,17 +272,17 @@ irm ccs.kaitran.ca/uninstall | iex
260
272
  ## 📖 Documentation
261
273
 
262
274
  **Complete documentation in [docs/](./docs/)**:
263
- - [Installation Guide](./docs/installation.md)
264
- - [Configuration](./docs/configuration.md)
265
- - [Usage Examples](./docs/usage.md)
266
- - [Troubleshooting](./docs/troubleshooting.md)
267
- - [Contributing](./docs/contributing.md)
275
+ - [Installation Guide](./docs/en/installation.md)
276
+ - [Configuration](./docs/en/configuration.md)
277
+ - [Usage Examples](./docs/en/usage.md)
278
+ - [Troubleshooting](./docs/en/troubleshooting.md)
279
+ - [Contributing](./docs/en/contributing.md)
268
280
 
269
281
  ---
270
282
 
271
283
  ## 🤝 Contributing
272
284
 
273
- We welcome contributions! Please see our [Contributing Guide](./docs/contributing.md) for details.
285
+ We welcome contributions! Please see our [Contributing Guide](./docs/en/contributing.md) for details.
274
286
 
275
287
  ---
276
288
 
@@ -284,6 +296,6 @@ CCS is licensed under the [MIT License](LICENSE).
284
296
 
285
297
  **Made with ❤️ for developers who hit rate limits too often**
286
298
 
287
- [⭐ Star this repo](https://github.com/kaitranntt/ccs) | [🐛 Report issues](https://github.com/kaitranntt/ccs/issues) | [📖 Read docs](./docs/)
299
+ [⭐ Star this repo](https://github.com/kaitranntt/ccs) | [🐛 Report issues](https://github.com/kaitranntt/ccs/issues) | [📖 Read docs](./docs/en/)
288
300
 
289
301
  </div>
package/README.vi.md CHANGED
@@ -234,7 +234,9 @@ ccs --uninstall # Gỡ bỏ lệnh và kỹ năng CCS khỏi ~/.claude/
234
234
 
235
235
  ---
236
236
 
237
- ### 🗑️ Gỡ Cài Đặt
237
+ ### 🗑️ Gỡ Cài Đặt Chính Thức
238
+
239
+ **Cách được khuyến nghị để gỡ bỏ hoàn toàn CCS:**
238
240
 
239
241
  **macOS / Linux**:
240
242
  ```bash
@@ -246,6 +248,16 @@ curl -fsSL ccs.kaitran.ca/uninstall | bash
246
248
  irm ccs.kaitran.ca/uninstall | iex
247
249
  ```
248
250
 
251
+ > 💡 **Tại sao dùng uninstaller chính thức?**
252
+ > - Gỡ bỏ tất cả file và cấu hình CCS
253
+ > - Dọn dẹp PATH modifications
254
+ > - Gỡ bỏ commands/skills Claude CLI
255
+ > - Xử lý các trường hợp đặc biệt đã test
256
+
257
+ **Phương pháp thay thế** (nếu uninstaller chính thức thất bại):
258
+ - **npm**: `npm uninstall -g @kaitranntt/ccs`
259
+ - **Thủ công**: Xem [hướng dẫn khắc phục](./docs/vi/troubleshooting.vi.md#gỡ-cài-đặt-thủ-công)
260
+
249
261
  ---
250
262
 
251
263
  ## 🎯 Triết Lý
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.4.4
1
+ 2.4.5
package/bin/ccs.js CHANGED
@@ -4,35 +4,24 @@
4
4
  const { spawn } = require('child_process');
5
5
  const path = require('path');
6
6
  const fs = require('fs');
7
- const { showError, colors } = require('./helpers');
7
+ const { error } = require('./helpers');
8
8
  const { detectClaudeCli, showClaudeNotFoundError } = require('./claude-detector');
9
9
  const { getSettingsPath } = require('./config-manager');
10
10
 
11
11
  // Version (sync with package.json)
12
12
  const CCS_VERSION = require('../package.json').version;
13
13
 
14
- // Helper: Get spawn options for claude execution
15
- // On Windows, .cmd/.bat/.ps1 files need shell: true
16
- function getSpawnOptions(claudePath) {
17
- const isWindows = process.platform === 'win32';
18
- const needsShell = isWindows && /\.(cmd|bat|ps1)$/i.test(claudePath);
19
-
20
- return {
21
- stdio: 'inherit',
22
- shell: needsShell,
23
- windowsHide: true // Hide the console window on Windows
24
- };
25
- }
26
-
27
- // Helper: Escape arguments for shell execution to prevent security vulnerabilities
28
- function escapeShellArg(arg) {
29
- if (process.platform !== 'win32') {
30
- // Unix-like systems: escape single quotes and wrap in single quotes
31
- return "'" + arg.replace(/'/g, "'\"'\"'") + "'";
32
- } else {
33
- // Windows: escape double quotes and wrap in double quotes
34
- return '"' + arg.replace(/"/g, '""') + '"';
35
- }
14
+ // Execute Claude CLI with unified spawn logic
15
+ function execClaude(claudeCli, args) {
16
+ const child = spawn(claudeCli, args, { stdio: 'inherit', windowsHide: true });
17
+ child.on('exit', (code, signal) => {
18
+ if (signal) process.kill(process.pid, signal);
19
+ else process.exit(code || 0);
20
+ });
21
+ child.on('error', () => {
22
+ showClaudeNotFoundError();
23
+ process.exit(1);
24
+ });
36
25
  }
37
26
 
38
27
  // Special command handlers
@@ -51,39 +40,12 @@ function handleVersionCommand() {
51
40
 
52
41
  function handleHelpCommand(remainingArgs) {
53
42
  const claudeCli = detectClaudeCli();
54
-
55
- // Check if claude was found
56
43
  if (!claudeCli) {
57
44
  showClaudeNotFoundError();
58
45
  process.exit(1);
59
46
  }
60
47
 
61
- // Execute claude --help
62
- const spawnOpts = getSpawnOptions(claudeCli);
63
- let claudeArgs, child;
64
-
65
- if (spawnOpts.shell) {
66
- // When shell is required, escape arguments properly
67
- claudeArgs = [claudeCli, '--help', ...remainingArgs].map(escapeShellArg).join(' ');
68
- child = spawn(claudeArgs, spawnOpts);
69
- } else {
70
- // When no shell needed, use arguments array directly
71
- claudeArgs = ['--help', ...remainingArgs];
72
- child = spawn(claudeCli, claudeArgs, spawnOpts);
73
- }
74
-
75
- child.on('exit', (code, signal) => {
76
- if (signal) {
77
- process.kill(process.pid, signal);
78
- } else {
79
- process.exit(code || 0);
80
- }
81
- });
82
-
83
- child.on('error', (err) => {
84
- showClaudeNotFoundError();
85
- process.exit(1);
86
- });
48
+ execClaude(claudeCli, ['--help', ...remainingArgs]);
87
49
  }
88
50
 
89
51
  function handleInstallCommand() {
@@ -91,9 +53,7 @@ function handleInstallCommand() {
91
53
  console.log('[Installing CCS Commands and Skills]');
92
54
  console.log('Feature not yet implemented in Node.js standalone');
93
55
  console.log('Use traditional installer for now:');
94
- console.log(process.platform === 'win32'
95
- ? ' irm ccs.kaitran.ca/install | iex'
96
- : ' curl -fsSL ccs.kaitran.ca/install | bash');
56
+ console.log(' curl -fsSL ccs.kaitran.ca/install | bash');
97
57
  process.exit(0);
98
58
  }
99
59
 
@@ -151,40 +111,12 @@ function main() {
151
111
  // Special case: "default" profile just runs claude directly
152
112
  if (profile === 'default') {
153
113
  const claudeCli = detectClaudeCli();
154
-
155
- // Check if claude was found
156
114
  if (!claudeCli) {
157
115
  showClaudeNotFoundError();
158
116
  process.exit(1);
159
117
  }
160
118
 
161
- // Execute claude with args
162
- const spawnOpts = getSpawnOptions(claudeCli);
163
- let claudeArgs, child;
164
-
165
- if (spawnOpts.shell) {
166
- // When shell is required, escape arguments properly
167
- claudeArgs = [claudeCli, ...remainingArgs].map(escapeShellArg).join(' ');
168
- child = spawn(claudeArgs, spawnOpts);
169
- } else {
170
- // When no shell needed, use arguments array directly
171
- claudeArgs = remainingArgs;
172
- child = spawn(claudeCli, claudeArgs, spawnOpts);
173
- }
174
-
175
- child.on('exit', (code, signal) => {
176
- if (signal) {
177
- process.kill(process.pid, signal);
178
- } else {
179
- process.exit(code || 0);
180
- }
181
- });
182
-
183
- child.on('error', (err) => {
184
- showClaudeNotFoundError();
185
- process.exit(1);
186
- });
187
-
119
+ execClaude(claudeCli, remainingArgs);
188
120
  return;
189
121
  }
190
122
 
@@ -201,32 +133,7 @@ function main() {
201
133
  }
202
134
 
203
135
  // Execute claude with --settings
204
- const claudeArgsList = ['--settings', settingsPath, ...remainingArgs];
205
- const spawnOpts = getSpawnOptions(claudeCli);
206
- let claudeArgs, child;
207
-
208
- if (spawnOpts.shell) {
209
- // When shell is required, escape arguments properly
210
- claudeArgs = [claudeCli, ...claudeArgsList].map(escapeShellArg).join(' ');
211
- child = spawn(claudeArgs, spawnOpts);
212
- } else {
213
- // When no shell needed, use arguments array directly
214
- claudeArgs = claudeArgsList;
215
- child = spawn(claudeCli, claudeArgs, spawnOpts);
216
- }
217
-
218
- child.on('exit', (code, signal) => {
219
- if (signal) {
220
- process.kill(process.pid, signal);
221
- } else {
222
- process.exit(code || 0);
223
- }
224
- });
225
-
226
- child.on('error', (err) => {
227
- showClaudeNotFoundError();
228
- process.exit(1);
229
- });
136
+ execClaude(claudeCli, ['--settings', settingsPath, ...remainingArgs]);
230
137
  }
231
138
 
232
139
  // Run main
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const { execSync } = require('child_process');
5
- const { showError, expandPath } = require('./helpers');
5
+ const { expandPath } = require('./helpers');
6
6
 
7
7
  // Detect Claude CLI executable
8
8
  function detectClaudeCli() {
@@ -60,40 +60,11 @@ function detectClaudeCli() {
60
60
  return null;
61
61
  }
62
62
 
63
- // Show Claude not found error with diagnostics
63
+ // Show Claude not found error
64
64
  function showClaudeNotFoundError() {
65
- const isWindows = process.platform === 'win32';
66
- const pathDirs = (process.env.PATH || '').split(isWindows ? ';' : ':');
67
-
68
- const errorMsg = `Claude CLI not found in PATH
69
-
70
- CCS requires Claude CLI to be installed and available in your PATH.
71
-
72
- [i] Diagnostic Info:
73
- Platform: ${process.platform}
74
- PATH directories: ${pathDirs.length}
75
- Looking for: claude${isWindows ? '.exe' : ''}
76
-
77
- Solutions:
78
- 1. Install Claude CLI:
79
- https://docs.claude.com/en/docs/claude-code/installation
80
-
81
- 2. Verify installation:
82
- ${isWindows ? 'Get-Command claude' : 'command -v claude'}
83
-
84
- 3. If installed but not in PATH, add it:
85
- # Find Claude installation
86
- ${isWindows ? 'where.exe claude' : 'which claude'}
87
-
88
- # Or set custom path
89
- ${isWindows
90
- ? '$env:CCS_CLAUDE_PATH = \'C:\\path\\to\\claude.exe\''
91
- : 'export CCS_CLAUDE_PATH=\'/path/to/claude\''
92
- }
93
-
94
- Restart your terminal after installation.`;
95
-
96
- showError(errorMsg);
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);
97
68
  }
98
69
 
99
70
  module.exports = {
@@ -3,7 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
- const { showError, expandPath, validateProfileName } = require('./helpers');
6
+ const { error, expandPath } = require('./helpers');
7
7
 
8
8
  // Get config file path
9
9
  function getConfigPath() {
@@ -16,30 +16,7 @@ function readConfig() {
16
16
 
17
17
  // Check config exists
18
18
  if (!fs.existsSync(configPath)) {
19
- const isWindows = process.platform === 'win32';
20
- showError(`Config file not found: ${configPath}
21
-
22
- Solutions:
23
- 1. Reinstall CCS (auto-creates config):
24
- npm install -g @kaitranntt/ccs --force
25
-
26
- 2. Or use traditional installer:
27
- ${isWindows ? 'irm ccs.kaitran.ca/install | iex' : 'curl -fsSL ccs.kaitran.ca/install | bash'}
28
-
29
- 3. Or create manually:
30
- mkdir -p ~/.ccs
31
- cat > ~/.ccs/config.json << 'EOF'
32
- {
33
- "profiles": {
34
- "glm": "~/.ccs/glm.settings.json",
35
- "default": "~/.claude/settings.json"
36
- }
37
- }
38
- EOF
39
-
40
- Note: If you installed with npm --ignore-scripts, configs weren't created.
41
- Reinstall without that flag: npm install -g @kaitranntt/ccs --force`);
42
- process.exit(1);
19
+ error(`Config file not found: ${configPath}`);
43
20
  }
44
21
 
45
22
  // Read and parse JSON
@@ -48,23 +25,12 @@ EOF
48
25
  const configContent = fs.readFileSync(configPath, 'utf8');
49
26
  config = JSON.parse(configContent);
50
27
  } catch (e) {
51
- const isWindows = process.platform === 'win32';
52
- showError(`Invalid JSON in ${configPath}
53
-
54
- Fix the JSON syntax or reinstall:
55
- ${isWindows ? 'irm ccs.kaitran.ca/install | iex' : 'curl -fsSL ccs.kaitran.ca/install | bash'}`);
56
- process.exit(1);
28
+ error(`Invalid JSON in ${configPath}: ${e.message}`);
57
29
  }
58
30
 
59
31
  // Validate config has profiles object
60
32
  if (!config.profiles || typeof config.profiles !== 'object') {
61
- const isWindows = process.platform === 'win32';
62
- showError(`Config must have 'profiles' object
63
-
64
- See config.example.json for correct format
65
- Or reinstall:
66
- ${isWindows ? 'irm ccs.kaitran.ca/install | iex' : 'curl -fsSL ccs.kaitran.ca/install | bash'}`);
67
- process.exit(1);
33
+ error(`Config must have 'profiles' object in ${configPath}`);
68
34
  }
69
35
 
70
36
  return config;
@@ -74,24 +40,12 @@ Or reinstall:
74
40
  function getSettingsPath(profile) {
75
41
  const config = readConfig();
76
42
 
77
- // Validate profile name
78
- if (!validateProfileName(profile)) {
79
- showError(`Invalid profile name: ${profile}
80
-
81
- Use only alphanumeric characters, dash, or underscore.`);
82
- process.exit(1);
83
- }
84
-
85
43
  // Get settings path
86
44
  const settingsPath = config.profiles[profile];
87
45
 
88
46
  if (!settingsPath) {
89
- const availableProfiles = Object.keys(config.profiles).map(p => ` - ${p}`).join('\n');
90
- showError(`Profile '${profile}' not found in ${getConfigPath()}
91
-
92
- Available profiles:
93
- ${availableProfiles}`);
94
- process.exit(1);
47
+ const availableProfiles = Object.keys(config.profiles).join(', ');
48
+ error(`Profile '${profile}' not found. Available: ${availableProfiles}`);
95
49
  }
96
50
 
97
51
  // Expand path
@@ -99,14 +53,7 @@ ${availableProfiles}`);
99
53
 
100
54
  // Validate settings file exists
101
55
  if (!fs.existsSync(expandedPath)) {
102
- const isWindows = process.platform === 'win32';
103
- showError(`Settings file not found: ${expandedPath}
104
-
105
- Solutions:
106
- 1. Create the settings file for profile '${profile}'
107
- 2. Update the path in ${getConfigPath()}
108
- 3. Or reinstall: ${isWindows ? 'irm ccs.kaitran.ca/install | iex' : 'curl -fsSL ccs.kaitran.ca/install | bash'}`);
109
- process.exit(1);
56
+ error(`Settings file not found: ${expandedPath}`);
110
57
  }
111
58
 
112
59
  // Validate settings file is valid JSON
@@ -114,15 +61,7 @@ Solutions:
114
61
  const settingsContent = fs.readFileSync(expandedPath, 'utf8');
115
62
  JSON.parse(settingsContent);
116
63
  } catch (e) {
117
- showError(`Invalid JSON in ${expandedPath}
118
-
119
- Details: ${e.message}
120
-
121
- Solutions:
122
- 1. Validate JSON at https://jsonlint.com
123
- 2. Or reset to template: echo '{"env":{}}' > ${expandedPath}
124
- 3. Or reinstall CCS`);
125
- process.exit(1);
64
+ error(`Invalid JSON in ${expandedPath}: ${e.message}`);
126
65
  }
127
66
 
128
67
  return expandedPath;
package/bin/helpers.js CHANGED
@@ -15,15 +15,11 @@ const colors = useColors ? {
15
15
  reset: '\x1b[0m'
16
16
  } : { red: '', yellow: '', cyan: '', green: '', bold: '', reset: '' };
17
17
 
18
- // Error formatting
19
- function showError(message) {
20
- console.error('');
21
- console.error(colors.red + colors.bold + '╔═════════════════════════════════════════════╗' + colors.reset);
22
- console.error(colors.red + colors.bold + '║ ERROR ║' + colors.reset);
23
- console.error(colors.red + colors.bold + '╚═════════════════════════════════════════════╝' + colors.reset);
24
- console.error('');
25
- console.error(colors.red + message + colors.reset);
26
- console.error('');
18
+ // Simple error formatting
19
+ function error(message) {
20
+ console.error(`ERROR: ${message}`);
21
+ console.error('Try: npm install -g @kaitranntt/ccs --force');
22
+ process.exit(1);
27
23
  }
28
24
 
29
25
  // Path expansion (~ and env vars)
@@ -45,21 +41,9 @@ function expandPath(pathStr) {
45
41
  return path.normalize(pathStr);
46
42
  }
47
43
 
48
- // Validate profile name (alphanumeric, dash, underscore only)
49
- function validateProfileName(profile) {
50
- return /^[a-zA-Z0-9_-]+$/.test(profile);
51
- }
52
-
53
- // Validate path safety (prevent injection)
54
- function isPathSafe(pathStr) {
55
- // Allow: alphanumeric, path separators, space, dash, underscore, dot, colon, tilde
56
- return !/[;|&<>`$*?\[\]'"()]/.test(pathStr);
57
- }
58
44
 
59
45
  module.exports = {
60
46
  colors,
61
- showError,
62
- expandPath,
63
- validateProfileName,
64
- isPathSafe
47
+ error,
48
+ expandPath
65
49
  };