@gaiaworks/gaia-cli 0.3.8 → 0.3.10

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/README.md CHANGED
@@ -1,32 +1,32 @@
1
- # @gaiaworks/gaia-cli
2
-
3
- 这是 `gaia-cli` 的 npm 分发壳包,用于通过 `npx` 下载并运行对应版本的 Go 二进制。
4
-
5
- ## 使用方式
6
-
7
- 临时执行:
8
-
9
- ```bash
10
- npx @gaiaworks/gaia-cli version
11
- ```
12
-
13
- 全局安装:
14
-
15
- ```bash
16
- npx @gaiaworks/gaia-cli@latest install
17
- gaia version
18
- ```
19
-
20
- 安装模式:
21
-
22
- ```bash
23
- npx @gaiaworks/gaia-cli@latest install
24
- npx @gaiaworks/gaia-cli@latest install --mode external
25
- npx @gaiaworks/gaia-cli@latest install --mode internal
26
- ```
27
-
28
- 未指定 `--mode` 时默认写入 `external`。安装脚本会执行 `gaia setup --mode <external|internal>`,用于初始化本地 `install_mode` 和 Agent Skills。
29
-
1
+ # @gaiaworks/gaia-cli
2
+
3
+ 这是 `gaia-cli` 的 npm 分发壳包,用于通过 `npx` 下载并运行对应版本的 Go 二进制。
4
+
5
+ ## 使用方式
6
+
7
+ 临时执行:
8
+
9
+ ```bash
10
+ npx @gaiaworks/gaia-cli version
11
+ ```
12
+
13
+ 全局安装:
14
+
15
+ ```bash
16
+ npx @gaiaworks/gaia-cli@latest install
17
+ gaia version
18
+ ```
19
+
20
+ 安装模式:
21
+
22
+ ```bash
23
+ npx @gaiaworks/gaia-cli@latest install
24
+ npx @gaiaworks/gaia-cli@latest install --mode external
25
+ npx @gaiaworks/gaia-cli@latest install --mode internal
26
+ ```
27
+
28
+ 未指定 `--mode` 时默认写入 `external`。安装脚本会执行 `gaia setup --mode <external|internal>`,用于初始化本地 `install_mode` 和 Agent Skills。
29
+
30
30
  安装时会根据 npm 包版本,从 OSS 的同版本目录下载当前平台对应的 `gaia-cli` 二进制。
31
31
 
32
32
  升级到 npm `latest` 并保留当前安装模式:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaiaworks/gaia-cli",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "JavaScript launcher for gaia-cli.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -1,13 +1,13 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const { spawnSync } = require('child_process');
7
- const { binaryName, install } = require('./install');
8
-
9
- const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
10
- const packageSpec = `${packageJson.name}@${packageJson.version}`;
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { spawnSync } = require('child_process');
7
+ const { binaryName, install } = require('./install');
8
+
9
+ const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
10
+ const packageSpec = `${packageJson.name}@${packageJson.version}`;
11
11
  const binaryPath = path.join(__dirname, '..', 'bin', binaryName());
12
12
  const shellPathMarker = '# Added by gaia-cli installer';
13
13
 
@@ -24,146 +24,146 @@ function npmInvocation(args) {
24
24
  }
25
25
  return { command: 'npm', args };
26
26
  }
