@qoder-ai/qoder-agent-sdk 1.0.17 → 1.0.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qoder-ai/qoder-agent-sdk",
3
- "version": "1.0.17",
3
+ "version": "1.0.20",
4
4
  "description": "TypeScript SDK for building Qoder-powered coding agents.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -34,54 +34,7 @@
34
34
  "node": ">=18.0.0"
35
35
  },
36
36
  "scripts": {
37
- "build": "tsc",
38
- "watch": "tsc --watch",
39
- "clean": "rm -rf dist",
40
- "bundle": "npm run clean && tsc && node scripts/bundle.mjs",
41
- "bundle:worker": "npm run clean && tsc && node scripts/bundle.mjs --runtime-transport=worker --require-worker-runtime",
42
- "build:obfuscated": "npm run bundle",
43
- "check:package-version": "node scripts/check-package-version.mjs",
44
- "hooks:install": "sh scripts/install-git-hooks.sh",
45
- "release:check": "bash scripts/release-npm.sh check",
46
- "release:beta": "bash scripts/release-npm.sh beta",
47
- "release:latest": "bash scripts/release-npm.sh latest",
48
- "release:promote": "bash scripts/release-npm.sh promote",
49
- "prepublishOnly": "npm run bundle",
50
- "postinstall": "node scripts/postinstall.cjs",
51
- "demo:minimal": "npm run demo:chat",
52
- "demo:image": "node examples/image-input-demo/demo.mjs",
53
- "demo:models": "npm run build && node examples/list-models-demo/demo.mjs",
54
- "demo:service-account-token": "npm run build && node examples/service-account-token-demo/demo.mjs",
55
- "demo:usage": "npm run build && node examples/usage-info-demo/demo.mjs",
56
- "demo:steer-control": "npm run build && node examples/steer-control-demo/demo.mjs",
57
- "demo:chat": "npm run build && node examples/interactive-chat-demo/demo.mjs",
58
- "demo:mcp-oauth": "npm run build && node examples/mcp-oauth-demo/demo.mjs",
59
- "demo:inprocess-mcp": "npm run build && node examples/inprocess-mcp-demo/demo.mjs",
60
- "demo:byok": "npm run build && node examples/byok-validation-demo/demo.mjs",
61
- "test": "npm run test:unit",
62
- "test:unit": "npm run build && node --test tests/*.test.mjs",
63
- "test:query-session-control:unit": "npm run build && node --test tests/query-session-control/unit/*.test.mjs",
64
- "test:query-session-control:functional": "npm run build && node --test tests/query-session-control/functional/*.test.mjs",
65
- "test:query-input-output:unit": "npm run build && node --test tests/query-input-output/unit/*.test.mjs",
66
- "test:query-input-output:functional": "npm run build && node --test tests/query-input-output/functional/*.test.mjs",
67
- "test:query-auth": "npm run test:query-auth:unit && npm run test:query-auth:functional",
68
- "test:query-auth:unit": "npm run build && node --test tests/query-auth/unit/*.test.mjs",
69
- "test:query-auth:functional": "npm run build && node --test tests/query-auth/functional/*.test.mjs",
70
- "test:query-auth:e2e": "npm run test:query-auth:functional",
71
- "test:query-system-prompt:unit": "npm run build && node --test tests/query-system-prompt/unit/*.test.mjs",
72
- "test:query-system-prompt:functional": "npm run build && node --test tests/query-system-prompt/functional/*.test.mjs",
73
- "test:query-mcp:functional": "npm run build && node --test tests/query-mcp/functional/*.test.mjs",
74
- "test:query-output-events:unit": "npm run build && node --test tests/query-output-events/unit/*.test.mjs",
75
- "test:query-output-events:functional": "npm run build && node --test tests/query-output-events/functional/*.test.mjs",
76
- "test:query-working-directory:functional": "npm run build && node --test tests/query-working-directory/functional/*.test.mjs",
77
- "test:query-runtime-control:unit": "npm run build && node --test tests/query-runtime-control/unit/*.test.mjs",
78
- "test:query-runtime-control:functional": "npm run build && node --test tests/query-runtime-control/functional/*.test.mjs",
79
- "test:worker-runtime:functional": "npm run build && node --test tests/worker-runtime/functional/*.test.mjs",
80
- "test:skills-loading-control:unit": "npm run build && node --test tests/skills-loading-control/unit/*.test.mjs",
81
- "test:skills-loading-control:functional": "npm run build && node --test tests/skills-loading-control/functional/*.test.mjs",
82
- "test:plugins-loading-control:unit": "npm run build && node --test tests/plugins-loading-control/unit/*.test.mjs",
83
- "test:plugins-loading-control:functional": "npm run build && node --test tests/plugins-loading-control/functional/*.test.mjs",
84
- "test:integration": "npm run build && node --test tests/integration/**/*.test.mjs"
37
+ "postinstall": "node scripts/postinstall.cjs"
85
38
  },
