@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 +29 -29
- package/package.json +1 -1
- package/scripts/install-wizard.js +176 -169
- package/scripts/install.js +170 -170
- package/scripts/run.js +40 -40
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,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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
console.error(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
console.error(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
};
|
package/scripts/install.js
CHANGED
|
@@ -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
|
}
|