rainskills 0.1.0 → 0.1.2
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 +156 -420
- package/SKILL.md +37 -7
- package/agents/openai.yaml +2 -2
- package/bin/rainskills-tools.js +712 -0
- package/bin/rainskills.js +1109 -4
- package/install.sh +536 -63
- package/marketplace/rainskills/.claude-plugin/plugin.json +2 -2
- package/marketplace/rainskills/.codex-plugin/plugin.json +5 -5
- package/marketplace/rainskills/skills/rainskills/SKILL.md +38 -8
- package/package.json +14 -5
- package/pi/rainskills-mcp.ts +214 -1850
- package/rainbond-app-assistant/SKILL.md +123 -49
- package/rainbond-app-assistant/evals/01-linked-topology-missing.response.md +3 -3
- package/rainbond-app-assistant/evals/02-linked-topology-building.response.md +3 -3
- package/rainbond-app-assistant/evals/03-runtime-healthy-delivery-unknown.response.md +3 -3
- package/rainbond-app-assistant/evals/04-delivered-stop-without-promotion.expected.yaml +1 -1
- package/rainbond-app-assistant/evals/04-delivered-stop-without-promotion.response.md +1 -1
- package/rainbond-app-assistant/evals/05-delivered-explicit-dev-to-test.response.md +3 -3
- package/rainbond-app-assistant/evals/06-manual-validation-stop.expected.yaml +1 -1
- package/rainbond-app-assistant/evals/06-manual-validation-stop.response.md +3 -3
- package/rainbond-app-assistant/evals/07-cluster-capacity-blocked.response.md +4 -4
- package/rainbond-app-assistant/evals/08-code-build-handoff-stop.response.md +2 -2
- package/rainbond-app-assistant/evals/09-source-backed-failure-no-fallback.response.md +2 -2
- package/rainbond-app-assistant/evals/10-identity-ambiguous-stop.response.md +3 -3
- package/rainbond-app-assistant/evals/12-complex-suite-needs-descriptor-stop.response.md +2 -2
- package/rainbond-app-assistant/evals/13-explicit-git-url-locks-source.response.md +2 -2
- package/rainbond-app-assistant/evals/14-strategy-switch-cleans-residue.response.md +2 -2
- package/rainbond-app-assistant/evals/15-turn-discipline-reuse-alias.response.md +2 -2
- package/rainbond-app-assistant/references/operational-reference.md +77 -0
- package/rainbond-app-assistant/references/output-contract.md +454 -0
- package/rainbond-app-assistant/references/product-object-model.md +1558 -0
- package/rainbond-app-assistant/references/workflow-rules.md +343 -0
- package/rainbond-app-assistant/schemas/app-assistant-result.schema.yaml +6 -1
- package/rainbond-app-version-assistant/SKILL.md +57 -18
- package/rainbond-delivery-verifier/SKILL.md +86 -13
- package/rainbond-env-sync/SKILL.md +52 -11
- package/rainbond-fullstack-bootstrap/SKILL.md +79 -11
- package/rainbond-fullstack-bootstrap/evals/01-reuse-only.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/02-deferred-dependency.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/03-capacity-blocked.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/04-template-skip.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/05-source-ambiguity-stop.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/06-external-artifact-unreachable.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/07-provider-connection-contract.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/08-explicit-dependency-after-manual-components.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/09-inferred-provider-consumer-gate.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/10-compose-service-name-not-hostname.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/11-reverse-proxy-not-dropped.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/12-optional-services-disclose-not-deploy.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/13-env-reference-dependency-completeness.response.md +1 -1
- package/rainbond-fullstack-bootstrap/evals/14-client-package-upload.expected.yaml +1 -1
- package/rainbond-fullstack-bootstrap/evals/14-client-package-upload.response.md +3 -3
- package/rainbond-fullstack-bootstrap/evals/15-unfinished-dockerfile-recovery.expected.yaml +25 -0
- package/rainbond-fullstack-bootstrap/evals/15-unfinished-dockerfile-recovery.response.md +44 -0
- package/rainbond-fullstack-bootstrap/evals/16-complete-cnb-recreate-confirmed.expected.yaml +25 -0
- package/rainbond-fullstack-bootstrap/evals/16-complete-cnb-recreate-confirmed.response.md +44 -0
- package/rainbond-fullstack-bootstrap/modules/10-context-loading.md +8 -8
- package/rainbond-fullstack-bootstrap/modules/30-creation-rules.md +10 -10
- package/rainbond-fullstack-bootstrap/modules/40-source-and-package-rules.md +13 -9
- package/rainbond-fullstack-bootstrap/modules/50-workflow-and-convergence.md +6 -6
- package/rainbond-fullstack-bootstrap/modules/60-verification-and-handoffs.md +4 -4
- package/rainbond-fullstack-bootstrap/modules/70-output-contract.md +3 -3
- package/rainbond-fullstack-bootstrap/references/quick-reference.md +6 -6
- package/rainbond-fullstack-bootstrap/references/source-build-parameter-guide.md +6 -6
- package/rainbond-fullstack-bootstrap/references/sql-init-recipe.md +3 -3
- package/rainbond-fullstack-bootstrap/schemas/bootstrap-result.schema.yaml +3 -2
- package/rainbond-fullstack-bootstrap/scripts/upload_local_package.py +1 -1
- package/rainbond-fullstack-bootstrap/scripts/validate_bootstrap_output.py +2 -2
- package/rainbond-fullstack-troubleshooter/SKILL.md +103 -13
- package/rainbond-fullstack-troubleshooter/evals/10-config-file-configmap-missing.expected.yaml +21 -0
- package/rainbond-fullstack-troubleshooter/evals/10-config-file-configmap-missing.response.md +51 -0
- package/rainbond-fullstack-troubleshooter/references/operational-reference.md +70 -0
- package/rainbond-fullstack-troubleshooter/references/output-contract.md +259 -0
- package/rainbond-fullstack-troubleshooter/references/root-cause-rules.md +210 -0
- package/rainbond-fullstack-troubleshooter/schemas/troubleshoot-result.schema.yaml +3 -1
- package/rainbond-fullstack-troubleshooter/scripts/validate_troubleshoot_output.py +22 -1
- package/rainbond-platform-installer/SKILL.md +73 -9
- package/rainbond-platform-installer/agents/openai.yaml +2 -2
- package/rainbond-platform-installer/references/installation-policy.json +35 -1
- package/rainbond-platform-installer/references/installation-policy.md +33 -8
- package/rainbond-platform-installer/references/troubleshooting.md +3 -2
- package/rainbond-platform-installer/scripts/auto-update.js +495 -0
- package/rainbond-platform-installer/scripts/console-origin.js +159 -0
- package/rainbond-platform-installer/scripts/environment-credentials.js +225 -0
- package/rainbond-platform-installer/scripts/environment-registry.js +349 -0
- package/rainbond-platform-installer/scripts/existing-kubernetes-installer.js +1370 -0
- package/rainbond-platform-installer/scripts/host-cluster-installer.js +2143 -0
- package/rainbond-platform-installer/scripts/local-runtime-commands.js +180 -0
- package/rainbond-platform-installer/scripts/local-runtime.js +18 -0
- package/rainbond-platform-installer/scripts/mcp-router.js +212 -0
- package/rainbond-platform-installer/scripts/mcp-server.js +79 -0
- package/rainbond-platform-installer/scripts/platform-installer.js +694 -255
- package/rainbond-platform-installer/scripts/platform-routing.js +326 -0
- package/rainbond-platform-installer/scripts/runtime-credentials.js +163 -0
- package/rainbond-platform-installer/scripts/runtime-intents.js +251 -0
- package/rainbond-platform-installer/scripts/runtime-operations.js +383 -0
- package/rainbond-platform-installer/scripts/runtime-state.js +656 -0
- package/rainbond-platform-installer/scripts/secure-state.js +59 -13
- package/rainbond-platform-installer/scripts/ssh-key-setup.js +242 -0
- package/rainbond-platform-installer/scripts/telemetry.js +244 -0
- package/rainbond-platform-installer/scripts/user-message.js +125 -0
- package/rainbond-platform-installer/scripts/windows-auth.js +1 -1
- package/rainbond-platform-installer/scripts/windows-client-config.js +101 -22
- package/rainbond-platform-installer/scripts/windows-onboarding.js +179 -83
- package/rainbond-platform-installer/scripts/windows-platform.js +3 -3
- package/rainbond-platform-installer/scripts/windows-platform.ps1 +264 -28
- package/rainbond-platform-installer/scripts/windows-read-user-environment.ps1 +16 -0
- package/rainbond-platform-query/SKILL.md +88 -0
- package/rainbond-platform-query/agents/openai.yaml +4 -0
- package/rainbond-platform-query/evals/01-current-enterprise.expected.yaml +10 -0
- package/rainbond-project-init/SKILL.md +68 -9
- package/rainbond-project-init/references/manifest-rules.md +487 -0
- package/rainbond-project-init/references/operational-reference.md +74 -0
- package/rainbond-project-init/references/output-contract.md +272 -0
- package/rainbond-project-init/references/workflow-and-verification.md +85 -0
- package/rainbond-template-installer/SKILL.md +80 -16
package/install.sh
CHANGED
|
@@ -9,12 +9,21 @@
|
|
|
9
9
|
# hand-write ~/.rainbond/mcp.env. See README.md "给 AI 助手的指引".
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
|
|
12
|
+
RAINSKILLS_VERBOSE="${RAINSKILLS_VERBOSE:-0}"
|
|
13
|
+
for rainskills_bootstrap_arg in "$@"; do
|
|
14
|
+
if [[ "$rainskills_bootstrap_arg" == "--verbose" ]]; then
|
|
15
|
+
RAINSKILLS_VERBOSE=1
|
|
16
|
+
break
|
|
17
|
+
fi
|
|
18
|
+
done
|
|
19
|
+
|
|
12
20
|
RAINBOND_SKILLS_HOME_DEFAULT="${RAINBOND_SKILLS_HOME:-$HOME/.rainbond/skills}"
|
|
13
21
|
RAINBOND_SKILLS_OSS_TARBALL_URL="${RAINBOND_SKILLS_OSS_URL:-https://get.rainbond.com/rainskills/rainskills-latest.tar.gz}"
|
|
14
22
|
RAINBOND_SKILLS_GITHUB_TARBALL_URL="https://github.com/goodrain/rainskills/archive/refs/heads/main.tar.gz"
|
|
15
23
|
RAINBOND_SKILLS_TARBALL_URL_OVERRIDE="${RAINBOND_SKILLS_TARBALL_URL:-}"
|
|
16
24
|
|
|
17
25
|
bootstrap_log() {
|
|
26
|
+
[[ "$RAINSKILLS_VERBOSE" == "1" ]] || return 0
|
|
18
27
|
printf '%s\n' "$1"
|
|
19
28
|
}
|
|
20
29
|
|
|
@@ -92,8 +101,10 @@ bootstrap_download_if_needed() {
|
|
|
92
101
|
chmod +x "$target_script" 2>/dev/null || true
|
|
93
102
|
|
|
94
103
|
bootstrap_log "切换到 $target_script 继续执行……"
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
local controlling_tty=""
|
|
105
|
+
controlling_tty="$(tty 2>/dev/null || true)"
|
|
106
|
+
if [[ "$controlling_tty" == /dev/* && -r "$controlling_tty" ]]; then
|
|
107
|
+
exec bash "$target_script" "$@" <"$controlling_tty"
|
|
97
108
|
else
|
|
98
109
|
exec bash "$target_script" "$@"
|
|
99
110
|
fi
|
|
@@ -114,8 +125,19 @@ should_skip_bootstrap_for_refresh() {
|
|
|
114
125
|
return 1
|
|
115
126
|
}
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
is_runtime_connect_invocation() {
|
|
129
|
+
[[ "${1:-}" == "connect" ]]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if is_runtime_connect_invocation "$@"; then
|
|
133
|
+
[[ "${RAINSKILLS_RUNTIME_CONNECT_COMPLETION:-0}" == "1" ]] \
|
|
134
|
+
&& [[ "${RAINSKILLS_RUNTIME_OPERATION_ID:-}" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$ ]] \
|
|
135
|
+
|| bootstrap_die "runtime connect 必须通过 Rainskills Node 入口执行。"
|
|
136
|
+
SCRIPT_DIR="$(resolve_script_dir)"
|
|
137
|
+
[[ -n "$SCRIPT_DIR" && -f "$SCRIPT_DIR/bin/rainskills.js" ]] \
|
|
138
|
+
|| bootstrap_die "runtime connect 缺少受信任的 Rainskills Node 入口。"
|
|
139
|
+
elif should_skip_bootstrap_for_refresh "$@"; then
|
|
140
|
+
SCRIPT_DIR="$(resolve_script_dir)"
|
|
119
141
|
else
|
|
120
142
|
bootstrap_download_if_needed "$@"
|
|
121
143
|
fi
|
|
@@ -153,13 +175,15 @@ ACTIVE_SHELL_RC=""
|
|
|
153
175
|
VALIDATED_TOKEN=""
|
|
154
176
|
OBTAINED_RAINBOND_TOKEN=""
|
|
155
177
|
RAINSKILLS_INSTALL_REPORT_URL="https://log.rainbond.com/api/rainskills/installations"
|
|
156
|
-
|
|
178
|
+
RAINSKILLS_LIFECYCLE_REPORT_URL="https://log.rainbond.com/api/rainskills/lifecycle-events"
|
|
179
|
+
RAINSKILLS_INSTALL_ATTEMPT_ID="${RAINSKILLS_INSTALL_ATTEMPT_ID:-}"
|
|
157
180
|
RAINSKILLS_INSTALL_EID=""
|
|
158
181
|
RAINSKILLS_INSTALL_CLIENT="unknown"
|
|
159
182
|
RAINSKILLS_INSTALL_ACTION="install"
|
|
160
183
|
RAINSKILLS_INSTALL_FAILURE_STAGE="bootstrap"
|
|
161
184
|
RAINSKILLS_INSTALL_FAILURE_CATEGORY="invalid_arguments"
|
|
162
185
|
RAINSKILLS_INSTALL_TERMINAL_REPORTED=0
|
|
186
|
+
RAINSKILLS_TELEMETRY_SEQUENCE=0
|
|
163
187
|
RAINSKILLS_BROWSER_LOGIN_SERVER_PID=""
|
|
164
188
|
RAINSKILLS_BROWSER_LOGIN_READER_PID=""
|
|
165
189
|
RAINSKILLS_BROWSER_LOGIN_RESULT_FILE=""
|
|
@@ -197,6 +221,7 @@ Options:
|
|
|
197
221
|
remember to restart Claude Code / Codex afterwards)
|
|
198
222
|
--dest PATH Install skills to a custom directory only
|
|
199
223
|
--force Overwrite existing installed skills
|
|
224
|
+
--verbose Show installation paths and copy diagnostics
|
|
200
225
|
--skip-mcp Skip Rainbond MCP setup
|
|
201
226
|
--saas Use Rainbond Cloud (https://run.rainbond.com)
|
|
202
227
|
--self-hosted Use a self-hosted Rainbond Console (requires --rainbond-url)
|
|
@@ -223,10 +248,34 @@ log() {
|
|
|
223
248
|
printf '%s\n' "$1"
|
|
224
249
|
}
|
|
225
250
|
|
|
251
|
+
install_detail_log() {
|
|
252
|
+
[[ "$RAINSKILLS_VERBOSE" == "1" ]] || return 0
|
|
253
|
+
log "$1"
|
|
254
|
+
}
|
|
255
|
+
|
|
226
256
|
warn() {
|
|
227
257
|
printf '警告:%s\n' "$1" >&2
|
|
228
258
|
}
|
|
229
259
|
|
|
260
|
+
print_capability_summary() {
|
|
261
|
+
printf '[RAINSKILLS_USER_MESSAGE_BEGIN:install.completed]\n'
|
|
262
|
+
cat <<'EOF'
|
|
263
|
+
Rainskills 安装完成,下一条消息即可直接使用。
|
|
264
|
+
|
|
265
|
+
下一步可以直接说:
|
|
266
|
+
|
|
267
|
+
- 帮我部署当前项目
|
|
268
|
+
- 帮我部署一个 Git 仓库
|
|
269
|
+
- 帮我通过镜像或安装包部署应用
|
|
270
|
+
- 帮我安装一个应用模板
|
|
271
|
+
- 帮我分析当前项目应该如何部署
|
|
272
|
+
|
|
273
|
+
也可以直接告诉我你想部署什么应用。
|
|
274
|
+
EOF
|
|
275
|
+
printf '[RAINSKILLS_USER_MESSAGE_END:install.completed]\n'
|
|
276
|
+
printf '[RAINSKILLS_AGENT_SUMMARY_REQUIRED:include-next-actions]\n'
|
|
277
|
+
}
|
|
278
|
+
|
|
230
279
|
new_rainskills_install_attempt_id() {
|
|
231
280
|
if command -v python3 >/dev/null 2>&1; then
|
|
232
281
|
python3 - <<'PY'
|
|
@@ -311,20 +360,169 @@ PY
|
|
|
311
360
|
) &
|
|
312
361
|
}
|
|
313
362
|
|
|
363
|
+
rainskills_telemetry_target() {
|
|
364
|
+
if [[ -n "${RAINSKILLS_TELEMETRY_TARGET:-}" ]]; then
|
|
365
|
+
printf '%s\n' "$RAINSKILLS_TELEMETRY_TARGET"
|
|
366
|
+
elif [[ "$(uname -s 2>/dev/null || true)" == "Darwin" ]]; then
|
|
367
|
+
printf 'local-macos\n'
|
|
368
|
+
else
|
|
369
|
+
printf 'local-linux\n'
|
|
370
|
+
fi
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
rainskills_telemetry_platform() {
|
|
374
|
+
case "$(uname -s 2>/dev/null || true)" in
|
|
375
|
+
Darwin) printf 'darwin\n' ;;
|
|
376
|
+
*) printf 'linux\n' ;;
|
|
377
|
+
esac
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
rainskills_telemetry_failure_phase() {
|
|
381
|
+
case "${RAINSKILLS_TELEMETRY_ERROR_STAGE:-bootstrap}" in
|
|
382
|
+
skill_installation|bootstrap) printf 'bootstrap\n' ;;
|
|
383
|
+
download) printf 'rootfs_download\n' ;;
|
|
384
|
+
authorization) printf 'authorize_legacy\n' ;;
|
|
385
|
+
verification) printf 'verify_console\n' ;;
|
|
386
|
+
configuration) printf 'configure_mcp\n' ;;
|
|
387
|
+
*) printf 'bootstrap\n' ;;
|
|
388
|
+
esac
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
rainskills_telemetry_error_code() {
|
|
392
|
+
case "${RAINSKILLS_INSTALL_FAILURE_CATEGORY:-unknown}" in
|
|
393
|
+
invalid_arguments) printf 'invalid_arguments\n' ;;
|
|
394
|
+
tarball_unavailable|download_failed) printf 'download_failed\n' ;;
|
|
395
|
+
authorization_failed) printf 'authorization_failed\n' ;;
|
|
396
|
+
mcp_verification_failed) printf 'mcp_verification_failed\n' ;;
|
|
397
|
+
mcp_configuration_failed|skill_installation_failed) printf 'configuration_failed\n' ;;
|
|
398
|
+
network_unreachable) printf 'network_unreachable\n' ;;
|
|
399
|
+
*) printf 'unknown\n' ;;
|
|
400
|
+
esac
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
report_rainskills_lifecycle_event() {
|
|
404
|
+
local lifecycle_phase="$1"
|
|
405
|
+
local step="$2"
|
|
406
|
+
local lifecycle_action="$3"
|
|
407
|
+
local lifecycle_status="$4"
|
|
408
|
+
local error_code="${5:-}"
|
|
409
|
+
local blocked_reason="${6:-}"
|
|
410
|
+
local auth_method="${7:-}"
|
|
411
|
+
|
|
412
|
+
[[ -n "$RAINSKILLS_INSTALL_ATTEMPT_ID" ]] || return 0
|
|
413
|
+
command -v python3 >/dev/null 2>&1 || return 0
|
|
414
|
+
RAINSKILLS_TELEMETRY_SEQUENCE=$((RAINSKILLS_TELEMETRY_SEQUENCE + 1))
|
|
415
|
+
|
|
416
|
+
local payload telemetry_dir
|
|
417
|
+
telemetry_dir="${RAINSKILLS_TELEMETRY_DIR:-${HOME:-/tmp}/.rainbond/rainskills/telemetry}"
|
|
418
|
+
payload="$({
|
|
419
|
+
python3 - \
|
|
420
|
+
"$RAINSKILLS_INSTALL_ATTEMPT_ID" \
|
|
421
|
+
"${RAINSKILLS_TELEMETRY_OPERATION_ID:-}" \
|
|
422
|
+
"${RAINSKILLS_TELEMETRY_INSTALLATION_ID:-}" \
|
|
423
|
+
"${RAINSKILLS_TELEMETRY_CLIENT:-$RAINSKILLS_INSTALL_CLIENT}" \
|
|
424
|
+
"${RAINSKILLS_TELEMETRY_CONTROL_MODE:-posix}" \
|
|
425
|
+
"$(rainskills_telemetry_target)" \
|
|
426
|
+
"$(rainskills_telemetry_platform)" \
|
|
427
|
+
"$RAINSKILLS_INSTALL_ACTION" \
|
|
428
|
+
"$RAINSKILLS_TELEMETRY_SEQUENCE" \
|
|
429
|
+
"$lifecycle_phase" \
|
|
430
|
+
"$step" \
|
|
431
|
+
"$lifecycle_action" \
|
|
432
|
+
"$lifecycle_status" \
|
|
433
|
+
"$error_code" \
|
|
434
|
+
"$blocked_reason" \
|
|
435
|
+
"$auth_method" \
|
|
436
|
+
"$RAINSKILLS_INSTALL_EID" \
|
|
437
|
+
"$RAINSKILLS_LIFECYCLE_REPORT_URL" \
|
|
438
|
+
"$telemetry_dir" <<'PY'
|
|
439
|
+
import datetime
|
|
440
|
+
import json
|
|
441
|
+
import os
|
|
442
|
+
import sys
|
|
443
|
+
import uuid
|
|
444
|
+
|
|
445
|
+
(attempt, operation, installation, client, control_mode, target, platform, action,
|
|
446
|
+
sequence, phase, step, lifecycle_action, status, error_code, blocked_reason,
|
|
447
|
+
auth_method, eid, report_url, telemetry_dir) = sys.argv[1:]
|
|
448
|
+
event = {
|
|
449
|
+
"schema": "rainskills.lifecycle-event.v1",
|
|
450
|
+
"event_id": str(uuid.uuid4()),
|
|
451
|
+
"install_attempt_id": attempt,
|
|
452
|
+
"operation_id": operation or None,
|
|
453
|
+
"installation_id": installation or None,
|
|
454
|
+
"parent_event_id": None,
|
|
455
|
+
"sequence": int(sequence),
|
|
456
|
+
"attempt": 1,
|
|
457
|
+
"resumed_from": None,
|
|
458
|
+
"package_version": os.environ.get("RAINSKILLS_PACKAGE_VERSION") or None,
|
|
459
|
+
"platform": platform,
|
|
460
|
+
"control_mode": control_mode,
|
|
461
|
+
"target": target,
|
|
462
|
+
"client": client if client in {"codex", "claude_code", "both", "unknown"} else "unknown",
|
|
463
|
+
"eid": eid or None,
|
|
464
|
+
"phase": None,
|
|
465
|
+
"lifecycle_phase": phase,
|
|
466
|
+
"step": step,
|
|
467
|
+
"action": action if action in {"install", "refresh"} else "install",
|
|
468
|
+
"lifecycle_action": lifecycle_action or None,
|
|
469
|
+
"status": None,
|
|
470
|
+
"lifecycle_status": status,
|
|
471
|
+
"duration_ms": None,
|
|
472
|
+
"error_code": error_code or None,
|
|
473
|
+
"error_stage": phase if error_code else None,
|
|
474
|
+
"reason_code": error_code or None,
|
|
475
|
+
"blocked_reason": blocked_reason or None,
|
|
476
|
+
"interrupt_signal": None,
|
|
477
|
+
"transport": "ssh" if target == "remote-linux" else ("wsl" if control_mode == "wsl" else ("powershell" if control_mode == "windows-native" else "direct")),
|
|
478
|
+
"auth_method": auth_method or None,
|
|
479
|
+
"retryable": status in {"blocked", "started"},
|
|
480
|
+
"exit_code": None,
|
|
481
|
+
"http_status": None,
|
|
482
|
+
"created_at": datetime.datetime.utcnow().isoformat(timespec="milliseconds") + "Z",
|
|
483
|
+
}
|
|
484
|
+
try:
|
|
485
|
+
os.makedirs(telemetry_dir, mode=0o700, exist_ok=True)
|
|
486
|
+
os.chmod(telemetry_dir, 0o700)
|
|
487
|
+
event_path = os.path.join(telemetry_dir, "events.jsonl")
|
|
488
|
+
with open(event_path, "a", encoding="utf-8") as stream:
|
|
489
|
+
stream.write(json.dumps(event, separators=(",", ":")) + "\n")
|
|
490
|
+
os.chmod(event_path, 0o600)
|
|
491
|
+
except Exception:
|
|
492
|
+
pass
|
|
493
|
+
print(json.dumps(event, separators=(",", ":")))
|
|
494
|
+
PY
|
|
495
|
+
} 2>/dev/null || true)"
|
|
496
|
+
[[ -n "$payload" ]] || return 0
|
|
497
|
+
(
|
|
498
|
+
curl --silent --show-error --connect-timeout 2 --max-time 3 \
|
|
499
|
+
-X POST "$RAINSKILLS_LIFECYCLE_REPORT_URL" \
|
|
500
|
+
-H 'Content-Type: application/json' \
|
|
501
|
+
-H "Idempotency-Key: $(python3 -c 'import json,sys; print(json.load(sys.stdin)["event_id"])' <<<"$payload")" \
|
|
502
|
+
--data-binary "$payload" >/dev/null 2>&1 || true
|
|
503
|
+
) &
|
|
504
|
+
}
|
|
505
|
+
|
|
314
506
|
initialize_rainskills_installation_reporting() {
|
|
315
507
|
local arg target=""
|
|
316
|
-
|
|
508
|
+
if [[ -z "$RAINSKILLS_INSTALL_ATTEMPT_ID" ]]; then
|
|
509
|
+
RAINSKILLS_INSTALL_ATTEMPT_ID="$(new_rainskills_install_attempt_id)"
|
|
510
|
+
fi
|
|
317
511
|
for arg in "$@"; do
|
|
318
512
|
case "$arg" in
|
|
319
513
|
refresh)
|
|
320
514
|
RAINSKILLS_INSTALL_ACTION="refresh"
|
|
321
515
|
;;
|
|
516
|
+
connect)
|
|
517
|
+
RAINSKILLS_INSTALL_ACTION="connect"
|
|
518
|
+
;;
|
|
322
519
|
codex|claude|all)
|
|
323
520
|
target="$arg"
|
|
324
521
|
;;
|
|
325
522
|
esac
|
|
326
523
|
done
|
|
327
524
|
RAINSKILLS_INSTALL_CLIENT="$(rainskills_install_client_for_target "$target")"
|
|
525
|
+
report_rainskills_lifecycle_event "bootstrap" "resume" "resume" "started"
|
|
328
526
|
report_rainskills_installation "started" "started"
|
|
329
527
|
}
|
|
330
528
|
|
|
@@ -410,12 +608,14 @@ record_rainskills_authorization() {
|
|
|
410
608
|
eid="$(resolve_rainskills_enterprise_id "$base_url" "$token" || true)"
|
|
411
609
|
[[ -n "$eid" ]] || return 0
|
|
412
610
|
RAINSKILLS_INSTALL_EID="$eid"
|
|
611
|
+
report_rainskills_lifecycle_event "authorize_legacy" "legacy_callback" "authorize" "completed" "" "" "browser_loopback"
|
|
413
612
|
report_rainskills_installation "authorized" "success"
|
|
414
613
|
}
|
|
415
614
|
|
|
416
615
|
set_rainskills_failure_context() {
|
|
417
616
|
RAINSKILLS_INSTALL_FAILURE_STAGE="$1"
|
|
418
617
|
RAINSKILLS_INSTALL_FAILURE_CATEGORY="$2"
|
|
618
|
+
RAINSKILLS_TELEMETRY_ERROR_STAGE="$1"
|
|
419
619
|
}
|
|
420
620
|
|
|
421
621
|
report_unhandled_rainskills_installation_failure() {
|
|
@@ -424,6 +624,12 @@ report_unhandled_rainskills_installation_failure() {
|
|
|
424
624
|
"${BASH_SUBSHELL:-0}" -eq 0 && \
|
|
425
625
|
"$RAINSKILLS_INSTALL_TERMINAL_REPORTED" -eq 0 ]]; then
|
|
426
626
|
RAINSKILLS_INSTALL_TERMINAL_REPORTED=1
|
|
627
|
+
report_rainskills_lifecycle_event \
|
|
628
|
+
"$(rainskills_telemetry_failure_phase)" \
|
|
629
|
+
"resume" \
|
|
630
|
+
"resume" \
|
|
631
|
+
"failed" \
|
|
632
|
+
"$(rainskills_telemetry_error_code)"
|
|
427
633
|
report_rainskills_installation \
|
|
428
634
|
"failed" \
|
|
429
635
|
"failure" \
|
|
@@ -561,7 +767,7 @@ copy_skill() {
|
|
|
561
767
|
|
|
562
768
|
if [[ ! -e "$dest" ]]; then
|
|
563
769
|
cp -R "$src" "$dest"
|
|
564
|
-
|
|
770
|
+
install_detail_log "[install] 已安装到 $dest"
|
|
565
771
|
INSTALL_COUNT_NEW=$((INSTALL_COUNT_NEW + 1))
|
|
566
772
|
return 0
|
|
567
773
|
fi
|
|
@@ -569,13 +775,13 @@ copy_skill() {
|
|
|
569
775
|
if [[ "$FORCE" -eq 1 ]]; then
|
|
570
776
|
rm -rf "$dest"
|
|
571
777
|
cp -R "$src" "$dest"
|
|
572
|
-
|
|
778
|
+
install_detail_log "[overwrite] 已强制覆盖 $dest"
|
|
573
779
|
INSTALL_COUNT_FORCED=$((INSTALL_COUNT_FORCED + 1))
|
|
574
780
|
return 0
|
|
575
781
|
fi
|
|
576
782
|
|
|
577
783
|
if diff -rq "$src" "$dest" >/dev/null 2>&1; then
|
|
578
|
-
|
|
784
|
+
install_detail_log "[skip] $dest 已是最新"
|
|
579
785
|
INSTALL_COUNT_UNCHANGED=$((INSTALL_COUNT_UNCHANGED + 1))
|
|
580
786
|
return 0
|
|
581
787
|
fi
|
|
@@ -585,7 +791,7 @@ copy_skill() {
|
|
|
585
791
|
changed_files="$( { diff -rq "$src" "$dest" 2>/dev/null || true; } | wc -l | tr -d ' ')"
|
|
586
792
|
rm -rf "$dest"
|
|
587
793
|
cp -R "$src" "$dest"
|
|
588
|
-
|
|
794
|
+
install_detail_log "[update] 已更新 ${dest}(${changed_files} 项变化,本地修改已被覆盖)"
|
|
589
795
|
INSTALL_COUNT_UPDATED=$((INSTALL_COUNT_UPDATED + 1))
|
|
590
796
|
}
|
|
591
797
|
|
|
@@ -596,6 +802,10 @@ parse_args() {
|
|
|
596
802
|
ACTION="refresh"
|
|
597
803
|
shift
|
|
598
804
|
;;
|
|
805
|
+
connect)
|
|
806
|
+
ACTION="connect"
|
|
807
|
+
shift
|
|
808
|
+
;;
|
|
599
809
|
claude|codex|all)
|
|
600
810
|
TARGET="$1"
|
|
601
811
|
shift
|
|
@@ -612,6 +822,10 @@ parse_args() {
|
|
|
612
822
|
FORCE=1
|
|
613
823
|
shift
|
|
614
824
|
;;
|
|
825
|
+
--verbose)
|
|
826
|
+
RAINSKILLS_VERBOSE=1
|
|
827
|
+
shift
|
|
828
|
+
;;
|
|
615
829
|
--skip-mcp)
|
|
616
830
|
SKIP_MCP=1
|
|
617
831
|
shift
|
|
@@ -669,6 +883,109 @@ parse_args() {
|
|
|
669
883
|
done
|
|
670
884
|
}
|
|
671
885
|
|
|
886
|
+
valid_runtime_operation_id() {
|
|
887
|
+
[[ "${1:-}" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$ ]]
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
assert_internal_connect_entry() {
|
|
891
|
+
[[ "${1:-}" == "connect" ]] || return 0
|
|
892
|
+
if [[ "${RAINSKILLS_RUNTIME_CONNECT_COMPLETION:-0}" != "1" ]] \
|
|
893
|
+
|| ! valid_runtime_operation_id "${RAINSKILLS_RUNTIME_OPERATION_ID:-}"; then
|
|
894
|
+
printf '错误:runtime connect 必须通过 Rainskills Node 入口执行。\n' >&2
|
|
895
|
+
return 1
|
|
896
|
+
fi
|
|
897
|
+
|
|
898
|
+
shift
|
|
899
|
+
local target="" environment_kind="" console_origin="" mode=""
|
|
900
|
+
while [[ $# -gt 0 ]]; do
|
|
901
|
+
case "$1" in
|
|
902
|
+
codex|claude|all)
|
|
903
|
+
[[ -z "$target" ]] || return 1
|
|
904
|
+
target="$1"
|
|
905
|
+
shift
|
|
906
|
+
;;
|
|
907
|
+
--saas)
|
|
908
|
+
[[ -z "$mode" ]] || return 1
|
|
909
|
+
mode="saas"
|
|
910
|
+
environment_kind="saas"
|
|
911
|
+
console_origin="$SAAS_DEFAULT_URL"
|
|
912
|
+
shift
|
|
913
|
+
;;
|
|
914
|
+
--self-hosted)
|
|
915
|
+
[[ -z "$mode" ]] || return 1
|
|
916
|
+
mode="self-hosted"
|
|
917
|
+
environment_kind="private"
|
|
918
|
+
shift
|
|
919
|
+
;;
|
|
920
|
+
--rainbond-url)
|
|
921
|
+
[[ $# -ge 2 && -z "$console_origin" ]] || return 1
|
|
922
|
+
console_origin="$2"
|
|
923
|
+
shift 2
|
|
924
|
+
;;
|
|
925
|
+
--allow-insecure-http)
|
|
926
|
+
shift
|
|
927
|
+
;;
|
|
928
|
+
--non-interactive)
|
|
929
|
+
shift
|
|
930
|
+
;;
|
|
931
|
+
*)
|
|
932
|
+
return 1
|
|
933
|
+
;;
|
|
934
|
+
esac
|
|
935
|
+
done
|
|
936
|
+
[[ -n "$target" && -n "$environment_kind" && -n "$console_origin" ]] || return 1
|
|
937
|
+
if [[ "$mode" == "saas" ]]; then
|
|
938
|
+
[[ "$console_origin" == "$SAAS_DEFAULT_URL" ]] || return 1
|
|
939
|
+
else
|
|
940
|
+
assert_canonical_runtime_console_origin "$console_origin" || return 1
|
|
941
|
+
fi
|
|
942
|
+
node "$SCRIPT_DIR/bin/rainskills.js" runtime assert-connect \
|
|
943
|
+
--onboarding-id "$RAINSKILLS_RUNTIME_OPERATION_ID" \
|
|
944
|
+
--target "$target" \
|
|
945
|
+
--environment-kind "$environment_kind" \
|
|
946
|
+
--console-origin "$console_origin" >/dev/null 2>&1 \
|
|
947
|
+
|| {
|
|
948
|
+
printf '错误:runtime connect 内部门禁验证失败。\n' >&2
|
|
949
|
+
return 1
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
assert_canonical_runtime_console_origin() {
|
|
954
|
+
local value="$1"
|
|
955
|
+
python3 - "$value" <<'PY' >/dev/null 2>&1
|
|
956
|
+
import sys
|
|
957
|
+
from urllib.parse import urlsplit
|
|
958
|
+
|
|
959
|
+
value = sys.argv[1]
|
|
960
|
+
if not value or value.strip() != value or len(value) > 2048:
|
|
961
|
+
raise SystemExit(1)
|
|
962
|
+
try:
|
|
963
|
+
parsed = urlsplit(value)
|
|
964
|
+
port = parsed.port
|
|
965
|
+
except Exception:
|
|
966
|
+
raise SystemExit(1)
|
|
967
|
+
if (
|
|
968
|
+
parsed.scheme not in ("http", "https")
|
|
969
|
+
or not parsed.hostname
|
|
970
|
+
or parsed.username is not None
|
|
971
|
+
or parsed.password is not None
|
|
972
|
+
or parsed.path not in ("", "/")
|
|
973
|
+
or parsed.query
|
|
974
|
+
or parsed.fragment
|
|
975
|
+
):
|
|
976
|
+
raise SystemExit(1)
|
|
977
|
+
host = parsed.hostname.lower()
|
|
978
|
+
if ":" in host:
|
|
979
|
+
host = "[{}]".format(host)
|
|
980
|
+
default_port = 80 if parsed.scheme == "http" else 443
|
|
981
|
+
origin = "{}://{}".format(parsed.scheme, host)
|
|
982
|
+
if port is not None and port != default_port:
|
|
983
|
+
origin += ":{}".format(port)
|
|
984
|
+
if value.rstrip("/") != origin:
|
|
985
|
+
raise SystemExit(1)
|
|
986
|
+
PY
|
|
987
|
+
}
|
|
988
|
+
|
|
672
989
|
resolve_target() {
|
|
673
990
|
if [[ -n "$CUSTOM_DEST" ]]; then
|
|
674
991
|
return 0
|
|
@@ -1522,7 +1839,8 @@ device_flow_login_to_rainbond() {
|
|
|
1522
1839
|
fi
|
|
1523
1840
|
[[ "$request_status" -eq 0 ]] || return "$request_status"
|
|
1524
1841
|
|
|
1525
|
-
printf '\
|
|
1842
|
+
printf '\n[RAINSKILLS_USER_MESSAGE_BEGIN:runtime.device-authorization]\n' >&2
|
|
1843
|
+
printf 'Rainbond 设备授权\n' >&2
|
|
1526
1844
|
printf '授权码:%s\n' "$DEVICE_FLOW_USER_CODE" >&2
|
|
1527
1845
|
printf '授权地址:%s\n' "$DEVICE_FLOW_VERIFICATION_URI_COMPLETE" >&2
|
|
1528
1846
|
printf '终端正在等待授权结果,完成后会自动继续,Ctrl+C 可取消。\n' >&2
|
|
@@ -1532,6 +1850,7 @@ device_flow_login_to_rainbond() {
|
|
|
1532
1850
|
else
|
|
1533
1851
|
printf '请在任意能够访问该 Rainbond 平台的电脑上打开上面的地址并完成登录授权。\n' >&2
|
|
1534
1852
|
fi
|
|
1853
|
+
printf '[RAINSKILLS_USER_MESSAGE_END:runtime.device-authorization]\n' >&2
|
|
1535
1854
|
|
|
1536
1855
|
if ! poll_device_authorization "$base_url"; then
|
|
1537
1856
|
cleanup_device_flow
|
|
@@ -1772,6 +2091,51 @@ configure_shell_autoload() {
|
|
|
1772
2091
|
log "[update] 已更新 $rc_file"
|
|
1773
2092
|
}
|
|
1774
2093
|
|
|
2094
|
+
rainskills_package_spec() {
|
|
2095
|
+
local package_version
|
|
2096
|
+
package_version="$(node -p 'require(process.argv[1]).version' "$SCRIPT_DIR/package.json")"
|
|
2097
|
+
[[ "$package_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] \
|
|
2098
|
+
|| die "Rainskills package version 无效"
|
|
2099
|
+
printf 'rainskills@%s\n' "$package_version"
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
codex_local_mcp_matches() {
|
|
2103
|
+
local package_spec="$1"
|
|
2104
|
+
local config_file="$HOME/.codex/config.toml"
|
|
2105
|
+
[[ -f "$config_file" ]] || return 1
|
|
2106
|
+
python3 - "$config_file" "$package_spec" <<'PY' >/dev/null
|
|
2107
|
+
import re
|
|
2108
|
+
import sys
|
|
2109
|
+
|
|
2110
|
+
content = open(sys.argv[1], encoding="utf-8").read()
|
|
2111
|
+
match = re.search(r'(?ms)^\[mcp_servers\.rainbond\]\s*(.*?)(?=^\[|\Z)', content)
|
|
2112
|
+
if not match:
|
|
2113
|
+
raise SystemExit(1)
|
|
2114
|
+
block = match.group(1)
|
|
2115
|
+
expected = ["--yes", sys.argv[2], "mcp", "serve", "--client", "codex"]
|
|
2116
|
+
if re.search(r'^command\s*=\s*"npx"\s*$', block, re.M) and all(re.escape('"{}"'.format(v)) in block for v in expected):
|
|
2117
|
+
raise SystemExit(0)
|
|
2118
|
+
raise SystemExit(1)
|
|
2119
|
+
PY
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
claude_local_mcp_matches() {
|
|
2123
|
+
local package_spec="$1"
|
|
2124
|
+
local config_file="$HOME/.claude.json"
|
|
2125
|
+
[[ -f "$config_file" ]] || return 1
|
|
2126
|
+
python3 - "$config_file" "$package_spec" <<'PY' >/dev/null
|
|
2127
|
+
import json
|
|
2128
|
+
import sys
|
|
2129
|
+
|
|
2130
|
+
payload = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
2131
|
+
server = ((payload.get("mcpServers") or {}).get("rainbond") or {})
|
|
2132
|
+
expected = ["--yes", sys.argv[2], "mcp", "serve", "--client", "claude"]
|
|
2133
|
+
if server.get("command") == "npx" and server.get("args") == expected:
|
|
2134
|
+
raise SystemExit(0)
|
|
2135
|
+
raise SystemExit(1)
|
|
2136
|
+
PY
|
|
2137
|
+
}
|
|
2138
|
+
|
|
1775
2139
|
codex_config_matches() {
|
|
1776
2140
|
local mcp_url="$1"
|
|
1777
2141
|
local config_file="$HOME/.codex/config.toml"
|
|
@@ -1831,7 +2195,13 @@ PY
|
|
|
1831
2195
|
}
|
|
1832
2196
|
|
|
1833
2197
|
configure_codex_mcp() {
|
|
1834
|
-
local
|
|
2198
|
+
local package_spec
|
|
2199
|
+
package_spec="$(rainskills_package_spec)"
|
|
2200
|
+
|
|
2201
|
+
if codex_local_mcp_matches "$package_spec"; then
|
|
2202
|
+
log "[configure] Codex 已连接 Rainskills 本地 MCP"
|
|
2203
|
+
return 0
|
|
2204
|
+
fi
|
|
1835
2205
|
|
|
1836
2206
|
if ! command -v codex >/dev/null 2>&1; then
|
|
1837
2207
|
warn "未找到 Codex CLI,跳过 Codex MCP 配置。"
|
|
@@ -1840,14 +2210,20 @@ configure_codex_mcp() {
|
|
|
1840
2210
|
|
|
1841
2211
|
backup_file "$HOME/.codex/config.toml"
|
|
1842
2212
|
codex mcp remove rainbond >/dev/null 2>&1 || true
|
|
1843
|
-
if ! codex mcp add rainbond --
|
|
2213
|
+
if ! codex mcp add rainbond -- npx --yes "$package_spec" mcp serve --client codex >/dev/null; then
|
|
1844
2214
|
return 1
|
|
1845
2215
|
fi
|
|
1846
2216
|
log "[configure] 已配置 Codex MCP"
|
|
1847
2217
|
}
|
|
1848
2218
|
|
|
1849
2219
|
configure_claude_mcp() {
|
|
1850
|
-
local
|
|
2220
|
+
local package_spec
|
|
2221
|
+
package_spec="$(rainskills_package_spec)"
|
|
2222
|
+
|
|
2223
|
+
if claude_local_mcp_matches "$package_spec"; then
|
|
2224
|
+
log "[configure] Claude 已连接 Rainskills 本地 MCP"
|
|
2225
|
+
return 0
|
|
2226
|
+
fi
|
|
1851
2227
|
|
|
1852
2228
|
if ! command -v claude >/dev/null 2>&1; then
|
|
1853
2229
|
warn "未找到 Claude CLI,跳过 Claude MCP 配置。"
|
|
@@ -1856,7 +2232,7 @@ configure_claude_mcp() {
|
|
|
1856
2232
|
|
|
1857
2233
|
backup_file "$HOME/.claude.json"
|
|
1858
2234
|
claude mcp remove --scope user rainbond >/dev/null 2>&1 || true
|
|
1859
|
-
if ! claude mcp add --scope user --
|
|
2235
|
+
if ! claude mcp add --scope user rainbond -- npx --yes "$package_spec" mcp serve --client claude >/dev/null; then
|
|
1860
2236
|
return 1
|
|
1861
2237
|
fi
|
|
1862
2238
|
log "[configure] 已配置 Claude MCP"
|
|
@@ -1897,13 +2273,46 @@ validate_mcp_connectivity() {
|
|
|
1897
2273
|
fi
|
|
1898
2274
|
|
|
1899
2275
|
if [[ ! "$http_code" =~ ^2 ]]; then
|
|
1900
|
-
cleanup_mcp_validation
|
|
1901
2276
|
if [[ "$http_code" == "404" ]]; then
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2277
|
+
if python3 - "$response_file" "$header_file" <<'PY' >/dev/null; then
|
|
2278
|
+
import json
|
|
2279
|
+
import sys
|
|
2280
|
+
|
|
2281
|
+
body_path, header_path = sys.argv[1:]
|
|
2282
|
+
with open(body_path, "r", encoding="utf-8", errors="replace") as fh:
|
|
2283
|
+
body = fh.read(65537)
|
|
2284
|
+
if len(body.encode("utf-8")) > 65536:
|
|
2285
|
+
raise SystemExit(1)
|
|
2286
|
+
with open(header_path, "r", encoding="utf-8", errors="replace") as fh:
|
|
2287
|
+
headers = fh.read().lower()
|
|
2288
|
+
|
|
2289
|
+
content_type = ""
|
|
2290
|
+
for line in headers.splitlines():
|
|
2291
|
+
if line.startswith("content-type:"):
|
|
2292
|
+
content_type = line.split(":", 1)[1].strip()
|
|
2293
|
+
trimmed = body.strip()
|
|
2294
|
+
verified = False
|
|
2295
|
+
if "text/plain" in content_type:
|
|
2296
|
+
verified = trimmed == "Not Found"
|
|
2297
|
+
elif "text/html" in content_type:
|
|
2298
|
+
lower = trimmed.lower()
|
|
2299
|
+
verified = "<title" in lower and "not found" in lower
|
|
2300
|
+
elif "json" in content_type:
|
|
2301
|
+
try:
|
|
2302
|
+
payload = json.loads(trimmed)
|
|
2303
|
+
except Exception:
|
|
2304
|
+
payload = None
|
|
2305
|
+
if isinstance(payload, dict):
|
|
2306
|
+
verified = any(payload.get(key) in (404, "404") for key in (
|
|
2307
|
+
"code", "status", "status_code", "error_code"
|
|
2308
|
+
))
|
|
2309
|
+
raise SystemExit(0 if verified else 1)
|
|
2310
|
+
PY
|
|
2311
|
+
cleanup_mcp_validation
|
|
2312
|
+
return 2
|
|
2313
|
+
fi
|
|
1906
2314
|
fi
|
|
2315
|
+
cleanup_mcp_validation
|
|
1907
2316
|
die "Rainbond MCP 校验失败,HTTP 状态码 ${http_code}"
|
|
1908
2317
|
fi
|
|
1909
2318
|
|
|
@@ -1971,11 +2380,43 @@ PY
|
|
|
1971
2380
|
log "[verify] Rainbond MCP 可访问"
|
|
1972
2381
|
}
|
|
1973
2382
|
|
|
2383
|
+
select_compatible_mcp_endpoint() {
|
|
2384
|
+
local preferred_url="$1"
|
|
2385
|
+
local generic_url="$2"
|
|
2386
|
+
local token="$3"
|
|
2387
|
+
local validation_status
|
|
2388
|
+
SELECTED_MCP_URL=""
|
|
2389
|
+
|
|
2390
|
+
if validate_mcp_connectivity "$preferred_url" "$token"; then
|
|
2391
|
+
SELECTED_MCP_URL="$preferred_url"
|
|
2392
|
+
return 0
|
|
2393
|
+
else
|
|
2394
|
+
validation_status=$?
|
|
2395
|
+
fi
|
|
2396
|
+
if [[ "$validation_status" -ne 2 ]]; then
|
|
2397
|
+
return "$validation_status"
|
|
2398
|
+
fi
|
|
2399
|
+
|
|
2400
|
+
log "[compat] 当前 Rainbond 未提供 RainSkills 专用 MCP 地址,尝试兼容 MCP 地址。"
|
|
2401
|
+
if validate_mcp_connectivity "$generic_url" "$token"; then
|
|
2402
|
+
SELECTED_MCP_URL="$generic_url"
|
|
2403
|
+
return 0
|
|
2404
|
+
else
|
|
2405
|
+
validation_status=$?
|
|
2406
|
+
fi
|
|
2407
|
+
if [[ "$validation_status" -eq 2 ]]; then
|
|
2408
|
+
die "当前 Rainbond 未提供可用的 MCP 接口,请升级 Rainbond 后重试。"
|
|
2409
|
+
fi
|
|
2410
|
+
return "$validation_status"
|
|
2411
|
+
}
|
|
2412
|
+
|
|
1974
2413
|
migrate_codex_mcp_if_generic() {
|
|
1975
2414
|
local generic_url="$1"
|
|
1976
2415
|
local dedicated_url="$2"
|
|
1977
2416
|
local config_file="$HOME/.codex/config.toml"
|
|
1978
|
-
codex_config_matches "$generic_url"
|
|
2417
|
+
if ! codex_config_matches "$generic_url" && ! codex_config_matches "$dedicated_url"; then
|
|
2418
|
+
return 0
|
|
2419
|
+
fi
|
|
1979
2420
|
|
|
1980
2421
|
local backup="${config_file}.rainskills-backup"
|
|
1981
2422
|
cp "$config_file" "$backup"
|
|
@@ -1985,14 +2426,16 @@ migrate_codex_mcp_if_generic() {
|
|
|
1985
2426
|
warn "Codex MCP 迁移失败,已恢复原配置。"
|
|
1986
2427
|
return 1
|
|
1987
2428
|
fi
|
|
1988
|
-
log "[migrate] Codex MCP 已切换到
|
|
2429
|
+
log "[migrate] Codex MCP 已切换到 Rainskills 本地路由"
|
|
1989
2430
|
}
|
|
1990
2431
|
|
|
1991
2432
|
migrate_claude_mcp_if_generic() {
|
|
1992
2433
|
local generic_url="$1"
|
|
1993
2434
|
local dedicated_url="$2"
|
|
1994
2435
|
local config_file="$HOME/.claude.json"
|
|
1995
|
-
claude_config_matches "$generic_url"
|
|
2436
|
+
if ! claude_config_matches "$generic_url" && ! claude_config_matches "$dedicated_url"; then
|
|
2437
|
+
return 0
|
|
2438
|
+
fi
|
|
1996
2439
|
|
|
1997
2440
|
local backup="${config_file}.rainskills-backup"
|
|
1998
2441
|
cp "$config_file" "$backup"
|
|
@@ -2002,7 +2445,7 @@ migrate_claude_mcp_if_generic() {
|
|
|
2002
2445
|
warn "Claude MCP 迁移失败,已恢复原配置。"
|
|
2003
2446
|
return 1
|
|
2004
2447
|
fi
|
|
2005
|
-
log "[migrate] Claude MCP 已切换到
|
|
2448
|
+
log "[migrate] Claude MCP 已切换到 Rainskills 本地路由"
|
|
2006
2449
|
}
|
|
2007
2450
|
|
|
2008
2451
|
looks_like_jwt() {
|
|
@@ -2194,6 +2637,7 @@ read_cached_rainbond_url() {
|
|
|
2194
2637
|
|
|
2195
2638
|
do_refresh() {
|
|
2196
2639
|
set_rainskills_failure_context "authorization" "authorization_failed"
|
|
2640
|
+
report_rainskills_lifecycle_event "authorize_legacy" "legacy_callback" "authorize" "started" "" "" "browser_loopback"
|
|
2197
2641
|
ensure_python3
|
|
2198
2642
|
|
|
2199
2643
|
# Refresh exists because the cached JWT is broken; ignore inherited tokens
|
|
@@ -2235,9 +2679,10 @@ do_refresh() {
|
|
|
2235
2679
|
base_url="$(normalize_rainbond_url "$base_url_input")"
|
|
2236
2680
|
confirm_insecure_http_if_needed "$base_url"
|
|
2237
2681
|
|
|
2238
|
-
local token generic_mcp_url codex_mcp_url claude_mcp_url
|
|
2682
|
+
local token generic_mcp_url codex_mcp_url claude_mcp_url selected_codex_mcp_url selected_claude_mcp_url
|
|
2239
2683
|
local migrate_codex=0 migrate_claude=0
|
|
2240
2684
|
local validate_codex=0 validate_claude=0
|
|
2685
|
+
local using_legacy_compat=0
|
|
2241
2686
|
obtain_rainbond_token "$base_url" "$DEPLOYMENT_MODE_INPUT"
|
|
2242
2687
|
token="$OBTAINED_RAINBOND_TOKEN"
|
|
2243
2688
|
generic_mcp_url="${base_url}/console/mcp/query"
|
|
@@ -2272,11 +2717,15 @@ do_refresh() {
|
|
|
2272
2717
|
|
|
2273
2718
|
set_rainskills_failure_context "verification" "mcp_verification_failed"
|
|
2274
2719
|
if (( validate_codex == 1 )); then
|
|
2275
|
-
|
|
2720
|
+
select_compatible_mcp_endpoint "$codex_mcp_url" "$generic_mcp_url" "$token"
|
|
2721
|
+
selected_codex_mcp_url="$SELECTED_MCP_URL"
|
|
2722
|
+
[[ "$selected_codex_mcp_url" != "$generic_mcp_url" ]] || using_legacy_compat=1
|
|
2276
2723
|
token="$VALIDATED_TOKEN"
|
|
2277
2724
|
fi
|
|
2278
2725
|
if (( validate_claude == 1 )); then
|
|
2279
|
-
|
|
2726
|
+
select_compatible_mcp_endpoint "$claude_mcp_url" "$generic_mcp_url" "$token"
|
|
2727
|
+
selected_claude_mcp_url="$SELECTED_MCP_URL"
|
|
2728
|
+
[[ "$selected_claude_mcp_url" != "$generic_mcp_url" ]] || using_legacy_compat=1
|
|
2280
2729
|
token="$VALIDATED_TOKEN"
|
|
2281
2730
|
fi
|
|
2282
2731
|
|
|
@@ -2285,16 +2734,21 @@ do_refresh() {
|
|
|
2285
2734
|
configure_shell_autoload
|
|
2286
2735
|
|
|
2287
2736
|
set_rainskills_failure_context "configuration" "mcp_configuration_failed"
|
|
2288
|
-
|
|
2737
|
+
report_rainskills_lifecycle_event "configure_mcp" "configure_mcp" "configure_mcp" "started"
|
|
2738
|
+
if (( validate_codex == 1 )); then
|
|
2289
2739
|
migrate_codex_mcp_if_generic "$generic_mcp_url" "$codex_mcp_url" \
|
|
2290
|
-
|| die "Codex MCP
|
|
2740
|
+
|| die "Codex MCP 本地路由迁移失败"
|
|
2291
2741
|
fi
|
|
2292
|
-
if ((
|
|
2742
|
+
if (( validate_claude == 1 )); then
|
|
2293
2743
|
migrate_claude_mcp_if_generic "$generic_mcp_url" "$claude_mcp_url" \
|
|
2294
|
-
|| die "Claude MCP
|
|
2744
|
+
|| die "Claude MCP 本地路由迁移失败"
|
|
2295
2745
|
fi
|
|
2296
2746
|
log ""
|
|
2297
|
-
|
|
2747
|
+
if (( using_legacy_compat == 1 )); then
|
|
2748
|
+
log "JWT 刷新完成。当前 Rainbond 将继续使用兼容 MCP 地址。"
|
|
2749
|
+
else
|
|
2750
|
+
log "JWT 刷新完成。脚本管理的旧 MCP 地址已按需迁移到 Rainskills 本地路由。"
|
|
2751
|
+
fi
|
|
2298
2752
|
if (( validate_codex == 1 || validate_claude == 1 )); then
|
|
2299
2753
|
log "请重启 Claude Code 或 Codex 让新 JWT 生效(它们在启动时一次性读取 RAINBOND_JWT)。"
|
|
2300
2754
|
fi
|
|
@@ -2302,6 +2756,7 @@ do_refresh() {
|
|
|
2302
2756
|
log "如果想立刻在当前终端使用,请执行:source ${ACTIVE_SHELL_RC}"
|
|
2303
2757
|
fi
|
|
2304
2758
|
RAINSKILLS_INSTALL_TERMINAL_REPORTED=1
|
|
2759
|
+
report_rainskills_lifecycle_event "configure_mcp" "configure_mcp" "configure_mcp" "completed"
|
|
2305
2760
|
report_rainskills_installation "configured" "success"
|
|
2306
2761
|
}
|
|
2307
2762
|
|
|
@@ -2316,6 +2771,7 @@ configure_mcp() {
|
|
|
2316
2771
|
fi
|
|
2317
2772
|
|
|
2318
2773
|
set_rainskills_failure_context "authorization" "authorization_failed"
|
|
2774
|
+
report_rainskills_lifecycle_event "authorize_legacy" "legacy_callback" "authorize" "started" "" "" "browser_loopback"
|
|
2319
2775
|
ensure_python3
|
|
2320
2776
|
resolve_deployment_mode
|
|
2321
2777
|
|
|
@@ -2354,54 +2810,67 @@ configure_mcp() {
|
|
|
2354
2810
|
base_url="$(normalize_rainbond_url "$base_url_input")"
|
|
2355
2811
|
confirm_insecure_http_if_needed "$base_url"
|
|
2356
2812
|
|
|
2357
|
-
local token codex_mcp_url claude_mcp_url
|
|
2813
|
+
local token generic_mcp_url codex_mcp_url claude_mcp_url selected_codex_mcp_url selected_claude_mcp_url
|
|
2358
2814
|
obtain_rainbond_token "$base_url" "$DEPLOYMENT_MODE_INPUT"
|
|
2359
2815
|
token="$OBTAINED_RAINBOND_TOKEN"
|
|
2360
2816
|
RAINSKILLS_INSTALL_CLIENT="$(rainskills_install_client_for_target "$TARGET")"
|
|
2361
2817
|
record_rainskills_authorization "$base_url" "$token"
|
|
2818
|
+
generic_mcp_url="${base_url}/console/mcp/query"
|
|
2362
2819
|
codex_mcp_url="${base_url}/console/mcp/rainskills/codex/query"
|
|
2363
2820
|
claude_mcp_url="${base_url}/console/mcp/rainskills/claude-code/query"
|
|
2364
2821
|
|
|
2365
2822
|
set_rainskills_failure_context "verification" "mcp_verification_failed"
|
|
2366
2823
|
case "$TARGET" in
|
|
2367
2824
|
codex)
|
|
2368
|
-
|
|
2825
|
+
select_compatible_mcp_endpoint "$codex_mcp_url" "$generic_mcp_url" "$token"
|
|
2826
|
+
selected_codex_mcp_url="$SELECTED_MCP_URL"
|
|
2369
2827
|
token="$VALIDATED_TOKEN"
|
|
2370
2828
|
;;
|
|
2371
2829
|
claude)
|
|
2372
|
-
|
|
2830
|
+
select_compatible_mcp_endpoint "$claude_mcp_url" "$generic_mcp_url" "$token"
|
|
2831
|
+
selected_claude_mcp_url="$SELECTED_MCP_URL"
|
|
2373
2832
|
token="$VALIDATED_TOKEN"
|
|
2374
2833
|
;;
|
|
2375
2834
|
all)
|
|
2376
|
-
|
|
2835
|
+
select_compatible_mcp_endpoint "$codex_mcp_url" "$generic_mcp_url" "$token"
|
|
2836
|
+
selected_codex_mcp_url="$SELECTED_MCP_URL"
|
|
2377
2837
|
token="$VALIDATED_TOKEN"
|
|
2378
|
-
|
|
2838
|
+
select_compatible_mcp_endpoint "$claude_mcp_url" "$generic_mcp_url" "$token"
|
|
2839
|
+
selected_claude_mcp_url="$SELECTED_MCP_URL"
|
|
2379
2840
|
token="$VALIDATED_TOKEN"
|
|
2380
2841
|
;;
|
|
2381
2842
|
esac
|
|
2382
2843
|
|
|
2383
2844
|
# Refresh this process's env for any downstream CLI behavior that resolves it.
|
|
2384
2845
|
export RAINBOND_JWT="$token"
|
|
2385
|
-
|
|
2386
|
-
|
|
2846
|
+
if [[ "$ACTION" == "connect" ]]; then
|
|
2847
|
+
node "$SCRIPT_DIR/bin/rainskills.js" runtime persist-connect-credential \
|
|
2848
|
+
--onboarding-id "$RAINSKILLS_RUNTIME_OPERATION_ID" >/dev/null \
|
|
2849
|
+
|| die "runtime connect 凭据安全写入失败。"
|
|
2850
|
+
else
|
|
2851
|
+
write_token_file "$token" "$base_url"
|
|
2852
|
+
configure_shell_autoload
|
|
2853
|
+
fi
|
|
2387
2854
|
|
|
2388
2855
|
set_rainskills_failure_context "configuration" "mcp_configuration_failed"
|
|
2856
|
+
report_rainskills_lifecycle_event "configure_mcp" "configure_mcp" "configure_mcp" "started"
|
|
2389
2857
|
local configured=0
|
|
2390
2858
|
case "$TARGET" in
|
|
2391
2859
|
codex)
|
|
2392
|
-
configure_codex_mcp
|
|
2860
|
+
configure_codex_mcp && configured=1 || true
|
|
2393
2861
|
;;
|
|
2394
2862
|
claude)
|
|
2395
|
-
configure_claude_mcp
|
|
2863
|
+
configure_claude_mcp && configured=1 || true
|
|
2396
2864
|
;;
|
|
2397
2865
|
all)
|
|
2398
|
-
configure_codex_mcp
|
|
2399
|
-
configure_claude_mcp
|
|
2866
|
+
configure_codex_mcp && configured=$((configured + 1)) || true
|
|
2867
|
+
configure_claude_mcp && configured=$((configured + 1)) || true
|
|
2400
2868
|
;;
|
|
2401
2869
|
esac
|
|
2402
2870
|
|
|
2403
2871
|
(( configured > 0 )) || die "所选平台都未能完成 MCP 配置。"
|
|
2404
2872
|
RAINSKILLS_INSTALL_TERMINAL_REPORTED=1
|
|
2873
|
+
report_rainskills_lifecycle_event "configure_mcp" "configure_mcp" "configure_mcp" "completed"
|
|
2405
2874
|
report_rainskills_installation "configured" "success"
|
|
2406
2875
|
|
|
2407
2876
|
if [[ -n "$ACTIVE_SHELL_RC" ]]; then
|
|
@@ -2418,6 +2887,24 @@ main() {
|
|
|
2418
2887
|
return 0
|
|
2419
2888
|
fi
|
|
2420
2889
|
|
|
2890
|
+
if [[ "$ACTION" == "connect" ]]; then
|
|
2891
|
+
[[ "${RAINSKILLS_RUNTIME_CONNECT_COMPLETION:-0}" == "1" ]] \
|
|
2892
|
+
&& valid_runtime_operation_id "${RAINSKILLS_RUNTIME_OPERATION_ID:-}" \
|
|
2893
|
+
|| die "runtime connect 必须通过 Rainskills Node 入口执行。"
|
|
2894
|
+
if [[ "$DEPLOYMENT_MODE_INPUT" == "self-hosted" ]]; then
|
|
2895
|
+
assert_canonical_runtime_console_origin "$RAINBOND_URL_INPUT" \
|
|
2896
|
+
|| die "Rainbond Console 地址必须是已经安全校验的规范 origin。"
|
|
2897
|
+
fi
|
|
2898
|
+
ALLOW_INSECURE_HTTP=1
|
|
2899
|
+
resolve_target
|
|
2900
|
+
RAINSKILLS_INSTALL_CLIENT="$(rainskills_install_client_for_target "$TARGET")"
|
|
2901
|
+
configure_mcp
|
|
2902
|
+
if [[ "${RAINSKILLS_RUNTIME_CONNECT_COMPLETION:-0}" == "1" ]]; then
|
|
2903
|
+
node "$SCRIPT_DIR/bin/rainskills.js" runtime complete-connect \
|
|
2904
|
+
--onboarding-id "$RAINSKILLS_RUNTIME_OPERATION_ID"
|
|
2905
|
+
fi
|
|
2906
|
+
return 0
|
|
2907
|
+
fi
|
|
2421
2908
|
resolve_target
|
|
2422
2909
|
RAINSKILLS_INSTALL_CLIENT="$(rainskills_install_client_for_target "$TARGET")"
|
|
2423
2910
|
set_rainskills_failure_context "skill_installation" "skill_installation_failed"
|
|
@@ -2440,34 +2927,20 @@ main() {
|
|
|
2440
2927
|
done < <(collect_destinations)
|
|
2441
2928
|
|
|
2442
2929
|
for dest in "${destinations[@]}"; do
|
|
2443
|
-
|
|
2930
|
+
install_detail_log "安装到:$dest"
|
|
2444
2931
|
for skill_dir in "${skills[@]}"; do
|
|
2445
2932
|
copy_skill "$skill_dir" "$dest"
|
|
2446
2933
|
done
|
|
2447
2934
|
done
|
|
2448
2935
|
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
fi
|
|
2454
|
-
|
|
2455
|
-
log ""
|
|
2456
|
-
log "安装完成。本次:${INSTALL_COUNT_NEW} 项新装 / ${INSTALL_COUNT_UPDATED} 项已更新 / ${INSTALL_COUNT_UNCHANGED} 项已是最新 / ${INSTALL_COUNT_FORCED} 项强制覆盖"
|
|
2457
|
-
case "$TARGET" in
|
|
2458
|
-
codex)
|
|
2459
|
-
log "请重新加载 shell 环境并重启 Codex 以加载新技能和 MCP。"
|
|
2460
|
-
;;
|
|
2461
|
-
claude)
|
|
2462
|
-
log "请重新加载 shell 环境并重启 Claude Code 以加载新技能和 MCP。"
|
|
2463
|
-
;;
|
|
2464
|
-
all)
|
|
2465
|
-
log "请重启 Codex / Claude Code 以加载新技能和 MCP。"
|
|
2466
|
-
;;
|
|
2467
|
-
esac
|
|
2936
|
+
install_detail_log ""
|
|
2937
|
+
install_detail_log "安装完成。本次:${INSTALL_COUNT_NEW} 项新装 / ${INSTALL_COUNT_UPDATED} 项已更新 / ${INSTALL_COUNT_UNCHANGED} 项已是最新 / ${INSTALL_COUNT_FORCED} 项强制覆盖"
|
|
2938
|
+
install_detail_log ""
|
|
2939
|
+
print_capability_summary
|
|
2468
2940
|
}
|
|
2469
2941
|
|
|
2470
2942
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
2943
|
+
assert_internal_connect_entry "$@"
|
|
2471
2944
|
trap 'handle_installer_signal 130' INT
|
|
2472
2945
|
trap 'handle_installer_signal 143' TERM
|
|
2473
2946
|
trap 'handle_installer_exit "$?"' EXIT
|