@guiho/runx 0.2.6 → 0.4.0

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.
Files changed (92) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/DOCS.md +128 -109
  3. package/README.md +58 -58
  4. package/devops/build-binaries.ts +44 -62
  5. package/devops/devops.xdocs.md +8 -5
  6. package/devops/install.ps1 +182 -152
  7. package/devops/install.sh +197 -262
  8. package/devops/installers.spec.ts +31 -0
  9. package/devops/verify-release-assets.ts +17 -0
  10. package/docs/decisions/decisions.xdocs.md +4 -0
  11. package/docs/decisions/interactive-init-manifest.md +187 -0
  12. package/docs/docs.xdocs.md +3 -1
  13. package/docs/plans/interactive-init-manifest.md +177 -0
  14. package/docs/plans/plans.xdocs.md +11 -0
  15. package/docs/plans/rfc-0034-cli-compliance-migration.md +586 -0
  16. package/docs/plans/upgrade-reliability-implementation.md +95 -0
  17. package/docs/reviews/implementation/implementation.xdocs.md +6 -0
  18. package/docs/reviews/implementation/interactive-init-manifest-review.md +69 -0
  19. package/docs/reviews/implementation/rfc-0034-cli-compliance-migration-review.md +59 -0
  20. package/docs/reviews/plans/interactive-init-manifest-review.md +82 -0
  21. package/docs/reviews/plans/plans.xdocs.md +10 -0
  22. package/docs/reviews/plans/rfc-0034-cli-compliance-migration-review.md +80 -0
  23. package/docs/reviews/plans/upgrade-reliability-implementation-review.md +65 -0
  24. package/docs/superpowers/specs/2026-07-15-upgrade-reliability-design.md +662 -0
  25. package/docs/superpowers/specs/specs.xdocs.md +24 -0
  26. package/docs/superpowers/superpowers.xdocs.md +21 -0
  27. package/docs/todo/rfc-0034-cli-compliance-migration-implementation.md +58 -0
  28. package/docs/todo/rfc-0034-cli-compliance-migration.md +151 -0
  29. package/docs/todo/todo.xdocs.md +6 -2
  30. package/docs/validation/interactive-init-manifest.md +79 -0
  31. package/docs/validation/rfc-0034-cli-compliance-migration.md +67 -0
  32. package/docs/validation/upgrade-reliability.md +124 -0
  33. package/docs/validation/validation.xdocs.md +9 -0
  34. package/library/agents.d.ts +28 -4
  35. package/library/agents.d.ts.map +1 -1
  36. package/library/agents.js +143 -41
  37. package/library/cli.d.ts.map +1 -1
  38. package/library/cli.js +297 -317
  39. package/library/configuration.d.ts +57 -0
  40. package/library/configuration.d.ts.map +1 -0
  41. package/library/configuration.js +111 -0
  42. package/library/embedded-resources.d.ts +6 -1
  43. package/library/embedded-resources.d.ts.map +1 -1
  44. package/library/embedded-resources.js +10 -4
  45. package/library/executor.d.ts +5 -1
  46. package/library/executor.d.ts.map +1 -1
  47. package/library/executor.js +10 -10
  48. package/library/help.d.ts +8 -4
  49. package/library/help.d.ts.map +1 -1
  50. package/library/help.js +70 -42
  51. package/library/init.d.ts +18 -0
  52. package/library/init.d.ts.map +1 -0
  53. package/library/init.js +43 -0
  54. package/library/manifest.d.ts +4 -40
  55. package/library/manifest.d.ts.map +1 -1
  56. package/library/manifest.js +4 -112
  57. package/library/path-utils.d.ts +13 -0
  58. package/library/path-utils.d.ts.map +1 -0
  59. package/library/path-utils.js +82 -0
  60. package/library/recovery.d.ts +7 -0
  61. package/library/recovery.d.ts.map +1 -0
  62. package/library/recovery.js +23 -0
  63. package/library/release-catalog.d.ts +32 -0
  64. package/library/release-catalog.d.ts.map +1 -0
  65. package/library/release-catalog.js +124 -0
  66. package/library/self-management.d.ts +24 -4
  67. package/library/self-management.d.ts.map +1 -1
  68. package/library/self-management.js +279 -99
  69. package/library/storage.d.ts +13 -0
  70. package/library/storage.d.ts.map +1 -0
  71. package/library/storage.js +38 -0
  72. package/library/types.d.ts +11 -16
  73. package/library/types.d.ts.map +1 -1
  74. package/library/types.js +3 -0
  75. package/library/update-cache.d.ts +21 -0
  76. package/library/update-cache.d.ts.map +1 -0
  77. package/library/update-cache.js +68 -0
  78. package/library/upgrade-reporting.d.ts +11 -0
  79. package/library/upgrade-reporting.d.ts.map +1 -0
  80. package/library/upgrade-reporting.js +67 -0
  81. package/library/upgrade-types.d.ts +72 -0
  82. package/library/upgrade-types.d.ts.map +1 -0
  83. package/library/upgrade-types.js +3 -0
  84. package/package.json +6 -3
  85. package/scripts/runx-bin.mjs +49 -0
  86. package/scripts/runx-bin.spec.ts +62 -0
  87. package/scripts/scripts.xdocs.md +3 -3
  88. package/skills/guiho-s-runx/SKILL.md +64 -59
  89. package/skills/guiho-s-runx/guiho-s-runx.xdocs.md +7 -4
  90. package/skills/skills.xdocs.md +1 -1
  91. package/docs/todo/implement-runx-alpha.md +0 -36
  92. package/scripts/runx-bin.ts +0 -24
