@anh3d0nic/qwen-code-termux-ice 16.0.4 → 16.0.7
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/bin/qwen-ice +25 -5
- package/package.json +6 -5
- package/scripts/build.js +88 -0
- package/scripts/build_package.js +37 -0
- package/scripts/build_sandbox.js +174 -0
- package/scripts/build_vscode_companion.js +30 -0
- package/scripts/check-build-status.js +148 -0
- package/scripts/check-i18n.ts +462 -0
- package/scripts/check-lockfile.js +74 -0
- package/scripts/clean.js +59 -0
- package/scripts/copy_bundle_assets.js +90 -0
- package/scripts/copy_files.js +86 -0
- package/scripts/create_alias.sh +39 -0
- package/scripts/dev.js +109 -0
- package/scripts/esbuild-shims.js +29 -0
- package/scripts/generate-git-commit-info.js +71 -0
- package/scripts/generate-settings-schema.ts +146 -0
- package/scripts/get-release-version.js +411 -0
- package/scripts/ice-mobile.js +5 -0
- package/scripts/ice-session.js +6 -0
- package/scripts/ice-skills.js +31 -0
- package/scripts/ice-teams.js +34 -0
- package/scripts/ice-v10.js +276 -0
- package/scripts/ice-v11.js +276 -0
- package/scripts/ice-v12.js +568 -0
- package/scripts/ice-v13.js +824 -0
- package/scripts/ice-v14.js +1059 -0
- package/scripts/ice-v15.js +1501 -0
- package/scripts/ice-v2.js +26 -0
- package/scripts/ice-v3-core.js +261 -0
- package/scripts/ice-v3.js +46 -0
- package/scripts/ice-v4.js +657 -0
- package/scripts/ice-v5.js +371 -0
- package/scripts/ice-v6.js +305 -0
- package/scripts/ice-v7.js +291 -0
- package/scripts/ice-v8.js +550 -0
- package/scripts/ice-v9.js +546 -0
- package/scripts/install-ice.sh +70 -0
- package/scripts/install.sh +136 -0
- package/scripts/installation/INSTALLATION_GUIDE.md +250 -0
- package/scripts/installation/install-qwen-with-source.bat +302 -0
- package/scripts/installation/install-qwen-with-source.sh +570 -0
- package/scripts/lint.js +205 -0
- package/scripts/local_telemetry.js +219 -0
- package/scripts/postinstall.cjs +235 -0
- package/scripts/pre-commit.js +22 -0
- package/scripts/prepare-package.js +186 -0
- package/scripts/prepare-termux.cjs +26 -0
- package/scripts/sandbox_command.js +128 -0
- package/scripts/start.js +86 -0
- package/scripts/telemetry.js +85 -0
- package/scripts/telemetry_gcp.js +188 -0
- package/scripts/telemetry_utils.js +450 -0
- package/scripts/test-v10.js +18 -0
- package/scripts/test-v11.js +18 -0
- package/scripts/test-v12.js +18 -0
- package/scripts/test-v13.js +18 -0
- package/scripts/test-v14.js +18 -0
- package/scripts/test-v3.js +47 -0
- package/scripts/test-v4.js +47 -0
- package/scripts/test-v6.js +59 -0
- package/scripts/test-v8.js +42 -0
- package/scripts/test-v9.js +18 -0
- package/scripts/test-windows-paths.js +51 -0
- package/scripts/tests/get-release-version.test.js +186 -0
- package/scripts/tests/test-setup.ts +12 -0
- package/scripts/tests/vitest.config.ts +26 -0
- package/scripts/unused-keys-only-in-locales.json +62 -0
- package/scripts/version.js +112 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Qwen
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Prepares the bundled CLI package for npm publishing
|
|
9
|
+
* This script adds publishing metadata (package.json, README, LICENSE) to dist/
|
|
10
|
+
* All runtime assets (cli.js, vendor/, *.sb) are already in dist/ from the bundle step
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { execSync } from 'node:child_process';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = path.dirname(__filename);
|
|
20
|
+
const rootDir = path.resolve(__dirname, '..');
|
|
21
|
+
|
|
22
|
+
const distDir = path.join(rootDir, 'dist');
|
|
23
|
+
const cliBundlePath = path.join(distDir, 'cli.js');
|
|
24
|
+
const vendorDir = path.join(distDir, 'vendor');
|
|
25
|
+
|
|
26
|
+
// Verify dist directory and bundle exist
|
|
27
|
+
if (!fs.existsSync(distDir)) {
|
|
28
|
+
console.error('Error: dist/ directory not found');
|
|
29
|
+
console.error('Please run "npm run bundle" first');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(cliBundlePath)) {
|
|
34
|
+
console.error(`Error: Bundle not found at ${cliBundlePath}`);
|
|
35
|
+
console.error('Please run "npm run bundle" first');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!fs.existsSync(vendorDir)) {
|
|
40
|
+
console.error(`Error: Vendor directory not found at ${vendorDir}`);
|
|
41
|
+
console.error('Please run "npm run bundle" first');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Copy README and LICENSE
|
|
46
|
+
console.log('Copying documentation files...');
|
|
47
|
+
const filesToCopy = ['README.md', 'LICENSE', 'CHANGELOG.md'];
|
|
48
|
+
for (const file of filesToCopy) {
|
|
49
|
+
const sourcePath = path.join(rootDir, file);
|
|
50
|
+
const destPath = path.join(distDir, file);
|
|
51
|
+
if (fs.existsSync(sourcePath)) {
|
|
52
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
53
|
+
console.log(`Copied ${file}`);
|
|
54
|
+
} else {
|
|
55
|
+
console.warn(`Warning: ${file} not found at ${sourcePath}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Copy locales folder
|
|
60
|
+
console.log('Copying locales folder...');
|
|
61
|
+
const localesSourceDir = path.join(
|
|
62
|
+
rootDir,
|
|
63
|
+
'packages',
|
|
64
|
+
'cli',
|
|
65
|
+
'src',
|
|
66
|
+
'i18n',
|
|
67
|
+
'locales',
|
|
68
|
+
);
|
|
69
|
+
const localesDestDir = path.join(distDir, 'locales');
|
|
70
|
+
|
|
71
|
+
if (fs.existsSync(localesSourceDir)) {
|
|
72
|
+
// Recursive copy function
|
|
73
|
+
function copyRecursiveSync(src, dest) {
|
|
74
|
+
const stats = fs.statSync(src);
|
|
75
|
+
if (stats.isDirectory()) {
|
|
76
|
+
if (!fs.existsSync(dest)) {
|
|
77
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
const entries = fs.readdirSync(src);
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const srcPath = path.join(src, entry);
|
|
82
|
+
const destPath = path.join(dest, entry);
|
|
83
|
+
copyRecursiveSync(srcPath, destPath);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
fs.copyFileSync(src, dest);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
copyRecursiveSync(localesSourceDir, localesDestDir);
|
|
91
|
+
console.log('Copied locales folder');
|
|
92
|
+
} else {
|
|
93
|
+
console.warn(`Warning: locales folder not found at ${localesSourceDir}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Copy extensions folder
|
|
97
|
+
console.log('Copying extension examples folder...');
|
|
98
|
+
const extensionExamplesDir = path.join(
|
|
99
|
+
rootDir,
|
|
100
|
+
'packages',
|
|
101
|
+
'cli',
|
|
102
|
+
'src',
|
|
103
|
+
'commands',
|
|
104
|
+
'extensions',
|
|
105
|
+
'examples',
|
|
106
|
+
);
|
|
107
|
+
const extensionExamplesDestDir = path.join(distDir, 'examples');
|
|
108
|
+
|
|
109
|
+
if (fs.existsSync(extensionExamplesDir)) {
|
|
110
|
+
// Recursive copy function
|
|
111
|
+
function copyRecursiveSync(src, dest) {
|
|
112
|
+
const stats = fs.statSync(src);
|
|
113
|
+
if (stats.isDirectory()) {
|
|
114
|
+
if (!fs.existsSync(dest)) {
|
|
115
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
const entries = fs.readdirSync(src);
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
const srcPath = path.join(src, entry);
|
|
120
|
+
const destPath = path.join(dest, entry);
|
|
121
|
+
copyRecursiveSync(srcPath, destPath);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
fs.copyFileSync(src, dest);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
copyRecursiveSync(extensionExamplesDir, extensionExamplesDestDir);
|
|
129
|
+
console.log('Copied extension examples folder');
|
|
130
|
+
} else {
|
|
131
|
+
console.warn(
|
|
132
|
+
`Warning: extension examples folder not found at ${extensionExamplesDir}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Copy package.json from root and modify it for publishing
|
|
137
|
+
console.log('Creating package.json for distribution...');
|
|
138
|
+
const rootPackageJson = JSON.parse(
|
|
139
|
+
fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8'),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Create a clean package.json for the published package
|
|
143
|
+
const distPackageJson = {
|
|
144
|
+
name: rootPackageJson.name,
|
|
145
|
+
version: rootPackageJson.version,
|
|
146
|
+
description:
|
|
147
|
+
rootPackageJson.description || 'Qwen Code - AI-powered coding assistant',
|
|
148
|
+
repository: rootPackageJson.repository,
|
|
149
|
+
type: 'module',
|
|
150
|
+
main: 'cli.js',
|
|
151
|
+
bin: {
|
|
152
|
+
qwen: 'cli.js',
|
|
153
|
+
},
|
|
154
|
+
files: [
|
|
155
|
+
'cli.js',
|
|
156
|
+
'vendor',
|
|
157
|
+
'*.sb',
|
|
158
|
+
'README.md',
|
|
159
|
+
'LICENSE',
|
|
160
|
+
'CHANGELOG.md',
|
|
161
|
+
'locales',
|
|
162
|
+
],
|
|
163
|
+
config: rootPackageJson.config,
|
|
164
|
+
dependencies: {},
|
|
165
|
+
optionalDependencies: {
|
|
166
|
+
'@mmmbuto/node-pty-android-arm64': '~1.1.0',
|
|
167
|
+
'@lydell/node-pty-linux-arm64': '~1.2.0-beta.2',
|
|
168
|
+
'@teddyzhu/clipboard': '0.0.5',
|
|
169
|
+
'@teddyzhu/clipboard-darwin-arm64': '0.0.5',
|
|
170
|
+
'@teddyzhu/clipboard-darwin-x64': '0.0.5',
|
|
171
|
+
'@teddyzhu/clipboard-linux-x64-gnu': '0.0.5',
|
|
172
|
+
'@teddyzhu/clipboard-linux-arm64-gnu': '0.0.5',
|
|
173
|
+
'@teddyzhu/clipboard-win32-x64-msvc': '0.0.5',
|
|
174
|
+
'@teddyzhu/clipboard-win32-arm64-msvc': '0.0.5',
|
|
175
|
+
},
|
|
176
|
+
engines: rootPackageJson.engines,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
fs.writeFileSync(
|
|
180
|
+
path.join(distDir, 'package.json'),
|
|
181
|
+
JSON.stringify(distPackageJson, null, 2) + '\n',
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
console.log('\n✅ Package prepared for publishing at dist/');
|
|
185
|
+
console.log('\nPackage structure:');
|
|
186
|
+
execSync('ls -lh dist/', { stdio: 'inherit', cwd: rootDir });
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const os = require('node:os');
|
|
8
|
+
const { spawnSync } = require('node:child_process');
|
|
9
|
+
|
|
10
|
+
const isTermux =
|
|
11
|
+
os.platform() === 'android' ||
|
|
12
|
+
process.env.TERMUX_VERSION ||
|
|
13
|
+
(process.env.PREFIX && process.env.PREFIX.includes('com.termux'));
|
|
14
|
+
|
|
15
|
+
// Skip everything on Termux (bundle is pre-built in npm package)
|
|
16
|
+
if (isTermux) {
|
|
17
|
+
console.log('Termux detected: skipping husky and bundle');
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// On Mac/Linux/Windows during npm install, skip bundle
|
|
22
|
+
// Bundle should be run explicitly via 'npm run build && npm run bundle'
|
|
23
|
+
// This avoids issues with unbuilt workspace packages
|
|
24
|
+
console.log('Skipping bundle during npm install.');
|
|
25
|
+
console.log('Run "npm run build && npm run bundle" manually if needed.');
|
|
26
|
+
process.exit(0);
|
|
@@ -0,0 +1,128 @@
|
|
|
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 'node:child_process';
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { join, dirname } from 'node:path';
|
|
23
|
+
import stripJsonComments from 'strip-json-comments';
|
|
24
|
+
import os from 'node: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 qwenSandbox = process.env.QWEN_SANDBOX;
|
|
36
|
+
|
|
37
|
+
if (!qwenSandbox) {
|
|
38
|
+
const userSettingsFile = join(os.homedir(), '.qwen', 'settings.json');
|
|
39
|
+
if (existsSync(userSettingsFile)) {
|
|
40
|
+
const settings = JSON.parse(
|
|
41
|
+
stripJsonComments(readFileSync(userSettingsFile, 'utf-8')),
|
|
42
|
+
);
|
|
43
|
+
if (settings.sandbox) {
|
|
44
|
+
qwenSandbox = settings.sandbox;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!qwenSandbox) {
|
|
50
|
+
let currentDir = process.cwd();
|
|
51
|
+
while (true) {
|
|
52
|
+
const qwenEnv = join(currentDir, '.qwen', '.env');
|
|
53
|
+
const regularEnv = join(currentDir, '.env');
|
|
54
|
+
if (existsSync(qwenEnv)) {
|
|
55
|
+
dotenv.config({ path: qwenEnv, 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
|
+
qwenSandbox = process.env.QWEN_SANDBOX;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
qwenSandbox = (qwenSandbox || '').toLowerCase();
|
|
71
|
+
|
|
72
|
+
const commandExists = (cmd) => {
|
|
73
|
+
// Use 'where.exe' (not 'where') on Windows because PowerShell aliases
|
|
74
|
+
// 'where' to 'Where-Object', which breaks command detection.
|
|
75
|
+
const checkCommand = os.platform() === 'win32' ? 'where.exe' : 'command -v';
|
|
76
|
+
try {
|
|
77
|
+
execSync(`${checkCommand} ${cmd}`, { stdio: 'ignore' });
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
if (os.platform() === 'win32' && !cmd.endsWith('.exe')) {
|
|
81
|
+
try {
|
|
82
|
+
execSync(`${checkCommand} ${cmd}.exe`, { stdio: 'ignore' });
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
let command = '';
|
|
93
|
+
if (['1', 'true'].includes(qwenSandbox)) {
|
|
94
|
+
if (commandExists('docker')) {
|
|
95
|
+
command = 'docker';
|
|
96
|
+
} else if (commandExists('podman')) {
|
|
97
|
+
command = 'podman';
|
|
98
|
+
} else {
|
|
99
|
+
console.error(
|
|
100
|
+
'ERROR: install docker or podman or specify command in QWEN_SANDBOX',
|
|
101
|
+
);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
} else if (qwenSandbox && !['0', 'false'].includes(qwenSandbox)) {
|
|
105
|
+
if (commandExists(qwenSandbox)) {
|
|
106
|
+
command = qwenSandbox;
|
|
107
|
+
} else {
|
|
108
|
+
console.error(
|
|
109
|
+
`ERROR: missing sandbox command '${qwenSandbox}' (from QWEN_SANDBOX)`,
|
|
110
|
+
);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
if (os.platform() === 'darwin' && process.env.SEATBELT_PROFILE !== 'none') {
|
|
115
|
+
if (commandExists('sandbox-exec')) {
|
|
116
|
+
command = 'sandbox-exec';
|
|
117
|
+
} else {
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!argv.q) {
|
|
126
|
+
console.log(command);
|
|
127
|
+
}
|
|
128
|
+
process.exit(0);
|
package/scripts/start.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
// Suppress deprecated url.parse() warnings from dependencies
|
|
21
|
+
process.noDeprecation = true;
|
|
22
|
+
|
|
23
|
+
import { spawn, execSync } from 'node:child_process';
|
|
24
|
+
import { dirname, join } from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { readFileSync } from 'node:fs';
|
|
27
|
+
|
|
28
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const root = join(__dirname, '..');
|
|
30
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));
|
|
31
|
+
|
|
32
|
+
// check build status, write warnings to file for app to display if needed
|
|
33
|
+
execSync('node ./scripts/check-build-status.js', {
|
|
34
|
+
stdio: 'inherit',
|
|
35
|
+
cwd: root,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const nodeArgs = [];
|
|
39
|
+
let sandboxCommand = undefined;
|
|
40
|
+
try {
|
|
41
|
+
sandboxCommand = execSync('node scripts/sandbox_command.js', {
|
|
42
|
+
cwd: root,
|
|
43
|
+
})
|
|
44
|
+
.toString()
|
|
45
|
+
.trim();
|
|
46
|
+
} catch {
|
|
47
|
+
// ignore
|
|
48
|
+
}
|
|
49
|
+
// if debugging is enabled and sandboxing is disabled, use --inspect-brk flag
|
|
50
|
+
// note with sandboxing this flag is passed to the binary inside the sandbox
|
|
51
|
+
// inside sandbox SANDBOX should be set and sandbox_command.js should fail
|
|
52
|
+
if (process.env.DEBUG && !sandboxCommand) {
|
|
53
|
+
if (process.env.SANDBOX) {
|
|
54
|
+
const port = process.env.DEBUG_PORT || '9229';
|
|
55
|
+
nodeArgs.push(`--inspect-brk=0.0.0.0:${port}`);
|
|
56
|
+
} else {
|
|
57
|
+
nodeArgs.push('--inspect-brk');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
nodeArgs.push(join(root, 'packages', 'cli'));
|
|
62
|
+
nodeArgs.push(...process.argv.slice(2));
|
|
63
|
+
|
|
64
|
+
const env = {
|
|
65
|
+
...process.env,
|
|
66
|
+
CLI_VERSION: pkg.version,
|
|
67
|
+
DEV: 'true',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (process.env.DEBUG) {
|
|
71
|
+
// If this is not set, the debugger will pause on the outer process rather
|
|
72
|
+
// than the relaunched process making it harder to debug.
|
|
73
|
+
env.QWEN_CODE_NO_RELAUNCH = 'true';
|
|
74
|
+
}
|
|
75
|
+
// Use process.cwd() to inherit the working directory from launch.json cwd setting
|
|
76
|
+
// This allows debugging from a specific directory (e.g., .todo)
|
|
77
|
+
const workingDir = process.env.QWEN_WORKING_DIR || process.cwd();
|
|
78
|
+
const child = spawn('node', nodeArgs, {
|
|
79
|
+
stdio: 'inherit',
|
|
80
|
+
env,
|
|
81
|
+
cwd: workingDir,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
child.on('close', (code) => {
|
|
85
|
+
process.exit(code);
|
|
86
|
+
});
|
|
@@ -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 Qwen Code 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();
|