@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,219 @@
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 { fileURLToPath } from 'node:url';
13
+ import {
14
+ BIN_DIR,
15
+ OTEL_DIR,
16
+ ensureBinary,
17
+ fileExists,
18
+ manageTelemetrySettings,
19
+ registerCleanup,
20
+ waitForPort,
21
+ } from './telemetry_utils.js';
22
+
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = path.dirname(__filename);
25
+
26
+ const OTEL_CONFIG_FILE = path.join(OTEL_DIR, 'collector-local.yaml');
27
+ const OTEL_LOG_FILE = path.join(OTEL_DIR, 'collector.log');
28
+ const JAEGER_LOG_FILE = path.join(OTEL_DIR, 'jaeger.log');
29
+ const JAEGER_PORT = 16686;
30
+
31
+ // This configuration is for the primary otelcol-contrib instance.
32
+ // It receives from the CLI on 4317, exports traces to Jaeger on 14317,
33
+ // and sends metrics/logs to the debug log.
34
+ const OTEL_CONFIG_CONTENT = `
35
+ receivers:
36
+ otlp:
37
+ protocols:
38
+ grpc:
39
+ endpoint: "localhost:4317"
40
+ processors:
41
+ batch:
42
+ timeout: 1s
43
+ exporters:
44
+ otlp:
45
+ endpoint: "localhost:14317"
46
+ tls:
47
+ insecure: true
48
+ debug:
49
+ verbosity: detailed
50
+ service:
51
+ telemetry:
52
+ logs:
53
+ level: "debug"
54
+ metrics:
55
+ level: "none"
56
+ pipelines:
57
+ traces:
58
+ receivers: [otlp]
59
+ processors: [batch]
60
+ exporters: [otlp]
61
+ metrics:
62
+ receivers: [otlp]
63
+ processors: [batch]
64
+ exporters: [debug]
65
+ logs:
66
+ receivers: [otlp]
67
+ processors: [batch]
68
+ exporters: [debug]
69
+ `;
70
+
71
+ async function main() {
72
+ // 1. Ensure binaries are available, downloading if necessary.
73
+ // Binaries are stored in the project's .qwen/otel/bin directory
74
+ // to avoid modifying the user's system.
75
+ if (!fileExists(BIN_DIR)) fs.mkdirSync(BIN_DIR, { recursive: true });
76
+
77
+ const otelcolPath = await ensureBinary(
78
+ 'otelcol-contrib',
79
+ 'open-telemetry/opentelemetry-collector-releases',
80
+ (version, platform, arch, ext) =>
81
+ `otelcol-contrib_${version}_${platform}_${arch}.${ext}`,
82
+ 'otelcol-contrib',
83
+ false, // isJaeger = false
84
+ ).catch((e) => {
85
+ console.error(`🛑 Error getting otelcol-contrib: ${e.message}`);
86
+ return null;
87
+ });
88
+ if (!otelcolPath) process.exit(1);
89
+
90
+ const jaegerPath = await ensureBinary(
91
+ 'jaeger',
92
+ 'jaegertracing/jaeger',
93
+ (version, platform, arch, ext) =>
94
+ `jaeger-${version}-${platform}-${arch}.${ext}`,
95
+ 'jaeger',
96
+ true, // isJaeger = true
97
+ ).catch((e) => {
98
+ console.error(`🛑 Error getting jaeger: ${e.message}`);
99
+ return null;
100
+ });
101
+ if (!jaegerPath) process.exit(1);
102
+
103
+ // 2. Kill any existing processes to ensure a clean start.
104
+ console.log('🧹 Cleaning up old processes and logs...');
105
+ try {
106
+ execSync('pkill -f "otelcol-contrib"');
107
+ console.log('✅ Stopped existing otelcol-contrib process.');
108
+ } catch (_e) {} // eslint-disable-line no-empty
109
+ try {
110
+ execSync('pkill -f "jaeger"');
111
+ console.log('✅ Stopped existing jaeger process.');
112
+ } catch (_e) {} // eslint-disable-line no-empty
113
+ try {
114
+ if (fileExists(OTEL_LOG_FILE)) fs.unlinkSync(OTEL_LOG_FILE);
115
+ console.log('✅ Deleted old collector log.');
116
+ } catch (e) {
117
+ if (e.code !== 'ENOENT') console.error(e);
118
+ }
119
+ try {
120
+ if (fileExists(JAEGER_LOG_FILE)) fs.unlinkSync(JAEGER_LOG_FILE);
121
+ console.log('✅ Deleted old jaeger log.');
122
+ } catch (e) {
123
+ if (e.code !== 'ENOENT') console.error(e);
124
+ }
125
+
126
+ let jaegerProcess, collectorProcess;
127
+ let jaegerLogFd, collectorLogFd;
128
+
129
+ const originalSandboxSetting = manageTelemetrySettings(
130
+ true,
131
+ 'http://localhost:4317',
132
+ 'local',
133
+ );
134
+
135
+ registerCleanup(
136
+ () => [jaegerProcess, collectorProcess],
137
+ () => [jaegerLogFd, collectorLogFd],
138
+ originalSandboxSetting,
139
+ );
140
+
141
+ if (!fileExists(OTEL_DIR)) fs.mkdirSync(OTEL_DIR, { recursive: true });
142
+ fs.writeFileSync(OTEL_CONFIG_FILE, OTEL_CONFIG_CONTENT);
143
+ console.log('📄 Wrote OTEL collector config.');
144
+
145
+ // Start Jaeger
146
+ console.log(`🚀 Starting Jaeger service... Logs: ${JAEGER_LOG_FILE}`);
147
+ jaegerLogFd = fs.openSync(JAEGER_LOG_FILE, 'a');
148
+ jaegerProcess = spawn(
149
+ jaegerPath,
150
+ ['--set=receivers.otlp.protocols.grpc.endpoint=localhost:14317'],
151
+ { stdio: ['ignore', jaegerLogFd, jaegerLogFd] },
152
+ );
153
+ console.log(`⏳ Waiting for Jaeger to start (PID: ${jaegerProcess.pid})...`);
154
+
155
+ try {
156
+ await waitForPort(JAEGER_PORT);
157
+ console.log(`✅ Jaeger started successfully.`);
158
+ } catch (_) {
159
+ console.error(`🛑 Error: Jaeger failed to start on port ${JAEGER_PORT}.`);
160
+ if (jaegerProcess && jaegerProcess.pid) {
161
+ process.kill(jaegerProcess.pid, 'SIGKILL');
162
+ }
163
+ if (fileExists(JAEGER_LOG_FILE)) {
164
+ console.error('📄 Jaeger Log Output:');
165
+ console.error(fs.readFileSync(JAEGER_LOG_FILE, 'utf-8'));
166
+ }
167
+ process.exit(1);
168
+ }
169
+
170
+ // Start the primary OTEL collector
171
+ console.log(`🚀 Starting OTEL collector... Logs: ${OTEL_LOG_FILE}`);
172
+ collectorLogFd = fs.openSync(OTEL_LOG_FILE, 'a');
173
+ collectorProcess = spawn(otelcolPath, ['--config', OTEL_CONFIG_FILE], {
174
+ stdio: ['ignore', collectorLogFd, collectorLogFd],
175
+ });
176
+ console.log(
177
+ `⏳ Waiting for OTEL collector to start (PID: ${collectorProcess.pid})...`,
178
+ );
179
+
180
+ try {
181
+ await waitForPort(4317);
182
+ console.log(`✅ OTEL collector started successfully.`);
183
+ } catch (_) {
184
+ console.error(`🛑 Error: OTEL collector failed to start on port 4317.`);
185
+ if (collectorProcess && collectorProcess.pid) {
186
+ process.kill(collectorProcess.pid, 'SIGKILL');
187
+ }
188
+ if (fileExists(OTEL_LOG_FILE)) {
189
+ console.error('📄 OTEL Collector Log Output:');
190
+ console.error(fs.readFileSync(OTEL_LOG_FILE, 'utf-8'));
191
+ }
192
+ process.exit(1);
193
+ }
194
+
195
+ [jaegerProcess, collectorProcess].forEach((proc) => {
196
+ if (proc) {
197
+ proc.on('error', (err) => {
198
+ console.error(`${proc.spawnargs[0]} process error:`, err);
199
+ process.exit(1);
200
+ });
201
+ }
202
+ });
203
+
204
+ console.log(`
205
+ ✨ Local telemetry environment is running.`);
206
+ console.log(
207
+ `
208
+ 🔎 View traces in the Jaeger UI: http://localhost:${JAEGER_PORT}`,
209
+ );
210
+ console.log(`📊 View metrics in the logs and metrics: ${OTEL_LOG_FILE}`);
211
+ console.log(
212
+ `
213
+ 📄 Tail logs and metrics in another terminal: tail -f ${OTEL_LOG_FILE}`,
214
+ );
215
+ console.log(`
216
+ Press Ctrl+C to exit.`);
217
+ }
218
+
219
+ main();
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ // TERMUX: Minimal install message
8
+ const os = require('node:os');
9
+
10
+ if (os.platform() === 'android' || process.env.PREFIX?.includes('com.termux')) {
11
+ console.log('✓ qwen-code-termux installed');
12
+ console.log(' Run: qwen');
13
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { execSync } from 'node:child_process';
8
+ import lintStaged from 'lint-staged';
9
+
10
+ try {
11
+ // Get repository root
12
+ const root = execSync('git rev-parse --show-toplevel').toString().trim();
13
+
14
+ // Run lint-staged with API directly
15
+ const passed = await lintStaged({ cwd: root });
16
+
17
+ // Exit with appropriate code
18
+ process.exit(passed ? 0 : 1);
19
+ } catch {
20
+ // Exit with error code
21
+ process.exit(1);
22
+ }
@@ -0,0 +1,162 @@
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'];
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 package.json from root and modify it for publishing
97
+ console.log('Creating package.json for distribution...');
98
+ const rootPackageJson = JSON.parse(
99
+ fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8'),
100
+ );
101
+ const corePackageJson = JSON.parse(
102
+ fs.readFileSync(
103
+ path.join(rootDir, 'packages', 'core', 'package.json'),
104
+ 'utf-8',
105
+ ),
106
+ );
107
+
108
+ const runtimeDependencies = {};
109
+ if (corePackageJson.dependencies?.tiktoken) {
110
+ runtimeDependencies.tiktoken = corePackageJson.dependencies.tiktoken;
111
+ }
112
+ const isTermuxBuild =
113
+ process.platform === 'android' ||
114
+ !!process.env['TERMUX_VERSION'] ||
115
+ !!(process.env['PREFIX'] && process.env['PREFIX'].includes('com.termux'));
116
+ const optionalDependencies = isTermuxBuild
117
+ ? undefined
118
+ : {
119
+ '@lydell/node-pty': '1.1.0',
120
+ '@lydell/node-pty-darwin-arm64': '1.1.0',
121
+ '@lydell/node-pty-darwin-x64': '1.1.0',
122
+ '@lydell/node-pty-linux-x64': '1.1.0',
123
+ '@lydell/node-pty-win32-arm64': '1.1.0',
124
+ '@lydell/node-pty-win32-x64': '1.1.0',
125
+ 'node-pty': '^1.0.0',
126
+ };
127
+
128
+ // Create a clean package.json for the published package
129
+ const distPackageJson = {
130
+ name: rootPackageJson.name,
131
+ version: rootPackageJson.version,
132
+ description:
133
+ rootPackageJson.description || 'Qwen Code - AI-powered coding assistant',
134
+ repository: rootPackageJson.repository,
135
+ type: 'module',
136
+ main: 'cli.js',
137
+ bin: {
138
+ qwen: 'cli.js',
139
+ },
140
+ files: [
141
+ 'cli.js',
142
+ 'vendor',
143
+ '*.sb',
144
+ 'tiktoken_bg.wasm',
145
+ 'README.md',
146
+ 'LICENSE',
147
+ 'locales',
148
+ ],
149
+ config: rootPackageJson.config,
150
+ dependencies: runtimeDependencies,
151
+ engines: rootPackageJson.engines,
152
+ ...(optionalDependencies ? { optionalDependencies } : {}),
153
+ };
154
+
155
+ fs.writeFileSync(
156
+ path.join(distDir, 'package.json'),
157
+ JSON.stringify(distPackageJson, null, 2) + '\n',
158
+ );
159
+
160
+ console.log('\n✅ Package prepared for publishing at dist/');
161
+ console.log('\nPackage structure:');
162
+ execSync('ls -lh dist/', { stdio: 'inherit', cwd: rootDir });
@@ -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 '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 geminiSandbox = process.env.GEMINI_SANDBOX;
36
+
37
+ if (!geminiSandbox) {
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
+ geminiSandbox = settings.sandbox;
45
+ }
46
+ }
47
+ }
48
+
49
+ if (!geminiSandbox) {
50
+ let currentDir = process.cwd();
51
+ while (true) {
52
+ const geminiEnv = join(currentDir, '.qwen', '.env');
53
+ const regularEnv = join(currentDir, '.env');
54
+ if (existsSync(geminiEnv)) {
55
+ dotenv.config({ path: geminiEnv, 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
+ geminiSandbox = process.env.GEMINI_SANDBOX;
68
+ }
69
+
70
+ geminiSandbox = (geminiSandbox || '').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(geminiSandbox)) {
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 GEMINI_SANDBOX',
99
+ );
100
+ process.exit(1);
101
+ }
102
+ } else if (geminiSandbox && !['0', 'false'].includes(geminiSandbox)) {
103
+ if (commandExists(geminiSandbox)) {
104
+ command = geminiSandbox;
105
+ } else {
106
+ console.error(
107
+ `ERROR: missing sandbox command '${geminiSandbox}' (from GEMINI_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,83 @@
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 'node:child_process';
21
+ import { dirname, join } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { readFileSync } from 'node: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.QWEN_CODE_NO_RELAUNCH = 'true';
71
+ }
72
+ // Use process.cwd() to inherit the working directory from launch.json cwd setting
73
+ // This allows debugging from a specific directory (e.g., .todo)
74
+ const workingDir = process.env.QWEN_WORKING_DIR || process.cwd();
75
+ const child = spawn('node', nodeArgs, {
76
+ stdio: 'inherit',
77
+ env,
78
+ cwd: workingDir,
79
+ });
80
+
81
+ child.on('close', (code) => {
82
+ process.exit(code);
83
+ });