fss-link 1.5.7 → 1.6.0

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 (40) hide show
  1. package/README.md +1 -1
  2. package/bundle/fss-link.js +4785 -3183
  3. package/package.json +4 -1
  4. package/scripts/analyze-session-logs.sh +279 -0
  5. package/scripts/build.js +55 -0
  6. package/scripts/build_package.js +37 -0
  7. package/scripts/build_sandbox.js +195 -0
  8. package/scripts/build_vscode_companion.js +30 -0
  9. package/scripts/check-build-status.js +148 -0
  10. package/scripts/check-publish.js +101 -0
  11. package/scripts/clean.js +55 -0
  12. package/scripts/copy_bundle_assets.js +40 -0
  13. package/scripts/copy_files.js +56 -0
  14. package/scripts/create_alias.sh +39 -0
  15. package/scripts/emergency-kill-all-tests.sh +95 -0
  16. package/scripts/emergency-kill-vitest.sh +95 -0
  17. package/scripts/extract-session-logs.sh +202 -0
  18. package/scripts/generate-git-commit-info.js +71 -0
  19. package/scripts/get-previous-tag.js +213 -0
  20. package/scripts/get-release-version.js +119 -0
  21. package/scripts/index-session-logs.sh +173 -0
  22. package/scripts/install-linux.sh +294 -0
  23. package/scripts/install-macos.sh +343 -0
  24. package/scripts/install-windows.ps1 +427 -0
  25. package/scripts/local_telemetry.js +219 -0
  26. package/scripts/memory-monitor.sh +165 -0
  27. package/scripts/postinstall-message.js +31 -0
  28. package/scripts/prepare-package.js +51 -0
  29. package/scripts/process-session-log.py +302 -0
  30. package/scripts/quick-install.sh +195 -0
  31. package/scripts/sandbox_command.js +126 -0
  32. package/scripts/start.js +76 -0
  33. package/scripts/telemetry.js +85 -0
  34. package/scripts/telemetry_gcp.js +188 -0
  35. package/scripts/telemetry_utils.js +421 -0
  36. package/scripts/test-windows-paths.js +51 -0
  37. package/scripts/tests/get-release-version.test.js +110 -0
  38. package/scripts/tests/test-setup.ts +12 -0
  39. package/scripts/tests/vitest.config.ts +20 -0
  40. package/scripts/version.js +83 -0
