@kaitranntt/ccs 7.63.0 → 7.63.1-dev.2
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 +10 -3
- package/dist/api/services/profile-lifecycle-service.js +4 -4
- package/dist/api/services/profile-lifecycle-service.js.map +1 -1
- package/dist/api/services/profile-writer.d.ts.map +1 -1
- package/dist/api/services/profile-writer.js +3 -5
- package/dist/api/services/profile-writer.js.map +1 -1
- package/dist/ccs.js +21 -9
- package/dist/ccs.js.map +1 -1
- package/dist/cliproxy/accounts/email-account-identity.d.ts +12 -0
- package/dist/cliproxy/accounts/email-account-identity.d.ts.map +1 -0
- package/dist/cliproxy/accounts/email-account-identity.js +124 -0
- package/dist/cliproxy/accounts/email-account-identity.js.map +1 -0
- package/dist/cliproxy/accounts/query.d.ts.map +1 -1
- package/dist/cliproxy/accounts/query.js +15 -8
- package/dist/cliproxy/accounts/query.js.map +1 -1
- package/dist/cliproxy/accounts/registry.d.ts +6 -0
- package/dist/cliproxy/accounts/registry.d.ts.map +1 -1
- package/dist/cliproxy/accounts/registry.js +136 -42
- package/dist/cliproxy/accounts/registry.js.map +1 -1
- package/dist/cliproxy/auth/token-manager.d.ts.map +1 -1
- package/dist/cliproxy/auth/token-manager.js +45 -11
- package/dist/cliproxy/auth/token-manager.js.map +1 -1
- package/dist/cliproxy/executor/index.d.ts.map +1 -1
- package/dist/cliproxy/executor/index.js +25 -12
- package/dist/cliproxy/executor/index.js.map +1 -1
- package/dist/cliproxy/quota-fetcher-codex.d.ts +0 -3
- package/dist/cliproxy/quota-fetcher-codex.d.ts.map +1 -1
- package/dist/cliproxy/quota-fetcher-codex.js +46 -17
- package/dist/cliproxy/quota-fetcher-codex.js.map +1 -1
- package/dist/cliproxy/services/variant-settings.d.ts.map +1 -1
- package/dist/cliproxy/services/variant-settings.js +4 -6
- package/dist/cliproxy/services/variant-settings.js.map +1 -1
- package/dist/cliproxy/stats-transformer.d.ts.map +1 -1
- package/dist/cliproxy/stats-transformer.js +26 -3
- package/dist/cliproxy/stats-transformer.js.map +1 -1
- package/dist/commands/cliproxy/quota-subcommand.d.ts.map +1 -1
- package/dist/commands/cliproxy/quota-subcommand.js +25 -22
- package/dist/commands/cliproxy/quota-subcommand.js.map +1 -1
- package/dist/commands/cliproxy/variant-subcommand.d.ts.map +1 -1
- package/dist/commands/cliproxy/variant-subcommand.js +14 -6
- package/dist/commands/cliproxy/variant-subcommand.js.map +1 -1
- package/dist/commands/install-command.d.ts.map +1 -1
- package/dist/commands/install-command.js +8 -2
- package/dist/commands/install-command.js.map +1 -1
- package/dist/copilot/copilot-executor.d.ts.map +1 -1
- package/dist/copilot/copilot-executor.js +11 -2
- package/dist/copilot/copilot-executor.js.map +1 -1
- package/dist/delegation/executor/result-aggregator.d.ts +2 -1
- package/dist/delegation/executor/result-aggregator.d.ts.map +1 -1
- package/dist/delegation/executor/result-aggregator.js +21 -1
- package/dist/delegation/executor/result-aggregator.js.map +1 -1
- package/dist/delegation/executor/types.d.ts +6 -0
- package/dist/delegation/executor/types.d.ts.map +1 -1
- package/dist/delegation/headless-executor.d.ts.map +1 -1
- package/dist/delegation/headless-executor.js +69 -4
- package/dist/delegation/headless-executor.js.map +1 -1
- package/dist/management/instance-manager.d.ts +1 -1
- package/dist/management/instance-manager.d.ts.map +1 -1
- package/dist/management/instance-manager.js +10 -2
- package/dist/management/instance-manager.js.map +1 -1
- package/dist/shared/compatible-cli-contracts.d.ts +4 -0
- package/dist/shared/compatible-cli-contracts.d.ts.map +1 -1
- package/dist/targets/codex-adapter.d.ts.map +1 -1
- package/dist/targets/codex-adapter.js +78 -3
- package/dist/targets/codex-adapter.js.map +1 -1
- package/dist/targets/codex-detector.d.ts.map +1 -1
- package/dist/targets/codex-detector.js +28 -7
- package/dist/targets/codex-detector.js.map +1 -1
- package/dist/ui/assets/{accounts-DkxZnPJE.js → accounts-CUUJb7DN.js} +1 -1
- package/dist/ui/assets/{alert-dialog-CiYMglgR.js → alert-dialog-B3irFOWw.js} +1 -1
- package/dist/ui/assets/{api-DaOtMRT4.js → api-D-Qqy1yz.js} +2 -2
- package/dist/ui/assets/{auth-section-BMaKBRA_.js → auth-section-DIo3G68G.js} +1 -1
- package/dist/ui/assets/{backups-section-DOpSADoH.js → backups-section-AFO82gIq.js} +1 -1
- package/dist/ui/assets/{channels-zDFV-BlC.js → channels-BCTKZ7gt.js} +1 -1
- package/dist/ui/assets/{checkbox-Cb5AZBZL.js → checkbox-jPTeVbcz.js} +1 -1
- package/dist/ui/assets/{claude-extension-B5RngGem.js → claude-extension-BNiAud6L.js} +1 -1
- package/dist/ui/assets/cliproxy-CoYvAxcj.js +3 -0
- package/dist/ui/assets/{cliproxy-ai-providers-DVaaS-CT.js → cliproxy-ai-providers-C9jXuZcV.js} +1 -1
- package/dist/ui/assets/cliproxy-control-panel-DRSjNcrR.js +1 -0
- package/dist/ui/assets/codex-DSAAz_k8.js +27 -0
- package/dist/ui/assets/{confirm-dialog-B9vRgowr.js → confirm-dialog-Dn7GsgSZ.js} +1 -1
- package/dist/ui/assets/{copilot-HvsOp6hu.js → copilot-Ddvc59py.js} +2 -2
- package/dist/ui/assets/{cursor-C1XOjAWS.js → cursor-CPLxNsYY.js} +1 -1
- package/dist/ui/assets/{droid-DshEfT1H.js → droid-C5ArrKfL.js} +1 -1
- package/dist/ui/assets/{globalenv-section-CmcMkb6z.js → globalenv-section-CddBw-9L.js} +1 -1
- package/dist/ui/assets/{health-CE0VQs6K.js → health-7Wp3Ar7n.js} +1 -1
- package/dist/ui/assets/icons-C7Np5k44.js +1 -0
- package/dist/ui/assets/{index-CmKclBR1.js → index-1GqjYVWP.js} +1 -1
- package/dist/ui/assets/{index-CmtSgCxo.js → index-BPIA8mAF.js} +1 -1
- package/dist/ui/assets/index-BsTZB-Im.css +1 -0
- package/dist/ui/assets/{index-DAtuJuGe.js → index-C7OpByHy.js} +36 -36
- package/dist/ui/assets/index-CBwOAp7P.js +1 -0
- package/dist/ui/assets/{index-CesVGA6m.js → index-DSgKWnG8.js} +1 -1
- package/dist/ui/assets/{masked-input-B2tcbvAj.js → masked-input-BlP9jFt5.js} +1 -1
- package/dist/ui/assets/{proxy-status-widget-BnJD49TF.js → proxy-status-widget-CFyrt5Yw.js} +1 -1
- package/dist/ui/assets/{raw-json-settings-editor-panel-DnUbq1__.js → raw-json-settings-editor-panel-CfcTOhs4.js} +1 -1
- package/dist/ui/assets/{searchable-select-ULayr5K1.js → searchable-select-CcCH2Ug3.js} +1 -1
- package/dist/ui/assets/{separator--ZH5ZM-3.js → separator-DRULo1eG.js} +1 -1
- package/dist/ui/assets/shared-ByRk7nxS.js +8 -0
- package/dist/ui/assets/{switch-DmDIWykO.js → switch-BO_9wSB8.js} +1 -1
- package/dist/ui/assets/{table-E5IxHhrW.js → table-84HxfWMM.js} +1 -1
- package/dist/ui/assets/tanstack-CkjseTWE.js +4 -0
- package/dist/ui/assets/updates-BgPdUQoA.js +1 -0
- package/dist/ui/index.html +4 -4
- package/dist/utils/claude-config-path.d.ts +2 -0
- package/dist/utils/claude-config-path.d.ts.map +1 -1
- package/dist/utils/claude-config-path.js +6 -1
- package/dist/utils/claude-config-path.js.map +1 -1
- package/dist/utils/websearch/claude-tool-args.d.ts +5 -0
- package/dist/utils/websearch/claude-tool-args.d.ts.map +1 -0
- package/dist/utils/websearch/claude-tool-args.js +125 -0
- package/dist/utils/websearch/claude-tool-args.js.map +1 -0
- package/dist/utils/websearch/hook-env.d.ts.map +1 -1
- package/dist/utils/websearch/hook-env.js +8 -0
- package/dist/utils/websearch/hook-env.js.map +1 -1
- package/dist/utils/websearch/hook-installer.d.ts +3 -2
- package/dist/utils/websearch/hook-installer.d.ts.map +1 -1
- package/dist/utils/websearch/hook-installer.js +3 -2
- package/dist/utils/websearch/hook-installer.js.map +1 -1
- package/dist/utils/websearch/index.d.ts +3 -0
- package/dist/utils/websearch/index.d.ts.map +1 -1
- package/dist/utils/websearch/index.js +23 -2
- package/dist/utils/websearch/index.js.map +1 -1
- package/dist/utils/websearch/mcp-installer.d.ts +14 -0
- package/dist/utils/websearch/mcp-installer.d.ts.map +1 -0
- package/dist/utils/websearch/mcp-installer.js +351 -0
- package/dist/utils/websearch/mcp-installer.js.map +1 -0
- package/dist/utils/websearch/profile-hook-injector.d.ts +5 -3
- package/dist/utils/websearch/profile-hook-injector.d.ts.map +1 -1
- package/dist/utils/websearch/profile-hook-injector.js +5 -3
- package/dist/utils/websearch/profile-hook-injector.js.map +1 -1
- package/dist/utils/websearch/status.d.ts.map +1 -1
- package/dist/utils/websearch/status.js +67 -1
- package/dist/utils/websearch/status.js.map +1 -1
- package/dist/utils/websearch/trace.d.ts +23 -0
- package/dist/utils/websearch/trace.d.ts.map +1 -0
- package/dist/utils/websearch/trace.js +206 -0
- package/dist/utils/websearch/trace.js.map +1 -0
- package/dist/utils/websearch-manager.d.ts +11 -11
- package/dist/utils/websearch-manager.d.ts.map +1 -1
- package/dist/utils/websearch-manager.js +32 -17
- package/dist/utils/websearch-manager.js.map +1 -1
- package/dist/web-server/index.d.ts.map +1 -1
- package/dist/web-server/index.js +3 -0
- package/dist/web-server/index.js.map +1 -1
- package/dist/web-server/routes/account-routes.d.ts.map +1 -1
- package/dist/web-server/routes/account-routes.js +2 -1
- package/dist/web-server/routes/account-routes.js.map +1 -1
- package/dist/web-server/routes/cliproxy-auth-routes.d.ts.map +1 -1
- package/dist/web-server/routes/cliproxy-auth-routes.js +1 -1
- package/dist/web-server/routes/cliproxy-auth-routes.js.map +1 -1
- package/dist/web-server/routes/cliproxy-local-proxy.d.ts +20 -0
- package/dist/web-server/routes/cliproxy-local-proxy.d.ts.map +1 -0
- package/dist/web-server/routes/cliproxy-local-proxy.js +117 -0
- package/dist/web-server/routes/cliproxy-local-proxy.js.map +1 -0
- package/dist/web-server/services/codex-dashboard-service.d.ts.map +1 -1
- package/dist/web-server/services/codex-dashboard-service.js +27 -8
- package/dist/web-server/services/codex-dashboard-service.js.map +1 -1
- package/lib/hooks/websearch-transformer.cjs +660 -96
- package/lib/mcp/ccs-websearch-server.cjs +339 -0
- package/package.json +2 -1
- package/scripts/github/normalize-ai-review-output.mjs +328 -0
- package/dist/ui/assets/cliproxy-VYe0Qov1.js +0 -3
- package/dist/ui/assets/cliproxy-control-panel-FVIQcFti.js +0 -1
- package/dist/ui/assets/codex-D2yIwOs4.js +0 -27
- package/dist/ui/assets/icons-EMBHZkGo.js +0 -1
- package/dist/ui/assets/index-6dNBcNC3.js +0 -1
- package/dist/ui/assets/index-BAuT6yuc.css +0 -1
- package/dist/ui/assets/shared-qizFb9Ye.js +0 -8
- package/dist/ui/assets/tanstack-B8i0evp-.js +0 -4
- package/dist/ui/assets/updates-2Uu4Mgtg.js +0 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
getActiveProviderIds,
|
|
5
|
+
getQueryFingerprint,
|
|
6
|
+
getSkipReason,
|
|
7
|
+
hasAnyActiveProviders,
|
|
8
|
+
runLocalWebSearch,
|
|
9
|
+
shouldSkipHook,
|
|
10
|
+
traceWebSearchEvent,
|
|
11
|
+
} = require('../hooks/websearch-transformer.cjs');
|
|
12
|
+
|
|
13
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
14
|
+
const SERVER_NAME = 'ccs-websearch';
|
|
15
|
+
const SERVER_VERSION = '1.0.0';
|
|
16
|
+
const TOOL_NAME = 'WebSearch';
|
|
17
|
+
const TOOL_ALIASES = ['search'];
|
|
18
|
+
const TOOL_DESCRIPTION =
|
|
19
|
+
'Third-party WebSearch replacement for CCS-managed Claude launches. Use this instead of Bash/curl/http fetches for web lookups. Provider order: Exa, Tavily, Brave Search, DuckDuckGo, then optional legacy CLI fallback.';
|
|
20
|
+
|
|
21
|
+
function isSupportedToolName(name) {
|
|
22
|
+
return name === TOOL_NAME || TOOL_ALIASES.includes(name);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let inputBuffer = Buffer.alloc(0);
|
|
26
|
+
const sessionState = {
|
|
27
|
+
initializeCount: 0,
|
|
28
|
+
toolsListCount: 0,
|
|
29
|
+
exposed: false,
|
|
30
|
+
toolCalls: 0,
|
|
31
|
+
};
|
|
32
|
+
let sessionSummaryWritten = false;
|
|
33
|
+
|
|
34
|
+
function shouldExposeTools() {
|
|
35
|
+
return !shouldSkipHook() && hasAnyActiveProviders();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getTools() {
|
|
39
|
+
if (!shouldExposeTools()) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
name: TOOL_NAME,
|
|
46
|
+
description: TOOL_DESCRIPTION,
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
query: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description:
|
|
53
|
+
'Web query to resolve through CCS providers. Prefer this tool over ad hoc Bash/curl lookups when you need current web information.',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ['query'],
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeMessage(message) {
|
|
64
|
+
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeResponse(id, result) {
|
|
68
|
+
writeMessage({
|
|
69
|
+
jsonrpc: '2.0',
|
|
70
|
+
id,
|
|
71
|
+
result,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writeError(id, code, message) {
|
|
76
|
+
writeMessage({
|
|
77
|
+
jsonrpc: '2.0',
|
|
78
|
+
id,
|
|
79
|
+
error: {
|
|
80
|
+
code,
|
|
81
|
+
message,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function handleToolCall(message) {
|
|
87
|
+
const id = message.id;
|
|
88
|
+
const params = message.params || {};
|
|
89
|
+
const toolArgs = params.arguments || {};
|
|
90
|
+
const toolName = params.name || '<missing>';
|
|
91
|
+
const query = typeof toolArgs.query === 'string' ? toolArgs.query.trim() : '';
|
|
92
|
+
const fingerprint = getQueryFingerprint(query);
|
|
93
|
+
|
|
94
|
+
if (!isSupportedToolName(toolName)) {
|
|
95
|
+
traceWebSearchEvent('mcp_tool_call_rejected', {
|
|
96
|
+
source: 'mcp',
|
|
97
|
+
reason: 'unknown_tool',
|
|
98
|
+
toolName,
|
|
99
|
+
});
|
|
100
|
+
writeError(id, -32602, `Unknown tool: ${toolName}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
sessionState.toolCalls += 1;
|
|
105
|
+
traceWebSearchEvent('mcp_tool_call_received', {
|
|
106
|
+
source: 'mcp',
|
|
107
|
+
toolName,
|
|
108
|
+
...fingerprint,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!shouldExposeTools()) {
|
|
112
|
+
traceWebSearchEvent('mcp_tool_call_unavailable', {
|
|
113
|
+
source: 'mcp',
|
|
114
|
+
toolName,
|
|
115
|
+
exposed: false,
|
|
116
|
+
skipReason: getSkipReason(),
|
|
117
|
+
activeProviderIds: getActiveProviderIds(),
|
|
118
|
+
...fingerprint,
|
|
119
|
+
});
|
|
120
|
+
writeResponse(id, {
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: 'text',
|
|
124
|
+
text: 'CCS WebSearch is unavailable for this profile or no providers are ready.',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
isError: true,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!query) {
|
|
133
|
+
traceWebSearchEvent('mcp_tool_call_rejected', {
|
|
134
|
+
source: 'mcp',
|
|
135
|
+
reason: 'empty_query',
|
|
136
|
+
toolName,
|
|
137
|
+
});
|
|
138
|
+
writeError(id, -32602, `Tool "${TOOL_NAME}" requires a non-empty string query.`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result = await runLocalWebSearch(query);
|
|
143
|
+
if (result.success) {
|
|
144
|
+
traceWebSearchEvent('mcp_tool_call_result', {
|
|
145
|
+
source: 'mcp',
|
|
146
|
+
toolName,
|
|
147
|
+
success: true,
|
|
148
|
+
providerId: result.providerId,
|
|
149
|
+
providerName: result.providerName,
|
|
150
|
+
...fingerprint,
|
|
151
|
+
});
|
|
152
|
+
writeResponse(id, {
|
|
153
|
+
content: [{ type: 'text', text: result.content }],
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
traceWebSearchEvent('mcp_tool_call_result', {
|
|
159
|
+
source: 'mcp',
|
|
160
|
+
toolName,
|
|
161
|
+
success: false,
|
|
162
|
+
noActiveProviders: Boolean(result.noActiveProviders),
|
|
163
|
+
errorCount: result.errors.length,
|
|
164
|
+
...fingerprint,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const errorDetail =
|
|
168
|
+
result.noActiveProviders || result.errors.length === 0
|
|
169
|
+
? 'No active WebSearch providers are ready.'
|
|
170
|
+
: result.errors.map((entry) => `${entry.provider}: ${entry.error}`).join(' | ');
|
|
171
|
+
|
|
172
|
+
writeResponse(id, {
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: 'text',
|
|
176
|
+
text: `CCS local WebSearch failed for "${query}". ${errorDetail}`,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
isError: true,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function handleMessage(message) {
|
|
184
|
+
if (!message || message.jsonrpc !== '2.0' || typeof message.method !== 'string') {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
switch (message.method) {
|
|
189
|
+
case 'initialize':
|
|
190
|
+
sessionState.initializeCount += 1;
|
|
191
|
+
sessionState.exposed = sessionState.exposed || shouldExposeTools();
|
|
192
|
+
traceWebSearchEvent('mcp_initialize', {
|
|
193
|
+
source: 'mcp',
|
|
194
|
+
exposed: shouldExposeTools(),
|
|
195
|
+
skipReason: getSkipReason(),
|
|
196
|
+
activeProviderIds: getActiveProviderIds(),
|
|
197
|
+
});
|
|
198
|
+
writeResponse(message.id, {
|
|
199
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
200
|
+
capabilities: {
|
|
201
|
+
tools: {},
|
|
202
|
+
},
|
|
203
|
+
serverInfo: {
|
|
204
|
+
name: SERVER_NAME,
|
|
205
|
+
version: SERVER_VERSION,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
case 'notifications/initialized':
|
|
210
|
+
return;
|
|
211
|
+
case 'ping':
|
|
212
|
+
writeResponse(message.id, {});
|
|
213
|
+
return;
|
|
214
|
+
case 'tools/list':
|
|
215
|
+
sessionState.toolsListCount += 1;
|
|
216
|
+
{
|
|
217
|
+
const tools = getTools();
|
|
218
|
+
const exposed = tools.length > 0;
|
|
219
|
+
sessionState.exposed = sessionState.exposed || exposed;
|
|
220
|
+
traceWebSearchEvent('mcp_tools_list', {
|
|
221
|
+
source: 'mcp',
|
|
222
|
+
exposed,
|
|
223
|
+
toolNames: tools.map((tool) => tool.name),
|
|
224
|
+
activeProviderIds: getActiveProviderIds(),
|
|
225
|
+
skipReason: getSkipReason(),
|
|
226
|
+
});
|
|
227
|
+
writeResponse(message.id, { tools });
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
case 'tools/call':
|
|
231
|
+
await handleToolCall(message);
|
|
232
|
+
return;
|
|
233
|
+
default:
|
|
234
|
+
if (message.id !== undefined) {
|
|
235
|
+
writeError(message.id, -32601, `Method not found: ${message.method}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function writeSessionSummary(exitCodeOrSignal) {
|
|
241
|
+
if (sessionSummaryWritten) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
sessionSummaryWritten = true;
|
|
246
|
+
traceWebSearchEvent('mcp_session_summary', {
|
|
247
|
+
source: 'mcp',
|
|
248
|
+
initializeCount: sessionState.initializeCount,
|
|
249
|
+
toolsListCount: sessionState.toolsListCount,
|
|
250
|
+
exposed: sessionState.exposed,
|
|
251
|
+
toolCalls: sessionState.toolCalls,
|
|
252
|
+
calledWebSearch: sessionState.toolCalls > 0,
|
|
253
|
+
likelyBypassed: sessionState.exposed && sessionState.toolCalls === 0 ? 'unknown' : false,
|
|
254
|
+
activeProviderIds: getActiveProviderIds(),
|
|
255
|
+
skipReason: getSkipReason(),
|
|
256
|
+
exitCode: typeof exitCodeOrSignal === 'number' ? exitCodeOrSignal : null,
|
|
257
|
+
exitSignal: typeof exitCodeOrSignal === 'string' ? exitCodeOrSignal : null,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function parseMessages() {
|
|
262
|
+
while (true) {
|
|
263
|
+
let body;
|
|
264
|
+
const startsWithLegacyHeaders = inputBuffer
|
|
265
|
+
.slice(0, Math.min(inputBuffer.length, 32))
|
|
266
|
+
.toString('utf8')
|
|
267
|
+
.toLowerCase()
|
|
268
|
+
.startsWith('content-length:');
|
|
269
|
+
|
|
270
|
+
if (startsWithLegacyHeaders) {
|
|
271
|
+
const headerEnd = inputBuffer.indexOf('\r\n\r\n');
|
|
272
|
+
if (headerEnd === -1) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const headerText = inputBuffer.slice(0, headerEnd).toString('utf8');
|
|
277
|
+
const contentLengthMatch = headerText.match(/content-length:\s*(\d+)/i);
|
|
278
|
+
if (!contentLengthMatch) {
|
|
279
|
+
inputBuffer = Buffer.alloc(0);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const contentLength = Number.parseInt(contentLengthMatch[1], 10);
|
|
284
|
+
const messageEnd = headerEnd + 4 + contentLength;
|
|
285
|
+
if (inputBuffer.length < messageEnd) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
body = inputBuffer.slice(headerEnd + 4, messageEnd).toString('utf8');
|
|
290
|
+
inputBuffer = inputBuffer.slice(messageEnd);
|
|
291
|
+
} else {
|
|
292
|
+
const newlineIndex = inputBuffer.indexOf('\n');
|
|
293
|
+
if (newlineIndex === -1) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
body = inputBuffer.slice(0, newlineIndex).toString('utf8').replace(/\r$/, '').trim();
|
|
298
|
+
inputBuffer = inputBuffer.slice(newlineIndex + 1);
|
|
299
|
+
if (!body) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let message;
|
|
305
|
+
try {
|
|
306
|
+
message = JSON.parse(body);
|
|
307
|
+
} catch {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Promise.resolve(handleMessage(message)).catch((error) => {
|
|
312
|
+
if (message && message.id !== undefined) {
|
|
313
|
+
writeError(message.id, -32603, (error && error.message) || 'Internal error');
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
process.stdin.on('data', (chunk) => {
|
|
320
|
+
inputBuffer = Buffer.concat([inputBuffer, chunk]);
|
|
321
|
+
parseMessages();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
process.stdin.on('error', () => {
|
|
325
|
+
process.exit(0);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
process.on('exit', (code) => {
|
|
329
|
+
writeSessionSummary(code);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
['SIGINT', 'SIGTERM', 'SIGHUP'].forEach((signal) => {
|
|
333
|
+
process.on(signal, () => {
|
|
334
|
+
writeSessionSummary(signal);
|
|
335
|
+
process.exit(0);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
process.stdin.resume();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaitranntt/ccs",
|
|
3
|
-
"version": "7.63.
|
|
3
|
+
"version": "7.63.1-dev.2",
|
|
4
4
|
"description": "Claude Code Switch - Instant profile switching between Claude, GLM, Kimi, and more",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"prebuild": "node scripts/clean-dist.js",
|
|
65
65
|
"prebuild:server": "node scripts/clean-dist.js",
|
|
66
66
|
"prebuild:all": "rm -rf dist tsconfig.tsbuildinfo",
|
|
67
|
+
"prepack": "bun run build:all",
|
|
67
68
|
"postbuild:all": "node scripts/verify-bundle.js",
|
|
68
69
|
"typecheck": "tsc --noEmit",
|
|
69
70
|
"lint": "eslint src/",
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const ASSESSMENTS = {
|
|
6
|
+
approved: '✅ APPROVED',
|
|
7
|
+
approved_with_notes: '⚠️ APPROVED WITH NOTES',
|
|
8
|
+
changes_requested: '❌ CHANGES REQUESTED',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const SEVERITY_ORDER = ['high', 'medium', 'low'];
|
|
12
|
+
const SEVERITY_HEADERS = {
|
|
13
|
+
high: '### 🔴 High',
|
|
14
|
+
medium: '### 🟡 Medium',
|
|
15
|
+
low: '### 🟢 Low',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const STATUS_LABELS = {
|
|
19
|
+
pass: '✅',
|
|
20
|
+
fail: '⚠️',
|
|
21
|
+
na: 'N/A',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const RENDERER_OWNED_MARKUP_PATTERNS = [
|
|
25
|
+
{ pattern: /^#{1,6}\s/u, reason: 'markdown heading' },
|
|
26
|
+
{ pattern: /^\s*Verdict\s*:/iu, reason: 'verdict label' },
|
|
27
|
+
{ pattern: /^\s*PR\s*#?\d+\s*Review(?:\s*[:.-]|$)/iu, reason: 'ad hoc PR heading' },
|
|
28
|
+
{ pattern: /\|\s*[-:]+\s*\|/u, reason: 'markdown table' },
|
|
29
|
+
{ pattern: /```/u, reason: 'code fence' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function cleanText(value) {
|
|
33
|
+
return typeof value === 'string' ? value.trim().replace(/\s+/g, ' ') : '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function escapeMarkdownText(value) {
|
|
37
|
+
return cleanText(value).replace(/\\/g, '\\\\').replace(/([`*_{}[\]<>|])/g, '\\$1');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderCode(value) {
|
|
41
|
+
const text = cleanText(value);
|
|
42
|
+
const longestFence = Math.max(...[...text.matchAll(/`+/g)].map((match) => match[0].length), 0);
|
|
43
|
+
const fence = '`'.repeat(longestFence + 1);
|
|
44
|
+
return `${fence}${text}${fence}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function validatePlainTextField(fieldName, value) {
|
|
48
|
+
const text = cleanText(value);
|
|
49
|
+
if (!text) {
|
|
50
|
+
return { ok: false, reason: `${fieldName} is required` };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const match = RENDERER_OWNED_MARKUP_PATTERNS.find(({ pattern }) => pattern.test(text));
|
|
54
|
+
if (match) {
|
|
55
|
+
return { ok: false, reason: `${fieldName} contains ${match.reason}` };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { ok: true, value: text };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeStringList(fieldName, raw) {
|
|
62
|
+
if (!Array.isArray(raw)) {
|
|
63
|
+
return { ok: false, reason: `${fieldName} must be an array` };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const values = [];
|
|
67
|
+
for (const [index, item] of raw.entries()) {
|
|
68
|
+
const validation = validatePlainTextField(`${fieldName}[${index}]`, item);
|
|
69
|
+
if (!validation.ok) return validation;
|
|
70
|
+
values.push(validation.value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { ok: true, value: values };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeChecklistRows(fieldName, labelField, raw) {
|
|
77
|
+
if (!Array.isArray(raw)) {
|
|
78
|
+
return { ok: false, reason: `${fieldName} must be an array` };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const rows = [];
|
|
82
|
+
for (const [index, item] of raw.entries()) {
|
|
83
|
+
const label = validatePlainTextField(`${fieldName}[${index}].${labelField}`, item?.[labelField]);
|
|
84
|
+
if (!label.ok) return label;
|
|
85
|
+
|
|
86
|
+
const notes = validatePlainTextField(`${fieldName}[${index}].notes`, item?.notes);
|
|
87
|
+
if (!notes.ok) return notes;
|
|
88
|
+
|
|
89
|
+
const status = cleanText(item?.status).toLowerCase();
|
|
90
|
+
if (!STATUS_LABELS[status]) {
|
|
91
|
+
return { ok: false, reason: `${fieldName}[${index}].status is invalid` };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
rows.push({ [labelField]: label.value, status, notes: notes.value });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (rows.length === 0) {
|
|
98
|
+
return { ok: false, reason: `${fieldName} must contain at least 1 item` };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { ok: true, value: rows };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readExecutionMetadata(executionFile) {
|
|
105
|
+
if (!executionFile || !fs.existsSync(executionFile)) {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const turns = JSON.parse(fs.readFileSync(executionFile, 'utf8'));
|
|
111
|
+
const init = turns.find((turn) => turn?.type === 'system' && turn?.subtype === 'init');
|
|
112
|
+
const result = [...turns].reverse().find((turn) => turn?.type === 'result');
|
|
113
|
+
return {
|
|
114
|
+
runtimeTools: Array.isArray(init?.tools) ? init.tools : [],
|
|
115
|
+
turnsUsed: typeof result?.num_turns === 'number' ? result.num_turns : null,
|
|
116
|
+
};
|
|
117
|
+
} catch {
|
|
118
|
+
return {};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function normalizeStructuredOutput(raw) {
|
|
123
|
+
if (!raw) {
|
|
124
|
+
return { ok: false, reason: 'missing structured output' };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let parsed;
|
|
128
|
+
try {
|
|
129
|
+
parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
130
|
+
} catch {
|
|
131
|
+
return { ok: false, reason: 'structured output is not valid JSON' };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
135
|
+
return { ok: false, reason: 'structured output must be an object' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const summary = validatePlainTextField('summary', parsed.summary);
|
|
139
|
+
if (!summary.ok) return summary;
|
|
140
|
+
|
|
141
|
+
const overallAssessment = cleanText(parsed.overallAssessment).toLowerCase();
|
|
142
|
+
const overallRationale = validatePlainTextField('overallRationale', parsed.overallRationale);
|
|
143
|
+
if (!overallRationale.ok) return overallRationale;
|
|
144
|
+
|
|
145
|
+
const findings = Array.isArray(parsed.findings) ? parsed.findings : null;
|
|
146
|
+
const securityChecklist = normalizeChecklistRows('securityChecklist', 'check', parsed.securityChecklist);
|
|
147
|
+
if (!securityChecklist.ok) return securityChecklist;
|
|
148
|
+
|
|
149
|
+
const ccsCompliance = normalizeChecklistRows('ccsCompliance', 'rule', parsed.ccsCompliance);
|
|
150
|
+
if (!ccsCompliance.ok) return ccsCompliance;
|
|
151
|
+
|
|
152
|
+
const informational = normalizeStringList('informational', parsed.informational);
|
|
153
|
+
if (!informational.ok) return informational;
|
|
154
|
+
|
|
155
|
+
const strengths = normalizeStringList('strengths', parsed.strengths);
|
|
156
|
+
if (!strengths.ok) return strengths;
|
|
157
|
+
|
|
158
|
+
if (!ASSESSMENTS[overallAssessment] || findings === null) {
|
|
159
|
+
return { ok: false, reason: 'structured output is missing required review fields' };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const normalizedFindings = [];
|
|
163
|
+
for (const [index, finding] of findings.entries()) {
|
|
164
|
+
const severity = cleanText(finding?.severity).toLowerCase();
|
|
165
|
+
const title = validatePlainTextField(`findings[${index}].title`, finding?.title);
|
|
166
|
+
if (!title.ok) return title;
|
|
167
|
+
|
|
168
|
+
const file = validatePlainTextField(`findings[${index}].file`, finding?.file);
|
|
169
|
+
if (!file.ok) return file;
|
|
170
|
+
|
|
171
|
+
const what = validatePlainTextField(`findings[${index}].what`, finding?.what);
|
|
172
|
+
if (!what.ok) return what;
|
|
173
|
+
|
|
174
|
+
const why = validatePlainTextField(`findings[${index}].why`, finding?.why);
|
|
175
|
+
if (!why.ok) return why;
|
|
176
|
+
|
|
177
|
+
const fix = validatePlainTextField(`findings[${index}].fix`, finding?.fix);
|
|
178
|
+
if (!fix.ok) return fix;
|
|
179
|
+
|
|
180
|
+
let line = null;
|
|
181
|
+
if (finding && Object.hasOwn(finding, 'line')) {
|
|
182
|
+
if (finding.line === null) {
|
|
183
|
+
line = null;
|
|
184
|
+
} else if (typeof finding.line === 'number' && Number.isInteger(finding.line) && finding.line > 0) {
|
|
185
|
+
line = finding.line;
|
|
186
|
+
} else {
|
|
187
|
+
return { ok: false, reason: `findings[${index}].line is invalid` };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!SEVERITY_HEADERS[severity]) {
|
|
192
|
+
return { ok: false, reason: `findings[${index}].severity is invalid` };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
normalizedFindings.push({
|
|
196
|
+
severity,
|
|
197
|
+
title: title.value,
|
|
198
|
+
file: file.value,
|
|
199
|
+
line,
|
|
200
|
+
what: what.value,
|
|
201
|
+
why: why.value,
|
|
202
|
+
fix: fix.value,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
ok: true,
|
|
208
|
+
value: {
|
|
209
|
+
summary: summary.value,
|
|
210
|
+
findings: normalizedFindings,
|
|
211
|
+
overallAssessment,
|
|
212
|
+
overallRationale: overallRationale.value,
|
|
213
|
+
securityChecklist: securityChecklist.value,
|
|
214
|
+
ccsCompliance: ccsCompliance.value,
|
|
215
|
+
informational: informational.value,
|
|
216
|
+
strengths: strengths.value,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function renderChecklistTable(title, labelHeader, labelKey, rows) {
|
|
222
|
+
const lines = ['', title, '', `| ${labelHeader} | Status | Notes |`, '|---|---|---|'];
|
|
223
|
+
for (const row of rows) {
|
|
224
|
+
lines.push(
|
|
225
|
+
`| ${escapeMarkdownText(row[labelKey])} | ${STATUS_LABELS[row.status]} | ${escapeMarkdownText(row.notes)} |`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return lines;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function renderBulletSection(title, items) {
|
|
232
|
+
if (items.length === 0) return [];
|
|
233
|
+
return ['', title, ...items.map((item) => `- ${escapeMarkdownText(item)}`)];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function renderStructuredReview(review, { model }) {
|
|
237
|
+
const lines = ['### 📋 Summary', '', escapeMarkdownText(review.summary), '', '### 🔍 Findings'];
|
|
238
|
+
|
|
239
|
+
if (review.findings.length === 0) {
|
|
240
|
+
lines.push('No confirmed issues found after reviewing the diff and surrounding code.');
|
|
241
|
+
} else {
|
|
242
|
+
for (const severity of SEVERITY_ORDER) {
|
|
243
|
+
const findings = review.findings.filter((finding) => finding.severity === severity);
|
|
244
|
+
if (findings.length === 0) continue;
|
|
245
|
+
|
|
246
|
+
lines.push('', SEVERITY_HEADERS[severity], '');
|
|
247
|
+
for (const finding of findings) {
|
|
248
|
+
const location = finding.line ? `${finding.file}:${finding.line}` : finding.file;
|
|
249
|
+
lines.push(`- **${renderCode(location)} — ${escapeMarkdownText(finding.title)}**`);
|
|
250
|
+
lines.push(` Problem: ${escapeMarkdownText(finding.what)}`);
|
|
251
|
+
lines.push(` Why it matters: ${escapeMarkdownText(finding.why)}`);
|
|
252
|
+
lines.push(` Suggested fix: ${escapeMarkdownText(finding.fix)}`);
|
|
253
|
+
lines.push('');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (lines[lines.length - 1] === '') lines.pop();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
lines.push(...renderChecklistTable('### 🔒 Security Checklist', 'Check', 'check', review.securityChecklist));
|
|
260
|
+
lines.push(...renderChecklistTable('### 📊 CCS Compliance', 'Rule', 'rule', review.ccsCompliance));
|
|
261
|
+
lines.push(...renderBulletSection('### 💡 Informational', review.informational));
|
|
262
|
+
lines.push(...renderBulletSection("### ✅ What's Done Well", review.strengths));
|
|
263
|
+
|
|
264
|
+
lines.push(
|
|
265
|
+
'',
|
|
266
|
+
'### 🎯 Overall Assessment',
|
|
267
|
+
'',
|
|
268
|
+
`**${ASSESSMENTS[review.overallAssessment]}** — ${escapeMarkdownText(review.overallRationale)}`,
|
|
269
|
+
'',
|
|
270
|
+
`> 🤖 Reviewed by \`${model}\``
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
return lines.join('\n');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function renderIncompleteReview({ model, reason, runUrl, runtimeTools, turnsUsed }) {
|
|
277
|
+
const lines = [
|
|
278
|
+
'### ⚠️ AI Review Incomplete',
|
|
279
|
+
'',
|
|
280
|
+
'Claude did not return validated structured review output, so this workflow did not publish raw scratch text.',
|
|
281
|
+
'',
|
|
282
|
+
`- Reason: ${escapeMarkdownText(reason)}`,
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
if (runtimeTools?.length) {
|
|
286
|
+
lines.push(`- Runtime tools: ${runtimeTools.map(renderCode).join(', ')}`);
|
|
287
|
+
}
|
|
288
|
+
if (typeof turnsUsed === 'number') {
|
|
289
|
+
lines.push(`- Turns used: ${turnsUsed}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
lines.push('', `Re-run \`/review\` or inspect [the workflow run](${runUrl}).`, '', `> 🤖 Reviewed by \`${model}\``);
|
|
293
|
+
return lines.join('\n');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function writeReviewFromEnv(env = process.env) {
|
|
297
|
+
const outputFile = env.AI_REVIEW_OUTPUT_FILE || 'pr_review.md';
|
|
298
|
+
const model = env.AI_REVIEW_MODEL || 'unknown-model';
|
|
299
|
+
const runUrl = env.AI_REVIEW_RUN_URL || '#';
|
|
300
|
+
const validation = normalizeStructuredOutput(env.AI_REVIEW_STRUCTURED_OUTPUT);
|
|
301
|
+
const metadata = readExecutionMetadata(env.AI_REVIEW_EXECUTION_FILE);
|
|
302
|
+
const content = validation.ok
|
|
303
|
+
? renderStructuredReview(validation.value, { model })
|
|
304
|
+
: renderIncompleteReview({
|
|
305
|
+
model,
|
|
306
|
+
reason: validation.reason,
|
|
307
|
+
runUrl,
|
|
308
|
+
runtimeTools: metadata.runtimeTools,
|
|
309
|
+
turnsUsed: metadata.turnsUsed,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
313
|
+
fs.writeFileSync(outputFile, `${content}\n`, 'utf8');
|
|
314
|
+
|
|
315
|
+
if (!validation.ok) {
|
|
316
|
+
console.warn(`::warning::AI review output normalization fell back to incomplete comment: ${validation.reason}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return { usedFallback: !validation.ok, content };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const isMain =
|
|
323
|
+
process.argv[1] &&
|
|
324
|
+
path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
325
|
+
|
|
326
|
+
if (isMain) {
|
|
327
|
+
writeReviewFromEnv();
|
|
328
|
+
}
|