diff --git a/CHANGELOG.md b/CHANGELOG.md index 79b30b4..c11f052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Added + +- `skills` module on Bash and native Windows: updates agent skills in both project and global scopes via `skills update --project --global` (matching the interactive "Both" option without prompting), falling back to `npx --yes skills update` when the `skills` CLI is not installed; `--non-interactive` appends `--yes` to skip upstream-deletion prompts. + ## [2.1.3] - 2026-08-10 ### Fixed diff --git a/PLAN.md b/PLAN.md index 73c1bae..cd30fe4 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,3 +1,20 @@ +# Plan: skills module updates + +## Goal + +- Add a `skills` module that updates agent skills in both project and global scopes by default, matching `npx skills update`'s interactive "Both" option without prompting. + +## Execution checklist + +- [ ] Register a default-on `skills` module between `pi` and `mise` on Bash and native Windows. +- [ ] Resolve the backing command as the `skills` CLI first, then `npx --yes skills`; skip gracefully when neither exists and fail under `--only`. +- [ ] Run `skills update --project --global`, appending `--yes` under `--non-interactive`; dry-run prints the resolved command without executing. +- [ ] Cover direct, npx-fallback, non-interactive, dry-run, and missing-dependency behavior in Bash tests; mirror success, fallback, and missing-dependency coverage in native Windows tests. +- [ ] Update README, SPEC (module matrix, execution order, §8.15), CHANGELOG, and this plan. +- [ ] Pass lint and tests before opening the PR. + +--- + # Plan: v2.1.3 fnm and complete pi updates ## Goal diff --git a/README.md b/README.md index f3141a5..17cf172 100644 --- a/README.md +++ b/README.md @@ -101,11 +101,12 @@ Modules are auto-detected: if the underlying command isn’t installed, the modu - `rustup`: update Rust toolchains via `rustup update` - `claude`: update Claude Code CLI via `claude update` - `pi`: update the pi AI CLI and installed extensions via `pi update --all` +- `skills`: update agent skills in both project and global scopes via `skills update` (falls back to `npx --yes skills update`); passing both scopes matches the interactive "Both" option without prompting - `mise`: update mise and upgrade installed tools (`mise self-update`, `mise upgrade`) - `go`: update Go binaries from `GO_BINARIES` in `~/.updatesrc` (entries default to `@latest`) - `macos`: list available macOS software updates via `softwareupdate -l` (disabled by default; enable with `--macos-updates` or `--full`) -Native Windows v2.1 default-on modules: `winget`, `node`, `bun`, `python`, `uv`, `pipx`, `rustup`, `claude`, `pi`, `go`. +Native Windows v2.1 default-on modules: `winget`, `node`, `bun`, `python`, `uv`, `pipx`, `rustup`, `claude`, `pi`, `skills`, `go`. On native Windows, `--full` selects every supported Windows module even if `SKIP_MODULES` in config would otherwise omit one; explicit `--skip` still wins. Platform support summary: @@ -115,7 +116,7 @@ Platform support summary: | brew, shell, repos | Yes | Yes | No | | linux | No | Yes | No | | winget | No | No | Yes | -| node, bun, python, uv, pipx, rustup, claude, pi, go | Yes | Yes | Yes | +| node, bun, python, uv, pipx, rustup, claude, pi, skills, go | Yes | Yes | Yes | | mas, macos | Yes | No | No | | mise | Yes | Yes | Deferred | @@ -164,6 +165,7 @@ Install what you actually use: - `rustup`: from https://rustup.rs - `claude` (Claude Code CLI) for the `claude` module - `pi` (npm-installed AI coding CLI) for the `pi` module +- `skills` CLI (or `npx`) for the `skills` module (`npx skills add ...`) - `go` (for the `go` module) - On Linux: a supported system package manager (`apt-get`, `dnf`, `yum`, `pacman`, `zypper`, or `apk`) and `sudo` (if not running as root) diff --git a/SPEC.md b/SPEC.md index 7257596..17e8767 100644 --- a/SPEC.md +++ b/SPEC.md @@ -28,7 +28,7 @@ v2.1 preserves the stable v2 contract. Patch release v2.1.1 filters Node upgrade ### 1.3 Success metrics - **Primary KPIs:** - - All 17 modules pass lint + stub/contract tests across macOS, Linux, and Windows. + - All 18 modules pass lint + stub/contract tests across macOS, Linux, and Windows. - JSONL output is parseable by `jq` for all event types. - Config file (`~/.updatesrc`) correctly sets defaults overridden by CLI flags across `HOME`/`USERPROFILE` layouts. - **Guardrails:** @@ -344,7 +344,7 @@ If `UPDATES_SELF_UPDATE_REPO` is set, the CLI **MUST** print an error and exit ` ### 7.2 Module list & platform matrix -Execution order: `brew`, `shell`, `repos`, `linux`, `winget`, `node`, `bun`, `python`, `uv`, `mas`, `pipx`, `rustup`, `claude`, `pi`, `mise`, `go`, `macos`. +Execution order: `brew`, `shell`, `repos`, `linux`, `winget`, `node`, `bun`, `python`, `uv`, `mas`, `pipx`, `rustup`, `claude`, `pi`, `skills`, `mise`, `go`, `macos`. | Module | macOS | Linux | WSL | Windows | Notes | | -------- | :---: | :---: | :-: | :-----: | ----- | @@ -362,15 +362,16 @@ Execution order: `brew`, `shell`, `repos`, `linux`, `winget`, `node`, `bun`, `py | `rustup` | Yes | Yes | Yes | Yes | Requires `rustup` | | `claude` | Yes | Yes | Yes | Yes | Requires `claude`; runs `claude update` | | `pi` | Yes | Yes | Yes | Yes | Requires `pi`; runs `pi update --all` | +| `skills` | Yes | Yes | Yes | Yes | Requires `skills` or `npx`; runs `skills update` for project + global scopes | | `mise` | Yes | Yes | Yes | No | Requires `mise` | | `go` | Yes | Yes | Yes | Yes | Requires `go`; binary list from config | | `macos` | Yes | No | No | No | Requires `softwareupdate` (opt-in) | -Native Windows default runs auto-select `winget`, `node`, `bun`, `python`, `uv`, `pipx`, `rustup`, `claude`, `pi`, and `go` when their backing commands or config are present. `mise` remains deferred on native Windows until installer ownership can be handled safely. +Native Windows default runs auto-select `winget`, `node`, `bun`, `python`, `uv`, `pipx`, `rustup`, `claude`, `pi`, `skills`, and `go` when their backing commands or config are present. `mise` remains deferred on native Windows until installer ownership can be handled safely. ### 7.3 Module execution order -Fixed: `brew` > `shell` > `repos` > `linux` > `winget` > `node` > `bun` > `python` > `uv` > `mas` > `pipx` > `rustup` > `claude` > `pi` > `mise` > `go` > `macos`. +Fixed: `brew` > `shell` > `repos` > `linux` > `winget` > `node` > `bun` > `python` > `uv` > `mas` > `pipx` > `rustup` > `claude` > `pi` > `skills` > `mise` > `go` > `macos`. Rationale: platform package managers first (`brew`, `linux`, `winget`), then git-backed local repos, then language/runtime tools, then opt-in system modules last. @@ -561,7 +562,22 @@ Purpose: update the `pi` AI coding CLI and its installed extensions (pinned sour - Non-dry-run: `pi update --all` - Side effects: updates `pi` and installed extensions to their latest versions. -### 8.15 `mise` +### 8.15 `skills` + +Purpose: update agent skills installed on the machine in both project and global scopes. + +- Requires: the `skills` CLI (`npx skills`), or `npx` as a fallback adapter. + - Resolution order: a direct `skills` command on `PATH` first, then `npx --yes skills`. + - If neither is available, default runs warn and skip; explicit `--only skills` fails. +- Scope: runs `skills update --project --global`, which the skills CLI resolves to its interactive "Both" scope without prompting. +- Non-dry-run: + - Bash: ` update --project --global [--yes]` + - Native Windows: `skills update --project --global` or `npx --yes skills update --project --global` +- With `-n` / `--non-interactive`: appends `--yes` so upstream-deletion prompts inside the skills CLI are skipped gracefully. +- Dry run: prints the resolved command; executes nothing. +- Side effects: refreshes project-scoped and globally tracked skills from their recorded sources (local/node_modules-sourced skills are skipped by the skills CLI itself). + +### 8.16 `mise` Purpose: update mise itself and upgrade all installed tool versions. @@ -572,7 +588,7 @@ Purpose: update mise itself and upgrade all installed tool versions. - Side effects: updates mise binary and installed tool versions to latest matching constraints. - Native Windows remains deferred: the official Windows installation paths are Scoop and winget, while `mise self-update` is unavailable for package-managed installs. `updates` has no reliable ownership signal that would distinguish those installs from a manually downloaded binary without adding manager-specific coupling. -### 8.16 `go` +### 8.17 `go` Purpose: update Go binaries from a user-specified list. @@ -585,7 +601,7 @@ Purpose: update Go binaries from a user-specified list. - `--only go`: error (return `1`) - Side effects: rebuilds and installs Go binaries to `$GOBIN` or `$GOPATH/bin`. -### 8.17 `macos` +### 8.18 `macos` Purpose: list available macOS software updates. diff --git a/tests/test_cli.sh b/tests/test_cli.sh index 1b33519..24bc7af 100755 --- a/tests/test_cli.sh +++ b/tests/test_cli.sh @@ -30,6 +30,9 @@ if [ -z "$SYSTEM_GIT" ]; then echo "tests: git is required for Git fixture coverage" >&2 exit 1 fi +# Referenced by heredoc-backed test bodies evaluated through run_test. +# shellcheck disable=SC2034 +SYSTEM_BASH="$(command -v bash)" SYSTEM_PYTHON3="$(command -v python3 2>/dev/null || true)" if [ -z "$SYSTEM_PYTHON3" ]; then echo "python3 is required for tests/test_cli.sh" >&2 @@ -371,6 +374,8 @@ write_stub claude 'echo "claude $*" >>"$CALL_LOG"' # shellcheck disable=SC2016 write_stub pi 'echo "pi $*" >>"$CALL_LOG"' # shellcheck disable=SC2016 +write_stub skills 'echo "skills $*" >>"$CALL_LOG"' +# shellcheck disable=SC2016 write_stub softwareupdate 'echo "softwareupdate $*" >>"$CALL_LOG"' test_selected() { @@ -563,7 +568,7 @@ echo "$out" | grep -q '^brew' echo "$out" | grep -q '^shell' echo "$out" | grep -q '^linux' actual_modules="$(printf '%s\n' "$out" | awk '{print $1}' | paste -sd' ' -)" -expected_modules='brew shell repos linux winget node bun python uv mas pipx rustup claude pi mise go macos' +expected_modules='brew shell repos linux winget node bun python uv mas pipx rustup claude pi skills mise go macos' if [ "$actual_modules" != "$expected_modules" ]; then echo "Expected module order: $expected_modules" >&2 echo "Actual module order: $actual_modules" >&2 @@ -1187,7 +1192,7 @@ setup_python_guard_fixture : >"$CALL_LOG" python_no_report_skip_stderr="${tmp_dir}/python-no-report-skip-stderr.log" python_no_report_skip_out="$( - PYTHON_GUARD_NO_REPORT_HELP=1 PYTHONUSERBASE="$python_user_base" PYTHONPATH="$python_path" "$SCRIPT" --skip brew,shell,linux,node,uv,mas,pipx,rustup,claude,mise,go,macos,repos,bun,pi --no-emoji --no-color 2>"$python_no_report_skip_stderr" + PYTHON_GUARD_NO_REPORT_HELP=1 PYTHONUSERBASE="$python_user_base" PYTHONPATH="$python_path" "$SCRIPT" --skip brew,shell,linux,node,uv,mas,pipx,rustup,claude,mise,go,macos,repos,bun,pi,skills --no-emoji --no-color 2>"$python_no_report_skip_stderr" )" echo "$python_no_report_skip_out" | grep -q '^==> python END (SKIP)' grep -q '^WARN: python: skipping guarded user-site upgrades: pip does not support --dry-run --report$' "$python_no_report_skip_stderr" @@ -1739,6 +1744,94 @@ grep -q '^pi update --all$' "$CALL_LOG" UPDATES_TEST_CASE +run_test "skills module updates project and global scopes" <<'UPDATES_TEST_CASE' +: >"$CALL_LOG" +out="$("$SCRIPT" --only skills --no-emoji --no-color)" +echo "$out" | grep -q '^==> skills START$' +echo "$out" | grep -q '^==> skills END (OK)' +grep -q '^skills update --project --global$' "$CALL_LOG" + +UPDATES_TEST_CASE + +run_test "skills module dry-run shows the both-scope command without executing it" <<'UPDATES_TEST_CASE' +: >"$CALL_LOG" +out="$("$SCRIPT" --dry-run --only skills --no-emoji --no-color)" +echo "$out" | grep -q '^DRY RUN: skills update --project --global$' +if [ -s "$CALL_LOG" ]; then + echo "Expected skills dry-run not to execute any command" >&2 + exit 1 +fi + +UPDATES_TEST_CASE + +run_test "skills module adds --yes in non-interactive mode" <<'UPDATES_TEST_CASE' +: >"$CALL_LOG" +"$SCRIPT" -n --only skills --no-emoji >/dev/null +grep -q '^skills update --project --global --yes$' "$CALL_LOG" + +UPDATES_TEST_CASE + +run_test "skills module falls back to npx when the CLI is not installed" <<'UPDATES_TEST_CASE' +rm -f "${stub_bin}/skills" +# shellcheck disable=SC2016 +write_stub npx 'echo "npx $*" >>"$CALL_LOG"' +: >"$CALL_LOG" +out="$("$SCRIPT" --only skills --no-emoji --no-color)" +echo "$out" | grep -q '^==> skills END (OK)' +grep -q '^npx --yes skills update --project --global$' "$CALL_LOG" +: >"$CALL_LOG" +out="$("$SCRIPT" --dry-run --only skills --no-emoji --no-color)" +echo "$out" | grep -q '^DRY RUN: npx --yes skills update --project --global$' +if grep -q '^npx ' "$CALL_LOG"; then + echo "Expected skills dry-run not to execute the npx fallback" >&2 + exit 1 +fi +# shellcheck disable=SC2016 +write_stub skills 'echo "skills $*" >>"$CALL_LOG"' +rm -f "${stub_bin}/npx" + +UPDATES_TEST_CASE + +run_test "skills module skips without skills or npx and errors under --only" <<'UPDATES_TEST_CASE' +rm -f "${stub_bin}/skills" "${stub_bin}/npx" +# Host tool directories can expose a real npx (system Node installs); isolate +# PATH to fixture-controlled executables so neither command can leak in. Stubs +# embed the interpreter path because `env bash` cannot resolve with this PATH. +skills_iso_bin="${tmp_dir}/skills-isolated-bin" +mkdir -p "$skills_iso_bin" +{ + printf '#!%s\n' "$SYSTEM_BASH" + printf 'echo Darwin\n' +} >"${skills_iso_bin}/uname" +{ + printf '#!%s\n' "$SYSTEM_BASH" + printf 'echo updates\n' +} >"${skills_iso_bin}/basename" +chmod +x "${skills_iso_bin}/uname" "${skills_iso_bin}/basename" +saved_path="$PATH" +export PATH="$skills_iso_bin" +skip_stderr="${tmp_dir}/skills-skip-stderr.log" +: >"$skip_stderr" +all_but_skills=(--skip "brew,shell,repos,linux,winget,node,bun,python,uv,mas,pipx,rustup,claude,pi,mise,go,macos" --no-emoji --no-color) +out="$("$SYSTEM_BASH" "$SCRIPT" "${all_but_skills[@]}" 2>"$skip_stderr")" +set +e +out_only="$("$SYSTEM_BASH" "$SCRIPT" --only skills --no-emoji --no-color 2>"$skip_stderr")" +rc=$? +set -e +export PATH="$saved_path" +echo "$out" | grep -q '^==> skills END (SKIP)' +echo "$out" | grep -q '^Skipping skills: neither skills nor npx found\.$' +if [ "$rc" -ne 1 ]; then + echo "Expected --only skills to fail without skills or npx (got $rc)" >&2 + exit 1 +fi +echo "$out_only" | grep -q '^==> skills END (FAIL)' +grep -q 'skills: required command not found' "$skip_stderr" +# shellcheck disable=SC2016 +write_stub skills 'echo "skills $*" >>"$CALL_LOG"' + +UPDATES_TEST_CASE + run_test "empty ncu output means node module reports up-to-date" <<'UPDATES_TEST_CASE' rm -f "${stub_bin}/python" "${stub_bin}/python3" write_ncu_stub '{}' @@ -1892,7 +1985,7 @@ fi exit 1 ' rm -f "${stub_bin}/npx" -node_skip_args=(--skip "brew,shell,repos,linux,winget,bun,python,uv,mas,pipx,rustup,claude,pi,mise,go,macos" --no-emoji --no-color) +node_skip_args=(--skip "brew,shell,repos,linux,winget,bun,python,uv,mas,pipx,rustup,claude,pi,skills,mise,go,macos" --no-emoji --no-color) node_capability_stderr="${tmp_dir}/node-capability-stderr.log" out="$("$SCRIPT" "${node_skip_args[@]}" 2>"$node_capability_stderr")" echo "$out" | grep -q '^==> node END (SKIP)' diff --git a/tests/test_windows_native.ps1 b/tests/test_windows_native.ps1 index b29f745..6c0245c 100644 --- a/tests/test_windows_native.ps1 +++ b/tests/test_windows_native.ps1 @@ -2186,6 +2186,37 @@ if (Should-RunTest 'native Claude and Pi modules cover missing dry-run failure s } } +if (Should-RunTest 'native payload runs Skills update module') { + Invoke-TestCase 'native payload runs Skills update module' { + Invoke-WithTempInstall { + param($installRoot) + Install-RepoWindowsRuntime -RepoRoot $repoRoot -InstallRoot $installRoot -Version $currentReleaseVersion + $stubDir = Join-Path $installRoot 'stubs' + $log = Join-Path $installRoot 'skills-module.log' + $null = New-Item -ItemType Directory -Path $stubDir -Force + Write-CmdStub -Path (Join-Path $stubDir 'skills.cmd') -Lines @(('echo skills:%*>>"{0}"' -f $log)) + $result = Invoke-Bootstrap -InstallRoot $installRoot -ArgumentList @('--no-self-update', '--only', 'skills', '--no-emoji', '--no-color') -Environment @{ PATH = $stubDir } + Assert-Equal -Expected 0 -Actual $result.ExitCode -Message "Skills module should succeed`n$($result.Output)" + $calls = Get-Content -LiteralPath $log -Raw + Assert-Match -Text $calls -Pattern '(?m)^skills:update --project --global\s*$' -Message 'Skills module should update project and global scopes without prompting' + + $emptyPath = Join-Path $installRoot 'empty-skills-path' + $null = New-Item -ItemType Directory -Path $emptyPath -Force + $missing = Invoke-Bootstrap -InstallRoot $installRoot -ArgumentList @('--no-self-update', '--only', 'skills', '--no-emoji') -Environment @{ PATH = $emptyPath } + Assert-Equal -Expected 1 -Actual $missing.ExitCode -Message 'explicit missing Skills dependency should fail the run' + Assert-Match -Text $missing.Output -Pattern '(?i)skills not found' -Message 'missing Skills dependency should be explicit' + + $npxLog = Join-Path $installRoot 'skills-npx.log' + Write-CmdStub -Path (Join-Path $stubDir 'npx.cmd') -Lines @(('echo npx:%*>>"{0}"' -f $npxLog)) + Remove-Item -LiteralPath (Join-Path $stubDir 'skills.cmd') -Force + $fallback = Invoke-Bootstrap -InstallRoot $installRoot -ArgumentList @('--no-self-update', '--only', 'skills', '--non-interactive', '--no-emoji', '--no-color') -Environment @{ PATH = $stubDir } + Assert-Equal -Expected 0 -Actual $fallback.ExitCode -Message "Skills npx fallback should succeed`n$($fallback.Output)" + $npxCalls = Get-Content -LiteralPath $npxLog -Raw + Assert-Match -Text $npxCalls -Pattern '(?m)^npx:--yes skills update --project --global --yes\s*$' -Message 'npx fallback should update both scopes non-interactively' + } + } +} + if (Should-RunTest 'native payload rejects duplicate or noncanonical version assignments') { Invoke-TestCase 'native payload rejects duplicate or noncanonical version assignments' { Invoke-WithTempInstall { diff --git a/updates b/updates index 0b17e1a..48138e2 100755 --- a/updates +++ b/updates @@ -78,6 +78,7 @@ pipx|module_pipx|macos,linux|0|Upgrade pipx-managed apps via pipx rustup|module_rustup|macos,linux|0|Update Rust toolchains via rustup claude|module_claude|macos,linux|0|Update Claude Code CLI pi|module_pi|macos,linux|0|Update pi AI CLI and extensions via pi update --all +skills|module_skills|macos,linux|0|Update agent skills in project + global scopes mise|module_mise|macos,linux|0|Update mise and upgrade installed tools go|module_go|macos,linux|0|Update Go binaries from GO_BINARIES config macos|module_macos|macos|1|List available macOS software updates (opt-in)' @@ -2530,7 +2531,7 @@ json_pairs() { ############################################################################### # SECTION: modules — brew, shell, linux, node, python, uv, mas, pipx, -# rustup, claude, mise, go, macos +# rustup, claude, pi, skills, mise, go, macos ############################################################################### python_externally_managed() { @@ -3671,6 +3672,47 @@ module_pi() { return 0 } +skills_update_argv() { + # Sets the global array SKILLS_UPDATE_ARGV to the invocation that updates + # both scopes without prompts. `skills update` with both --project and + # --global resolves to the interactive "Both" scope without a prompt. + SKILLS_UPDATE_ARGV=() + if cmd_exists skills; then + SKILLS_UPDATE_ARGV=(skills) + elif cmd_exists npx; then + SKILLS_UPDATE_ARGV=(npx --yes skills) + else + return 1 + fi + + SKILLS_UPDATE_ARGV+=(update --project --global) + if [ "$NON_INTERACTIVE" -eq 1 ]; then + SKILLS_UPDATE_ARGV+=(--yes) + fi + return 0 +} + +module_skills() { + log "Agent skills $(emoji "🧠")" + + if ! skills_update_argv; then + if missing_dependency_is_error; then + err "skills: required command not found: skills (or npx)" + return 1 + fi + log "Skipping skills: neither skills nor npx found." + return 2 + fi + + if [ "$DRY_RUN" -eq 1 ]; then + log "DRY RUN: ${SKILLS_UPDATE_ARGV[*]}" + return 0 + fi + + run "${SKILLS_UPDATE_ARGV[@]}" || return 1 + return 0 +} + module_mise() { log "mise $(emoji "🧩")" ensure_cmd_or_skip mise mise || return $? diff --git a/updates-main.ps1 b/updates-main.ps1 index 5749475..fab44b5 100644 --- a/updates-main.ps1 +++ b/updates-main.ps1 @@ -79,6 +79,7 @@ $script:ModuleRegistry = @( [ordered]@{ Name = 'rustup'; Platforms = @('macos', 'linux', 'windows'); Default = $true; Handler = 'Invoke-ModuleRustup'; Description = 'Update Rust toolchains via rustup' }, [ordered]@{ Name = 'claude'; Platforms = @('macos', 'linux', 'windows'); Default = $true; Handler = 'Invoke-ModuleClaude'; Description = 'Update Claude Code CLI' }, [ordered]@{ Name = 'pi'; Platforms = @('macos', 'linux', 'windows'); Default = $true; Handler = 'Invoke-ModulePi'; Description = 'Update pi AI CLI and extensions via pi update --all' }, + [ordered]@{ Name = 'skills'; Platforms = @('macos', 'linux', 'windows'); Default = $true; Handler = 'Invoke-ModuleSkills'; Description = 'Update agent skills in project + global scopes' }, [ordered]@{ Name = 'mise'; Platforms = @('macos', 'linux'); Default = $true; Description = 'Update mise and upgrade installed tools' }, [ordered]@{ Name = 'go'; Platforms = @('macos', 'linux', 'windows'); Default = $true; Handler = 'Invoke-ModuleGo'; Description = 'Update Go binaries from GO_BINARIES config' }, [ordered]@{ Name = 'macos'; Platforms = @('macos'); Default = $false; Description = 'List available macOS software updates (opt-in)' } @@ -1373,6 +1374,38 @@ function Invoke-ModulePi { return 0 } +function Invoke-ModuleSkills { + # `skills update` with both --project and --global resolves to the + # interactive "Both" scope without a prompt. + $arguments = New-Object System.Collections.Generic.List[string] + $skills = Resolve-ApplicationCommand @('skills.exe', 'skills.cmd', 'skills') + if ($skills) { + $filePath = $skills + $arguments.Add('update') + } else { + $npx = Resolve-ApplicationCommand @('npx.exe', 'npx.cmd', 'npx') + if (-not $npx) { + return (Resolve-MissingDependency -ModuleName 'skills' -Detail 'skills not found (need skills or npx).') + } + $filePath = $npx + $arguments.Add('--yes') + $arguments.Add('skills') + $arguments.Add('update') + } + $arguments.Add('--project') + $arguments.Add('--global') + if ($script:NonInteractive) { + $arguments.Add('--yes') + } + + $result = Invoke-LoggedProcess -FilePath $filePath -ArgumentList $arguments.ToArray() + if ($result.ExitCode -ne 0) { + Write-ErrorLine 'skills: update failed' + return 1 + } + return 0 +} + function Invoke-Module { param([string]$ModuleName)