@@ -0,0 +1,195 @@
1
+ #!/bin/bash
2
+
3
+ # FSS Link Quick Install Script
4
+ # Universal installer that detects OS and runs appropriate installation
5
+
6
+ set -e
7
+
8
+ # Colors for output
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ BLUE='\033[0;34m'
13
+ NC='\033[0m'
14
+
15
+ log_info() {
16
+ echo -e "${BLUE}[INFO]${NC} $1"
17
+ }
18
+
19
+ log_success() {
20
+ echo -e "${GREEN}[SUCCESS]${NC} $1"
21
+ }
22
+
23
+ log_warning() {
24
+ echo -e "${YELLOW}[WARNING]${NC} $1"
25
+ }
26
+
27
+ log_error() {
28
+ echo -e "${RED}[ERROR]${NC} $1"
29
+ }
30
+
31
+ # Detect operating system
32
+ detect_os() {
33
+ case "$(uname -s)" in
34
+ Darwin)
35
+ OS="macos"
36
+ ;;
37
+ Linux)
38
+ OS="linux"
39
+ ;;
40
+ CYGWIN*|MINGW32*|MSYS*|MINGW*)
41
+ OS="windows"
42
+ ;;
43
+ *)
44
+ log_error "Unsupported operating system: $(uname -s)"
45
+ exit 1
46
+ ;;
47
+ esac
48
+
49
+ log_info "Detected OS: $OS"
50
+ }
51
+
52
+ # Download installation script
53
+ download_installer() {
54
+ local script_name="install-${OS}.sh"
55
+ local script_url="https://raw.githubusercontent.com/FSSCoding/fss-link/main/scripts/${script_name}"
56
+
57
+ log_info "Downloading $script_name..."
58
+
59
+ if command -v curl &> /dev/null; then
60
+ curl -fsSL "$script_url" -o "$script_name"
61
+ elif command -v wget &> /dev/null; then
62
+ wget -q "$script_url" -O "$script_name"
63
+ else
64
+ log_error "Neither curl nor wget found. Please install one of them."
65
+ exit 1
66
+ fi
67
+
68
+ if [ -f "$script_name" ]; then
69
+ chmod +x "$script_name"
70
+ log_success "Downloaded $script_name"
71
+ return 0
72
+ else
73
+ log_error "Failed to download $script_name"
74
+ return 1
75
+ fi
76
+ }
77
+
78
+ # Run the appropriate installer
79
+ run_installer() {
80
+ local script_name="install-${OS}.sh"
81
+
82
+ if [ "$OS" = "windows" ]; then
83
+ # For Windows, we need PowerShell
84
+ log_info "For Windows installation, please run:"
85
+ log_info "PowerShell -ExecutionPolicy Bypass -File install-windows.ps1"
86
+ return 0
87
+ fi
88
+
89
+ log_info "Running $script_name..."
90
+ ./"$script_name" "$@"
91
+
92
+ # Clean up
93
+ rm -f "$script_name"
94
+ }
95
+
96
+ # NPM fallback installation
97
+ npm_install() {
98
+ log_info "Attempting direct NPM installation..."
99
+
100
+ if command -v npm &> /dev/null; then
101
+ npm install -g fss-link
102
+
103
+ if command -v fss-link &> /dev/null; then
104
+ log_success "FSS Link installed via NPM"
105
+ log_info "Run 'fss-link --help' to get started"
106
+ return 0
107
+ else
108
+ log_error "NPM installation succeeded but fss-link command not found"
109
+ return 1
110
+ fi
111
+ else
112
+ log_error "NPM not found. Please install Node.js 20+ first."
113
+ return 1
114
+ fi
115
+ }
116
+
117
+ # Main function
118
+ main() {
119
+ echo "🚀 FSS Link Universal Quick Installer"
120
+ echo "====================================="
121
+ echo ""
122
+
123
+ # Handle help flag
124
+ if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
125
+ cat << EOF
126
+ FSS Link Universal Quick Installer
127
+
128
+ Usage: $0 [options]
129
+
130
+ Options:
131
+ --help, -h Show this help message
132
+ --npm-only Skip OS-specific installer, use NPM directly
133
+ --version, -v Show script version
134
+
135
+ This script will:
136
+ 1. Detect your operating system
137
+ 2. Download the appropriate installation script
138
+ 3. Run the OS-specific installer
139
+ 4. Verify the installation
140
+
141
+ Supported Systems:
142
+ - Linux (Ubuntu, Debian, CentOS, Fedora, Arch, openSUSE)
143
+ - macOS (10.15+)
144
+ - Windows (10+)
145
+
146
+ Requirements:
147
+ - Node.js 20.0.0 or higher
148
+ - Build tools for native dependencies
149
+ - Internet connection for downloading
150
+
151
+ Examples:
152
+ curl -fsSL https://raw.githubusercontent.com/FSSCoding/fss-link/main/scripts/quick-install.sh | bash
153
+ bash quick-install.sh --npm-only
154
+ EOF
155
+ exit 0
156
+ fi
157
+
158
+ # Handle version flag
159
+ if [ "${1:-}" = "--version" ] || [ "${1:-}" = "-v" ]; then
160
+ echo "FSS Link Universal Quick Installer v1.0.0"
161
+ exit 0
162
+ fi
163
+
164
+ # Handle NPM-only installation
165
+ if [ "${1:-}" = "--npm-only" ]; then
166
+ npm_install
167
+ exit $?
168
+ fi
169
+
170
+ # Detect OS
171
+ detect_os
172
+
173
+ # Try downloading and running OS-specific installer
174
+ if download_installer; then
175
+ run_installer "$@"
176
+ else
177
+ log_warning "OS-specific installer download failed, trying NPM installation..."
178
+ npm_install
179
+ fi
180
+
181
+ # Final verification
182
+ if command -v fss-link &> /dev/null; then
183
+ VERSION=$(fss-link --version 2>/dev/null || echo "unknown")
184
+ log_success "🎉 FSS Link installation complete!"
185
+ log_info "Version: $VERSION"
186
+ log_info "Run 'fss-link --help' to get started"
187
+ else
188
+ log_error "Installation may have failed - fss-link command not found"
189
+ log_info "Try running: npm install -g fss-link"
190
+ exit 1
191
+ fi
192
+ }
193
+
194
+ # Run main function with all arguments
195
+ main "$@"
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ //
8
+ // Licensed under the Apache License, Version 2.0 (the "License");
9
+ // you may not use this file except in compliance with the License.
10
+ // You may obtain a copy of the License at
11
+ //
12
+ // http://www.apache.org/licenses/LICENSE-2.0
13
+ //
14
+ // Unless required by applicable law or agreed to in writing, software
15
+ // distributed under the License is distributed on an "AS IS" BASIS,
16
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ // See the License for the specific language governing permissions and
18
+ // limitations under the License.
19
+
20
+ import { execSync } from 'child_process';
21
+ import { existsSync, readFileSync } from 'fs';
22
+ import { join, dirname } from 'path';
23
+ import stripJsonComments from 'strip-json-comments';
24
+ import os from 'os';
25
+ import yargs from 'yargs';
26
+ import { hideBin } from 'yargs/helpers';
27
+ import dotenv from 'dotenv';
28
+
29
+ const argv = yargs(hideBin(process.argv)).option('q', {
30
+ alias: 'quiet',
31
+ type: 'boolean',
32
+ default: false,
33
+ }).argv;
34
+
35
+ let fssLinkSandbox = process.env.FSS_LINK_SANDBOX;
36
+
37
+ if (!fssLinkSandbox) {
38
+ const userSettingsFile = join(os.homedir(), '.fss-link', 'settings.json');
39
+ if (existsSync(userSettingsFile)) {
40
+ const settings = JSON.parse(
41
+ stripJsonComments(readFileSync(userSettingsFile, 'utf-8')),
42
+ );
43
+ if (settings.sandbox) {
44
+ fssLinkSandbox = settings.sandbox;
45
+ }
46
+ }
47
+ }
48
+
49
+ if (!fssLinkSandbox) {
50
+ let currentDir = process.cwd();
51
+ while (true) {
52
+ const fssLinkEnv = join(currentDir, '.fss-link', '.env');
53
+ const regularEnv = join(currentDir, '.env');
54
+ if (existsSync(fssLinkEnv)) {
55
+ dotenv.config({ path: fssLinkEnv, quiet: true });
56
+ break;
57
+ } else if (existsSync(regularEnv)) {
58
+ dotenv.config({ path: regularEnv, quiet: true });
59
+ break;
60
+ }
61
+ const parentDir = dirname(currentDir);
62
+ if (parentDir === currentDir) {
63
+ break;
64
+ }
65
+ currentDir = parentDir;
66
+ }
67
+ fssLinkSandbox = process.env.FSS_LINK_SANDBOX;
68
+ }
69
+
70
+ fssLinkSandbox = (fssLinkSandbox || '').toLowerCase();
71
+
72
+ const commandExists = (cmd) => {
73
+ const checkCommand = os.platform() === 'win32' ? 'where' : 'command -v';
74
+ try {
75
+ execSync(`${checkCommand} ${cmd}`, { stdio: 'ignore' });
76
+ return true;
77
+ } catch {
78
+ if (os.platform() === 'win32') {
79
+ try {
80
+ execSync(`${checkCommand} ${cmd}.exe`, { stdio: 'ignore' });
81
+ return true;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+ return false;
87
+ }
88
+ };
89
+
90
+ let command = '';
91
+ if (['1', 'true'].includes(fssLinkSandbox)) {
92
+ if (commandExists('docker')) {
93
+ command = 'docker';
94
+ } else if (commandExists('podman')) {
95
+ command = 'podman';
96
+ } else {
97
+ console.error(
98
+ 'ERROR: install docker or podman or specify command in FSS_LINK_SANDBOX',
99
+ );
100
+ process.exit(1);
101
+ }
102
+ } else if (fssLinkSandbox && !['0', 'false'].includes(fssLinkSandbox)) {
103
+ if (commandExists(fssLinkSandbox)) {
104
+ command = fssLinkSandbox;
105
+ } else {
106
+ console.error(
107
+ `ERROR: missing sandbox command '${fssLinkSandbox}' (from FSS_LINK_SANDBOX)`,
108
+ );
109
+ process.exit(1);
110
+ }
111
+ } else {
112
+ if (os.platform() === 'darwin' && process.env.SEATBELT_PROFILE !== 'none') {
113
+ if (commandExists('sandbox-exec')) {
114
+ command = 'sandbox-exec';
115
+ } else {
116
+ process.exit(1);
117
+ }
118
+ } else {
119
+ process.exit(1);
120
+ }
121
+ }
122
+
123
+ if (!argv.q) {
124
+ console.log(command);
125
+ }
126
+ process.exit(0);
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ //
8
+ // Licensed under the Apache License, Version 2.0 (the "License");
9
+ // you may not use this file except in compliance with the License.
10
+ // You may obtain a copy of the License at
11
+ //
12
+ // http://www.apache.org/licenses/LICENSE-2.0
13
+ //
14
+ // Unless required by applicable law_or_agreed to in writing, software
15
+ // distributed under the License is distributed on an "AS IS" BASIS,
16
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ // See the License for the specific language governing permissions and
18
+ // limitations under the License.
19
+
20
+ import { spawn, execSync } from 'child_process';
21
+ import { dirname, join } from 'path';
22
+ import { fileURLToPath } from 'url';
23
+ import { readFileSync } from 'fs';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const root = join(__dirname, '..');
27
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));
28
+
29
+ // check build status, write warnings to file for app to display if needed
30
+ execSync('node ./scripts/check-build-status.js', {
31
+ stdio: 'inherit',
32
+ cwd: root,
33
+ });
34
+
35
+ const nodeArgs = [];
36
+ let sandboxCommand = undefined;
37
+ try {
38
+ sandboxCommand = execSync('node scripts/sandbox_command.js', {
39
+ cwd: root,
40
+ })
41
+ .toString()
42
+ .trim();
43
+ } catch {
44
+ // ignore
45
+ }
46
+ // if debugging is enabled and sandboxing is disabled, use --inspect-brk flag
47
+ // note with sandboxing this flag is passed to the binary inside the sandbox
48
+ // inside sandbox SANDBOX should be set and sandbox_command.js should fail
49
+ if (process.env.DEBUG && !sandboxCommand) {
50
+ if (process.env.SANDBOX) {
51
+ const port = process.env.DEBUG_PORT || '9229';
52
+ nodeArgs.push(`--inspect-brk=0.0.0.0:${port}`);
53
+ } else {
54
+ nodeArgs.push('--inspect-brk');
55
+ }
56
+ }
57
+
58
+ nodeArgs.push(join(root, 'packages', 'cli'));
59
+ nodeArgs.push(...process.argv.slice(2));
60
+
61
+ const env = {
62
+ ...process.env,
63
+ CLI_VERSION: pkg.version,
64
+ DEV: 'true',
65
+ };
66
+
67
+ if (process.env.DEBUG) {
68
+ // If this is not set, the debugger will pause on the outer process rather
69
+ // than the relaunched process making it harder to debug.
70
+ env.GEMINI_CLI_NO_RELAUNCH = 'true';
71
+ }
72
+ const child = spawn('node', nodeArgs, { stdio: 'inherit', env });
73
+
74
+ child.on('close', (code) => {
75
+ process.exit(code);
76
+ });
@@ -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 'child_process';
10
+ import { join } from 'path';
11
+ import { existsSync, readFileSync } from 'fs';
12
+
13
+ const projectRoot = join(import.meta.dirname, '..');
14
+
15
+ const SETTINGS_DIRECTORY_NAME = '.fss-link';
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 'path';
10
+ import fs from 'fs';
11
+ import { spawn, execSync } from '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();