27
-
28
- function parseInstallArgs(args) {
29
- let mode = 'external';
30
- let explicitMode = false;
31
- const npmArgs = [];
32
-
33
- for (let i = 0; i < args.length; i += 1) {
34
- const arg = args[i];
35
- if (arg === '--mode') {
36
- const value = args[i + 1];
37
- if (!value) {
38
- throw new Error('--mode requires a value: external or internal');
39
- }
40
- mode = value;
41
- explicitMode = true;
42
- i += 1;
43
- continue;
44
- }
45
- if (arg.startsWith('--mode=')) {
46
- mode = arg.slice('--mode='.length);
47
- explicitMode = true;
48
- continue;
49
- }
50
- npmArgs.push(arg);
51
- }
52
-
53
- if (mode !== 'external' && mode !== 'internal') {
54
- throw new Error(`Unsupported mode: ${mode}. Expected external or internal.`);
55
- }
56
-
57
- return { explicitMode, mode, npmArgs };
58
- }
59
-
60
- function run(command, args, options = {}) {
61
- const result = spawnSync(command, args, {
62
- stdio: options.stdio || 'inherit',
63
- encoding: 'utf8',
64
- });
65
-
66
- if (result.error) {
67
- console.error(`[gaia-cli] Failed to run ${command}: ${result.error.message}`);
68
- process.exit(1);
69
- }
70
-
71
- if (result.status !== 0) {
72
- process.exit(typeof result.status === 'number' ? result.status : 1);
73
- }
74
- }
75
-
76
- function runCapture(command, args) {
77
- return spawnSync(command, args, {
78
- stdio: ['ignore', 'pipe', 'pipe'],
79
- encoding: 'utf8',
80
- });
81
- }
82
-
27
+
28
+ function parseInstallArgs(args) {
29
+ let mode = 'external';
30
+ let explicitMode = false;
31
+ const npmArgs = [];
32
+
33
+ for (let i = 0; i < args.length; i += 1) {
34
+ const arg = args[i];
35
+ if (arg === '--mode') {
36
+ const value = args[i + 1];
37
+ if (!value) {
38
+ throw new Error('--mode requires a value: external or internal');
39
+ }
40
+ mode = value;
41
+ explicitMode = true;
42
+ i += 1;
43
+ continue;
44
+ }
45
+ if (arg.startsWith('--mode=')) {
46
+ mode = arg.slice('--mode='.length);
47
+ explicitMode = true;
48
+ continue;
49
+ }
50
+ npmArgs.push(arg);
51
+ }
52
+
53
+ if (mode !== 'external' && mode !== 'internal') {
54
+ throw new Error(`Unsupported mode: ${mode}. Expected external or internal.`);
55
+ }
56
+
57
+ return { explicitMode, mode, npmArgs };
58
+ }
59
+
60
+ function run(command, args, options = {}) {
61
+ const result = spawnSync(command, args, {
62
+ stdio: options.stdio || 'inherit',
63
+ encoding: 'utf8',
64
+ });
65
+
66
+ if (result.error) {
67
+ console.error(`[gaia-cli] Failed to run ${command}: ${result.error.message}`);
68
+ process.exit(1);
69
+ }
70
+
71
+ if (result.status !== 0) {
72
+ process.exit(typeof result.status === 'number' ? result.status : 1);
73
+ }
74
+ }
75
+
76
+ function runCapture(command, args) {
77
+ return spawnSync(command, args, {
78
+ stdio: ['ignore', 'pipe', 'pipe'],
79
+ encoding: 'utf8',
80
+ });
81
+ }
82
+
83
83
  function npmGlobalBinDir() {
84
84
  const npm = npmInvocation(['prefix', '-g']);
85
85
  const result = runCapture(npm.command, npm.args);
86
- if (result.error || result.status !== 0) return null;
87
- const prefix = result.stdout.trim();
88
- if (!prefix) return null;
89
- return process.platform === 'win32' ? prefix : path.join(prefix, 'bin');
90
- }
91
-
86
+ if (result.error || result.status !== 0) return null;
87
+ const prefix = result.stdout.trim();
88
+ if (!prefix) return null;
89
+ return process.platform === 'win32' ? prefix : path.join(prefix, 'bin');
90
+ }
91
+
92
92
  function npmGlobalCommandPath(binDir, platform = process.platform) {
93
- if (!binDir) return null;
94
- if (platform === 'win32') return path.win32.join(binDir, 'gaia.cmd');
95
- return path.posix.join(binDir, 'gaia');
93
+ if (!binDir) return null;
94
+ if (platform === 'win32') return path.win32.join(binDir, 'gaia.cmd');
95
+ return path.posix.join(binDir, 'gaia');
96
96
  }
97
97
 
98
98
  function setupCommandPath(packageBinary) {
99
99
  return packageBinary;
100
100
  }
