rainskills 0.1.3 → 0.1.5
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 +5 -5
- package/SKILL.md +1 -1
- package/bin/rainskills.js +16 -1
- 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 +2 -2
- package/package.json +1 -1
- package/pi/rainskills-mcp.ts +1 -1
- package/rainbond-app-assistant/SKILL.md +5 -5
- package/rainbond-app-version-assistant/SKILL.md +4 -4
- package/rainbond-delivery-verifier/SKILL.md +4 -4
- package/rainbond-env-sync/SKILL.md +4 -4
- package/rainbond-fullstack-bootstrap/SKILL.md +5 -5
- package/rainbond-fullstack-troubleshooter/SKILL.md +4 -4
- package/rainbond-platform-installer/SKILL.md +1 -1
- package/rainbond-platform-installer/scripts/auto-update-worker.js +9 -16
- package/rainbond-platform-installer/scripts/auto-update.js +441 -27
- package/rainbond-platform-installer/scripts/installed-version.js +1 -1
- package/rainbond-platform-query/SKILL.md +4 -4
- package/rainbond-project-init/SKILL.md +5 -5
- package/rainbond-template-installer/SKILL.md +5 -5
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ Rainskills 安装完成,下一条消息即可直接使用。
|
|
|
72
72
|
固定 launcher 来自当前 `package.json` 版本:
|
|
73
73
|
|
|
74
74
|
```json
|
|
75
|
-
["npx", "--yes", "rainskills@0.1.
|
|
75
|
+
["npx", "--yes", "rainskills@0.1.5"]
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
所有调用都把 launcher 与参数合并成 argv 数组后直接执行;不得使用 `rainskills@latest`,不得把参数拼成 shell 字符串。
|
|
@@ -192,21 +192,21 @@ Windows 本地安装是预览能力,也可以改选 Linux 服务器。它要
|
|
|
192
192
|
|
|
193
193
|
```bash
|
|
194
194
|
# 本地单机
|
|
195
|
-
npx --yes rainskills@0.1.
|
|
195
|
+
npx --yes rainskills@0.1.5 platform install --onboarding-id <id> \
|
|
196
196
|
--location local --mode single-node
|
|
197
197
|
|
|
198
198
|
# Linux 服务器单机
|
|
199
|
-
npx --yes rainskills@0.1.
|
|
199
|
+
npx --yes rainskills@0.1.5 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.
|
|
203
|
+
npx --yes rainskills@0.1.5 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.
|
|
209
|
+
npx --yes rainskills@0.1.5 platform install --onboarding-id <id> \
|
|
210
210
|
--location server --mode existing-kubernetes \
|
|
211
211
|
--kubeconfig <path> --kube-context <name> --chart-version <version>
|
|
212
212
|
```
|
package/SKILL.md
CHANGED
|
@@ -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.
|
|
41
|
+
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `npx --yes rainskills@0.1.5`。
|
|
42
42
|
|
|
43
43
|
- 列表:用本地 launcher 执行 `environment list --json`,只展示名称、类型、状态、是否默认和最近验证时间。
|
|
44
44
|
- 重命名:用本地 launcher 执行 `environment rename --environment-id <uuid> --name <name>`。
|
package/bin/rainskills.js
CHANGED
|
@@ -1046,6 +1046,7 @@ async function runAutoUpdatePhase(args, {
|
|
|
1046
1046
|
platform = process.platform,
|
|
1047
1047
|
packageRoot = path.resolve(__dirname, ".."),
|
|
1048
1048
|
checkForUpdate,
|
|
1049
|
+
acquireArtifact,
|
|
1049
1050
|
synchronizeSkills,
|
|
1050
1051
|
updateState,
|
|
1051
1052
|
delegate,
|
|
@@ -1087,6 +1088,7 @@ async function runAutoUpdatePhase(args, {
|
|
|
1087
1088
|
}
|
|
1088
1089
|
}
|
|
1089
1090
|
let lease = null;
|
|
1091
|
+
let artifact = null;
|
|
1090
1092
|
try {
|
|
1091
1093
|
if (autoUpdate.isStableVersion(currentVersion) && autoUpdate.isSafeAutoUpdateEntry(args)) {
|
|
1092
1094
|
lease = getState().acquireLease?.() || null;
|
|
@@ -1101,15 +1103,27 @@ async function runAutoUpdatePhase(args, {
|
|
|
1101
1103
|
env,
|
|
1102
1104
|
home,
|
|
1103
1105
|
platform,
|
|
1106
|
+
...(activeOperationDetector ? { activeOperationDetector } : {}),
|
|
1104
1107
|
...(state ? { updateState: state } : {}),
|
|
1105
1108
|
});
|
|
1106
1109
|
if (decision.action !== "delegate") {
|
|
1107
1110
|
return { handled: false, reason: decision.reason };
|
|
1108
1111
|
}
|
|
1109
|
-
|
|
1112
|
+
artifact = await (acquireArtifact || autoUpdate.acquireStableUpdateArtifact)(decision, {
|
|
1113
|
+
home,
|
|
1114
|
+
platform,
|
|
1115
|
+
});
|
|
1116
|
+
const detectActive = activeOperationDetector
|
|
1117
|
+
|| (() => autoUpdate.hasActiveOperation({ home, platform }));
|
|
1118
|
+
if (detectActive()) return { handled: false, reason: "active-operation" };
|
|
1119
|
+
const invocation = autoUpdate.buildStableUpdateInvocation(decision, args, {
|
|
1120
|
+
platform,
|
|
1121
|
+
artifactPath: artifact.path,
|
|
1122
|
+
});
|
|
1110
1123
|
const environment = autoUpdate.buildStableUpdateEnvironment(env, {
|
|
1111
1124
|
fromVersion: currentVersion,
|
|
1112
1125
|
targetVersion: decision.version,
|
|
1126
|
+
registry: decision.registry,
|
|
1113
1127
|
});
|
|
1114
1128
|
let result;
|
|
1115
1129
|
try {
|
|
@@ -1132,6 +1146,7 @@ async function runAutoUpdatePhase(args, {
|
|
|
1132
1146
|
signal: result.signal || null,
|
|
1133
1147
|
};
|
|
1134
1148
|
} finally {
|
|
1149
|
+
try { artifact?.cleanup(); } catch { /* protected cleanup is best effort */ }
|
|
1135
1150
|
lease?.release();
|
|
1136
1151
|
}
|
|
1137
1152
|
}
|
|
@@ -14,7 +14,7 @@ This is the single marketplace entry for the complete Rainskills product. The in
|
|
|
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.5 <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.
|
|
41
|
+
环境是全局列表,不是项目绑定。本地状态命令必须定位已安装的同级 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后通过 `node` 以 argv 数组执行;不得为了查询本地环境访问 npm。只有连接或安装环境时才使用与当前技能包一致的固定 launcher `npx --yes rainskills@0.1.5`。
|
|
42
42
|
|
|
43
43
|
- 列表:用本地 launcher 执行 `environment list --json`,只展示名称、类型、状态、是否默认和最近验证时间。
|
|
44
44
|
- 重命名:用本地 launcher 执行 `environment rename --environment-id <uuid> --name <name>`。
|
package/package.json
CHANGED
package/pi/rainskills-mcp.ts
CHANGED
|
@@ -15500,7 +15500,7 @@ function serializeMessage(message) {
|
|
|
15500
15500
|
// package.json
|
|
15501
15501
|
var package_default = {
|
|
15502
15502
|
name: "rainskills",
|
|
15503
|
-
version: "0.1.
|
|
15503
|
+
version: "0.1.5",
|
|
15504
15504
|
description: "Interactive Rainbond skill installer for Codex and Claude Code",
|
|
15505
15505
|
license: "Apache-2.0",
|
|
15506
15506
|
homepage: "https://github.com/goodrain/rainskills#readme",
|
|
@@ -98,7 +98,7 @@ 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.
|
|
101
|
+
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.5"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止使用 `rainskills@latest`,禁止拼接或执行 shell 字符串。
|
|
102
102
|
|
|
103
103
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接或安装运行环境后,才使用上面的固定 npx launcher。
|
|
104
104
|
|
|
@@ -106,7 +106,7 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
106
106
|
```json
|
|
107
107
|
{
|
|
108
108
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
109
|
-
"launcher": ["npx", "--yes", "rainskills@0.1.
|
|
109
|
+
"launcher": ["npx", "--yes", "rainskills@0.1.5"],
|
|
110
110
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
111
111
|
"local_argv": {
|
|
112
112
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -126,9 +126,9 @@ description: "Use whenever a user asks to deploy, run, deliver, publish, inspect
|
|
|
126
126
|
"existing": ["saas", "private-existing"]
|
|
127
127
|
},
|
|
128
128
|
"connect_argv": {
|
|
129
|
-
"saas": ["npx", "--yes", "rainskills@0.1.
|
|
130
|
-
"private-existing": ["npx", "--yes", "rainskills@0.1.
|
|
131
|
-
"install-private": ["npx", "--yes", "rainskills@0.1.
|
|
129
|
+
"saas": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
130
|
+
"private-existing": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"],
|
|
131
|
+
"install-private": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--install-private", "--location", "<private-location>", "--intent-json", "<intent-json>"]
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
```
|
|
@@ -14,7 +14,7 @@ description: "Use when a user explicitly asks for an existing Rainbond app versi
|
|
|
14
14
|
|
|
15
15
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
16
16
|
|
|
17
|
-
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.
|
|
17
|
+
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.5"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
18
18
|
|
|
19
19
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接运行环境后,才使用上面的固定 npx launcher。
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ description: "Use when a user explicitly asks for an existing Rainbond app versi
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
24
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
25
|
-
"launcher": ["npx", "--yes", "rainskills@0.1.
|
|
25
|
+
"launcher": ["npx", "--yes", "rainskills@0.1.5"],
|
|
26
26
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
27
27
|
"local_argv": {
|
|
28
28
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -37,8 +37,8 @@ description: "Use when a user explicitly asks for an existing Rainbond app versi
|
|
|
37
37
|
},
|
|
38
38
|
"routes": {"existing": ["saas", "private-existing"]},
|
|
39
39
|
"connect_argv": {
|
|
40
|
-
"saas": ["npx", "--yes", "rainskills@0.1.
|
|
41
|
-
"private-existing": ["npx", "--yes", "rainskills@0.1.
|
|
40
|
+
"saas": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
41
|
+
"private-existing": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"]
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
```
|
|
@@ -46,7 +46,7 @@ description: "Use only when the user explicitly asks for final delivery or acces
|
|
|
46
46
|
|
|
47
47
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
48
48
|
|
|
49
|
-
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.
|
|
49
|
+
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.5"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
50
50
|
|
|
51
51
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接运行环境后,才使用上面的固定 npx launcher。
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@ description: "Use only when the user explicitly asks for final delivery or acces
|
|
|
54
54
|
```json
|
|
55
55
|
{
|
|
56
56
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
57
|
-
"launcher": ["npx", "--yes", "rainskills@0.1.
|
|
57
|
+
"launcher": ["npx", "--yes", "rainskills@0.1.5"],
|
|
58
58
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
59
59
|
"local_argv": {
|
|
60
60
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -67,8 +67,8 @@ description: "Use only when the user explicitly asks for final delivery or acces
|
|
|
67
67
|
},
|
|
68
68
|
"routes": {"existing": ["saas", "private-existing"]},
|
|
69
69
|
"connect_argv": {
|
|
70
|
-
"saas": ["npx", "--yes", "rainskills@0.1.
|
|
71
|
-
"private-existing": ["npx", "--yes", "rainskills@0.1.
|
|
70
|
+
"saas": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
71
|
+
"private-existing": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"]
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
```
|
|
@@ -14,7 +14,7 @@ description: "Use when a user explicitly asks to sync non-sensitive preview or p
|
|
|
14
14
|
|
|
15
15
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
16
16
|
|
|
17
|
-
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.
|
|
17
|
+
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.5"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
18
18
|
|
|
19
19
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接运行环境后,才使用上面的固定 npx launcher。
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ description: "Use when a user explicitly asks to sync non-sensitive preview or p
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
24
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
25
|
-
"launcher": ["npx", "--yes", "rainskills@0.1.
|
|
25
|
+
"launcher": ["npx", "--yes", "rainskills@0.1.5"],
|
|
26
26
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
27
27
|
"local_argv": {
|
|
28
28
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -35,8 +35,8 @@ description: "Use when a user explicitly asks to sync non-sensitive preview or p
|
|
|
35
35
|
},
|
|
36
36
|
"routes": {"existing": ["saas", "private-existing"]},
|
|
37
37
|
"connect_argv": {
|
|
38
|
-
"saas": ["npx", "--yes", "rainskills@0.1.
|
|
39
|
-
"private-existing": ["npx", "--yes", "rainskills@0.1.
|
|
38
|
+
"saas": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
39
|
+
"private-existing": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"]
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
```
|
|
@@ -14,7 +14,7 @@ description: "Use only when the user explicitly asks to create the Rainbond app
|
|
|
14
14
|
|
|
15
15
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
16
16
|
|
|
17
|
-
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.
|
|
17
|
+
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.5"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
18
18
|
|
|
19
19
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接或安装运行环境后,才使用上面的固定 npx launcher。
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ description: "Use only when the user explicitly asks to create the Rainbond app
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
24
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
25
|
-
"launcher": ["npx", "--yes", "rainskills@0.1.
|
|
25
|
+
"launcher": ["npx", "--yes", "rainskills@0.1.5"],
|
|
26
26
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
27
27
|
"local_argv": {
|
|
28
28
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -42,9 +42,9 @@ description: "Use only when the user explicitly asks to create the Rainbond app
|
|
|
42
42
|
"existing": ["saas", "private-existing"]
|
|
43
43
|
},
|
|
44
44
|
"connect_argv": {
|
|
45
|
-
"saas": ["npx", "--yes", "rainskills@0.1.
|
|
46
|
-
"private-existing": ["npx", "--yes", "rainskills@0.1.
|
|
47
|
-
"install-private": ["npx", "--yes", "rainskills@0.1.
|
|
45
|
+
"saas": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
46
|
+
"private-existing": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"],
|
|
47
|
+
"install-private": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--install-private", "--location", "<private-location>", "--intent-json", "<intent-json>"]
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
```
|
|
@@ -46,7 +46,7 @@ description: "Use only when the user explicitly asks for a bounded build, runtim
|
|
|
46
46
|
|
|
47
47
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
48
48
|
|
|
49
|
-
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.
|
|
49
|
+
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.5"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
50
50
|
|
|
51
51
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接运行环境后,才使用上面的固定 npx launcher。
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@ description: "Use only when the user explicitly asks for a bounded build, runtim
|
|
|
54
54
|
```json
|
|
55
55
|
{
|
|
56
56
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
57
|
-
"launcher": ["npx", "--yes", "rainskills@0.1.
|
|
57
|
+
"launcher": ["npx", "--yes", "rainskills@0.1.5"],
|
|
58
58
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
59
59
|
"local_argv": {
|
|
60
60
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -67,8 +67,8 @@ description: "Use only when the user explicitly asks for a bounded build, runtim
|
|
|
67
67
|
},
|
|
68
68
|
"routes": {"existing": ["saas", "private-existing"]},
|
|
69
69
|
"connect_argv": {
|
|
70
|
-
"saas": ["npx", "--yes", "rainskills@0.1.
|
|
71
|
-
"private-existing": ["npx", "--yes", "rainskills@0.1.
|
|
70
|
+
"saas": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
71
|
+
"private-existing": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"]
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
```
|
|
@@ -31,7 +31,7 @@ Do not use it to deploy an application to an existing Rainbond. Route those requ
|
|
|
31
31
|
## Workflow
|
|
32
32
|
|
|
33
33
|
1. Read [installation-policy.md](references/installation-policy.md).
|
|
34
|
-
2. Use the package-version launcher `["npx", "--yes", "rainskills@0.1.
|
|
34
|
+
2. Use the package-version launcher `["npx", "--yes", "rainskills@0.1.5"]`; its version must equal this package's `package.json`. For a Rainskills marker, first validate schema `rainskills.next-action.v1`, action, onboarding id, and the bounded `argv` array, then append that array to the launcher. Never use `latest` or evaluate a shell string from output.
|
|
35
35
|
3. 在任何平台安装命令之前,先执行 launcher + `["runtime", "message", "--id", "private-deployment-location"]`,并原样输出固定的三项部署位置消息。选择 1 后执行带 `["--location", "local", "--mode", "single-node"]` 的 `platform install`;选择 2 后执行带 `["--location", "server"]` 的 `platform install`,由 helper 继续显示固定的服务器类型消息;选择 3 后执行 launcher + `["runtime", "message", "--id", "private-console-origin"]` 并进入已有环境连接,不得执行 `platform install`。若是没有原始业务 intent 的直接平台安装请求,已有环境连接使用受限 `{"type":"environment-add"}` intent。
|
|
36
36
|
4. Let the helper perform one read-only preflight against the already selected local or remote target, then show resources, blockers, and applicable host changes. Never invoke `platform install` without an explicit `--location`; the helper must not ask for the deployment location again.
|
|
37
37
|
5. Obtain explicit confirmation before sending `y` to the waiting process or rerunning the exact command with `--yes`.
|
|
@@ -9,18 +9,8 @@ const { version: installedVersion } = require("./installed-version.js");
|
|
|
9
9
|
|
|
10
10
|
const UPDATE_ENTRY = Object.freeze(["runtime", "status", "--json"]);
|
|
11
11
|
const DEFAULT_DELEGATE_TIMEOUT_MS = 30_000;
|
|
12
|
-
const SAFE_ENVIRONMENT_KEYS = Object.freeze([
|
|
13
|
-
"HOME", "USERPROFILE", "PATH", "Path", "SystemRoot", "ComSpec",
|
|
14
|
-
"TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL", "HTTP_PROXY",
|
|
15
|
-
"HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy",
|
|
16
|
-
]);
|
|
17
|
-
|
|
18
12
|
function backgroundUpdateEnvironment(source = process.env) {
|
|
19
|
-
|
|
20
|
-
for (const key of SAFE_ENVIRONMENT_KEYS) {
|
|
21
|
-
if (typeof source[key] === "string") result[key] = source[key];
|
|
22
|
-
}
|
|
23
|
-
return result;
|
|
13
|
+
return autoUpdate.sanitizeAutoUpdateEnvironment(source);
|
|
24
14
|
}
|
|
25
15
|
|
|
26
16
|
function terminateChild(child, platform, signal = "SIGTERM") {
|
|
@@ -87,6 +77,7 @@ async function runBackgroundAutoUpdate({
|
|
|
87
77
|
platform = process.platform,
|
|
88
78
|
updateState = autoUpdate.createAutoUpdateState({ home, platform }),
|
|
89
79
|
checkForUpdate = autoUpdate.checkForStableUpdate,
|
|
80
|
+
acquireArtifact = autoUpdate.acquireStableUpdateArtifact,
|
|
90
81
|
activeOperationDetector = () => autoUpdate.hasActiveOperation({ home, platform }),
|
|
91
82
|
runDelegated = (invocation, environment) => runBoundedChild(
|
|
92
83
|
invocation,
|
|
@@ -95,6 +86,7 @@ async function runBackgroundAutoUpdate({
|
|
|
95
86
|
),
|
|
96
87
|
} = {}) {
|
|
97
88
|
let lease;
|
|
89
|
+
let artifact;
|
|
98
90
|
try {
|
|
99
91
|
lease = updateState.acquireLease?.() || null;
|
|
100
92
|
} catch {
|
|
@@ -111,17 +103,17 @@ async function runBackgroundAutoUpdate({
|
|
|
111
103
|
updateState,
|
|
112
104
|
});
|
|
113
105
|
if (decision.action !== "delegate") return decision;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
106
|
+
artifact = await acquireArtifact(decision, { home, platform });
|
|
107
|
+
if (activeOperationDetector()) return { action: "continue", reason: "active-operation" };
|
|
117
108
|
const invocation = autoUpdate.buildStableUpdateInvocation(
|
|
118
|
-
decision
|
|
109
|
+
decision,
|
|
119
110
|
UPDATE_ENTRY,
|
|
120
|
-
{ platform }
|
|
111
|
+
{ platform, artifactPath: artifact.path }
|
|
121
112
|
);
|
|
122
113
|
const environment = autoUpdate.buildStableUpdateEnvironment(env, {
|
|
123
114
|
fromVersion: currentVersion,
|
|
124
115
|
targetVersion: decision.version,
|
|
116
|
+
registry: decision.registry,
|
|
125
117
|
});
|
|
126
118
|
const result = await runDelegated(invocation, environment);
|
|
127
119
|
if (result.code !== 0 || result.signal) {
|
|
@@ -133,6 +125,7 @@ async function runBackgroundAutoUpdate({
|
|
|
133
125
|
try { updateState.recordFailure?.(); } catch { /* best effort */ }
|
|
134
126
|
return { action: "continue", reason: "update-failed" };
|
|
135
127
|
} finally {
|
|
128
|
+
try { artifact?.cleanup(); } catch { /* protected cleanup is best effort */ }
|
|
136
129
|
lease?.release();
|
|
137
130
|
}
|
|
138
131
|
}
|
|
@@ -5,16 +5,41 @@ const fs = require("node:fs");
|
|
|
5
5
|
const https = require("node:https");
|
|
6
6
|
const os = require("node:os");
|
|
7
7
|
const path = require("node:path");
|
|
8
|
+
const tls = require("node:tls");
|
|
8
9
|
|
|
9
10
|
const { createSecureStateStore } = require("./secure-state.js");
|
|
10
11
|
|
|
11
12
|
const OFFICIAL_LATEST_URL = "https://registry.npmjs.org/rainskills/latest";
|
|
12
13
|
const OFFICIAL_REGISTRY = "https://registry.npmjs.org/";
|
|
14
|
+
const MIRROR_REGISTRY = "https://registry.npmmirror.com/";
|
|
15
|
+
const REGISTRY_SOURCES = Object.freeze({
|
|
16
|
+
official: Object.freeze({
|
|
17
|
+
name: "official",
|
|
18
|
+
registry: OFFICIAL_REGISTRY,
|
|
19
|
+
latestUrl: OFFICIAL_LATEST_URL,
|
|
20
|
+
tarballOrigin: OFFICIAL_REGISTRY,
|
|
21
|
+
}),
|
|
22
|
+
mirror: Object.freeze({
|
|
23
|
+
name: "mirror",
|
|
24
|
+
registry: MIRROR_REGISTRY,
|
|
25
|
+
latestUrl: `${MIRROR_REGISTRY}rainskills/latest`,
|
|
26
|
+
tarballOrigin: "https://cdn.npmmirror.com/",
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
13
29
|
const UPDATE_STATE_SCHEMA = "rainskills.auto-update.v1";
|
|
14
30
|
const UPDATE_STATE_VERSION = 1;
|
|
15
31
|
const UPDATE_OPERATION_ID = "b53d72d4-5e88-4aae-9a9d-8305142d0262";
|
|
16
32
|
const DEFAULT_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
17
33
|
const MAX_METADATA_BYTES = 64 * 1024;
|
|
34
|
+
const MAX_TARBALL_BYTES = 16 * 1024 * 1024;
|
|
35
|
+
const METADATA_TIMEOUT_MS = 3000;
|
|
36
|
+
const TARBALL_TIMEOUT_MS = 15_000;
|
|
37
|
+
const INTERNAL_TIMEOUT_CODE = "RAINSKILLS_REQUEST_TIMEOUT";
|
|
38
|
+
const PROTOCOL_ERROR_CODE = "RAINSKILLS_PROTOCOL_ERROR";
|
|
39
|
+
const METADATA_FALLBACK_ERROR_CODES = Object.freeze([
|
|
40
|
+
"ENOTFOUND", "EAI_AGAIN", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT",
|
|
41
|
+
"EHOSTUNREACH", "ENETUNREACH",
|
|
42
|
+
]);
|
|
18
43
|
const STABLE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
19
44
|
const SAFE_ENTRY = Object.freeze(["runtime", "status", "--json"]);
|
|
20
45
|
const DEFAULT_AGENT_ROOTS = Object.freeze([
|
|
@@ -22,6 +47,23 @@ const DEFAULT_AGENT_ROOTS = Object.freeze([
|
|
|
22
47
|
[".claude", "skills"],
|
|
23
48
|
[".agents", "skills"],
|
|
24
49
|
]);
|
|
50
|
+
const AUTO_UPDATE_ENVIRONMENT_KEYS = Object.freeze([
|
|
51
|
+
"HOME", "USERPROFILE", "PATH", "Path", "SystemRoot", "ComSpec",
|
|
52
|
+
"TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL",
|
|
53
|
+
"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy",
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
function protocolError(message) {
|
|
57
|
+
const error = new Error(message);
|
|
58
|
+
error.code = PROTOCOL_ERROR_CODE;
|
|
59
|
+
return error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function timeoutError(label) {
|
|
63
|
+
const error = new Error(`${label}请求超时`);
|
|
64
|
+
error.code = INTERNAL_TIMEOUT_CODE;
|
|
65
|
+
return error;
|
|
66
|
+
}
|
|
25
67
|
|
|
26
68
|
function isStableVersion(value) {
|
|
27
69
|
return typeof value === "string" && STABLE_VERSION_PATTERN.test(value);
|
|
@@ -48,36 +90,85 @@ function isSafeAutoUpdateEntry(args) {
|
|
|
48
90
|
&& args.every((value, index) => value === SAFE_ENTRY[index]);
|
|
49
91
|
}
|
|
50
92
|
|
|
51
|
-
function
|
|
93
|
+
function tarballUrlFor(source, version) {
|
|
94
|
+
if (source === REGISTRY_SOURCES.mirror) {
|
|
95
|
+
return `${source.tarballOrigin}packages/rainskills/${version}/rainskills-${version}.tgz`;
|
|
96
|
+
}
|
|
97
|
+
return `${source.tarballOrigin}rainskills/-/rainskills-${version}.tgz`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function metadataTarballUrlFor(source, version) {
|
|
101
|
+
return `${source.registry}rainskills/-/rainskills-${version}.tgz`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function sourceForRegistry(registry) {
|
|
105
|
+
return Object.values(REGISTRY_SOURCES).find((source) => source.registry === registry) || null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function validateRegistryLatestMetadata(metadata, source) {
|
|
109
|
+
if (!Object.values(REGISTRY_SOURCES).includes(source)) {
|
|
110
|
+
throw protocolError("npm registry 来源无效");
|
|
111
|
+
}
|
|
52
112
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
53
|
-
throw
|
|
113
|
+
throw protocolError("npm latest metadata 无效");
|
|
54
114
|
}
|
|
55
115
|
if (metadata.name !== "rainskills" || !isStableVersion(metadata.version)) {
|
|
56
|
-
throw
|
|
116
|
+
throw protocolError("npm latest 不是 Rainskills 正式版");
|
|
57
117
|
}
|
|
58
|
-
const
|
|
118
|
+
const expectedMetadataTarball = metadataTarballUrlFor(source, metadata.version);
|
|
119
|
+
const downloadTarball = tarballUrlFor(source, metadata.version);
|
|
59
120
|
if (
|
|
60
121
|
!metadata.dist
|
|
61
122
|
|| typeof metadata.dist !== "object"
|
|
62
123
|
|| typeof metadata.dist.integrity !== "string"
|
|
63
124
|
|| !/^sha512-[A-Za-z0-9+/]{86}==$/.test(metadata.dist.integrity)
|
|
64
|
-
|| metadata.dist.tarball !==
|
|
125
|
+
|| metadata.dist.tarball !== expectedMetadataTarball
|
|
65
126
|
) {
|
|
66
|
-
throw
|
|
127
|
+
throw protocolError("npm latest 制品来源或完整性信息无效");
|
|
67
128
|
}
|
|
68
|
-
return {
|
|
129
|
+
return {
|
|
130
|
+
version: metadata.version,
|
|
131
|
+
registry: source.registry,
|
|
132
|
+
tarball: downloadTarball,
|
|
133
|
+
integrity: metadata.dist.integrity,
|
|
134
|
+
};
|
|
69
135
|
}
|
|
70
136
|
|
|
71
|
-
function
|
|
137
|
+
function validateOfficialLatestMetadata(metadata) {
|
|
138
|
+
return validateRegistryLatestMetadata(metadata, REGISTRY_SOURCES.official);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function validateContentLength(headers, maxBytes, { allowZero, label }) {
|
|
142
|
+
const raw = headers && headers["content-length"];
|
|
143
|
+
if (raw === undefined) return;
|
|
144
|
+
if (typeof raw !== "string" || !/^(0|[1-9]\d*)$/.test(raw)) {
|
|
145
|
+
throw protocolError(`${label} Content-Length 无效`);
|
|
146
|
+
}
|
|
147
|
+
const value = Number(raw);
|
|
148
|
+
if ((!allowZero && value === 0) || !Number.isSafeInteger(value) || value > maxBytes) {
|
|
149
|
+
throw protocolError(`${label} Content-Length 超出限制`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function fixedHttpsOptions(headers) {
|
|
154
|
+
return {
|
|
155
|
+
headers,
|
|
156
|
+
rejectUnauthorized: true,
|
|
157
|
+
checkServerIdentity: tls.checkServerIdentity,
|
|
158
|
+
ca: tls.rootCertificates,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function fetchRegistryMetadata(source, {
|
|
72
163
|
request = https.get,
|
|
73
|
-
timeoutMs =
|
|
164
|
+
timeoutMs = METADATA_TIMEOUT_MS,
|
|
74
165
|
maxBytes = MAX_METADATA_BYTES,
|
|
75
166
|
} = {}) {
|
|
76
167
|
return new Promise((resolve, reject) => {
|
|
77
168
|
let settled = false;
|
|
78
169
|
let req;
|
|
79
170
|
const deadline = setTimeout(() => {
|
|
80
|
-
req?.destroy(
|
|
171
|
+
req?.destroy(timeoutError("npm latest "));
|
|
81
172
|
}, timeoutMs);
|
|
82
173
|
const finish = (error, value) => {
|
|
83
174
|
if (settled) return;
|
|
@@ -86,12 +177,17 @@ function fetchOfficialLatest({
|
|
|
86
177
|
if (error) reject(error);
|
|
87
178
|
else resolve(value);
|
|
88
179
|
};
|
|
89
|
-
req = request(
|
|
90
|
-
headers: { accept: "application/json" },
|
|
91
|
-
}, (response) => {
|
|
180
|
+
req = request(source.latestUrl, fixedHttpsOptions({ accept: "application/json" }), (response) => {
|
|
92
181
|
if (response.statusCode !== 200 || response.headers.location) {
|
|
93
182
|
response.resume();
|
|
94
|
-
finish(
|
|
183
|
+
finish(protocolError("npm latest 响应无效"));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
validateContentLength(response.headers, maxBytes, { allowZero: true, label: "npm latest" });
|
|
188
|
+
} catch (error) {
|
|
189
|
+
response.resume();
|
|
190
|
+
finish(error);
|
|
95
191
|
return;
|
|
96
192
|
}
|
|
97
193
|
let size = 0;
|
|
@@ -99,7 +195,7 @@ function fetchOfficialLatest({
|
|
|
99
195
|
response.on("data", (chunk) => {
|
|
100
196
|
size += chunk.length;
|
|
101
197
|
if (size > maxBytes) {
|
|
102
|
-
req.destroy(
|
|
198
|
+
req.destroy(protocolError("npm latest 响应过大"));
|
|
103
199
|
return;
|
|
104
200
|
}
|
|
105
201
|
chunks.push(chunk);
|
|
@@ -108,17 +204,290 @@ function fetchOfficialLatest({
|
|
|
108
204
|
response.once("end", () => {
|
|
109
205
|
try {
|
|
110
206
|
const metadata = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
111
|
-
finish(null,
|
|
207
|
+
finish(null, validateRegistryLatestMetadata(metadata, source));
|
|
208
|
+
} catch (error) {
|
|
209
|
+
finish(error.code ? error : protocolError("npm latest JSON 无效"));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
req.setTimeout(timeoutMs, () => req.destroy(timeoutError("npm latest ")));
|
|
214
|
+
req.once("error", (error) => finish(error));
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function fetchOfficialLatest(options = {}) {
|
|
219
|
+
return fetchRegistryMetadata(REGISTRY_SOURCES.official, options);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function isMetadataFallbackError(error) {
|
|
223
|
+
return error?.code === INTERNAL_TIMEOUT_CODE
|
|
224
|
+
|| METADATA_FALLBACK_ERROR_CODES.includes(error?.code);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function fetchLatestWithFallback({
|
|
228
|
+
fetchMetadata = fetchRegistryMetadata,
|
|
229
|
+
...requestOptions
|
|
230
|
+
} = {}) {
|
|
231
|
+
try {
|
|
232
|
+
return await fetchMetadata(REGISTRY_SOURCES.official, requestOptions);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (!isMetadataFallbackError(error)) throw error;
|
|
235
|
+
return fetchMetadata(REGISTRY_SOURCES.mirror, requestOptions);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function validateUpdateDescriptor(descriptor) {
|
|
240
|
+
const source = sourceForRegistry(descriptor?.registry);
|
|
241
|
+
if (
|
|
242
|
+
!source
|
|
243
|
+
|| !isStableVersion(descriptor?.version)
|
|
244
|
+
|| descriptor.tarball !== tarballUrlFor(source, descriptor.version)
|
|
245
|
+
|| typeof descriptor.integrity !== "string"
|
|
246
|
+
|| !/^sha512-[A-Za-z0-9+/]{86}==$/.test(descriptor.integrity)
|
|
247
|
+
) {
|
|
248
|
+
throw protocolError("自动升级制品描述无效");
|
|
249
|
+
}
|
|
250
|
+
return { ...descriptor };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function expectedIntegrityBytes(integrity) {
|
|
254
|
+
const bytes = Buffer.from(integrity.slice("sha512-".length), "base64");
|
|
255
|
+
if (bytes.length !== 64) throw protocolError("自动升级 SHA-512 integrity 无效");
|
|
256
|
+
return bytes;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function writeAllSync(fd, chunk) {
|
|
260
|
+
let offset = 0;
|
|
261
|
+
while (offset < chunk.length) {
|
|
262
|
+
offset += fs.writeSync(fd, chunk, offset, chunk.length - offset);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function hashOpenFile(fd, maxBytes = MAX_TARBALL_BYTES) {
|
|
267
|
+
const hash = crypto.createHash("sha512");
|
|
268
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
269
|
+
let size = 0;
|
|
270
|
+
let offset = 0;
|
|
271
|
+
for (;;) {
|
|
272
|
+
const count = fs.readSync(fd, buffer, 0, buffer.length, offset);
|
|
273
|
+
if (count === 0) break;
|
|
274
|
+
size += count;
|
|
275
|
+
if (size > maxBytes) throw protocolError("自动升级本地制品过大");
|
|
276
|
+
hash.update(buffer.subarray(0, count));
|
|
277
|
+
offset += count;
|
|
278
|
+
}
|
|
279
|
+
if (size === 0) throw protocolError("自动升级本地制品为空");
|
|
280
|
+
return { size, digest: hash.digest() };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function downloadTarballToDescriptor(descriptor, fd, {
|
|
284
|
+
request = https.get,
|
|
285
|
+
timeoutMs = TARBALL_TIMEOUT_MS,
|
|
286
|
+
maxBytes = MAX_TARBALL_BYTES,
|
|
287
|
+
} = {}) {
|
|
288
|
+
return new Promise((resolve, reject) => {
|
|
289
|
+
let settled = false;
|
|
290
|
+
let req;
|
|
291
|
+
let size = 0;
|
|
292
|
+
const hash = crypto.createHash("sha512");
|
|
293
|
+
const deadline = setTimeout(() => req?.destroy(timeoutError("npm tarball ")), timeoutMs);
|
|
294
|
+
const finish = (error, value) => {
|
|
295
|
+
if (settled) return;
|
|
296
|
+
settled = true;
|
|
297
|
+
clearTimeout(deadline);
|
|
298
|
+
if (error) reject(error);
|
|
299
|
+
else resolve(value);
|
|
300
|
+
};
|
|
301
|
+
req = request(descriptor.tarball, fixedHttpsOptions({ accept: "application/octet-stream" }), (response) => {
|
|
302
|
+
if (response.statusCode !== 200 || response.headers.location) {
|
|
303
|
+
response.resume();
|
|
304
|
+
finish(protocolError("npm tarball 响应无效"));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
validateContentLength(response.headers, maxBytes, { allowZero: false, label: "npm tarball" });
|
|
309
|
+
} catch (error) {
|
|
310
|
+
response.resume();
|
|
311
|
+
finish(error);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
response.on("data", (chunk) => {
|
|
315
|
+
if (settled) return;
|
|
316
|
+
size += chunk.length;
|
|
317
|
+
if (size > maxBytes) {
|
|
318
|
+
req.destroy(protocolError("npm tarball 响应过大"));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
writeAllSync(fd, chunk);
|
|
323
|
+
hash.update(chunk);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
req.destroy(error);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
response.once("error", (error) => finish(error));
|
|
329
|
+
response.once("end", () => {
|
|
330
|
+
if (size === 0) {
|
|
331
|
+
finish(protocolError("npm tarball 为空"));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const actual = hash.digest();
|
|
336
|
+
const expected = expectedIntegrityBytes(descriptor.integrity);
|
|
337
|
+
if (!crypto.timingSafeEqual(actual, expected)) {
|
|
338
|
+
throw protocolError("npm tarball SHA-512 integrity 不匹配");
|
|
339
|
+
}
|
|
340
|
+
finish(null, { size, digest: actual.toString("hex") });
|
|
112
341
|
} catch (error) {
|
|
113
342
|
finish(error);
|
|
114
343
|
}
|
|
115
344
|
});
|
|
116
345
|
});
|
|
117
|
-
req.setTimeout(timeoutMs, () => req.destroy(
|
|
346
|
+
req.setTimeout(timeoutMs, () => req.destroy(timeoutError("npm tarball ")));
|
|
118
347
|
req.once("error", (error) => finish(error));
|
|
119
348
|
});
|
|
120
349
|
}
|
|
121
350
|
|
|
351
|
+
async function acquireStableUpdateArtifact(descriptor, {
|
|
352
|
+
home = os.homedir(),
|
|
353
|
+
platform = process.platform,
|
|
354
|
+
stateStore = createDefaultStateStore(platform, home),
|
|
355
|
+
request = https.get,
|
|
356
|
+
randomBytes = crypto.randomBytes,
|
|
357
|
+
} = {}) {
|
|
358
|
+
const validated = validateUpdateDescriptor(descriptor);
|
|
359
|
+
const directory = stateStore.ensurePrivateDirectory(
|
|
360
|
+
path.join(home, ".rainbond", "rainskills", "update-artifacts")
|
|
361
|
+
);
|
|
362
|
+
const digestPrefix = expectedIntegrityBytes(validated.integrity).toString("hex").slice(0, 16);
|
|
363
|
+
const finalPath = path.join(directory, `rainskills-${validated.version}-${digestPrefix}.tgz`);
|
|
364
|
+
if (fs.existsSync(finalPath)) {
|
|
365
|
+
stateStore.assertProtectedRegularFile(finalPath);
|
|
366
|
+
const noFollow = fs.constants.O_NOFOLLOW || 0;
|
|
367
|
+
const existingFd = fs.openSync(finalPath, fs.constants.O_RDONLY | noFollow);
|
|
368
|
+
let identity;
|
|
369
|
+
try {
|
|
370
|
+
identity = fs.fstatSync(existingFd);
|
|
371
|
+
const pathIdentity = fs.lstatSync(finalPath);
|
|
372
|
+
if (
|
|
373
|
+
pathIdentity.isSymbolicLink()
|
|
374
|
+
|| !pathIdentity.isFile()
|
|
375
|
+
|| pathIdentity.dev !== identity.dev
|
|
376
|
+
|| pathIdentity.ino !== identity.ino
|
|
377
|
+
) throw protocolError("自动升级既有制品身份发生变化");
|
|
378
|
+
const actual = hashOpenFile(existingFd);
|
|
379
|
+
if (!crypto.timingSafeEqual(actual.digest, expectedIntegrityBytes(validated.integrity))) {
|
|
380
|
+
throw protocolError("自动升级既有制品 SHA-512 integrity 不匹配");
|
|
381
|
+
}
|
|
382
|
+
} finally {
|
|
383
|
+
fs.closeSync(existingFd);
|
|
384
|
+
}
|
|
385
|
+
let cleaned = false;
|
|
386
|
+
return {
|
|
387
|
+
path: finalPath,
|
|
388
|
+
cleanup() {
|
|
389
|
+
if (cleaned) return;
|
|
390
|
+
cleaned = true;
|
|
391
|
+
try {
|
|
392
|
+
const current = fs.lstatSync(finalPath);
|
|
393
|
+
if (
|
|
394
|
+
!current.isSymbolicLink()
|
|
395
|
+
&& current.isFile()
|
|
396
|
+
&& current.dev === identity.dev
|
|
397
|
+
&& current.ino === identity.ino
|
|
398
|
+
) fs.unlinkSync(finalPath);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
if (error.code !== "ENOENT") throw error;
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
const candidatePath = path.join(
|
|
406
|
+
directory,
|
|
407
|
+
`.rainskills-${validated.version}.${process.pid}.${randomBytes(6).toString("hex")}.candidate`
|
|
408
|
+
);
|
|
409
|
+
const noFollow = fs.constants.O_NOFOLLOW || 0;
|
|
410
|
+
let fd;
|
|
411
|
+
let published = false;
|
|
412
|
+
let identity;
|
|
413
|
+
try {
|
|
414
|
+
fd = fs.openSync(
|
|
415
|
+
candidatePath,
|
|
416
|
+
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow,
|
|
417
|
+
0o600
|
|
418
|
+
);
|
|
419
|
+
await downloadTarballToDescriptor(validated, fd, { request });
|
|
420
|
+
fs.fsyncSync(fd);
|
|
421
|
+
stateStore.protectRegularFile(candidatePath);
|
|
422
|
+
identity = fs.fstatSync(fd);
|
|
423
|
+
const candidateIdentity = fs.lstatSync(candidatePath);
|
|
424
|
+
if (
|
|
425
|
+
candidateIdentity.isSymbolicLink()
|
|
426
|
+
|| !candidateIdentity.isFile()
|
|
427
|
+
|| candidateIdentity.dev !== identity.dev
|
|
428
|
+
|| candidateIdentity.ino !== identity.ino
|
|
429
|
+
) {
|
|
430
|
+
throw protocolError("自动升级候选制品身份发生变化");
|
|
431
|
+
}
|
|
432
|
+
fs.linkSync(candidatePath, finalPath);
|
|
433
|
+
published = true;
|
|
434
|
+
stateStore.protectRegularFile(finalPath);
|
|
435
|
+
const finalIdentity = fs.lstatSync(finalPath);
|
|
436
|
+
if (
|
|
437
|
+
finalIdentity.isSymbolicLink()
|
|
438
|
+
|| !finalIdentity.isFile()
|
|
439
|
+
|| finalIdentity.dev !== identity.dev
|
|
440
|
+
|| finalIdentity.ino !== identity.ino
|
|
441
|
+
) {
|
|
442
|
+
throw protocolError("自动升级最终制品身份发生变化");
|
|
443
|
+
}
|
|
444
|
+
fs.unlinkSync(candidatePath);
|
|
445
|
+
if (platform !== "win32") {
|
|
446
|
+
const directoryFd = fs.openSync(directory, "r");
|
|
447
|
+
try { fs.fsyncSync(directoryFd); } finally { fs.closeSync(directoryFd); }
|
|
448
|
+
}
|
|
449
|
+
fs.closeSync(fd);
|
|
450
|
+
fd = undefined;
|
|
451
|
+
let cleaned = false;
|
|
452
|
+
return {
|
|
453
|
+
path: finalPath,
|
|
454
|
+
cleanup() {
|
|
455
|
+
if (cleaned) return;
|
|
456
|
+
cleaned = true;
|
|
457
|
+
try {
|
|
458
|
+
const current = fs.lstatSync(finalPath);
|
|
459
|
+
if (
|
|
460
|
+
!current.isSymbolicLink()
|
|
461
|
+
&& current.isFile()
|
|
462
|
+
&& current.dev === identity.dev
|
|
463
|
+
&& current.ino === identity.ino
|
|
464
|
+
) fs.unlinkSync(finalPath);
|
|
465
|
+
} catch (error) {
|
|
466
|
+
if (error.code !== "ENOENT") throw error;
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
} catch (error) {
|
|
471
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
472
|
+
for (const target of [candidatePath, ...(published ? [finalPath] : [])]) {
|
|
473
|
+
try { fs.unlinkSync(target); } catch (cleanupError) {
|
|
474
|
+
if (cleanupError.code !== "ENOENT") throw cleanupError;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function prepareStableUpdate({
|
|
482
|
+
fetchLatest = fetchLatestWithFallback,
|
|
483
|
+
acquireArtifact = acquireStableUpdateArtifact,
|
|
484
|
+
...options
|
|
485
|
+
} = {}) {
|
|
486
|
+
const descriptor = validateUpdateDescriptor(await fetchLatest(options));
|
|
487
|
+
const artifact = await acquireArtifact(descriptor, options);
|
|
488
|
+
return { descriptor, artifact };
|
|
489
|
+
}
|
|
490
|
+
|
|
122
491
|
function createDefaultStateStore(platform, home) {
|
|
123
492
|
if (platform === "win32") {
|
|
124
493
|
const { createWindowsSecureStateStore } = require("./windows-platform.js");
|
|
@@ -276,7 +645,7 @@ async function checkForStableUpdate({
|
|
|
276
645
|
platform = process.platform,
|
|
277
646
|
now = () => Date.now(),
|
|
278
647
|
ttlMs = DEFAULT_CHECK_TTL_MS,
|
|
279
|
-
fetchLatest =
|
|
648
|
+
fetchLatest = fetchLatestWithFallback,
|
|
280
649
|
activeOperationDetector,
|
|
281
650
|
updateState,
|
|
282
651
|
} = {}) {
|
|
@@ -312,7 +681,9 @@ async function checkForStableUpdate({
|
|
|
312
681
|
try { state.recordFailure?.(); } catch { /* best-effort only */ }
|
|
313
682
|
return { action: "continue", reason: "check-failed" };
|
|
314
683
|
}
|
|
315
|
-
|
|
684
|
+
try {
|
|
685
|
+
latest = validateUpdateDescriptor(latest);
|
|
686
|
+
} catch {
|
|
316
687
|
try { state.recordCheck(null); } catch { /* best-effort only */ }
|
|
317
688
|
return { action: "continue", reason: "invalid-latest" };
|
|
318
689
|
}
|
|
@@ -320,28 +691,55 @@ async function checkForStableUpdate({
|
|
|
320
691
|
try { state.recordCheck(latest.version); } catch { /* best-effort only */ }
|
|
321
692
|
return { action: "continue", reason: "up-to-date" };
|
|
322
693
|
}
|
|
323
|
-
return { action: "delegate",
|
|
694
|
+
return { action: "delegate", ...latest };
|
|
324
695
|
}
|
|
325
696
|
|
|
326
|
-
function buildStableUpdateInvocation(
|
|
327
|
-
|
|
697
|
+
function buildStableUpdateInvocation(descriptor, args, {
|
|
698
|
+
platform = process.platform,
|
|
699
|
+
artifactPath,
|
|
700
|
+
} = {}) {
|
|
701
|
+
const validated = validateUpdateDescriptor(descriptor);
|
|
328
702
|
if (!isSafeAutoUpdateEntry(args)) throw new Error("自动升级入口无效");
|
|
703
|
+
const isAbsoluteArtifact = platform === "win32"
|
|
704
|
+
? path.win32.isAbsolute(artifactPath || "")
|
|
705
|
+
: path.isAbsolute(artifactPath || "");
|
|
706
|
+
if (typeof artifactPath !== "string" || !isAbsoluteArtifact) {
|
|
707
|
+
throw new Error("自动升级本地制品路径无效");
|
|
708
|
+
}
|
|
329
709
|
return {
|
|
330
|
-
executable: platform === "win32" ? "
|
|
331
|
-
args: [
|
|
710
|
+
executable: platform === "win32" ? "npm.cmd" : "npm",
|
|
711
|
+
args: [
|
|
712
|
+
"exec",
|
|
713
|
+
"--yes",
|
|
714
|
+
"--ignore-scripts",
|
|
715
|
+
`--registry=${validated.registry}`,
|
|
716
|
+
`--package=${artifactPath}`,
|
|
717
|
+
"--",
|
|
718
|
+
"rainskills",
|
|
719
|
+
...args,
|
|
720
|
+
],
|
|
332
721
|
};
|
|
333
722
|
}
|
|
334
723
|
|
|
335
|
-
function
|
|
724
|
+
function sanitizeAutoUpdateEnvironment(source = process.env) {
|
|
725
|
+
const environment = {};
|
|
726
|
+
for (const key of AUTO_UPDATE_ENVIRONMENT_KEYS) {
|
|
727
|
+
if (typeof source[key] === "string") environment[key] = source[key];
|
|
728
|
+
}
|
|
729
|
+
return environment;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function buildStableUpdateEnvironment(source, { fromVersion, targetVersion, registry }) {
|
|
336
733
|
if (!isStableVersion(fromVersion) || !isStableVersion(targetVersion)) {
|
|
337
734
|
throw new Error("自动升级环境只能绑定正式版");
|
|
338
735
|
}
|
|
736
|
+
if (!sourceForRegistry(registry)) throw new Error("自动升级 registry 无效");
|
|
339
737
|
return {
|
|
340
|
-
...source,
|
|
738
|
+
...sanitizeAutoUpdateEnvironment(source),
|
|
341
739
|
RAINSKILLS_AUTO_UPDATE_HOP: "1",
|
|
342
740
|
RAINSKILLS_AUTO_UPDATE_FROM: fromVersion,
|
|
343
741
|
RAINSKILLS_AUTO_UPDATE_TARGET: targetVersion,
|
|
344
|
-
npm_config_registry:
|
|
742
|
+
npm_config_registry: registry,
|
|
345
743
|
npm_config_ignore_scripts: "true",
|
|
346
744
|
};
|
|
347
745
|
}
|
|
@@ -492,19 +890,35 @@ function synchronizeInstalledSkills({
|
|
|
492
890
|
}
|
|
493
891
|
|
|
494
892
|
module.exports = {
|
|
893
|
+
AUTO_UPDATE_ENVIRONMENT_KEYS,
|
|
495
894
|
DEFAULT_CHECK_TTL_MS,
|
|
895
|
+
INTERNAL_TIMEOUT_CODE,
|
|
896
|
+
MAX_METADATA_BYTES,
|
|
897
|
+
MAX_TARBALL_BYTES,
|
|
898
|
+
METADATA_FALLBACK_ERROR_CODES,
|
|
899
|
+
METADATA_TIMEOUT_MS,
|
|
496
900
|
OFFICIAL_LATEST_URL,
|
|
901
|
+
OFFICIAL_REGISTRY,
|
|
902
|
+
REGISTRY_SOURCES,
|
|
903
|
+
TARBALL_TIMEOUT_MS,
|
|
904
|
+
acquireStableUpdateArtifact,
|
|
497
905
|
buildStableUpdateEnvironment,
|
|
498
906
|
buildStableUpdateInvocation,
|
|
499
907
|
checkForStableUpdate,
|
|
500
908
|
compareStableVersions,
|
|
501
909
|
createAutoUpdateState,
|
|
910
|
+
fetchLatestWithFallback,
|
|
502
911
|
fetchOfficialLatest,
|
|
912
|
+
fetchRegistryMetadata,
|
|
503
913
|
hasActiveOperation,
|
|
504
914
|
isSafeAutoUpdateEntry,
|
|
505
915
|
isStableVersion,
|
|
916
|
+
prepareStableUpdate,
|
|
506
917
|
recordSkillInstallDestinations,
|
|
507
918
|
resolveInstallDestinations,
|
|
919
|
+
sanitizeAutoUpdateEnvironment,
|
|
508
920
|
synchronizeInstalledSkills,
|
|
921
|
+
validateRegistryLatestMetadata,
|
|
922
|
+
validateUpdateDescriptor,
|
|
509
923
|
validateOfficialLatestMetadata,
|
|
510
924
|
};
|
|
@@ -14,7 +14,7 @@ Node.js 前置检查通过后,每次请求先执行本地 launcher + `["enviro
|
|
|
14
14
|
|
|
15
15
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
16
16
|
|
|
17
|
-
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.
|
|
17
|
+
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.5"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
18
18
|
|
|
19
19
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接运行环境后,才使用上面的固定 npx launcher。
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ Node.js 前置检查通过后,每次请求先执行本地 launcher + `["enviro
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
24
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
25
|
-
"launcher": ["npx", "--yes", "rainskills@0.1.
|
|
25
|
+
"launcher": ["npx", "--yes", "rainskills@0.1.5"],
|
|
26
26
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
27
27
|
"local_argv": {
|
|
28
28
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -35,8 +35,8 @@ Node.js 前置检查通过后,每次请求先执行本地 launcher + `["enviro
|
|
|
35
35
|
},
|
|
36
36
|
"routes": {"existing": ["saas", "private-existing"]},
|
|
37
37
|
"connect_argv": {
|
|
38
|
-
"saas": ["npx", "--yes", "rainskills@0.1.
|
|
39
|
-
"private-existing": ["npx", "--yes", "rainskills@0.1.
|
|
38
|
+
"saas": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
39
|
+
"private-existing": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"]
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
```
|
|
@@ -14,7 +14,7 @@ description: "Use only when the user explicitly asks to initialize or link a loc
|
|
|
14
14
|
|
|
15
15
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
16
16
|
|
|
17
|
-
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.
|
|
17
|
+
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.5"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
18
18
|
|
|
19
19
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接或安装运行环境后,才使用上面的固定 npx launcher。
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ description: "Use only when the user explicitly asks to initialize or link a loc
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
24
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
25
|
-
"launcher": ["npx", "--yes", "rainskills@0.1.
|
|
25
|
+
"launcher": ["npx", "--yes", "rainskills@0.1.5"],
|
|
26
26
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
27
27
|
"local_argv": {
|
|
28
28
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -35,9 +35,9 @@ description: "Use only when the user explicitly asks to initialize or link a loc
|
|
|
35
35
|
},
|
|
36
36
|
"routes": {"new": ["saas", "private-existing", "install-private"]},
|
|
37
37
|
"connect_argv": {
|
|
38
|
-
"saas": ["npx", "--yes", "rainskills@0.1.
|
|
39
|
-
"private-existing": ["npx", "--yes", "rainskills@0.1.
|
|
40
|
-
"install-private": ["npx", "--yes", "rainskills@0.1.
|
|
38
|
+
"saas": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
39
|
+
"private-existing": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"],
|
|
40
|
+
"install-private": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--install-private", "--location", "<private-location>", "--intent-json", "<intent-json>"]
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
```
|
|
@@ -14,7 +14,7 @@ description: "Use when the user explicitly asks to install a local or cloud Rain
|
|
|
14
14
|
|
|
15
15
|
第一步检查 Node.js 是否存在且主版本不低于 18。Node.js 缺失或低于 18 时,只说明“Rainskills 执行组件需要 Node.js 18 或更高版本”并停止:不选择运行环境,不调用 MCP,不猜测替代命令。只有用户或 agent 明确同意后才安装或升级 Node.js,再从同一原始 intent 继续。
|
|
16
16
|
|
|
17
|
-
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.
|
|
17
|
+
固定 launcher 是 `["npx", "--yes", "rainskills@0.1.5"]`;版本必须与本技能包 `package.json` 一致。把 launcher 与参数拼成 argv 数组直接执行,禁止 `rainskills@latest` 或执行 shell 字符串。
|
|
18
18
|
|
|
19
19
|
本地 launcher 必须从当前 Skill 所在目录的同级目录定位 `rainbond-platform-installer/scripts/local-runtime.js`,解析为绝对路径后使用 `["node", "<绝对路径>"]` 执行。`environment list`、`operation begin`、`operation complete` 和 `runtime message` 只能使用本地 launcher;本地 launcher 只读取已安装文件和本机受保护状态,不得访问 npm 或其它网络。只有用户选定连接或安装运行环境后,才使用上面的固定 npx launcher。
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ description: "Use when the user explicitly asks to install a local or cloud Rain
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
24
24
|
"schema": "rainskills.skill-runtime-contract.v1",
|
|
25
|
-
"launcher": ["npx", "--yes", "rainskills@0.1.
|
|
25
|
+
"launcher": ["npx", "--yes", "rainskills@0.1.5"],
|
|
26
26
|
"local_launcher": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js"],
|
|
27
27
|
"local_argv": {
|
|
28
28
|
"environment-list": ["node", "<installed-skills-root>/rainbond-platform-installer/scripts/local-runtime.js", "environment", "list", "--json"],
|
|
@@ -38,9 +38,9 @@ description: "Use when the user explicitly asks to install a local or cloud Rain
|
|
|
38
38
|
"existing": ["saas", "private-existing"]
|
|
39
39
|
},
|
|
40
40
|
"connect_argv": {
|
|
41
|
-
"saas": ["npx", "--yes", "rainskills@0.1.
|
|
42
|
-
"private-existing": ["npx", "--yes", "rainskills@0.1.
|
|
43
|
-
"install-private": ["npx", "--yes", "rainskills@0.1.
|
|
41
|
+
"saas": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--saas", "--intent-json", "<intent-json>"],
|
|
42
|
+
"private-existing": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--rainbond-url", "<rainbond-url>", "--intent-json", "<intent-json>"],
|
|
43
|
+
"install-private": ["npx", "--yes", "rainskills@0.1.5", "runtime", "connect", "<target>", "--install-private", "--location", "<private-location>", "--intent-json", "<intent-json>"]
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
```
|