@delta-infra/cli 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -10
- package/package.json +11 -1
- package/scripts/install.js +298 -52
- package/scripts/run.js +53 -14
- package/skills/delta-sandbox/SKILL.md +1 -0
- package/skills/delta-sandbox/references/lifecycle.md +1 -1
- package/skills/delta-shared/SKILL.md +43 -18
package/README.md
CHANGED
|
@@ -10,28 +10,71 @@ Delta Infra 的命令行工具。
|
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
# macOS (Apple Silicon)
|
|
13
|
-
curl -L https://github.com/yzailab/delta-infra-cli/releases/latest/download/delta-cli-darwin-arm64.tar.gz
|
|
13
|
+
curl -L -o delta-cli.tar.gz https://github.com/yzailab/delta-infra-cli/releases/latest/download/delta-cli-darwin-arm64.tar.gz
|
|
14
|
+
tar -xzf delta-cli.tar.gz
|
|
14
15
|
sudo mv delta-cli-darwin-arm64 /usr/local/bin/delta-cli
|
|
16
|
+
rm delta-cli.tar.gz
|
|
15
17
|
|
|
16
18
|
# Linux (AMD64)
|
|
17
|
-
curl -L https://github.com/yzailab/delta-infra-cli/releases/latest/download/delta-cli-linux-amd64.tar.gz
|
|
19
|
+
curl -L -o delta-cli.tar.gz https://github.com/yzailab/delta-infra-cli/releases/latest/download/delta-cli-linux-amd64.tar.gz
|
|
20
|
+
tar -xzf delta-cli.tar.gz
|
|
18
21
|
chmod +x delta-cli-linux-amd64
|
|
19
22
|
sudo mv delta-cli-linux-amd64 /usr/local/bin/delta-cli
|
|
23
|
+
rm delta-cli.tar.gz
|
|
24
|
+
|
|
25
|
+
# Windows (AMD64,PowerShell)
|
|
26
|
+
Invoke-WebRequest -Uri https://github.com/yzailab/delta-infra-cli/releases/latest/download/delta-cli-windows-amd64.zip -OutFile delta-cli.zip
|
|
27
|
+
Expand-Archive -Path delta-cli.zip -DestinationPath . -Force
|
|
28
|
+
Move-Item -Path delta-cli-windows-amd64.exe -Destination delta-cli.exe
|
|
29
|
+
Remove-Item -Path delta-cli.zip
|
|
30
|
+
# 将 delta-cli.exe 放到 PATH 中的目录
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 方式二:一键安装脚本(国内用户推荐)
|
|
34
|
+
|
|
35
|
+
安装脚本会自动尝试国内 npm 镜像(npmmirror),失败后再回退到 npm 官方源:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Linux / macOS
|
|
39
|
+
curl -L https://raw.githubusercontent.com/yzailab/delta-infra-cli/main/install.sh | bash
|
|
20
40
|
|
|
21
|
-
# Windows (
|
|
22
|
-
|
|
23
|
-
# 解压后将 delta-cli-windows-amd64.exe 重命名为 delta-cli.exe,并放到 PATH 目录下
|
|
41
|
+
# Windows (PowerShell)
|
|
42
|
+
Invoke-RestMethod -Uri https://raw.githubusercontent.com/yzailab/delta-infra-cli/main/install.ps1 | Invoke-Expression
|
|
24
43
|
```
|
|
25
44
|
|
|
26
|
-
###
|
|
45
|
+
### 方式三:npm
|
|
27
46
|
|
|
28
47
|
```bash
|
|
29
48
|
npm install -g @delta-infra/cli
|
|
30
49
|
```
|
|
31
50
|
|
|
32
|
-
|
|
51
|
+
`postinstall` 会按以下顺序尝试下载当前平台的二进制:
|
|
33
52
|
|
|
34
|
-
|
|
53
|
+
1. `DELTA_CLI_MIRROR` 环境变量(如果设置)
|
|
54
|
+
2. 国内加速镜像 `https://gh-proxy.com/https://github.com/...`
|
|
55
|
+
3. GitHub 源站 `https://github.com/yzailab/delta-infra-cli/releases/download/...`
|
|
56
|
+
|
|
57
|
+
如果安装时下载失败,可以:
|
|
58
|
+
|
|
59
|
+
- 直接运行一次 `delta-cli`,它会自动重新尝试下载。
|
|
60
|
+
- 或者在安装前设置本地压缩包路径:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
DELTA_CLI_ARCHIVE=/path/to/delta-cli-linux-amd64.tar.gz npm install -g @delta-infra/cli
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
常用环境变量:
|
|
67
|
+
|
|
68
|
+
| 变量 | 说明 |
|
|
69
|
+
|------|------|
|
|
70
|
+
| `DELTA_CLI_MIRROR` | 下载镜像,例如 `https://gh-proxy.com/https://github.com`。只接受 HTTPS 且需在白名单内。 |
|
|
71
|
+
| `DELTA_CLI_MIRROR_ALLOWLIST` | 额外允许的主机列表,逗号分隔。 |
|
|
72
|
+
| `DELTA_CLI_ARCHIVE` | 指向预先下载好的 `.tar.gz` / `.zip` 本地路径,跳过网络下载。 |
|
|
73
|
+
| `DELTA_CLI_SKIP_POSTINSTALL` | 设置为 `1` 跳过安装时的二进制下载,之后由 `delta-cli` 运行时兜底。 |
|
|
74
|
+
| `DELTA_CLI_FATAL_ON_ERROR` | 安装失败时退出码非 0(默认警告,用于调试)。 |
|
|
75
|
+
| `DELTA_CLI_DOWNLOAD_TIMEOUT` | 单个下载源最大等待时间(毫秒,默认 120000)。 |
|
|
76
|
+
|
|
77
|
+
### 方式四:Go Install
|
|
35
78
|
|
|
36
79
|
```bash
|
|
37
80
|
go install github.com/delta-infra/delta-infra-cli/cmd/delta-cli@latest
|
|
@@ -45,12 +88,25 @@ go install github.com/delta-infra/delta-infra-cli/cmd/delta-cli@latest
|
|
|
45
88
|
delta-cli config init
|
|
46
89
|
```
|
|
47
90
|
|
|
48
|
-
|
|
91
|
+
执行时会交互式提示输入服务端地址(默认:`http://172.17.152.6:8000/api/v1`)以及 API Key / Bearer Token(可选,也可后续用 `auth login` 配置)。
|
|
92
|
+
|
|
93
|
+
非交互式环境(如 CI)会直接使用默认值,也可以通过 flag 或环境变量一次性指定:
|
|
49
94
|
|
|
50
95
|
```bash
|
|
51
|
-
|
|
96
|
+
# 通过 flag
|
|
97
|
+
delta-cli config init \
|
|
98
|
+
--base-url http://your-server/api/v1 \
|
|
99
|
+
--api-key your-api-key
|
|
100
|
+
|
|
101
|
+
# 通过环境变量
|
|
102
|
+
export DELTA_INFRA_BASE_URL=http://your-server/api/v1
|
|
103
|
+
export DELTA_SANDBOX_API_KEY=your-api-key
|
|
104
|
+
export DELTA_SANDBOX_TOKEN=your-token
|
|
105
|
+
delta-cli config init
|
|
52
106
|
```
|
|
53
107
|
|
|
108
|
+
配置文件保存在 `~/.delta-infra/config.json`(权限 0600)。
|
|
109
|
+
|
|
54
110
|
### 2. 认证
|
|
55
111
|
|
|
56
112
|
支持 API Key 或 Bearer Token:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delta-infra/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Delta Sandbox CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"delta-cli": "scripts/run.js"
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"postinstall": "node scripts/install.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
+
"checksums.txt",
|
|
12
13
|
"scripts/install.js",
|
|
13
14
|
"scripts/run.js",
|
|
14
15
|
"skills/delta-sandbox/SKILL.md",
|
|
@@ -16,6 +17,15 @@
|
|
|
16
17
|
"skills/delta-shared/SKILL.md",
|
|
17
18
|
"skills/delta-shared/references/"
|
|
18
19
|
],
|
|
20
|
+
"os": [
|
|
21
|
+
"darwin",
|
|
22
|
+
"linux",
|
|
23
|
+
"win32"
|
|
24
|
+
],
|
|
25
|
+
"cpu": [
|
|
26
|
+
"x64",
|
|
27
|
+
"arm64"
|
|
28
|
+
],
|
|
19
29
|
"keywords": [
|
|
20
30
|
"delta",
|
|
21
31
|
"sandbox",
|
package/scripts/install.js
CHANGED
|
@@ -3,19 +3,23 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const https = require('https');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
|
-
const { execSync } = require('child_process');
|
|
6
|
+
const { execSync, spawn } = require('child_process');
|
|
7
7
|
|
|
8
8
|
const BINARY_NAME = 'delta-cli';
|
|
9
9
|
const VERSION = require('../package.json').version;
|
|
10
|
-
const RELEASE_BASE_URL = 'https://github.com/yzailab/delta-infra-cli/releases/download';
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
const DEFAULT_ALLOWED_HOSTS = [
|
|
12
|
+
'github.com',
|
|
13
|
+
'gh-proxy.com',
|
|
14
|
+
'objects.githubusercontent.com',
|
|
15
|
+
'release-assets.githubusercontent.com',
|
|
16
|
+
'github-releases.githubusercontent.com',
|
|
17
|
+
'raw.githubusercontent.com',
|
|
18
|
+
];
|
|
19
|
+
const DEFAULT_ALLOWED_HOST_PREFIXES = [
|
|
20
|
+
'github-production-release-asset-',
|
|
21
|
+
];
|
|
22
|
+
const MAX_REDIRECTS = 5;
|
|
19
23
|
|
|
20
24
|
const platformMap = {
|
|
21
25
|
darwin: 'darwin',
|
|
@@ -28,6 +32,20 @@ const archMap = {
|
|
|
28
32
|
arm64: 'arm64',
|
|
29
33
|
};
|
|
30
34
|
|
|
35
|
+
const RELEASE_SOURCE_BASE = 'https://github.com/yzailab/delta-infra-cli/releases/download';
|
|
36
|
+
const RELEASE_DOMESTIC_BASE = 'https://gh-proxy.com/https://github.com/yzailab/delta-infra-cli/releases/download';
|
|
37
|
+
|
|
38
|
+
function releaseAssetUrls(version, assetName) {
|
|
39
|
+
const urls = [];
|
|
40
|
+
const mirror = process.env.DELTA_CLI_MIRROR || '';
|
|
41
|
+
if (mirror) {
|
|
42
|
+
urls.push(`${mirror.replace(/\/$/, '')}/yzailab/delta-infra-cli/releases/download/v${version}/${assetName}`);
|
|
43
|
+
}
|
|
44
|
+
urls.push(`${RELEASE_DOMESTIC_BASE}/v${version}/${assetName}`);
|
|
45
|
+
urls.push(`${RELEASE_SOURCE_BASE}/v${version}/${assetName}`);
|
|
46
|
+
return urls;
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
function resolveUrl(base, location) {
|
|
32
50
|
if (!location) return base;
|
|
33
51
|
if (location.startsWith('http://') || location.startsWith('https://')) {
|
|
@@ -37,55 +55,239 @@ function resolveUrl(base, location) {
|
|
|
37
55
|
return new URL(location, baseUrl).href;
|
|
38
56
|
}
|
|
39
57
|
|
|
40
|
-
function
|
|
58
|
+
function loadChecksums() {
|
|
59
|
+
const checksumPath = path.join(__dirname, '..', 'checksums.txt');
|
|
60
|
+
if (!fs.existsSync(checksumPath)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const content = fs.readFileSync(checksumPath, 'utf8');
|
|
64
|
+
const map = new Map();
|
|
65
|
+
for (const line of content.split(/\r?\n/)) {
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
68
|
+
const idx = trimmed.indexOf(' ');
|
|
69
|
+
let hash, filename;
|
|
70
|
+
if (idx === -1) {
|
|
71
|
+
const parts = trimmed.split(/\s+/);
|
|
72
|
+
if (parts.length < 2) continue;
|
|
73
|
+
hash = parts[0];
|
|
74
|
+
filename = parts[1];
|
|
75
|
+
} else {
|
|
76
|
+
hash = trimmed.slice(0, idx);
|
|
77
|
+
filename = trimmed.slice(idx + 2);
|
|
78
|
+
}
|
|
79
|
+
map.set(filename, hash.toLowerCase());
|
|
80
|
+
}
|
|
81
|
+
return map;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function fileSha256(filePath) {
|
|
41
85
|
return new Promise((resolve, reject) => {
|
|
86
|
+
const hash = crypto.createHash('sha256');
|
|
87
|
+
const stream = fs.createReadStream(filePath);
|
|
88
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
89
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
90
|
+
stream.on('error', reject);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isAllowListedUrl(urlStr) {
|
|
95
|
+
let url;
|
|
96
|
+
try {
|
|
97
|
+
url = new URL(urlStr);
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (url.protocol !== 'https:') {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const host = url.hostname.toLowerCase();
|
|
105
|
+
const allowlist = new Set(DEFAULT_ALLOWED_HOSTS);
|
|
106
|
+
(process.env.DELTA_CLI_MIRROR_ALLOWLIST || '')
|
|
107
|
+
.split(',')
|
|
108
|
+
.map((h) => h.trim().toLowerCase())
|
|
109
|
+
.filter(Boolean)
|
|
110
|
+
.forEach((h) => allowlist.add(h));
|
|
111
|
+
for (const allowed of allowlist) {
|
|
112
|
+
if (host === allowed || host.endsWith('.' + allowed)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const prefix of DEFAULT_ALLOWED_HOST_PREFIXES) {
|
|
117
|
+
if (host.startsWith(prefix)) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getHttpProxy() {
|
|
125
|
+
return (
|
|
126
|
+
process.env.HTTPS_PROXY ||
|
|
127
|
+
process.env.https_proxy ||
|
|
128
|
+
process.env.HTTP_PROXY ||
|
|
129
|
+
process.env.http_proxy ||
|
|
130
|
+
''
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const DOWNLOAD_TIMEOUT_MS = parseInt(process.env.DELTA_CLI_DOWNLOAD_TIMEOUT, 10) || 120000;
|
|
135
|
+
|
|
136
|
+
function nodeDownload(url, dest, redirectCount = 0) {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
if (!isAllowListedUrl(url)) {
|
|
139
|
+
reject(new Error(`Download URL is not allow-listed: ${url}`));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (redirectCount > MAX_REDIRECTS) {
|
|
143
|
+
reject(new Error('Too many redirects while downloading'));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
42
147
|
const file = fs.createWriteStream(dest);
|
|
43
148
|
const hash = crypto.createHash('sha256');
|
|
149
|
+
let finished = false;
|
|
150
|
+
const maxTimer = setTimeout(() => {
|
|
151
|
+
cleanup(new Error(`Download did not complete within ${DOWNLOAD_TIMEOUT_MS}ms`));
|
|
152
|
+
}, DOWNLOAD_TIMEOUT_MS);
|
|
44
153
|
|
|
45
|
-
const cleanup = () => {
|
|
46
|
-
|
|
154
|
+
const cleanup = (err) => {
|
|
155
|
+
if (finished) return;
|
|
156
|
+
finished = true;
|
|
157
|
+
clearTimeout(maxTimer);
|
|
158
|
+
try {
|
|
159
|
+
file.destroy();
|
|
160
|
+
fs.unlinkSync(dest);
|
|
161
|
+
} catch {}
|
|
162
|
+
reject(err);
|
|
47
163
|
};
|
|
48
164
|
|
|
49
|
-
https
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
165
|
+
https
|
|
166
|
+
.get(url, { timeout: 60000 }, (response) => {
|
|
167
|
+
if (response.statusCode >= 301 && response.statusCode <= 308 && response.headers.location) {
|
|
168
|
+
const nextUrl = resolveUrl(url, response.headers.location);
|
|
169
|
+
if (!isAllowListedUrl(nextUrl)) {
|
|
170
|
+
cleanup(new Error(`Redirect URL is not allow-listed: ${nextUrl}`));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
file.destroy();
|
|
174
|
+
try {
|
|
175
|
+
fs.unlinkSync(dest);
|
|
176
|
+
} catch {}
|
|
177
|
+
nodeDownload(nextUrl, dest, redirectCount + 1).then(resolve).catch(reject);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (response.statusCode !== 200) {
|
|
181
|
+
cleanup(new Error(`Download failed: HTTP ${response.statusCode}`));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
response.pipe(file);
|
|
185
|
+
response.on('data', (chunk) => hash.update(chunk));
|
|
186
|
+
file.on('finish', () => {
|
|
187
|
+
file.close((err) => {
|
|
188
|
+
if (finished) return;
|
|
189
|
+
if (err) {
|
|
190
|
+
cleanup(err);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
finished = true;
|
|
194
|
+
clearTimeout(maxTimer);
|
|
195
|
+
resolve(hash.digest('hex'));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
file.on('error', cleanup);
|
|
199
|
+
})
|
|
200
|
+
.on('error', cleanup)
|
|
201
|
+
.on('timeout', () => {
|
|
202
|
+
cleanup(new Error('Download timed out'));
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function curlDownload(url, dest, proxy) {
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
const maxSeconds = Math.ceil(DOWNLOAD_TIMEOUT_MS / 1000);
|
|
210
|
+
const args = ['-L', '--fail', '--show-error', '--silent', '--max-time', String(maxSeconds), '-o', dest];
|
|
211
|
+
if (proxy) {
|
|
212
|
+
args.push('--proxy', proxy);
|
|
213
|
+
}
|
|
214
|
+
args.push(url);
|
|
215
|
+
const child = spawn('curl', args, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
216
|
+
child.on('error', (err) => {
|
|
217
|
+
reject(new Error(`curl not available or failed to start: ${err.message}`));
|
|
218
|
+
});
|
|
219
|
+
child.on('close', (code) => {
|
|
220
|
+
if (code !== 0) {
|
|
221
|
+
reject(new Error(`curl download failed (exit ${code})`));
|
|
60
222
|
return;
|
|
61
223
|
}
|
|
62
|
-
|
|
63
|
-
response.on('data', (chunk) => hash.update(chunk));
|
|
64
|
-
file.on('finish', () => {
|
|
65
|
-
file.close(() => resolve(hash.digest('hex')));
|
|
66
|
-
});
|
|
67
|
-
}).on('error', (err) => {
|
|
68
|
-
file.destroy();
|
|
69
|
-
cleanup();
|
|
70
|
-
reject(err);
|
|
224
|
+
fileSha256(dest).then(resolve).catch(reject);
|
|
71
225
|
});
|
|
72
226
|
});
|
|
73
227
|
}
|
|
74
228
|
|
|
229
|
+
async function download(url, dest) {
|
|
230
|
+
const proxy = getHttpProxy();
|
|
231
|
+
const preferCurl =
|
|
232
|
+
process.env.DELTA_CLI_USE_CURL === '1' || (!!proxy && process.env.DELTA_CLI_USE_CURL !== '0');
|
|
233
|
+
|
|
234
|
+
if (preferCurl) {
|
|
235
|
+
try {
|
|
236
|
+
return await curlDownload(url, dest, proxy);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
if (process.env.DELTA_CLI_USE_CURL === '1') {
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
241
|
+
console.warn(`[delta-cli] curl download failed (${err.message}), falling back to Node https.`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return nodeDownload(url, dest);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function verifyAsset(assetName, filePath, checksums) {
|
|
249
|
+
if (!checksums) {
|
|
250
|
+
console.warn('[delta-cli] checksums.txt not found; skipping checksum verification.');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const expected = checksums.get(assetName);
|
|
254
|
+
if (!expected) {
|
|
255
|
+
throw new Error(`No checksum found for ${assetName} in checksums.txt`);
|
|
256
|
+
}
|
|
257
|
+
const actual = crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
258
|
+
if (actual !== expected) {
|
|
259
|
+
throw new Error(`Checksum mismatch for ${assetName}: expected ${expected}, got ${actual}`);
|
|
260
|
+
}
|
|
261
|
+
console.log(`[delta-cli] Checksum OK for ${assetName}.`);
|
|
262
|
+
}
|
|
263
|
+
|
|
75
264
|
function extract(archivePath, destDir, platform) {
|
|
265
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
76
266
|
if (platform === 'windows') {
|
|
77
|
-
|
|
267
|
+
const escapedArchive = archivePath.replace(/'/g, "''");
|
|
268
|
+
const escapedDest = destDir.replace(/'/g, "''");
|
|
269
|
+
execSync(
|
|
270
|
+
`powershell.exe -NoProfile -Command "Expand-Archive -Path '${escapedArchive}' -DestinationPath '${escapedDest}' -Force"`,
|
|
271
|
+
{ stdio: 'inherit' }
|
|
272
|
+
);
|
|
78
273
|
} else {
|
|
79
274
|
execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, { stdio: 'inherit' });
|
|
80
275
|
}
|
|
81
276
|
}
|
|
82
277
|
|
|
83
|
-
async function
|
|
278
|
+
async function install(options = {}) {
|
|
279
|
+
const fatal = options.fatal === true;
|
|
280
|
+
const allowSkip = options.allowSkip !== false;
|
|
281
|
+
|
|
282
|
+
if (allowSkip && (process.env.CI === 'true' || process.env.CI === '1' || process.env.DELTA_CLI_SKIP_POSTINSTALL)) {
|
|
283
|
+
console.log('[delta-cli] postinstall skipped (CI or DELTA_CLI_SKIP_POSTINSTALL).');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
84
287
|
const platform = platformMap[process.platform];
|
|
85
288
|
const arch = archMap[process.arch];
|
|
86
289
|
if (!platform || !arch) {
|
|
87
|
-
|
|
88
|
-
process.exit(1);
|
|
290
|
+
throw new Error(`Unsupported platform: ${process.platform} ${process.arch}`);
|
|
89
291
|
}
|
|
90
292
|
|
|
91
293
|
const isWin = platform === 'windows';
|
|
@@ -98,25 +300,69 @@ async function main() {
|
|
|
98
300
|
|
|
99
301
|
fs.mkdirSync(binDir, { recursive: true });
|
|
100
302
|
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
303
|
+
const localArchive = process.env.DELTA_CLI_ARCHIVE;
|
|
304
|
+
if (localArchive) {
|
|
305
|
+
if (!fs.existsSync(localArchive)) {
|
|
306
|
+
throw new Error(`DELTA_CLI_ARCHIVE file not found: ${localArchive}`);
|
|
307
|
+
}
|
|
308
|
+
fs.copyFileSync(localArchive, archivePath);
|
|
309
|
+
} else {
|
|
310
|
+
const urls = releaseAssetUrls(VERSION, assetName);
|
|
311
|
+
let lastErr;
|
|
312
|
+
let checksum;
|
|
313
|
+
for (const url of urls) {
|
|
314
|
+
console.log(`[delta-cli] Downloading ${assetName} from ${new URL(url).hostname}...`);
|
|
315
|
+
try {
|
|
316
|
+
checksum = await download(url, archivePath);
|
|
317
|
+
break;
|
|
318
|
+
} catch (err) {
|
|
319
|
+
console.warn(`[delta-cli] Download failed from ${url}: ${err.message}`);
|
|
320
|
+
lastErr = err;
|
|
321
|
+
}
|
|
112
322
|
}
|
|
323
|
+
if (checksum === undefined) {
|
|
324
|
+
throw lastErr || new Error('All download sources failed');
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const checksums = loadChecksums();
|
|
329
|
+
verifyAsset(assetName, archivePath, checksums);
|
|
330
|
+
|
|
331
|
+
console.log(`[delta-cli] Extracting to ${binDir}...`);
|
|
332
|
+
extract(archivePath, binDir, platform);
|
|
333
|
+
fs.unlinkSync(archivePath);
|
|
334
|
+
|
|
335
|
+
const extractedBinary = path.join(binDir, `${BINARY_NAME}-${platform}-${arch}${binaryExt}`);
|
|
336
|
+
if (fs.existsSync(extractedBinary) && extractedBinary !== binaryPath) {
|
|
337
|
+
fs.renameSync(extractedBinary, binaryPath);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!fs.existsSync(binaryPath)) {
|
|
341
|
+
throw new Error(`Binary not found after extraction: ${binaryPath}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (platform !== 'windows') {
|
|
113
345
|
fs.chmodSync(binaryPath, 0o755);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
console.log(`[delta-cli] Installed at ${binaryPath}.`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function handleError(err) {
|
|
352
|
+
const fatal = process.env.DELTA_CLI_FATAL_ON_ERROR === '1';
|
|
353
|
+
const message = err && err.message ? err.message : String(err);
|
|
354
|
+
if (fatal) {
|
|
355
|
+
console.error('[delta-cli] install failed:', message);
|
|
118
356
|
process.exit(1);
|
|
119
357
|
}
|
|
358
|
+
console.error('[delta-cli] postinstall warning:', message);
|
|
359
|
+
console.error('The wrapper is installed, but the native binary may be missing.');
|
|
360
|
+
console.error('Run "delta-cli" once to retry, or set DELTA_CLI_ARCHIVE to a local archive path.');
|
|
361
|
+
process.exit(0);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (require.main === module) {
|
|
365
|
+
install({ allowSkip: true }).catch(handleError);
|
|
120
366
|
}
|
|
121
367
|
|
|
122
|
-
|
|
368
|
+
module.exports = { install };
|
package/scripts/run.js
CHANGED
|
@@ -1,21 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
2
3
|
const path = require('path');
|
|
3
4
|
const { spawn } = require('child_process');
|
|
5
|
+
const { install } = require('./install');
|
|
4
6
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const binary = path.join(
|
|
8
|
+
__dirname,
|
|
9
|
+
'..',
|
|
10
|
+
'bin',
|
|
11
|
+
process.platform === 'win32' ? 'delta-cli.exe' : 'delta-cli'
|
|
12
|
+
);
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
14
|
+
async function ensureBinary() {
|
|
15
|
+
if (fs.existsSync(binary)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.error('[delta-cli] Binary not found, attempting to install...');
|
|
19
|
+
await install({ allowSkip: false, fatal: true });
|
|
20
|
+
if (!fs.existsSync(binary)) {
|
|
21
|
+
throw new Error('Binary is still missing after install attempt.');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
13
24
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
})
|
|
25
|
+
async function main() {
|
|
26
|
+
try {
|
|
27
|
+
await ensureBinary();
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error('[delta-cli] Could not install binary:', err.message);
|
|
30
|
+
console.error('Set DELTA_CLI_ARCHIVE to a local archive path, or reinstall the package.');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
18
33
|
|
|
19
|
-
child
|
|
20
|
-
|
|
21
|
-
|
|
34
|
+
const child = spawn(binary, process.argv.slice(2), {
|
|
35
|
+
stdio: 'inherit',
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
child.on('error', (err) => {
|
|
40
|
+
console.error('[delta-cli] Failed to start:', err.message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on('close', (code, signal) => {
|
|
45
|
+
if (code !== null && code !== undefined) {
|
|
46
|
+
process.exit(code);
|
|
47
|
+
}
|
|
48
|
+
if (signal) {
|
|
49
|
+
try {
|
|
50
|
+
process.kill(process.pid, signal);
|
|
51
|
+
} catch {
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
main();
|
|
@@ -54,6 +54,7 @@ metadata:
|
|
|
54
54
|
|
|
55
55
|
1. **查看可用镜像**:`delta-cli sandbox images` — 查询服务端支持的镜像列表(含标签、最低资源要求、支持 provider),根据用户需求匹配镜像
|
|
56
56
|
2. **创建**:`delta-cli sandbox create --image <img> --cpu 4 --memory 16Gi --gpu 1 --gpu-mem 8000`(创建后 sandbox 立即可用,无需额外连接)。**同一次任务若已有 `sandbox_id`,禁止再次 create,必须优先复用。**
|
|
57
|
+
- 这是 `sandbox create` 支持的完整资源参数集合,不存在其它“更正确”的资源 flag,不要 invented 不存在的参数。
|
|
57
58
|
3. **写入代码**:`delta-cli sandbox write <id> --path /workspace/train.py --data "..."`
|
|
58
59
|
4. **运行**:`delta-cli sandbox run <id> --command "python /workspace/train.py" --timeout 3600`
|
|
59
60
|
5. **读取结果**:`delta-cli sandbox read <id> --path /workspace/result.json`
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
1. **查看可用镜像** `delta-cli sandbox images` — 查询服务端支持的镜像列表,根据用户的 GPU/CPU 需求和标签匹配镜像
|
|
8
8
|
2. **列出现有 sandbox(可选)** `delta-cli sandbox list` — 查看当前用户已创建的活跃 sandbox,避免重复创建
|
|
9
|
-
3. **创建** `delta-cli sandbox create --image <image> --cpu 4 --memory 16Gi --gpu 1 --gpu-mem 8000
|
|
9
|
+
3. **创建** `delta-cli sandbox create --image <image> --cpu 4 --memory 16Gi --gpu 1 --gpu-mem 8000`(创建后立即可用,无需连接;这些是 `create` 全部资源参数,不要 invented 其它 flag)
|
|
10
10
|
4. **写入代码/数据** `delta-cli sandbox write <id> --path /workspace/train.py --data "..."`
|
|
11
11
|
5. **运行命令** `delta-cli sandbox run <id> --command "python /workspace/train.py"`
|
|
12
12
|
6. **读取结果** `delta-cli sandbox read <id> --path /workspace/result.json`
|
|
@@ -8,25 +8,39 @@ description: "delta-cli 全局共享规则。使用于首次配置、认证登
|
|
|
8
8
|
|
|
9
9
|
本技能指导你如何通过 delta-cli 操作 Delta Sandbox 及相关资源,以及所有命令域通用的注意事项。
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## 获取服务端地址
|
|
12
12
|
|
|
13
|
-
所有操作的前提是知道 delta-sandbox-server
|
|
13
|
+
所有操作的前提是知道 delta-sandbox-server 的地址。按以下优先级获取:
|
|
14
14
|
|
|
15
|
-
1.
|
|
16
|
-
2.
|
|
17
|
-
3.
|
|
15
|
+
1. 环境变量 `DELTA_INFRA_BASE_URL`(优先级最高)
|
|
16
|
+
2. `~/.delta-infra/config.json` 中的 `base_url`
|
|
17
|
+
3. 以上都为空时,**向用户询问真实地址**,再运行 `delta-cli config set base_url <url>` 保存
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
**不要猜测或虚构地址。** 已有配置文件时直接使用,只有配置文件不存在或操作报 `network` 错误时才需要重新确认。
|
|
20
20
|
|
|
21
21
|
## 配置初始化
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
如果 `~/.delta-infra/config.json` 已存在且包含有效的 `base_url` 与认证信息,则**无需执行** `config init`。
|
|
24
|
+
|
|
25
|
+
仅在以下情况才运行初始化:
|
|
26
|
+
|
|
27
|
+
- 配置文件不存在
|
|
28
|
+
- 配置文件为空或解析失败
|
|
29
|
+
- 用户明确要求重新初始化
|
|
30
|
+
|
|
31
|
+
首次使用或有上述情况时运行:
|
|
24
32
|
|
|
25
33
|
```bash
|
|
26
34
|
delta-cli config init
|
|
27
35
|
```
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
在交互式终端上,它会提示输入服务端地址(直接回车使用默认值)以及可选的 API Key / Bearer Token;AI 工具或非交互场景应使用 flag/环境变量,避免在 stdin 不可用时卡住:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
delta-cli config init --base-url <url> --api-key <key> --token <token>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
配置还可以通过环境变量 `DELTA_INFRA_BASE_URL`、`DELTA_SANDBOX_API_KEY`、`DELTA_SANDBOX_TOKEN` 提供。
|
|
30
44
|
|
|
31
45
|
## 配置管理
|
|
32
46
|
|
|
@@ -51,7 +65,24 @@ delta-cli config remove token
|
|
|
51
65
|
|
|
52
66
|
## 认证
|
|
53
67
|
|
|
54
|
-
delta-cli
|
|
68
|
+
delta-cli **不再内嵌默认凭证**,使用前必须存在有效认证。
|
|
69
|
+
|
|
70
|
+
### 优先检查,避免重复登录
|
|
71
|
+
|
|
72
|
+
执行任何需要认证的操作前,先查状态:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 1. 确认服务端地址
|
|
76
|
+
delta-cli config show
|
|
77
|
+
|
|
78
|
+
# 2. 确认认证是否已生效
|
|
79
|
+
delta-cli auth status
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- 如果 `auth status` 显示 `method: token` 或 `method: api_key`,说明配置文件已存在有效凭证,**直接执行目标操作**,不要重复 `auth login`。
|
|
83
|
+
- 只有当 `auth status` 显示 `method: none`、token 已过期,或操作返回 `error.type: auth` / `permission` 时,才需要重新登录。
|
|
84
|
+
|
|
85
|
+
### 认证方式
|
|
55
86
|
|
|
56
87
|
支持两种认证方式,优先级从高到低:
|
|
57
88
|
|
|
@@ -62,21 +93,15 @@ delta-cli **不再内嵌默认凭证**,使用前必须配置认证。
|
|
|
62
93
|
|
|
63
94
|
### 默认认证说明
|
|
64
95
|
|
|
65
|
-
- binary 中没有默认 token
|
|
96
|
+
- binary 中没有默认 token。
|
|
97
|
+
- 第一次使用或认证失效时,执行 `delta-cli auth login` 或设置对应环境变量。
|
|
66
98
|
- 如需更换账号,重新执行 `delta-cli auth login --token <token>` 或 `delta-cli auth login --api-key <key>`。
|
|
67
|
-
- 未配置时 `auth status` 会显示 `method: none`。
|
|
68
|
-
|
|
69
|
-
### 查看认证状态
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
delta-cli auth status
|
|
73
|
-
```
|
|
74
99
|
|
|
75
100
|
### 权限不足处理
|
|
76
101
|
|
|
77
102
|
遇到认证/权限错误时(`error.type: auth` 或 `permission`):
|
|
78
103
|
|
|
79
|
-
1.
|
|
104
|
+
1. 执行 `delta-cli auth status` 确认当前认证状态
|
|
80
105
|
2. 如需更换认证,执行 `delta-cli auth login --token <token>` 或 `delta-cli auth login --api-key <key>`
|
|
81
106
|
3. 如已认证但仍报错,检查 base_url 是否正确
|
|
82
107
|
|