rainskills 0.1.8-rc.2 → 0.1.9
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 +9 -9
- package/SKILL.md +2 -2
- package/bin/rainskills-tools.js +110 -54
- package/bin/rainskills.js +3 -3
- package/install.sh +43 -16
- package/marketplace/rainskills/.claude-plugin/plugin.json +1 -1
- package/marketplace/rainskills/.codex-plugin/plugin.json +1 -1
- package/marketplace/rainskills/skills/rainskills/SKILL.md +3 -3
- package/package.json +2 -2
- package/rainbond-app-assistant/SKILL.md +10 -7
- package/rainbond-app-version-assistant/SKILL.md +9 -6
- package/rainbond-delivery-verifier/SKILL.md +9 -6
- package/rainbond-env-sync/SKILL.md +9 -6
- package/rainbond-fullstack-bootstrap/SKILL.md +10 -7
- package/rainbond-fullstack-troubleshooter/SKILL.md +9 -6
- package/rainbond-opensource-app-deploy/SKILL.md +10 -7
- package/rainbond-platform-installer/SKILL.md +1 -1
- package/rainbond-platform-installer/scripts/auto-update.js +7 -2
- package/rainbond-platform-installer/scripts/installed-version.js +1 -1
- package/rainbond-platform-installer/scripts/platform-installer.js +28 -2
- package/rainbond-platform-installer/scripts/runtime-operations.js +5 -2
- package/rainbond-platform-installer/scripts/runtime-state.js +15 -18
- package/rainbond-platform-installer/scripts/secure-state.js +3 -1
- package/rainbond-platform-installer/scripts/windows-onboarding.js +18 -6
- package/rainbond-platform-query/SKILL.md +9 -6
- package/rainbond-project-init/SKILL.md +10 -7
- package/rainbond-template-installer/SKILL.md +10 -7
- package/scripts/install-local-cli.mjs +47 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Rainskills
|
|
2
2
|
|
|
3
|
-
Rainskills 是一组面向应用识别、部署、排障和交付的 AI Skills,支持 Codex 和
|
|
3
|
+
Rainskills 是一组面向应用识别、部署、排障和交付的 AI Skills,支持 Codex、Claude Code 和 Pi Agent。用户在市场中只会看到一个 `Rainskills` 产品,安装后 10 个 `rainbond-*` Skill 仍会独立触发。
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
@@ -41,7 +41,7 @@ Claude Code:
|
|
|
41
41
|
/reload-plugins
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
安装流程支持 Codex 和
|
|
44
|
+
安装流程支持 Codex、Claude Code 和 Pi Agent;Pi 与其他 Agent 共用同一组 Skills 和本地 Rainskills CLI,不再提供单独的 Pi MCP adapter。macOS、Linux 和 WSL 不支持 OpenClaw 安装。不要手工复制 Skill、拼接凭据或修改 Agent 配置。
|
|
45
45
|
|
|
46
46
|
## 安装完成时用户会看到什么
|
|
47
47
|
|
|
@@ -72,14 +72,14 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
72
72
|
固定 launcher 来自当前 `package.json` 版本:
|
|
73
73
|
|
|
74
74
|
```json
|
|
75
|
-
["
|
|
75
|
+
["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
所有调用都把 launcher 与参数合并成 argv 数组后直接执行;不得使用 `rainskills@latest`,不得把参数拼成 shell 字符串。
|
|
79
79
|
|
|
80
80
|
1. 执行 launcher + `["environment", "list", "--json"]`,先于项目扫描和任何 Rainbond 业务调用。未指定环境时只使用全局默认环境,默认不可用时停止,不自动回退。
|
|
81
81
|
2. 每个请求生成独立 operation UUID,并执行 `operation begin`;显式环境只写入这次 operation。所有 Rainbond 查询和变更都通过 `~/.rainbond/bin/rainskills-tools.js`,并绑定返回的 operation ID。项目不保存环境、团队或应用绑定,同一项目可以部署到多个目标。
|
|
82
|
-
3. 将动作转换为 `runtime-intents.js` 中对应的受限 intent 并完成字段校验。target 只允许 `codex`、`claude`、`all`;按用户选择构造 launcher + `["runtime", "connect", "<target>", ...环境参数, "--intent-json", "<JSON.stringify(已校验 intent)>"]`。
|
|
82
|
+
3. 将动作转换为 `runtime-intents.js` 中对应的受限 intent 并完成字段校验。target 只允许 `codex`、`claude`、`pi`、`all`;按用户选择构造 launcher + `["runtime", "connect", "<target>", ...环境参数, "--intent-json", "<JSON.stringify(已校验 intent)>"]`。
|
|
83
83
|
4. 环境参数必须恰好选择一组且互斥:Cloud 用 `["--saas"]`,已有私有环境用 `["--rainbond-url", "<已验证 Console origin>"]`,新建私有环境用 `["--install-private", "--location", "local"]` 或 `["--install-private", "--location", "server"]`。
|
|
84
84
|
5. 安装私有环境时,只消费完成 schema、action、onboarding id 和参数边界校验的 `rainskills.next-action.v1.argv`;拒绝字符串命令和其他输出字段。
|
|
85
85
|
6. 探针失败进入 reconnect。连接或安装完成后执行 launcher + `["intent", "resume", "--onboarding-id", "<同一 onboarding-id>"]`,恢复受保护的原始 intent 和 `resume_step`,不重新猜测用户动作。
|
|
@@ -192,21 +192,21 @@ Windows 本地安装是预览能力,也可以改选 Linux 服务器。它要
|
|
|
192
192
|
|
|
193
193
|
```bash
|
|
194
194
|
# 本地单机
|
|
195
|
-
|
|
195
|
+
node ~/.rainbond/lib/rainskills/bin/rainskills.js platform install --onboarding-id <id> \
|
|
196
196
|
--location local --mode single-node
|
|
197
197
|
|
|
198
198
|
# Linux 服务器单机
|
|
199
|
-
|
|
199
|
+
node ~/.rainbond/lib/rainskills/bin/rainskills.js platform install --onboarding-id <id> \
|
|
200
200
|
--location server --mode single-node --ssh <user@host>
|
|
201
201
|
|
|
202
202
|
# 服务器主机集群(自动生成受保护示例文件)
|
|
203
|
-
|
|
203
|
+
node ~/.rainbond/lib/rainskills/bin/rainskills.js platform install --onboarding-id <id> \
|
|
204
204
|
--location server --mode host-cluster
|
|
205
205
|
|
|
206
206
|
# 已有高级 ROI cluster.yaml 才追加:--cluster-config <path>
|
|
207
207
|
|
|
208
208
|
# 已有 Kubernetes
|
|
209
|
-
|
|
209
|
+
node ~/.rainbond/lib/rainskills/bin/rainskills.js platform install --onboarding-id <id> \
|
|
210
210
|
--location server --mode existing-kubernetes \
|
|
211
211
|
--kubeconfig <path> --kube-context <name> --chart-version <version>
|
|
212
212
|
```
|
|
@@ -228,7 +228,7 @@ npx --yes rainskills@0.1.8-rc.2 platform install --onboarding-id <id> \
|
|
|
228
228
|
|
|
229
229
|
Rainskills 会在用户下一次发起业务动作时,由本地运行时立即返回环境查询结果,并另行启动后台任务静默检查更新。更新只跟随 npm `latest` 指向的正式版。当前版本是 RC 或其他预发布版本时不会查询、不会自动升级;npm 上的新 RC 版本也不参与正式版自动升级。
|
|
230
230
|
|
|
231
|
-
发现更高的正式版后,后台任务只委托到经过校验的精确版本,例如 `rainskills@0.1.
|
|
231
|
+
发现更高的正式版后,后台任务只委托到经过校验的精确版本,例如 `rainskills@0.1.9`,不会执行浮动的 `@latest` 业务代码。新版本原子刷新已经安装的 Rainskills Skills;当前业务继续使用启动时已经加载的版本,最迟从下一条新任务开始使用新版。npm 超时、版本检查失败、安装位置不安全或文件迁移失败时会保留旧版本,且不会阻塞或改变当前操作。
|
|
232
232
|
|
|
233
233
|
升级只更新 Rainskills 自身,不触发 Rainbond 安装、运行环境选择、登录授权或重新对接,也不会新增 Agent MCP 配置。更新内容仅包括 Skills 和本地 CLI。原始业务操作会继续执行;只有该业务操作本身需要运行环境时,才按既有门禁检查当前连接。可用连接直接复用,401 只重新授权一次,403 立即停止,从未连接过运行环境时才进入环境选择。
|
|
234
234
|
|
package/SKILL.md
CHANGED
|
@@ -9,7 +9,7 @@ This is the single marketplace entry for the complete Rainskills product. The in
|
|
|
9
9
|
|
|
10
10
|
## Initialize
|
|
11
11
|
|
|
12
|
-
1. Detect the current host client and map it to exactly one supported installer target: Codex=`codex
|
|
12
|
+
1. Detect the current host client and map it to exactly one supported installer target: Codex=`codex`, Claude Code=`claude`, or Pi Agent=`pi`. All three use the same installed Skills and protected local Rainskills CLI; Pi has no separate MCP adapter or generated extension. The macOS, Linux, and WSL installer intentionally does not support OpenClaw; report that limitation instead of invoking an unsupported target. Do not ask the user which supported client they are currently using. If the host cannot be determined reliably, omit the target and let the installer ask.
|
|
13
13
|
2. Resolve the directory containing this `SKILL.md`. On native Windows, if the adjacent `bin/rainskills.js` exists, run it by absolute path with the detected target in an attached interactive terminal: `node <skill-directory>/bin/rainskills.js <target>`. On macOS, Linux, or WSL, if the adjacent `install.sh` exists, run `bash <skill-directory>/install.sh <target>` the same way. Do not replace the installer with manual file copies.
|
|
14
14
|
3. Keep stdin, stdout, and stderr attached. When `RAINSKILLS_USER_INPUT_REQUIRED` appears, pause for that installer choice. If the installer emits `rainskills.next-action.v1`, execute only its fixed `argv` through the same launcher; never evaluate output as a shell command. If the adjacent `bin/rainskills.js` exists, use it for fixed next actions; otherwise use the same versioned npm package fallback described below.
|
|
15
15
|
4. Stay attached until every independent Skill is installed. Do not select, connect, or configure an application runtime during installation. In the user-facing response, output only the fixed completion message below.
|
|
@@ -38,7 +38,7 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
38
38
|
|
|
39
39
|
## Manage Runtime Environments
|
|
40
40
|
|
|
41
|
-
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `
|
|
41
|
+
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.9`)。
|
|
42
42
|
|
|
43
43
|
- 列表:用本地 launcher 执行 `environment list --json`,只展示名称、类型、状态、是否默认和最近验证时间。
|
|
44
44
|
- 重命名:用本地 launcher 执行 `environment rename --environment-id <uuid> --name <name>`。
|
package/bin/rainskills-tools.js
CHANGED
|
@@ -110,6 +110,7 @@ function readPrivateEnvFile(configPath) {
|
|
|
110
110
|
function requireRuntimeModule(filename) {
|
|
111
111
|
const candidates = [
|
|
112
112
|
path.resolve(__dirname, "..", "rainbond-platform-installer", "scripts", filename),
|
|
113
|
+
path.resolve(__dirname, "..", "lib", "rainskills", "rainbond-platform-installer", "scripts", filename),
|
|
113
114
|
path.resolve(__dirname, "..", "lib", "rainbond-platform-installer", "scripts", filename),
|
|
114
115
|
];
|
|
115
116
|
for (const candidate of candidates) {
|
|
@@ -509,7 +510,6 @@ function buildAuditMetadata(operation, skillBinding) {
|
|
|
509
510
|
return {
|
|
510
511
|
schema: "rainskills.operation-meta.v1",
|
|
511
512
|
operation_id: operation.operation_id,
|
|
512
|
-
runtime_operation_id: operation.runtime_operation_id,
|
|
513
513
|
cli_version: CLI_VERSION,
|
|
514
514
|
confirmation_type: "rainskills_cli",
|
|
515
515
|
root_skill_id: skillBinding.rootSkillId,
|
|
@@ -578,15 +578,23 @@ function fitOutput(value) {
|
|
|
578
578
|
};
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
-
function sanitizeToolResult(value) {
|
|
582
|
-
if (Array.isArray(value))
|
|
581
|
+
function sanitizeToolResult(value, pathParts = []) {
|
|
582
|
+
if (Array.isArray(value)) {
|
|
583
|
+
return value.map((item) => sanitizeToolResult(item, pathParts));
|
|
584
|
+
}
|
|
583
585
|
if (!value || typeof value !== "object") return value;
|
|
584
586
|
const safe = {};
|
|
585
587
|
for (const [key, item] of Object.entries(value)) {
|
|
586
588
|
// Console destructive operations may return this one-time control value.
|
|
587
589
|
// It is not a credential and must remain available for the confirmation flow.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
+
const isPackageUploadAuthMode = key === "authorization"
|
|
591
|
+
&& item === "none"
|
|
592
|
+
&& pathParts.length === 1
|
|
593
|
+
&& pathParts[0] === "upload_request";
|
|
594
|
+
if (key !== "confirmation_token"
|
|
595
|
+
&& !isPackageUploadAuthMode
|
|
596
|
+
&& SENSITIVE_RESPONSE_KEY_PATTERN.test(key)) continue;
|
|
597
|
+
safe[key] = sanitizeToolResult(item, [...pathParts, key]);
|
|
590
598
|
}
|
|
591
599
|
return safe;
|
|
592
600
|
}
|
|
@@ -623,17 +631,12 @@ function safeJson(value, secrets = []) {
|
|
|
623
631
|
return output;
|
|
624
632
|
}
|
|
625
633
|
|
|
626
|
-
function
|
|
634
|
+
function httpJsonRpcRequest(config, endpointPath, message, {
|
|
627
635
|
timeoutMs = REQUEST_TIMEOUT_MS,
|
|
628
636
|
maxResponseBytes = RESPONSE_MAX_BYTES,
|
|
629
637
|
} = {}) {
|
|
630
|
-
const endpoint = new URL(`${config.baseUrl.replace(/\/+$/, "")}${
|
|
631
|
-
const payload = JSON.stringify(
|
|
632
|
-
jsonrpc: "2.0",
|
|
633
|
-
id: 1,
|
|
634
|
-
method,
|
|
635
|
-
params,
|
|
636
|
-
});
|
|
638
|
+
const endpoint = new URL(`${config.baseUrl.replace(/\/+$/, "")}${endpointPath}`);
|
|
639
|
+
const payload = JSON.stringify(message);
|
|
637
640
|
const client = endpoint.protocol === "https:" ? https : http;
|
|
638
641
|
|
|
639
642
|
return new Promise((resolve, reject) => {
|
|
@@ -694,47 +697,11 @@ function rpcRequest(config, method, params = {}, {
|
|
|
694
697
|
return;
|
|
695
698
|
}
|
|
696
699
|
const body = Buffer.concat(chunks, responseBytes).toString("utf8");
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
fail(new BridgeError(
|
|
703
|
-
response.statusCode === 404
|
|
704
|
-
? "Rainskills API endpoint is unavailable; upgrade Rainbond Console"
|
|
705
|
-
: "Rainbond API request failed",
|
|
706
|
-
EXIT.TRANSPORT
|
|
707
|
-
));
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
let rpc;
|
|
711
|
-
try {
|
|
712
|
-
rpc = JSON.parse(body);
|
|
713
|
-
} catch (_error) {
|
|
714
|
-
fail(new BridgeError("Rainbond returned invalid JSON", EXIT.TRANSPORT));
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
if (!rpc || rpc.jsonrpc !== "2.0" || rpc.id !== 1) {
|
|
718
|
-
fail(new BridgeError("Rainbond returned an invalid JSON-RPC response", EXIT.TRANSPORT));
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
if (rpc.error) {
|
|
722
|
-
const protocolErrors = new Set([-32700, -32600, -32601]);
|
|
723
|
-
fail(new BridgeError(
|
|
724
|
-
"Rainbond rejected the tool request",
|
|
725
|
-
protocolErrors.has(rpc.error.code) ? EXIT.TRANSPORT : EXIT.TOOL,
|
|
726
|
-
{
|
|
727
|
-
code: rpc.error.code,
|
|
728
|
-
message: rpc.error.message,
|
|
729
|
-
}
|
|
730
|
-
));
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
if (!("result" in rpc)) {
|
|
734
|
-
fail(new BridgeError("Rainbond JSON-RPC result is missing", EXIT.TRANSPORT));
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
settle(resolve, rpc.result);
|
|
700
|
+
settle(resolve, {
|
|
701
|
+
statusCode: response.statusCode || 0,
|
|
702
|
+
headers: response.headers,
|
|
703
|
+
body,
|
|
704
|
+
});
|
|
738
705
|
});
|
|
739
706
|
});
|
|
740
707
|
request.on("error", (error) => {
|
|
@@ -749,6 +716,95 @@ function rpcRequest(config, method, params = {}, {
|
|
|
749
716
|
});
|
|
750
717
|
}
|
|
751
718
|
|
|
719
|
+
function parseJsonRpcBody(body, expectedId, { allowEmpty = false, contentType = "" } = {}) {
|
|
720
|
+
if (!body.trim() && allowEmpty) return null;
|
|
721
|
+
let serialized = body;
|
|
722
|
+
if (contentType.toLowerCase().includes("text/event-stream")) {
|
|
723
|
+
const data = body.split(/\r?\n/)
|
|
724
|
+
.filter((line) => line.startsWith("data:"))
|
|
725
|
+
.map((line) => line.slice(5).trim())
|
|
726
|
+
.filter(Boolean)
|
|
727
|
+
.at(-1);
|
|
728
|
+
if (!data) throw new BridgeError("Rainbond returned invalid MCP event data", EXIT.TRANSPORT);
|
|
729
|
+
serialized = data;
|
|
730
|
+
}
|
|
731
|
+
let rpc;
|
|
732
|
+
try {
|
|
733
|
+
rpc = JSON.parse(serialized);
|
|
734
|
+
} catch (_error) {
|
|
735
|
+
throw new BridgeError("Rainbond returned invalid JSON", EXIT.TRANSPORT);
|
|
736
|
+
}
|
|
737
|
+
if (!rpc || rpc.jsonrpc !== "2.0" || (expectedId !== undefined && rpc.id !== expectedId)) {
|
|
738
|
+
throw new BridgeError("Rainbond returned an invalid JSON-RPC response", EXIT.TRANSPORT);
|
|
739
|
+
}
|
|
740
|
+
if (rpc.error) {
|
|
741
|
+
const protocolErrors = new Set([-32700, -32600, -32601]);
|
|
742
|
+
throw new BridgeError(
|
|
743
|
+
"Rainbond rejected the tool request",
|
|
744
|
+
protocolErrors.has(rpc.error.code) ? EXIT.TRANSPORT : EXIT.TOOL,
|
|
745
|
+
{ code: rpc.error.code, message: rpc.error.message }
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (!("result" in rpc)) {
|
|
749
|
+
throw new BridgeError("Rainbond JSON-RPC result is missing", EXIT.TRANSPORT);
|
|
750
|
+
}
|
|
751
|
+
return rpc.result;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function assertRpcHttpSuccess(response, { endpointUnavailable = false } = {}) {
|
|
755
|
+
if (response.statusCode === 401 || response.statusCode === 403) {
|
|
756
|
+
throw new BridgeError("Rainbond authentication failed", EXIT.CONFIG);
|
|
757
|
+
}
|
|
758
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
759
|
+
throw new BridgeError(
|
|
760
|
+
endpointUnavailable && response.statusCode === 404
|
|
761
|
+
? "Rainskills API endpoint is unavailable; upgrade Rainbond Console"
|
|
762
|
+
: "Rainbond API request failed",
|
|
763
|
+
EXIT.TRANSPORT
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function isVerifiedMissingRpcRoute(response) {
|
|
769
|
+
if (response.statusCode !== 404) return false;
|
|
770
|
+
const contentType = String(response.headers["content-type"] || "").toLowerCase();
|
|
771
|
+
const body = response.body.trim();
|
|
772
|
+
if (contentType.includes("text/plain")) return body === "Not Found";
|
|
773
|
+
if (contentType.includes("text/html")) {
|
|
774
|
+
const normalized = body.toLowerCase();
|
|
775
|
+
return normalized.includes("<title") && normalized.includes("not found");
|
|
776
|
+
}
|
|
777
|
+
if (!contentType.includes("json")) return false;
|
|
778
|
+
try {
|
|
779
|
+
const payload = JSON.parse(body);
|
|
780
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
|
|
781
|
+
if (["Not Found", "not found"].includes(payload.detail)) return true;
|
|
782
|
+
return ["code", "status", "status_code", "error_code"]
|
|
783
|
+
.some((key) => payload[key] === 404 || payload[key] === "404");
|
|
784
|
+
} catch {
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
async function rpcRequest(config, method, params = {}, options = {}) {
|
|
790
|
+
const response = await httpJsonRpcRequest(config, ENDPOINT_PATH, {
|
|
791
|
+
jsonrpc: "2.0",
|
|
792
|
+
id: 1,
|
|
793
|
+
method,
|
|
794
|
+
params,
|
|
795
|
+
}, options);
|
|
796
|
+
if (isVerifiedMissingRpcRoute(response)) {
|
|
797
|
+
throw new BridgeError(
|
|
798
|
+
"当前 Rainbond 版本不支持 Rainskills CLI,请先将 Rainbond 升级到 v6.9.9 或更高版本",
|
|
799
|
+
EXIT.TRANSPORT
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
assertRpcHttpSuccess(response);
|
|
803
|
+
return parseJsonRpcBody(response.body, 1, {
|
|
804
|
+
contentType: String(response.headers["content-type"] || ""),
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
752
808
|
async function listTools(config) {
|
|
753
809
|
const cached = loadCachedCatalog(config);
|
|
754
810
|
if (cached) return cached.tools;
|
package/bin/rainskills.js
CHANGED
|
@@ -80,7 +80,7 @@ function parseRuntimeAssertConnectArgs(args) {
|
|
|
80
80
|
throw new Error("runtime connect 内部门禁参数无效");
|
|
81
81
|
}
|
|
82
82
|
if (!UUID_PATTERN.test(args[3] || "")) throw new Error("runtime connect operation 无效");
|
|
83
|
-
if (!["codex", "claude", "all"].includes(args[5])) throw new Error("runtime connect target 无效");
|
|
83
|
+
if (!["codex", "claude", "pi", "all"].includes(args[5])) throw new Error("runtime connect target 无效");
|
|
84
84
|
if (!["saas", "private"].includes(args[7])) throw new Error("runtime connect environment kind 无效");
|
|
85
85
|
return {
|
|
86
86
|
operationId: args[3],
|
|
@@ -122,8 +122,8 @@ function parseRuntimeConnectArgs(args) {
|
|
|
122
122
|
throw new Error("不是 runtime connect 命令");
|
|
123
123
|
}
|
|
124
124
|
const targetClient = args[2];
|
|
125
|
-
if (!["codex", "claude", "all"].includes(targetClient)) {
|
|
126
|
-
throw new Error("runtime connect 需要固定目标 codex、claude 或 all");
|
|
125
|
+
if (!["codex", "claude", "pi", "all"].includes(targetClient)) {
|
|
126
|
+
throw new Error("runtime connect 需要固定目标 codex、claude、pi 或 all");
|
|
127
127
|
}
|
|
128
128
|
let environmentChoice = "";
|
|
129
129
|
let rainbondUrl = "";
|
package/install.sh
CHANGED
|
@@ -204,6 +204,7 @@ Usage:
|
|
|
204
204
|
./install.sh
|
|
205
205
|
./install.sh claude
|
|
206
206
|
./install.sh codex
|
|
207
|
+
./install.sh pi
|
|
207
208
|
./install.sh all
|
|
208
209
|
./install.sh --dest <path>
|
|
209
210
|
./install.sh all --saas
|
|
@@ -214,7 +215,8 @@ Usage:
|
|
|
214
215
|
Options:
|
|
215
216
|
claude Install and configure Claude Code
|
|
216
217
|
codex Install and configure Codex
|
|
217
|
-
|
|
218
|
+
pi Install and configure Pi Agent
|
|
219
|
+
all Install and configure Codex, Claude Code, and Pi Agent
|
|
218
220
|
refresh Re-run browser login and refresh the protected CLI credential
|
|
219
221
|
--dest PATH Install skills to a custom directory only
|
|
220
222
|
--force Overwrite existing installed skills
|
|
@@ -293,6 +295,9 @@ rainskills_install_client_for_target() {
|
|
|
293
295
|
claude)
|
|
294
296
|
printf 'claude_code\n'
|
|
295
297
|
;;
|
|
298
|
+
pi)
|
|
299
|
+
printf 'pi\n'
|
|
300
|
+
;;
|
|
296
301
|
all)
|
|
297
302
|
printf 'all\n'
|
|
298
303
|
;;
|
|
@@ -456,7 +461,7 @@ event = {
|
|
|
456
461
|
"platform": platform,
|
|
457
462
|
"control_mode": control_mode,
|
|
458
463
|
"target": target,
|
|
459
|
-
"client": client if client in {"codex", "claude_code", "both", "unknown"} else "unknown",
|
|
464
|
+
"client": client if client in {"codex", "claude_code", "pi", "all", "both", "unknown"} else "unknown",
|
|
460
465
|
"eid": eid or None,
|
|
461
466
|
"phase": None,
|
|
462
467
|
"lifecycle_phase": phase,
|
|
@@ -513,7 +518,7 @@ initialize_rainskills_installation_reporting() {
|
|
|
513
518
|
connect)
|
|
514
519
|
RAINSKILLS_INSTALL_ACTION="connect"
|
|
515
520
|
;;
|
|
516
|
-
codex|claude|all)
|
|
521
|
+
codex|claude|pi|all)
|
|
517
522
|
target="$arg"
|
|
518
523
|
;;
|
|
519
524
|
esac
|
|
@@ -811,12 +816,12 @@ parse_args() {
|
|
|
811
816
|
ACTION="connect"
|
|
812
817
|
shift
|
|
813
818
|
;;
|
|
814
|
-
claude|codex|all)
|
|
819
|
+
claude|codex|pi|all)
|
|
815
820
|
TARGET="$1"
|
|
816
821
|
shift
|
|
817
822
|
;;
|
|
818
823
|
openclaw)
|
|
819
|
-
die "macOS/Linux 安装器不再支持 OpenClaw,请使用 Codex 或
|
|
824
|
+
die "macOS/Linux 安装器不再支持 OpenClaw,请使用 Codex、Claude Code 或 Pi Agent。"
|
|
820
825
|
;;
|
|
821
826
|
--dest)
|
|
822
827
|
[[ $# -ge 2 ]] || die "--dest 需要一个路径值"
|
|
@@ -904,7 +909,7 @@ assert_internal_connect_entry() {
|
|
|
904
909
|
local target="" environment_kind="" console_origin="" mode=""
|
|
905
910
|
while [[ $# -gt 0 ]]; do
|
|
906
911
|
case "$1" in
|
|
907
|
-
codex|claude|all)
|
|
912
|
+
codex|claude|pi|all)
|
|
908
913
|
[[ -z "$target" ]] || return 1
|
|
909
914
|
target="$1"
|
|
910
915
|
shift
|
|
@@ -1008,10 +1013,11 @@ resolve_target() {
|
|
|
1008
1013
|
log "请选择要安装和配置的平台:"
|
|
1009
1014
|
log " 1) Codex"
|
|
1010
1015
|
log " 2) Claude Code"
|
|
1011
|
-
log " 3)
|
|
1016
|
+
log " 3) Pi Agent"
|
|
1017
|
+
log " 4) 全部"
|
|
1012
1018
|
|
|
1013
1019
|
while true; do
|
|
1014
|
-
printf '请输入选项 [1-
|
|
1020
|
+
printf '请输入选项 [1-4]: '
|
|
1015
1021
|
read -r choice
|
|
1016
1022
|
case "$choice" in
|
|
1017
1023
|
1)
|
|
@@ -1022,12 +1028,16 @@ resolve_target() {
|
|
|
1022
1028
|
TARGET="claude"
|
|
1023
1029
|
return 0
|
|
1024
1030
|
;;
|
|
1025
|
-
3
|
|
1031
|
+
3)
|
|
1032
|
+
TARGET="pi"
|
|
1033
|
+
return 0
|
|
1034
|
+
;;
|
|
1035
|
+
4|"")
|
|
1026
1036
|
TARGET="all"
|
|
1027
1037
|
return 0
|
|
1028
1038
|
;;
|
|
1029
1039
|
*)
|
|
1030
|
-
log "请输入 1、2 或
|
|
1040
|
+
log "请输入 1、2、3 或 4。"
|
|
1031
1041
|
;;
|
|
1032
1042
|
esac
|
|
1033
1043
|
done
|
|
@@ -1046,9 +1056,13 @@ collect_destinations() {
|
|
|
1046
1056
|
codex)
|
|
1047
1057
|
destinations+=("$HOME/.codex/skills")
|
|
1048
1058
|
;;
|
|
1059
|
+
pi)
|
|
1060
|
+
destinations+=("$HOME/.pi/agent/skills")
|
|
1061
|
+
;;
|
|
1049
1062
|
all)
|
|
1050
1063
|
destinations+=("$HOME/.claude/skills")
|
|
1051
1064
|
destinations+=("$HOME/.codex/skills")
|
|
1065
|
+
destinations+=("$HOME/.pi/agent/skills")
|
|
1052
1066
|
;;
|
|
1053
1067
|
*)
|
|
1054
1068
|
die "未知安装目标:$TARGET"
|
|
@@ -2238,6 +2252,21 @@ PY
|
|
|
2238
2252
|
log "[verify] Rainbond MCP 可访问"
|
|
2239
2253
|
}
|
|
2240
2254
|
|
|
2255
|
+
validate_runtime_mcp_connectivity() {
|
|
2256
|
+
local base_url="$1"
|
|
2257
|
+
local token="$2"
|
|
2258
|
+
local status
|
|
2259
|
+
if validate_mcp_connectivity "${base_url}/console/mcp/rainskills/api/query" "$token"; then
|
|
2260
|
+
return 0
|
|
2261
|
+
else
|
|
2262
|
+
status=$?
|
|
2263
|
+
fi
|
|
2264
|
+
if [[ "$status" -eq 2 ]]; then
|
|
2265
|
+
die "当前 Rainbond 版本不支持 Rainskills CLI。请先将 Rainbond 升级到 v6.9.9 或更高版本,然后从当前任务继续。"
|
|
2266
|
+
fi
|
|
2267
|
+
return "$status"
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2241
2270
|
looks_like_jwt() {
|
|
2242
2271
|
# JWT compact form: header.payload.signature, three base64url segments.
|
|
2243
2272
|
[[ "$1" =~ ^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$ ]]
|
|
@@ -2469,14 +2498,13 @@ do_refresh() {
|
|
|
2469
2498
|
base_url="$(normalize_rainbond_url "$base_url_input")"
|
|
2470
2499
|
confirm_insecure_http_if_needed "$base_url"
|
|
2471
2500
|
|
|
2472
|
-
local token
|
|
2501
|
+
local token
|
|
2473
2502
|
obtain_rainbond_token "$base_url" "$DEPLOYMENT_MODE_INPUT"
|
|
2474
2503
|
token="$OBTAINED_RAINBOND_TOKEN"
|
|
2475
|
-
cli_api_url="${base_url}/console/mcp/rainskills/api/query"
|
|
2476
2504
|
record_rainskills_authorization "$base_url" "$token"
|
|
2477
2505
|
|
|
2478
2506
|
set_rainskills_failure_context "verification" "mcp_verification_failed"
|
|
2479
|
-
if !
|
|
2507
|
+
if ! validate_runtime_mcp_connectivity "$base_url" "$token"; then
|
|
2480
2508
|
die "Rainbond CLI 接口校验失败。"
|
|
2481
2509
|
fi
|
|
2482
2510
|
token="$VALIDATED_TOKEN"
|
|
@@ -2547,15 +2575,14 @@ configure_runtime_connection() {
|
|
|
2547
2575
|
base_url="$(normalize_rainbond_url "$base_url_input")"
|
|
2548
2576
|
confirm_insecure_http_if_needed "$base_url"
|
|
2549
2577
|
|
|
2550
|
-
local token
|
|
2578
|
+
local token
|
|
2551
2579
|
obtain_rainbond_token "$base_url" "$DEPLOYMENT_MODE_INPUT"
|
|
2552
2580
|
token="$OBTAINED_RAINBOND_TOKEN"
|
|
2553
2581
|
RAINSKILLS_INSTALL_CLIENT="$(rainskills_install_client_for_target "$TARGET")"
|
|
2554
2582
|
record_rainskills_authorization "$base_url" "$token"
|
|
2555
|
-
cli_api_url="${base_url}/console/mcp/rainskills/api/query"
|
|
2556
2583
|
|
|
2557
2584
|
set_rainskills_failure_context "verification" "mcp_verification_failed"
|
|
2558
|
-
if !
|
|
2585
|
+
if ! validate_runtime_mcp_connectivity "$base_url" "$token"; then
|
|
2559
2586
|
die "Rainbond CLI 接口校验失败。"
|
|
2560
2587
|
fi
|
|
2561
2588
|
token="$VALIDATED_TOKEN"
|
|
@@ -9,12 +9,12 @@ This is the single marketplace entry for the complete Rainskills product. The in
|
|
|
9
9
|
|
|
10
10
|
## Initialize
|
|
11
11
|
|
|
12
|
-
1. Detect the current host client and map it to exactly one supported installer target: Codex=`codex
|
|
12
|
+
1. Detect the current host client and map it to exactly one supported installer target: Codex=`codex`, Claude Code=`claude`, or Pi Agent=`pi`. All three use the same installed Skills and protected local Rainskills CLI; Pi has no separate MCP adapter or generated extension. The macOS, Linux, and WSL installer intentionally does not support OpenClaw; report that limitation instead of invoking an unsupported target. Do not ask the user which supported client they are currently using. If the host cannot be determined reliably, omit the target and let the installer ask.
|
|
13
13
|
2. Resolve the directory containing this `SKILL.md`. On native Windows, if the adjacent `bin/rainskills.js` exists, run it by absolute path with the detected target in an attached interactive terminal: `node <skill-directory>/bin/rainskills.js <target>`. On macOS, Linux, or WSL, if the adjacent `install.sh` exists, run `bash <skill-directory>/install.sh <target>` the same way. Do not replace the installer with manual file copies.
|
|
14
14
|
3. Keep stdin, stdout, and stderr attached. When `RAINSKILLS_USER_INPUT_REQUIRED` appears, pause for that installer choice. If the installer emits `rainskills.next-action.v1`, execute only its fixed `argv` through the same launcher; never evaluate output as a shell command. If the adjacent `bin/rainskills.js` exists, use it for fixed next actions; otherwise use the same versioned npm package fallback described below.
|
|
15
15
|
4. Stay attached until every independent Skill is installed. Do not select, connect, or configure an application runtime during installation. In the user-facing response, output only the fixed completion message below.
|
|
16
16
|
|
|
17
|
-
If the adjacent installer is missing, check the local Node.js version before choosing the fallback. With `npx` and Node.js 18 or newer, use `npx --yes rainskills@0.1.
|
|
17
|
+
If the adjacent installer is missing, check the local Node.js version before choosing the fallback. With `npx` and Node.js 18 or newer, use `npx --yes rainskills@0.1.9 <target>`. With no Node.js or a version below 18, use `bash <(curl -fsSL https://get.rainbond.com/rainskills/install.sh) <target>` instead. Omit `<target>` only when the host cannot be determined reliably. Keep either command attached to the interactive terminal. For an update or repair, refresh this marketplace Skill first, then run the installer again; it compares and updates every independent internal Skill.
|
|
18
18
|
|
|
19
19
|
Skills-only 安装不需要 Node.js;CDN fallback 只负责安装 Skill 文件,不代表运行环境连接、应用部署或平台安装已经可执行。用户首次提出需要运行环境的动作时,对应业务 Skill 才检查 Node.js;固定 Rainskills launcher 需要 Node.js 18 或更高版本。缺失或版本过低时保留原始 intent 并停止,等待用户或 agent 明确同意安装或升级 Node.js,安装完成消息不得提前提示 Node.js。
|
|
20
20
|
|
|
@@ -38,7 +38,7 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
38
38
|
|
|
39
39
|
## Manage Runtime Environments
|
|
40
40
|
|
|
41
|
-
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `
|
|
41
|
+
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `node <home>/.rainbond/lib/rainskills/bin/rainskills.js`(运行包版本 `rainskills@0.1.9`)。
|
|
42
42
|
|
|
43
43
|
- 列表:用本地 launcher 执行 `environment list --json`,只展示名称、类型、状态、是否默认和最近验证时间。
|
|
44
44
|
- 重命名:用本地 launcher 执行 `environment rename --environment-id <uuid> --name <name>`。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rainskills",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Interactive Rainbond skill installer for Codex and
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "Interactive Rainbond skill installer for Codex, Claude Code, and Pi Agent",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/goodrain/rainskills#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -98,17 +98,20 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
98
98
|
|
|
99
99
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
100
100
|
|
|
101
|
-
固定 launcher 是 `["
|
|
101
|
+
固定 launcher 是 `["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"]`;运行包版本标记为 `rainskills@0.1.9`,且必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止使用 `rainskills@latest`,禁止拼接或执行 shell 字符串。
|
|
102
102
|
|
|
103
|
-
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm
|
|
103
|
+
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接或安装运行环境后,才使用上面的固定本地 launcher。
|
|
104
104
|
|
|
105
105
|
所有 Rainbond 查询和变更必须通过本地 `~/.rainbond/bin/rainskills-tools.js` 执行,并绑定本次 operation ID。禁止 Agent 直接调用 Rainbond MCP,也不得启动本地 Rainskills MCP 服务;只允许执行 CLI 返回的结构化结果与确认续接 argv。
|
|
106
106
|
|
|
107
|
+
只有 CLI 返回并通过校验的 `rainskills.next-action.v1` argv 才能执行续接。普通失败一律禁止自动重试:不得再次执行原命令,不得执行 `--help`、`sleep`、`rg`、`grep`,不得搜索 Rainskills 源码;同一 `operation complete` 最多执行一次。
|
|
108
|
+
|
|
107
109
|
<!-- rainskills-runtime-contract:start -->
|
|
108
110
|
```json
|
|
109
111
|
{
|
|
110
112
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
111
|
-
"
|
|
113
|
+
"package_version": "rainskills@0.1.9",
|
|
114
|
+
"launcher": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js"],
|
|
112
115
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
113
116
|
"local_argv": {
|
|
114
117
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -128,15 +131,15 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
128
131
|
"existing": ["saas", "private-existing"]
|
|
129
132
|
},
|
|
130
133
|
"connect_argv": {
|
|
131
|
-
"saas": ["
|
|
132
|
-
"private-existing": ["
|
|
133
|
-
"install-private": ["
|
|
134
|
+
"saas": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
135
|
+
"private-existing": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"],
|
|
136
|
+
"install-private": ["node", "<home>/.rainbond/lib/rainskills/bin/rainskills.js", "runtime", "connect", "<target>", "--install-private", "--location", "<private-location>", "--intent-json", "<intent-json>"]
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
```
|
|
137
140
|
<!-- rainskills-runtime-contract:end -->
|
|
138
141
|
|
|
139
|
-
`deploy`/`create` 含 `service_id` 时属于 existing,否则属于 new;`query`、`troubleshoot`、`modify` 始终属于 existing。意图不明确时先澄清,不执行 connect。target 只允许 `codex`、`claude`、`all`。连接前校验 intent,只执行其 scope 在契约中允许的完整 argv。只消费 schema 为 `rainskills.next-action.v1` 且完成字段校验后的 `argv` 数组。
|
|
142
|
+
`deploy`/`create` 含 `service_id` 时属于 existing,否则属于 new;`query`、`troubleshoot`、`modify` 始终属于 existing。意图不明确时先澄清,不执行 connect。target 只允许 `codex`、`claude`、`pi`、`all`。连接前校验 intent,只执行其 scope 在契约中允许的完整 argv。只消费 schema 为 `rainskills.next-action.v1` 且完成字段校验后的 `argv` 数组。
|
|
140
143
|
|
|
141
144
|
连接或平台安装完成后,用固定 `onboarding-id` 执行 launcher + `["intent", "resume", "--onboarding-id", "<同一 onboarding-id>"]`,恢复原始 intent 和 `resume_step`,不得重新猜测动作。
|
|
142
145
|
|