package/devops/install.sh CHANGED
@@ -1,324 +1,259 @@
1
- #!/usr/bin/env bash
2
- #
3
- # Install GUIHO RunX as a native CLI binary from GitHub Releases.
4
- #
5
- set -Eeuo pipefail
1
+ #!/usr/bin/env sh
2
+ set -eu
6
3
 
7
4
  REPO="${RUNX_REPO:-CGuiho/runx}"
8
5
  VERSION="${RUNX_VERSION:-latest}"
9
6
  INSTALL_DIR="${RUNX_INSTALL_DIR:-$HOME/.local/bin}"
7
+ DOWNLOAD_BASE_URL="${RUNX_DOWNLOAD_BASE_URL:-}"
10
8
  ARCH_OVERRIDE=""
11
9
  VARIANT_OVERRIDE=""
12
10
  OS=""
13
11
  ARCH=""
14
- CANDIDATES=()
12
+ TARGET_VERSION=""
15
13
  TMP=""
16
14
 
17
- cleanup() {
18
- if [[ -n "$TMP" ]]; then
19
- rm -rf -- "$TMP"
20
- fi
21
- }
22
-
23
- trap cleanup EXIT
15
+ cleanup() { [ -z "$TMP" ] || rm -rf -- "$TMP"; }
16
+ trap cleanup EXIT HUP INT TERM
17
+ fail() { printf 'error: %s\n' "$*" >&2; exit 1; }
18
+ require_command() { command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"; }
19
+ require_value() { [ -n "${2:-}" ] || fail "$1 requires a value"; }
24
20
 
25
21
  usage() {
26
22
  cat <<EOF
27
- Install GUIHO RunX as a native CLI binary from GitHub Releases.
23
+ Install GUIHO RunX as a verified native CLI binary from GitHub Releases.
28
24
 
29
- Usage: install.sh [flags]
25
+ Usage: install.sh [--version VERSION] [--arch x64|arm64] [--variant baseline|default|modern] [--install-dir DIR]
30
26
 
31
- Flags:
32
- -v, --version VERSION Version to install (default: latest).
33
- Examples: latest, 0.1.1, @guiho/runx@0.1.1
34
- --arch ARCH Force architecture: x64 | arm64 (default: auto-detect)
35
- --variant VARIANT Force x64 variant: baseline | default | modern (default: baseline)
36
- --install-dir DIR Install directory (default: \$HOME/.local/bin)
37
- -h, --help Show this help
38
-
39
- Environment variables:
40
- RUNX_VERSION Same as --version
41
- RUNX_REPO GitHub repo (default: CGuiho/runx)
42
- RUNX_INSTALL_DIR Same as --install-dir
27
+ VERSION may be an exact stable or prerelease version. The default is latest stable.
43
28
  EOF
44
29
  }
45
30
 
46
- fail() {
47
- printf 'error: %s\n' "$*" >&2
48
- exit 1
49
- }
50
-
51
- require_command() {
52
- command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
53
- }
54
-
55
- require_value() {
56
- local option="$1"
57
- local value="${2:-}"
58
-
59
- [[ -n "$value" ]] || fail "$option requires a value"
60
- }
61
-
62
31
  parse_args() {
63
- while [[ $# -gt 0 ]]; do
32
+ while [ "$#" -gt 0 ]; do
64
33
  case "$1" in
65
- -v | --version)
66
- require_value "$1" "${2:-}"
67
- VERSION="$2"
68
- shift 2
69
- ;;
70
- --version=*)
71
- VERSION="${1#*=}"
72
- shift
73
- ;;
74
- --arch)
75
- require_value "$1" "${2:-}"
76
- ARCH_OVERRIDE="$2"
77
- shift 2
78
- ;;
79
- --arch=*)
80
- ARCH_OVERRIDE="${1#*=}"
81
- shift
82
- ;;
83
- --variant)
84
- require_value "$1" "${2:-}"
85
- VARIANT_OVERRIDE="$2"
86
- shift 2
87
- ;;
88
- --variant=*)
89
- VARIANT_OVERRIDE="${1#*=}"
90
- shift
91
- ;;
92
- --install-dir)
93
- require_value "$1" "${2:-}"
94
- INSTALL_DIR="$2"
95
- shift 2
96
- ;;
97
- --install-dir=*)
98
- INSTALL_DIR="${1#*=}"
99
- shift
100
- ;;
101
- -h | --help)
102
- usage
103
- exit 0
104
- ;;
105
- *)
106
- fail "unknown flag: $1. Run with --help for usage."
107
- ;;
34
+ -v|--version) require_value "$1" "${2:-}"; VERSION="$2"; shift 2 ;;
35
+ --version=*) VERSION=${1#*=}; shift ;;
36
+ --arch) require_value "$1" "${2:-}"; ARCH_OVERRIDE="$2"; shift 2 ;;
37
+ --arch=*) ARCH_OVERRIDE=${1#*=}; shift ;;
38
+ --variant) require_value "$1" "${2:-}"; VARIANT_OVERRIDE="$2"; shift 2 ;;
39
+ --variant=*) VARIANT_OVERRIDE=${1#*=}; shift ;;
40
+ --install-dir) require_value "$1" "${2:-}"; INSTALL_DIR="$2"; shift 2 ;;
41
+ --install-dir=*) INSTALL_DIR=${1#*=}; shift ;;
42
+ -h|--help) usage; exit 0 ;;
43
+ *) fail "unknown flag: $1" ;;
108
44
  esac
