@kaitranntt/ccs 4.4.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/README.md +98 -7
  2. package/VERSION +1 -1
  3. package/config/base-agy.settings.json +10 -0
  4. package/config/base-codex.settings.json +10 -0
  5. package/config/base-gemini.settings.json +10 -0
  6. package/dist/auth/auth-commands.d.ts +52 -0
  7. package/dist/auth/auth-commands.d.ts.map +1 -0
  8. package/dist/auth/auth-commands.js +479 -0
  9. package/dist/auth/auth-commands.js.map +1 -0
  10. package/dist/auth/profile-detector.d.ts +68 -0
  11. package/dist/auth/profile-detector.d.ts.map +1 -0
  12. package/dist/auth/profile-detector.js +209 -0
  13. package/dist/auth/profile-detector.js.map +1 -0
  14. package/dist/auth/profile-registry.d.ts +60 -0
  15. package/dist/auth/profile-registry.d.ts.map +1 -0
  16. package/dist/auth/profile-registry.js +188 -0
  17. package/dist/auth/profile-registry.js.map +1 -0
  18. package/dist/ccs.d.ts +10 -0
  19. package/dist/ccs.d.ts.map +1 -0
  20. package/dist/ccs.js +320 -0
  21. package/dist/ccs.js.map +1 -0
  22. package/dist/cliproxy/auth-handler.d.ts +95 -0
  23. package/dist/cliproxy/auth-handler.d.ts.map +1 -0
  24. package/dist/cliproxy/auth-handler.js +443 -0
  25. package/dist/cliproxy/auth-handler.js.map +1 -0
  26. package/dist/cliproxy/base-config-loader.d.ts +42 -0
  27. package/dist/cliproxy/base-config-loader.d.ts.map +1 -0
  28. package/dist/cliproxy/base-config-loader.js +123 -0
  29. package/dist/cliproxy/base-config-loader.js.map +1 -0
  30. package/dist/cliproxy/binary-manager.d.ts +104 -0
  31. package/dist/cliproxy/binary-manager.d.ts.map +1 -0
  32. package/dist/cliproxy/binary-manager.js +567 -0
  33. package/dist/cliproxy/binary-manager.js.map +1 -0
  34. package/dist/cliproxy/cliproxy-executor.d.ts +33 -0
  35. package/dist/cliproxy/cliproxy-executor.d.ts.map +1 -0
  36. package/dist/cliproxy/cliproxy-executor.js +297 -0
  37. package/dist/cliproxy/cliproxy-executor.js.map +1 -0
  38. package/dist/cliproxy/config-generator.d.ts +89 -0
  39. package/dist/cliproxy/config-generator.d.ts.map +1 -0
  40. package/dist/cliproxy/config-generator.js +263 -0
  41. package/dist/cliproxy/config-generator.js.map +1 -0
  42. package/dist/cliproxy/index.d.ts +13 -0
  43. package/dist/cliproxy/index.d.ts.map +1 -0
  44. package/dist/cliproxy/index.js +62 -0
  45. package/dist/cliproxy/index.js.map +1 -0
  46. package/dist/cliproxy/platform-detector.d.ts +48 -0
  47. package/dist/cliproxy/platform-detector.d.ts.map +1 -0
  48. package/dist/cliproxy/platform-detector.js +118 -0
  49. package/dist/cliproxy/platform-detector.js.map +1 -0
  50. package/dist/cliproxy/types.d.ts +169 -0
  51. package/dist/cliproxy/types.d.ts.map +1 -0
  52. package/dist/cliproxy/types.js +7 -0
  53. package/dist/cliproxy/types.js.map +1 -0
  54. package/dist/commands/doctor-command.d.ts +10 -0
  55. package/dist/commands/doctor-command.d.ts.map +1 -0
  56. package/dist/commands/doctor-command.js +44 -0
  57. package/dist/commands/doctor-command.js.map +1 -0
  58. package/dist/commands/help-command.d.ts +5 -0
  59. package/dist/commands/help-command.d.ts.map +1 -0
  60. package/dist/commands/help-command.js +104 -0
  61. package/dist/commands/help-command.js.map +1 -0
  62. package/dist/commands/install-command.d.ts +14 -0
  63. package/dist/commands/install-command.d.ts.map +1 -0
  64. package/dist/commands/install-command.js +39 -0
  65. package/dist/commands/install-command.js.map +1 -0
  66. package/dist/commands/shell-completion-command.d.ts +10 -0
  67. package/dist/commands/shell-completion-command.d.ts.map +1 -0
  68. package/dist/commands/shell-completion-command.js +85 -0
  69. package/dist/commands/shell-completion-command.js.map +1 -0
  70. package/dist/commands/sync-command.d.ts +10 -0
  71. package/dist/commands/sync-command.d.ts.map +1 -0
  72. package/dist/commands/sync-command.js +59 -0
  73. package/dist/commands/sync-command.js.map +1 -0
  74. package/dist/commands/update-command.d.ts +12 -0
  75. package/dist/commands/update-command.d.ts.map +1 -0
  76. package/dist/commands/update-command.js +295 -0
  77. package/dist/commands/update-command.js.map +1 -0
  78. package/dist/commands/version-command.d.ts +10 -0
  79. package/dist/commands/version-command.d.ts.map +1 -0
  80. package/dist/commands/version-command.js +100 -0
  81. package/dist/commands/version-command.js.map +1 -0
  82. package/dist/delegation/delegation-handler.d.ts +60 -0
  83. package/dist/delegation/delegation-handler.d.ts.map +1 -0
  84. package/dist/delegation/delegation-handler.js +174 -0
  85. package/dist/delegation/delegation-handler.js.map +1 -0
  86. package/dist/delegation/headless-executor.d.ts +114 -0
  87. package/dist/delegation/headless-executor.d.ts.map +1 -0
  88. package/dist/delegation/headless-executor.js +562 -0
  89. package/dist/delegation/headless-executor.js.map +1 -0
  90. package/dist/delegation/result-formatter.d.ts +108 -0
  91. package/dist/delegation/result-formatter.d.ts.map +1 -0
  92. package/dist/delegation/result-formatter.js +391 -0
  93. package/dist/delegation/result-formatter.js.map +1 -0
  94. package/dist/delegation/session-manager.d.ts +58 -0
  95. package/dist/delegation/session-manager.d.ts.map +1 -0
  96. package/dist/delegation/session-manager.js +153 -0
  97. package/dist/delegation/session-manager.js.map +1 -0
  98. package/dist/delegation/settings-parser.d.ts +31 -0
  99. package/dist/delegation/settings-parser.d.ts.map +1 -0
  100. package/dist/delegation/settings-parser.js +107 -0
  101. package/dist/delegation/settings-parser.js.map +1 -0
  102. package/dist/glmt/delta-accumulator.d.ts +210 -0
  103. package/dist/glmt/delta-accumulator.d.ts.map +1 -0
  104. package/dist/glmt/delta-accumulator.js +351 -0
  105. package/dist/glmt/delta-accumulator.js.map +1 -0
  106. package/dist/glmt/glmt-proxy.d.ts +72 -0
  107. package/dist/glmt/glmt-proxy.d.ts.map +1 -0
  108. package/dist/glmt/glmt-proxy.js +427 -0
  109. package/dist/glmt/glmt-proxy.js.map +1 -0
  110. package/dist/glmt/glmt-transformer.d.ts +265 -0
  111. package/dist/glmt/glmt-transformer.d.ts.map +1 -0
  112. package/dist/glmt/glmt-transformer.js +832 -0
  113. package/dist/glmt/glmt-transformer.js.map +1 -0
  114. package/dist/glmt/locale-enforcer.d.ts +38 -0
  115. package/dist/glmt/locale-enforcer.d.ts.map +1 -0
  116. package/dist/glmt/locale-enforcer.js +69 -0
  117. package/dist/glmt/locale-enforcer.js.map +1 -0
  118. package/dist/glmt/reasoning-enforcer.d.ts +52 -0
  119. package/dist/glmt/reasoning-enforcer.d.ts.map +1 -0
  120. package/dist/glmt/reasoning-enforcer.js +151 -0
  121. package/dist/glmt/reasoning-enforcer.js.map +1 -0
  122. package/dist/glmt/sse-parser.d.ts +47 -0
  123. package/dist/glmt/sse-parser.d.ts.map +1 -0
  124. package/dist/glmt/sse-parser.js +93 -0
  125. package/dist/glmt/sse-parser.js.map +1 -0
  126. package/dist/management/doctor.d.ts +104 -0
  127. package/dist/management/doctor.d.ts.map +1 -0
  128. package/dist/management/doctor.js +673 -0
  129. package/dist/management/doctor.js.map +1 -0
  130. package/dist/management/instance-manager.d.ts +57 -0
  131. package/dist/management/instance-manager.d.ts.map +1 -0
  132. package/dist/management/instance-manager.js +195 -0
  133. package/dist/management/instance-manager.js.map +1 -0
  134. package/dist/management/recovery-manager.d.ts +39 -0
  135. package/dist/management/recovery-manager.d.ts.map +1 -0
  136. package/dist/management/recovery-manager.js +141 -0
  137. package/dist/management/recovery-manager.js.map +1 -0
  138. package/dist/management/shared-manager.d.ts +47 -0
  139. package/dist/management/shared-manager.d.ts.map +1 -0
  140. package/dist/management/shared-manager.js +388 -0
  141. package/dist/management/shared-manager.js.map +1 -0
  142. package/dist/types/cli.d.ts +50 -0
  143. package/dist/types/cli.d.ts.map +1 -0
  144. package/dist/types/cli.js +16 -0
  145. package/dist/types/cli.js.map +1 -0
  146. package/dist/types/config.d.ts +51 -0
  147. package/dist/types/config.d.ts.map +1 -0
  148. package/dist/types/config.js +26 -0
  149. package/dist/types/config.js.map +1 -0
  150. package/dist/types/delegation.d.ts +61 -0
  151. package/dist/types/delegation.d.ts.map +1 -0
  152. package/dist/types/delegation.js +6 -0
  153. package/dist/types/delegation.js.map +1 -0
  154. package/dist/types/glmt.d.ts +95 -0
  155. package/dist/types/glmt.d.ts.map +1 -0
  156. package/dist/types/glmt.js +7 -0
  157. package/dist/types/glmt.js.map +1 -0
  158. package/dist/types/index.d.ts +13 -0
  159. package/dist/types/index.d.ts.map +1 -0
  160. package/dist/types/index.js +16 -0
  161. package/dist/types/index.js.map +1 -0
  162. package/dist/types/utils.d.ts +36 -0
  163. package/dist/types/utils.d.ts.map +1 -0
  164. package/dist/types/utils.js +22 -0
  165. package/dist/types/utils.js.map +1 -0
  166. package/dist/utils/claude-detector.d.ts +14 -0
  167. package/dist/utils/claude-detector.d.ts.map +1 -0
  168. package/dist/utils/claude-detector.js +112 -0
  169. package/dist/utils/claude-detector.js.map +1 -0
  170. package/dist/utils/claude-dir-installer.d.ts +46 -0
  171. package/dist/utils/claude-dir-installer.d.ts.map +1 -0
  172. package/dist/utils/claude-dir-installer.js +289 -0
  173. package/dist/utils/claude-dir-installer.js.map +1 -0
  174. package/dist/utils/claude-symlink-manager.d.ts +61 -0
  175. package/dist/utils/claude-symlink-manager.d.ts.map +1 -0
  176. package/dist/utils/claude-symlink-manager.js +291 -0
  177. package/dist/utils/claude-symlink-manager.js.map +1 -0
  178. package/dist/utils/config-manager.d.ts +32 -0
  179. package/dist/utils/config-manager.d.ts.map +1 -0
  180. package/dist/utils/config-manager.js +143 -0
  181. package/dist/utils/config-manager.js.map +1 -0
  182. package/dist/utils/delegation-validator.d.ts +39 -0
  183. package/dist/utils/delegation-validator.d.ts.map +1 -0
  184. package/dist/utils/delegation-validator.js +161 -0
  185. package/dist/utils/delegation-validator.js.map +1 -0
  186. package/dist/utils/error-codes.d.ts +36 -0
  187. package/dist/utils/error-codes.d.ts.map +1 -0
  188. package/dist/utils/error-codes.js +63 -0
  189. package/dist/utils/error-codes.js.map +1 -0
  190. package/dist/utils/error-manager.d.ts +59 -0
  191. package/dist/utils/error-manager.d.ts.map +1 -0
  192. package/dist/utils/error-manager.js +228 -0
  193. package/dist/utils/error-manager.js.map +1 -0
  194. package/dist/utils/helpers.d.ts +27 -0
  195. package/dist/utils/helpers.d.ts.map +1 -0
  196. package/dist/utils/helpers.js +150 -0
  197. package/dist/utils/helpers.js.map +1 -0
  198. package/dist/utils/package-manager-detector.d.ts +14 -0
  199. package/dist/utils/package-manager-detector.d.ts.map +1 -0
  200. package/dist/utils/package-manager-detector.js +162 -0
  201. package/dist/utils/package-manager-detector.js.map +1 -0
  202. package/dist/utils/progress-indicator.d.ts +52 -0
  203. package/dist/utils/progress-indicator.d.ts.map +1 -0
  204. package/dist/utils/progress-indicator.js +102 -0
  205. package/dist/utils/progress-indicator.js.map +1 -0
  206. package/dist/utils/prompt.d.ts +29 -0
  207. package/dist/utils/prompt.d.ts.map +1 -0
  208. package/dist/utils/prompt.js +116 -0
  209. package/dist/utils/prompt.js.map +1 -0
  210. package/dist/utils/shell-completion.d.ts +52 -0
  211. package/dist/utils/shell-completion.d.ts.map +1 -0
  212. package/dist/utils/shell-completion.js +231 -0
  213. package/dist/utils/shell-completion.js.map +1 -0
  214. package/dist/utils/shell-executor.d.ts +15 -0
  215. package/dist/utils/shell-executor.d.ts.map +1 -0
  216. package/dist/utils/shell-executor.js +57 -0
  217. package/dist/utils/shell-executor.js.map +1 -0
  218. package/dist/utils/update-checker.d.ts +48 -0
  219. package/dist/utils/update-checker.d.ts.map +1 -0
  220. package/dist/utils/update-checker.js +241 -0
  221. package/dist/utils/update-checker.js.map +1 -0
  222. package/lib/ccs +21 -1907
  223. package/lib/ccs.ps1 +26 -1800
  224. package/lib/error-codes.ps1 +2 -1
  225. package/lib/prompt.ps1 +2 -2
  226. package/package.json +31 -11
  227. package/scripts/add-shebang.js +39 -0
  228. package/scripts/bump-version.sh +25 -37
  229. package/scripts/dev-install.sh +32 -11
  230. package/scripts/postinstall.js +29 -29
  231. package/bin/auth/auth-commands.js +0 -499
  232. package/bin/auth/profile-detector.js +0 -204
  233. package/bin/auth/profile-registry.js +0 -225
  234. package/bin/ccs.js +0 -1034
  235. package/bin/delegation/README.md +0 -191
  236. package/bin/delegation/delegation-handler.js +0 -212
  237. package/bin/delegation/headless-executor.js +0 -618
  238. package/bin/delegation/result-formatter.js +0 -485
  239. package/bin/delegation/session-manager.js +0 -157
  240. package/bin/delegation/settings-parser.js +0 -109
  241. package/bin/glmt/delta-accumulator.js +0 -276
  242. package/bin/glmt/glmt-proxy.js +0 -495
  243. package/bin/glmt/glmt-transformer.js +0 -999
  244. package/bin/glmt/locale-enforcer.js +0 -72
  245. package/bin/glmt/reasoning-enforcer.js +0 -173
  246. package/bin/glmt/sse-parser.js +0 -96
  247. package/bin/management/doctor.js +0 -721
  248. package/bin/management/instance-manager.js +0 -202
  249. package/bin/management/recovery-manager.js +0 -135
  250. package/bin/management/shared-manager.js +0 -402
  251. package/bin/utils/claude-detector.js +0 -73
  252. package/bin/utils/claude-dir-installer.js +0 -283
  253. package/bin/utils/claude-symlink-manager.js +0 -289
  254. package/bin/utils/config-manager.js +0 -103
  255. package/bin/utils/delegation-validator.js +0 -154
  256. package/bin/utils/error-codes.js +0 -59
  257. package/bin/utils/error-manager.js +0 -165
  258. package/bin/utils/helpers.js +0 -136
  259. package/bin/utils/progress-indicator.js +0 -111
  260. package/bin/utils/prompt.js +0 -134
  261. package/bin/utils/shell-completion.js +0 -256
  262. package/bin/utils/update-checker.js +0 -243
