Skip to content

CLI: Bundle the ai command in core so it never downloads @storybook/cli#35147

Merged
kasperpeulen merged 10 commits into
nextfrom
kasper/ai-cli-bundled
Jun 12, 2026
Merged

CLI: Bundle the ai command in core so it never downloads @storybook/cli#35147
kasperpeulen merged 10 commits into
nextfrom
kasper/ai-cli-bundled

Conversation

@kasperpeulen

@kasperpeulen kasperpeulen commented Jun 11, 2026

Copy link
Copy Markdown
Member

Closes #35146

What I did

Yann found that npx storybook ai was not using the local binary: the dispatcher only routed dev/build/index to the bundled storybook/dist/bin/core.js, so ai was proxied to @storybook/cli — falling back to an npx --yes @storybook/cli@<version> download whenever the package wasn't locally installed at the pinned version. Since agent skills invoke npx storybook ai <tool> repeatedly, that download sat on a hot path (Slack).

  • Moved the entire ai command — the STORYBOOK_FEATURE_AI_CLI-gated MCP passthrough and the unflagged ai setup (setup-prompts, utils, types, tests) — from code/lib/cli-storybook/src/ai/ to code/core/src/cli/ai/, registered in code/core/src/bin/core.ts next to dev/build.
  • Added ai to the dispatcher's bundled-commands list.
  • Deleted the ai command from @storybook/cli (bin/run.ts). No shim: the dispatcher pins CLI versions, so older storybook versions keep their old paired CLI.
  • getStorybookData is now the one canonical project-metadata collector, living in core and exported via storybook/internal/cli; cli-storybook's automigrate/doctor/add consume it through a re-export instead of keeping their near-duplicate. getStoriesPathsFromConfig similarly moved to a single copy in storybook/internal/core-server.
  • detectLanguage/detectIncompatiblePackageVersions extracted from create-storybook's ProjectTypeService into core (storybook/internal/cli), with ProjectTypeService delegating — so core never imports source from a package that depends on core.
  • Feature-flag semantics unchanged; the telemetry from CLI: Add telemetry for the storybook ai <command> passthrough #35138 is untouched.
  • Eval scripts/README prompt-variant paths updated to the new location.