109
45
  done
110
46
  }
111
47
 
112
- detect_os() {
113
- case "$(uname -s)" in
114
- Linux) printf 'linux\n' ;;
115
- Darwin) printf 'macos\n' ;;
116
- *) fail "unsupported OS: $(uname -s)" ;;
117
- esac
118
- }
119
-
48
+ detect_os() { case "$(uname -s)" in Linux) printf 'linux\n' ;; Darwin) printf 'darwin\n' ;; *) fail "unsupported OS: $(uname -s)" ;; esac; }
120
49
  detect_arch() {
121
- if [[ -n "$ARCH_OVERRIDE" ]]; then
122
- case "$ARCH_OVERRIDE" in
123
- x64 | arm64) printf '%s\n' "$ARCH_OVERRIDE" ;;
124
- *) fail "invalid --arch '$ARCH_OVERRIDE'. Must be x64 or arm64." ;;
125
- esac
126
- return
127
- fi
128
-
129
- case "$(uname -m)" in
130
- x86_64 | amd64) printf 'x64\n' ;;
131
- arm64 | aarch64) printf 'arm64\n' ;;
132
- *) fail "unsupported architecture: $(uname -m)" ;;
133
- esac
50
+ detected=${ARCH_OVERRIDE:-$(uname -m)}
51
+ case "$detected" in x64|x86_64|amd64) printf 'x64\n' ;; arm64|aarch64) printf 'arm64\n' ;; *) fail "unsupported architecture: $detected" ;; esac
134
52
  }
135
53
 
136
- build_candidates() {
137
- local variant="${VARIANT_OVERRIDE:-baseline}"
138
-
139
- if [[ "$ARCH" == "arm64" ]]; then
140
- [[ -z "$VARIANT_OVERRIDE" ]] || fail "--variant is only valid for x64 installs"
141
- CANDIDATES=("runx-${OS}-arm64")
142
- return
143
- fi
144
-
145
- case "$variant" in
146
- baseline)
147
- CANDIDATES=("runx-${OS}-x64-baseline" "runx-${OS}-x64" "runx-${OS}-x64-modern")
148
- ;;
149
- default)
150
- CANDIDATES=("runx-${OS}-x64" "runx-${OS}-x64-baseline" "runx-${OS}-x64-modern")
151
- ;;
152
- modern)
153
- CANDIDATES=("runx-${OS}-x64-modern" "runx-${OS}-x64" "runx-${OS}-x64-baseline")
154
- ;;
155
- *)
156
- fail "invalid --variant '$variant'. Must be baseline, default, or modern."
54
+ normalize_version() {
55
+ value=$1
56
+ value=${value#@guiho/runx@}
57
+ value=${value#v}
58
+ printf '%s\n' "$value" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$' || fail "invalid RunX version: $1"
59
+ case "$value" in
60
+ *-*)
61
+ prerelease=${value#*-}
62
+ prerelease=${prerelease%%+*}
63
+ old_ifs=$IFS
64
+ IFS=.
65
+ set -- $prerelease
66
+ IFS=$old_ifs
67
+ for identifier do
68
+ if printf '%s\n' "$identifier" | grep -Eq '^[0-9]+$'; then
69
+ case "$identifier" in 0|[1-9]*) ;; 0*) fail "invalid RunX version: $value" ;; esac
70
+ fi
71
+ done
157
72
  ;;
158
73
  esac
74
+ printf '%s\n' "$value"
159
75
  }
