@seacloudai/seacloud-cli 0.0.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VtrixAI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ <div align="center">
2
+ <p>
3
+ <img src="./assets/seacloud-cli-image-en.png" alt="SeaCloud CLI banner">
4
+ </p>
5
+ <h1>SeaCloud CLI</h1>
6
+ <h3>The official CLI for the SeaCloud AI Platform</h3>
7
+ <p>
8
+ Built for AI agents. Authenticate, browse models, submit multimodal tasks,
9
+ track task status, and manage SkillHub skills from any agent or terminal.
10
+ </p>
11
+ <p>
12
+ <a href="https://www.npmjs.com/package/@seacloudai/seacloud-cli">
13
+ <img src="https://img.shields.io/npm/v/@seacloudai/seacloud-cli" alt="npm version">
14
+ </a>
15
+ <img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License">
16
+ <img src="https://img.shields.io/badge/node-%3E%3D18-339933" alt="Node.js >= 18">
17
+ <img src="https://img.shields.io/badge/go-%3E%3D1.26-00ADD8" alt="Go >= 1.26">
18
+ </p>
19
+ <p>
20
+ <a href="./README.zh.md">中文文档</a>
21
+ ·
22
+ <a href="https://vtrix.ai/">Official Website</a>
23
+ </p>
24
+ </div>
25
+
26
+ ## Features
27
+
28
+ - **Authentication**: Sign in with the browser-based device flow and store credentials locally.
29
+ - **Model discovery**: List available models and inspect full parameter specs in human-readable or JSON form.
30
+ - **Task execution**: Submit multimodal generation tasks from the CLI with parameter validation and structured output options.
31
+ - **Task tracking**: Poll task status and print result URLs or full JSON responses.
32
+ - **SkillHub integration**: Search, install, and configure agent skills from SeaCloud SkillHub.
33
+ - **Agent-friendly UX**: Supports `--dry-run`, JSON output, stable command shapes, and copy-pasteable examples.
34
+
35
+ ## Install
36
+
37
+ ### Install with npm
38
+
39
+ ```bash
40
+ npm install -g @seacloudai/seacloud-cli
41
+ ```
42
+
43
+ > Requires Node.js 18+
44
+
45
+ ### Install from source
46
+
47
+ Default install:
48
+
49
+ ```bash
50
+ git clone https://github.com/SeaCloudAI/seacloud-cli.git
51
+ cd seacloud-cli
52
+ make install
53
+ ```
54
+
55
+ > Requires Go 1.26+
56
+ > The installed binary uses the default service endpoints for the public CLI. You can override them with `SEACLOUD_BASE_URL`, `SEACLOUD_MODELS_URL`, `SEACLOUD_GENERATION_URL`, and `SEACLOUD_SKILLHUB_URL`.
57
+
58
+ If `/usr/local/bin` requires elevated permissions:
59
+
60
+ ```bash
61
+ sudo make install
62
+ ```
63
+
64
+ If you prefer a user-local install without `sudo`:
65
+
66
+ ```bash
67
+ make install PREFIX=$HOME/.local
68
+ export PATH="$HOME/.local/bin:$PATH"
69
+ ```
70
+
71
+ ### Download binaries
72
+
73
+ Prebuilt binaries are published on the [Releases](https://github.com/SeaCloudAI/seacloud-cli/releases) page for:
74
+
75
+ - macOS `amd64`
76
+ - macOS `arm64`
77
+ - Linux `amd64`
78
+ - Linux `arm64`
79
+ - Windows `amd64`
80
+
81
+ ## Quick Start
82
+
83
+ ### Authenticate
84
+
85
+ ```bash
86
+ seacloud auth login
87
+ seacloud auth status
88
+ ```
89
+
90
+ ### Browse models
91
+
92
+ ```bash
93
+ seacloud models list
94
+ seacloud models list --type video
95
+ seacloud models spec kirin_v2_6_i2v
96
+ seacloud models spec kirin_v2_6_i2v --output json
97
+ ```
98
+
99
+ ### Run a task
100
+
101
+ ```bash
102
+ seacloud run kirin_v2_6_i2v --param image=https://example.com/cat.jpg
103
+ seacloud run kirin_v2_6_i2v --param prompt="a cat running" --param duration=5
104
+ seacloud run kirin_v2_6_i2v --param mode=pro --output url
105
+ ```
106
+
107
+ ### Check task status
108
+
109
+ ```bash
110
+ seacloud task status <task_id>
111
+ seacloud task status <task_id> --output url
112
+ seacloud task status <task_id> --output json
113
+ ```
114
+
115
+ ### Manage skills
116
+
117
+ ```bash
118
+ seacloud skills list
119
+ seacloud skills find prompt
120
+ seacloud skills add some-skill
121
+ seacloud skills config --show
122
+ ```
123
+
124
+ ## Commands
125
+
126
+ ### `seacloud auth`
127
+
128
+ ```bash
129
+ seacloud auth login
130
+ seacloud auth status
131
+ seacloud auth logout
132
+ seacloud auth set-key <api-key>
133
+ ```
134
+
135
+ ### `seacloud models`
136
+
137
+ ```bash
138
+ seacloud models list
139
+ seacloud models list --keywords kirin
140
+ seacloud models list --output id
141
+ seacloud models spec <model_id>
142
+ seacloud models spec <model_id> --output json
143
+ ```
144
+
145
+ ### `seacloud run`
146
+
147
+ ```bash
148
+ seacloud run <model_id> --param key=value
149
+ seacloud run <model_id> --param prompt="hello" --param duration=5
150
+ seacloud run <model_id> --output json
151
+ ```
152
+
153
+ Nested fields use dot notation:
154
+
155
+ ```bash
156
+ seacloud run some_model \
157
+ --param camera_control.type=simple \
158
+ --param camera_control.speed=2
159
+ ```
160
+
161
+ ### `seacloud task`
162
+
163
+ ```bash
164
+ seacloud task status <task_id>
165
+ ```
166
+
167
+ ### `seacloud skills`
168
+
169
+ ```bash
170
+ seacloud skills list
171
+ seacloud skills find [query]
172
+ seacloud skills add <slug>
173
+ seacloud skills config --show
174
+ ```
175
+
176
+ ### `seacloud version`
177
+
178
+ ```bash
179
+ seacloud version
180
+ ```
181
+
182
+ ## Output and Automation
183
+
184
+ - Use `--output json` where supported for machine-readable responses.
185
+ - Use `--output url` on task commands to print only result URLs.
186
+ - Use global `--dry-run` to inspect execution without sending requests.
187
+
188
+ Example:
189
+
190
+ ```bash
191
+ seacloud --dry-run run kirin_v2_6_i2v --param prompt=test
192
+ ```
193
+
194
+ ## Release
195
+
196
+ Release assets are built from source and published to GitHub Releases.
197
+ The npm package downloads the matching prebuilt binary for the user platform during installation.
198
+
199
+ If you maintain releases manually, the repository includes:
200
+
201
+ - `scripts/build.sh`
202
+ - `.goreleaser.yml`
203
+ - `scripts/set-release-version.js`
204
+
205
+ ## Repository Layout
206
+
207
+ ```text
208
+ seacloud-cli/
209
+ ├── cmd/ # CLI command definitions
210
+ ├── internal/auth/ # Auth client and login flow
211
+ ├── internal/models/ # Model list and spec APIs
212
+ ├── internal/generation/# Task submit and polling
213
+ ├── internal/skillhub/ # SkillHub client and install logic
214
+ ├── package.json # npm package manifest
215
+ ├── scripts/ # Build, release, and npm wrapper scripts
216
+ └── skills/ # Built-in skill definitions
217
+ ```
218
+
219
+ ## Contributing
220
+
221
+ Issues and pull requests are welcome. Before sending larger changes, it is best to open an issue first so the scope can be discussed.
222
+
223
+ For local verification:
224
+
225
+ ```bash
226
+ go test ./...
227
+ go run . --help
228
+ ```
package/README.zh.md ADDED
@@ -0,0 +1,228 @@
1
+ <div align="center">
2
+ <p>
3
+ <img src="./assets/seacloud-cli-image-zh.png" alt="SeaCloud CLI banner">
4
+ </p>
5
+ <h1>SeaCloud CLI</h1>
6
+ <h3>SeaCloud AI 平台的官方命令行界面</h3>
7
+ <p>
8
+ 专为人工智能代理而设计。可从任何代理或终端完成认证、模型查询、
9
+ 多模态任务执行、任务状态追踪和 SkillHub 技能管理。
10
+ </p>
11
+ <p>
12
+ <a href="https://www.npmjs.com/package/@seacloudai/seacloud-cli">
13
+ <img src="https://img.shields.io/npm/v/@seacloudai/seacloud-cli" alt="npm version">
14
+ </a>
15
+ <img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License">
16
+ <img src="https://img.shields.io/badge/node-%3E%3D18-339933" alt="Node.js >= 18">
17
+ <img src="https://img.shields.io/badge/go-%3E%3D1.26-00ADD8" alt="Go >= 1.26">
18
+ </p>
19
+ <p>
20
+ <a href="./README.md">English</a>
21
+ ·
22
+ <a href="https://vtrix.ai/">SeaCloud官网</a>
23
+ </p>
24
+ </div>
25
+
26
+ ## 功能特性
27
+
28
+ - **认证登录**:支持浏览器设备码登录,并将凭证安全保存在本地。
29
+ - **模型发现**:列出可用模型,并以可读文本或 JSON 查看完整参数规格。
30
+ - **任务执行**:通过 CLI 提交多模态生成任务,支持参数校验和结构化输出。
31
+ - **任务追踪**:轮询任务状态,输出结果 URL 或完整 JSON。
32
+ - **SkillHub 集成**:搜索、安装和配置 SeaCloud SkillHub 技能。
33
+ - **Agent 友好**:支持 `--dry-run`、JSON 输出、稳定命令结构和可直接复制的示例。
34
+
35
+ ## 安装
36
+
37
+ ### 使用 npm 安装
38
+
39
+ ```bash
40
+ npm install -g @seacloudai/seacloud-cli
41
+ ```
42
+
43
+ > 需要 Node.js 18+
44
+
45
+ ### 从源码安装
46
+
47
+ 默认安装方式:
48
+
49
+ ```bash
50
+ git clone https://github.com/SeaCloudAI/seacloud-cli.git
51
+ cd seacloud-cli
52
+ make install
53
+ ```
54
+
55
+ > 需要 Go 1.26+
56
+ > 安装后的二进制会注入公开版本使用的默认服务地址。你也可以通过 `SEACLOUD_BASE_URL`、`SEACLOUD_MODELS_URL`、`SEACLOUD_GENERATION_URL`、`SEACLOUD_SKILLHUB_URL` 覆盖这些地址。
57
+
58
+ 如果 `/usr/local/bin` 需要更高权限:
59
+
60
+ ```bash
61
+ sudo make install
62
+ ```
63
+
64
+ 如果你想在无 `sudo` 的情况下安装到用户目录:
65
+
66
+ ```bash
67
+ make install PREFIX=$HOME/.local
68
+ export PATH="$HOME/.local/bin:$PATH"
69
+ ```
70
+
71
+ ### 下载预编译二进制
72
+
73
+ 预编译二进制发布在 [Releases](https://github.com/SeaCloudAI/seacloud-cli/releases) 页面,当前支持:
74
+
75
+ - macOS `amd64`
76
+ - macOS `arm64`
77
+ - Linux `amd64`
78
+ - Linux `arm64`
79
+ - Windows `amd64`
80
+
81
+ ## 快速开始
82
+
83
+ ### 登录认证
84
+
85
+ ```bash
86
+ seacloud auth login
87
+ seacloud auth status
88
+ ```
89
+
90
+ ### 查询模型
91
+
92
+ ```bash
93
+ seacloud models list
94
+ seacloud models list --type video
95
+ seacloud models spec kirin_v2_6_i2v
96
+ seacloud models spec kirin_v2_6_i2v --output json
97
+ ```
98
+
99
+ ### 执行任务
100
+
101
+ ```bash
102
+ seacloud run kirin_v2_6_i2v --param image=https://example.com/cat.jpg
103
+ seacloud run kirin_v2_6_i2v --param prompt="a cat running" --param duration=5
104
+ seacloud run kirin_v2_6_i2v --param mode=pro --output url
105
+ ```
106
+
107
+ ### 查询任务状态
108
+
109
+ ```bash
110
+ seacloud task status <task_id>
111
+ seacloud task status <task_id> --output url
112
+ seacloud task status <task_id> --output json
113
+ ```
114
+
115
+ ### 管理技能
116
+
117
+ ```bash
118
+ seacloud skills list
119
+ seacloud skills find prompt
120
+ seacloud skills add some-skill
121
+ seacloud skills config --show
122
+ ```
123
+
124
+ ## 命令概览
125
+
126
+ ### `seacloud auth`
127
+
128
+ ```bash
129
+ seacloud auth login
130
+ seacloud auth status
131
+ seacloud auth logout
132
+ seacloud auth set-key <api-key>
133
+ ```
134
+
135
+ ### `seacloud models`
136
+
137
+ ```bash
138
+ seacloud models list
139
+ seacloud models list --keywords kirin
140
+ seacloud models list --output id
141
+ seacloud models spec <model_id>
142
+ seacloud models spec <model_id> --output json
143
+ ```
144
+
145
+ ### `seacloud run`
146
+
147
+ ```bash
148
+ seacloud run <model_id> --param key=value
149
+ seacloud run <model_id> --param prompt="hello" --param duration=5
150
+ seacloud run <model_id> --output json
151
+ ```
152
+
153
+ 嵌套字段支持 dot notation:
154
+
155
+ ```bash
156
+ seacloud run some_model \
157
+ --param camera_control.type=simple \
158
+ --param camera_control.speed=2
159
+ ```
160
+
161
+ ### `seacloud task`
162
+
163
+ ```bash
164
+ seacloud task status <task_id>
165
+ ```
166
+
167
+ ### `seacloud skills`
168
+
169
+ ```bash
170
+ seacloud skills list
171
+ seacloud skills find [query]
172
+ seacloud skills add <slug>
173
+ seacloud skills config --show
174
+ ```
175
+
176
+ ### `seacloud version`
177
+
178
+ ```bash
179
+ seacloud version
180
+ ```
181
+
182
+ ## 自动化与输出
183
+
184
+ - 在支持的命令上使用 `--output json` 获取机器可读输出。
185
+ - 在任务命令上使用 `--output url` 只打印结果 URL。
186
+ - 使用全局 `--dry-run` 在不发请求的前提下检查执行内容。
187
+
188
+ 示例:
189
+
190
+ ```bash
191
+ seacloud --dry-run run kirin_v2_6_i2v --param prompt=test
192
+ ```
193
+
194
+ ## 发布说明
195
+
196
+ 发布产物由源码构建后上传到 GitHub Releases。
197
+ npm 包在安装时会自动下载当前平台对应的预编译二进制。
198
+
199
+ 如果你需要手动维护发布流程,仓库中保留了这些文件:
200
+
201
+ - `scripts/build.sh`
202
+ - `.goreleaser.yml`
203
+ - `scripts/set-release-version.js`
204
+
205
+ ## 仓库结构
206
+
207
+ ```text
208
+ seacloud-cli/
209
+ ├── cmd/ # CLI 命令定义
210
+ ├── internal/auth/ # 认证客户端与登录流程
211
+ ├── internal/models/ # 模型列表与模型规格接口
212
+ ├── internal/generation/ # 任务提交与轮询
213
+ ├── internal/skillhub/ # SkillHub 客户端与安装逻辑
214
+ ├── package.json # npm 包清单
215
+ ├── scripts/ # 构建、发版与 npm 包装脚本
216
+ └── skills/ # 内置技能定义
217
+ ```
218
+
219
+ ## 参与贡献
220
+
221
+ 欢迎提交 Issue 和 Pull Request。对于较大的改动,建议先开一个 Issue 讨论范围。
222
+
223
+ 本地验证可使用:
224
+
225
+ ```bash
226
+ go test ./...
227
+ go run . --help
228
+ ```
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ 0526a5a3d950218a9f170521d194f70fbd592d1a878eb143ad04b4c9a9e3041e seacloud_0.0.3_darwin_amd64.tar.gz
2
+ 8c42e932084982adde59795bb9a12494688e0394142c04721179a171b6381b88 seacloud_0.0.3_darwin_arm64.tar.gz
3
+ 1cfb46f6d0c0c972749dae083ad32b8e5690c97b22bd47639d03a1af48514867 seacloud_0.0.3_linux_amd64.tar.gz
4
+ bdcd4a2a82375c2800cd657be46702e1b32a8abf0034997cca55b0c09f3dfc33 seacloud_0.0.3_linux_arm64.tar.gz
5
+ 75a7787446c1c86ce7005b5657913f77144b9ccb03ed6c3420d66adfe8e44207 seacloud_0.0.3_windows_amd64.zip
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@seacloudai/seacloud-cli",
3
+ "version": "0.0.3",
4
+ "description": "npm installer wrapper for the SeaCloud CLI binary",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "bin": {
10
+ "seacloud": "scripts/seacloud.js",
11
+ "seacloud-skills": "scripts/seacloud-skills.js"
12
+ },
13
+ "files": [
14
+ "assets",
15
+ "npm-bundles",
16
+ "scripts/postinstall.js",
17
+ "scripts/seacloud.js",
18
+ "scripts/seacloud-skills.js",
19
+ "vendor"
20
+ ],
21
+ "scripts": {
22
+ "prepare:npm-bundles": "node ./scripts/prepare-npm-bundles.js",
23
+ "postinstall": "node ./scripts/postinstall.js"
24
+ },
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "dependencies": {
29
+ "adm-zip": "^0.5.16",
30
+ "tar": "^7.4.3"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/SeaCloudAI/seacloud-cli.git"
35
+ },
36
+ "seacloud": {
37
+ "bundledAssetsDir": "npm-bundles",
38
+ "projectName": "seacloud",
39
+ "releaseBaseUrlTemplate": "https://github.com/SeaCloudAI/seacloud-cli/releases/download/v{version}"
40
+ }
41
+ }
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env node
2
+
3
+ const crypto = require("crypto");
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const AdmZip = require("adm-zip");
8
+ const tar = require("tar");
9
+
10
+ const rootDir = path.resolve(__dirname, "..");
11
+ const pkg = require(path.join(rootDir, "package.json"));
12
+
13
+ const bundledAssetsDir = path.join(
14
+ rootDir,
15
+ pkg.seacloud?.bundledAssetsDir || "npm-bundles"
16
+ );
17
+ const vendorDir = path.join(rootDir, "vendor");
18
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "seacloud-npm-"));
19
+
20
+ const SUPPORTED_TARGETS = {
21
+ "darwin-arm64": { os: "darwin", arch: "arm64", ext: "tar.gz", bin: "seacloud" },
22
+ "darwin-x64": { os: "darwin", arch: "amd64", ext: "tar.gz", bin: "seacloud" },
23
+ "linux-arm64": { os: "linux", arch: "arm64", ext: "tar.gz", bin: "seacloud" },
24
+ "linux-x64": { os: "linux", arch: "amd64", ext: "tar.gz", bin: "seacloud" },
25
+ "win32-x64": { os: "windows", arch: "amd64", ext: "zip", bin: "seacloud.exe" }
26
+ };
27
+
28
+ async function main() {
29
+ try {
30
+ if (process.env.SEACLOUD_SKIP_POSTINSTALL === "1") {
31
+ log("skip postinstall because SEACLOUD_SKIP_POSTINSTALL=1");
32
+ return;
33
+ }
34
+
35
+ const target = resolveTarget();
36
+ const releaseSources = resolveReleaseSources();
37
+ const projectName = pkg.seacloud?.projectName || "seacloud";
38
+ const version = pkg.version;
39
+
40
+ const assetName = `${projectName}_${version}_${target.os}_${target.arch}.${target.ext}`;
41
+ const archivePath = path.join(tmpDir, assetName);
42
+ const extractDir = path.join(tmpDir, "extract");
43
+
44
+ fs.mkdirSync(vendorDir, { recursive: true });
45
+ fs.rmSync(vendorDir, { recursive: true, force: true });
46
+ fs.mkdirSync(vendorDir, { recursive: true });
47
+ fs.mkdirSync(extractDir, { recursive: true });
48
+
49
+ log(`downloading ${assetName}`);
50
+ const expectedSha = await resolveExpectedSha(releaseSources, assetName);
51
+ await materializeArchive(releaseSources, assetName, archivePath, expectedSha);
52
+
53
+ await extractArchive(archivePath, extractDir, target.ext);
54
+ const extractedBinary = findFileRecursive(extractDir, target.bin);
55
+ if (!extractedBinary) {
56
+ throw new Error(`failed to locate ${target.bin} in extracted archive`);
57
+ }
58
+
59
+ const finalBinary = path.join(vendorDir, target.bin);
60
+ fs.copyFileSync(extractedBinary, finalBinary);
61
+ if (process.platform !== "win32") {
62
+ fs.chmodSync(finalBinary, 0o755);
63
+ }
64
+
65
+ log(`installed ${target.bin} to vendor directory`);
66
+ } finally {
67
+ fs.rmSync(tmpDir, { recursive: true, force: true });
68
+ }
69
+ }
70
+
71
+ function resolveTarget() {
72
+ const key = `${process.platform}-${process.arch}`;
73
+ const target = SUPPORTED_TARGETS[key];
74
+ if (!target) {
75
+ throw new Error(`unsupported platform: ${process.platform}/${process.arch}`);
76
+ }
77
+ return target;
78
+ }
79
+
80
+ function resolveReleaseBaseUrl() {
81
+ return resolveBaseUrl(
82
+ process.env.SEACLOUD_RELEASE_BASE_URL,
83
+ pkg.seacloud?.releaseBaseUrlTemplate,
84
+ "missing seacloud.releaseBaseUrlTemplate in package.json"
85
+ );
86
+ }
87
+
88
+ function resolveReleaseMirrorBaseUrl() {
89
+ return resolveBaseUrl(
90
+ process.env.SEACLOUD_RELEASE_MIRROR_BASE_URL,
91
+ pkg.seacloud?.releaseMirrorBaseUrlTemplate || null,
92
+ null
93
+ );
94
+ }
95
+
96
+ function resolveBaseUrl(fromEnv, template, missingMessage) {
97
+ if (fromEnv) {
98
+ return stripTrailingSlash(fromEnv);
99
+ }
100
+ if (!template) {
101
+ if (missingMessage) {
102
+ throw new Error(missingMessage);
103
+ }
104
+ return null;
105
+ }
106
+ return stripTrailingSlash(
107
+ template
108
+ .replaceAll("{version}", pkg.version)
109
+ .replaceAll("{tag}", `v${pkg.version}`)
110
+ );
111
+ }
112
+
113
+ function resolveReleaseSources() {
114
+ const sources = [
115
+ { name: "GitHub Release", type: "remote", baseUrl: resolveReleaseBaseUrl() }
116
+ ];
117
+ const mirrorBaseUrl = resolveReleaseMirrorBaseUrl();
118
+
119
+ if (mirrorBaseUrl && mirrorBaseUrl !== sources[0].baseUrl) {
120
+ sources.push({ name: "Release mirror", type: "remote", baseUrl: mirrorBaseUrl });
121
+ }
122
+
123
+ if (fs.existsSync(bundledAssetsDir)) {
124
+ sources.push({ name: "bundled npm package", type: "local", basePath: bundledAssetsDir });
125
+ }
126
+
127
+ return sources;
128
+ }
129
+
130
+ function getChecksumLocation(source) {
131
+ return source.type === "local"
132
+ ? path.join(source.basePath, "SHA256SUMS")
133
+ : `${source.baseUrl}/SHA256SUMS`;
134
+ }
135
+
136
+ function getAssetLocation(source, assetName) {
137
+ return source.type === "local"
138
+ ? path.join(source.basePath, assetName)
139
+ : `${source.baseUrl}/${assetName}`;
140
+ }
141
+
142
+ async function resolveExpectedSha(sources, assetName) {
143
+ const errors = [];
144
+
145
+ for (const source of sources) {
146
+ const checksumLocation = getChecksumLocation(source);
147
+ try {
148
+ const checksumText = source.type === "local"
149
+ ? fs.readFileSync(checksumLocation, "utf8")
150
+ : await fetchText(checksumLocation);
151
+ const expectedSha = parseChecksum(checksumText, assetName);
152
+ if (!expectedSha) {
153
+ throw new Error(`checksum for ${assetName} not found in SHA256SUMS`);
154
+ }
155
+ log(`using checksums from ${source.name}`);
156
+ return expectedSha;
157
+ } catch (err) {
158
+ errors.push(`${source.name}: ${err.message}`);
159
+ }
160
+ }
161
+
162
+ throw new Error(`failed to resolve checksums for ${assetName}\n${errors.join("\n")}`);
163
+ }
164
+
165
+ async function materializeArchive(sources, assetName, archivePath, expectedSha) {
166
+ const errors = [];
167
+
168
+ for (const source of sources) {
169
+ const assetLocation = getAssetLocation(source, assetName);
170
+ try {
171
+ log(`trying ${source.name}: ${assetLocation}`);
172
+ if (source.type === "local") {
173
+ fs.copyFileSync(assetLocation, archivePath);
174
+ } else {
175
+ await downloadFile(assetLocation, archivePath);
176
+ }
177
+ const actualSha = sha256File(archivePath);
178
+ if (actualSha !== expectedSha) {
179
+ throw new Error(`checksum mismatch for ${assetName}`);
180
+ }
181
+ log(`downloaded from ${source.name}`);
182
+ return;
183
+ } catch (err) {
184
+ fs.rmSync(archivePath, { force: true });
185
+ errors.push(`${source.name}: ${err.message}`);
186
+ }
187
+ }
188
+
189
+ throw new Error(`failed to obtain ${assetName}\n${errors.join("\n")}`);
190
+ }
191
+
192
+ function stripTrailingSlash(value) {
193
+ return value.replace(/\/+$/, "");
194
+ }
195
+
196
+ async function fetchText(url) {
197
+ try {
198
+ const response = await fetch(url);
199
+ if (!response.ok) {
200
+ throw new Error(`HTTP ${response.status}`);
201
+ }
202
+ return response.text();
203
+ } catch (err) {
204
+ throw new Error(`failed to download ${url}: ${err.message}`);
205
+ }
206
+ }
207
+
208
+ async function downloadFile(url, destination) {
209
+ try {
210
+ const response = await fetch(url);
211
+ if (!response.ok) {
212
+ throw new Error(`HTTP ${response.status}`);
213
+ }
214
+ const buffer = Buffer.from(await response.arrayBuffer());
215
+ fs.writeFileSync(destination, buffer);
216
+ } catch (err) {
217
+ throw new Error(`failed to download ${url}: ${err.message}`);
218
+ }
219
+ }
220
+
221
+ function parseChecksum(content, fileName) {
222
+ for (const line of content.split(/\r?\n/)) {
223
+ const trimmed = line.trim();
224
+ if (!trimmed) {
225
+ continue;
226
+ }
227
+ const match = trimmed.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
228
+ if (match && path.basename(match[2]) === fileName) {
229
+ return match[1].toLowerCase();
230
+ }
231
+ }
232
+ return null;
233
+ }
234
+
235
+ function sha256File(filePath) {
236
+ const hash = crypto.createHash("sha256");
237
+ hash.update(fs.readFileSync(filePath));
238
+ return hash.digest("hex");
239
+ }
240
+
241
+ async function extractArchive(archivePath, destination, extension) {
242
+ if (extension === "zip") {
243
+ const zip = new AdmZip(archivePath);
244
+ zip.extractAllTo(destination, true);
245
+ return;
246
+ }
247
+ if (extension === "tar.gz") {
248
+ await tar.x({
249
+ file: archivePath,
250
+ cwd: destination
251
+ });
252
+ return;
253
+ }
254
+ throw new Error(`unsupported archive format: ${extension}`);
255
+ }
256
+
257
+ function findFileRecursive(dir, fileName) {
258
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
259
+ for (const entry of entries) {
260
+ const fullPath = path.join(dir, entry.name);
261
+ if (entry.isFile() && entry.name === fileName) {
262
+ return fullPath;
263
+ }
264
+ if (entry.isDirectory()) {
265
+ const found = findFileRecursive(fullPath, fileName);
266
+ if (found) {
267
+ return found;
268
+ }
269
+ }
270
+ }
271
+ return null;
272
+ }
273
+
274
+ function log(message) {
275
+ console.log(`[seacloud installer] ${message}`);
276
+ }
277
+
278
+ main().catch((err) => {
279
+ console.error(`[seacloud installer] ${err.message}`);
280
+ console.error(
281
+ "[seacloud installer] hint: GitHub download failed; package will fall back to bundled archives when available."
282
+ );
283
+ process.exit(1);
284
+ });
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { spawn } = require("child_process");
6
+
7
+ const rootDir = path.resolve(__dirname, "..");
8
+ const exeName = process.platform === "win32" ? "seacloud.exe" : "seacloud";
9
+ const exePath = path.join(rootDir, "vendor", exeName);
10
+
11
+ if (!fs.existsSync(exePath)) {
12
+ console.error("seacloud binary is not installed.");
13
+ console.error("Reinstall the package: npm install -g @seacloudai/seacloud-cli");
14
+ process.exit(1);
15
+ }
16
+
17
+ const child = spawn(exePath, ["skills", ...process.argv.slice(2)], {
18
+ stdio: "inherit",
19
+ });
20
+
21
+ child.on("error", (err) => {
22
+ console.error(`failed to start seacloud skills: ${err.message}`);
23
+ process.exit(1);
24
+ });
25
+
26
+ child.on("exit", (code, signal) => {
27
+ if (signal) {
28
+ process.kill(process.pid, signal);
29
+ return;
30
+ }
31
+ process.exit(code ?? 0);
32
+ });
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { spawn } = require("child_process");
6
+
7
+ const rootDir = path.resolve(__dirname, "..");
8
+ const exeName = process.platform === "win32" ? "seacloud.exe" : "seacloud";
9
+ const exePath = path.join(rootDir, "vendor", exeName);
10
+
11
+ if (!fs.existsSync(exePath)) {
12
+ console.error("seacloud binary is not installed.");
13
+ console.error("Reinstall the package: npm install -g @seacloudai/seacloud-cli");
14
+ process.exit(1);
15
+ }
16
+
17
+ const child = spawn(exePath, process.argv.slice(2), {
18
+ stdio: "inherit",
19
+ });
20
+
21
+ child.on("error", (err) => {
22
+ console.error(`failed to start seacloud: ${err.message}`);
23
+ process.exit(1);
24
+ });
25
+
26
+ child.on("exit", (code, signal) => {
27
+ if (signal) {
28
+ process.kill(process.pid, signal);
29
+ return;
30
+ }
31
+ process.exit(code ?? 0);
32
+ });