86
39
  "keywords": [
87
40
  "qoder",
@@ -97,12 +50,7 @@
97
50
  "peerDependencies": {
98
51
  "zod": "^3.25.0 || ^4.0.0"
99
52
  },
100
- "devDependencies": {
101
- "@types/node": "^22.0.0",
102
- "esbuild": "^0.24.2",
103
- "typescript": "^5.7.0",
104
- "zod": "^4.0.0"
105
- },
106
- "qoderCliVersion": "1.1.11",
53
+ "qoderCliVersion": "1.1.18",
54
+ "qoderSdkBrand": "global",
107
55
  "license": "SEE LICENSE IN LICENSE"
108
56
  }
@@ -22,22 +22,15 @@ const https = require('https');
22
22
  const os = require('os');
23
23
  const path = require('path');
24
24
 
25
- const GLOBAL_PROD_DOWNLOAD_BASE_URL =
26
- 'https://download.qoder.com/qodercli/releases';
25
+ const GLOBAL_PROD_DOWNLOAD_BASE_URL = 'https://download.qoder.com/qodercli/releases';
27
26
  const GLOBAL_DEV_DOWNLOAD_BASE_URL =
28
27
  'https://qs-cli-dev.oss-cn-hangzhou.aliyuncs.com/qodercli/dev/releases';
29
- const CN_PROD_DOWNLOAD_BASE_URL =
30
- 'https://static.qoder.com.cn/qoder-cli-cn/releases';
31
- const CN_DEV_DOWNLOAD_BASE_URL =
32
- 'https://static.qoder.com.cn/qoder-cli-cn/dev/releases';
33
- const LOG_PREFIX = '[@qoder-ai/qoder-agent-sdk]';
34
- const WORKER_RUNTIME_ENTRY_NAMES = [
35
- 'qoder-worker-runtime.obf.mjs',
36
- 'qoder-worker-runtime.mjs',
37
- ];
38
- const REQUIRED_WORKER_RUNTIME_ASSETS = [
39
- path.join('vendor', 'ripgrep', 'x64-win32', 'rg.exe'),
40
- ];
28
+ const CN_PROD_DOWNLOAD_BASE_URL = 'https://static.qoder.com.cn/qoder-cli-cn/releases';
29
+ const CN_DEV_DOWNLOAD_BASE_URL = 'https://static.qoder.com.cn/qoder-cli-cn/dev/releases';
30
+ const PACKAGE_JSON = require('../package.json');
31
+ const LOG_PREFIX = `[${PACKAGE_JSON.name ?? '@qoder-ai/qoder-agent-sdk'}]`;
32
+ const WORKER_RUNTIME_ENTRY_NAMES = ['qoder-worker-runtime.obf.mjs', 'qoder-worker-runtime.mjs'];
33
+ const REQUIRED_WORKER_RUNTIME_ASSETS = [path.join('vendor', 'ripgrep', 'x64-win32', 'rg.exe')];
41
34
 