160
76
 
161
- build_url() {
162
- local asset="$1"
77
+ resolve_target_version() {
78
+ if [ "$VERSION" != latest ]; then normalize_version "$VERSION"; return; fi
79
+ printf 'Resolving latest stable RunX release...\n' >&2
80
+ effective=$(curl --fail --location --silent --show-error --proto '=https' --tlsv1.2 --output /dev/null --write-out '%{url_effective}' "https://github.com/${REPO}/releases/latest")
81
+ tag=${effective##*/}
82
+ tag=$(printf '%s' "$tag" | sed 's/%40/@/g; s/%2[Ff]/\//g')
83
+ normalize_version "$tag"
84
+ }
163
85
 
164
- if [[ "$VERSION" == "latest" ]]; then
165
- printf 'https://github.com/%s/releases/latest/download/%s\n' "$REPO" "$asset"
86
+ build_candidates() {
87
+ variant=${VARIANT_OVERRIDE:-baseline}
88
+ if [ "$ARCH" = arm64 ]; then
89
+ [ -z "$VARIANT_OVERRIDE" ] || fail '--variant is only valid for x64 installs'
90
+ printf 'runx-%s-arm64\n' "$OS"
166
91
  return
167
92
  fi
168
-
169
- local tag
170
- case "$VERSION" in
171
- @guiho/runx@*) tag="$VERSION" ;;
172
- @*) tag="$VERSION" ;;
173
- *) tag="@guiho/runx@${VERSION}" ;;
93
+ case "$variant" in
94
+ baseline) printf 'runx-%s-x64-baseline\nrunx-%s-x64\nrunx-%s-x64-modern\n' "$OS" "$OS" "$OS" ;;
95
+ default) printf 'runx-%s-x64\nrunx-%s-x64-baseline\nrunx-%s-x64-modern\n' "$OS" "$OS" "$OS" ;;
96
+ modern) printf 'runx-%s-x64-modern\nrunx-%s-x64\nrunx-%s-x64-baseline\n' "$OS" "$OS" "$OS" ;;
97
+ *) fail "invalid variant: $variant" ;;
174
98
  esac
175
-
176
- local encoded_tag="${tag//@/%40}"
177
- encoded_tag="${encoded_tag//\//%2F}"
178
- printf 'https://github.com/%s/releases/download/%s/%s\n' "$REPO" "$encoded_tag" "$asset"
179
99
  }
180
100
 
181
101
  verify_native_binary() {
182
- local path="$1"
183
- local magic2
184
- local magic4
185
-
186
- magic2="$(LC_ALL=C head -c 2 "$path" 2>/dev/null || true)"
187
- magic4="$(LC_ALL=C head -c 4 "$path" 2>/dev/null || true)"
188
-
189
- case "$magic4" in
190
- $'\177ELF' | $'\xcf\xfa\xed\xfe' | $'\xce\xfa\xed\xfe' | $'\xca\xfe\xba\xbe') return 0 ;;
191
- '<!DO' | '<htm') return 1 ;;
192
- esac
193
-
194
- case "$magic2" in
195
- MZ) return 0 ;;
196
- '#!') return 1 ;;
197
- esac
198
-
199
- return 2
200
- }
201
-
202
- shell_profile_path() {
203
- local shell_name="${SHELL##*/}"
204
-
205
- case "$shell_name" in
206
- fish) printf '%s/.config/fish/config.fish\n' "$HOME" ;;
207
- zsh) printf '%s/.zshrc\n' "$HOME" ;;
208
- bash)
209
- if [[ "$OS" == "macos" && -f "$HOME/.bash_profile" ]]; then
210
- printf '%s/.bash_profile\n' "$HOME"
211
- else
212
- printf '%s/.bashrc\n' "$HOME"
213
- fi
214
- ;;
215
- *) printf '%s/.profile\n' "$HOME" ;;
216
- esac
217
- }
218
-
219
- path_contains_install_dir() {
220
- case ":$PATH:" in
221
- *:"$INSTALL_DIR":*) return 0 ;;
102
+ magic=$(od -An -tx1 -N4 "$1" 2>/dev/null | tr -d ' \n')
103
+ case "$OS:$magic" in
104
+ linux:7f454c46|darwin:cffaedfe|darwin:cefaedfe|darwin:cafebabe|darwin:feedfacf|darwin:feedface) return 0 ;;
222
105
  *) return 1 ;;