Known trade-off (decided in #35146): npx storybook --help (which routes to @storybook/cli) no longer lists ai; npx storybook ai --help is the entry point.

Two independent reviews (the thermo-nuclear-code-quality-review agent, and Codex xhigh running the same skill from the cursor-team-kit plugin) were run; all findings fixed or explicitly resolved — see the review-resolution comment.

Note: this PR is stacked on #35138 (kasper/ai-cli-telemetry); retarget to next once that merges.

How to test

Manual QA performed in a linked react-vite/default-ts sandbox with a cleared npx cache (~/.npm/_npx removed) and Storybook dev running:

  • STORYBOOK_FEATURE_AI_CLI=1 npx storybook ai --help shows static options plus the live commands fetched from the running Storybook.
  • STORYBOOK_FEATURE_AI_CLI=1 npx storybook ai list-all-documentation and preview-stories succeed; ~/.npm/_npx stays empty — even with node_modules/@storybook/cli removed entirely, which previously forced the npx download.
  • npx storybook ai setup generates the setup prompt (language detection now scoped to the target working dir).
  • npx storybook doctor still routes to @storybook/cli via the dispatcher, and works through the delegated getStorybookData.
  • STORYBOOK_TELEMETRY_DEBUG=1 shows the ai-command event firing with command/success/duration and target-project metadata, identical to CLI: Add telemetry for the storybook ai <command> passthrough #35138.
  • All moved/affected unit tests pass (686 across core cli/ai, cli-storybook, create-storybook suites); full yarn nx run-many -t check passes (the nextjs-vite:check failure is pre-existing on the base branch).

Summary by CodeRabbit

Release Notes

  • New Features

    • AI setup command is now integrated into the core Storybook CLI with improved language detection capabilities.
  • Improvements

    • Enhanced language detection logic with better TypeScript and JavaScript project recognition.
    • Centralized core utilities for improved code organization and maintainability.

…rybook/cli

The dispatcher only routed dev/build/index to the bundled core binary;
`storybook ai` was proxied to @storybook/cli, falling back to an npx
download on version mismatch. Since agent skills invoke
`npx storybook ai <tool>` repeatedly, that download sat on a hot path.

Move the whole ai command (MCP passthrough + setup) from
@storybook/cli into code/core/src/cli/ai, register it in bin/core.ts
next to dev/build, and add 'ai' to the dispatcher's bundled list.
getStorybookData is reimplemented on top of core's getStorybookInfo;
ProjectTypeService keeps the cross-package source import pattern.

Closes #35146
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

The PR relocates the storybook ai setup CLI command from the Storybook CLI package into core, centralizes language detection and story-path resolution logic, updates dispatcher routing to handle the ai command, and removes the old CLI wiring while updating evaluation tooling and documentation references.

Changes

Relocate storybook ai CLI command to core

Layer / File(s) Summary
Dispatcher routing and core command registration
code/core/src/bin/dispatcher.ts, code/core/src/bin/core.ts
Route ai command to core binary; import AI setup handler and MCP passthrough registration; register storybook ai setup subcommand that merges options, generates runId, wraps execution under telemetry, and routes errors through a new curried handler.
Storybook data collection and story path resolution
code/core/src/cli/getStorybookData.ts, code/core/src/core-server/utils/get-stories-paths-from-config.ts, code/core/src/cli/index.ts, code/core/src/core-server/index.ts, code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts, code/lib/cli-storybook/src/util.ts
Introduce getStorybookData helper to collect Storybook config, version, package manager, and story entry paths; add getStoriesPathsFromConfig to resolve story files; expose and re-export these from core and switch consumers to the centralized implementations.
Centralized language detection and compatibility checking
code/core/src/cli/detectLanguage.ts, code/core/src/cli/detectLanguage.test.ts, code/lib/create-storybook/src/services/ProjectTypeService.ts
Implement detectLanguage with optional workingDir and detectIncompatiblePackageVersions; add tests; update ProjectTypeService to delegate to the centralized helpers.
AI setup handler refactored to use new helpers
code/core/src/cli/ai/index.ts, code/core/src/cli/ai/mcp/register.ts, code/core/src/cli/ai/setup-prompts/partials/rules.ts
Update aiSetup to import getStorybookData locally and call detectLanguage directly; adjust MCP passthrough documentation to reference new code location; update prompt import paths.
Remove old CLI wiring and update documentation/eval scripts
code/lib/cli-storybook/src/bin/run.ts, scripts/eval/lib/run-trial.ts, scripts/eval/lib/utils.ts, scripts/eval/run-batch.ts, scripts/eval/README.md
Remove storybook ai command and MCP passthrough registration from the legacy CLI entrypoint; update eval scripts, help text, and README to reference the prompt registry under core.
Misc small import/path updates
code/core/src/cli/ai/setup-prompts/partials/rules.ts
Adjust relative import path for getMonorepoType.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • storybookjs/storybook#34559: Centralizes detectLanguage and detectIncompatiblePackageVersions, affecting the same language-detection and compatibility-check functions.
  • storybookjs/storybook#34706: Writes ai-setup-ran cache marker during storybook ai setup; the retrieved PR reads that marker in checklist code.
  • storybookjs/storybook#34345: Overlaps with moving/rewiring the storybook ai CLI subcommand between packages.

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

…rt, shared story-paths helper

- Extract detectLanguage/detectIncompatiblePackageVersions into core
  (storybook/internal/cli) and delegate from ProjectTypeService, so core
  no longer imports source from create-storybook (which depends on core).
- Move getStoriesPathsFromConfig into core-server as the single shared
  copy; cli-storybook re-exports it and the ai module imports it, instead
  of two verbatim duplicates.
@kasperpeulen kasperpeulen added maintenance User-facing maintenance tasks ci:normal Run our default set of CI jobs (choose this for most PRs). labels Jun 11, 2026

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/core/src/cli/ai/storybook-data.ts (1)

34-36: ⚠️ Potential issue | 🟠 Major

Fix workingDir derivation for configDir="."
With the current relative-path logic, dirname(join(process.cwd(), configDir)) turns configDir="." into dirname(process.cwd()) (one level up), which will mis-point any setup that relies on workingDir.

Proposed fix
-import { dirname, isAbsolute, join } from 'node:path';
+import { dirname, isAbsolute, resolve } from 'node:path';
@@
-  const workingDir = isAbsolute(configDir)
-    ? dirname(configDir)
-    : dirname(join(process.cwd(), configDir));
+  const workingDir = isAbsolute(configDir)
+    ? dirname(configDir)
+    : resolve(process.cwd(), dirname(configDir));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/cli/ai/storybook-data.ts` around lines 34 - 36, The workingDir
calculation incorrectly uses dirname(join(process.cwd(), configDir)) for
relative paths, which turns configDir="." into dirname(process.cwd()) (one level
up); update the logic in the workingDir derivation (the expression using
isAbsolute, dirname, join, process.cwd(), and configDir) to special-case
configDir === "." (or equivalently when join(process.cwd(), configDir) equals
process.cwd()) and return process.cwd() directly for that case, otherwise keep
the existing dirname(join(process.cwd(), configDir)) branch; ensure absolute
paths still use dirname(configDir).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@code/core/src/cli/ai/storybook-data.ts`:
- Around line 34-36: The workingDir calculation incorrectly uses
dirname(join(process.cwd(), configDir)) for relative paths, which turns
configDir="." into dirname(process.cwd()) (one level up); update the logic in
the workingDir derivation (the expression using isAbsolute, dirname, join,
process.cwd(), and configDir) to special-case configDir === "." (or equivalently
when join(process.cwd(), configDir) equals process.cwd()) and return
process.cwd() directly for that case, otherwise keep the existing
dirname(join(process.cwd(), configDir)) branch; ensure absolute paths still use
dirname(configDir).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f966a75-0e0b-46e7-a534-5da571791031

📥 Commits

Reviewing files that changed from the base of the PR and between e9f53d6 and 6011786.

📒 Files selected for processing (2)
  • code/core/src/cli/ai/storybook-data.ts
  • scripts/eval/README.md

@kasperpeulen kasperpeulen added the qa:needed Pull Requests that will need manual QA prior to release. label Jun 11, 2026
… in detectLanguage

Addresses the codex thermo-nuclear review:
- getStorybookData moves to storybook/internal/cli as the one canonical
  project-metadata collector; automigrate/doctor/add delegate to it via a
  re-export instead of keeping a near-duplicate.
- detectLanguage takes an explicit workingDir for its js/tsconfig lookup
  (defaulting to process.cwd() for init), and ai setup passes the target
  Storybook's working dir instead of relying on the process cwd.
@kasperpeulen

Copy link
Copy Markdown
Member Author

Independent review findings and resolutions

Two independent reviews were run per #35146: the thermo-nuclear-code-quality-review agent (Claude) and Codex (xhigh) running the same thermo-nuclear-code-quality-review skill from the cursor-team-kit plugin.

Finding Severity Resolution
Core imported source from create-storybook, which depends on core (layering inversion via ../../lib/... import of ProjectTypeService) Blocker Fixed: detectLanguage/detectIncompatiblePackageVersions extracted into core (storybook/internal/cli); ProjectTypeService delegates. The cross-package import is gone.
getStoriesPathsFromConfig duplicated verbatim between core and cli-storybook Should-fix Fixed: single copy in core-server, exported via storybook/internal/core-server; cli-storybook re-exports it.
Second getStorybookData near-duplicating the automigrate collector High Fixed: getStorybookData is now the one canonical collector in storybook/internal/cli; automigrate/doctor/add consume it via re-export.
detectLanguage read jsconfig.json/tsconfig.json from implicit process.cwd() Medium Fixed: explicit workingDir parameter; ai setup passes the target Storybook's working dir. storybook init keeps the cwd default (it already runs from the project root).
Dispatcher routing + core registration of ai has no automated test Medium Explicitly resolved, not changed: a list-membership assertion on dispatcher.ts would test the code pattern, not the contract (AGENTS.md: avoid tests for patterns already guaranteed elsewhere), and a true end-to-end test would need built dist binaries inside the unit-test run. The contract is verified by the documented manual QA: with @storybook/cli removed from node_modules and the npx cache cleared, npx storybook ai <tool> runs from the bundled core binary with zero downloads, while storybook doctor still proxies.
storybook --help (routed to @storybook/cli) no longer lists ai; direct npx @storybook/cli ai is now invalid Medium Explicitly resolved, not changed: decided in #35146 — no shim. The dispatcher pins @storybook/cli versions, so released pairings are unaffected; npx storybook ai --help is the entry point.
Stale eval README paths; dead normalizeStories import Low Fixed.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/core/src/cli/getStorybookData.ts (1)

40-42: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep workingDir anchored to the project root for root-level config directories.

Lines 40-42 always strip one path segment from configDir before exposing workingDir on Line 68. That works for packages/foo/.storybook, but --config-dir . or an absolute project-root config directory resolves workingDir to the parent of the project instead. aiSetup now passes this value into detectLanguage(), so JS/TS detection can read the wrong jsconfig.json / tsconfig.json and emit the wrong prompt. Please derive workingDir from the actual project directory instead of unconditionally calling dirname(configDir).

Also applies to: 68-68

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/cli/getStorybookData.ts` around lines 40 - 42, The workingDir
computation currently always does dirname(configDir), which wrongly returns the
project parent for root-level config dirs; change workingDir so it resolves
configDir first (resolve(join/process.cwd() or use the absolute path) and then:
if the resolved configDir is the project root (resolvedConfigDir ===
process.cwd()) or does not point into a subfolder named ".storybook", set
workingDir = resolvedConfigDir (or process.cwd()), otherwise set workingDir =
dirname(resolvedConfigDir); update the code that defines workingDir (the const
workingDir using isAbsolute/configDir) and ensure aiSetup/detectLanguage receive
this corrected workingDir so JS/TS detection reads the correct
jsconfig.json/tsconfig.json.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@code/core/src/cli/getStorybookData.ts`:
- Around line 40-42: The workingDir computation currently always does
dirname(configDir), which wrongly returns the project parent for root-level
config dirs; change workingDir so it resolves configDir first
(resolve(join/process.cwd() or use the absolute path) and then: if the resolved
configDir is the project root (resolvedConfigDir === process.cwd()) or does not
point into a subfolder named ".storybook", set workingDir = resolvedConfigDir
(or process.cwd()), otherwise set workingDir = dirname(resolvedConfigDir);
update the code that defines workingDir (the const workingDir using
isAbsolute/configDir) and ensure aiSetup/detectLanguage receive this corrected
workingDir so JS/TS detection reads the correct jsconfig.json/tsconfig.json.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9a6c4d88-d845-4d92-84ed-6ce3f8a224ba

📥 Commits

Reviewing files that changed from the base of the PR and between 6011786 and 4294bd5.

📒 Files selected for processing (5)
  • code/core/src/cli/ai/index.ts
  • code/core/src/cli/detectLanguage.ts
  • code/core/src/cli/getStorybookData.ts
  • code/core/src/cli/index.ts
  • code/lib/cli-storybook/src/automigrate/helpers/mainConfigFile.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • code/core/src/cli/index.ts
  • code/core/src/cli/detectLanguage.ts

@storybook-app-bot

storybook-app-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

Package Benchmarks

Commit: 6bc246f, ran on 12 June 2026 at 09:48:26 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 72 72 0
Self size 20.86 MB 21.01 MB 🚨 +157 KB 🚨
Dependency size 36.12 MB 36.12 MB 0 B
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 203 203 0
Self size 952 KB 802 KB 🎉 -150 KB 🎉
Dependency size 89.01 MB 89.17 MB 🚨 +155 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 196 196 0
Self size 32 KB 32 KB 0 B
Dependency size 87.50 MB 87.66 MB 🚨 +157 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 73 73 0
Self size 1.08 MB 1.08 MB 🎉 -2 KB 🎉
Dependency size 56.97 MB 57.13 MB 🚨 +157 KB 🚨
Bundle Size Analyzer node node

…-export

Second thermo-nuclear review pass: detectLanguage's workingDir scoping is
an intended fix (config files are found next to the target Storybook, not
the invoking cwd) — covered with memfs tests; the re-export in util.ts
moves below the import block.
Base automatically changed from kasper/ai-cli-telemetry to next June 12, 2026 09:04
@kasperpeulen kasperpeulen added qa:skip Pull Requests that do not need any QA. and removed qa:needed Pull Requests that will need manual QA prior to release. labels Jun 12, 2026
`dirname(join(process.cwd(), configDir))` collapsed `--config-dir .` to the
project's parent directory, mispointing story-glob resolution and the
tsconfig/jsconfig lookup in detectLanguage. Taking dirname before resolving
keeps root-level config dirs anchored to the project root. Pre-existing on
next, surfaced by the move (CodeRabbit review on #35147).
@kasperpeulen

Copy link
Copy Markdown
Member Author

CodeRabbit's outside-diff finding on the workingDir derivation is fixed in c7c43a0: getWorkingDir now takes dirname before resolving, so --config-dir . anchors to the project root instead of its parent (was pre-existing on next, surfaced by the move since workingDir now feeds detectLanguage). Covered by a new unit test. The package-benchmark deltas (storybook +156KB / @storybook/cli −150KB self size) are the relocation itself, not a regression.

@kasperpeulen kasperpeulen merged commit 2a8f815 into next Jun 12, 2026
143 checks passed
@kasperpeulen kasperpeulen deleted the kasper/ai-cli-bundled branch June 12, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:normal Run our default set of CI jobs (choose this for most PRs). maintenance User-facing maintenance tasks qa:skip Pull Requests that do not need any QA.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI: Bundle the ai command in core so it never downloads @storybook/cli

2 participants