@gaiaworks/gaia-cli 0.0.5 → 0.0.6

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