@mmmbuto/qwen-code-termux 0.6.4-termux → 0.6.401

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.
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @license
5
+ * Copyright 2025 Google LLC
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+
9
+ import { execSync } from 'node:child_process';
10
+ import { join } from 'node:path';
11
+ import { existsSync, readFileSync } from 'node:fs';
12
+
13
+ const projectRoot = join(import.meta.dirname, '..');
14
+
15
+ const SETTINGS_DIRECTORY_NAME = '.qwen';
16
+ const USER_SETTINGS_DIR = join(
17
+ process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || '',
18
+ SETTINGS_DIRECTORY_NAME,
19
+ );
20
+ const USER_SETTINGS_PATH = join(USER_SETTINGS_DIR, 'settings.json');
21
+ const WORKSPACE_SETTINGS_PATH = join(
22
+ projectRoot,
23
+ SETTINGS_DIRECTORY_NAME,
24
+ 'settings.json',
25
+ );
26
+
27
+ let settingsTarget = undefined;
28
+
29
+ function loadSettingsValue(filePath) {
30
+ try {
31
+ if (existsSync(filePath)) {
32
+ const content = readFileSync(filePath, 'utf-8');
33
+ const jsonContent = content.replace(/\/\/[^\n]*/g, '');
34
+ const settings = JSON.parse(jsonContent);
35
+ return settings.telemetry?.target;
36
+ }
37
+ } catch (e) {
38
+ console.warn(
39
+ `⚠️ Warning: Could not parse settings file at ${filePath}: ${e.message}`,
40
+ );
41
+ }
42
+ return undefined;
43
+ }
44
+
45
+ settingsTarget = loadSettingsValue(WORKSPACE_SETTINGS_PATH);
46
+
47
+ if (!settingsTarget) {
48
+ settingsTarget = loadSettingsValue(USER_SETTINGS_PATH);
49
+ }
50
+
51
+ let target = settingsTarget || 'local';
52
+ const allowedTargets = ['local', 'gcp'];
53
+
54
+ const targetArg = process.argv.find((arg) => arg.startsWith('--target='));
55
+ if (targetArg) {
56
+ const potentialTarget = targetArg.split('=')[1];
57
+ if (allowedTargets.includes(potentialTarget)) {
58
+ target = potentialTarget;
59
+ console.log(`⚙️ Using command-line target: ${target}`);
60
+ } else {
61
+ console.error(
62
+ `🛑 Error: Invalid target '${potentialTarget}'. Allowed targets are: ${allowedTargets.join(', ')}.`,
63
+ );
64
+ process.exit(1);
65
+ }
66
+ } else if (settingsTarget) {
67
+ console.log(
68
+ `⚙️ Using telemetry target from settings.json: ${settingsTarget}`,
69
+ );
70
+ }
71
+
72
+ const scriptPath = join(
73
+ projectRoot,
74
+ 'scripts',
75
+ target === 'gcp' ? 'telemetry_gcp.js' : 'local_telemetry.js',
76
+ );
77
+
78
+ try {
79
+ console.log(`🚀 Running telemetry script for target: ${target}.`);
80
+ execSync(`node ${scriptPath}`, { stdio: 'inherit', cwd: projectRoot });
81
+ } catch (error) {
82
+ console.error(`🛑 Failed to run telemetry script for target: ${target}`);
83
+ console.error(error);
84
+ process.exit(1);
85
+ }
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @license
5
+ * Copyright 2025 Google LLC
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+
9
+ import path from 'node:path';
10
+ import fs from 'node:fs';
11
+ import { spawn, execSync } from 'node:child_process';
12
+ import {
13
+ OTEL_DIR,
14
+ BIN_DIR,
15
+ fileExists,
16
+ waitForPort,
17
+ ensureBinary,
18
+ manageTelemetrySettings,
19
+ registerCleanup,
20
+ } from './telemetry_utils.js';
21
+
22
+ const OTEL_CONFIG_FILE = path.join(OTEL_DIR, 'collector-gcp.yaml');
23
+ const OTEL_LOG_FILE = path.join(OTEL_DIR, 'collector-gcp.log');
24
+
25
+ const getOtelConfigContent = (projectId) => `
26
+ receivers:
27
+ otlp:
28
+ protocols:
29
+ grpc:
30
+ endpoint: "localhost:4317"
31
+ processors:
32
+ batch:
33
+ timeout: 1s
34
+ exporters:
35
+ googlecloud:
36
+ project: "${projectId}"
37
+ metric:
38
+ prefix: "custom.googleapis.com/gemini_cli"
39
+ log:
40
+ default_log_name: "gemini_cli"
41
+ debug:
42
+ verbosity: detailed
43
+ service:
44
+ telemetry:
45
+ logs:
46
+ level: "debug"
47
+ metrics:
48
+ level: "none"
49
+ pipelines:
50
+ traces:
51
+ receivers: [otlp]
52
+ processors: [batch]
53
+ exporters: [googlecloud]
54
+ metrics:
55
+ receivers: [otlp]
56
+ processors: [batch]
57
+ exporters: [googlecloud, debug]
58
+ logs:
59
+ receivers: [otlp]
60
+ processors: [batch]
61
+ exporters: [googlecloud, debug]
62
+ `;
63
+
64
+ async function main() {
65
+ console.log('✨ Starting Local Telemetry Exporter for Google Cloud ✨');
66
+
67
+ let collectorProcess;
68
+ let collectorLogFd;
69
+
70
+ const originalSandboxSetting = manageTelemetrySettings(
71
+ true,
72
+ 'http://localhost:4317',
73
+ 'gcp',
74
+ );
75
+ registerCleanup(
76
+ () => [collectorProcess].filter((p) => p), // Function to get processes
77
+ () => [collectorLogFd].filter((fd) => fd), // Function to get FDs
78
+ originalSandboxSetting,
79
+ );
80
+
81
+ const projectId = process.env.OTLP_GOOGLE_CLOUD_PROJECT;
82
+ if (!projectId) {
83
+ console.error(
84
+ '🛑 Error: OTLP_GOOGLE_CLOUD_PROJECT environment variable is not exported.',
85
+ );
86
+ console.log(
87
+ ' Please set it to your Google Cloud Project ID and try again.',
88
+ );
89
+ console.log(' `export OTLP_GOOGLE_CLOUD_PROJECT=your-project-id`');
90
+ process.exit(1);
91
+ }
92
+ console.log(`✅ Using OTLP Google Cloud Project ID: ${projectId}`);
93
+
94
+ console.log('\n🔑 Please ensure you are authenticated with Google Cloud:');
95
+ console.log(
96
+ ' - Run `gcloud auth application-default login` OR ensure `GOOGLE_APPLICATION_CREDENTIALS` environment variable points to a valid service account key.',
97
+ );
98
+ console.log(
99
+ ' - The account needs "Cloud Trace Agent", "Monitoring Metric Writer", and "Logs Writer" roles.',
100
+ );
101
+
102
+ if (!fileExists(BIN_DIR)) fs.mkdirSync(BIN_DIR, { recursive: true });
103
+
104
+ const otelcolPath = await ensureBinary(
105
+ 'otelcol-contrib',
106
+ 'open-telemetry/opentelemetry-collector-releases',
107
+ (version, platform, arch, ext) =>
108
+ `otelcol-contrib_${version}_${platform}_${arch}.${ext}`,
109
+ 'otelcol-contrib',
110
+ false, // isJaeger = false
111
+ ).catch((e) => {
112
+ console.error(`🛑 Error getting otelcol-contrib: ${e.message}`);
113
+ return null;
114
+ });
115
+ if (!otelcolPath) process.exit(1);
116
+
117
+ console.log('🧹 Cleaning up old processes and logs...');
118
+ try {
119
+ execSync('pkill -f "otelcol-contrib"');
120
+ console.log('✅ Stopped existing otelcol-contrib process.');
121
+ } catch (_e) {
122
+ /* no-op */
123
+ }
124
+ try {
125
+ fs.unlinkSync(OTEL_LOG_FILE);
126
+ console.log('✅ Deleted old GCP collector log.');
127
+ } catch (e) {
128
+ if (e.code !== 'ENOENT') console.error(e);
129
+ }
130
+
131
+ if (!fileExists(OTEL_DIR)) fs.mkdirSync(OTEL_DIR, { recursive: true });
132
+ fs.writeFileSync(OTEL_CONFIG_FILE, getOtelConfigContent(projectId));
133
+ console.log(`📄 Wrote OTEL collector config to ${OTEL_CONFIG_FILE}`);
134
+
135
+ console.log(`🚀 Starting OTEL collector for GCP... Logs: ${OTEL_LOG_FILE}`);
136
+ collectorLogFd = fs.openSync(OTEL_LOG_FILE, 'a');
137
+ collectorProcess = spawn(otelcolPath, ['--config', OTEL_CONFIG_FILE], {
138
+ stdio: ['ignore', collectorLogFd, collectorLogFd],
139
+ env: { ...process.env },
140
+ });
141
+
142
+ console.log(
143
+ `⏳ Waiting for OTEL collector to start (PID: ${collectorProcess.pid})...`,
144
+ );
145
+
146
+ try {
147
+ await waitForPort(4317);
148
+ console.log(`✅ OTEL collector started successfully on port 4317.`);
149
+ } catch (err) {
150
+ console.error(`🛑 Error: OTEL collector failed to start on port 4317.`);
151
+ console.error(err.message);
152
+ if (collectorProcess && collectorProcess.pid) {
153
+ process.kill(collectorProcess.pid, 'SIGKILL');
154
+ }
155
+ if (fileExists(OTEL_LOG_FILE)) {
156
+ console.error('📄 OTEL Collector Log Output:');
157
+ console.error(fs.readFileSync(OTEL_LOG_FILE, 'utf-8'));
158
+ }
159
+ process.exit(1);
160
+ }
161
+
162
+ collectorProcess.on('error', (err) => {
163
+ console.error(`${collectorProcess.spawnargs[0]} process error:`, err);
164
+ process.exit(1);
165
+ });
166
+
167
+ console.log(`\n✨ Local OTEL collector for GCP is running.`);
168
+ console.log(
169
+ '\n🚀 To send telemetry, run the Gemini CLI in a separate terminal window.',
170
+ );
171
+ console.log(`\n📄 Collector logs are being written to: ${OTEL_LOG_FILE}`);
172
+ console.log(
173
+ `📄 Tail collector logs in another terminal: tail -f ${OTEL_LOG_FILE}`,
174
+ );
175
+ console.log(`\n📊 View your telemetry data in Google Cloud Console:`);
176
+ console.log(
177
+ ` - Logs: https://console.cloud.google.com/logs/query;query=logName%3D%22projects%2F${projectId}%2Flogs%2Fgemini_cli%22?project=${projectId}`,
178
+ );
179
+ console.log(
180
+ ` - Metrics: https://console.cloud.google.com/monitoring/metrics-explorer?project=${projectId}`,
181
+ );
182
+ console.log(
183
+ ` - Traces: https://console.cloud.google.com/traces/list?project=${projectId}`,
184
+ );
185
+ console.log(`\nPress Ctrl+C to exit.`);
186
+ }
187
+
188
+ main();
@@ -0,0 +1,439 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @license
5
+ * Copyright 2025 Google LLC
6
+ * SPDX-License-Identifier: Apache-2.0
7
+ */
8
+
9
+ import path from 'node:path';
10
+ import fs from 'node:fs';
11
+ import net from 'node:net';
12
+ import os from 'node:os';
13
+ import { spawnSync } from 'node:child_process';
14
+ import { fileURLToPath } from 'node:url';
15
+ import crypto from 'node:crypto';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+
20
+ const projectRoot = path.resolve(__dirname, '..');
21
+ const projectHash = crypto
22
+ .createHash('sha256')
23
+ .update(projectRoot)
24
+ .digest('hex');
25
+
26
+ // User-level .gemini directory in home
27
+ const USER_GEMINI_DIR = path.join(os.homedir(), '.qwen');
28
+ // Project-level .gemini directory in the workspace
29
+ const WORKSPACE_GEMINI_DIR = path.join(projectRoot, '.qwen');
30
+
31
+ // Telemetry artifacts are stored in a hashed directory under the user's ~/.qwen/tmp
32
+ export const OTEL_DIR = path.join(USER_GEMINI_DIR, 'tmp', projectHash, 'otel');
33
+ export const BIN_DIR = path.join(OTEL_DIR, 'bin');
34
+
35
+ // Workspace settings remain in the project's .gemini directory
36
+ export const WORKSPACE_SETTINGS_FILE = path.join(
37
+ WORKSPACE_GEMINI_DIR,
38
+ 'settings.json',
39
+ );
40
+
41
+ export function getJson(url) {
42
+ const tmpFile = path.join(
43
+ os.tmpdir(),
44
+ `qwen-code-releases-${Date.now()}.json`,
45
+ );
46
+ try {
47
+ const result = spawnSync(
48
+ 'curl',
49
+ ['-sL', '-H', 'User-Agent: qwen-code-dev-script', '-o', tmpFile, url],
50
+ { stdio: 'pipe', encoding: 'utf-8' },
51
+ );
52
+ if (result.status !== 0) {
53
+ throw new Error(result.stderr);
54
+ }
55
+ const content = fs.readFileSync(tmpFile, 'utf-8');
56
+ return JSON.parse(content);
57
+ } catch (e) {
58
+ console.error(`Failed to fetch or parse JSON from ${url}`);
59
+ throw e;
60
+ } finally {
61
+ if (fs.existsSync(tmpFile)) {
62
+ fs.unlinkSync(tmpFile);
63
+ }
64
+ }
65
+ }
66
+
67
+ export function downloadFile(url, dest) {
68
+ try {
69
+ const result = spawnSync('curl', ['-fL', '-sS', '-o', dest, url], {
70
+ stdio: 'pipe',
71
+ encoding: 'utf-8',
72
+ });
73
+ if (result.status !== 0) {
74
+ throw new Error(result.stderr);
75
+ }
76
+ return dest;
77
+ } catch (e) {
78
+ console.error(`Failed to download file from ${url}`);
79
+ throw e;
80
+ }
81
+ }
82
+
83
+ export function findFile(startPath, filter) {
84
+ if (!fs.existsSync(startPath)) {
85
+ return null;
86
+ }
87
+ const files = fs.readdirSync(startPath);
88
+ for (const file of files) {
89
+ const filename = path.join(startPath, file);
90
+ const stat = fs.lstatSync(filename);
91
+ if (stat.isDirectory()) {
92
+ const result = findFile(filename, filter);
93
+ if (result) return result;
94
+ } else if (filter(file)) {
95
+ return filename;
96
+ }
97
+ }
98
+ return null;
99
+ }
100
+
101
+ export function fileExists(filePath) {
102
+ return fs.existsSync(filePath);
103
+ }
104
+
105
+ export function readJsonFile(filePath) {
106
+ if (!fileExists(filePath)) {
107
+ return {};
108
+ }
109
+ const content = fs.readFileSync(filePath, 'utf-8');
110
+ try {
111
+ return JSON.parse(content);
112
+ } catch (e) {
113
+ console.error(`Error parsing JSON from ${filePath}: ${e.message}`);
114
+ return {};
115
+ }
116
+ }
117
+
118
+ export function writeJsonFile(filePath, data) {
119
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
120
+ }
121
+
122
+ export function moveBinary(source, destination) {
123
+ try {
124
+ fs.renameSync(source, destination);
125
+ } catch (error) {
126
+ if (error.code !== 'EXDEV') {
127
+ throw error;
128
+ }
129
+ // Handle a cross-device error: copy-to-temp-then-rename.
130
+ const destDir = path.dirname(destination);
131
+ const destFile = path.basename(destination);
132
+ const tempDest = path.join(destDir, `${destFile}.tmp`);
133
+
134
+ try {
135
+ fs.copyFileSync(source, tempDest);
136
+ fs.renameSync(tempDest, destination);
137
+ } catch (moveError) {
138
+ // If copy or rename fails, clean up the intermediate temp file.
139
+ if (fs.existsSync(tempDest)) {
140
+ fs.unlinkSync(tempDest);
141
+ }
142
+ throw moveError;
143
+ }
144
+ fs.unlinkSync(source);
145
+ }
146
+ }
147
+
148
+ export function waitForPort(port, timeout = 10000) {
149
+ return new Promise((resolve, reject) => {
150
+ const startTime = Date.now();
151
+ const tryConnect = () => {
152
+ const socket = new net.Socket();
153
+ socket.once('connect', () => {
154
+ socket.end();
155
+ resolve();
156
+ });
157
+ socket.once('error', (_) => {
158
+ if (Date.now() - startTime > timeout) {
159
+ reject(new Error(`Timeout waiting for port ${port} to open.`));
160
+ } else {
161
+ setTimeout(tryConnect, 500);
162
+ }
163
+ });
164
+ socket.connect(port, 'localhost');
165
+ };
166
+ tryConnect();
167
+ });
168
+ }
169
+
170
+ export async function ensureBinary(
171
+ executableName,
172
+ repo,
173
+ assetNameCallback,
174
+ binaryNameInArchive,
175
+ isJaeger = false,
176
+ ) {
177
+ const executablePath = path.join(BIN_DIR, executableName);
178
+ if (fileExists(executablePath)) {
179
+ console.log(`✅ ${executableName} already exists at ${executablePath}`);
180
+ return executablePath;
181
+ }
182
+
183
+ console.log(`🔍 ${executableName} not found. Downloading from ${repo}...`);
184
+
185
+ const platform = process.platform === 'win32' ? 'windows' : process.platform;
186
+ const arch = process.arch === 'x64' ? 'amd64' : process.arch;
187
+ const ext = platform === 'windows' ? 'zip' : 'tar.gz';
188
+
189
+ if (isJaeger && platform === 'windows' && arch === 'arm64') {
190
+ console.warn(
191
+ `⚠️ Jaeger does not have a release for Windows on ARM64. Skipping.`,
192
+ );
193
+ return null;
194
+ }
195
+
196
+ let release;
197
+ let asset;
198
+
199
+ if (isJaeger) {
200
+ console.log(`🔍 Finding latest Jaeger v2+ asset...`);
201
+ const releases = getJson(`https://api.github.com/repos/${repo}/releases`);
202
+ const sortedReleases = releases
203
+ .filter((r) => !r.prerelease && r.tag_name.startsWith('v'))
204
+ .sort((a, b) => {
205
+ const aVersion = a.tag_name.substring(1).split('.').map(Number);
206
+ const bVersion = b.tag_name.substring(1).split('.').map(Number);
207
+ for (let i = 0; i < Math.max(aVersion.length, bVersion.length); i++) {
208
+ if ((aVersion[i] || 0) > (bVersion[i] || 0)) return -1;
209
+ if ((aVersion[i] || 0) < (bVersion[i] || 0)) return 1;
210
+ }
211
+ return 0;
212
+ });
213
+
214
+ for (const r of sortedReleases) {
215
+ const expectedSuffix =
216
+ platform === 'windows'
217
+ ? `-${platform}-${arch}.zip`
218
+ : `-${platform}-${arch}.tar.gz`;
219
+ const foundAsset = r.assets.find(
220
+ (a) =>
221
+ a.name.startsWith('jaeger-2.') && a.name.endsWith(expectedSuffix),
222
+ );
223
+
224
+ if (foundAsset) {
225
+ release = r;
226
+ asset = foundAsset;
227
+ console.log(
228
+ `⬇️ Found ${asset.name} in release ${r.tag_name}, downloading...`,
229
+ );
230
+ break;
231
+ }
232
+ }
233
+ if (!asset) {
234
+ throw new Error(
235
+ `Could not find a suitable Jaeger v2 asset for platform ${platform}/${arch}.`,
236
+ );
237
+ }
238
+ } else {
239
+ release = getJson(`https://api.github.com/repos/${repo}/releases/latest`);
240
+ const version = release.tag_name.startsWith('v')
241
+ ? release.tag_name.substring(1)
242
+ : release.tag_name;
243
+ const assetName = assetNameCallback(version, platform, arch, ext);
244
+ asset = release.assets.find((a) => a.name === assetName);
245
+ if (!asset) {
246
+ throw new Error(
247
+ `Could not find a suitable asset for ${repo} (version ${version}) on platform ${platform}/${arch}. Searched for: ${assetName}`,
248
+ );
249
+ }
250
+ }
251
+
252
+ const downloadUrl = asset.browser_download_url;
253
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qwen-code-telemetry-'));
254
+ const archivePath = path.join(tmpDir, asset.name);
255
+
256
+ try {
257
+ console.log(`⬇️ Downloading ${asset.name}...`);
258
+ downloadFile(downloadUrl, archivePath);
259
+ console.log(`📦 Extracting ${asset.name}...`);
260
+
261
+ const actualExt = asset.name.endsWith('.zip') ? 'zip' : 'tar.gz';
262
+
263
+ let result;
264
+ if (actualExt === 'zip') {
265
+ result = spawnSync('unzip', ['-o', archivePath, '-d', tmpDir], {
266
+ stdio: 'pipe',
267
+ encoding: 'utf-8',
268
+ });
269
+ } else {
270
+ result = spawnSync('tar', ['-xzf', archivePath, '-C', tmpDir], {
271
+ stdio: 'pipe',
272
+ encoding: 'utf-8',
273
+ });
274
+ }
275
+ if (result.status !== 0) {
276
+ throw new Error(result.stderr);
277
+ }
278
+
279
+ const nameToFind = binaryNameInArchive || executableName;
280
+ const foundBinaryPath = findFile(tmpDir, (file) => {
281
+ if (platform === 'windows') {
282
+ return file === `${nameToFind}.exe`;
283
+ }
284
+ return file === nameToFind;
285
+ });
286
+
287
+ if (!foundBinaryPath) {
288
+ throw new Error(
289
+ `Could not find binary "${nameToFind}" in extracted archive at ${tmpDir}. Contents: ${fs.readdirSync(tmpDir).join(', ')}`,
290
+ );
291
+ }
292
+
293
+ moveBinary(foundBinaryPath, executablePath);
294
+
295
+ if (platform !== 'windows') {
296
+ fs.chmodSync(executablePath, '755');
297
+ }
298
+
299
+ console.log(`✅ ${executableName} installed at ${executablePath}`);
300
+ return executablePath;
301
+ } finally {
302
+ fs.rmSync(tmpDir, { recursive: true, force: true });
303
+ if (fs.existsSync(archivePath)) {
304
+ fs.unlinkSync(archivePath);
305
+ }
306
+ }
307
+ }
308
+
309
+ export function manageTelemetrySettings(
310
+ enable,
311
+ oTelEndpoint = 'http://localhost:4317',
312
+ target = 'local',
313
+ originalSandboxSettingToRestore,
314
+ ) {
315
+ const workspaceSettings = readJsonFile(WORKSPACE_SETTINGS_FILE);
316
+ const currentSandboxSetting = workspaceSettings.sandbox;
317
+ let settingsModified = false;
318
+
319
+ if (typeof workspaceSettings.telemetry !== 'object') {
320
+ workspaceSettings.telemetry = {};
321
+ }
322
+
323
+ if (enable) {
324
+ if (workspaceSettings.telemetry.enabled !== true) {
325
+ workspaceSettings.telemetry.enabled = true;
326
+ settingsModified = true;
327
+ console.log('⚙️ Enabled telemetry in workspace settings.');
328
+ }
329
+ if (workspaceSettings.sandbox !== false) {
330
+ workspaceSettings.sandbox = false;
331
+ settingsModified = true;
332
+ console.log('✅ Disabled sandbox mode for telemetry.');
333
+ }
334
+ if (workspaceSettings.telemetry.otlpEndpoint !== oTelEndpoint) {
335
+ workspaceSettings.telemetry.otlpEndpoint = oTelEndpoint;
336
+ settingsModified = true;
337
+ console.log(`🔧 Set telemetry OTLP endpoint to ${oTelEndpoint}.`);
338
+ }
339
+ if (workspaceSettings.telemetry.target !== target) {
340
+ workspaceSettings.telemetry.target = target;
341
+ settingsModified = true;
342
+ console.log(`🎯 Set telemetry target to ${target}.`);
343
+ }
344
+ } else {
345
+ if (workspaceSettings.telemetry.enabled === true) {
346
+ delete workspaceSettings.telemetry.enabled;
347
+ settingsModified = true;
348
+ console.log('⚙️ Disabled telemetry in workspace settings.');
349
+ }
350
+ if (workspaceSettings.telemetry.otlpEndpoint) {
351
+ delete workspaceSettings.telemetry.otlpEndpoint;
352
+ settingsModified = true;
353
+ console.log('🔧 Cleared telemetry OTLP endpoint.');
354
+ }
355
+ if (workspaceSettings.telemetry.target) {
356
+ delete workspaceSettings.telemetry.target;
357
+ settingsModified = true;
358
+ console.log('🎯 Cleared telemetry target.');
359
+ }
360
+ if (Object.keys(workspaceSettings.telemetry).length === 0) {
361
+ delete workspaceSettings.telemetry;
362
+ }
363
+
364
+ if (
365
+ originalSandboxSettingToRestore !== undefined &&
366
+ workspaceSettings.sandbox !== originalSandboxSettingToRestore
367
+ ) {
368
+ workspaceSettings.sandbox = originalSandboxSettingToRestore;
369
+ settingsModified = true;
370
+ console.log('✅ Restored original sandbox setting.');
371
+ }
372
+ }
373
+
374
+ if (settingsModified) {
375
+ writeJsonFile(WORKSPACE_SETTINGS_FILE, workspaceSettings);
376
+ console.log('✅ Workspace settings updated.');
377
+ } else {
378
+ console.log(
379
+ enable
380
+ ? '✅ Workspace settings are already configured for telemetry.'
381
+ : '✅ Workspace settings already reflect telemetry disabled.',
382
+ );
383
+ }
384
+ return currentSandboxSetting;
385
+ }
386
+
387
+ export function registerCleanup(
388
+ getProcesses,
389
+ getLogFileDescriptors,
390
+ originalSandboxSetting,
391
+ ) {
392
+ let cleanedUp = false;
393
+ const cleanup = () => {
394
+ if (cleanedUp) return;
395
+ cleanedUp = true;
396
+
397
+ console.log('\n👋 Shutting down...');
398
+
399
+ manageTelemetrySettings(false, null, originalSandboxSetting);
400
+
401
+ const processes = getProcesses ? getProcesses() : [];
402
+ processes.forEach((proc) => {
403
+ if (proc && proc.pid) {
404
+ const name = path.basename(proc.spawnfile);
405
+ try {
406
+ console.log(`🛑 Stopping ${name} (PID: ${proc.pid})...`);
407
+ process.kill(proc.pid, 'SIGTERM');
408
+ console.log(`✅ ${name} stopped.`);
409
+ } catch (e) {
410
+ if (e.code !== 'ESRCH') {
411
+ console.error(`Error stopping ${name}: ${e.message}`);
412
+ }
413
+ }
414
+ }
415
+ });
416
+
417
+ const logFileDescriptors = getLogFileDescriptors
418
+ ? getLogFileDescriptors()
419
+ : [];
420
+ logFileDescriptors.forEach((fd) => {
421
+ if (fd) {
422
+ try {
423
+ fs.closeSync(fd);
424
+ } catch (_) {
425
+ /* no-op */
426
+ }
427
+ }
428
+ });
429
+ };
430
+
431
+ process.on('exit', cleanup);
432
+ process.on('SIGINT', () => process.exit(0));
433
+ process.on('SIGTERM', () => process.exit(0));
434
+ process.on('uncaughtException', (err) => {
435
+ console.error('Uncaught Exception:', err);
436
+ cleanup();
437
+ process.exit(1);
438
+ });
439
+ }