223
106
  esac
224
107
  }
225
108
 
226
- append_path_to_profile() {
227
- local profile="$1"
228
- local shell_name="${SHELL##*/}"
229
-
230
- if [[ "$shell_name" == "fish" ]]; then
231
- mkdir -p "$HOME/.config/fish"
232
- if [[ -f "$profile" ]] && grep -Fq "$INSTALL_DIR" "$profile"; then
233
- return 0
109
+ verify_installed_version() {
110
+ verify_output=$(mktemp "${TMPDIR:-/tmp}/runx-version.XXXXXX") || return 1
111
+ "$1" --version >"$verify_output" 2>&1 &
112
+ verify_pid=$!
113
+ verify_waited=0
114
+ while kill -0 "$verify_pid" 2>/dev/null; do
115
+ if [ "$verify_waited" -ge 10 ]; then
116
+ kill "$verify_pid" 2>/dev/null || true
117
+ wait "$verify_pid" 2>/dev/null || true
118
+ rm -f -- "$verify_output"
119
+ printf 'error: installed RunX version check timed out after 10 seconds\n' >&2
120
+ return 1
234
121
  fi
235
- printf '\n# Added by runx installer\nfish_add_path %q\n' "$INSTALL_DIR" >>"$profile"
236
- return 0
237
- fi
238
-
239
- if [[ -f "$profile" ]] && grep -Fq "$INSTALL_DIR" "$profile"; then
240
- return 0
122
+ sleep 1
123
+ verify_waited=$((verify_waited + 1))
124
+ done
125
+ if ! wait "$verify_pid"; then
126
+ cat "$verify_output" >&2
127
+ rm -f -- "$verify_output"
128
+ return 1
241
129
  fi
242
-
243
- printf '\n# Added by runx installer\nexport PATH=%q:\$PATH\n' "$INSTALL_DIR" >>"$profile"
130
+ actual=$(cat "$verify_output")
131
+ rm -f -- "$verify_output"
132
+ [ "$actual" = "$TARGET_VERSION" ] || { printf 'error: installed RunX reported %s; expected %s\n' "${actual:-<empty>}" "$TARGET_VERSION" >&2; return 1; }
244
133
  }
245
134
 
246
135
  ensure_path() {
247
- export PATH="$INSTALL_DIR:$PATH"
248
-
249
- if path_contains_install_dir; then
250
- printf 'runx: %s is available in PATH for this installer process.\n' "$INSTALL_DIR"
136
+ PATH="$INSTALL_DIR:$PATH"; export PATH
137
+ profile="$HOME/.profile"
138
+ case "${SHELL##*/}" in zsh) profile="$HOME/.zshrc" ;; bash) profile="$HOME/.bashrc" ;; fish) profile="$HOME/.config/fish/config.fish" ;; esac
139
+ mkdir -p -- "$(dirname "$profile")"
140
+ if ! grep -Fq "$INSTALL_DIR" "$profile" 2>/dev/null; then
141
+ if [ "${SHELL##*/}" = fish ]; then printf '\n# Added by runx installer\nfish_add_path %s\n' "$INSTALL_DIR" >>"$profile"
142
+ else printf '\n# Added by runx installer\nexport PATH=%s:$PATH\n' "$INSTALL_DIR" >>"$profile"; fi
251
143
  fi
252
-
253
- local profile
254
- profile="$(shell_profile_path)"
255
- append_path_to_profile "$profile"
256
-
257
- printf 'runx: ensured %s is added to PATH in %s\n' "$INSTALL_DIR" "$profile"
258
- printf 'runx: restart your terminal, or run this for the current shell:\n'
259
- printf ' export PATH=%q:\$PATH\n' "$INSTALL_DIR"
260
144
  }
261
145
 