@@ -1,618 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const { spawn } = require('child_process');
5
- const path = require('path');
6
- const os = require('os');
7
- const fs = require('fs');
8
- const { SessionManager } = require('./session-manager');
9
- const { SettingsParser } = require('./settings-parser');
10
-
11
- /**
12
- * Headless executor for Claude CLI delegation
13
- * Spawns claude with -p flag for single-turn execution
14
- */
15
- class HeadlessExecutor {
16
- /**
17
- * Execute task via headless Claude CLI
18
- * @param {string} profile - Profile name (glm, kimi, custom)
19
- * @param {string} enhancedPrompt - Enhanced prompt with context
20
- * @param {Object} options - Execution options
21
- * @param {string} options.cwd - Working directory (absolute path)
22
- * @param {number} options.timeout - Timeout in milliseconds (default: 600000 = 10 minutes)
23
- * @param {string} options.outputFormat - Output format: 'stream-json' or 'text' (default: 'stream-json')
24
- * @param {string} options.permissionMode - Permission mode: 'default', 'plan', 'acceptEdits', 'bypassPermissions' (default: 'acceptEdits')
25
- * @param {boolean} options.resumeSession - Resume last session for profile (default: false)
26
- * @param {string} options.sessionId - Specific session ID to resume
27
- * @returns {Promise<Object>} Execution result
28
- */
29
- static async execute(profile, enhancedPrompt, options = {}) {
30
- const {
31
- cwd = process.cwd(),
32
- timeout = 600000, // 10 minutes default
33
- outputFormat = 'stream-json', // Use stream-json for real-time progress
34
- permissionMode = 'acceptEdits',
35
- resumeSession = false,
36
- sessionId = null
37
- } = options;
38
-
39
- // Validate permission mode
40
- this._validatePermissionMode(permissionMode);
41
-
42
- // Initialize session manager
43
- const sessionMgr = new SessionManager();
44
-
45
- // Detect Claude CLI path
46
- const claudeCli = this._detectClaudeCli();
47
- if (!claudeCli) {
48
- throw new Error('Claude CLI not found in PATH. Install from: https://docs.claude.com/en/docs/claude-code/installation');
49
- }
50
-
51
- // Get settings path for profile
52
- const settingsPath = path.join(os.homedir(), '.ccs', `${profile}.settings.json`);
53
-
54
- // Validate settings file exists
55
- if (!fs.existsSync(settingsPath)) {
56
- throw new Error(`Settings file not found: ${settingsPath}\nProfile "${profile}" may not be configured.`);
57
- }
58
-
59
- // Smart slash command detection and preservation
60
- // Detects if prompt contains slash command and restructures for proper execution
61
- const processedPrompt = this._processSlashCommand(enhancedPrompt);
62
-
63
- // Prepare arguments
64
- const args = ['-p', processedPrompt, '--settings', settingsPath];
65
-
66
- // Always use stream-json for real-time progress visibility
67
- // Note: --verbose is required when using --print with stream-json
68
- args.push('--output-format', 'stream-json', '--verbose');
69
-
70
- // Add permission mode
71
- if (permissionMode && permissionMode !== 'default') {
72
- if (permissionMode === 'bypassPermissions') {
73
- args.push('--dangerously-skip-permissions');
74
- // Warn about dangerous mode
75
- if (process.env.CCS_DEBUG) {
76
- console.warn('[!] WARNING: Using --dangerously-skip-permissions mode');
77
- console.warn('[!] This bypasses ALL permission checks. Use only in trusted environments.');
78
- }
79
- } else {
80
- args.push('--permission-mode', permissionMode);
81
- }
82
- }
83
-
84
- // Add resume flag for multi-turn sessions
85
- if (resumeSession) {
86
- const lastSession = sessionMgr.getLastSession(profile);
87
-
88
- if (lastSession) {
89
- args.push('--resume', lastSession.sessionId);
90
- if (process.env.CCS_DEBUG) {
91
- const cost = lastSession.totalCost !== undefined && lastSession.totalCost !== null ? lastSession.totalCost.toFixed(4) : '0.0000';
92
- console.error(`[i] Resuming session: ${lastSession.sessionId} (${lastSession.turns} turns, $${cost})`);
93
- }
94
- } else if (sessionId) {
95
- args.push('--resume', sessionId);
96
- if (process.env.CCS_DEBUG) {
97
- console.error(`[i] Resuming specific session: ${sessionId}`);
98
- }
99
- } else {
100
- console.warn('[!] No previous session found, starting new session');
101
- }
102
- } else if (sessionId) {
103
- args.push('--resume', sessionId);
104
- if (process.env.CCS_DEBUG) {
105
- console.error(`[i] Resuming specific session: ${sessionId}`);
106
- }
107
- }
108
-
109
- // Add tool restrictions from settings
110
- const toolRestrictions = SettingsParser.parseToolRestrictions(cwd);
111
-
112
- if (toolRestrictions.allowedTools.length > 0) {
113
- args.push('--allowedTools');
114
- toolRestrictions.allowedTools.forEach(tool => args.push(tool));
115
- }
116
-
117
- if (toolRestrictions.disallowedTools.length > 0) {
118
- args.push('--disallowedTools');
119
- toolRestrictions.disallowedTools.forEach(tool => args.push(tool));
120
- }
121
-
122
- // Note: No max-turns limit - using time-based limits instead (default 10min timeout)
123
-
124
- // Debug log args
125
- if (process.env.CCS_DEBUG) {
126
- console.error(`[i] Claude CLI args: ${args.join(' ')}`);
127
- }
128
-
129
- // Execute with spawn
130
- return new Promise((resolve, reject) => {
131
- const startTime = Date.now();
132
-
133
- // Show progress unless explicitly disabled with CCS_QUIET
134
- const showProgress = !process.env.CCS_QUIET;
135
-
136
- // Show initial progress message
137
- if (showProgress) {
138
- const modelName = profile === 'glm' ? 'GLM-4.6' : profile === 'kimi' ? 'Kimi' : profile.toUpperCase();
139
- console.error(`[i] Delegating to ${modelName}...`);
140
- }
141
-
142
- const proc = spawn(claudeCli, args, {
143
- cwd,
144
- stdio: ['ignore', 'pipe', 'pipe'],
145
- timeout
146
- });
147
-
148
- let stdout = '';
149
- let stderr = '';
150
- let progressInterval;
151
- const messages = []; // Accumulate stream-json messages
152
- let partialLine = ''; // Buffer for incomplete JSON lines
153
-
154
- // Handle parent process termination (Ctrl+C or Esc in Claude)
155
- // When main Claude session is killed, cleanup spawned child process
156
- const cleanupHandler = () => {
157
- if (!proc.killed) {
158
- if (process.env.CCS_DEBUG) {
159
- console.error('[!] Parent process terminating, killing delegated session...');
160
- }
161
- proc.kill('SIGTERM');
162
- // Force kill if not dead after 2s
163
- setTimeout(() => {
164
- if (!proc.killed) {
165
- proc.kill('SIGKILL');
166
- }
167
- }, 2000);
168
- }
169
- };
170
-
171
- // Register signal handlers for parent process termination
172
- process.once('SIGINT', cleanupHandler);
173
- process.once('SIGTERM', cleanupHandler);
174
-
175
- // Cleanup signal handlers when child process exits
176
- const removeSignalHandlers = () => {
177
- process.removeListener('SIGINT', cleanupHandler);
178
- process.removeListener('SIGTERM', cleanupHandler);
179
- };
180
-
181
- proc.on('close', removeSignalHandlers);
182
- proc.on('error', removeSignalHandlers);
183
-
184
- // Progress indicator (show elapsed time every 5 seconds)
185
- if (showProgress) {
186
- progressInterval = setInterval(() => {
187
- const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
188
- process.stderr.write(`[i] Still running... ${elapsed}s elapsed\r`);
189
- }, 5000);
190
- }
191
-
192
- // Capture stdout (stream-json format - jsonl)
193
- proc.stdout.on('data', (data) => {
194
- stdout += data.toString();
195
-
196
- // Parse stream-json messages (jsonl format - one JSON per line)
197
- const chunk = partialLine + data.toString();
198
- const lines = chunk.split('\n');
199
- partialLine = lines.pop() || ''; // Save incomplete line for next chunk
200
-
201
- for (const line of lines) {
202
- if (!line.trim()) continue;
203
-
204
- try {
205
- const msg = JSON.parse(line);
206
- messages.push(msg);
207
-
208
- // Show real-time tool use with verbose details
209
- if (showProgress && msg.type === 'assistant') {
210
- const toolUses = msg.message?.content?.filter(c => c.type === 'tool_use') || [];
211
-
212
- for (const tool of toolUses) {
213
- process.stderr.write('\r\x1b[K'); // Clear line
214
-
215
- // Show verbose tool use with description/input if available
216
- const toolInput = tool.input || {};
217
- let verboseMsg = `[Tool] ${tool.name}`;
218
-
219
- // Add context based on tool type (all Claude Code tools)
220
- switch (tool.name) {
221
- case 'Bash':
222
- if (toolInput.command) {
223
- // Truncate long commands
224
- const cmd = toolInput.command.length > 80
225
- ? toolInput.command.substring(0, 77) + '...'
226
- : toolInput.command;
227
- verboseMsg += `: ${cmd}`;
228
- }
229
- break;
230
-
231
- case 'Edit':
232
- case 'Write':
233
- case 'Read':
234
- if (toolInput.file_path) {
235
- verboseMsg += `: ${toolInput.file_path}`;
236
- }
237
- break;
238
-
239
- case 'NotebookEdit':
240
- case 'NotebookRead':
241
- if (toolInput.notebook_path) {
242
- verboseMsg += `: ${toolInput.notebook_path}`;
243
- }
244
- break;
245
-
246
- case 'Grep':
247
- if (toolInput.pattern) {
248
- verboseMsg += `: searching for "${toolInput.pattern}"`;
249
- if (toolInput.path) {
250
- verboseMsg += ` in ${toolInput.path}`;
251
- }
252
- }
253
- break;
254
-
255
- case 'Glob':
256
- if (toolInput.pattern) {
257
- verboseMsg += `: ${toolInput.pattern}`;
258
- }
259
- break;
260
-
261
- case 'SlashCommand':
262
- if (toolInput.command) {
263
- verboseMsg += `: ${toolInput.command}`;
264
- }
265
- break;
266
-
267
- case 'Task':
268
- if (toolInput.description) {
269
- verboseMsg += `: ${toolInput.description}`;
270
- } else if (toolInput.prompt) {
271
- const prompt = toolInput.prompt.length > 60
272
- ? toolInput.prompt.substring(0, 57) + '...'
273
- : toolInput.prompt;
274
- verboseMsg += `: ${prompt}`;
275
- }
276
- break;
277
-
278
- case 'TodoWrite':
279
- if (toolInput.todos && Array.isArray(toolInput.todos)) {
280
- // Show in_progress task instead of just count
281
- const inProgressTask = toolInput.todos.find(t => t.status === 'in_progress');
282
- if (inProgressTask && inProgressTask.activeForm) {
283
- verboseMsg += `: ${inProgressTask.activeForm}`;
284
- } else {
285
- // Fallback to count if no in_progress task
286
- verboseMsg += `: ${toolInput.todos.length} task(s)`;
287
- }
288
- }
289
- break;
290
-
291
- case 'WebFetch':
292
- if (toolInput.url) {
293
- verboseMsg += `: ${toolInput.url}`;
294
- }
295
- break;
296
-
297
- case 'WebSearch':
298
- if (toolInput.query) {
299
- verboseMsg += `: "${toolInput.query}"`;
300
- }
301
- break;
302
-
303
- default:
304
- // For unknown tools, show first meaningful parameter
305
- if (Object.keys(toolInput).length > 0) {
306
- const firstKey = Object.keys(toolInput)[0];
307
- const firstValue = toolInput[firstKey];
308
- if (typeof firstValue === 'string' && firstValue.length < 60) {
309
- verboseMsg += `: ${firstValue}`;
310
- }
311
- }
312
- }
313
-
314
- process.stderr.write(`${verboseMsg}\n`);
315
- }
316
- }
317
- } catch (parseError) {
318
- // Skip malformed JSON lines (shouldn't happen with stream-json)
319
- if (process.env.CCS_DEBUG) {
320
- console.error(`[!] Failed to parse stream-json line: ${parseError.message}`);
321
- }
322
- }
323
- }
324
- });
325
-
326
- // Stream stderr in real-time (progress messages from Claude CLI)
327
- proc.stderr.on('data', (data) => {
328
- const stderrText = data.toString();
329
- stderr += stderrText;
330
-
331
- // Show stderr in real-time if in TTY
332
- if (showProgress) {
333
- // Clear progress line before showing stderr
334
- if (progressInterval) {
335
- process.stderr.write('\r\x1b[K'); // Clear line
336
- }
337
- process.stderr.write(stderrText);
338
- }
339
- });
340
-
341
- // Handle completion
342
- proc.on('close', (exitCode) => {
343
- const duration = Date.now() - startTime;
344
-
345
- // Clear progress indicator
346
- if (progressInterval) {
347
- clearInterval(progressInterval);
348
- process.stderr.write('\r\x1b[K'); // Clear line
349
- }
350
-
351
- // Show completion message
352
- if (showProgress) {
353
- const durationSec = (duration / 1000).toFixed(1);
354
- if (timedOut) {
355
- console.error(`[i] Execution timed out after ${durationSec}s`);
356
- } else {
357
- console.error(`[i] Execution completed in ${durationSec}s`);
358
- }
359
- console.error(''); // Blank line before formatted output
360
- }
361
-
362
- const result = {
363
- exitCode,
364
- stdout,
365
- stderr,
366
- cwd,
367
- profile,
368
- duration,
369
- timedOut,
370
- success: exitCode === 0 && !timedOut,
371
- messages // Include all stream-json messages
372
- };
373
-
374
- // Extract metadata from final 'result' message in stream-json
375
- const resultMessage = messages.find(m => m.type === 'result');
376
- if (resultMessage) {
377
- // Add parsed fields from result message
378
- result.sessionId = resultMessage.session_id || null;
379
- result.totalCost = resultMessage.total_cost_usd || 0;
380
- result.numTurns = resultMessage.num_turns || 0;
381
- result.isError = resultMessage.is_error || false;
382
- result.type = resultMessage.type || null;
383
- result.subtype = resultMessage.subtype || null;
384
- result.durationApi = resultMessage.duration_api_ms || 0;
385
- result.permissionDenials = resultMessage.permission_denials || [];
386
- result.errors = resultMessage.errors || [];
387
-
388
- // Extract content from result message
389
- result.content = resultMessage.result || '';
390
- } else {
391
- // Fallback: no result message found (shouldn't happen)
392
- result.content = stdout;
393
- if (process.env.CCS_DEBUG) {
394
- console.error(`[!] No result message found in stream-json output`);
395
- }
396
- }
397
-
398
- // Store or update session if we have session ID (even on timeout, for :continue support)
399
- if (result.sessionId) {
400
- if (resumeSession || sessionId) {
401
- // Update existing session
402
- sessionMgr.updateSession(profile, result.sessionId, {
403
- totalCost: result.totalCost
404
- });
405
- } else {
406
- // Store new session
407
- sessionMgr.storeSession(profile, {
408
- sessionId: result.sessionId,
409
- totalCost: result.totalCost,
410
- cwd: result.cwd
411
- });
412
- }
413
-
414
- // Cleanup expired sessions periodically
415
- if (Math.random() < 0.1) { // 10% chance
416
- sessionMgr.cleanupExpired();
417
- }
418
- }
419
-
420
- resolve(result);
421
- });
422
-
423
- // Handle errors
424
- proc.on('error', (error) => {
425
- if (progressInterval) {
426
- clearInterval(progressInterval);
427
- }
428
- reject(new Error(`Failed to execute Claude CLI: ${error.message}`));
429
- });
430
-
431
- // Handle timeout with graceful SIGTERM then forceful SIGKILL
432
- let timedOut = false;
433
- if (timeout > 0) {
434
- const timeoutHandle = setTimeout(() => {
435
- if (!proc.killed) {
436
- timedOut = true;
437
-
438
- if (progressInterval) {
439
- clearInterval(progressInterval);
440
- process.stderr.write('\r\x1b[K'); // Clear line
441
- }
442
-
443
- if (process.env.CCS_DEBUG) {
444
- console.error(`[!] Timeout reached after ${timeout}ms, sending SIGTERM for graceful shutdown...`);
445
- }
446
-
447
- // Send SIGTERM for graceful shutdown
448
- proc.kill('SIGTERM');
449
-
450
- // If process doesn't terminate within 10s, force kill
451
- setTimeout(() => {
452
- if (!proc.killed) {
453
- if (process.env.CCS_DEBUG) {
454
- console.error(`[!] Process did not terminate gracefully, sending SIGKILL...`);
455
- }
456
- proc.kill('SIGKILL');
457
- }
458
- }, 10000); // Give 10s for graceful shutdown instead of 5s
459
- }
460
- }, timeout);
461
-
462
- // Clear timeout on successful completion
463
- proc.on('close', () => clearTimeout(timeoutHandle));
464
- }
465
- });
466
- }
467
-
468
- /**
469
- * Validate permission mode
470
- * @param {string} mode - Permission mode
471
- * @throws {Error} If mode is invalid
472
- * @private
473
- */
474
- static _validatePermissionMode(mode) {
475
- const VALID_MODES = ['default', 'plan', 'acceptEdits', 'bypassPermissions'];
476
- if (!VALID_MODES.includes(mode)) {
477
- throw new Error(
478
- `Invalid permission mode: "${mode}". Valid modes: ${VALID_MODES.join(', ')}`
479
- );
480
- }
481
- }
482
-
483
- /**
484
- * Detect Claude CLI executable
485
- * @returns {string|null} Path to claude CLI or null if not found
486
- * @private
487
- */
488
- static _detectClaudeCli() {
489
- // Check environment variable override
490
- if (process.env.CCS_CLAUDE_PATH) {
491
- return process.env.CCS_CLAUDE_PATH;
492
- }
493
-
494
- // Try to find in PATH
495
- const { execSync } = require('child_process');
496
- try {
497
- const result = execSync('command -v claude', { encoding: 'utf8' });
498
- return result.trim();
499
- } catch (error) {
500
- return null;
501
- }
502
- }
503
-
504
- /**
505
- * Execute with retry logic
506
- * @param {string} profile - Profile name
507
- * @param {string} enhancedPrompt - Enhanced prompt
508
- * @param {Object} options - Execution options
509
- * @param {number} options.maxRetries - Maximum retry attempts (default: 2)
510
- * @returns {Promise<Object>} Execution result
511
- */
512
- static async executeWithRetry(profile, enhancedPrompt, options = {}) {
513
- const { maxRetries = 2, ...execOptions } = options;
514
- let lastError;
515
-
516
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
517
- try {
518
- const result = await this.execute(profile, enhancedPrompt, execOptions);
519
-
520
- // If successful, return immediately
521
- if (result.success) {
522
- return result;
523
- }
524
-
525
- // If not last attempt, retry
526
- if (attempt < maxRetries) {
527
- console.error(`[!] Attempt ${attempt + 1} failed, retrying...`);
528
- await this._sleep(1000 * (attempt + 1)); // Exponential backoff
529
- continue;
530
- }
531
-
532
- // Last attempt failed, return result anyway
533
- return result;
534
- } catch (error) {
535
- lastError = error;
536
-
537
- if (attempt < maxRetries) {
538
- console.error(`[!] Attempt ${attempt + 1} errored: ${error.message}, retrying...`);
539
- await this._sleep(1000 * (attempt + 1));
540
- }
541
- }
542
- }
543
-
544
- // All retries exhausted
545
- throw lastError || new Error('Execution failed after all retry attempts');
546
- }
547
-
548
- /**
549
- * Sleep utility for retry backoff
550
- * @param {number} ms - Milliseconds to sleep
551
- * @returns {Promise<void>}
552
- * @private
553
- */
554
- static _sleep(ms) {
555
- return new Promise(resolve => setTimeout(resolve, ms));
556
- }
557
-
558
- /**
559
- * Process prompt to detect and preserve slash commands
560
- * Implements smart enhancement: preserves slash command at start, allows context in rest
561
- * @param {string} prompt - Original prompt (may contain slash command)
562
- * @returns {string} Processed prompt with slash command preserved
563
- * @private
564
- */
565
- static _processSlashCommand(prompt) {
566
- const trimmed = prompt.trim();
567
-
568
- // Case 1: Already starts with slash command - keep as-is
569
- if (trimmed.match(/^\/[\w:-]+(\s|$)/)) {
570
- return prompt;
571
- }
572
-
573
- // Case 2: Find slash command embedded in text
574
- // Look for /command that's NOT part of a file path
575
- // File paths: /home/user, /path/to/file (have / before or after)
576
- // Commands: /cook, /plan (standalone, preceded by space/colon/start)
577
- // Strategy: Find LAST occurrence that looks like a command, not a path
578
- const embeddedSlash = trimmed.match(/(?:^|[^\w/])(\/[\w:-]+)(\s+[\s\S]*)?$/);
579
-
580
- if (embeddedSlash) {
581
- const command = embeddedSlash[1]; // e.g., "/cook"
582
- const args = (embeddedSlash[2] || '').trim(); // Everything after command
583
-
584
- // Calculate where the command starts (excluding preceding char if any)
585
- const matchStart = embeddedSlash.index + (embeddedSlash[0][0] === '/' ? 0 : 1);
586
- const beforeCommand = trimmed.substring(0, matchStart).trim();
587
-
588
- // Restructure: command first, context after
589
- if (beforeCommand && args) {
590
- return `${command} ${args}\n\nContext: ${beforeCommand}`;
591
- } else if (beforeCommand) {
592
- return `${command}\n\nContext: ${beforeCommand}`;
593
- }
594
- return args ? `${command} ${args}` : command;
595
- }
596
-
597
- // No slash command detected, return as-is
598
- return prompt;
599
- }
600
-
601
- /**
602
- * Test if profile is executable (quick health check)
603
- * @param {string} profile - Profile name
604
- * @returns {Promise<boolean>} True if profile can execute
605
- */
606
- static async testProfile(profile) {
607
- try {
608
- const result = await this.execute(profile, 'Say "test successful"', {
609
- timeout: 10000
610
- });
611
- return result.success;
612
- } catch (error) {
613
- return false;
614
- }
615
- }
616
- }
617
-
618
- module.exports = { HeadlessExecutor };