101
-
102
- function pathContains(dir) {
103
- if (!dir) return false;
104
- const entries = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
105
- return entries.some((entry) => path.resolve(entry) === path.resolve(dir));
106
- }
107
-
108
- function shellProfilePath(env = process.env) {
109
- if (process.platform === 'win32') return null;
110
- const home = env.HOME;
111
- if (!home) return null;
112
- const shell = path.basename(env.SHELL || '');
113
- if (shell === 'zsh') return path.join(home, '.zshrc');
114
- if (shell === 'bash') return path.join(home, '.bashrc');
115
- return path.join(home, '.profile');
116
- }
117
-
118
- function ensureWindowsPathConfigured(binDir) {
119
- if (!binDir || process.platform !== 'win32' || pathContains(binDir)) return null;
120
- const command = [
121
- "$dir = $args[0]",
122
- "$current = [Environment]::GetEnvironmentVariable('Path', 'User')",
123
- "if (-not $current) { $current = '' }",
124
- "$parts = $current -split ';' | Where-Object { $_ }",
125
- "if ($parts -notcontains $dir) {",
126
- " $next = (@($dir) + $parts) -join ';'",
127
- " [Environment]::SetEnvironmentVariable('Path', $next, 'User')",
128
- "}",
129
- ].join("; ");
130
- const result = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command, binDir], {
131
- stdio: ["ignore", "pipe", "pipe"],
132
- encoding: "utf8",
133
- });
134
- if (result.error || result.status !== 0) {
135
- const detail = result.error ? result.error.message : `${result.stdout || ""}${result.stderr || ""}`.trim();
136
- throw new Error(`Failed to update user PATH: ${detail}`);
137
- }
138
- return "Windows user PATH";
139
- }
140
-
141
- function pathExportBlock(binDir) {
142
- return `${shellPathMarker}\nexport PATH="${binDir}:$PATH"\n`;
143
- }
144
-
101
+
102
+ function pathContains(dir) {
103
+ if (!dir) return false;
104
+ const entries = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
105
+ return entries.some((entry) => path.resolve(entry) === path.resolve(dir));
106
+ }
107
+
108
+ function shellProfilePath(env = process.env) {
109
+ if (process.platform === 'win32') return null;
110
+ const home = env.HOME;
111
+ if (!home) return null;
112
+ const shell = path.basename(env.SHELL || '');
113
+ if (shell === 'zsh') return path.join(home, '.zshrc');
114
+ if (shell === 'bash') return path.join(home, '.bashrc');
115
+ return path.join(home, '.profile');
116
+ }
117
+
118
+ function ensureWindowsPathConfigured(binDir) {
119
+ if (!binDir || process.platform !== 'win32' || pathContains(binDir)) return null;
120
+ const command = [
121
+ "$dir = $args[0]",
122
+ "$current = [Environment]::GetEnvironmentVariable('Path', 'User')",
123
+ "if (-not $current) { $current = '' }",
124
+ "$parts = $current -split ';' | Where-Object { $_ }",
125
+ "if ($parts -notcontains $dir) {",
126
+ " $next = (@($dir) + $parts) -join ';'",
127
+ " [Environment]::SetEnvironmentVariable('Path', $next, 'User')",
128
+ "}",
129
+ ].join("; ");
130
+ const result = spawnSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", command, binDir], {
131
+ stdio: ["ignore", "pipe", "pipe"],
132
+ encoding: "utf8",
133
+ });
134
+ if (result.error || result.status !== 0) {
135
+ const detail = result.error ? result.error.message : `${result.stdout || ""}${result.stderr || ""}`.trim();
136
+ throw new Error(`Failed to update user PATH: ${detail}`);
137
+ }
138
+ return "Windows user PATH";
139
+ }
140
+
141
+ function pathExportBlock(binDir) {
142
+ return `${shellPathMarker}\nexport PATH="${binDir}:$PATH"\n`;
143
+ }
144
+
145
145
  function ensurePathConfigured(binDir, profilePath = shellProfilePath(), platform = process.platform) {
146
146
  if (!binDir) return null;
147
147
  if (platform === 'win32') {
148
148
  return ensureWindowsPathConfigured(binDir);
149
- }
150
- if (!profilePath || pathContains(binDir)) return null;
151
- const existing = fs.existsSync(profilePath) ? fs.readFileSync(profilePath, 'utf8') : '';
152
- if (existing.includes(binDir) || existing.includes(shellPathMarker)) return profilePath;
153
- const prefix = existing && !existing.endsWith('\n') ? '\n' : '';
154
- fs.appendFileSync(profilePath, `${prefix}${pathExportBlock(binDir)}`);
155
- return profilePath;
156
- }
157
-
158
- function ensureLocalBinary() {
159
- if (fs.existsSync(binaryPath)) return;
160
- install();
161
- }
162
-
163
- function installWizard(args) {
164
- const { explicitMode, mode, npmArgs } = parseInstallArgs(args);
165
- if (npmArgs.length > 0) {
166
- throw new Error(`Unsupported install arguments: ${npmArgs.join(' ')}`);
149
+ }
150
+ if (!profilePath || pathContains(binDir)) return null;
151
+ const existing = fs.existsSync(profilePath) ? fs.readFileSync(profilePath, 'utf8') : '';
152
+ if (existing.includes(binDir) || existing.includes(shellPathMarker)) return profilePath;
153
+ const prefix = existing && !existing.endsWith('\n') ? '\n' : '';
154
+ fs.appendFileSync(profilePath, `${prefix}${pathExportBlock(binDir)}`);
155
+ return profilePath;
156
+ }
157
+
158
+ function ensureLocalBinary() {
159
+ if (fs.existsSync(binaryPath)) return;
160
+ install();
161
+ }
162
+
163
+ function installWizard(args) {
164
+ const { explicitMode, mode, npmArgs } = parseInstallArgs(args);
165
+ if (npmArgs.length > 0) {
166
+ throw new Error(`Unsupported install arguments: ${npmArgs.join(' ')}`);
167
167
  }
168
168
 
169
169
  ensureLocalBinary();
@@ -171,40 +171,47 @@ function installWizard(args) {
171
171
  run(npm.command, npm.args);
172
172
  const globalBinDir = npmGlobalBinDir();
173
173
  const setupCommand = setupCommandPath(binaryPath);
174
- run(setupCommand, ['setup', '--mode', mode], { stdio: 'ignore' });
175
- const profilePath = ensurePathConfigured(globalBinDir);
176
- const suffix = explicitMode ? `当前模式: ${mode}。` : '';
177
- if (profilePath && globalBinDir && !pathContains(globalBinDir)) {
178
- console.error(`[gaia-cli] 安装完成,${suffix}已更新 PATH 配置: ${profilePath}`);
179
- console.error(`[gaia-cli] 重新打开终端后可以执行: gaia version`);
180
- return;
181
- }
182
- if (explicitMode) {
183
- console.error(`[gaia-cli] 安装完成,当前模式: ${mode}。可以执行: gaia version`);
184
- } else {
185
- console.error('[gaia-cli] 安装完成。可以执行: gaia version');
186
- }
187
- }
188
-
189
- if (require.main === module) {
190
- try {
191
- installWizard(process.argv.slice(2));
192
- } catch (error) {
193
- console.error(`[gaia-cli] Install failed: ${error.message}`);
194
- process.exit(1);
195
- }
196
- }
197
-
198
- module.exports = {
199
- ensurePathConfigured,
200
- ensureWindowsPathConfigured,
174
+ console.error('[gaia-cli] 正在安装 Gaia Skill: gaia-shared...');
175
+ try {
176
+ run(setupCommand, ['setup', '--mode', mode], { stdio: 'ignore' });
177
+ } catch (error) {
178
+ console.error('[gaia-cli] gaia-shared 安装失败。');
179
+ throw error;
180
+ }
181
+ console.error('[gaia-cli] ✓ Gaia Skill gaia-shared 安装完成。');
182
+ const profilePath = ensurePathConfigured(globalBinDir);
183
+ const suffix = explicitMode ? `当前模式: ${mode}。` : '';
184
+ if (profilePath && globalBinDir && !pathContains(globalBinDir)) {
185
+ console.error(`[gaia-cli] 安装完成,${suffix}已更新 PATH 配置: ${profilePath}`);
186
+ console.error(`[gaia-cli] 重新打开终端后可以执行: gaia version`);
187
+ return;
188
+ }
189
+ if (explicitMode) {
190
+ console.error(`[gaia-cli] 安装完成,当前模式: ${mode}。可以执行: gaia version`);
191
+ } else {
192
+ console.error('[gaia-cli] 安装完成。可以执行: gaia version');
193
+ }
194
+ }
195
+
196
+ if (require.main === module) {
197
+ try {
198
+ installWizard(process.argv.slice(2));
199
+ } catch (error) {
200
+ console.error(`[gaia-cli] Install failed: ${error.message}`);
201
+ process.exit(1);
202
+ }
203
+ }
204
+
205
+ module.exports = {
206
+ ensurePathConfigured,
207
+ ensureWindowsPathConfigured,
201
208
  npmGlobalBinDir,
202
209
  npmGlobalCommandPath,
203
210
  npmInvocation,
204
- pathContains,
205
- pathExportBlock,
211
+ pathContains,
212
+ pathExportBlock,
206
213
  shellProfilePath,
207
214
  setupCommandPath,
208
- installWizard,
209
- parseInstallArgs,
210
- };
215
+ installWizard,
216
+ parseInstallArgs,
217
+ };
@@ -1,25 +1,25 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const os = require('os');
6
- const path = require('path');
7
- const { spawnSync } = require('child_process');
8
-
9
- const PACKAGE_ROOT = path.resolve(__dirname, '..');
10
- const PACKAGE_JSON = path.join(PACKAGE_ROOT, 'package.json');
11
- const BIN_DIR = path.join(PACKAGE_ROOT, 'bin');
12
- const DOWNLOAD_BASE = process.env.GAIA_CLI_DOWNLOAD_BASE || 'https://assets.gaiaworkforce.com/gaia-cli/releases';
13
- const ALLOWED_DOWNLOAD_HOSTS = new Set([
14
- 'assets.gaiaworkforce.com',
15
- 'gaiafe.oss-cn-shanghai.aliyuncs.com',
16
- ]);
17
-
18
- function readPackageVersion() {
19
- const content = fs.readFileSync(PACKAGE_JSON, 'utf8');
20
- return JSON.parse(content).version;
21
- }
22
-
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
10
+ const PACKAGE_JSON = path.join(PACKAGE_ROOT, 'package.json');
11
+ const BIN_DIR = path.join(PACKAGE_ROOT, 'bin');
12
+ const DOWNLOAD_BASE = process.env.GAIA_CLI_DOWNLOAD_BASE || 'https://assets.gaiaworkforce.com/gaia-cli/releases';
13
+ const ALLOWED_DOWNLOAD_HOSTS = new Set([
14
+ 'assets.gaiaworkforce.com',
15
+ 'gaiafe.oss-cn-shanghai.aliyuncs.com',
16
+ ]);
17
+
18
+ function readPackageVersion() {
19
+ const content = fs.readFileSync(PACKAGE_JSON, 'utf8');
20
+ return JSON.parse(content).version;
21
+ }
22
+
23
23
  function normalizeVersion(version) {
24
24
  return version.startsWith('v') ? version.slice(1) : version;
25
25
  }