262
- check_shadowing() {
263
- local installed="$INSTALL_DIR/runx"
264
- local resolved
265
-
266
- resolved="$(command -v runx 2>/dev/null || true)"
267
- [[ -n "$resolved" ]] || return 0
268
- [[ "$resolved" == "$installed" ]] && return 0
269
-
270
- printf '\nwarning: another runx appears earlier in PATH:\n' >&2
271
- printf ' %s\n' "$resolved" >&2
272
- printf 'The newly installed binary is at:\n' >&2
273
- printf ' %s\n' "$installed" >&2
146
+ download_asset() {
147
+ url=$1
148
+ output=$2
149
+ set +e
150
+ if [ -n "$DOWNLOAD_BASE_URL" ]; then
151
+ http_code=$(curl --location --progress-bar --show-error --output "$output" --write-out '%{http_code}' "$url")
152
+ else
153
+ http_code=$(curl --location --progress-bar --show-error --proto '=https' --tlsv1.2 --output "$output" --write-out '%{http_code}' "$url")
154
+ fi
155
+ curl_exit=$?
156
+ set -e
157
+ [ "$curl_exit" -eq 0 ] || fail "download failed for $url (curl exit $curl_exit)"
158
+ case "$http_code" in
159
+ 404) return 44 ;;
160
+ 2??) return 0 ;;
161
+ *) fail "download failed for $url (HTTP $http_code)" ;;
162
+ esac
274
163
  }
275
164
 
