@kaitranntt/ccs 5.20.0 → 6.0.0-dev.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.
- package/README.md +39 -0
- package/VERSION +1 -1
- package/dist/ccs.js +9 -1
- package/dist/ccs.js.map +1 -1
- package/dist/cliproxy/account-manager.d.ts.map +1 -1
- package/dist/cliproxy/account-manager.js +3 -1
- package/dist/cliproxy/account-manager.js.map +1 -1
- package/dist/cliproxy/cliproxy-executor.d.ts.map +1 -1
- package/dist/cliproxy/cliproxy-executor.js +145 -76
- package/dist/cliproxy/cliproxy-executor.js.map +1 -1
- package/dist/cliproxy/session-tracker.d.ts +54 -0
- package/dist/cliproxy/session-tracker.d.ts.map +1 -0
- package/dist/cliproxy/session-tracker.js +228 -0
- package/dist/cliproxy/session-tracker.js.map +1 -0
- package/dist/cliproxy/stats-fetcher.d.ts +19 -0
- package/dist/cliproxy/stats-fetcher.d.ts.map +1 -1
- package/dist/cliproxy/stats-fetcher.js +41 -3
- package/dist/cliproxy/stats-fetcher.js.map +1 -1
- package/dist/config/unified-config-loader.d.ts +33 -0
- package/dist/config/unified-config-loader.d.ts.map +1 -1
- package/dist/config/unified-config-loader.js +105 -3
- package/dist/config/unified-config-loader.js.map +1 -1
- package/dist/config/unified-config-types.d.ts +75 -1
- package/dist/config/unified-config-types.d.ts.map +1 -1
- package/dist/config/unified-config-types.js +21 -1
- package/dist/config/unified-config-types.js.map +1 -1
- package/dist/ui/assets/{accounts-achdtDUJ.js → accounts-CZmhPg-s.js} +1 -1
- package/dist/ui/assets/{analytics-BVlC8Y7-.js → analytics-BeHvdYDb.js} +34 -34
- package/dist/ui/assets/api-BJE0UHIr.js +1 -0
- package/dist/ui/assets/card-CB1ifO1P.js +1 -0
- package/dist/ui/assets/cliproxy-B7L-XME_.js +1 -0
- package/dist/ui/assets/cliproxy-control-panel-D4MuMEOJ.js +1 -0
- package/dist/ui/assets/code-editor-DxoT483F.js +2 -0
- package/dist/ui/assets/{form-utils-DKkU3nz7.js → form-utils-CKETQmt9.js} +1 -1
- package/dist/ui/assets/{health-Xbq8eUat.js → health-Dl8j1bVN.js} +1 -1
- package/dist/ui/assets/icons-Dzu62U5U.js +1 -0
- package/dist/ui/assets/index-Arbf-zPY.js +46 -0
- package/dist/ui/assets/index-B2VhinkK.css +1 -0
- package/dist/ui/assets/{radix-ui-OFtPgiRV.js → radix-ui-Bhyw9pC9.js} +2 -2
- package/dist/ui/assets/{react-vendor-CjrBBxxX.js → react-vendor-DadlvKzT.js} +1 -1
- package/dist/ui/assets/settings-D3HNz4K7.js +1 -0
- package/dist/ui/assets/shared-DiXNS0L2.js +1 -0
- package/dist/ui/assets/{tanstack-DMWkeNzM.js → tanstack-DZ3a6J0N.js} +1 -1
- package/dist/ui/index.html +7 -7
- package/dist/utils/shell-executor.d.ts.map +1 -1
- package/dist/utils/shell-executor.js +6 -1
- package/dist/utils/shell-executor.js.map +1 -1
- package/dist/utils/websearch-manager.d.ts +197 -0
- package/dist/utils/websearch-manager.d.ts.map +1 -0
- package/dist/utils/websearch-manager.js +685 -0
- package/dist/utils/websearch-manager.js.map +1 -0
- package/dist/web-server/health-service.d.ts.map +1 -1
- package/dist/web-server/health-service.js +50 -0
- package/dist/web-server/health-service.js.map +1 -1
- package/dist/web-server/routes.d.ts.map +1 -1
- package/dist/web-server/routes.js +169 -1
- package/dist/web-server/routes.js.map +1 -1
- package/dist/web-server/shutdown.d.ts +4 -4
- package/dist/web-server/shutdown.d.ts.map +1 -1
- package/dist/web-server/shutdown.js +9 -17
- package/dist/web-server/shutdown.js.map +1 -1
- package/lib/hooks/block-websearch.cjs +75 -0
- package/lib/hooks/websearch-transformer.cjs +600 -0
- package/package.json +2 -1
- package/scripts/dev-install.sh +57 -5
- package/scripts/preinstall.js +59 -0
- package/dist/ui/assets/api-BwWsFLoC.js +0 -1
- package/dist/ui/assets/cliproxy-DuaxYcVk.js +0 -1
- package/dist/ui/assets/cliproxy-control-panel-DTiBzA5u.js +0 -1
- package/dist/ui/assets/code-editor-D7CYoILm.js +0 -36
- package/dist/ui/assets/icons-Alnq4BWm.js +0 -1
- package/dist/ui/assets/index-B1gvMo-b.css +0 -1
- package/dist/ui/assets/index-C2RMogI8.js +0 -12
- package/dist/ui/assets/settings-95g3F5Gk.js +0 -1
- package/dist/ui/assets/shared-Cg5XjdQM.js +0 -1
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WebSearch Manager - Manages WebSearch hook for CCS
|
|
4
|
+
*
|
|
5
|
+
* WebSearch is a server-side tool executed by Anthropic's API.
|
|
6
|
+
* Third-party providers (gemini, agy, codex, qwen) don't have access.
|
|
7
|
+
* This manager installs a hook that uses CLI tools (Gemini CLI) as fallback.
|
|
8
|
+
*
|
|
9
|
+
* Simplified Architecture:
|
|
10
|
+
* - No MCP complexity
|
|
11
|
+
* - Uses CLI tools (currently Gemini CLI)
|
|
12
|
+
* - Easy to extend for future CLI tools (opencode, etc.)
|
|
13
|
+
*
|
|
14
|
+
* @module utils/websearch-manager
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.getMcpConfigPath = exports.hasMcpWebSearch = exports.ensureMcpWebSearch = exports.displayWebSearchStatus = exports.getWebSearchReadiness = exports.getWebSearchHookEnv = exports.getWebSearchHookConfig = exports.hasWebSearchHook = exports.installWebSearchHook = exports.getCliInstallHints = exports.hasAnyWebSearchCli = exports.getWebSearchCliProviders = exports.clearAllCliCaches = exports.clearOpenCodeCliCache = exports.hasOpenCodeCli = exports.getOpenCodeCliStatus = exports.clearGrokCliCache = exports.hasGrokCli = exports.getGrokCliStatus = exports.clearGeminiCliCache = exports.hasGeminiCli = exports.getGeminiCliStatus = void 0;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const ui_1 = require("./ui");
|
|
46
|
+
const unified_config_loader_1 = require("../config/unified-config-loader");
|
|
47
|
+
// CCS hooks directory
|
|
48
|
+
const CCS_HOOKS_DIR = path.join(os.homedir(), '.ccs', 'hooks');
|
|
49
|
+
// Hook file name
|
|
50
|
+
const WEBSEARCH_HOOK = 'websearch-transformer.cjs';
|
|
51
|
+
// Buffer time added to max provider timeout for hook timeout (seconds)
|
|
52
|
+
const HOOK_TIMEOUT_BUFFER = 30;
|
|
53
|
+
// Minimum hook timeout in seconds (fallback if no providers configured)
|
|
54
|
+
const MIN_HOOK_TIMEOUT = 60;
|
|
55
|
+
// Path to Claude settings.json
|
|
56
|
+
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
|
|
57
|
+
// Cache for Gemini CLI status (per process)
|
|
58
|
+
let geminiCliCache = null;
|
|
59
|
+
/**
|
|
60
|
+
* Check if Gemini CLI is installed globally
|
|
61
|
+
*
|
|
62
|
+
* Requires global install: `npm install -g @google/gemini-cli`
|
|
63
|
+
* No npx fallback - must be in PATH
|
|
64
|
+
*
|
|
65
|
+
* @returns Gemini CLI status with path and version
|
|
66
|
+
*/
|
|
67
|
+
function getGeminiCliStatus() {
|
|
68
|
+
// Return cached result if available
|
|
69
|
+
if (geminiCliCache) {
|
|
70
|
+
return geminiCliCache;
|
|
71
|
+
}
|
|
72
|
+
const result = {
|
|
73
|
+
installed: false,
|
|
74
|
+
path: null,
|
|
75
|
+
version: null,
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
const isWindows = process.platform === 'win32';
|
|
79
|
+
const whichCmd = isWindows ? 'where gemini' : 'which gemini';
|
|
80
|
+
const pathResult = (0, child_process_1.execSync)(whichCmd, {
|
|
81
|
+
encoding: 'utf8',
|
|
82
|
+
timeout: 5000,
|
|
83
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
84
|
+
});
|
|
85
|
+
const geminiPath = pathResult.trim().split('\n')[0]; // First result on Windows
|
|
86
|
+
if (geminiPath) {
|
|
87
|
+
result.installed = true;
|
|
88
|
+
result.path = geminiPath;
|
|
89
|
+
// Try to get version
|
|
90
|
+
try {
|
|
91
|
+
const versionResult = (0, child_process_1.execSync)('gemini --version', {
|
|
92
|
+
encoding: 'utf8',
|
|
93
|
+
timeout: 5000,
|
|
94
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
95
|
+
});
|
|
96
|
+
result.version = versionResult.trim();
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Version check failed, but CLI is installed
|
|
100
|
+
result.version = 'unknown';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Command not found - Gemini CLI not installed
|
|
106
|
+
}
|
|
107
|
+
// Cache result
|
|
108
|
+
geminiCliCache = result;
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
exports.getGeminiCliStatus = getGeminiCliStatus;
|
|
112
|
+
/**
|
|
113
|
+
* Check if Gemini CLI is available (quick boolean check)
|
|
114
|
+
*/
|
|
115
|
+
function hasGeminiCli() {
|
|
116
|
+
return getGeminiCliStatus().installed;
|
|
117
|
+
}
|
|
118
|
+
exports.hasGeminiCli = hasGeminiCli;
|
|
119
|
+
/**
|
|
120
|
+
* Clear Gemini CLI cache (for testing or after installation)
|
|
121
|
+
*/
|
|
122
|
+
function clearGeminiCliCache() {
|
|
123
|
+
geminiCliCache = null;
|
|
124
|
+
}
|
|
125
|
+
exports.clearGeminiCliCache = clearGeminiCliCache;
|
|
126
|
+
// Cache for Grok CLI status (per process)
|
|
127
|
+
let grokCliCache = null;
|
|
128
|
+
/**
|
|
129
|
+
* Check if Grok CLI is installed globally
|
|
130
|
+
*
|
|
131
|
+
* Grok CLI (grok-4-cli by lalomorales22) provides web search + X search.
|
|
132
|
+
* Requires: `npm install -g grok-cli` and XAI_API_KEY env var.
|
|
133
|
+
*
|
|
134
|
+
* @returns Grok CLI status with path and version
|
|
135
|
+
*/
|
|
136
|
+
function getGrokCliStatus() {
|
|
137
|
+
// Return cached result if available
|
|
138
|
+
if (grokCliCache) {
|
|
139
|
+
return grokCliCache;
|
|
140
|
+
}
|
|
141
|
+
const result = {
|
|
142
|
+
installed: false,
|
|
143
|
+
path: null,
|
|
144
|
+
version: null,
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
const isWindows = process.platform === 'win32';
|
|
148
|
+
const whichCmd = isWindows ? 'where grok' : 'which grok';
|
|
149
|
+
const pathResult = (0, child_process_1.execSync)(whichCmd, {
|
|
150
|
+
encoding: 'utf8',
|
|
151
|
+
timeout: 5000,
|
|
152
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
153
|
+
});
|
|
154
|
+
const grokPath = pathResult.trim().split('\n')[0]; // First result on Windows
|
|
155
|
+
if (grokPath) {
|
|
156
|
+
result.installed = true;
|
|
157
|
+
result.path = grokPath;
|
|
158
|
+
// Try to get version
|
|
159
|
+
try {
|
|
160
|
+
const versionResult = (0, child_process_1.execSync)('grok --version', {
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
timeout: 5000,
|
|
163
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
164
|
+
});
|
|
165
|
+
result.version = versionResult.trim();
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Version check failed, but CLI is installed
|
|
169
|
+
result.version = 'unknown';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Command not found - Grok CLI not installed
|
|
175
|
+
}
|
|
176
|
+
// Cache result
|
|
177
|
+
grokCliCache = result;
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
exports.getGrokCliStatus = getGrokCliStatus;
|
|
181
|
+
/**
|
|
182
|
+
* Check if Grok CLI is available (quick boolean check)
|
|
183
|
+
*/
|
|
184
|
+
function hasGrokCli() {
|
|
185
|
+
return getGrokCliStatus().installed;
|
|
186
|
+
}
|
|
187
|
+
exports.hasGrokCli = hasGrokCli;
|
|
188
|
+
/**
|
|
189
|
+
* Clear Grok CLI cache (for testing or after installation)
|
|
190
|
+
*/
|
|
191
|
+
function clearGrokCliCache() {
|
|
192
|
+
grokCliCache = null;
|
|
193
|
+
}
|
|
194
|
+
exports.clearGrokCliCache = clearGrokCliCache;
|
|
195
|
+
// Cache for OpenCode CLI status (per process)
|
|
196
|
+
let opencodeCliCache = null;
|
|
197
|
+
/**
|
|
198
|
+
* Check if OpenCode CLI is installed globally
|
|
199
|
+
*
|
|
200
|
+
* OpenCode provides built-in web search via opencode/grok-code model.
|
|
201
|
+
* Install: curl -fsSL https://opencode.ai/install | bash
|
|
202
|
+
*
|
|
203
|
+
* @returns OpenCode CLI status with path and version
|
|
204
|
+
*/
|
|
205
|
+
function getOpenCodeCliStatus() {
|
|
206
|
+
// Return cached result if available
|
|
207
|
+
if (opencodeCliCache) {
|
|
208
|
+
return opencodeCliCache;
|
|
209
|
+
}
|
|
210
|
+
const result = {
|
|
211
|
+
installed: false,
|
|
212
|
+
path: null,
|
|
213
|
+
version: null,
|
|
214
|
+
};
|
|
215
|
+
try {
|
|
216
|
+
const isWindows = process.platform === 'win32';
|
|
217
|
+
const whichCmd = isWindows ? 'where opencode' : 'which opencode';
|
|
218
|
+
const pathResult = (0, child_process_1.execSync)(whichCmd, {
|
|
219
|
+
encoding: 'utf8',
|
|
220
|
+
timeout: 5000,
|
|
221
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
222
|
+
});
|
|
223
|
+
const opencodePath = pathResult.trim().split('\n')[0]; // First result on Windows
|
|
224
|
+
if (opencodePath) {
|
|
225
|
+
result.installed = true;
|
|
226
|
+
result.path = opencodePath;
|
|
227
|
+
// Try to get version
|
|
228
|
+
try {
|
|
229
|
+
const versionResult = (0, child_process_1.execSync)('opencode --version', {
|
|
230
|
+
encoding: 'utf8',
|
|
231
|
+
timeout: 5000,
|
|
232
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
233
|
+
});
|
|
234
|
+
result.version = versionResult.trim();
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Version check failed, but CLI is installed
|
|
238
|
+
result.version = 'unknown';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Command not found - OpenCode CLI not installed
|
|
244
|
+
}
|
|
245
|
+
// Cache result
|
|
246
|
+
opencodeCliCache = result;
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
exports.getOpenCodeCliStatus = getOpenCodeCliStatus;
|
|
250
|
+
/**
|
|
251
|
+
* Check if OpenCode CLI is available (quick boolean check)
|
|
252
|
+
*/
|
|
253
|
+
function hasOpenCodeCli() {
|
|
254
|
+
return getOpenCodeCliStatus().installed;
|
|
255
|
+
}
|
|
256
|
+
exports.hasOpenCodeCli = hasOpenCodeCli;
|
|
257
|
+
/**
|
|
258
|
+
* Clear OpenCode CLI cache (for testing or after installation)
|
|
259
|
+
*/
|
|
260
|
+
function clearOpenCodeCliCache() {
|
|
261
|
+
opencodeCliCache = null;
|
|
262
|
+
}
|
|
263
|
+
exports.clearOpenCodeCliCache = clearOpenCodeCliCache;
|
|
264
|
+
/**
|
|
265
|
+
* Clear all CLI caches
|
|
266
|
+
*/
|
|
267
|
+
function clearAllCliCaches() {
|
|
268
|
+
geminiCliCache = null;
|
|
269
|
+
grokCliCache = null;
|
|
270
|
+
opencodeCliCache = null;
|
|
271
|
+
}
|
|
272
|
+
exports.clearAllCliCaches = clearAllCliCaches;
|
|
273
|
+
/**
|
|
274
|
+
* Get all WebSearch CLI providers with their status
|
|
275
|
+
*/
|
|
276
|
+
function getWebSearchCliProviders() {
|
|
277
|
+
const geminiStatus = getGeminiCliStatus();
|
|
278
|
+
const grokStatus = getGrokCliStatus();
|
|
279
|
+
const opencodeStatus = getOpenCodeCliStatus();
|
|
280
|
+
return [
|
|
281
|
+
{
|
|
282
|
+
id: 'gemini',
|
|
283
|
+
name: 'Gemini CLI',
|
|
284
|
+
command: 'gemini',
|
|
285
|
+
installed: geminiStatus.installed,
|
|
286
|
+
version: geminiStatus.version,
|
|
287
|
+
installCommand: 'npm install -g @google/gemini-cli',
|
|
288
|
+
docsUrl: 'https://github.com/google-gemini/gemini-cli',
|
|
289
|
+
requiresApiKey: false,
|
|
290
|
+
description: 'Google Gemini with web search (FREE tier: 1000 req/day)',
|
|
291
|
+
freeTier: true,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: 'opencode',
|
|
295
|
+
name: 'OpenCode',
|
|
296
|
+
command: 'opencode',
|
|
297
|
+
installed: opencodeStatus.installed,
|
|
298
|
+
version: opencodeStatus.version,
|
|
299
|
+
installCommand: 'curl -fsSL https://opencode.ai/install | bash',
|
|
300
|
+
docsUrl: 'https://github.com/sst/opencode',
|
|
301
|
+
requiresApiKey: false,
|
|
302
|
+
description: 'OpenCode with built-in web search (FREE via Zen)',
|
|
303
|
+
freeTier: true,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: 'grok',
|
|
307
|
+
name: 'Grok CLI',
|
|
308
|
+
command: 'grok',
|
|
309
|
+
installed: grokStatus.installed,
|
|
310
|
+
version: grokStatus.version,
|
|
311
|
+
installCommand: 'npm install -g @vibe-kit/grok-cli',
|
|
312
|
+
docsUrl: 'https://github.com/superagent-ai/grok-cli',
|
|
313
|
+
requiresApiKey: true,
|
|
314
|
+
apiKeyEnvVar: 'GROK_API_KEY',
|
|
315
|
+
description: 'xAI Grok CLI with AI coding agent capabilities',
|
|
316
|
+
freeTier: false,
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
exports.getWebSearchCliProviders = getWebSearchCliProviders;
|
|
321
|
+
/**
|
|
322
|
+
* Check if any WebSearch CLI is available
|
|
323
|
+
*/
|
|
324
|
+
function hasAnyWebSearchCli() {
|
|
325
|
+
return hasGeminiCli() || hasGrokCli() || hasOpenCodeCli();
|
|
326
|
+
}
|
|
327
|
+
exports.hasAnyWebSearchCli = hasAnyWebSearchCli;
|
|
328
|
+
/**
|
|
329
|
+
* Get install hints for CLI-only users when no WebSearch CLI is installed
|
|
330
|
+
*/
|
|
331
|
+
function getCliInstallHints() {
|
|
332
|
+
if (hasAnyWebSearchCli()) {
|
|
333
|
+
return [];
|
|
334
|
+
}
|
|
335
|
+
return [
|
|
336
|
+
'[i] WebSearch: No CLI tools installed',
|
|
337
|
+
' Gemini CLI (FREE): npm i -g @google/gemini-cli',
|
|
338
|
+
' OpenCode (FREE): curl -fsSL https://opencode.ai/install | bash',
|
|
339
|
+
' Grok CLI (paid): npm i -g @vibe-kit/grok-cli',
|
|
340
|
+
];
|
|
341
|
+
}
|
|
342
|
+
exports.getCliInstallHints = getCliInstallHints;
|
|
343
|
+
// ========== Hook Management ==========
|
|
344
|
+
/**
|
|
345
|
+
* Install WebSearch hook to ~/.ccs/hooks/
|
|
346
|
+
*
|
|
347
|
+
* This hook intercepts WebSearch and executes via Gemini CLI.
|
|
348
|
+
*
|
|
349
|
+
* @returns true if hook installed successfully
|
|
350
|
+
*/
|
|
351
|
+
function installWebSearchHook() {
|
|
352
|
+
try {
|
|
353
|
+
const wsConfig = (0, unified_config_loader_1.getWebSearchConfig)();
|
|
354
|
+
// Skip if disabled
|
|
355
|
+
if (!wsConfig.enabled) {
|
|
356
|
+
if (process.env.CCS_DEBUG) {
|
|
357
|
+
console.error((0, ui_1.info)('WebSearch disabled - skipping hook install'));
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
// Ensure hooks directory exists
|
|
362
|
+
if (!fs.existsSync(CCS_HOOKS_DIR)) {
|
|
363
|
+
fs.mkdirSync(CCS_HOOKS_DIR, { recursive: true, mode: 0o700 });
|
|
364
|
+
}
|
|
365
|
+
const hookPath = path.join(CCS_HOOKS_DIR, WEBSEARCH_HOOK);
|
|
366
|
+
// Find the bundled hook script
|
|
367
|
+
// In npm package: node_modules/ccs/lib/hooks/
|
|
368
|
+
// In development: lib/hooks/
|
|
369
|
+
const possiblePaths = [
|
|
370
|
+
path.join(__dirname, '..', '..', 'lib', 'hooks', WEBSEARCH_HOOK),
|
|
371
|
+
path.join(__dirname, '..', 'lib', 'hooks', WEBSEARCH_HOOK),
|
|
372
|
+
];
|
|
373
|
+
let sourcePath = null;
|
|
374
|
+
for (const p of possiblePaths) {
|
|
375
|
+
if (fs.existsSync(p)) {
|
|
376
|
+
sourcePath = p;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (!sourcePath) {
|
|
381
|
+
if (process.env.CCS_DEBUG) {
|
|
382
|
+
console.error((0, ui_1.warn)(`WebSearch hook source not found: ${WEBSEARCH_HOOK}`));
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
// Copy hook to ~/.ccs/hooks/
|
|
387
|
+
fs.copyFileSync(sourcePath, hookPath);
|
|
388
|
+
fs.chmodSync(hookPath, 0o755);
|
|
389
|
+
if (process.env.CCS_DEBUG) {
|
|
390
|
+
console.error((0, ui_1.info)(`Installed WebSearch hook: ${hookPath}`));
|
|
391
|
+
}
|
|
392
|
+
// Ensure hook is configured in settings.json
|
|
393
|
+
ensureHookConfig();
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
if (process.env.CCS_DEBUG) {
|
|
398
|
+
console.error((0, ui_1.warn)(`Failed to install WebSearch hook: ${error.message}`));
|
|
399
|
+
}
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
exports.installWebSearchHook = installWebSearchHook;
|
|
404
|
+
/**
|
|
405
|
+
* Check if WebSearch hook is installed
|
|
406
|
+
*/
|
|
407
|
+
function hasWebSearchHook() {
|
|
408
|
+
const hookPath = path.join(CCS_HOOKS_DIR, WEBSEARCH_HOOK);
|
|
409
|
+
return fs.existsSync(hookPath);
|
|
410
|
+
}
|
|
411
|
+
exports.hasWebSearchHook = hasWebSearchHook;
|
|
412
|
+
/**
|
|
413
|
+
* Get WebSearch hook configuration for settings.json
|
|
414
|
+
* Timeout is computed from max provider timeout in config.yaml + buffer
|
|
415
|
+
*/
|
|
416
|
+
function getWebSearchHookConfig() {
|
|
417
|
+
const hookPath = path.join(CCS_HOOKS_DIR, WEBSEARCH_HOOK);
|
|
418
|
+
const wsConfig = (0, unified_config_loader_1.getWebSearchConfig)();
|
|
419
|
+
// Compute max timeout from enabled providers
|
|
420
|
+
const timeouts = [];
|
|
421
|
+
if (wsConfig.providers?.gemini?.enabled && wsConfig.providers.gemini.timeout) {
|
|
422
|
+
timeouts.push(wsConfig.providers.gemini.timeout);
|
|
423
|
+
}
|
|
424
|
+
if (wsConfig.providers?.opencode?.enabled && wsConfig.providers.opencode.timeout) {
|
|
425
|
+
timeouts.push(wsConfig.providers.opencode.timeout);
|
|
426
|
+
}
|
|
427
|
+
if (wsConfig.providers?.grok?.enabled && wsConfig.providers.grok.timeout) {
|
|
428
|
+
timeouts.push(wsConfig.providers.grok.timeout);
|
|
429
|
+
}
|
|
430
|
+
// Hook timeout = max provider timeout + buffer (or minimum if none configured)
|
|
431
|
+
const maxProviderTimeout = timeouts.length > 0 ? Math.max(...timeouts) : MIN_HOOK_TIMEOUT;
|
|
432
|
+
const hookTimeout = maxProviderTimeout + HOOK_TIMEOUT_BUFFER;
|
|
433
|
+
return {
|
|
434
|
+
PreToolUse: [
|
|
435
|
+
{
|
|
436
|
+
matcher: 'WebSearch',
|
|
437
|
+
hooks: [
|
|
438
|
+
{
|
|
439
|
+
type: 'command',
|
|
440
|
+
command: `node "${hookPath}"`,
|
|
441
|
+
timeout: hookTimeout,
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
exports.getWebSearchHookConfig = getWebSearchHookConfig;
|
|
449
|
+
/**
|
|
450
|
+
* Ensure WebSearch hook is configured in ~/.claude/settings.json
|
|
451
|
+
*/
|
|
452
|
+
function ensureHookConfig() {
|
|
453
|
+
try {
|
|
454
|
+
const wsConfig = (0, unified_config_loader_1.getWebSearchConfig)();
|
|
455
|
+
if (!wsConfig.enabled) {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
// Read existing settings or start fresh
|
|
459
|
+
let settings = {};
|
|
460
|
+
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
461
|
+
try {
|
|
462
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
|
|
463
|
+
settings = JSON.parse(content);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
if (process.env.CCS_DEBUG) {
|
|
467
|
+
console.error((0, ui_1.warn)('Malformed settings.json - will merge carefully'));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Check if WebSearch hook already configured
|
|
472
|
+
const hooks = settings.hooks;
|
|
473
|
+
const expectedHookPath = path.join(CCS_HOOKS_DIR, WEBSEARCH_HOOK);
|
|
474
|
+
const expectedCommand = `node "${expectedHookPath}"`;
|
|
475
|
+
if (hooks?.PreToolUse) {
|
|
476
|
+
const webSearchHookIndex = hooks.PreToolUse.findIndex((h) => {
|
|
477
|
+
const hook = h;
|
|
478
|
+
return hook.matcher === 'WebSearch';
|
|
479
|
+
});
|
|
480
|
+
if (webSearchHookIndex !== -1) {
|
|
481
|
+
// Hook exists - check if it needs updating (different command path or timeout)
|
|
482
|
+
const existingHook = hooks.PreToolUse[webSearchHookIndex];
|
|
483
|
+
const existingHooks = existingHook.hooks;
|
|
484
|
+
const currentHookConfig = getWebSearchHookConfig();
|
|
485
|
+
const expectedHooks = currentHookConfig.PreToolUse[0]
|
|
486
|
+
.hooks;
|
|
487
|
+
const expectedTimeout = expectedHooks[0].timeout;
|
|
488
|
+
let needsUpdate = false;
|
|
489
|
+
if (existingHooks?.[0]?.command !== expectedCommand) {
|
|
490
|
+
existingHooks[0].command = expectedCommand;
|
|
491
|
+
needsUpdate = true;
|
|
492
|
+
}
|
|
493
|
+
if (existingHooks?.[0]?.timeout !== expectedTimeout) {
|
|
494
|
+
existingHooks[0].timeout = expectedTimeout;
|
|
495
|
+
needsUpdate = true;
|
|
496
|
+
}
|
|
497
|
+
if (needsUpdate) {
|
|
498
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf8');
|
|
499
|
+
if (process.env.CCS_DEBUG) {
|
|
500
|
+
console.error((0, ui_1.info)('Updated WebSearch hook config in settings.json'));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Get hook config
|
|
507
|
+
const hookConfig = getWebSearchHookConfig();
|
|
508
|
+
// Merge hook config into settings
|
|
509
|
+
if (!settings.hooks) {
|
|
510
|
+
settings.hooks = {};
|
|
511
|
+
}
|
|
512
|
+
const settingsHooks = settings.hooks;
|
|
513
|
+
if (!settingsHooks.PreToolUse) {
|
|
514
|
+
settingsHooks.PreToolUse = [];
|
|
515
|
+
}
|
|
516
|
+
// Add our hook config
|
|
517
|
+
const preToolUseHooks = hookConfig.PreToolUse;
|
|
518
|
+
settingsHooks.PreToolUse.push(...preToolUseHooks);
|
|
519
|
+
// Ensure ~/.claude directory exists
|
|
520
|
+
const claudeDir = path.dirname(CLAUDE_SETTINGS_PATH);
|
|
521
|
+
if (!fs.existsSync(claudeDir)) {
|
|
522
|
+
fs.mkdirSync(claudeDir, { recursive: true, mode: 0o700 });
|
|
523
|
+
}
|
|
524
|
+
// Write updated settings
|
|
525
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), 'utf8');
|
|
526
|
+
if (process.env.CCS_DEBUG) {
|
|
527
|
+
console.error((0, ui_1.info)('Added WebSearch hook to settings.json'));
|
|
528
|
+
}
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
if (process.env.CCS_DEBUG) {
|
|
533
|
+
console.error((0, ui_1.warn)(`Failed to configure WebSearch hook: ${error.message}`));
|
|
534
|
+
}
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// ========== Environment Variables for Hook ==========
|
|
539
|
+
/**
|
|
540
|
+
* Get environment variables for WebSearch hook configuration.
|
|
541
|
+
*
|
|
542
|
+
* Simple env vars - hook reads these to control behavior.
|
|
543
|
+
*
|
|
544
|
+
* @returns Record of environment variables to set before spawning Claude
|
|
545
|
+
*/
|
|
546
|
+
function getWebSearchHookEnv() {
|
|
547
|
+
const wsConfig = (0, unified_config_loader_1.getWebSearchConfig)();
|
|
548
|
+
const env = {};
|
|
549
|
+
// Skip hook entirely if disabled
|
|
550
|
+
if (!wsConfig.enabled) {
|
|
551
|
+
env.CCS_WEBSEARCH_SKIP = '1';
|
|
552
|
+
return env;
|
|
553
|
+
}
|
|
554
|
+
// Pass master switch
|
|
555
|
+
env.CCS_WEBSEARCH_ENABLED = '1';
|
|
556
|
+
// Pass individual provider enabled states
|
|
557
|
+
// Hook will only use providers that are BOTH enabled AND installed
|
|
558
|
+
if (wsConfig.providers?.gemini?.enabled) {
|
|
559
|
+
env.CCS_WEBSEARCH_GEMINI = '1';
|
|
560
|
+
if (wsConfig.providers.gemini.model) {
|
|
561
|
+
env.CCS_WEBSEARCH_GEMINI_MODEL = wsConfig.providers.gemini.model;
|
|
562
|
+
}
|
|
563
|
+
env.CCS_WEBSEARCH_TIMEOUT = String(wsConfig.providers.gemini.timeout || 55);
|
|
564
|
+
}
|
|
565
|
+
if (wsConfig.providers?.opencode?.enabled) {
|
|
566
|
+
env.CCS_WEBSEARCH_OPENCODE = '1';
|
|
567
|
+
if (wsConfig.providers.opencode.model) {
|
|
568
|
+
env.CCS_WEBSEARCH_OPENCODE_MODEL = wsConfig.providers.opencode.model;
|
|
569
|
+
}
|
|
570
|
+
// Use opencode timeout if no gemini timeout set
|
|
571
|
+
if (!env.CCS_WEBSEARCH_TIMEOUT) {
|
|
572
|
+
env.CCS_WEBSEARCH_TIMEOUT = String(wsConfig.providers.opencode.timeout || 90);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (wsConfig.providers?.grok?.enabled) {
|
|
576
|
+
env.CCS_WEBSEARCH_GROK = '1';
|
|
577
|
+
// Use grok timeout if no other timeout set
|
|
578
|
+
if (!env.CCS_WEBSEARCH_TIMEOUT) {
|
|
579
|
+
env.CCS_WEBSEARCH_TIMEOUT = String(wsConfig.providers.grok.timeout || 55);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// Default timeout if none set
|
|
583
|
+
if (!env.CCS_WEBSEARCH_TIMEOUT) {
|
|
584
|
+
env.CCS_WEBSEARCH_TIMEOUT = '55';
|
|
585
|
+
}
|
|
586
|
+
return env;
|
|
587
|
+
}
|
|
588
|
+
exports.getWebSearchHookEnv = getWebSearchHookEnv;
|
|
589
|
+
/**
|
|
590
|
+
* Get WebSearch readiness status for display
|
|
591
|
+
*
|
|
592
|
+
* Called on third-party profile startup to inform user.
|
|
593
|
+
*/
|
|
594
|
+
function getWebSearchReadiness() {
|
|
595
|
+
const wsConfig = (0, unified_config_loader_1.getWebSearchConfig)();
|
|
596
|
+
// Check if WebSearch is disabled entirely
|
|
597
|
+
if (!wsConfig.enabled) {
|
|
598
|
+
return {
|
|
599
|
+
readiness: 'unavailable',
|
|
600
|
+
geminiCli: false,
|
|
601
|
+
grokCli: false,
|
|
602
|
+
opencodeCli: false,
|
|
603
|
+
message: 'Disabled in config',
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
// Check all CLIs
|
|
607
|
+
const geminiInstalled = hasGeminiCli();
|
|
608
|
+
const grokInstalled = hasGrokCli();
|
|
609
|
+
const opencodeInstalled = hasOpenCodeCli();
|
|
610
|
+
// Build message based on installed CLIs
|
|
611
|
+
const installedClis = [];
|
|
612
|
+
if (geminiInstalled)
|
|
613
|
+
installedClis.push('Gemini');
|
|
614
|
+
if (grokInstalled)
|
|
615
|
+
installedClis.push('Grok');
|
|
616
|
+
if (opencodeInstalled)
|
|
617
|
+
installedClis.push('OpenCode');
|
|
618
|
+
if (installedClis.length > 0) {
|
|
619
|
+
return {
|
|
620
|
+
readiness: 'ready',
|
|
621
|
+
geminiCli: geminiInstalled,
|
|
622
|
+
grokCli: grokInstalled,
|
|
623
|
+
opencodeCli: opencodeInstalled,
|
|
624
|
+
message: `Ready (${installedClis.join(' + ')})`,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
readiness: 'unavailable',
|
|
629
|
+
geminiCli: false,
|
|
630
|
+
grokCli: false,
|
|
631
|
+
opencodeCli: false,
|
|
632
|
+
message: 'Install: npm i -g @google/gemini-cli',
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
exports.getWebSearchReadiness = getWebSearchReadiness;
|
|
636
|
+
/**
|
|
637
|
+
* Display WebSearch status (single line, equilibrium UX)
|
|
638
|
+
*
|
|
639
|
+
* Only call for third-party profiles.
|
|
640
|
+
* Shows detailed install hints when no CLI is installed.
|
|
641
|
+
*/
|
|
642
|
+
function displayWebSearchStatus() {
|
|
643
|
+
const status = getWebSearchReadiness();
|
|
644
|
+
switch (status.readiness) {
|
|
645
|
+
case 'ready':
|
|
646
|
+
console.error((0, ui_1.ok)(`WebSearch: ${status.message}`));
|
|
647
|
+
break;
|
|
648
|
+
case 'unavailable':
|
|
649
|
+
console.error((0, ui_1.fail)(`WebSearch: ${status.message}`));
|
|
650
|
+
// Show install hints for CLI-only users
|
|
651
|
+
const hints = getCliInstallHints();
|
|
652
|
+
if (hints.length > 0) {
|
|
653
|
+
for (const hint of hints) {
|
|
654
|
+
console.error((0, ui_1.info)(hint));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
exports.displayWebSearchStatus = displayWebSearchStatus;
|
|
661
|
+
// ========== Backward Compatibility Exports ==========
|
|
662
|
+
// These are kept for imports that haven't been updated yet
|
|
663
|
+
/**
|
|
664
|
+
* @deprecated Use installWebSearchHook instead - MCP is no longer used
|
|
665
|
+
*/
|
|
666
|
+
function ensureMcpWebSearch() {
|
|
667
|
+
// No-op - MCP is no longer used
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
exports.ensureMcpWebSearch = ensureMcpWebSearch;
|
|
671
|
+
/**
|
|
672
|
+
* @deprecated MCP is no longer used
|
|
673
|
+
*/
|
|
674
|
+
function hasMcpWebSearch() {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
exports.hasMcpWebSearch = hasMcpWebSearch;
|
|
678
|
+
/**
|
|
679
|
+
* @deprecated MCP is no longer used
|
|
680
|
+
*/
|
|
681
|
+
function getMcpConfigPath() {
|
|
682
|
+
return path.join(os.homedir(), '.claude', '.mcp.json');
|
|
683
|
+
}
|
|
684
|
+
exports.getMcpConfigPath = getMcpConfigPath;
|
|
685
|
+
//# sourceMappingURL=websearch-manager.js.map
|