@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.
Files changed (69) hide show
  1. package/bin/qwen-ice +25 -5
  2. package/package.json +6 -5
  3. package/scripts/build.js +88 -0
  4. package/scripts/build_package.js +37 -0
  5. package/scripts/build_sandbox.js +174 -0
  6. package/scripts/build_vscode_companion.js +30 -0
  7. package/scripts/check-build-status.js +148 -0
  8. package/scripts/check-i18n.ts +462 -0
  9. package/scripts/check-lockfile.js +74 -0
  10. package/scripts/clean.js +59 -0
  11. package/scripts/copy_bundle_assets.js +90 -0
  12. package/scripts/copy_files.js +86 -0
  13. package/scripts/create_alias.sh +39 -0
  14. package/scripts/dev.js +109 -0
  15. package/scripts/esbuild-shims.js +29 -0
  16. package/scripts/generate-git-commit-info.js +71 -0
  17. package/scripts/generate-settings-schema.ts +146 -0
  18. package/scripts/get-release-version.js +411 -0
  19. package/scripts/ice-mobile.js +5 -0
  20. package/scripts/ice-session.js +6 -0
  21. package/scripts/ice-skills.js +31 -0
  22. package/scripts/ice-teams.js +34 -0
  23. package/scripts/ice-v10.js +276 -0
  24. package/scripts/ice-v11.js +276 -0
  25. package/scripts/ice-v12.js +568 -0
  26. package/scripts/ice-v13.js +824 -0
  27. package/scripts/ice-v14.js +1059 -0
  28. package/scripts/ice-v15.js +1501 -0
  29. package/scripts/ice-v2.js +26 -0
  30. package/scripts/ice-v3-core.js +261 -0
  31. package/scripts/ice-v3.js +46 -0
  32. package/scripts/ice-v4.js +657 -0
  33. package/scripts/ice-v5.js +371 -0
  34. package/scripts/ice-v6.js +305 -0
  35. package/scripts/ice-v7.js +291 -0
  36. package/scripts/ice-v8.js +550 -0
  37. package/scripts/ice-v9.js +546 -0
  38. package/scripts/install-ice.sh +70 -0
  39. package/scripts/install.sh +136 -0
  40. package/scripts/installation/INSTALLATION_GUIDE.md +250 -0
  41. package/scripts/installation/install-qwen-with-source.bat +302 -0
  42. package/scripts/installation/install-qwen-with-source.sh +570 -0
  43. package/scripts/lint.js +205 -0
  44. package/scripts/local_telemetry.js +219 -0
  45. package/scripts/postinstall.cjs +235 -0
  46. package/scripts/pre-commit.js +22 -0
  47. package/scripts/prepare-package.js +186 -0
  48. package/scripts/prepare-termux.cjs +26 -0
  49. package/scripts/sandbox_command.js +128 -0
  50. package/scripts/start.js +86 -0
  51. package/scripts/telemetry.js +85 -0
  52. package/scripts/telemetry_gcp.js +188 -0
  53. package/scripts/telemetry_utils.js +450 -0
  54. package/scripts/test-v10.js +18 -0
  55. package/scripts/test-v11.js +18 -0
  56. package/scripts/test-v12.js +18 -0
  57. package/scripts/test-v13.js +18 -0
  58. package/scripts/test-v14.js +18 -0
  59. package/scripts/test-v3.js +47 -0
  60. package/scripts/test-v4.js +47 -0
  61. package/scripts/test-v6.js +59 -0
  62. package/scripts/test-v8.js +42 -0
  63. package/scripts/test-v9.js +18 -0
  64. package/scripts/test-windows-paths.js +51 -0
  65. package/scripts/tests/get-release-version.test.js +186 -0
  66. package/scripts/tests/test-setup.ts +12 -0
  67. package/scripts/tests/vitest.config.ts +26 -0
  68. package/scripts/unused-keys-only-in-locales.json +62 -0
  69. package/scripts/version.js +112 -0
