feat(lib): add package runner detection helpers - #118

Merged
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners
Apr 11, 2026
Merged

feat(lib): add package runner detection helpers#118
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners

Conversation

@wyattjoh

@wyattjohwyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add lib/runners.ts with a Runner type, RUNNERS constant (bunx, npx, pnpm dlx, yarn dlx), detectAvailableRunners() (uses Bun.which), and preferredRunner() (picks the runner matching the project's package manager).
  • Add a select<T> wrapper to lib/prompts.ts mirroring the existing confirm wrapper for piped-stdin safety.
  • Use the new helpers in init/skills.ts: installSkills now picks a runner instead of hardcoding npx, prompts the user to choose when multiple runners are available (with the project's pm runner labelled (detected) as the default), and threads packageManager through from init/index.ts.

Originally stacked on #116, now rebased onto main after #116 merged.

Test plan

  • bun run test passes (59 passed)
  • bun test packages/cli-core/src/lib/runners.test.ts passes (16 tests covering RUNNERS shape, runnerCommand, preferredRunner, detectAvailableRunners)
  • Manual: on a bun-only machine (no npx on PATH), run clerk init and confirm installSkills falls back to bunx instead of failing

@wyattjoh
wyattjoh marked this pull request as draft April 7, 2026 17:12
@wyattjoh
wyattjohforce-pushed the fix/init-skills-installer branch 2 times, most recently from 6cf5261 to dd71548CompareApril 7, 2026 17:41
Base automatically changed from fix/init-skills-installer to mainApril 7, 2026 18:16
@wyattjoh
wyattjoh marked this pull request as ready for review April 7, 2026 18:25
@wyattjoh

wyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai

coderabbitaiBot commented Apr 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a runner abstraction and runtime runner detection to make skill installation runner-agnostic (bunx, npx, pnpm, yarn). Reworks buildSkillsArgs to return runner-agnostic arguments and updates installSkills to accept a packageManager, detect available runners, choose a preferred runner (or prompt interactively), and spawn the selected runner. Introduces runners module and tests, a generic select prompt helper, updates skills tests, and updates README instructions to describe runner-specific install commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately reflects the main change: adding package runner detection helpers (runners.ts module with detection and selection logic) to the lib directory.
Description check✅ PassedThe description clearly explains the changeset: adding runners.ts with Runner type and utilities, adding select to prompts.ts, and integrating these helpers into the init/skills flow with specific runner selection behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The select() prompt inside installSkills() can reject
when the user cancels; wrap the select<Runner>({...}) call in a try/catch and
call throwUserAbort() in the catch to ensure a clean exit on user cancellation.
Locate the runner = await select<Runner>(...) block and surround it with try {
... } catch { throwUserAbort(); } so prompt cancellations are handled
gracefully.
In `@packages/cli-core/src/lib/runners.ts`:
- Around line 38-42: detectAvailableRunners() currently treats presence of the
"yarn" binary (checked via Bun.which()) as sufficient to offer the RUNNERS entry
for yarn which hardcodes prefixArgs ["dlx"], but Yarn Classic (v1) lacks the dlx
subcommand; update detectAvailableRunners() to additionally probe the installed
yarn for dlx support by either checking yarn --version and ensuring it's Berry
(>=2) or by trying to execute a harmless yarn dlx --version probe, and only
include the RUNNERS entry with id "yarn" (from RUNNERS) when that probe
succeeds; keep using Bun.which() to find the binary but gate adding the yarn
runner on the version/subcommand check so Classic Yarn is not advertised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cfd7c5df-7c77-4d33-a9f4-2b6ec636eb24

📥 Commits

Reviewing files that changed from the base of the PR and between c5bc9ef and 4edf28e.

📒 Files selected for processing (4)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts

Comment threadpackages/cli-core/src/commands/init/skills.ts
Comment threadpackages/cli-core/src/lib/runners.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/commands/init/skills.ts (1)

117-124: ⚠️ Potential issue | 🟠 Major

Handle cancellation on the runner picker.

select() can reject on Ctrl+C, and this new path currently bubbles out of installSkills() as an uncaught prompt error instead of a clean command abort. Wrap the picker in try/catch and call throwUserAbort() from the catch. As per coding guidelines, "Call throwUserAbort() when the user cancels a prompt or confirmation for clean exit".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli-core/src/commands/init/skills.ts` around lines 117 - 124, The
runner picker can reject on Ctrl+C and currently bubbles an uncaught prompt
error from installSkills; wrap the select<Runner> call in a try/catch around the
code that assigns runner and, in the catch block, call throwUserAbort() to
perform a clean command abort (reference the select<Runner> invocation that
assigns runner in installSkills and the throwUserAbort() helper); ensure the
catch only handles prompt cancellation and rethrow other unexpected errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The runner picker can reject on Ctrl+C and currently
bubbles an uncaught prompt error from installSkills; wrap the select<Runner>
call in a try/catch around the code that assigns runner and, in the catch block,
call throwUserAbort() to perform a clean command abort (reference the
select<Runner> invocation that assigns runner in installSkills and the
throwUserAbort() helper); ensure the catch only handles prompt cancellation and
rethrow other unexpected errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f92a3a-5b42-42d7-9e1a-ff2d25f7fa92

📥 Commits

Reviewing files that changed from the base of the PR and between 4edf28e and 2e3b67f.

📒 Files selected for processing (7)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/skills.test.ts
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/prompts.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts
💤 Files with no reviewable changes (1)
  • packages/cli-core/src/commands/init/skills.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/lib/runners.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/prompts.ts

@wyattjoh
wyattjoh requested a review from RaillyApril 8, 2026 20:38
@wyattjoh
wyattjohforce-pushed the feat/lib-runners branch 2 times, most recently from 006f9cc to 515fd36CompareApril 9, 2026 20:23
const available = detectAvailableRunners();
if (available.length === 0) {
const suggested = runnerForPackageManager(packageManager);
console.log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the console.log commands to use the new log?
I think we will need to add a lint rule.

@Railly

Copy link
Copy Markdown
Contributor

@wyattjoh could you update the console.logs with the log utility Carp created? 👀

@wyattjoh
wyattjoh requested a review from jfosheeApril 9, 2026 22:59

@jfosheejfoshee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how this is implemented and tested. I was thinking it would be pretty painful to do a real integration test of this, so I think mocking which and spawnSync is a good strategy. That said, you point out they may not always be mockable.

So, something to consider as time marches on, this might be one of those times we should add a level of indirection. If there are a handful of system/shell functions that we use, we could wrap them up into an interface like:

interface ShellCommands {
which(string): string;
...
}

And that would have a trivial bun implementation. And would also be trivially mocked without fear the tests aren't running on some platforms.

expect(result.map((r) => r.id)).toEqual(["bunx", "npx", "pnpm"]);
});

test("includes yarn when `yarn dlx --help` exits 0 (Yarn Berry)", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about these yarn flavors!

Comment threadpackages/cli-core/src/lib/runners.ts Outdated
* Known runners in preference order. When no project package manager is
* provided, the first available runner from this list wins.
*/
export const RUNNERS: readonly Runner[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: might be more clear if this was named KNOWN_RUNNERS

Adds `lib/runners.ts` with a Runner type, RUNNERS constant (bunx, npx,
pnpm dlx, yarn dlx), `detectAvailableRunners()` (uses Bun.which), and
`preferredRunner()` (picks the runner matching the project's package
manager). Also adds a `select<T>` wrapper to lib/prompts.ts mirroring
the existing `confirm` wrapper for piped-stdin safety.
Uses the new helpers in init/skills.ts: `installSkills` now picks a
runner instead of hardcoding npx, prompts the user to choose when
multiple runners are available (with the project's pm runner labelled
"(detected)" as the default), and threads packageManager through from
init/index.ts.
Adds runnerForPackageManager() helper so the skills install fallbacks
suggest a runner that matches the project's package manager (e.g.
`bunx skills add` for a Bun project) instead of hardcoding `npx`.
Updates the init README to describe runner detection.
Yarn Classic (v1) lacks the `dlx` subcommand, so detecting `yarn` on PATH
via `Bun.which()` was not enough to safely advertise `yarn dlx`. Probe
`yarn dlx --help` and only include the runner when it exits 0 (Berry).
Switch all output in skills.ts and bootstrap.ts to use the central log
object (log.info, log.warn, log.success, log.blank) so output respects
log levels, throttling, and test capture.
Removes direct color function wrappers (cyan, yellow, dim) in favour of
log method semantics: log.warn handles yellow, log.success handles green,
and backtick spans in messages handle cyan highlighting.
Logging guidance now lives in .claude/rules/logging.md, scoped to
packages/cli-core/src/** so it auto-loads only when working on CLI source.
Strips the Logging, Error Handling, Testing, and Commands sections from
CLAUDE.md since each is already covered by an autoloaded rule file
(.claude/rules/{logging,errors,testing,commands}.md), and removes the
explicit .claude/rules/e2e.md reference for the same reason.
Adds the eslint no-console rule via oxlint, scoped to production code
under packages/cli-core/src so all output is forced through the log
helper. Test files and scripts/ are exempt since they legitimately use
console for spy/mock plumbing and release tooling output.
@wyattjoh
wyattjoh merged commit 6ce3cf3 into mainApr 11, 2026
6 checks passed
@wyattjoh
wyattjoh deleted the feat/lib-runners branch April 11, 2026 06:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@wyattjoh@Railly@jfoshee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(lib): add package runner detection helpers - #118

Merged
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners
Apr 11, 2026
Merged

feat(lib): add package runner detection helpers#118
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners

Conversation

@wyattjoh

@wyattjohwyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add lib/runners.ts with a Runner type, RUNNERS constant (bunx, npx, pnpm dlx, yarn dlx), detectAvailableRunners() (uses Bun.which), and preferredRunner() (picks the runner matching the project's package manager).
  • Add a select<T> wrapper to lib/prompts.ts mirroring the existing confirm wrapper for piped-stdin safety.
  • Use the new helpers in init/skills.ts: installSkills now picks a runner instead of hardcoding npx, prompts the user to choose when multiple runners are available (with the project's pm runner labelled (detected) as the default), and threads packageManager through from init/index.ts.

Originally stacked on #116, now rebased onto main after #116 merged.

Test plan

  • bun run test passes (59 passed)
  • bun test packages/cli-core/src/lib/runners.test.ts passes (16 tests covering RUNNERS shape, runnerCommand, preferredRunner, detectAvailableRunners)
  • Manual: on a bun-only machine (no npx on PATH), run clerk init and confirm installSkills falls back to bunx instead of failing

@wyattjoh
wyattjoh marked this pull request as draft April 7, 2026 17:12
@wyattjoh
wyattjohforce-pushed the fix/init-skills-installer branch 2 times, most recently from 6cf5261 to dd71548CompareApril 7, 2026 17:41
Base automatically changed from fix/init-skills-installer to mainApril 7, 2026 18:16
@wyattjoh
wyattjoh marked this pull request as ready for review April 7, 2026 18:25
@wyattjoh

wyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai

coderabbitaiBot commented Apr 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a runner abstraction and runtime runner detection to make skill installation runner-agnostic (bunx, npx, pnpm, yarn). Reworks buildSkillsArgs to return runner-agnostic arguments and updates installSkills to accept a packageManager, detect available runners, choose a preferred runner (or prompt interactively), and spawn the selected runner. Introduces runners module and tests, a generic select prompt helper, updates skills tests, and updates README instructions to describe runner-specific install commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately reflects the main change: adding package runner detection helpers (runners.ts module with detection and selection logic) to the lib directory.
Description check✅ PassedThe description clearly explains the changeset: adding runners.ts with Runner type and utilities, adding select to prompts.ts, and integrating these helpers into the init/skills flow with specific runner selection behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The select() prompt inside installSkills() can reject
when the user cancels; wrap the select<Runner>({...}) call in a try/catch and
call throwUserAbort() in the catch to ensure a clean exit on user cancellation.
Locate the runner = await select<Runner>(...) block and surround it with try {
... } catch { throwUserAbort(); } so prompt cancellations are handled
gracefully.
In `@packages/cli-core/src/lib/runners.ts`:
- Around line 38-42: detectAvailableRunners() currently treats presence of the
"yarn" binary (checked via Bun.which()) as sufficient to offer the RUNNERS entry
for yarn which hardcodes prefixArgs ["dlx"], but Yarn Classic (v1) lacks the dlx
subcommand; update detectAvailableRunners() to additionally probe the installed
yarn for dlx support by either checking yarn --version and ensuring it's Berry
(>=2) or by trying to execute a harmless yarn dlx --version probe, and only
include the RUNNERS entry with id "yarn" (from RUNNERS) when that probe
succeeds; keep using Bun.which() to find the binary but gate adding the yarn
runner on the version/subcommand check so Classic Yarn is not advertised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cfd7c5df-7c77-4d33-a9f4-2b6ec636eb24

📥 Commits

Reviewing files that changed from the base of the PR and between c5bc9ef and 4edf28e.

📒 Files selected for processing (4)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts

Comment threadpackages/cli-core/src/commands/init/skills.ts
Comment threadpackages/cli-core/src/lib/runners.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/commands/init/skills.ts (1)

117-124: ⚠️ Potential issue | 🟠 Major

Handle cancellation on the runner picker.

select() can reject on Ctrl+C, and this new path currently bubbles out of installSkills() as an uncaught prompt error instead of a clean command abort. Wrap the picker in try/catch and call throwUserAbort() from the catch. As per coding guidelines, "Call throwUserAbort() when the user cancels a prompt or confirmation for clean exit".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli-core/src/commands/init/skills.ts` around lines 117 - 124, The
runner picker can reject on Ctrl+C and currently bubbles an uncaught prompt
error from installSkills; wrap the select<Runner> call in a try/catch around the
code that assigns runner and, in the catch block, call throwUserAbort() to
perform a clean command abort (reference the select<Runner> invocation that
assigns runner in installSkills and the throwUserAbort() helper); ensure the
catch only handles prompt cancellation and rethrow other unexpected errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The runner picker can reject on Ctrl+C and currently
bubbles an uncaught prompt error from installSkills; wrap the select<Runner>
call in a try/catch around the code that assigns runner and, in the catch block,
call throwUserAbort() to perform a clean command abort (reference the
select<Runner> invocation that assigns runner in installSkills and the
throwUserAbort() helper); ensure the catch only handles prompt cancellation and
rethrow other unexpected errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f92a3a-5b42-42d7-9e1a-ff2d25f7fa92

📥 Commits

Reviewing files that changed from the base of the PR and between 4edf28e and 2e3b67f.

📒 Files selected for processing (7)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/skills.test.ts
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/prompts.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts
💤 Files with no reviewable changes (1)
  • packages/cli-core/src/commands/init/skills.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/lib/runners.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/prompts.ts

@wyattjoh
wyattjoh requested a review from RaillyApril 8, 2026 20:38
@wyattjoh
wyattjohforce-pushed the feat/lib-runners branch 2 times, most recently from 006f9cc to 515fd36CompareApril 9, 2026 20:23
const available = detectAvailableRunners();
if (available.length === 0) {
const suggested = runnerForPackageManager(packageManager);
console.log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the console.log commands to use the new log?
I think we will need to add a lint rule.

@Railly

Copy link
Copy Markdown
Contributor

@wyattjoh could you update the console.logs with the log utility Carp created? 👀

@wyattjoh
wyattjoh requested a review from jfosheeApril 9, 2026 22:59

@jfosheejfoshee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how this is implemented and tested. I was thinking it would be pretty painful to do a real integration test of this, so I think mocking which and spawnSync is a good strategy. That said, you point out they may not always be mockable.

So, something to consider as time marches on, this might be one of those times we should add a level of indirection. If there are a handful of system/shell functions that we use, we could wrap them up into an interface like:

interface ShellCommands {
which(string): string;
...
}

And that would have a trivial bun implementation. And would also be trivially mocked without fear the tests aren't running on some platforms.

expect(result.map((r) => r.id)).toEqual(["bunx", "npx", "pnpm"]);
});

test("includes yarn when `yarn dlx --help` exits 0 (Yarn Berry)", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about these yarn flavors!

Comment threadpackages/cli-core/src/lib/runners.ts Outdated
* Known runners in preference order. When no project package manager is
* provided, the first available runner from this list wins.
*/
export const RUNNERS: readonly Runner[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: might be more clear if this was named KNOWN_RUNNERS

Adds `lib/runners.ts` with a Runner type, RUNNERS constant (bunx, npx,
pnpm dlx, yarn dlx), `detectAvailableRunners()` (uses Bun.which), and
`preferredRunner()` (picks the runner matching the project's package
manager). Also adds a `select<T>` wrapper to lib/prompts.ts mirroring
the existing `confirm` wrapper for piped-stdin safety.
Uses the new helpers in init/skills.ts: `installSkills` now picks a
runner instead of hardcoding npx, prompts the user to choose when
multiple runners are available (with the project's pm runner labelled
"(detected)" as the default), and threads packageManager through from
init/index.ts.
Adds runnerForPackageManager() helper so the skills install fallbacks
suggest a runner that matches the project's package manager (e.g.
`bunx skills add` for a Bun project) instead of hardcoding `npx`.
Updates the init README to describe runner detection.
Yarn Classic (v1) lacks the `dlx` subcommand, so detecting `yarn` on PATH
via `Bun.which()` was not enough to safely advertise `yarn dlx`. Probe
`yarn dlx --help` and only include the runner when it exits 0 (Berry).
Switch all output in skills.ts and bootstrap.ts to use the central log
object (log.info, log.warn, log.success, log.blank) so output respects
log levels, throttling, and test capture.
Removes direct color function wrappers (cyan, yellow, dim) in favour of
log method semantics: log.warn handles yellow, log.success handles green,
and backtick spans in messages handle cyan highlighting.
Logging guidance now lives in .claude/rules/logging.md, scoped to
packages/cli-core/src/** so it auto-loads only when working on CLI source.
Strips the Logging, Error Handling, Testing, and Commands sections from
CLAUDE.md since each is already covered by an autoloaded rule file
(.claude/rules/{logging,errors,testing,commands}.md), and removes the
explicit .claude/rules/e2e.md reference for the same reason.
Adds the eslint no-console rule via oxlint, scoped to production code
under packages/cli-core/src so all output is forced through the log
helper. Test files and scripts/ are exempt since they legitimately use
console for spy/mock plumbing and release tooling output.
@wyattjoh
wyattjoh merged commit 6ce3cf3 into mainApr 11, 2026
6 checks passed
@wyattjoh
wyattjoh deleted the feat/lib-runners branch April 11, 2026 06:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@wyattjoh@Railly@jfoshee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(lib): add package runner detection helpers - #118

Merged
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners
Apr 11, 2026
Merged

feat(lib): add package runner detection helpers#118
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners

Conversation

@wyattjoh

@wyattjohwyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add lib/runners.ts with a Runner type, RUNNERS constant (bunx, npx, pnpm dlx, yarn dlx), detectAvailableRunners() (uses Bun.which), and preferredRunner() (picks the runner matching the project's package manager).
  • Add a select<T> wrapper to lib/prompts.ts mirroring the existing confirm wrapper for piped-stdin safety.
  • Use the new helpers in init/skills.ts: installSkills now picks a runner instead of hardcoding npx, prompts the user to choose when multiple runners are available (with the project's pm runner labelled (detected) as the default), and threads packageManager through from init/index.ts.

Originally stacked on #116, now rebased onto main after #116 merged.

Test plan

  • bun run test passes (59 passed)
  • bun test packages/cli-core/src/lib/runners.test.ts passes (16 tests covering RUNNERS shape, runnerCommand, preferredRunner, detectAvailableRunners)
  • Manual: on a bun-only machine (no npx on PATH), run clerk init and confirm installSkills falls back to bunx instead of failing

@wyattjoh
wyattjoh marked this pull request as draft April 7, 2026 17:12
@wyattjoh
wyattjohforce-pushed the fix/init-skills-installer branch 2 times, most recently from 6cf5261 to dd71548CompareApril 7, 2026 17:41
Base automatically changed from fix/init-skills-installer to mainApril 7, 2026 18:16
@wyattjoh
wyattjoh marked this pull request as ready for review April 7, 2026 18:25
@wyattjoh

wyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai

coderabbitaiBot commented Apr 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a runner abstraction and runtime runner detection to make skill installation runner-agnostic (bunx, npx, pnpm, yarn). Reworks buildSkillsArgs to return runner-agnostic arguments and updates installSkills to accept a packageManager, detect available runners, choose a preferred runner (or prompt interactively), and spawn the selected runner. Introduces runners module and tests, a generic select prompt helper, updates skills tests, and updates README instructions to describe runner-specific install commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately reflects the main change: adding package runner detection helpers (runners.ts module with detection and selection logic) to the lib directory.
Description check✅ PassedThe description clearly explains the changeset: adding runners.ts with Runner type and utilities, adding select to prompts.ts, and integrating these helpers into the init/skills flow with specific runner selection behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The select() prompt inside installSkills() can reject
when the user cancels; wrap the select<Runner>({...}) call in a try/catch and
call throwUserAbort() in the catch to ensure a clean exit on user cancellation.
Locate the runner = await select<Runner>(...) block and surround it with try {
... } catch { throwUserAbort(); } so prompt cancellations are handled
gracefully.
In `@packages/cli-core/src/lib/runners.ts`:
- Around line 38-42: detectAvailableRunners() currently treats presence of the
"yarn" binary (checked via Bun.which()) as sufficient to offer the RUNNERS entry
for yarn which hardcodes prefixArgs ["dlx"], but Yarn Classic (v1) lacks the dlx
subcommand; update detectAvailableRunners() to additionally probe the installed
yarn for dlx support by either checking yarn --version and ensuring it's Berry
(>=2) or by trying to execute a harmless yarn dlx --version probe, and only
include the RUNNERS entry with id "yarn" (from RUNNERS) when that probe
succeeds; keep using Bun.which() to find the binary but gate adding the yarn
runner on the version/subcommand check so Classic Yarn is not advertised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cfd7c5df-7c77-4d33-a9f4-2b6ec636eb24

📥 Commits

Reviewing files that changed from the base of the PR and between c5bc9ef and 4edf28e.

📒 Files selected for processing (4)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts

Comment threadpackages/cli-core/src/commands/init/skills.ts
Comment threadpackages/cli-core/src/lib/runners.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/commands/init/skills.ts (1)

117-124: ⚠️ Potential issue | 🟠 Major

Handle cancellation on the runner picker.

select() can reject on Ctrl+C, and this new path currently bubbles out of installSkills() as an uncaught prompt error instead of a clean command abort. Wrap the picker in try/catch and call throwUserAbort() from the catch. As per coding guidelines, "Call throwUserAbort() when the user cancels a prompt or confirmation for clean exit".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli-core/src/commands/init/skills.ts` around lines 117 - 124, The
runner picker can reject on Ctrl+C and currently bubbles an uncaught prompt
error from installSkills; wrap the select<Runner> call in a try/catch around the
code that assigns runner and, in the catch block, call throwUserAbort() to
perform a clean command abort (reference the select<Runner> invocation that
assigns runner in installSkills and the throwUserAbort() helper); ensure the
catch only handles prompt cancellation and rethrow other unexpected errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The runner picker can reject on Ctrl+C and currently
bubbles an uncaught prompt error from installSkills; wrap the select<Runner>
call in a try/catch around the code that assigns runner and, in the catch block,
call throwUserAbort() to perform a clean command abort (reference the
select<Runner> invocation that assigns runner in installSkills and the
throwUserAbort() helper); ensure the catch only handles prompt cancellation and
rethrow other unexpected errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f92a3a-5b42-42d7-9e1a-ff2d25f7fa92

📥 Commits

Reviewing files that changed from the base of the PR and between 4edf28e and 2e3b67f.

📒 Files selected for processing (7)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/skills.test.ts
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/prompts.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts
💤 Files with no reviewable changes (1)
  • packages/cli-core/src/commands/init/skills.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/lib/runners.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/prompts.ts

@wyattjoh
wyattjoh requested a review from RaillyApril 8, 2026 20:38
@wyattjoh
wyattjohforce-pushed the feat/lib-runners branch 2 times, most recently from 006f9cc to 515fd36CompareApril 9, 2026 20:23
const available = detectAvailableRunners();
if (available.length === 0) {
const suggested = runnerForPackageManager(packageManager);
console.log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the console.log commands to use the new log?
I think we will need to add a lint rule.

@Railly

Copy link
Copy Markdown
Contributor

@wyattjoh could you update the console.logs with the log utility Carp created? 👀

@wyattjoh
wyattjoh requested a review from jfosheeApril 9, 2026 22:59

@jfosheejfoshee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how this is implemented and tested. I was thinking it would be pretty painful to do a real integration test of this, so I think mocking which and spawnSync is a good strategy. That said, you point out they may not always be mockable.

So, something to consider as time marches on, this might be one of those times we should add a level of indirection. If there are a handful of system/shell functions that we use, we could wrap them up into an interface like:

interface ShellCommands {
which(string): string;
...
}

And that would have a trivial bun implementation. And would also be trivially mocked without fear the tests aren't running on some platforms.

expect(result.map((r) => r.id)).toEqual(["bunx", "npx", "pnpm"]);
});

test("includes yarn when `yarn dlx --help` exits 0 (Yarn Berry)", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about these yarn flavors!

Comment threadpackages/cli-core/src/lib/runners.ts Outdated
* Known runners in preference order. When no project package manager is
* provided, the first available runner from this list wins.
*/
export const RUNNERS: readonly Runner[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: might be more clear if this was named KNOWN_RUNNERS

Adds `lib/runners.ts` with a Runner type, RUNNERS constant (bunx, npx,
pnpm dlx, yarn dlx), `detectAvailableRunners()` (uses Bun.which), and
`preferredRunner()` (picks the runner matching the project's package
manager). Also adds a `select<T>` wrapper to lib/prompts.ts mirroring
the existing `confirm` wrapper for piped-stdin safety.
Uses the new helpers in init/skills.ts: `installSkills` now picks a
runner instead of hardcoding npx, prompts the user to choose when
multiple runners are available (with the project's pm runner labelled
"(detected)" as the default), and threads packageManager through from
init/index.ts.
Adds runnerForPackageManager() helper so the skills install fallbacks
suggest a runner that matches the project's package manager (e.g.
`bunx skills add` for a Bun project) instead of hardcoding `npx`.
Updates the init README to describe runner detection.
Yarn Classic (v1) lacks the `dlx` subcommand, so detecting `yarn` on PATH
via `Bun.which()` was not enough to safely advertise `yarn dlx`. Probe
`yarn dlx --help` and only include the runner when it exits 0 (Berry).
Switch all output in skills.ts and bootstrap.ts to use the central log
object (log.info, log.warn, log.success, log.blank) so output respects
log levels, throttling, and test capture.
Removes direct color function wrappers (cyan, yellow, dim) in favour of
log method semantics: log.warn handles yellow, log.success handles green,
and backtick spans in messages handle cyan highlighting.
Logging guidance now lives in .claude/rules/logging.md, scoped to
packages/cli-core/src/** so it auto-loads only when working on CLI source.
Strips the Logging, Error Handling, Testing, and Commands sections from
CLAUDE.md since each is already covered by an autoloaded rule file
(.claude/rules/{logging,errors,testing,commands}.md), and removes the
explicit .claude/rules/e2e.md reference for the same reason.
Adds the eslint no-console rule via oxlint, scoped to production code
under packages/cli-core/src so all output is forced through the log
helper. Test files and scripts/ are exempt since they legitimately use
console for spy/mock plumbing and release tooling output.
@wyattjoh
wyattjoh merged commit 6ce3cf3 into mainApr 11, 2026
6 checks passed
@wyattjoh
wyattjoh deleted the feat/lib-runners branch April 11, 2026 06:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@wyattjoh@Railly@jfoshee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(lib): add package runner detection helpers - #118

Merged
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners
Apr 11, 2026
Merged

feat(lib): add package runner detection helpers#118
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners

Conversation

@wyattjoh

@wyattjohwyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add lib/runners.ts with a Runner type, RUNNERS constant (bunx, npx, pnpm dlx, yarn dlx), detectAvailableRunners() (uses Bun.which), and preferredRunner() (picks the runner matching the project's package manager).
  • Add a select<T> wrapper to lib/prompts.ts mirroring the existing confirm wrapper for piped-stdin safety.
  • Use the new helpers in init/skills.ts: installSkills now picks a runner instead of hardcoding npx, prompts the user to choose when multiple runners are available (with the project's pm runner labelled (detected) as the default), and threads packageManager through from init/index.ts.

Originally stacked on #116, now rebased onto main after #116 merged.

Test plan

  • bun run test passes (59 passed)
  • bun test packages/cli-core/src/lib/runners.test.ts passes (16 tests covering RUNNERS shape, runnerCommand, preferredRunner, detectAvailableRunners)
  • Manual: on a bun-only machine (no npx on PATH), run clerk init and confirm installSkills falls back to bunx instead of failing

@wyattjoh
wyattjoh marked this pull request as draft April 7, 2026 17:12
@wyattjoh
wyattjohforce-pushed the fix/init-skills-installer branch 2 times, most recently from 6cf5261 to dd71548CompareApril 7, 2026 17:41
Base automatically changed from fix/init-skills-installer to mainApril 7, 2026 18:16
@wyattjoh
wyattjoh marked this pull request as ready for review April 7, 2026 18:25
@wyattjoh

wyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai

coderabbitaiBot commented Apr 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a runner abstraction and runtime runner detection to make skill installation runner-agnostic (bunx, npx, pnpm, yarn). Reworks buildSkillsArgs to return runner-agnostic arguments and updates installSkills to accept a packageManager, detect available runners, choose a preferred runner (or prompt interactively), and spawn the selected runner. Introduces runners module and tests, a generic select prompt helper, updates skills tests, and updates README instructions to describe runner-specific install commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately reflects the main change: adding package runner detection helpers (runners.ts module with detection and selection logic) to the lib directory.
Description check✅ PassedThe description clearly explains the changeset: adding runners.ts with Runner type and utilities, adding select to prompts.ts, and integrating these helpers into the init/skills flow with specific runner selection behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The select() prompt inside installSkills() can reject
when the user cancels; wrap the select<Runner>({...}) call in a try/catch and
call throwUserAbort() in the catch to ensure a clean exit on user cancellation.
Locate the runner = await select<Runner>(...) block and surround it with try {
... } catch { throwUserAbort(); } so prompt cancellations are handled
gracefully.
In `@packages/cli-core/src/lib/runners.ts`:
- Around line 38-42: detectAvailableRunners() currently treats presence of the
"yarn" binary (checked via Bun.which()) as sufficient to offer the RUNNERS entry
for yarn which hardcodes prefixArgs ["dlx"], but Yarn Classic (v1) lacks the dlx
subcommand; update detectAvailableRunners() to additionally probe the installed
yarn for dlx support by either checking yarn --version and ensuring it's Berry
(>=2) or by trying to execute a harmless yarn dlx --version probe, and only
include the RUNNERS entry with id "yarn" (from RUNNERS) when that probe
succeeds; keep using Bun.which() to find the binary but gate adding the yarn
runner on the version/subcommand check so Classic Yarn is not advertised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cfd7c5df-7c77-4d33-a9f4-2b6ec636eb24

📥 Commits

Reviewing files that changed from the base of the PR and between c5bc9ef and 4edf28e.

📒 Files selected for processing (4)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts

Comment threadpackages/cli-core/src/commands/init/skills.ts
Comment threadpackages/cli-core/src/lib/runners.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/commands/init/skills.ts (1)

117-124: ⚠️ Potential issue | 🟠 Major

Handle cancellation on the runner picker.

select() can reject on Ctrl+C, and this new path currently bubbles out of installSkills() as an uncaught prompt error instead of a clean command abort. Wrap the picker in try/catch and call throwUserAbort() from the catch. As per coding guidelines, "Call throwUserAbort() when the user cancels a prompt or confirmation for clean exit".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli-core/src/commands/init/skills.ts` around lines 117 - 124, The
runner picker can reject on Ctrl+C and currently bubbles an uncaught prompt
error from installSkills; wrap the select<Runner> call in a try/catch around the
code that assigns runner and, in the catch block, call throwUserAbort() to
perform a clean command abort (reference the select<Runner> invocation that
assigns runner in installSkills and the throwUserAbort() helper); ensure the
catch only handles prompt cancellation and rethrow other unexpected errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The runner picker can reject on Ctrl+C and currently
bubbles an uncaught prompt error from installSkills; wrap the select<Runner>
call in a try/catch around the code that assigns runner and, in the catch block,
call throwUserAbort() to perform a clean command abort (reference the
select<Runner> invocation that assigns runner in installSkills and the
throwUserAbort() helper); ensure the catch only handles prompt cancellation and
rethrow other unexpected errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f92a3a-5b42-42d7-9e1a-ff2d25f7fa92

📥 Commits

Reviewing files that changed from the base of the PR and between 4edf28e and 2e3b67f.

📒 Files selected for processing (7)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/skills.test.ts
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/prompts.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts
💤 Files with no reviewable changes (1)
  • packages/cli-core/src/commands/init/skills.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/lib/runners.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/prompts.ts

@wyattjoh
wyattjoh requested a review from RaillyApril 8, 2026 20:38
@wyattjoh
wyattjohforce-pushed the feat/lib-runners branch 2 times, most recently from 006f9cc to 515fd36CompareApril 9, 2026 20:23
const available = detectAvailableRunners();
if (available.length === 0) {
const suggested = runnerForPackageManager(packageManager);
console.log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the console.log commands to use the new log?
I think we will need to add a lint rule.

@Railly

Copy link
Copy Markdown
Contributor

@wyattjoh could you update the console.logs with the log utility Carp created? 👀

@wyattjoh
wyattjoh requested a review from jfosheeApril 9, 2026 22:59

@jfosheejfoshee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how this is implemented and tested. I was thinking it would be pretty painful to do a real integration test of this, so I think mocking which and spawnSync is a good strategy. That said, you point out they may not always be mockable.

So, something to consider as time marches on, this might be one of those times we should add a level of indirection. If there are a handful of system/shell functions that we use, we could wrap them up into an interface like:

interface ShellCommands {
which(string): string;
...
}

And that would have a trivial bun implementation. And would also be trivially mocked without fear the tests aren't running on some platforms.

expect(result.map((r) => r.id)).toEqual(["bunx", "npx", "pnpm"]);
});

test("includes yarn when `yarn dlx --help` exits 0 (Yarn Berry)", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about these yarn flavors!

Comment threadpackages/cli-core/src/lib/runners.ts Outdated
* Known runners in preference order. When no project package manager is
* provided, the first available runner from this list wins.
*/
export const RUNNERS: readonly Runner[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: might be more clear if this was named KNOWN_RUNNERS

Adds `lib/runners.ts` with a Runner type, RUNNERS constant (bunx, npx,
pnpm dlx, yarn dlx), `detectAvailableRunners()` (uses Bun.which), and
`preferredRunner()` (picks the runner matching the project's package
manager). Also adds a `select<T>` wrapper to lib/prompts.ts mirroring
the existing `confirm` wrapper for piped-stdin safety.
Uses the new helpers in init/skills.ts: `installSkills` now picks a
runner instead of hardcoding npx, prompts the user to choose when
multiple runners are available (with the project's pm runner labelled
"(detected)" as the default), and threads packageManager through from
init/index.ts.
Adds runnerForPackageManager() helper so the skills install fallbacks
suggest a runner that matches the project's package manager (e.g.
`bunx skills add` for a Bun project) instead of hardcoding `npx`.
Updates the init README to describe runner detection.
Yarn Classic (v1) lacks the `dlx` subcommand, so detecting `yarn` on PATH
via `Bun.which()` was not enough to safely advertise `yarn dlx`. Probe
`yarn dlx --help` and only include the runner when it exits 0 (Berry).
Switch all output in skills.ts and bootstrap.ts to use the central log
object (log.info, log.warn, log.success, log.blank) so output respects
log levels, throttling, and test capture.
Removes direct color function wrappers (cyan, yellow, dim) in favour of
log method semantics: log.warn handles yellow, log.success handles green,
and backtick spans in messages handle cyan highlighting.
Logging guidance now lives in .claude/rules/logging.md, scoped to
packages/cli-core/src/** so it auto-loads only when working on CLI source.
Strips the Logging, Error Handling, Testing, and Commands sections from
CLAUDE.md since each is already covered by an autoloaded rule file
(.claude/rules/{logging,errors,testing,commands}.md), and removes the
explicit .claude/rules/e2e.md reference for the same reason.
Adds the eslint no-console rule via oxlint, scoped to production code
under packages/cli-core/src so all output is forced through the log
helper. Test files and scripts/ are exempt since they legitimately use
console for spy/mock plumbing and release tooling output.
@wyattjoh
wyattjoh merged commit 6ce3cf3 into mainApr 11, 2026
6 checks passed
@wyattjoh
wyattjoh deleted the feat/lib-runners branch April 11, 2026 06:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@wyattjoh@Railly@jfoshee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(lib): add package runner detection helpers - #118

Merged
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners
Apr 11, 2026
Merged

feat(lib): add package runner detection helpers#118
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners

Conversation

@wyattjoh

@wyattjohwyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add lib/runners.ts with a Runner type, RUNNERS constant (bunx, npx, pnpm dlx, yarn dlx), detectAvailableRunners() (uses Bun.which), and preferredRunner() (picks the runner matching the project's package manager).
  • Add a select<T> wrapper to lib/prompts.ts mirroring the existing confirm wrapper for piped-stdin safety.
  • Use the new helpers in init/skills.ts: installSkills now picks a runner instead of hardcoding npx, prompts the user to choose when multiple runners are available (with the project's pm runner labelled (detected) as the default), and threads packageManager through from init/index.ts.

Originally stacked on #116, now rebased onto main after #116 merged.

Test plan

  • bun run test passes (59 passed)
  • bun test packages/cli-core/src/lib/runners.test.ts passes (16 tests covering RUNNERS shape, runnerCommand, preferredRunner, detectAvailableRunners)
  • Manual: on a bun-only machine (no npx on PATH), run clerk init and confirm installSkills falls back to bunx instead of failing

@wyattjoh
wyattjoh marked this pull request as draft April 7, 2026 17:12
@wyattjoh
wyattjohforce-pushed the fix/init-skills-installer branch 2 times, most recently from 6cf5261 to dd71548CompareApril 7, 2026 17:41
Base automatically changed from fix/init-skills-installer to mainApril 7, 2026 18:16
@wyattjoh
wyattjoh marked this pull request as ready for review April 7, 2026 18:25
@wyattjoh

wyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai

coderabbitaiBot commented Apr 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a runner abstraction and runtime runner detection to make skill installation runner-agnostic (bunx, npx, pnpm, yarn). Reworks buildSkillsArgs to return runner-agnostic arguments and updates installSkills to accept a packageManager, detect available runners, choose a preferred runner (or prompt interactively), and spawn the selected runner. Introduces runners module and tests, a generic select prompt helper, updates skills tests, and updates README instructions to describe runner-specific install commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately reflects the main change: adding package runner detection helpers (runners.ts module with detection and selection logic) to the lib directory.
Description check✅ PassedThe description clearly explains the changeset: adding runners.ts with Runner type and utilities, adding select to prompts.ts, and integrating these helpers into the init/skills flow with specific runner selection behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The select() prompt inside installSkills() can reject
when the user cancels; wrap the select<Runner>({...}) call in a try/catch and
call throwUserAbort() in the catch to ensure a clean exit on user cancellation.
Locate the runner = await select<Runner>(...) block and surround it with try {
... } catch { throwUserAbort(); } so prompt cancellations are handled
gracefully.
In `@packages/cli-core/src/lib/runners.ts`:
- Around line 38-42: detectAvailableRunners() currently treats presence of the
"yarn" binary (checked via Bun.which()) as sufficient to offer the RUNNERS entry
for yarn which hardcodes prefixArgs ["dlx"], but Yarn Classic (v1) lacks the dlx
subcommand; update detectAvailableRunners() to additionally probe the installed
yarn for dlx support by either checking yarn --version and ensuring it's Berry
(>=2) or by trying to execute a harmless yarn dlx --version probe, and only
include the RUNNERS entry with id "yarn" (from RUNNERS) when that probe
succeeds; keep using Bun.which() to find the binary but gate adding the yarn
runner on the version/subcommand check so Classic Yarn is not advertised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cfd7c5df-7c77-4d33-a9f4-2b6ec636eb24

📥 Commits

Reviewing files that changed from the base of the PR and between c5bc9ef and 4edf28e.

📒 Files selected for processing (4)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts

Comment threadpackages/cli-core/src/commands/init/skills.ts
Comment threadpackages/cli-core/src/lib/runners.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/commands/init/skills.ts (1)

117-124: ⚠️ Potential issue | 🟠 Major

Handle cancellation on the runner picker.

select() can reject on Ctrl+C, and this new path currently bubbles out of installSkills() as an uncaught prompt error instead of a clean command abort. Wrap the picker in try/catch and call throwUserAbort() from the catch. As per coding guidelines, "Call throwUserAbort() when the user cancels a prompt or confirmation for clean exit".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli-core/src/commands/init/skills.ts` around lines 117 - 124, The
runner picker can reject on Ctrl+C and currently bubbles an uncaught prompt
error from installSkills; wrap the select<Runner> call in a try/catch around the
code that assigns runner and, in the catch block, call throwUserAbort() to
perform a clean command abort (reference the select<Runner> invocation that
assigns runner in installSkills and the throwUserAbort() helper); ensure the
catch only handles prompt cancellation and rethrow other unexpected errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The runner picker can reject on Ctrl+C and currently
bubbles an uncaught prompt error from installSkills; wrap the select<Runner>
call in a try/catch around the code that assigns runner and, in the catch block,
call throwUserAbort() to perform a clean command abort (reference the
select<Runner> invocation that assigns runner in installSkills and the
throwUserAbort() helper); ensure the catch only handles prompt cancellation and
rethrow other unexpected errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f92a3a-5b42-42d7-9e1a-ff2d25f7fa92

📥 Commits

Reviewing files that changed from the base of the PR and between 4edf28e and 2e3b67f.

📒 Files selected for processing (7)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/skills.test.ts
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/prompts.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts
💤 Files with no reviewable changes (1)
  • packages/cli-core/src/commands/init/skills.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/lib/runners.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/prompts.ts

@wyattjoh
wyattjoh requested a review from RaillyApril 8, 2026 20:38
@wyattjoh
wyattjohforce-pushed the feat/lib-runners branch 2 times, most recently from 006f9cc to 515fd36CompareApril 9, 2026 20:23
const available = detectAvailableRunners();
if (available.length === 0) {
const suggested = runnerForPackageManager(packageManager);
console.log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the console.log commands to use the new log?
I think we will need to add a lint rule.

@Railly

Copy link
Copy Markdown
Contributor

@wyattjoh could you update the console.logs with the log utility Carp created? 👀

@wyattjoh
wyattjoh requested a review from jfosheeApril 9, 2026 22:59

@jfosheejfoshee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how this is implemented and tested. I was thinking it would be pretty painful to do a real integration test of this, so I think mocking which and spawnSync is a good strategy. That said, you point out they may not always be mockable.

So, something to consider as time marches on, this might be one of those times we should add a level of indirection. If there are a handful of system/shell functions that we use, we could wrap them up into an interface like:

interface ShellCommands {
which(string): string;
...
}

And that would have a trivial bun implementation. And would also be trivially mocked without fear the tests aren't running on some platforms.

expect(result.map((r) => r.id)).toEqual(["bunx", "npx", "pnpm"]);
});

test("includes yarn when `yarn dlx --help` exits 0 (Yarn Berry)", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about these yarn flavors!

Comment threadpackages/cli-core/src/lib/runners.ts Outdated
* Known runners in preference order. When no project package manager is
* provided, the first available runner from this list wins.
*/
export const RUNNERS: readonly Runner[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: might be more clear if this was named KNOWN_RUNNERS

Adds `lib/runners.ts` with a Runner type, RUNNERS constant (bunx, npx,
pnpm dlx, yarn dlx), `detectAvailableRunners()` (uses Bun.which), and
`preferredRunner()` (picks the runner matching the project's package
manager). Also adds a `select<T>` wrapper to lib/prompts.ts mirroring
the existing `confirm` wrapper for piped-stdin safety.
Uses the new helpers in init/skills.ts: `installSkills` now picks a
runner instead of hardcoding npx, prompts the user to choose when
multiple runners are available (with the project's pm runner labelled
"(detected)" as the default), and threads packageManager through from
init/index.ts.
Adds runnerForPackageManager() helper so the skills install fallbacks
suggest a runner that matches the project's package manager (e.g.
`bunx skills add` for a Bun project) instead of hardcoding `npx`.
Updates the init README to describe runner detection.
Yarn Classic (v1) lacks the `dlx` subcommand, so detecting `yarn` on PATH
via `Bun.which()` was not enough to safely advertise `yarn dlx`. Probe
`yarn dlx --help` and only include the runner when it exits 0 (Berry).
Switch all output in skills.ts and bootstrap.ts to use the central log
object (log.info, log.warn, log.success, log.blank) so output respects
log levels, throttling, and test capture.
Removes direct color function wrappers (cyan, yellow, dim) in favour of
log method semantics: log.warn handles yellow, log.success handles green,
and backtick spans in messages handle cyan highlighting.
Logging guidance now lives in .claude/rules/logging.md, scoped to
packages/cli-core/src/** so it auto-loads only when working on CLI source.
Strips the Logging, Error Handling, Testing, and Commands sections from
CLAUDE.md since each is already covered by an autoloaded rule file
(.claude/rules/{logging,errors,testing,commands}.md), and removes the
explicit .claude/rules/e2e.md reference for the same reason.
Adds the eslint no-console rule via oxlint, scoped to production code
under packages/cli-core/src so all output is forced through the log
helper. Test files and scripts/ are exempt since they legitimately use
console for spy/mock plumbing and release tooling output.
@wyattjoh
wyattjoh merged commit 6ce3cf3 into mainApr 11, 2026
6 checks passed
@wyattjoh
wyattjoh deleted the feat/lib-runners branch April 11, 2026 06:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@wyattjoh@Railly@jfoshee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(lib): add package runner detection helpers - #118

Merged
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners
Apr 11, 2026
Merged

feat(lib): add package runner detection helpers#118
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners

Conversation

@wyattjoh

@wyattjohwyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add lib/runners.ts with a Runner type, RUNNERS constant (bunx, npx, pnpm dlx, yarn dlx), detectAvailableRunners() (uses Bun.which), and preferredRunner() (picks the runner matching the project's package manager).
  • Add a select<T> wrapper to lib/prompts.ts mirroring the existing confirm wrapper for piped-stdin safety.
  • Use the new helpers in init/skills.ts: installSkills now picks a runner instead of hardcoding npx, prompts the user to choose when multiple runners are available (with the project's pm runner labelled (detected) as the default), and threads packageManager through from init/index.ts.

Originally stacked on #116, now rebased onto main after #116 merged.

Test plan

  • bun run test passes (59 passed)
  • bun test packages/cli-core/src/lib/runners.test.ts passes (16 tests covering RUNNERS shape, runnerCommand, preferredRunner, detectAvailableRunners)
  • Manual: on a bun-only machine (no npx on PATH), run clerk init and confirm installSkills falls back to bunx instead of failing

@wyattjoh
wyattjoh marked this pull request as draft April 7, 2026 17:12
@wyattjoh
wyattjohforce-pushed the fix/init-skills-installer branch 2 times, most recently from 6cf5261 to dd71548CompareApril 7, 2026 17:41
Base automatically changed from fix/init-skills-installer to mainApril 7, 2026 18:16
@wyattjoh
wyattjoh marked this pull request as ready for review April 7, 2026 18:25
@wyattjoh

wyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai

coderabbitaiBot commented Apr 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a runner abstraction and runtime runner detection to make skill installation runner-agnostic (bunx, npx, pnpm, yarn). Reworks buildSkillsArgs to return runner-agnostic arguments and updates installSkills to accept a packageManager, detect available runners, choose a preferred runner (or prompt interactively), and spawn the selected runner. Introduces runners module and tests, a generic select prompt helper, updates skills tests, and updates README instructions to describe runner-specific install commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately reflects the main change: adding package runner detection helpers (runners.ts module with detection and selection logic) to the lib directory.
Description check✅ PassedThe description clearly explains the changeset: adding runners.ts with Runner type and utilities, adding select to prompts.ts, and integrating these helpers into the init/skills flow with specific runner selection behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The select() prompt inside installSkills() can reject
when the user cancels; wrap the select<Runner>({...}) call in a try/catch and
call throwUserAbort() in the catch to ensure a clean exit on user cancellation.
Locate the runner = await select<Runner>(...) block and surround it with try {
... } catch { throwUserAbort(); } so prompt cancellations are handled
gracefully.
In `@packages/cli-core/src/lib/runners.ts`:
- Around line 38-42: detectAvailableRunners() currently treats presence of the
"yarn" binary (checked via Bun.which()) as sufficient to offer the RUNNERS entry
for yarn which hardcodes prefixArgs ["dlx"], but Yarn Classic (v1) lacks the dlx
subcommand; update detectAvailableRunners() to additionally probe the installed
yarn for dlx support by either checking yarn --version and ensuring it's Berry
(>=2) or by trying to execute a harmless yarn dlx --version probe, and only
include the RUNNERS entry with id "yarn" (from RUNNERS) when that probe
succeeds; keep using Bun.which() to find the binary but gate adding the yarn
runner on the version/subcommand check so Classic Yarn is not advertised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cfd7c5df-7c77-4d33-a9f4-2b6ec636eb24

📥 Commits

Reviewing files that changed from the base of the PR and between c5bc9ef and 4edf28e.

📒 Files selected for processing (4)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts

Comment threadpackages/cli-core/src/commands/init/skills.ts
Comment threadpackages/cli-core/src/lib/runners.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/commands/init/skills.ts (1)

117-124: ⚠️ Potential issue | 🟠 Major

Handle cancellation on the runner picker.

select() can reject on Ctrl+C, and this new path currently bubbles out of installSkills() as an uncaught prompt error instead of a clean command abort. Wrap the picker in try/catch and call throwUserAbort() from the catch. As per coding guidelines, "Call throwUserAbort() when the user cancels a prompt or confirmation for clean exit".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli-core/src/commands/init/skills.ts` around lines 117 - 124, The
runner picker can reject on Ctrl+C and currently bubbles an uncaught prompt
error from installSkills; wrap the select<Runner> call in a try/catch around the
code that assigns runner and, in the catch block, call throwUserAbort() to
perform a clean command abort (reference the select<Runner> invocation that
assigns runner in installSkills and the throwUserAbort() helper); ensure the
catch only handles prompt cancellation and rethrow other unexpected errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The runner picker can reject on Ctrl+C and currently
bubbles an uncaught prompt error from installSkills; wrap the select<Runner>
call in a try/catch around the code that assigns runner and, in the catch block,
call throwUserAbort() to perform a clean command abort (reference the
select<Runner> invocation that assigns runner in installSkills and the
throwUserAbort() helper); ensure the catch only handles prompt cancellation and
rethrow other unexpected errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f92a3a-5b42-42d7-9e1a-ff2d25f7fa92

📥 Commits

Reviewing files that changed from the base of the PR and between 4edf28e and 2e3b67f.

📒 Files selected for processing (7)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/skills.test.ts
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/prompts.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts
💤 Files with no reviewable changes (1)
  • packages/cli-core/src/commands/init/skills.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/lib/runners.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/prompts.ts

@wyattjoh
wyattjoh requested a review from RaillyApril 8, 2026 20:38
@wyattjoh
wyattjohforce-pushed the feat/lib-runners branch 2 times, most recently from 006f9cc to 515fd36CompareApril 9, 2026 20:23
const available = detectAvailableRunners();
if (available.length === 0) {
const suggested = runnerForPackageManager(packageManager);
console.log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the console.log commands to use the new log?
I think we will need to add a lint rule.

@Railly

Copy link
Copy Markdown
Contributor

@wyattjoh could you update the console.logs with the log utility Carp created? 👀

@wyattjoh
wyattjoh requested a review from jfosheeApril 9, 2026 22:59

@jfosheejfoshee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how this is implemented and tested. I was thinking it would be pretty painful to do a real integration test of this, so I think mocking which and spawnSync is a good strategy. That said, you point out they may not always be mockable.

So, something to consider as time marches on, this might be one of those times we should add a level of indirection. If there are a handful of system/shell functions that we use, we could wrap them up into an interface like:

interface ShellCommands {
which(string): string;
...
}

And that would have a trivial bun implementation. And would also be trivially mocked without fear the tests aren't running on some platforms.

expect(result.map((r) => r.id)).toEqual(["bunx", "npx", "pnpm"]);
});

test("includes yarn when `yarn dlx --help` exits 0 (Yarn Berry)", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about these yarn flavors!

Comment threadpackages/cli-core/src/lib/runners.ts Outdated
* Known runners in preference order. When no project package manager is
* provided, the first available runner from this list wins.
*/
export const RUNNERS: readonly Runner[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: might be more clear if this was named KNOWN_RUNNERS

Adds `lib/runners.ts` with a Runner type, RUNNERS constant (bunx, npx,
pnpm dlx, yarn dlx), `detectAvailableRunners()` (uses Bun.which), and
`preferredRunner()` (picks the runner matching the project's package
manager). Also adds a `select<T>` wrapper to lib/prompts.ts mirroring
the existing `confirm` wrapper for piped-stdin safety.
Uses the new helpers in init/skills.ts: `installSkills` now picks a
runner instead of hardcoding npx, prompts the user to choose when
multiple runners are available (with the project's pm runner labelled
"(detected)" as the default), and threads packageManager through from
init/index.ts.
Adds runnerForPackageManager() helper so the skills install fallbacks
suggest a runner that matches the project's package manager (e.g.
`bunx skills add` for a Bun project) instead of hardcoding `npx`.
Updates the init README to describe runner detection.
Yarn Classic (v1) lacks the `dlx` subcommand, so detecting `yarn` on PATH
via `Bun.which()` was not enough to safely advertise `yarn dlx`. Probe
`yarn dlx --help` and only include the runner when it exits 0 (Berry).
Switch all output in skills.ts and bootstrap.ts to use the central log
object (log.info, log.warn, log.success, log.blank) so output respects
log levels, throttling, and test capture.
Removes direct color function wrappers (cyan, yellow, dim) in favour of
log method semantics: log.warn handles yellow, log.success handles green,
and backtick spans in messages handle cyan highlighting.
Logging guidance now lives in .claude/rules/logging.md, scoped to
packages/cli-core/src/** so it auto-loads only when working on CLI source.
Strips the Logging, Error Handling, Testing, and Commands sections from
CLAUDE.md since each is already covered by an autoloaded rule file
(.claude/rules/{logging,errors,testing,commands}.md), and removes the
explicit .claude/rules/e2e.md reference for the same reason.
Adds the eslint no-console rule via oxlint, scoped to production code
under packages/cli-core/src so all output is forced through the log
helper. Test files and scripts/ are exempt since they legitimately use
console for spy/mock plumbing and release tooling output.
@wyattjoh
wyattjoh merged commit 6ce3cf3 into mainApr 11, 2026
6 checks passed
@wyattjoh
wyattjoh deleted the feat/lib-runners branch April 11, 2026 06:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@wyattjoh@Railly@jfoshee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(lib): add package runner detection helpers - #118

Merged
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners
Apr 11, 2026
Merged

feat(lib): add package runner detection helpers#118
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners

Conversation

@wyattjoh

@wyattjohwyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add lib/runners.ts with a Runner type, RUNNERS constant (bunx, npx, pnpm dlx, yarn dlx), detectAvailableRunners() (uses Bun.which), and preferredRunner() (picks the runner matching the project's package manager).
  • Add a select<T> wrapper to lib/prompts.ts mirroring the existing confirm wrapper for piped-stdin safety.
  • Use the new helpers in init/skills.ts: installSkills now picks a runner instead of hardcoding npx, prompts the user to choose when multiple runners are available (with the project's pm runner labelled (detected) as the default), and threads packageManager through from init/index.ts.

Originally stacked on #116, now rebased onto main after #116 merged.

Test plan

  • bun run test passes (59 passed)
  • bun test packages/cli-core/src/lib/runners.test.ts passes (16 tests covering RUNNERS shape, runnerCommand, preferredRunner, detectAvailableRunners)
  • Manual: on a bun-only machine (no npx on PATH), run clerk init and confirm installSkills falls back to bunx instead of failing

@wyattjoh
wyattjoh marked this pull request as draft April 7, 2026 17:12
@wyattjoh
wyattjohforce-pushed the fix/init-skills-installer branch 2 times, most recently from 6cf5261 to dd71548CompareApril 7, 2026 17:41
Base automatically changed from fix/init-skills-installer to mainApril 7, 2026 18:16
@wyattjoh
wyattjoh marked this pull request as ready for review April 7, 2026 18:25
@wyattjoh

wyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai

coderabbitaiBot commented Apr 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a runner abstraction and runtime runner detection to make skill installation runner-agnostic (bunx, npx, pnpm, yarn). Reworks buildSkillsArgs to return runner-agnostic arguments and updates installSkills to accept a packageManager, detect available runners, choose a preferred runner (or prompt interactively), and spawn the selected runner. Introduces runners module and tests, a generic select prompt helper, updates skills tests, and updates README instructions to describe runner-specific install commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately reflects the main change: adding package runner detection helpers (runners.ts module with detection and selection logic) to the lib directory.
Description check✅ PassedThe description clearly explains the changeset: adding runners.ts with Runner type and utilities, adding select to prompts.ts, and integrating these helpers into the init/skills flow with specific runner selection behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The select() prompt inside installSkills() can reject
when the user cancels; wrap the select<Runner>({...}) call in a try/catch and
call throwUserAbort() in the catch to ensure a clean exit on user cancellation.
Locate the runner = await select<Runner>(...) block and surround it with try {
... } catch { throwUserAbort(); } so prompt cancellations are handled
gracefully.
In `@packages/cli-core/src/lib/runners.ts`:
- Around line 38-42: detectAvailableRunners() currently treats presence of the
"yarn" binary (checked via Bun.which()) as sufficient to offer the RUNNERS entry
for yarn which hardcodes prefixArgs ["dlx"], but Yarn Classic (v1) lacks the dlx
subcommand; update detectAvailableRunners() to additionally probe the installed
yarn for dlx support by either checking yarn --version and ensuring it's Berry
(>=2) or by trying to execute a harmless yarn dlx --version probe, and only
include the RUNNERS entry with id "yarn" (from RUNNERS) when that probe
succeeds; keep using Bun.which() to find the binary but gate adding the yarn
runner on the version/subcommand check so Classic Yarn is not advertised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cfd7c5df-7c77-4d33-a9f4-2b6ec636eb24

📥 Commits

Reviewing files that changed from the base of the PR and between c5bc9ef and 4edf28e.

📒 Files selected for processing (4)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts

Comment threadpackages/cli-core/src/commands/init/skills.ts
Comment threadpackages/cli-core/src/lib/runners.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/commands/init/skills.ts (1)

117-124: ⚠️ Potential issue | 🟠 Major

Handle cancellation on the runner picker.

select() can reject on Ctrl+C, and this new path currently bubbles out of installSkills() as an uncaught prompt error instead of a clean command abort. Wrap the picker in try/catch and call throwUserAbort() from the catch. As per coding guidelines, "Call throwUserAbort() when the user cancels a prompt or confirmation for clean exit".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli-core/src/commands/init/skills.ts` around lines 117 - 124, The
runner picker can reject on Ctrl+C and currently bubbles an uncaught prompt
error from installSkills; wrap the select<Runner> call in a try/catch around the
code that assigns runner and, in the catch block, call throwUserAbort() to
perform a clean command abort (reference the select<Runner> invocation that
assigns runner in installSkills and the throwUserAbort() helper); ensure the
catch only handles prompt cancellation and rethrow other unexpected errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The runner picker can reject on Ctrl+C and currently
bubbles an uncaught prompt error from installSkills; wrap the select<Runner>
call in a try/catch around the code that assigns runner and, in the catch block,
call throwUserAbort() to perform a clean command abort (reference the
select<Runner> invocation that assigns runner in installSkills and the
throwUserAbort() helper); ensure the catch only handles prompt cancellation and
rethrow other unexpected errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f92a3a-5b42-42d7-9e1a-ff2d25f7fa92

📥 Commits

Reviewing files that changed from the base of the PR and between 4edf28e and 2e3b67f.

📒 Files selected for processing (7)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/skills.test.ts
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/prompts.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts
💤 Files with no reviewable changes (1)
  • packages/cli-core/src/commands/init/skills.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/lib/runners.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/prompts.ts

@wyattjoh
wyattjoh requested a review from RaillyApril 8, 2026 20:38
@wyattjoh
wyattjohforce-pushed the feat/lib-runners branch 2 times, most recently from 006f9cc to 515fd36CompareApril 9, 2026 20:23
const available = detectAvailableRunners();
if (available.length === 0) {
const suggested = runnerForPackageManager(packageManager);
console.log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the console.log commands to use the new log?
I think we will need to add a lint rule.

@Railly

Copy link
Copy Markdown
Contributor

@wyattjoh could you update the console.logs with the log utility Carp created? 👀

@wyattjoh
wyattjoh requested a review from jfosheeApril 9, 2026 22:59

@jfosheejfoshee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how this is implemented and tested. I was thinking it would be pretty painful to do a real integration test of this, so I think mocking which and spawnSync is a good strategy. That said, you point out they may not always be mockable.

So, something to consider as time marches on, this might be one of those times we should add a level of indirection. If there are a handful of system/shell functions that we use, we could wrap them up into an interface like:

interface ShellCommands {
which(string): string;
...
}

And that would have a trivial bun implementation. And would also be trivially mocked without fear the tests aren't running on some platforms.

expect(result.map((r) => r.id)).toEqual(["bunx", "npx", "pnpm"]);
});

test("includes yarn when `yarn dlx --help` exits 0 (Yarn Berry)", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about these yarn flavors!

Comment threadpackages/cli-core/src/lib/runners.ts Outdated
* Known runners in preference order. When no project package manager is
* provided, the first available runner from this list wins.
*/
export const RUNNERS: readonly Runner[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: might be more clear if this was named KNOWN_RUNNERS

Adds `lib/runners.ts` with a Runner type, RUNNERS constant (bunx, npx,
pnpm dlx, yarn dlx), `detectAvailableRunners()` (uses Bun.which), and
`preferredRunner()` (picks the runner matching the project's package
manager). Also adds a `select<T>` wrapper to lib/prompts.ts mirroring
the existing `confirm` wrapper for piped-stdin safety.
Uses the new helpers in init/skills.ts: `installSkills` now picks a
runner instead of hardcoding npx, prompts the user to choose when
multiple runners are available (with the project's pm runner labelled
"(detected)" as the default), and threads packageManager through from
init/index.ts.
Adds runnerForPackageManager() helper so the skills install fallbacks
suggest a runner that matches the project's package manager (e.g.
`bunx skills add` for a Bun project) instead of hardcoding `npx`.
Updates the init README to describe runner detection.
Yarn Classic (v1) lacks the `dlx` subcommand, so detecting `yarn` on PATH
via `Bun.which()` was not enough to safely advertise `yarn dlx`. Probe
`yarn dlx --help` and only include the runner when it exits 0 (Berry).
Switch all output in skills.ts and bootstrap.ts to use the central log
object (log.info, log.warn, log.success, log.blank) so output respects
log levels, throttling, and test capture.
Removes direct color function wrappers (cyan, yellow, dim) in favour of
log method semantics: log.warn handles yellow, log.success handles green,
and backtick spans in messages handle cyan highlighting.
Logging guidance now lives in .claude/rules/logging.md, scoped to
packages/cli-core/src/** so it auto-loads only when working on CLI source.
Strips the Logging, Error Handling, Testing, and Commands sections from
CLAUDE.md since each is already covered by an autoloaded rule file
(.claude/rules/{logging,errors,testing,commands}.md), and removes the
explicit .claude/rules/e2e.md reference for the same reason.
Adds the eslint no-console rule via oxlint, scoped to production code
under packages/cli-core/src so all output is forced through the log
helper. Test files and scripts/ are exempt since they legitimately use
console for spy/mock plumbing and release tooling output.
@wyattjoh
wyattjoh merged commit 6ce3cf3 into mainApr 11, 2026
6 checks passed
@wyattjoh
wyattjoh deleted the feat/lib-runners branch April 11, 2026 06:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@wyattjoh@Railly@jfoshee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(lib): add package runner detection helpers - #118

Merged
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners
Apr 11, 2026
Merged

feat(lib): add package runner detection helpers#118
wyattjoh merged 7 commits into
mainfrom
feat/lib-runners

Conversation

@wyattjoh

@wyattjohwyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add lib/runners.ts with a Runner type, RUNNERS constant (bunx, npx, pnpm dlx, yarn dlx), detectAvailableRunners() (uses Bun.which), and preferredRunner() (picks the runner matching the project's package manager).
  • Add a select<T> wrapper to lib/prompts.ts mirroring the existing confirm wrapper for piped-stdin safety.
  • Use the new helpers in init/skills.ts: installSkills now picks a runner instead of hardcoding npx, prompts the user to choose when multiple runners are available (with the project's pm runner labelled (detected) as the default), and threads packageManager through from init/index.ts.

Originally stacked on #116, now rebased onto main after #116 merged.

Test plan

  • bun run test passes (59 passed)
  • bun test packages/cli-core/src/lib/runners.test.ts passes (16 tests covering RUNNERS shape, runnerCommand, preferredRunner, detectAvailableRunners)
  • Manual: on a bun-only machine (no npx on PATH), run clerk init and confirm installSkills falls back to bunx instead of failing

@wyattjoh
wyattjoh marked this pull request as draft April 7, 2026 17:12
@wyattjoh
wyattjohforce-pushed the fix/init-skills-installer branch 2 times, most recently from 6cf5261 to dd71548CompareApril 7, 2026 17:41
Base automatically changed from fix/init-skills-installer to mainApril 7, 2026 18:16
@wyattjoh
wyattjoh marked this pull request as ready for review April 7, 2026 18:25
@wyattjoh

wyattjoh commented Apr 7, 2026

Copy link
Copy Markdown
ContributorAuthor

@coderabbitai

coderabbitaiBot commented Apr 7, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a runner abstraction and runtime runner detection to make skill installation runner-agnostic (bunx, npx, pnpm, yarn). Reworks buildSkillsArgs to return runner-agnostic arguments and updates installSkills to accept a packageManager, detect available runners, choose a preferred runner (or prompt interactively), and spawn the selected runner. Introduces runners module and tests, a generic select prompt helper, updates skills tests, and updates README instructions to describe runner-specific install commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 60.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title accurately reflects the main change: adding package runner detection helpers (runners.ts module with detection and selection logic) to the lib directory.
Description check✅ PassedThe description clearly explains the changeset: adding runners.ts with Runner type and utilities, adding select to prompts.ts, and integrating these helpers into the init/skills flow with specific runner selection behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The select() prompt inside installSkills() can reject
when the user cancels; wrap the select<Runner>({...}) call in a try/catch and
call throwUserAbort() in the catch to ensure a clean exit on user cancellation.
Locate the runner = await select<Runner>(...) block and surround it with try {
... } catch { throwUserAbort(); } so prompt cancellations are handled
gracefully.
In `@packages/cli-core/src/lib/runners.ts`:
- Around line 38-42: detectAvailableRunners() currently treats presence of the
"yarn" binary (checked via Bun.which()) as sufficient to offer the RUNNERS entry
for yarn which hardcodes prefixArgs ["dlx"], but Yarn Classic (v1) lacks the dlx
subcommand; update detectAvailableRunners() to additionally probe the installed
yarn for dlx support by either checking yarn --version and ensuring it's Berry
(>=2) or by trying to execute a harmless yarn dlx --version probe, and only
include the RUNNERS entry with id "yarn" (from RUNNERS) when that probe
succeeds; keep using Bun.which() to find the binary but gate adding the yarn
runner on the version/subcommand check so Classic Yarn is not advertised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cfd7c5df-7c77-4d33-a9f4-2b6ec636eb24

📥 Commits

Reviewing files that changed from the base of the PR and between c5bc9ef and 4edf28e.

📒 Files selected for processing (4)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts

Comment threadpackages/cli-core/src/commands/init/skills.ts
Comment threadpackages/cli-core/src/lib/runners.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/commands/init/skills.ts (1)

117-124: ⚠️ Potential issue | 🟠 Major

Handle cancellation on the runner picker.

select() can reject on Ctrl+C, and this new path currently bubbles out of installSkills() as an uncaught prompt error instead of a clean command abort. Wrap the picker in try/catch and call throwUserAbort() from the catch. As per coding guidelines, "Call throwUserAbort() when the user cancels a prompt or confirmation for clean exit".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/cli-core/src/commands/init/skills.ts` around lines 117 - 124, The
runner picker can reject on Ctrl+C and currently bubbles an uncaught prompt
error from installSkills; wrap the select<Runner> call in a try/catch around the
code that assigns runner and, in the catch block, call throwUserAbort() to
perform a clean command abort (reference the select<Runner> invocation that
assigns runner in installSkills and the throwUserAbort() helper); ensure the
catch only handles prompt cancellation and rethrow other unexpected errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/cli-core/src/commands/init/skills.ts`:
- Around line 117-124: The runner picker can reject on Ctrl+C and currently
bubbles an uncaught prompt error from installSkills; wrap the select<Runner>
call in a try/catch around the code that assigns runner and, in the catch block,
call throwUserAbort() to perform a clean command abort (reference the
select<Runner> invocation that assigns runner in installSkills and the
throwUserAbort() helper); ensure the catch only handles prompt cancellation and
rethrow other unexpected errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f92a3a-5b42-42d7-9e1a-ff2d25f7fa92

📥 Commits

Reviewing files that changed from the base of the PR and between 4edf28e and 2e3b67f.

📒 Files selected for processing (7)
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/commands/init/skills.test.ts
  • packages/cli-core/src/commands/init/skills.ts
  • packages/cli-core/src/lib/prompts.ts
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/lib/runners.ts
💤 Files with no reviewable changes (1)
  • packages/cli-core/src/commands/init/skills.test.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/cli-core/src/lib/runners.test.ts
  • packages/cli-core/src/commands/init/README.md
  • packages/cli-core/src/lib/runners.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli-core/src/commands/init/index.ts
  • packages/cli-core/src/lib/prompts.ts

@wyattjoh
wyattjoh requested a review from RaillyApril 8, 2026 20:38
@wyattjoh
wyattjohforce-pushed the feat/lib-runners branch 2 times, most recently from 006f9cc to 515fd36CompareApril 9, 2026 20:23
const available = detectAvailableRunners();
if (available.length === 0) {
const suggested = runnerForPackageManager(packageManager);
console.log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the console.log commands to use the new log?
I think we will need to add a lint rule.

@Railly

Copy link
Copy Markdown
Contributor

@wyattjoh could you update the console.logs with the log utility Carp created? 👀

@wyattjoh
wyattjoh requested a review from jfosheeApril 9, 2026 22:59

@jfosheejfoshee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how this is implemented and tested. I was thinking it would be pretty painful to do a real integration test of this, so I think mocking which and spawnSync is a good strategy. That said, you point out they may not always be mockable.

So, something to consider as time marches on, this might be one of those times we should add a level of indirection. If there are a handful of system/shell functions that we use, we could wrap them up into an interface like:

interface ShellCommands {
which(string): string;
...
}

And that would have a trivial bun implementation. And would also be trivially mocked without fear the tests aren't running on some platforms.

expect(result.map((r) => r.id)).toEqual(["bunx", "npx", "pnpm"]);
});

test("includes yarn when `yarn dlx --help` exits 0 (Yarn Berry)", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about these yarn flavors!

Comment threadpackages/cli-core/src/lib/runners.ts Outdated
* Known runners in preference order. When no project package manager is
* provided, the first available runner from this list wins.
*/
export const RUNNERS: readonly Runner[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: might be more clear if this was named KNOWN_RUNNERS

Adds `lib/runners.ts` with a Runner type, RUNNERS constant (bunx, npx,
pnpm dlx, yarn dlx), `detectAvailableRunners()` (uses Bun.which), and
`preferredRunner()` (picks the runner matching the project's package
manager). Also adds a `select<T>` wrapper to lib/prompts.ts mirroring
the existing `confirm` wrapper for piped-stdin safety.
Uses the new helpers in init/skills.ts: `installSkills` now picks a
runner instead of hardcoding npx, prompts the user to choose when
multiple runners are available (with the project's pm runner labelled
"(detected)" as the default), and threads packageManager through from
init/index.ts.
Adds runnerForPackageManager() helper so the skills install fallbacks
suggest a runner that matches the project's package manager (e.g.
`bunx skills add` for a Bun project) instead of hardcoding `npx`.
Updates the init README to describe runner detection.
Yarn Classic (v1) lacks the `dlx` subcommand, so detecting `yarn` on PATH
via `Bun.which()` was not enough to safely advertise `yarn dlx`. Probe
`yarn dlx --help` and only include the runner when it exits 0 (Berry).
Switch all output in skills.ts and bootstrap.ts to use the central log
object (log.info, log.warn, log.success, log.blank) so output respects
log levels, throttling, and test capture.
Removes direct color function wrappers (cyan, yellow, dim) in favour of
log method semantics: log.warn handles yellow, log.success handles green,
and backtick spans in messages handle cyan highlighting.
Logging guidance now lives in .claude/rules/logging.md, scoped to
packages/cli-core/src/** so it auto-loads only when working on CLI source.
Strips the Logging, Error Handling, Testing, and Commands sections from
CLAUDE.md since each is already covered by an autoloaded rule file
(.claude/rules/{logging,errors,testing,commands}.md), and removes the
explicit .claude/rules/e2e.md reference for the same reason.
Adds the eslint no-console rule via oxlint, scoped to production code
under packages/cli-core/src so all output is forced through the log
helper. Test files and scripts/ are exempt since they legitimately use
console for spy/mock plumbing and release tooling output.
@wyattjoh
wyattjoh merged commit 6ce3cf3 into mainApr 11, 2026
6 checks passed
@wyattjoh
wyattjoh deleted the feat/lib-runners branch April 11, 2026 06:50
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@wyattjoh@Railly@jfoshee