@guiho/runx 0.2.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 (72) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/DOCS.md +118 -0
  3. package/LICENSE.md +36 -0
  4. package/README.md +79 -0
  5. package/SECURITY.md +28 -0
  6. package/devops/build-binaries.ts +99 -0
  7. package/devops/devops.xdocs.md +25 -0
  8. package/devops/install.ps1 +216 -0
  9. package/devops/install.sh +324 -0
  10. package/docs/architecture/architecture.xdocs.md +20 -0
  11. package/docs/architecture/cli-architecture.md +39 -0
  12. package/docs/decisions/alpha-boundaries.md +29 -0
  13. package/docs/decisions/decisions.xdocs.md +19 -0
  14. package/docs/docs.xdocs.md +24 -0
  15. package/docs/plans/alpha-implementation.md +27 -0
  16. package/docs/plans/plans.xdocs.md +19 -0
  17. package/docs/requirements/alpha-command-catalog.md +41 -0
  18. package/docs/requirements/requirements.xdocs.md +19 -0
  19. package/docs/todo/implement-runx-alpha.md +36 -0
  20. package/docs/todo/protect-branches-and-tag-creation.md +83 -0
  21. package/docs/todo/todo.xdocs.md +21 -0
  22. package/docs/validation/alpha-implementation-summary.md +52 -0
  23. package/docs/validation/validation.xdocs.md +21 -0
  24. package/library/agents.d.ts +11 -0
  25. package/library/agents.d.ts.map +1 -0
  26. package/library/agents.js +51 -0
  27. package/library/cli.d.ts +3 -0
  28. package/library/cli.d.ts.map +1 -0
  29. package/library/cli.js +146 -0
  30. package/library/embedded-resources.d.ts +7 -0
  31. package/library/embedded-resources.d.ts.map +1 -0
  32. package/library/embedded-resources.js +5 -0
  33. package/library/errors.d.ts +6 -0
  34. package/library/errors.d.ts.map +1 -0
  35. package/library/errors.js +12 -0
  36. package/library/executor.d.ts +3 -0
  37. package/library/executor.d.ts.map +1 -0
  38. package/library/executor.js +24 -0
  39. package/library/flags.d.ts +5 -0
  40. package/library/flags.d.ts.map +1 -0
  41. package/library/flags.js +32 -0
  42. package/library/guiho-runx-bin.d.ts +3 -0
  43. package/library/guiho-runx-bin.d.ts.map +1 -0
  44. package/library/guiho-runx-bin.js +3 -0
  45. package/library/guiho-runx-native-bin.d.ts +3 -0
  46. package/library/guiho-runx-native-bin.d.ts.map +1 -0
  47. package/library/guiho-runx-native-bin.js +5 -0
  48. package/library/guiho-runx.d.ts +4 -0
  49. package/library/guiho-runx.d.ts.map +1 -0
  50. package/library/guiho-runx.js +2 -0
  51. package/library/help.d.ts +6 -0
  52. package/library/help.d.ts.map +1 -0
  53. package/library/help.js +47 -0
  54. package/library/manifest.d.ts +41 -0
  55. package/library/manifest.d.ts.map +1 -0
  56. package/library/manifest.js +112 -0
  57. package/library/render.d.ts +6 -0
  58. package/library/render.d.ts.map +1 -0
  59. package/library/render.js +40 -0
  60. package/library/self-management.d.ts +13 -0
  61. package/library/self-management.d.ts.map +1 -0
  62. package/library/self-management.js +82 -0
  63. package/library/types.d.ts +31 -0
  64. package/library/types.d.ts.map +1 -0
  65. package/library/types.js +0 -0
  66. package/package.json +77 -0
  67. package/scripts/runx-bin.ts +18 -0
  68. package/scripts/scripts.xdocs.md +20 -0
  69. package/skills/guiho-s-runx/SKILL.md +57 -0
  70. package/skills/guiho-s-runx/agents/openai.yaml +4 -0
  71. package/skills/guiho-s-runx/guiho-s-runx.xdocs.md +22 -0
  72. package/skills/skills.xdocs.md +19 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: RunX Changelog