@@ -0,0 +1,205 @@
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 { mkdirSync, rmSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+
14
+ const ACTIONLINT_VERSION = '1.7.7';
15
+ const SHELLCHECK_VERSION = '0.11.0';
16
+ const YAMLLINT_VERSION = '1.35.1';
17
+
18
+ const TEMP_DIR = join(tmpdir(), 'qwen-code-linters');
19
+
20
+ function getPlatformArch() {
21
+ const platform = process.platform;
22
+ const arch = process.arch;
23
+ if (platform === 'linux' && arch === 'x64') {
24
+ return {
25
+ actionlint: 'linux_amd64',
26
+ shellcheck: 'linux.x86_64',
27
+ };
28
+ }
29
+ if (platform === 'darwin' && arch === 'x64') {
30
+ return {
31
+ actionlint: 'darwin_amd64',
32
+ shellcheck: 'darwin.x86_64',
33
+ };
34
+ }
35
+ if (platform === 'darwin' && arch === 'arm64') {
36
+ return {
37
+ actionlint: 'darwin_arm64',
38
+ shellcheck: 'darwin.aarch64',
39
+ };
40
+ }
41
+ throw new Error(`Unsupported platform/architecture: ${platform}/${arch}`);
42
+ }
43
+
44
+ const platformArch = getPlatformArch();
45
+
46
+ /**
47
+ * @typedef {{
48
+ * check: string;
49
+ * installer: string;
50
+ * run: string;
51
+ * }}
52
+ */
53
+
54
+ /**
55
+ * @type {{[linterName: string]: Linter}}
56
+ */
57
+ const LINTERS = {
58
+ actionlint: {
59
+ check: 'command -v actionlint',
60
+ installer: `
61
+ mkdir -p "${TEMP_DIR}/actionlint"
62
+ curl -sSLo "${TEMP_DIR}/.actionlint.tgz" "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_${platformArch.actionlint}.tar.gz"
63
+ tar -xzf "${TEMP_DIR}/.actionlint.tgz" -C "${TEMP_DIR}/actionlint"
64
+ `,
65
+ run: `
66
+ actionlint \
67
+ -color \
68
+ -ignore 'SC2002:' \
69
+ -ignore 'SC2016:' \
70
+ -ignore 'SC2129:' \
71
+ -ignore 'label ".+" is unknown'
72
+ `,
73
+ },
74
+ shellcheck: {
75
+ check: 'command -v shellcheck',
76
+ installer: `
77
+ mkdir -p "${TEMP_DIR}/shellcheck"
78
+ curl -sSLo "${TEMP_DIR}/.shellcheck.txz" "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.${platformArch.shellcheck}.tar.xz"
79
+ tar -xf "${TEMP_DIR}/.shellcheck.txz" -C "${TEMP_DIR}/shellcheck" --strip-components=1
80
+ `,
81
+ run: `
82
+ git ls-files | grep -v '^integration-tests/terminal-bench/' | grep -E '^([^.]+|.*\\.(sh|zsh|bash))' | xargs file --mime-type \
83
+ | grep "text/x-shellscript" | awk '{ print substr($1, 1, length($1)-1) }' \
84
+ | xargs shellcheck \
85
+ --check-sourced \
86
+ --enable=all \
87
+ --exclude=SC2002,SC2129,SC2310 \
88
+ --severity=style \
89
+ --format=gcc \
90
+ --color=never | sed -e 's/note:/warning:/g' -e 's/style:/warning:/g'
91
+ `,
92
+ },
93
+ yamllint: {
94
+ check: 'command -v yamllint',
95
+ installer: `pip3 install --user "yamllint==${YAMLLINT_VERSION}"`,
96
+ run: "git ls-files | grep -E '\\.(yaml|yml)' | xargs yamllint --format github",
97
+ },
98
+ };
99
+
100
+ function runCommand(command, stdio = 'inherit') {
101
+ try {
102
+ const env = { ...process.env };
103
+ const nodeBin = join(process.cwd(), 'node_modules', '.bin');
104
+ env.PATH = `${nodeBin}:${TEMP_DIR}/actionlint:${TEMP_DIR}/shellcheck:${env.PATH}`;
105
+ if (process.platform === 'darwin') {
106
+ env.PATH = `${env.PATH}:${process.env.HOME}/Library/Python/3.12/bin`;
107
+ } else if (process.platform === 'linux') {
108
+ env.PATH = `${env.PATH}:${process.env.HOME}/.local/bin`;
109
+ }
110
+ execSync(command, { stdio, env });
111
+ return true;
112
+ } catch (_e) {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ export function setupLinters() {
118
+ console.log('Setting up linters...');
119
+ rmSync(TEMP_DIR, { recursive: true, force: true });
120
+ mkdirSync(TEMP_DIR, { recursive: true });
121
+
122
+ for (const linter in LINTERS) {
123
+ const { check, installer } = LINTERS[linter];
124
+ if (!runCommand(check, 'ignore')) {
125
+ console.log(`Installing ${linter}...`);
126
+ if (!runCommand(installer)) {
127
+ console.error(
128
+ `Failed to install ${linter}. Please install it manually.`,
129
+ );
130
+ process.exit(1);
131
+ }
132
+ }
133
+ }
134
+ console.log('All required linters are available.');
135
+ }
136
+
137
+ export function runESLint() {
138
+ console.log('\nRunning ESLint...');
139
+ if (!runCommand('npm run lint:ci')) {
140
+ process.exit(1);
141
+ }
142
+ }
143
+
144
+ export function runActionlint() {
145
+ console.log('\nRunning actionlint...');
146
+ if (!runCommand(LINTERS.actionlint.run)) {
147
+ process.exit(1);
148
+ }
149
+ }
150
+
151
+ export function runShellcheck() {
152
+ console.log('\nRunning shellcheck...');
153
+ if (!runCommand(LINTERS.shellcheck.run)) {
154
+ process.exit(1);
155
+ }
156
+ }
157
+
158
+ export function runYamllint() {
159
+ console.log('\nRunning yamllint...');
160
+ if (!runCommand(LINTERS.yamllint.run)) {
161
+ process.exit(1);
162
+ }
163
+ }
164
+
165
+ export function runPrettier() {
166
+ console.log('\nRunning Prettier...');
167
+ if (!runCommand('prettier --write .')) {
168
+ process.exit(1);
169
+ }
170
+ }
171
+
172
+ function main() {
173
+ const args = process.argv.slice(2);
174
+
175
+ if (args.includes('--setup')) {
176
+ setupLinters();
177
+ }
178
+ if (args.includes('--eslint')) {
179
+ runESLint();
180
+ }
181
+ if (args.includes('--actionlint')) {
182
+ runActionlint();
183
+ }
184
+ if (args.includes('--shellcheck')) {
185
+ runShellcheck();
186
+ }
187
+ if (args.includes('--yamllint')) {
188
+ runYamllint();
189
+ }
190
+ if (args.includes('--prettier')) {
191
+ runPrettier();
192
+ }
193
+
194
+ if (args.length === 0) {
195
+ setupLinters();
196
+ runESLint();
197
+ runActionlint();
198
+ runShellcheck();
199
+ runYamllint();
200
+ runPrettier();
201
+ console.log('\nAll linting checks passed!');
202
+ }
203
+ }
204
+
205
+ main();
@@ -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,235 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ❄️ ICE v15.0.0 — Postinstall Script
4
+ * Runs automatically after: npm install -g @anh3d0nic/qwen-code-termux-ice
5
+ *
6
+ * Creates ~/.qwen-ice/ directory structure and copies ICE scripts
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ // Paths
13
+ const HOME = process.env.HOME || '/data/data/com.termux/files/home';
14
+ const USER_QWEN_DIR = path.join(HOME, '.qwen');
15
+ const INSTALL_DIR = path.join(HOME, '.qwen-ice');
16
+ const PACKAGE_DIR = __dirname + '/..';
17
+
18
+ // Directories to create
19
+ const DIRS = [
20
+ INSTALL_DIR,
21
+ path.join(INSTALL_DIR, 'core'),
22
+ path.join(INSTALL_DIR, 'bin'),
23
+ path.join(INSTALL_DIR, 'memory'),
24
+ path.join(INSTALL_DIR, 'sessions'),
25
+ path.join(INSTALL_DIR, 'config'),
26
+ path.join(INSTALL_DIR, 'logs')
27
+ ];
28
+
29
+ // Files to copy from package to install dir
30
+ const FILES_TO_COPY = [
31
+ { src: 'scripts/ice-v15.js', dest: 'core/ice-v15.js' },
32
+ { src: 'scripts/ice-v14.js', dest: 'core/ice-v14.js' },
33
+ { src: 'scripts/ice-v13.js', dest: 'core/ice-v13.js' },
34
+ { src: 'bin/qwen-ice', dest: 'bin/qwen-ice' },
35
+ { src: 'bin/qwen-ice-api-setup', dest: 'bin/qwen-ice-api-setup' }
36
+ ];
37
+
38
+ // JSON files to preserve from ~/.qwen/
39
+ const JSON_FILES_TO_PRESERVE = [
40
+ 'session_memory.json',
41
+ 'quality_history.json',
42
+ 'confidence_history.json',
43
+ 'session_summary.json',
44
+ 'trend_history.json',
45
+ 'ice_session.json',
46
+ 'ice_active_context.json'
47
+ ];
48
+
49
+ function ensureDir(dir) {
50
+ if (!fs.existsSync(dir)) {
51
+ fs.mkdirSync(dir, { recursive: true });
52
+ console.log(`✅ Created ${dir}`);
53
+ }
54
+ }
55
+
56
+ function copyFile(src, dest) {
57
+ const srcPath = path.join(PACKAGE_DIR, src);
58
+ const destPath = path.join(INSTALL_DIR, dest);
59
+
60
+ if (fs.existsSync(srcPath)) {
61
+ const content = fs.readFileSync(srcPath, 'utf8');
62
+
63
+ // Ensure correct shebang for Termux
64
+ let fixedContent = content;
65
+ if (content.startsWith('#!')) {
66
+ const lines = content.split('\n');
67
+ if (lines[0].includes('/usr/bin/env node') || lines[0].includes('/usr/bin/node')) {
68
+ lines[0] = '#!/data/data/com.termux/files/usr/bin/node';
69
+ fixedContent = lines.join('\n');
70
+ }
71
+ }
72
+
73
+ fs.writeFileSync(destPath, fixedContent);
74
+ fs.chmodSync(destPath, 0o755);
75
+ console.log(`✅ Copied ${src} → ${dest}`);
76
+ } else {
77
+ console.log(`⚠️ Source not found: ${srcPath}`);
78
+ }
79
+ }
80
+
81
+ function preserveUserMemory() {
82
+ let preserved = 0;
83
+
84
+ JSON_FILES_TO_PRESERVE.forEach(file => {
85
+ const srcPath = path.join(USER_QWEN_DIR, file);
86
+ const destPath = path.join(INSTALL_DIR, 'memory', file);
87
+
88
+ if (fs.existsSync(srcPath)) {
89
+ try {
90
+ const content = fs.readFileSync(srcPath, 'utf8');
91
+ // Validate JSON
92
+ JSON.parse(content);
93
+ fs.writeFileSync(destPath, content);
94
+ console.log(`✅ Preserved ${file}`);
95
+ preserved++;
96
+ } catch (e) {
97
+ console.log(`⚠️ Corrupted ${file}, skipping`);
98
+ }
99
+ }
100
+ });
101
+
102
+ return preserved;
103
+ }
104
+
105
+ function createDefaultConfigFiles() {
106
+ const configDir = path.join(INSTALL_DIR, 'config');
107
+ const memoryDir = path.join(INSTALL_DIR, 'memory');
108
+
109
+ // Default API keys config
110
+ const apiKeysFile = path.join(configDir, 'api-keys.json');
111
+ if (!fs.existsSync(apiKeysFile)) {
112
+ const defaultKeys = {
113
+ keys: [
114
+ {
115
+ id: 'qwen-oauth',
116
+ provider: 'qwen',
117
+ type: 'oauth',
118
+ model: 'qwen3-coder-plus',
119
+ active: true,
120
+ note: 'Uses existing Qwen OAuth — do not modify'
121
+ }
122
+ ],
123
+ default_provider: 'qwen',
124
+ fallback_order: ['qwen', 'gemini', 'groq', 'openai', 'dashscope']
125
+ };
126
+ fs.writeFileSync(apiKeysFile, JSON.stringify(defaultKeys, null, 2));
127
+ console.log('✅ Created config/api-keys.json');
128
+ }
129
+
130
+ // Default settings
131
+ const settingsFile = path.join(configDir, 'settings.json');
132
+ if (!fs.existsSync(settingsFile)) {
133
+ const defaultSettings = {
134
+ version: '15.0.0',
135
+ theme: 'default',
136
+ yolo_mode: false,
137
+ auto_approve_commands: [],
138
+ max_context_length: 8192,
139
+ quality_threshold: 5,
140
+ created_at: new Date().toISOString()
141
+ };
142
+ fs.writeFileSync(settingsFile, JSON.stringify(defaultSettings, null, 2));
143
+ console.log('✅ Created config/settings.json');
144
+ }
145
+
146
+ // Default memory files if not preserved
147
+ const memoryFiles = {
148
+ 'session_memory.json': { user_patterns: [], known_errors: [], project_state: {}, user_preferences: [] },
149
+ 'quality_history.json': { history: [] },
150
+ 'confidence_history.json': { history: [] },
151
+ 'session_summary.json': { total_queries: 0, avg_quality: 0, patterns_learned: 0, errors_fixed: 0, top_intent: 'none' },
152
+ 'trend_history.json': { trends: [], alerts: [] }
153
+ };
154
+
155
+ Object.entries(memoryFiles).forEach(([file, data]) => {
156
+ const filePath = path.join(memoryDir, file);
157
+ if (!fs.existsSync(filePath)) {
158
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
159
+ }
160
+ });
161
+ }
162
+
163
+ function createSymlinks() {
164
+ const termuxPrefix = '/data/data/com.termux/files/usr';
165
+ const binDir = path.join(termuxPrefix, 'bin');
166
+
167
+ // Ensure bin directory exists
168
+ if (!fs.existsSync(binDir)) {
169
+ try {
170
+ fs.mkdirSync(binDir, { recursive: true });
171
+ } catch (e) {
172
+ console.log(`⚠️ Cannot create ${binDir}, skipping symlinks`);
173
+ return;
174
+ }
175
+ }
176
+
177
+ // Create symlinks
178
+ const symlinks = [
179
+ { target: path.join(INSTALL_DIR, 'bin', 'qwen-ice'), link: path.join(binDir, 'qwen-ice') },
180
+ { target: path.join(INSTALL_DIR, 'bin', 'qwen-ice-api-setup'), link: path.join(binDir, 'qwen-ice-api-setup') }
181
+ ];
182
+
183
+ symlinks.forEach(({ target, link }) => {
184
+ try {
185
+ if (fs.existsSync(link)) {
186
+ fs.unlinkSync(link);
187
+ }
188
+ fs.symlinkSync(target, link);
189
+ console.log(`✅ Symlink: ${link} → ${target}`);
190
+ } catch (e) {
191
+ console.log(`⚠️ Failed to create symlink ${link}: ${e.message}`);
192
+ }
193
+ });
194
+ }
195
+
196
+ function main() {
197
+ console.log('\n❄️ Qwen Code ICE v15.0.0 — Installing...\n');
198
+
199
+ // Step 1: Create directories
200
+ DIRS.forEach(ensureDir);
201
+
202
+ // Step 2: Copy ICE scripts
203
+ FILES_TO_COPY.forEach(({ src, dest }) => copyFile(src, dest));
204
+
205
+ // Step 3: Preserve user memory
206
+ const preserved = preserveUserMemory();
207
+ if (preserved === 0) {
208
+ console.log('ℹ️ No existing memory files found, creating defaults');
209
+ }
210
+
211
+ // Step 4: Create default config files
212
+ createDefaultConfigFiles();
213
+
214
+ // Step 5: Create symlinks
215
+ createSymlinks();
216
+
217
+ // Final message
218
+ console.log('\n' + '='.repeat(60));
219
+ console.log('✅ ICE v15.0.0 installed to ~/.qwen-ice');
220
+ console.log('='.repeat(60));
221
+ console.log('\n🚀 Commands:');
222
+ console.log(' qwen-ice — Interactive mode');
223
+ console.log(' qwen-ice "query" — Single query');
224
+ console.log(' qwen-ice --version — Show version');
225
+ console.log(' qwen-ice --help — Show help');
226
+ console.log(' qwen-ice api-setup — Configure API keys');
227
+ console.log(' qwen-ice exit — Show session summary\n');
228
+ }
229
+
230
+ // Run if called directly
231
+ if (require.main === module) {
232
+ main();
233
+ }
234
+
235
+ module.exports = { main };
@@ -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
+ }