42
35
  function isTruthy(value) {
43
36
  return value === '1' || value === 'true';
@@ -48,12 +41,9 @@ function getCliVersion() {
48
41
  if (fromEnv) return fromEnv.replace(/^v/, '');
49
42
 
50
43
  try {
51
- const packageJson = require('../package.json');
52
- const version = packageJson.qoderCliVersion ?? packageJson.version;
44
+ const version = PACKAGE_JSON.qoderCliVersion ?? PACKAGE_JSON.version;
53
45
  if (!version) {
54
- throw new Error(
55
- 'package.json must define "qoderCliVersion" or "version".',
56
- );
46
+ throw new Error('package.json must define "qoderCliVersion" or "version".');
57
47
  }
58
48
  return String(version).replace(/^v/, '');
59
49
  } catch (error) {
@@ -62,20 +52,21 @@ function getCliVersion() {
62
52
  }
63
53
 
64
54
  function isDevCliVersion(version) {
65
- return /(?:^|[.-])(alpha|dev|nightly)(?:[.-]|$)/i.test(version);
55
+ return (
56
+ /^\d+\.\d+\.\d+-/.test(version) ||
57
+ /(?:^|[.-])(alpha|beta|dev|nightly|rc|canary)(?:[.-]|$)/i.test(version)
58
+ );
66
59
  }
67
60
 
68
61
  function normalizeCliSite(value) {
69
62
  const normalized = String(value).trim().toLowerCase();
70
63
  if (normalized === 'global') return 'global';
71
64
  if (normalized === 'cn') return 'cn';
72
- throw new Error(
73
- `Unsupported QODER_CLI_SITE "${value}". Supported: global, cn`,
74
- );
65
+ throw new Error(`Unsupported QODER_CLI_SITE "${value}". Supported: global, cn`);
75
66
  }
76
67
 
77
68
  function getCliSite() {
78
- return normalizeCliSite(process.env.QODER_CLI_SITE ?? 'global');
69
+ return normalizeCliSite(process.env.QODER_CLI_SITE ?? PACKAGE_JSON.qoderSdkBrand ?? 'global');
79
70
  }
80
71
 
81
72
  function getCliArtifactPrefix(site = getCliSite()) {
@@ -87,10 +78,8 @@ function getDownloadBaseUrl(version = getCliVersion()) {
87
78
  return process.env.QODER_CLI_MIRROR;
88
79
  }
89
80
  const site = getCliSite();
90
- if (site !== 'global') {
91
- throw new Error(
92
- 'QODER_CLI_SITE=cn requires QODER_CLI_MIRROR for CLI downloads.',
93
- );
81
+ if (site === 'cn') {
82
+ return isDevCliVersion(version) ? CN_DEV_DOWNLOAD_BASE_URL : CN_PROD_DOWNLOAD_BASE_URL;
94
83
  }
95
84
  if (isDevCliVersion(version)) {
96
85
  return GLOBAL_DEV_DOWNLOAD_BASE_URL;
@@ -104,13 +93,9 @@ function getWorkerRuntimeDownloadBaseUrl(version = getCliVersion()) {
104
93
  }
105
94
  const site = getCliSite();
106
95
  if (site === 'cn') {
107
- return isDevCliVersion(version)
108
- ? CN_DEV_DOWNLOAD_BASE_URL
109
- : CN_PROD_DOWNLOAD_BASE_URL;
96
+ return isDevCliVersion(version) ? CN_DEV_DOWNLOAD_BASE_URL : CN_PROD_DOWNLOAD_BASE_URL;
110
97
  }
111
- return isDevCliVersion(version)
112
- ? GLOBAL_DEV_DOWNLOAD_BASE_URL
113
- : GLOBAL_PROD_DOWNLOAD_BASE_URL;
98
+ return isDevCliVersion(version) ? GLOBAL_DEV_DOWNLOAD_BASE_URL : GLOBAL_PROD_DOWNLOAD_BASE_URL;
114
99
  }
115
100
 
116
101
  function normalizeDownloadBaseUrl(baseUrl, version = getCliVersion()) {
@@ -158,6 +143,7 @@ function normalizePlatformOverride(value) {
158
143
  'linux-x64',
159
144
  'linux-x64-baseline',
160
145
  'linux-x64-musl',
146
+ 'windows-arm64',
161
147
  'windows-x64',
162
148
  ]);
163
149
 
@@ -180,10 +166,10 @@ function getPlatformTarget() {
180
166
  }
181
167
 
182
168
  if (process.platform === 'win32') {
183
- if (process.arch !== 'x64') {
169
+ if (process.arch !== 'x64' && process.arch !== 'arm64') {
184
170
  throw new Error(`Unsupported Windows architecture: ${process.arch}`);
185
171
  }
186
- return 'windows-x64';
172
+ return `windows-${process.arch}`;
187
173
  }
188
174
 
189
175
  if (process.platform === 'linux') {
@@ -209,10 +195,7 @@ function getArtifactName(target = getPlatformTarget()) {
209
195
 
210
196
  function getDownloadUrl() {
211
197
  const version = getCliVersion();
212
- const baseUrl = normalizeDownloadBaseUrl(
213
- getDownloadBaseUrl(version),
214
- version,
215
- );
198
+ const baseUrl = normalizeDownloadBaseUrl(getDownloadBaseUrl(version), version);
216
199
  return `${baseUrl}/${getArtifactName()}`;
217
200
  }
218
201
 
@@ -226,23 +209,15 @@ function hasWorkerRuntimeDownloadSiteOverride() {
226
209
 
227
210
  function getManifestWorkerRuntime(manifest = readRuntimeManifest()) {
228
211
  const workerRuntime = manifest?.workerRuntime;
229
- return workerRuntime && typeof workerRuntime === 'object'
230
- ? workerRuntime
231
- : null;
212
+ return workerRuntime && typeof workerRuntime === 'object' ? workerRuntime : null;
232
213
  }
233
214
 
234
215
  function getManifestWorkerRuntimeDownloadUrl(manifest = readRuntimeManifest()) {
235
216
  const workerRuntime = getManifestWorkerRuntime(manifest);
236
217
  if (!workerRuntime || workerRuntime.packaged === true) return null;
237
218
 
238
- const candidates = [
239
- workerRuntime.url,
240
- workerRuntime.artifact?.url,
241
- workerRuntime.source?.url,
242
- ];
243
- const url = candidates.find(
244
- (candidate) => typeof candidate === 'string' && candidate.length > 0,
245
- );
219
+ const candidates = [workerRuntime.url, workerRuntime.artifact?.url, workerRuntime.source?.url];
220
+ const url = candidates.find((candidate) => typeof candidate === 'string' && candidate.length > 0);
246
221
  return url ?? null;
247
222
  }
248
223
 
@@ -256,9 +231,7 @@ function getManifestWorkerRuntimeSha256(manifest = readRuntimeManifest()) {
256
231
  workerRuntime.source?.sha256,
257
232
  ];
258
233
  const checksum = candidates.find(
259
- (candidate) =>
260
- typeof candidate === 'string' &&
261
- /^[a-fA-F0-9]{64}$/.test(candidate.trim()),
234
+ (candidate) => typeof candidate === 'string' && /^[a-fA-F0-9]{64}$/.test(candidate.trim()),
262
235
  );
263
236
  return checksum ? checksum.trim().toLowerCase() : null;
264
237
  }
@@ -276,10 +249,7 @@ function getWorkerRuntimeDownloadUrl() {
276
249
  }
277
250
 
278
251
  const version = getCliVersion();
279
- const baseUrl = normalizeDownloadBaseUrl(
280
- getWorkerRuntimeDownloadBaseUrl(version),
281
- version,
282
- );
252
+ const baseUrl = normalizeDownloadBaseUrl(getWorkerRuntimeDownloadBaseUrl(version), version);
283
253
  return `${baseUrl}/${getWorkerRuntimeArtifactName()}`;
284
254
  }
285
255
 
@@ -297,8 +267,52 @@ function getBundledDir() {
297
267
  }
298
268
 
299
269
  function getBundledCliPath() {
300
- const cliName = process.platform === 'win32' ? 'qodercli.exe' : 'qodercli';
301
- return path.join(getBundledDir(), cliName);
270
+ const names = getCliSite() === 'cn' ? ['qodercn', 'qoderclicn'] : ['qoder', 'qodercli'];
271
+ for (const name of names) {
272
+ const cliName = process.platform === 'win32' ? `${name}.exe` : name;
273
+ const candidate = path.join(getBundledDir(), cliName);
274
+ if (fs.existsSync(candidate)) return candidate;
275
+ }
276
+ const fallback = names[1];
277
+ return path.join(getBundledDir(), process.platform === 'win32' ? `${fallback}.exe` : fallback);
278
+ }
279
+
280
+ function getReleaseManifestUrl(version = getCliVersion()) {
281
+ const site = getCliSite();
282
+ let base;
283
+ if (site === 'cn') {
284
+ base = isDevCliVersion(version)
285
+ ? 'https://static.qoder.com.cn/qoder-cli-cn/dev'
286
+ : 'https://static.qoder.com.cn/qoder-cli-cn';
287
+ } else {
288
+ base = isDevCliVersion(version)
289
+ ? 'https://qs-cli-dev.oss-cn-hangzhou.aliyuncs.com/qodercli/dev'
290
+ : 'https://download.qoder.com/qodercli';
291
+ }
292
+ return `${base}/channels/${version}/manifest.json`;
293
+ }
294
+
295
+ function manifestTarget(target = getPlatformTarget()) {
296
+ const [osName, rawArch, variant] = target.split('-');
297
+ const arch = rawArch === 'x64' ? 'amd64' : rawArch;
298
+ return {
299
+ os: osName === 'windows' ? 'windows' : osName,
300
+ arch: variant ? `${arch}-${variant}` : arch,
301
+ };
302
+ }
303
+
304
+ function selectManifestArtifact(manifest, target = getPlatformTarget()) {
305
+ const expected = manifestTarget(target);
306
+ const candidates = (manifest?.files ?? []).filter(
307
+ (file) => file.os === expected.os && file.arch === expected.arch,
308
+ );
309
+ const selected = candidates.find((file) => file.variant !== 'legacy') ?? candidates[0];
310
+ if (!selected?.url || !selected?.sha256) {
311
+ throw new Error(
312
+ `CLI release manifest has no artifact for ${target}: ${getReleaseManifestUrl()}`,
313
+ );
314
+ }
315
+ return selected;
302
316
  }
303
317
 
304
318
  function getPackagedWorkerRuntimeDir() {
@@ -323,9 +337,7 @@ function getPackagedWorkerRuntimeInfoPath() {
323
337
  }
324
338
 
325
339
  function hasPackagedWorkerRuntime() {
326
- return getPackagedWorkerRuntimePaths().some((candidate) =>
327
- fs.existsSync(candidate),
328
- );
340
+ return getPackagedWorkerRuntimePaths().some((candidate) => fs.existsSync(candidate));
329
341
  }
330
342
 
331
343
  function readPackagedWorkerRuntimeInfo() {
@@ -350,9 +362,7 @@ function isPackagedWorkerRuntimeCompatibleWithSite(site = getCliSite()) {
350
362
 
351
363
  const expectedProductName = getCliArtifactPrefix(site);
352
364
  const buildInfo =
353
- runtimeInfo.build && typeof runtimeInfo.build === 'object'
354
- ? runtimeInfo.build
355
- : {};
365
+ runtimeInfo.build && typeof runtimeInfo.build === 'object' ? runtimeInfo.build : {};
356
366
  const actualProductName = buildInfo.productName;
357
367
  const actualSite = buildInfo.site;
358
368
 
@@ -364,11 +374,7 @@ function isPackagedWorkerRuntimeCompatibleWithSite(site = getCliSite()) {
364
374
  return false;
365
375
  }
366
376
 
367
- if (
368
- typeof actualSite === 'string' &&
369
- actualSite.length > 0 &&
370
- actualSite !== site
371
- ) {
377
+ if (typeof actualSite === 'string' && actualSite.length > 0 && actualSite !== site) {
372
378
  return false;
373
379
  }
374
380
 
@@ -398,10 +404,7 @@ function getDefaultRuntimeTransport() {
398
404
  }
399
405
 
400
406
  function shouldInstallWorkerRuntimeForDefaultWorker() {
401
- return (
402
- getDefaultRuntimeTransport() === 'worker' &&
403
- !isPackagedWorkerRuntimeCompatibleWithSite()
404
- );
407
+ return getDefaultRuntimeTransport() === 'worker' && !isPackagedWorkerRuntimeCompatibleWithSite();
405
408
  }
406
409
 
407
410
  function shouldSkipBundledCliDownloadForWorkerRuntime() {
@@ -429,9 +432,7 @@ function requestUrl(url, redirectCount = 0) {
429
432
  reject(new Error('Redirect response missing location header'));
430
433
  return;
431
434
  }
432
- const nextUrl = location.startsWith('http')
433
- ? location
434
- : new URL(location, url).href;
435
+ const nextUrl = location.startsWith('http') ? location : new URL(location, url).href;
435
436
  response.resume();
436
437
  resolve(requestUrl(nextUrl, redirectCount + 1));
437
438
  return;
@@ -456,10 +457,7 @@ async function downloadFile(url, destPath) {
456
457
  }
457
458
 
458
459
  await new Promise((resolve, reject) => {
459
- const totalSize = Number.parseInt(
460
- response.headers['content-length'] || '0',
461
- 10,
462
- );
460
+ const totalSize = Number.parseInt(response.headers['content-length'] || '0', 10);
463
461
  let downloadedSize = 0;
464
462
  let lastProgress = 0;
465
463
  const file = fs.createWriteStream(destPath);
@@ -519,9 +517,7 @@ function sha256File(filePath) {
519
517
 
520
518
  async function verifyChecksum(archiveUrl, archivePath, expectedSha256) {
521
519
  if (isTruthy(process.env.QODER_SKIP_CHECKSUM)) {
522
- console.log(
523
- `${LOG_PREFIX} QODER_SKIP_CHECKSUM is set, skipping checksum verification.`,
524
- );
520
+ console.log(`${LOG_PREFIX} QODER_SKIP_CHECKSUM is set, skipping checksum verification.`);
525
521
  return;
526
522
  }
527
523
 
@@ -532,9 +528,7 @@ async function verifyChecksum(archiveUrl, archivePath, expectedSha256) {
532
528
  }
533
529
  const actual = sha256File(archivePath);
534
530
  if (actual !== expected) {
535
- throw new Error(
536
- `Checksum verification failed. Expected ${expected}, actual ${actual}`,
537
- );
531
+ throw new Error(`Checksum verification failed. Expected ${expected}, actual ${actual}`);
538
532
  }
539
533
  console.log(`${LOG_PREFIX} Checksum verified.`);
540
534
  return;
@@ -545,9 +539,7 @@ async function verifyChecksum(archiveUrl, archivePath, expectedSha256) {
545
539
  const expected = parseSha256(fs.readFileSync(checksumPath, 'utf8'));
546
540
  const actual = sha256File(archivePath);
547
541
  if (actual !== expected) {
548
- throw new Error(
549
- `Checksum verification failed. Expected ${expected}, actual ${actual}`,
550
- );
542
+ throw new Error(`Checksum verification failed. Expected ${expected}, actual ${actual}`);
551
543
  }
552
544
  console.log(`${LOG_PREFIX} Checksum verified.`);
553
545
  }
@@ -577,12 +569,7 @@ function getTarExtractInvocation(archivePath, destDir) {
577
569
  const cwd = pathApi.dirname(archivePath);
578
570
  return {
579
571
  cwd,
580
- args: [
581
- '-xzf',
582
- pathApi.basename(archivePath),
583
- '-C',
584
- pathApi.relative(cwd, destDir) || '.',
585
- ],
572
+ args: ['-xzf', pathApi.basename(archivePath), '-C', pathApi.relative(cwd, destDir) || '.'],
586
573
  };
587
574
  }
588
575
 
@@ -624,7 +611,7 @@ function findCliBinary(dir, cliName) {
624
611
 
625
612
  function isCliBinaryName(fileName, cliName) {
626
613
  if (fileName === cliName) return true;
627
- const sourceNames = ['qodercli', 'qoderclicn'];
614
+ const sourceNames = ['qoder', 'qodercli', 'qodercn', 'qoderclicn'];
628
615
  if (process.platform === 'win32') {
629
616
  return sourceNames.some(
630
617
  (sourceName) =>
@@ -635,8 +622,7 @@ function isCliBinaryName(fileName, cliName) {
635
622
  return sourceNames.some(
636
623
  (sourceName) =>
637
624
  fileName === sourceName ||
638
- (new RegExp(`^${sourceName}-.+$`, 'i').test(fileName) &&
639
- !fileName.endsWith('.sha256')),
625
+ (new RegExp(`^${sourceName}-.+$`, 'i').test(fileName) && !fileName.endsWith('.sha256')),
640
626
  );
641
627
  }
642
628
 
@@ -658,11 +644,12 @@ function copyDirContents(srcDir, destDir) {
658
644
  }
659
645
 
660
646
  function installExtractedContents(extractDir, bundledDir) {
661
- const cliName = process.platform === 'win32' ? 'qodercli.exe' : 'qodercli';
647
+ const canonicalName = getCliSite() === 'cn' ? 'qoderclicn' : 'qodercli';
648
+ const cliName = process.platform === 'win32' ? `${canonicalName}.exe` : canonicalName;
662
649
  const extractedCli = findCliBinary(extractDir, cliName);
663
650
  if (!extractedCli) {
664
651
  throw new Error(
665
- 'CLI binary not found in extracted archive. Expected qodercli/qoderclicn or platform-suffixed variants.',
652
+ 'CLI binary not found in extracted archive. Expected qoder/qodercli/qodercn/qoderclicn or platform-suffixed variants.',
666
653
  );
667
654
  }
668
655
 
@@ -679,17 +666,14 @@ function installExtractedContents(extractDir, bundledDir) {
679
666
  }
680
667
 
681
668
  const files = fs.readdirSync(bundledDir);
682
- console.log(
683
- `${LOG_PREFIX} Copied ${files.length} file(s) to _bundled: ${files.join(', ')}`,
684
- );
669
+ console.log(`${LOG_PREFIX} Copied ${files.length} file(s) to _bundled: ${files.join(', ')}`);
685
670
  return target;
686
671
  }
687
672
 
688
673
  function findWorkerRuntimeRoot(dir) {
689
674
  const entries = fs.readdirSync(dir, { withFileTypes: true });
690
675
  const hasWorkerRuntime = entries.some(
691
- (entry) =>
692
- entry.isFile() && WORKER_RUNTIME_ENTRY_NAMES.includes(entry.name),
676
+ (entry) => entry.isFile() && WORKER_RUNTIME_ENTRY_NAMES.includes(entry.name),
693
677
  );
694
678
  if (hasWorkerRuntime) return dir;
695
679
 
@@ -712,9 +696,7 @@ function getWorkerRuntimeEntryPath(runtimeDir) {
712
696
  function assertWorkerRuntimeComplete(runtimeDir, sourceDescription) {
713
697
  const runtimePath = getWorkerRuntimeEntryPath(runtimeDir);
714
698
  if (!fs.existsSync(runtimePath)) {
715
- throw new Error(
716
- `Worker runtime entry missing in ${sourceDescription}: ${runtimePath}`,
717
- );
699
+ throw new Error(`Worker runtime entry missing in ${sourceDescription}: ${runtimePath}`);
718
700
  }
719
701
 
720
702
  const missingAssets = REQUIRED_WORKER_RUNTIME_ASSETS.filter(
@@ -748,18 +730,14 @@ function installExtractedWorkerRuntime(extractDir, workerDir) {
748
730
  const runtimePath = assertWorkerRuntimeComplete(workerDir, workerDir);
749
731
 
750
732
  const files = fs.readdirSync(workerDir);
751
- console.log(
752
- `${LOG_PREFIX} Copied ${files.length} file(s) to _worker: ${files.join(', ')}`,
753
- );
733
+ console.log(`${LOG_PREFIX} Copied ${files.length} file(s) to _worker: ${files.join(', ')}`);
754
734
  return runtimePath;
755
735
  }
756
736
 
757
737
  async function installWorkerRuntimeFromDownload() {
758
738
  const url = getWorkerRuntimeDownloadUrl();
759
739
  const expectedSha256 = getWorkerRuntimeExpectedSha256(url);
760
- const tempDir = fs.mkdtempSync(
761
- path.join(os.tmpdir(), 'qoder-worker-runtime-'),
762
- );
740
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qoder-worker-runtime-'));
763
741
  const archivePath = path.join(tempDir, path.basename(new URL(url).pathname));
764
742
  const extractDir = path.join(tempDir, 'extract');
765
743
 
@@ -768,10 +746,7 @@ async function installWorkerRuntimeFromDownload() {
768
746
  await verifyChecksum(url, archivePath, expectedSha256);
769
747
  fs.mkdirSync(extractDir, { recursive: true });
770
748
  await extractArchive(archivePath, extractDir);
771
- return installExtractedWorkerRuntime(
772
- extractDir,
773
- getPackagedWorkerRuntimeDir(),
774
- );
749
+ return installExtractedWorkerRuntime(extractDir, getPackagedWorkerRuntimeDir());
775
750
  } finally {
776
751
  fs.rmSync(tempDir, { recursive: true, force: true });
777
752
  }
@@ -779,16 +754,12 @@ async function installWorkerRuntimeFromDownload() {
779
754
 
780
755
  async function main() {
781
756
  if (isTruthy(process.env.QODER_SKIP_DOWNLOAD)) {
782
- console.log(
783
- `${LOG_PREFIX} QODER_SKIP_DOWNLOAD is set, skipping runtime downloads.`,
784
- );
757
+ console.log(`${LOG_PREFIX} QODER_SKIP_DOWNLOAD is set, skipping runtime downloads.`);
785
758
  return;
786
759
  }
787
760
 
788
761
  if (shouldInstallWorkerRuntimeForDefaultWorker()) {
789
- console.log(
790
- `${LOG_PREFIX} Package default runtime is Worker; downloading Worker runtime.`,
791
- );
762
+ console.log(`${LOG_PREFIX} Package default runtime is Worker; downloading Worker runtime.`);
792
763
  await installWorkerRuntimeFromDownload();
793
764
  }
794
765
 
@@ -808,20 +779,41 @@ async function main() {
808
779
  const version = getCliVersion();
809
780
  const bundleName = getCliArtifactPrefix();
810
781
  const target = getPlatformTarget();
811
- const archiveName = getArtifactName(target);
812
- const downloadUrl = getDownloadUrl();
782
+ let archiveName;
783
+ let downloadUrl;
784
+ let expectedSha256;
785
+ if (process.env.QODER_CLI_MIRROR) {
786
+ archiveName = getArtifactName(target);
787
+ downloadUrl = getDownloadUrl();
788
+ } else {
789
+ const manifestUrl = getReleaseManifestUrl(version);
790
+ const manifestPath = path.join(
791
+ fs.mkdtempSync(path.join(os.tmpdir(), 'qoder-cli-manifest-')),
792
+ 'manifest.json',
793
+ );
794
+ try {
795
+ await downloadWithRetry(manifestUrl, manifestPath);
796
+ const selected = selectManifestArtifact(
797
+ JSON.parse(fs.readFileSync(manifestPath, 'utf8')),
798
+ target,
799
+ );
800
+ downloadUrl = selected.url;
801
+ expectedSha256 = selected.sha256;
802
+ archiveName = path.basename(new URL(downloadUrl).pathname);
803
+ } finally {
804
+ fs.rmSync(path.dirname(manifestPath), { recursive: true, force: true });
805
+ }
806
+ }
813
807
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qoder-cli-'));
814
808
  const archivePath = path.join(tempDir, archiveName);
815
809
  const extractDir = path.join(tempDir, 'extract');
816
810
 
817
- console.log(
818
- `${LOG_PREFIX} Installing ${bundleName} ${version} for ${target}.`,
819
- );
811
+ console.log(`${LOG_PREFIX} Installing ${bundleName} ${version} for ${target}.`);
820
812
 
821
813
  try {
822
814
  fs.mkdirSync(extractDir, { recursive: true });
823
815
  await downloadWithRetry(downloadUrl, archivePath);
824
- await verifyChecksum(downloadUrl, archivePath);
816
+ await verifyChecksum(downloadUrl, archivePath, expectedSha256);
825
817
  await extractArchive(archivePath, extractDir);
826
818
  const installedPath = installExtractedContents(extractDir, getBundledDir());
827
819
  const stats = fs.statSync(installedPath);
@@ -829,9 +821,7 @@ async function main() {
829
821
  `${LOG_PREFIX} CLI binary installed: ${installedPath} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`,
830
822
  );
831
823
  } catch (error) {
832
- console.error(
833
- `${LOG_PREFIX} ERROR: Failed to install bundled CLI: ${error.message}`,
834
- );
824
+ console.error(`${LOG_PREFIX} ERROR: Failed to install bundled CLI: ${error.message}`);
835
825
  console.error(
836
826
  `${LOG_PREFIX} Set QODER_SKIP_DOWNLOAD=1 to skip download and use a system qodercli.`,
837
827
  );
@@ -866,6 +856,7 @@ module.exports = {
866
856
  getPackagedWorkerRuntimePaths,
867
857
  getRuntimeManifestPath,
868
858
  getPlatformTarget,
859
+ getReleaseManifestUrl,
869
860
  getDefaultRuntimeTransport,
870
861
  getWorkerRuntimeExpectedSha256,
871
862
  getWorkerRuntimeArtifactName,
@@ -879,6 +870,7 @@ module.exports = {
879
870
  isDevCliVersion,
880
871
  normalizeDownloadBaseUrl,
881
872
  normalizePlatformOverride,
873
+ selectManifestArtifact,
882
874
  readPackagedWorkerRuntimeInfo,
883
875
  readRuntimeManifest,
884
876
  shouldInstallWorkerRuntimeForDefaultWorker,