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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rainskills
2
2
 
3
- Rainskills 是一组面向应用识别、部署、排障和交付的 AI Skills,支持 Codex 和 Claude Code。用户在市场中只会看到一个 `Rainskills` 产品,安装后 10 个 `rainbond-*` Skill 仍会独立触发。
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 和 Claude Code;macOS、Linux 和 WSL 不支持 OpenClaw 安装。不要手工复制 Skill、拼接凭据或修改 Agent 配置。
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
- ["npx", "--yes", "rainskills@0.1.8-rc.2"]
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
- npx --yes rainskills@0.1.8-rc.2 platform install --onboarding-id <id> \
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
- npx --yes rainskills@0.1.8-rc.2 platform install --onboarding-id <id> \
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
- npx --yes rainskills@0.1.8-rc.2 platform install --onboarding-id <id> \
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
- npx --yes rainskills@0.1.8-rc.2 platform install --onboarding-id <id> \
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.8-rc.2`,不会执行浮动的 `@latest` 业务代码。新版本原子刷新已经安装的 Rainskills Skills;当前业务继续使用启动时已经加载的版本,最迟从下一条新任务开始使用新版。npm 超时、版本检查失败、安装位置不安全或文件迁移失败时会保留旧版本,且不会阻塞或改变当前操作。
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` or Claude Code=`claude`. 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.
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 `npx --yes rainskills@0.1.8-rc.2`。
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>`。
@@ -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)) return value.map(sanitizeToolResult);
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
- if (key !== "confirmation_token" && SENSITIVE_RESPONSE_KEY_PATTERN.test(key)) continue;
589
- safe[key] = sanitizeToolResult(item);
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 rpcRequest(config, method, params = {}, {
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(/\/+$/, "")}${ENDPOINT_PATH}`);
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
- if (response.statusCode === 401 || response.statusCode === 403) {
698
- fail(new BridgeError("Rainbond authentication failed", EXIT.CONFIG));
699
- return;
700
- }
701
- if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300) {
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
- all Install and configure Codex and Claude Code
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 或 Claude Code。"
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-3]: '
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 或 3。"
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 cli_api_url
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 ! validate_mcp_connectivity "$cli_api_url" "$token"; then
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 cli_api_url
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 ! validate_mcp_connectivity "$cli_api_url" "$token"; then
2585
+ if ! validate_runtime_mcp_connectivity "$base_url" "$token"; then
2559
2586
  die "Rainbond CLI 接口校验失败。"
2560
2587
  fi
2561
2588
  token="$VALIDATED_TOKEN"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rainskills",
3
- "version": "0.1.8-rc.2",
3
+ "version": "0.1.9",
4
4
  "description": "Install the complete Rainskills AI deployment skill suite as one product.",
5
5
  "author": {
6
6
  "name": "Goodrain",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rainskills",
3
- "version": "0.1.8-rc.2",
3
+ "version": "0.1.9",
4
4
  "description": "Install the complete Rainskills AI deployment skill suite as one product.",
5
5
  "author": {
6
6
  "name": "Goodrain",
@@ -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` or Claude Code=`claude`. 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.
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.8-rc.2 <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.
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 `npx --yes rainskills@0.1.8-rc.2`。
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.8-rc.2",
4
- "description": "Interactive Rainbond skill installer for Codex and Claude Code",
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 是 `["npx", "--yes", "rainskills@0.1.8-rc.2"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止使用 `rainskills@latest`,禁止拼接或执行 shell 字符串。
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 或其它网络。只有用户选定连接或安装运行环境后,才使用上面的固定 npx launcher。
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
- "launcher": ["npx", "--yes", "rainskills@0.1.8-rc.2"],
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": ["npx", "--yes", "rainskills@0.1.8-rc.2", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
132
- "private-existing": ["npx", "--yes", "rainskills@0.1.8-rc.2", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"],
133
- "install-private": ["npx", "--yes", "rainskills@0.1.8-rc.2", "runtime", "connect", "<target>", "--install-private", "--location", "<private-location>", "--intent-json", "<intent-json>"]
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