3
+ purpose: Record externally meaningful RunX release changes.
4
+ description: Tracks release-level changes managed with Mirror.
5
+ created: 2026-07-12
6
+ flags: []
7
+ tags:
8
+ - releases
9
+ - changelog
10
+ keywords:
11
+ - runx
12
+ - changelog
13
+ owner: runx
14
+ ---
15
+
16
+ # Changelog
17
+
18
+ ## 0.2.0 - 2026-07-12
19
+
20
+ ### Added
21
+
22
+ - Added `publish.yml` GitHub Actions workflow to compile and upload release binaries to GitHub releases on version tag pushes.
23
+
24
+ ### Changed
25
+
26
+ - Updated `devops/build-binaries.ts` to compile the full 12 binary target matrix in parallel using optimized build flags.
27
+ - Replaced `devops/install.sh` and `devops/install.ps1` with robust, parameter-driven installers.
28
+
29
+ ## 0.1.1 - 2026-07-12
30
+
31
+ ### Changed
32
+
33
+ - Finalized task completion and validation evidence for the initial alpha release.
34
+
35
+ ## 0.1.0 - 2026-07-12
36
+
37
+ ### Added
38
+
39
+ - First alpha implementation of the RunX command-catalog CLI.
package/DOCS.md ADDED
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: RunX Documentation
3
+ purpose: Provide the canonical user-facing RunX CLI and manifest reference.
4
+ description: Documents command behavior, manifest schema, safety model, agent workflow, and native distribution.
5
+ created: 2026-07-12
6
+ flags: []
7
+ tags:
8
+ - documentation
9
+ - cli
10
+ keywords:
11
+ - runx
12
+ - manifest
13
+ owner: runx
14
+ ---
15
+
16
+ # RunX Documentation
17
+
18
+ ## Purpose
19
+
20
+ RunX makes project operations visible and documented. A `runx.yaml` file is a
21
+ single local command catalog; RunX discovers the nearest catalog, validates it,
22
+ lists its commands, and runs exactly one selected command.
23
+
24
+ ## Commands
25
+
26
+ | Command | Purpose |
27
+ | --- | --- |
28
+ | `runx` | Show the home page and usage without loading a manifest. |
29
+ | `runx list` | List all manifest commands. Add `--format json` for structured output. |
30
+ | `runx describe <selector>` | Show one command's description and operational metadata. |
31
+ | `runx check` | Validate discovery and the manifest without execution. |
32
+ | `runx run <selector>` | Execute a selected command. |
33
+ | `runx r <selector>` | Short alias for `runx run`. |
34
+ | `runx <selector>` | Human shorthand for `runx run <selector>` when no built-in name conflicts. |
35
+ | `runx agents install <local|global>` | Install `guiho-s-runx`; add `--tool agents|claude|all` when needed. |
36
+ | `runx agents instructions` | Add or refresh the managed RunX section in `AGENTS.md`. |
37
+ | `runx upgrade [check|list]` | Check releases, list releases, or replace a native installed executable. |
38
+ | `runx uninstall [--dry-run]` | Remove a native installed executable. |
39
+
40
+ Global flags: `--cwd <path>`, `--file <path>`, `--format <text|json>`,
41
+ `--verbose`, `--version`, `--help`, `--help-tree`, and `--help-docs`.
42
+
43
+ ## Manifest
44
+
45
+ RunX searches upward from the current directory for `runx.yaml`. Use `--file`
46
+ to select an explicit manifest. RunX does not merge files.
47
+
48
+ ```yaml
49
+ version: 1
50
+ project:
51
+ name: example
52
+ groups:
53
+ development:
54
+ summary: Local development work.
55
+ release:
56
+ summary: Explicit release actions.
57
+ commands:
58
+ - uid: example-check
59
+ id: check
60
+ group: development
61
+ summary: Type-check the project.
62
+ description: Runs the project's static type checker without emitting build files.
63
+ command: bun run typecheck
64
+ cwd: .
65
+ shell: auto
66
+ tags: [validation]
67
+ - uid: example-release
68
+ id: release
69
+ group: release
70
+ summary: Build the release artifact.
71
+ description: Builds the release artifact after the project has passed its required checks.
72
+ command: bun run build
73
+ confirm: always
74
+ ```
75
+
76
+ Required fields are `uid`, `id`, `group`, `summary`, `description`, and
77
+ `command`. `uid` and `id` begin with a lowercase letter and use lowercase
78
+ letters, digits, and hyphens. `uid` is globally unique; the group and ID pair
79
+ is also unique.
80
+
81
+ Optional fields are `cwd`, `shell`, `tags`, and `confirm`. `cwd` is relative to
82
+ the manifest and cannot escape its directory. Shell values are `auto`, `bash`,
83
+ `sh`, `powershell`, and `cmd`. Confirmation values are `never` and `always`.
84
+
85
+ ## Selectors and Safety
86
+
87
+ RunX resolves selectors in this order: exact UID, exact `group/id`, one-based
88
+ manifest index, then an unambiguous bare ID. Use UIDs in automation. An index is
89
+ only stable until commands are reordered, and an ambiguous bare ID fails rather
90
+ than selecting a command.
91
+
92
+ `list`, `describe`, `check`, and `run --dry-run` never execute a configured
93
+ command. A real run streams stdout/stderr and uses the child process exit code.
94
+ `confirm: always` requires the user to add `--yes`.
95
+
96
+ Manifests are executable local code. Do not place secrets in a manifest and do
97
+ not assume a group name determines safety.
98
+
99
+ ## Agent Skill
100
+
101
+ The bundled `guiho-s-runx` skill directs agents to validate and list a catalog,
102
+ select a UID, inspect unfamiliar commands, use dry runs, and obtain explicit
103
+ authorization before `--yes` for high-impact commands. Install it with:
104
+
105
+ ```text
106
+ runx agents install local
107
+ ```
108
+
109
+ ## Native Install, Upgrade, and Uninstall
110
+
111
+ `devops/install.ps1` installs Windows assets and `devops/install.sh` installs
112
+ macOS/Linux assets from GitHub Releases into the user's local bin directory.
113
+ `runx upgrade` uses the matching release asset and replaces a native executable;
114
+ on Windows replacement is scheduled after RunX exits. `runx uninstall` removes
115
+ the same native executable and supports `--dry-run`.
116
+
117
+ The alpha CI validates code and compiles a native executable but intentionally
118
+ does not publish to npm or GitHub Releases.
package/LICENSE.md ADDED
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: RunX MIT License
3
+ purpose: State the open-source license for RunX.
4
+ description: MIT license grant and warranty disclaimer.
5
+ created: 2026-07-12
6
+ flags: []
7
+ tags:
8
+ - legal
9
+ - license
10
+ keywords:
11
+ - runx
12
+ - mit
13
+ owner: runx
14
+ ---
15
+
16
+ MIT License
17
+
18
+ Copyright (c) 2026 Cristovao GUIHO
19
+
20
+ Permission is hereby granted, free of charge, to any person obtaining a copy
21
+ of this software and associated documentation files (the "Software"), to deal
22
+ in the Software without restriction, including without limitation the rights
23
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24
+ copies of the Software, and to permit persons to whom the Software is
25
+ furnished to do so, subject to the following conditions:
26
+
27
+ The above copyright notice and this permission notice shall be included in all
28
+ copies or substantial portions of the Software.
29
+
30
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: RunX README
3
+ purpose: Introduce RunX and give users the shortest installation and command-catalog path.
4
+ description: Public overview, direct installation commands, quick manifest example, and documentation links.
5
+ created: 2026-07-12
6
+ flags: []
7
+ tags:
8
+ - documentation
9
+ - cli
10
+ keywords:
11
+ - runx
12
+ - readme
13
+ owner: runx
14
+ ---
15
+
16
+ # GUIHO RunX
17
+
18
+ RunX is a local, language-agnostic command catalog. Put documented commands in
19
+ `runx.yaml`; use `runx list` to discover them and `runx run` to execute one.
20
+ It works for Bun, Python, Go, shell, and mixed repositories because it executes
21
+ local shell commands rather than package-manager scripts.
22
+
23
+ ## Install
24
+
25
+ RunX is distributed as a native executable from GitHub Releases. It is not
26
+ published to npm during the alpha.
27
+
28
+ ```powershell
29
+ irm https://raw.githubusercontent.com/CGuiho/runx/main/devops/install.ps1 | iex
30
+ ```
31
+
32
+ ```sh
33
+ curl -fsSL https://raw.githubusercontent.com/CGuiho/runx/main/devops/install.sh | sh
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ Create `runx.yaml` in a project root:
39
+
40
+ ```yaml
41
+ version: 1
42
+ groups:
43
+ development:
44
+ summary: Local development commands.
45
+ commands:
46
+ - uid: app-dev
47
+ id: dev
48
+ group: development
49
+ summary: Start the application locally.
50
+ description: Starts the application in local development mode until interrupted.
51
+ command: bun run dev
52
+ shell: bash
53
+ tags: [local, watch]
54
+ ```
55
+
56
+ Then use RunX:
57
+
58
+ ```text
59
+ runx Show the home page and usage.
60
+ runx list List commands in the nearest manifest.
61
+ runx describe app-dev Explain one command without execution.
62
+ runx run app-dev --dry-run Inspect the execution plan.
63
+ runx run app-dev Execute the command.
64
+ runx r app-dev Use the short run alias.
65
+ ```
66
+
67
+ Use `confirm: always` for commands that should require an explicit `--yes`.
68
+ Use `runx list --format json` and stable UIDs for agent automation.
69
+
70
+ ## Documentation
71
+
72
+ - [Full CLI and manifest reference](DOCS.md)
73
+ - [Contributing](CONTRIBUTING.md)
74
+ - [Security policy](SECURITY.md)
75
+ - [Changelog](CHANGELOG.md)
76
+
77
+ ## License
78
+
79
+ [MIT](LICENSE.md)
package/SECURITY.md ADDED
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: RunX Security Policy
3
+ purpose: Explain private vulnerability reporting and the RunX manifest trust boundary.
4
+ description: Documents responsible disclosure and the risks of executable local manifests.
5
+ created: 2026-07-12
6
+ flags: []
7
+ tags:
8
+ - security
9
+ keywords:
10
+ - runx
11
+ - security
12
+ owner: runx
13
+ ---
14
+
15
+ # Security Policy
16
+
17
+ ## Reporting
18
+
19
+ Report a potential security issue privately to `cg@guiho.co`. Do not include
20
+ credentials, tokens, or private manifests in public issues.
21
+
22
+ ## RunX Trust Boundary
23
+
24
+ RunX manifests contain shell commands and are trusted local executable code.
25
+ RunX validates structure and makes commands visible; it cannot make arbitrary
26
+ shell code safe. Do not store secrets in `runx.yaml`, inspect a command before
27
+ running it, and use `confirm: always` for operations that benefit from an
28
+ explicit `--yes` acknowledgement.
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
3
+ *
4
+ * Build release binaries for the supported RunX platform matrix.
5
+ */
6
+
7
+ import { mkdir, rm, stat } from 'node:fs/promises'
8
+ import { join } from 'node:path'
9
+ import { fileURLToPath } from 'node:url'
10
+
11
+ type BinaryTarget = {
12
+ readonly bunTarget: string
13
+ readonly assetName: string
14
+ }
15
+
16
+ const targets: readonly BinaryTarget[] = [
17
+ { bunTarget: 'bun-linux-arm64', assetName: 'runx-linux-arm64' },
18
+ { bunTarget: 'bun-linux-x64', assetName: 'runx-linux-x64' },
19
+ { bunTarget: 'bun-linux-x64-baseline', assetName: 'runx-linux-x64-baseline' },
20
+ { bunTarget: 'bun-linux-x64-modern', assetName: 'runx-linux-x64-modern' },
21
+ { bunTarget: 'bun-darwin-arm64', assetName: 'runx-macos-arm64' },
22
+ { bunTarget: 'bun-darwin-x64', assetName: 'runx-macos-x64' },
23
+ { bunTarget: 'bun-darwin-x64-baseline', assetName: 'runx-macos-x64-baseline' },
24
+ { bunTarget: 'bun-darwin-x64-modern', assetName: 'runx-macos-x64-modern' },
25
+ { bunTarget: 'bun-windows-arm64', assetName: 'runx-windows-arm64.exe' },
26
+ { bunTarget: 'bun-windows-x64', assetName: 'runx-windows-x64.exe' },
27
+ { bunTarget: 'bun-windows-x64-baseline', assetName: 'runx-windows-x64-baseline.exe' },
28
+ { bunTarget: 'bun-windows-x64-modern', assetName: 'runx-windows-x64-modern.exe' },
29
+ ]
30
+
31
+ const expectedAssetCount = 12
32
+ const assetNames = targets.map((target) => target.assetName)
33
+ const uniqueAssetNames = new Set(assetNames)
34
+
35
+ if (targets.length !== expectedAssetCount) {
36
+ throw new Error(`Expected ${expectedAssetCount} binary targets, found ${targets.length}`)
37
+ }
38
+
39
+ if (uniqueAssetNames.size !== assetNames.length) {
40
+ throw new Error('Binary target matrix contains duplicate asset names')
41
+ }
42
+
43
+ const root = fileURLToPath(new URL('..', import.meta.url))
44
+ const binDirectory = join(root, 'bin')
45
+ const bunExecutable = process.execPath
46
+
47
+ await rm(binDirectory, { recursive: true, force: true })
48
+ await mkdir(binDirectory, { recursive: true })
49
+
50
+ const builds = targets.map(async (target) => {
51
+ const outputPath = join(binDirectory, target.assetName)
52
+ const proc = Bun.spawn({
53
+ cmd: [
54
+ bunExecutable,
55
+ 'build',
56
+ 'source/guiho-runx-native-bin.ts',
57
+ '--compile',
58
+ '--production',
59
+ '--minify-whitespace',
60
+ '--minify-syntax',
61
+ '--target',
62
+ target.bunTarget,
63
+ '--outfile',
64
+ outputPath,
65
+ ],
66
+ cwd: root,
67
+ stdout: 'pipe',
68
+ stderr: 'pipe',
69
+ })
70
+
71
+ const [stdout, stderr, exitCode] = await Promise.all([
72
+ new Response(proc.stdout).text(),
73
+ new Response(proc.stderr).text(),
74
+ proc.exited,
75
+ ])
76
+
77
+ if (exitCode !== 0) {
78
+ throw new Error([
79
+ `Failed to build ${target.bunTarget} -> ${target.assetName}`,
80
+ stdout.trim(),
81
+ stderr.trim(),
82
+ ].filter(Boolean).join('\n'))
83
+ }
84
+
85
+ process.stdout.write(`built: ${target.bunTarget} -> bin/${target.assetName}\n`)
86
+ })
87
+
88
+ await Promise.all(builds)
89
+
90
+ for (const target of targets) {
91
+ const outputPath = join(binDirectory, target.assetName)
92
+ const output = await stat(outputPath)
93
+
94
+ if (output.size === 0) {
95
+ throw new Error(`Built binary is empty: bin/${target.assetName}`)
96
+ }
97
+ }
98
+
99
+ process.stdout.write(`verified ${targets.length} native binary assets\n`)
@@ -0,0 +1,25 @@
1
+ ---
2
+ subject: runx-devops
3
+ description: Native release-asset compilation and direct RunX installer scripts.
4
+ parent: runx
5
+ children: []
6
+ files:
7
+ build-binaries.ts: Cross-compiles supported Bun native executable assets.
8
+ install.ps1: Installs the latest Windows executable from GitHub Releases.
9
+ install.sh: Installs the latest macOS or Linux executable from GitHub Releases.
10
+ documents: {}
11
+ tags:
12
+ - devops
13
+ - installers
14
+ - releases
15
+ keywords:
16
+ - runx
17
+ - windows
18
+ - macos
19
+ - linux
20
+ - native binary
21
+ flags: []
22
+ status: stable
23
+ ---
24
+
25
+ These scripts support direct native installation without an npm publication.
@@ -0,0 +1,216 @@
1
+ param(
2
+ [string]$Version,
3
+ [string]$Arch,
4
+ [string]$Variant,
5
+ [string]$InstallDir,
6
+ [switch]$Help
7
+ )
8
+
9
+ $ErrorActionPreference = 'Stop'
10
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
11
+
12
+ # === Defaults from env vars or sensible defaults ===
13
+ if ([string]::IsNullOrWhiteSpace($Version)) {
14
+ $Version = if ($env:RUNX_VERSION) { $env:RUNX_VERSION } else { 'latest' }
15
+ }
16
+ $Repo = if ($env:RUNX_REPO) { $env:RUNX_REPO } else { 'CGuiho/runx' }
17
+ if ([string]::IsNullOrWhiteSpace($InstallDir)) {
18
+ $InstallDir = if ($env:RUNX_INSTALL_DIR) { $env:RUNX_INSTALL_DIR } else { Join-Path $HOME '.local\bin' }
19
+ }
20
+
21
+ # === Show help ===
22
+ if ($Help -or $Version -eq '--help' -or $Version -eq '-h') {
23
+ @"
24
+ Install GUIHO RunX as a native CLI binary from GitHub Releases.
25
+
26
+ Usage: install.ps1 [-Version VERSION] [-Arch ARCH] [-Variant VARIANT] [-InstallDir DIR]
27
+
28
+ Parameters:
29
+ -Version Version to install (default: latest).
30
+ Examples: latest, 0.1.1, @guiho/runx@0.1.1
31
+ -Arch Force architecture: x64 | arm64 (default: auto-detect)
32
+ -Variant Force x64 variant: baseline | default | modern (default: baseline)
33
+ -InstallDir Install directory (default: `$HOME\.local\bin)
34
+ -Help Show this help
35
+
36
+ Environment variables:
37
+ RUNX_VERSION, RUNX_REPO, RUNX_INSTALL_DIR
38
+ "@
39
+ return
40
+ }
41
+
42
+ # === Detect architecture (compatible with PowerShell 5.1+) ===
43
+ $detectedArch = if ($Arch) {
44
+ $Arch
45
+ } else {
46
+ switch ($env:PROCESSOR_ARCHITECTURE) {
47
+ 'AMD64' { 'x64' }
48
+ 'ARM64' { 'arm64' }
49
+ default { throw "Unsupported architecture: $env:PROCESSOR_ARCHITECTURE. Must be AMD64 or ARM64." }
50
+ }
51
+ }
52
+
53
+ if ($detectedArch -notin @('x64', 'arm64')) {
54
+ throw "Invalid architecture: $detectedArch. Must be x64 or arm64."
55
+ }
56
+
57
+ if (-not [Environment]::Is64BitOperatingSystem) {
58
+ throw 'Unsupported platform: Windows 32-bit is not supported.'
59
+ }
60
+
61
+ # === Build asset candidates (baseline-first for x64) ===
62
+ $variant = if ($Variant) { $Variant } else { 'baseline' }
63
+
64
+ $assetCandidates = if ($detectedArch -eq 'x64') {
65
+ switch ($variant) {
66
+ 'baseline' { @(
67
+ "runx-windows-x64-baseline.exe",
68
+ "runx-windows-x64.exe",
69
+ "runx-windows-x64-modern.exe"
70
+ )}
71
+ 'default' { @(
72
+ "runx-windows-x64.exe",
73
+ "runx-windows-x64-baseline.exe",
74
+ "runx-windows-x64-modern.exe"
75
+ )}
76
+ 'modern' { @(
77
+ "runx-windows-x64-modern.exe",
78
+ "runx-windows-x64.exe",
79
+ "runx-windows-x64-baseline.exe"
80
+ )}
81
+ default { throw "Invalid variant: $variant. Must be baseline, default, or modern." }
82
+ }
83
+ } else {
84
+ if ($Variant) {
85
+ throw '-Variant is only valid for x64 installs.'
86
+ }
87
+ @("runx-windows-arm64.exe")
88
+ }
89
+
90
+ # === Build download URL ===
91
+ function Get-DownloadUrl {
92
+ param([string]$Asset)
93
+
94
+ if ($Version -eq 'latest') {
95
+ return "https://github.com/$Repo/releases/latest/download/$Asset"
96
+ }
97
+
98
+ $tag = if ($Version.StartsWith('@guiho/runx@')) { $Version }
99
+ elseif ($Version.StartsWith('@')) { $Version }
100
+ else { "@guiho/runx@$Version" }
101
+
102
+ $encodedTag = [Uri]::EscapeDataString($tag)
103
+ return "https://github.com/$Repo/releases/download/$encodedTag/$Asset"
104
+ }
105
+
106
+ function Get-PathEntries {
107
+ param([string]$PathValue)
108
+
109
+ if ([string]::IsNullOrWhiteSpace($PathValue)) {
110
+ return @()
111
+ }
112
+
113
+ return @($PathValue -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
114
+ }
115
+
116
+ function Test-PathContains {
117
+ param(
118
+ [string]$PathValue,
119
+ [string]$Directory
120
+ )
121
+
122
+ $normalizedDirectory = $Directory.TrimEnd('\')
123
+ foreach ($entry in Get-PathEntries -PathValue $PathValue) {
124
+ if ($entry.TrimEnd('\').Equals($normalizedDirectory, [StringComparison]::OrdinalIgnoreCase)) {
125
+ return $true
126
+ }
127
+ }
128
+
129
+ return $false
130
+ }
131
+
132
+ function Add-InstallDirToPath {
133
+ param([string]$Directory)
134
+
135
+ $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
136
+ if (-not (Test-PathContains -PathValue $userPath -Directory $Directory)) {
137
+ $entries = @(Get-PathEntries -PathValue $userPath)
138
+ $newUserPath = (@($Directory) + $entries) -join ';'
139
+ [Environment]::SetEnvironmentVariable('Path', $newUserPath.TrimEnd(';'), 'User')
140
+ Write-Host "Added $Directory to user PATH. Restart your terminal to use runx globally."
141
+ } else {
142
+ Write-Host "$Directory is already configured in user PATH."
143
+ }
144
+
145
+ if (-not (Test-PathContains -PathValue $env:Path -Directory $Directory)) {
146
+ $env:Path = "$Directory;$env:Path"
147
+ }
148
+ }
149
+
150
+ $temporaryFile = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
151
+
152
+ function Test-NativeBinary {
153
+ param([string]$Path)
154
+
155
+ $bytes = [System.IO.File]::ReadAllBytes($Path)
156
+ if ($bytes.Length -lt 2) {
157
+ return $false
158
+ }
159
+
160
+ return $bytes[0] -eq 0x4D -and $bytes[1] -eq 0x5A
161
+ }
162
+
163
+ function Test-Shadowing {
164
+ param([string]$ExpectedPath)
165
+
166
+ $command = Get-Command runx -ErrorAction SilentlyContinue
167
+ if (-not $command) {
168
+ return
169
+ }
170
+
171
+ if (-not $command.Source.Equals($ExpectedPath, [StringComparison]::OrdinalIgnoreCase)) {
172
+ Write-Warning "Another runx appears earlier in PATH: $($command.Source)"
173
+ Write-Warning "The newly installed binary is at: $ExpectedPath"
174
+ }
175
+ }
176
+
177
+ # === Main ===
178
+ $variantLabel = if ($Variant) { " variant=$Variant" } else { "" }
179
+ Write-Host "runx: $Version os=windows arch=$detectedArch$variantLabel"
180
+
181
+ New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
182
+ $InstallDir = (Resolve-Path -LiteralPath $InstallDir).Path
183
+ $destination = Join-Path $InstallDir 'runx.exe'
184
+
185
+ foreach ($asset in $assetCandidates) {
186
+ $url = Get-DownloadUrl -Asset $asset
187
+ Write-Host " Trying $url"
188
+ try {
189
+ Invoke-WebRequest -Uri $url -OutFile $temporaryFile -UseBasicParsing -ErrorAction Stop
190
+ if (-not (Test-NativeBinary -Path $temporaryFile)) {
191
+ Write-Host " $asset was not a native Windows binary, trying next..."
192
+ continue
193
+ }
194
+
195
+ Move-Item -Force -Path $temporaryFile -Destination $destination
196
+ Write-Host "Installed runx to $destination"
197
+
198
+ Add-InstallDirToPath -Directory $InstallDir
199
+ Test-Shadowing -ExpectedPath $destination
200
+
201
+ if (Test-Path -LiteralPath $temporaryFile) {
202
+ Remove-Item -LiteralPath $temporaryFile -Force
203
+ }
204
+
205
+ Write-Host 'Run: runx --help'
206
+ return
207
+ } catch {
208
+ Write-Host " not available, trying next..."
209
+ }
210
+ }
211
+
212
+ if (Test-Path -LiteralPath $temporaryFile) {
213
+ Remove-Item -LiteralPath $temporaryFile -Force
214
+ }
215
+
216
+ throw "No compatible runx binary found. Check available assets at: https://github.com/$Repo/releases"