@@ -29,157 +29,157 @@ function assertInstallableVersion(version) {
29
29
  throw new Error('Cannot install from a source checkout with placeholder version 0.0.0; use `go run .\\cmd\\gaia <command>` for local development');
30
30
  }
31
31
  }
32
-
33
- function platformName(platform = process.platform) {
34
- if (platform === 'darwin') return 'darwin';
35
- if (platform === 'linux') return 'linux';
36
- if (platform === 'win32') return 'windows';
37
- throw new Error(`Unsupported platform: ${platform}`);
38
- }
39
-
40
- function archName(arch = process.arch) {
41
- if (arch === 'x64') return 'amd64';
42
- if (arch === 'arm64') return 'arm64';
43
- throw new Error(`Unsupported architecture: ${arch}`);
44
- }
45
-
46
- function binaryName(platform = process.platform) {
47
- return platform === 'win32' ? 'gaia-cli.exe' : 'gaia-cli';
48
- }
49
-
50
- function archiveName(version, platform = process.platform, arch = process.arch) {
51
- const cleanVersion = normalizeVersion(version);
52
- return `gaia-cli-${cleanVersion}-${platformName(platform)}-${archName(arch)}.tar.gz`;
53
- }
54
-
55
- function releaseTag(version) {
56
- const cleanVersion = normalizeVersion(version);
57
- return `v${cleanVersion}`;
58
- }
59
-
60
- function downloadUrl(version, platform = process.platform, arch = process.arch) {
61
- return `${DOWNLOAD_BASE}/${releaseTag(version)}/${archiveName(version, platform, arch)}`;
62
- }
63
-
64
- function assertAllowedUrl(rawUrl) {
65
- const parsed = new URL(rawUrl);
66
- if (parsed.protocol !== 'https:') {
67
- throw new Error(`Refusing non-HTTPS download URL: ${rawUrl}`);
68
- }
69
- if (!ALLOWED_DOWNLOAD_HOSTS.has(parsed.hostname)) {
70
- throw new Error(`Refusing download host: ${parsed.hostname}`);
71
- }
72
- }
73
-
74
- function ensureDir(dir) {
75
- fs.mkdirSync(dir, { recursive: true });
76
- }
77
-
78
- function run(command, args, options = {}) {
79
- const result = spawnSync(command, args, {
80
- stdio: options.stdio || 'inherit',
81
- cwd: options.cwd || PACKAGE_ROOT,
82
- encoding: 'utf8',
83
- });
84
- if (result.error) {
85
- throw result.error;
86
- }
87
- if (result.status !== 0) {
88
- throw new Error(`${command} ${args.join(' ')} exited with code ${result.status}`);
89
- }
90
- return result;
91
- }
92
-
93
- function downloadWithRedirects(url, destination) {
94
- assertAllowedUrl(url);
95
- const tmpPath = `${destination}.tmp`;
96
- fs.rmSync(tmpPath, { force: true });
97
-
98
- run('curl', [
99
- '--fail',
100
- '--location',
101
- '--show-error',
102
- '--silent',
103
- '--output',
104
- tmpPath,
105
- url,
106
- ]);
107
-
108
- fs.renameSync(tmpPath, destination);
109
- }
110
-
111
- function extractArchive(archivePath, targetDir) {
112
- fs.rmSync(targetDir, { recursive: true, force: true });
113
- ensureDir(targetDir);
114
- run('tar', ['-xzf', archivePath, '-C', targetDir]);
115
- }
116
-
117
- function findDownloadedBinary(targetDir, platform = process.platform) {
118
- const expectedName = binaryName(platform);
119
- const direct = path.join(targetDir, expectedName);
120
- if (fs.existsSync(direct)) return direct;
121
-
122
- const pending = [targetDir];
123
- while (pending.length > 0) {
124
- const currentDir = pending.pop();
125
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
126
- for (const entry of entries) {
127
- const entryPath = path.join(currentDir, entry.name);
128
- if (entry.isDirectory()) {
129
- pending.push(entryPath);
130
- } else if (entry.isFile() && entry.name === expectedName) {
131
- return entryPath;
132
- }
133
- }
134
- }
135
- throw new Error(`Downloaded archive does not contain ${expectedName}`);
136
- }
137
-
32
+
33
+ function platformName(platform = process.platform) {
34
+ if (platform === 'darwin') return 'darwin';
35
+ if (platform === 'linux') return 'linux';
36
+ if (platform === 'win32') return 'windows';
37
+ throw new Error(`Unsupported platform: ${platform}`);
38
+ }
39
+
40
+ function archName(arch = process.arch) {
41
+ if (arch === 'x64') return 'amd64';
42
+ if (arch === 'arm64') return 'arm64';
43
+ throw new Error(`Unsupported architecture: ${arch}`);
44
+ }
45
+
46
+ function binaryName(platform = process.platform) {
47
+ return platform === 'win32' ? 'gaia-cli.exe' : 'gaia-cli';
48
+ }
49
+
50
+ function archiveName(version, platform = process.platform, arch = process.arch) {
51
+ const cleanVersion = normalizeVersion(version);
52
+ return `gaia-cli-${cleanVersion}-${platformName(platform)}-${archName(arch)}.tar.gz`;
53
+ }
54
+
55
+ function releaseTag(version) {
56
+ const cleanVersion = normalizeVersion(version);
57
+ return `v${cleanVersion}`;
58
+ }
59
+
60
+ function downloadUrl(version, platform = process.platform, arch = process.arch) {
61
+ return `${DOWNLOAD_BASE}/${releaseTag(version)}/${archiveName(version, platform, arch)}`;
62
+ }
63
+
64
+ function assertAllowedUrl(rawUrl) {
65
+ const parsed = new URL(rawUrl);
66
+ if (parsed.protocol !== 'https:') {
67
+ throw new Error(`Refusing non-HTTPS download URL: ${rawUrl}`);
68
+ }
69
+ if (!ALLOWED_DOWNLOAD_HOSTS.has(parsed.hostname)) {
70
+ throw new Error(`Refusing download host: ${parsed.hostname}`);
71
+ }
72
+ }
73
+
74
+ function ensureDir(dir) {
75
+ fs.mkdirSync(dir, { recursive: true });
76
+ }
77
+
78
+ function run(command, args, options = {}) {
79
+ const result = spawnSync(command, args, {
80
+ stdio: options.stdio || 'inherit',
81
+ cwd: options.cwd || PACKAGE_ROOT,
82
+ encoding: 'utf8',
83
+ });
84
+ if (result.error) {
85
+ throw result.error;
86
+ }
87
+ if (result.status !== 0) {
88
+ throw new Error(`${command} ${args.join(' ')} exited with code ${result.status}`);
89
+ }
90
+ return result;
91
+ }
92
+
93
+ function downloadWithRedirects(url, destination) {
94
+ assertAllowedUrl(url);
95
+ const tmpPath = `${destination}.tmp`;
96
+ fs.rmSync(tmpPath, { force: true });
97
+
98
+ run('curl', [
99
+ '--fail',
100
+ '--location',
101
+ '--show-error',
102
+ '--silent',
103
+ '--output',
104
+ tmpPath,
105
+ url,
106
+ ]);
107
+
108
+ fs.renameSync(tmpPath, destination);
109
+ }
110
+
111
+ function extractArchive(archivePath, targetDir) {
112
+ fs.rmSync(targetDir, { recursive: true, force: true });
113
+ ensureDir(targetDir);
114
+ run('tar', ['-xzf', archivePath, '-C', targetDir]);
115
+ }
116
+
117
+ function findDownloadedBinary(targetDir, platform = process.platform) {
118
+ const expectedName = binaryName(platform);
119
+ const direct = path.join(targetDir, expectedName);
120
+ if (fs.existsSync(direct)) return direct;
121
+
122
+ const pending = [targetDir];
123
+ while (pending.length > 0) {
124
+ const currentDir = pending.pop();
125
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
126
+ for (const entry of entries) {
127
+ const entryPath = path.join(currentDir, entry.name);
128
+ if (entry.isDirectory()) {
129
+ pending.push(entryPath);
130
+ } else if (entry.isFile() && entry.name === expectedName) {
131
+ return entryPath;
132
+ }
133
+ }
134
+ }
135
+ throw new Error(`Downloaded archive does not contain ${expectedName}`);
136
+ }
137
+
138
138
  function install(options = {}) {
139
139
  const version = options.version || readPackageVersion();
140
140
  assertInstallableVersion(version);
141
- const url = options.url || downloadUrl(version);
142
- const archivePath = path.join(os.tmpdir(), archiveName(version));
143
- const unpackDir = path.join(os.tmpdir(), `gaia-cli-${process.pid}-${Date.now()}`);
144
- const finalBinary = path.join(BIN_DIR, binaryName());
145
-
146
- console.error(`[gaia-cli] Downloading ${url}`);
147
- ensureDir(BIN_DIR);
148
- downloadWithRedirects(url, archivePath);
149
- extractArchive(archivePath, unpackDir);
150
-
151
- const downloadedBinary = findDownloadedBinary(unpackDir);
152
- fs.copyFileSync(downloadedBinary, finalBinary);
153
- if (process.platform !== 'win32') {
154
- fs.chmodSync(finalBinary, 0o755);
155
- }
156
-
157
- fs.rmSync(archivePath, { force: true });
158
- fs.rmSync(unpackDir, { recursive: true, force: true });
159
- console.error(`[gaia-cli] Installed ${finalBinary}`);
160
- }
161
-
162
- if (require.main === module) {
163
- try {
164
- install();
165
- } catch (error) {
166
- console.error(`[gaia-cli] Failed to install binary: ${error.message}`);
167
- console.error('[gaia-cli] Please check network access to OSS assets or contact the administrator.');
168
- process.exit(1);
169
- }
170
- }
171
-
172
- module.exports = {
173
- ALLOWED_DOWNLOAD_HOSTS,
174
- archName,
141
+ const url = options.url || downloadUrl(version);
142
+ const archivePath = path.join(os.tmpdir(), archiveName(version));
143
+ const unpackDir = path.join(os.tmpdir(), `gaia-cli-${process.pid}-${Date.now()}`);
144
+ const finalBinary = path.join(BIN_DIR, binaryName());
145
+
146
+ console.error(`[gaia-cli] Downloading ${url}`);
147
+ ensureDir(BIN_DIR);
148
+ downloadWithRedirects(url, archivePath);
149
+ extractArchive(archivePath, unpackDir);
150
+
151
+ const downloadedBinary = findDownloadedBinary(unpackDir);
152
+ fs.copyFileSync(downloadedBinary, finalBinary);
153
+ if (process.platform !== 'win32') {
154
+ fs.chmodSync(finalBinary, 0o755);
155
+ }
156
+
157
+ fs.rmSync(archivePath, { force: true });
158
+ fs.rmSync(unpackDir, { recursive: true, force: true });
159
+ console.error(`[gaia-cli] Installed ${finalBinary}`);
160
+ }
161
+
162
+ if (require.main === module) {
163
+ try {
164
+ install();
165
+ } catch (error) {
166
+ console.error(`[gaia-cli] Failed to install binary: ${error.message}`);
167
+ console.error('[gaia-cli] Please check network access to OSS assets or contact the administrator.');
168
+ process.exit(1);
169
+ }
170
+ }
171
+
172
+ module.exports = {
173
+ ALLOWED_DOWNLOAD_HOSTS,
174
+ archName,
175
175
  archiveName,
176
176
  assertAllowedUrl,
177
177
  assertInstallableVersion,
178
- binaryName,
179
- downloadUrl,
180
- findDownloadedBinary,
181
- install,
182
- normalizeVersion,
183
- platformName,
184
- releaseTag,
185
- };
178
+ binaryName,
179
+ downloadUrl,
180
+ findDownloadedBinary,
181
+ install,
182
+ normalizeVersion,
183
+ platformName,
184
+ releaseTag,
185
+ };
package/scripts/run.js CHANGED
@@ -1,20 +1,20 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const { spawnSync } = require('child_process');
7
-
8
- const { binaryName } = require('./install');
9
-
10
- const PACKAGE_ROOT = path.resolve(__dirname, '..');
11
- const BINARY_PATH = path.join(PACKAGE_ROOT, 'bin', binaryName());
12
-
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { spawnSync } = require('child_process');
7
+
8
+ const { binaryName } = require('./install');
9
+
10
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
11
+ const BINARY_PATH = path.join(PACKAGE_ROOT, 'bin', binaryName());
12
+
13
13
  function runInstallWizard(args) {
14
- const result = spawnSync(process.execPath, [path.join(__dirname, 'install-wizard.js'), ...args], {
15
- stdio: 'inherit',
16
- });
17
- process.exit(typeof result.status === 'number' ? result.status : 1);
14
+ const result = spawnSync(process.execPath, [path.join(__dirname, 'install-wizard.js'), ...args], {
15
+ stdio: 'inherit',
16
+ });
17
+ process.exit(typeof result.status === 'number' ? result.status : 1);
18
18
  }