276
- install_binary() {
277
- TMP="$(mktemp -d)"
278
-
279
- for asset in "${CANDIDATES[@]}"; do
280
- local url
281
- url="$(build_url "$asset")"
282
- printf ' Trying %s\n' "$url"
283
-
284
- if curl --fail --location --silent --show-error --proto '=https' --tlsv1.2 "$url" --output "$TMP/runx"; then
285
- if ! verify_native_binary "$TMP/runx"; then
286
- printf ' %s was not a native binary, trying next candidate...\n' "$asset" >&2
287
- continue
288
- fi
165
+ install_transactional() {
166
+ source_path=$1
167
+ destination=$2
168
+ backup="${destination}.old.$$.$(date +%s)"
169
+ original_moved=false
170
+ if [ -e "$destination" ]; then mv -- "$destination" "$backup" || fail "could not back up existing RunX at $destination"; original_moved=true; fi
171
+ if ! install -m 0755 "$source_path" "$destination"; then
172
+ [ "$original_moved" = false ] || mv -- "$backup" "$destination" || fail "installation and automatic rollback both failed; backup remains at $backup"
173
+ fail 'installation failed; the previous executable was restored'
174
+ fi
175
+ printf 'Verifying...\n'
176
+ if ! verify_installed_version "$destination"; then
177
+ rm -f -- "$destination"
178
+ [ "$original_moved" = false ] || mv -- "$backup" "$destination" || fail "verification and automatic rollback both failed; backup remains at $backup"
179
+ fail 'installation verification failed; the previous executable was restored'
180
+ fi
181
+ [ "$original_moved" = false ] || rm -f -- "$backup"
182
+ }
289
183
 
290
- mkdir -p "$INSTALL_DIR"
291
- install -m 0755 "$TMP/runx" "$INSTALL_DIR/runx"
292
- printf 'Installed runx to %s/runx\n' "$INSTALL_DIR"
293
- ensure_path
294
- check_shadowing
295
- printf 'Run: runx --help\n'
296
- return 0
184
+ install_agent_assets() {
185
+ tag_base=${DOWNLOAD_BASE_URL:-"https://github.com/${REPO}/releases/download"}
186
+ skill_url="${tag_base%/}/${encoded_tag}/guiho-s-runx"
187
+ prompt_url="${tag_base%/}/${encoded_tag}/guiho-i-runx"
188
+ printf 'Downloading skill asset: %s\n' "$skill_url"
189
+ download_asset "$skill_url" "$TMP/guiho-s-runx" || fail 'could not download guiho-s-runx'
190
+ printf 'Downloading instruction asset: %s\n' "$prompt_url"
191
+ download_asset "$prompt_url" "$TMP/guiho-i-runx" || fail 'could not download guiho-i-runx'
192
+ for skill_root in "$HOME/.agents/skills/guiho-s-runx" "$HOME/.claude/skills/guiho-s-runx"; do
193
+ mkdir -p -- "$skill_root"
194
+ install -m 0644 "$TMP/guiho-s-runx" "$skill_root/SKILL.md"
195
+ printf 'Installed skill: %s\n' "$skill_root/SKILL.md"
196
+ done
197
+ targets=""
198
+ [ ! -f AGENTS.md ] || targets="AGENTS.md"
199
+ [ ! -f CLAUDE.md ] || targets="${targets}${targets:+ }CLAUDE.md"
200
+ [ -n "$targets" ] || targets="AGENTS.md"
201
+ for instruction_file in $targets; do
202
+ printf 'Reconciling instruction file: %s\n' "$instruction_file"
203
+ clean_file="$TMP/instruction-clean"
204
+ if [ -f "$instruction_file" ]; then
205
+ awk '
206
+ $0 == "<!-- BEGIN RUNX — DO NOT EDIT THIS SECTION -->" { managed=1; next }
207
+ $0 == "<!-- END RUNX -->" { managed=0; next }
208
+ !managed { print }
209
+ ' "$instruction_file" >"$clean_file"
210
+ else
211
+ : >"$clean_file"
297
212
  fi
298
-
299
- printf ' not available, trying next...\n'
213
+ {
214
+ cat "$clean_file"
215
+ [ ! -s "$clean_file" ] || printf '\n'
216
+ printf '<!-- BEGIN RUNX — DO NOT EDIT THIS SECTION -->\n'
217
+ cat "$TMP/guiho-i-runx"
218
+ printf '\n<!-- END RUNX -->\n'
219
+ } >"$instruction_file"
300
220
  done
301
-
302
- fail "no compatible runx binary found. Check available assets at: https://github.com/${REPO}/releases"
303
221
  }
304
222
 
305
223
  main() {
306
224
  parse_args "$@"
307
- require_command curl
308
- require_command grep
309
- require_command head
310
- require_command install
311
- require_command mktemp
312
- require_command uname
313
-
314
- OS="$(detect_os)"
315
- ARCH="$(detect_arch)"
316
- build_candidates
317
-
318
- local variant_label=""
319
- [[ -z "$VARIANT_OVERRIDE" ]] || variant_label=" variant=${VARIANT_OVERRIDE}"
320
- printf 'runx: %s os=%s arch=%s%s\n' "$VERSION" "$OS" "$ARCH" "$variant_label"
321
- install_binary
225
+ for command in awk cat curl date dirname grep head install mktemp mv od rm sed tr uname; do require_command "$command"; done
226
+ OS=$(detect_os); ARCH=$(detect_arch); TARGET_VERSION=$(resolve_target_version)
227
+ TMP=$(mktemp -d "${TMPDIR:-/tmp}/runx-install.XXXXXX")
228
+ mkdir -p -- "$INSTALL_DIR"
229
+ destination="$INSTALL_DIR/runx"
230
+ encoded_tag="%40guiho%2Frunx%40${TARGET_VERSION}"
231
+ downloaded=""
232
+ first_asset=$(build_candidates | head -n 1)
233
+ source_url="${DOWNLOAD_BASE_URL:-https://github.com/${REPO}/releases/download}/${encoded_tag}/${first_asset}"
234
+ printf 'Initiating GUIHO CLI Upgrade / Installation Sequence...\n'
235
+ printf 'Target Version: v%s\nArchitecture: %s\nVariant: %s\nSource URL: %s\n' "$TARGET_VERSION" "$ARCH" "${VARIANT_OVERRIDE:-baseline}" "$source_url"
236
+ build_candidates | while IFS= read -r asset; do printf '%s\n' "$asset"; done >"$TMP/candidates"
237
+ while IFS= read -r asset; do
238
+ if [ -n "$DOWNLOAD_BASE_URL" ]; then url="${DOWNLOAD_BASE_URL%/}/${encoded_tag}/${asset}"; else url="https://github.com/${REPO}/releases/download/${encoded_tag}/${asset}"; fi
239
+ candidate="$TMP/$asset"
240
+ printf 'Downloading %s\n' "$url"
241
+ if download_asset "$url" "$candidate"; then :; else
242
+ status=$?
243
+ if [ "$status" -eq 44 ]; then printf ' %s is not available; trying the next compatible candidate.\n' "$asset"; continue; fi
244
+ exit "$status"
245
+ fi
246
+ verify_native_binary "$candidate" || fail "downloaded asset $asset is not a native $OS executable"
247
+ downloaded=$candidate
248
+ break
249
+ done <"$TMP/candidates"
250
+ [ -n "$downloaded" ] || fail "no compatible RunX $TARGET_VERSION binary found at https://github.com/${REPO}/releases"
251
+ printf 'Replacing...\n'
252
+ install_transactional "$downloaded" "$destination"
253
+ install_agent_assets
254
+ [ "${RUNX_SKIP_PATH_UPDATE:-0}" = 1 ] || ensure_path
255
+ printf 'Final verification: %s --version\n' "$destination"
256
+ printf 'Installed and verified RunX %s at %s\n' "$TARGET_VERSION" "$destination"
322
257
  }
323
258
 
324
259
  main "$@"
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @copyright Copyright © 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
3
+ */
4
+
5
+ import { describe, expect, test } from 'bun:test'
6
+
7
+ describe('RunX direct installers', () => {
8
+ test('PowerShell installer exposes the complete RFC sequence', async () => {
9
+ const script = await Bun.file(new URL('./install.ps1', import.meta.url)).text()
10
+ expect(script).toContain('Initiating GUIHO CLI Upgrade / Installation Sequence...')
11
+ expect(script).toContain('guiho-s-runx')
12
+ expect(script).toContain('guiho-i-runx')
13
+ expect(script).toContain('.agents\\skills\\guiho-s-runx')
14
+ expect(script).toContain('.claude\\skills\\guiho-s-runx')
15
+ expect(script).toContain('BEGIN RUNX — DO NOT EDIT THIS SECTION')
16
+ expect(script).toContain('Test-NativeBinary')
17
+ expect(script).toContain('Install-Transactional')
18
+ })
19
+
20
+ test('POSIX installer selects Darwin assets and has no Bun dependency', async () => {
21
+ const script = await Bun.file(new URL('./install.sh', import.meta.url)).text()
22
+ expect(script).toContain("Darwin) printf 'darwin")
23
+ expect(script).toContain('--progress-bar')
24
+ expect(script).toContain('guiho-s-runx')
25
+ expect(script).toContain('guiho-i-runx')
26
+ expect(script).toContain('.agents/skills/guiho-s-runx')
27
+ expect(script).toContain('.claude/skills/guiho-s-runx')
28
+ expect(script).toContain('BEGIN RUNX — DO NOT EDIT THIS SECTION')
29
+ expect(script).not.toContain(' bun ')
30
+ })
31
+ })
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @copyright Copyright © 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
3
+ */
4
+
5
+ import { expectedReleaseAssetNames } from './build-binaries.js'
6
+ import { joinPath } from '../source/path-utils.js'
7
+
8
+ const root = Bun.fileURLToPath(new URL('..', import.meta.url))
9
+ const bin = joinPath(root, 'bin')
10
+ const observed = [...new Bun.Glob('*').scanSync({ cwd: bin, onlyFiles: true })].sort()
11
+ const expected = [...expectedReleaseAssetNames].sort()
12
+
13
+ if (JSON.stringify(observed) !== JSON.stringify(expected)) {
14
+ throw new Error(`Expected exactly ${expected.length} release assets.\nExpected: ${expected.join(', ')}\nObserved: ${observed.join(', ')}`)
15
+ }
16
+
17
+ process.stdout.write(`${JSON.stringify({ count: observed.length, assets: observed }, null, 2)}\n`)
@@ -6,12 +6,14 @@ children: []
6
6
  files:
7
7
  alpha-boundaries.md: Captures accepted alpha scope, safety, distribution, and compatibility decisions.
8
8
  citty-cli-migration.md: Defines the accepted full migration from handwritten parsing and routing to Citty.
9
+ interactive-init-manifest.md: Defines interactive initialization, Semantic Versioning, the public group, and the scripts directory contract.
9
10
  mirror-automatic-push.md: Defines automatic Mirror release pushes with a synchronized protected-main gate.
10
11
  npm-trusted-publishing.md: Defines the accepted npm OIDC publishing workflow and protected-branch patch-release trial.
11
12
  windows-self-upgrade.md: Defines synchronous Windows executable replacement, verification, rollback, and cleanup.
12
13
  documents:
13
14
  alpha-boundaries.md: Decision record for RunX alpha boundaries.
14
15
  citty-cli-migration.md: Decision record for the complete Citty CLI parser and router migration.
16
+ interactive-init-manifest.md: Accepted manifest and interactive initialization decision for runx init.
15
17
  mirror-automatic-push.md: Accepted release-policy decision for Mirror push=true and protected delivery ordering.
16
18
  npm-trusted-publishing.md: Decision record for GitHub Actions npm trusted publishing and the first automated patch release.
17
19
  windows-self-upgrade.md: Accepted design for synchronous and recoverable Windows native self-upgrade.
@@ -20,6 +22,8 @@ tags:
20
22
  keywords:
21
23
  - runx
22
24
  - decisions
25
+ - runx init
26
+ - scripts directory
23
27
  - mirror push
24
28
  - windows self-upgrade
25
29
  flags: []