19
19
 
20
20
  function runUpdater(args) {
@@ -30,30 +30,30 @@ function runUninstaller(args) {
30
30
  });
31
31
  process.exit(typeof result.status === 'number' ? result.status : 1);
32
32
  }
33
-
34
- function ensureBinary() {
35
- if (fs.existsSync(BINARY_PATH)) return;
36
- const result = spawnSync(process.execPath, [path.join(__dirname, 'install.js')], {
37
- stdio: 'inherit',
38
- });
39
- if (result.status !== 0) {
40
- process.exit(typeof result.status === 'number' ? result.status : 1);
41
- }
42
- }
43
-
44
- function runBinary(args) {
45
- ensureBinary();
46
- const result = spawnSync(BINARY_PATH, args, {
47
- stdio: 'inherit',
48
- });
49
- if (result.error) {
50
- console.error(`[gaia-cli] Failed to run ${BINARY_PATH}: ${result.error.message}`);
51
- process.exit(1);
52
- }
53
- process.exit(typeof result.status === 'number' ? result.status : 1);
54
- }
55
-
56
- const args = process.argv.slice(2);
33
+
34
+ function ensureBinary() {
35
+ if (fs.existsSync(BINARY_PATH)) return;
36
+ const result = spawnSync(process.execPath, [path.join(__dirname, 'install.js')], {
37
+ stdio: 'inherit',
38
+ });
39
+ if (result.status !== 0) {
40
+ process.exit(typeof result.status === 'number' ? result.status : 1);
41
+ }
42
+ }
43
+
44
+ function runBinary(args) {
45
+ ensureBinary();
46
+ const result = spawnSync(BINARY_PATH, args, {
47
+ stdio: 'inherit',
48
+ });
49
+ if (result.error) {
50
+ console.error(`[gaia-cli] Failed to run ${BINARY_PATH}: ${result.error.message}`);
51
+ process.exit(1);
52
+ }
53
+ process.exit(typeof result.status === 'number' ? result.status : 1);
54
+ }
55
+
56
+ const args = process.argv.slice(2);
57
57
  if (args[0] === 'install') {
58
58
  runInstallWizard(args.slice(1));
59
59
  }