Skip to content

Conversation

@TabishB
Copy link
Contributor

@TabishB TabishB commented Jan 23, 2026

Summary

  • Add animated welcome screen with ASCII art when running openspec artifact-experimental-setup interactively
  • Add searchable multi-select prompt for tool selection instead of requiring --tool flag
  • Support setting up multiple tools in a single command invocation

Test plan

  • Run openspec artifact-experimental-setup without --tool flag in interactive terminal
  • Verify welcome screen animation displays correctly
  • Test searchable multi-select with filtering
  • Verify multi-tool selection works and sets up all selected tools
  • Confirm --tool flag still works for non-interactive usage

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Animated welcome screen with terminal-aware fallback.
    • Interactive multi-tool setup: batch selection, per-tool progress, consolidated summary of configured tools, created skills and optional slash commands.
    • Searchable, keyboard-driven multi-select prompt.
    • New --no-interactive option and optional automated project config creation with guidance.
  • Chores

    • Release version metadata updated.
    • Improved portability of a helper script for macOS/Linux.

✏️ Tip: You can customize this high-level summary in your review settings.

Add animated welcome screen and searchable multi-select prompt when
running `openspec artifact-experimental-setup` without the --tool flag
in interactive mode. Users can now browse and select multiple tools
for setup instead of requiring the --tool flag.

- Add welcome screen with ASCII art animation
- Add searchable multi-select prompt component
- Support multi-tool setup in single command invocation
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

Warning

Rate limit exceeded

@TabishB has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 27 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Replaces the single-tool experimental setup with an interactive multi-tool batch setup, adds a searchable multi-select prompt and an animated welcome screen, centralizes skill/command template generation across tools, optionally creates a default project config, and adds a --no-interactive CLI option.

Changes

Cohort / File(s) Summary
Multi-tool Artifact Workflow Expansion
src/commands/artifact-workflow.ts
Rewrote the experimental setup to support interactive selection of one-or-more tools (or error when non-interactive), validate and aggregate tools, preload shared templates, write per-tool SKILL.md files, attempt slash-command generation via adapters (skip when absent), collect per-tool results, optionally create openspec/config.yaml, and add --no-interactive.
Searchable Multi-select Prompt
src/prompts/searchable-multi-select.ts
New inquirer-core prompt implementation providing a searchable, keyboard-driven multi-select with state (search, cursor, selected), Enter/Tab/Backspace/Arrow handling, pagination, optional validation, styled rendering, and exported searchableMultiSelect(config): Promise<string[]>.
Welcome Screen UI & ASCII Patterns
src/ui/ascii-patterns.ts, src/ui/welcome-screen.ts
Added WELCOME_ANIMATION frames and showWelcomeScreen(): Promise<void> implementing terminal capability detection, animated (or static fallback) side-by-side ASCII art + welcome text, timed frame rendering, and Enter-to-proceed handling.
Devops / Flake Update
flake.nix, scripts/update-flake.sh
Bumped flake version and updated pnpmDeps hash; made hash-extraction in scripts/update-flake.sh portable across macOS/Linux (removed PCRE grep usage).
Package Manifest
package.json
Minor manifest metadata adjustments coordinated with the flake update.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as artifact-workflow
    participant Prompt as searchableMultiSelect
    participant Validator as AI_TOOLS
    participant Templates as TemplateLoader
    participant Adapter as CommandAdapter
    participant FS as FileSystem

    User->>CLI: Run `artifact-experimental-setup`
    alt --tool provided
        CLI->>Validator: Validate single tool
    else --tool missing
        alt interactive
            CLI->>Prompt: Show selectable tools
            Prompt->>User: Display searchable list
            User->>Prompt: Select one+ tools
            Prompt->>CLI: Return selectedTools[]
        else non-interactive
            CLI->>User: Error with valid tool list
        end
    end

    loop for each selected tool
        CLI->>Validator: Ensure tool exists & skillsDir configured
        Validator->>CLI: Return tool metadata
        CLI->>Templates: Preload shared templates
        Templates->>CLI: Return templates
        CLI->>FS: Write SKILL.md into tool's skillsDir
        CLI->>Adapter: Check for command adapter
        alt adapter exists
            Adapter->>FS: Write slash-command files
            FS-->>CLI: Confirm files created
        else no adapter
            Adapter-->>CLI: Skip command generation
        end
        CLI->>CLI: Record per-tool results
    end

    CLI->>FS: Optionally create openspec/config.yaml (interactive)
    CLI->>User: Print consolidated summary (tools, skills, commands)
Loading
sequenceDiagram
    participant User
    participant showWelcome as showWelcomeScreen()
    participant Terminal as Terminal
    participant Renderer as FrameRenderer
    participant Input as InputHandler

    User->>showWelcome: start
    showWelcome->>Terminal: Check TTY, NO_COLOR, width
    alt canAnimate
        loop every interval
            showWelcome->>Renderer: Get next WELCOME_ANIMATION frame
            Renderer->>Terminal: Render art + text side-by-side
        end
    else
        showWelcome->>Renderer: Render static frame
        Renderer->>Terminal: Display once
    end
    showWelcome->>Input: Wait for Enter or Ctrl+C
    User->>Input: Press Enter
    Input->>showWelcome: proceed
    showWelcome->>Terminal: Clear screen
    showWelcome-->>User: return
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hopped through tools both near and far,

Picked several friends beneath the star,
Templates hummed and SKILLs took flight,
Diamond frames danced in glowing light,
Batch setup done — a joyful hop tonight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding interactive UI features to the artifact experimental setup command, which is reflected in all the key changes (welcome screen, multi-select prompt, multi-tool support).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps
Copy link

greptile-apps bot commented Jan 23, 2026

Greptile Summary

This PR enhances the artifact-experimental-setup command with an interactive UI that guides users through tool selection when the --tool flag is omitted. The implementation adds:

  • Animated Welcome Screen: A side-by-side ASCII art animation with welcome text that plays before tool selection, with graceful fallbacks for non-TTY terminals and narrow screens
  • Searchable Multi-Select: A custom prompt built on @inquirer/core that allows users to filter and select multiple tools simultaneously using intuitive keyboard navigation (↑↓ navigate, Enter to add, Backspace to remove, Tab to confirm)
  • Multi-Tool Batch Processing: Refactored the setup command to process multiple tools in a single invocation, creating skills and commands for all selected tools

The changes maintain backward compatibility - the --tool flag still works for non-interactive usage, and a new --no-interactive flag allows disabling prompts. The refactoring moves template generation outside the tool loop to avoid redundant work, and properly accumulates results across all tools for the final summary output.

Key implementation details:

  • Uses dynamic imports to prevent pre-commit hook hangs (consistent with issue openspec validate hangs when runnings as pre-commit hook #367)
  • Terminal capability detection with fallbacks for NO_COLOR, non-TTY, and narrow terminals
  • Proper cleanup of animation state and ANSI escape sequences
  • Validation that at least one tool must be selected before proceeding

Confidence Score: 4/5

  • This PR is safe to merge with minor risk from the interactive UI complexity
  • The implementation is solid with proper error handling, fallbacks for edge cases, and follows existing patterns in the codebase. The score is 4 rather than 5 due to the complexity of the interactive UI components (terminal state management, animation loops) which are harder to test thoroughly and could have subtle issues on different terminal emulators. However, the code includes appropriate safeguards (canAnimate checks, non-TTY handling, cleanup logic) and maintains backward compatibility with the existing --tool flag.
  • No files require special attention - all changes follow good practices

Important Files Changed

Filename Overview
src/prompts/searchable-multi-select.ts New searchable multi-select prompt with filtering, keyboard navigation, and validation - well-structured implementation
src/ui/welcome-screen.ts Animated welcome screen with terminal capability detection and graceful fallbacks - properly handles edge cases
src/commands/artifact-workflow.ts Added interactive multi-tool setup with welcome screen, refactored to support batch processing - significant logic changes but well-tested patterns

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as artifact-workflow.ts
    participant Welcome as welcome-screen.ts
    participant MultiSelect as searchable-multi-select.ts
    participant FileSystem as FileSystemUtils

    User->>CLI: openspec artifact-experimental-setup
    
    alt No --tool flag provided
        CLI->>CLI: Check isInteractive()
        
        alt Interactive mode
            CLI->>Welcome: showWelcomeScreen()
            Welcome->>Welcome: Render animated ASCII art
            Welcome-->>User: Display welcome animation
            User->>Welcome: Press Enter
            Welcome-->>CLI: Continue
            
            CLI->>MultiSelect: searchableMultiSelect(choices)
            MultiSelect-->>User: Display searchable list
            User->>MultiSelect: Type to filter, Enter to select
            User->>MultiSelect: Tab to confirm
            MultiSelect-->>CLI: Return selectedTools[]
            CLI->>CLI: Set options.selectedTools
        else Non-interactive mode
            CLI-->>User: Error: Missing --tool flag
        end
    end
    
    CLI->>CLI: Validate all selected tools
    
    loop For each tool in toolsToSetup
        CLI->>CLI: Start spinner
        
        loop For each skill template
            CLI->>FileSystem: writeFile(skillFile, content)
            FileSystem-->>CLI: Success
        end
        
        CLI->>CLI: Get command adapter
        
        alt Adapter exists
            loop For each command template
                CLI->>FileSystem: writeFile(commandFile, content)
                FileSystem-->>CLI: Success
            end
        else No adapter
            CLI->>CLI: Mark commands as skipped
        end
        
        CLI->>CLI: Stop spinner (success)
    end
    
    CLI-->>User: Display summary with created files
Loading

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@src/commands/artifact-workflow.ts`:
- Around line 1056-1062: The non-interactive branch is checking
process.stdin.isTTY directly (process.stdin.isTTY) but the codebase provides an
isInteractive() utility (already imported) that accounts for CI and
OPEN_SPEC_INTERACTIVE; replace the direct TTY check with a call to
isInteractive() (i.e., use !isInteractive()) in the artifact workflow branch so
the interactive detection is consistent with the rest of the code; update any
log comments if needed to reflect the utility-driven decision.
- Around line 1003-1006: The current catch block in the tool setup loop (which
calls spinner.fail(`Failed for ${tool.name}`) and then re-throws the error)
aborts the entire run; instead, change the logic in the tool processing loop
(where each tool is set up—look for the try/catch around the per-tool setup,
e.g., references to spinner and tool.name) to collect failures into a local
array (e.g., failures or failedTools) with entries containing tool.name and the
caught error, do not re-throw inside the per-tool catch so the loop continues to
the next tool, and after the loop finishes check the failures array and surface
a concise summary (log or throw a single aggregated error) so callers get a
complete report of which tools failed.

In `@src/prompts/searchable-multi-select.ts`:
- Around line 174-179: The page indicator is computed from Math.floor(cursor /
pageSize) which mismatches the sliding/centered window logic; instead determine
the window start index used by the sliding window (use the existing windowStart
variable if present or compute startIndex = Math.max(0, cursor -
Math.floor(pageSize/2))) and base currentPage on that startIndex (currentPage =
Math.floor(startIndex / pageSize) + 1) while keeping totalPages =
Math.ceil(filteredChoices.length / pageSize), then use those values when pushing
the pagination line; reference variables: filteredChoices, pageSize, cursor, and
windowStart/startIndex.

In `@src/ui/welcome-screen.ts`:
- Around line 131-140: The computed totalHeight and its explanatory comment are
stale/misleading: either remove the unused totalHeight variable and its outdated
comment, or if totalHeight is intended to be used, correct the arithmetic and
comment to match the actual calculation (ensure frameHeight = numContentLines +
1 and that the comment reflects those values) and keep numContentLines,
frameHeight and totalHeight consistent with WELCOME_ANIMATION.frames and
textLines; update or remove the line with totalHeight accordingly.
🧹 Nitpick comments (6)
src/prompts/searchable-multi-select.ts (2)

112-116: Character input handling may miss some printable characters.

The condition key.name && key.name.length === 1 may not capture all printable input. Some terminals report the actual character in key.sequence rather than key.name, and special characters or Unicode input might be missed.

Consider also checking key.sequence for more robust character capture:

♻️ Suggested improvement
      // Character input - handle printable characters
-      if (key.name && key.name.length === 1 && !key.ctrl) {
-        setSearchText(searchText + key.name);
+      const char = key.sequence ?? key.name;
+      if (char && char.length === 1 && !key.ctrl && !key.meta) {
+        setSearchText(searchText + char);
         setCursor(0);
       }

80-88: Allow toggling selection with Enter for already-selected items.

Currently, pressing Enter on an already-selected choice does nothing (line 83 checks !selectedSet.has(choice.value)). Users may expect Enter to toggle selection (deselect if already selected), which is a common UX pattern in multi-select prompts.

♻️ Suggested toggle behavior
      // Enter to add item
      if (isEnterKey(key)) {
        const choice = filteredChoices[cursor];
-        if (choice && !selectedSet.has(choice.value)) {
-          setSelectedValues([...selectedValues, choice.value]);
+        if (choice) {
+          if (selectedSet.has(choice.value)) {
+            // Deselect if already selected
+            setSelectedValues(selectedValues.filter(v => v !== choice.value));
+          } else {
+            setSelectedValues([...selectedValues, choice.value]);
+          }
           setSearchText('');
           setCursor(0);
         }
         return;
       }
src/ui/welcome-screen.ts (2)

90-101: Raw mode restoration should use try-finally for robustness.

If an error occurs between setRawMode(true) and the cleanup in onData, the terminal could be left in raw mode. While unlikely in this simple case, wrapping in try-finally or ensuring cleanup on error would be safer.


146-162: Potential race condition: interval callback may run after running is set to false.

The running flag is checked inside the interval callback (line 147), but clearInterval is called after setting running = false (lines 168-169). Due to JavaScript's event loop, the interval callback could theoretically execute one more time after running is set to false but before clearInterval completes. The early return handles this, but swapping the order would be cleaner.

♻️ Suggested order swap
  // Wait for Enter
  await waitForEnter();

  // Stop animation
-  running = false;
  clearInterval(interval);
+  running = false;
src/commands/artifact-workflow.ts (2)

873-878: Setting options.tool after multi-select is unnecessary.

After multi-select, options.tool is set to selectedTools[0] (line 877), but toolsToSetup on line 887 uses options.selectedTools || [options.tool!]. This works but the assignment to options.tool is confusing since it's not used meaningfully when selectedTools is populated. Consider simplifying.

♻️ Suggested simplification
      if (selectedTools.length === 0) {
        throw new Error('At least one tool must be selected');
      }

-      options.tool = selectedTools[0];
       options.selectedTools = selectedTools;
     } else {

Then update line 887:

  // Determine tools to set up
-  const toolsToSetup = options.selectedTools || [options.tool!];
+  const toolsToSetup = options.selectedTools ?? [options.tool!];

1078-1083: Git commit suggestion includes trailing slashes that may cause issues.

Line 1079 appends / to each skillsDir (t.skillsDir + '/'), which is unconventional for git add and could behave unexpectedly if the directory doesn't exist. Standard practice is to use paths without trailing slashes.

♻️ Suggested fix
      // Git commit suggestion with all tool directories
-      const toolDirs = validatedTools.map(t => t.skillsDir + '/').join(' ');
+      const toolDirs = validatedTools.map(t => t.skillsDir).join(' ');
       console.log(chalk.bold('To share with team:'));

Comment on lines +174 to +179
// Show pagination indicator if needed
if (filteredChoices.length > pageSize) {
const currentPage = Math.floor(cursor / pageSize) + 1;
const totalPages = Math.ceil(filteredChoices.length / pageSize);
lines.push(chalk.dim(` (${currentPage}/${totalPages})`));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pagination indicator calculation may be inaccurate with the sliding window approach.

The pagination uses a sliding/centered window (lines 155-160), but the page indicator calculation (lines 176-177) uses simple Math.floor(cursor / pageSize), which doesn't match the sliding window logic. This could show misleading page numbers.

🔧 Suggested fix
      // Show pagination indicator if needed
      if (filteredChoices.length > pageSize) {
-        const currentPage = Math.floor(cursor / pageSize) + 1;
-        const totalPages = Math.ceil(filteredChoices.length / pageSize);
-        lines.push(chalk.dim(`  (${currentPage}/${totalPages})`));
+        // Show position indicator instead of page numbers (more accurate with sliding window)
+        lines.push(chalk.dim(`  (${cursor + 1}/${filteredChoices.length})`));
      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Show pagination indicator if needed
if (filteredChoices.length > pageSize) {
const currentPage = Math.floor(cursor / pageSize) + 1;
const totalPages = Math.ceil(filteredChoices.length / pageSize);
lines.push(chalk.dim(` (${currentPage}/${totalPages})`));
}
// Show pagination indicator if needed
if (filteredChoices.length > pageSize) {
// Show position indicator instead of page numbers (more accurate with sliding window)
lines.push(chalk.dim(` (${cursor + 1}/${filteredChoices.length})`));
}
🤖 Prompt for AI Agents
In `@src/prompts/searchable-multi-select.ts` around lines 174 - 179, The page
indicator is computed from Math.floor(cursor / pageSize) which mismatches the
sliding/centered window logic; instead determine the window start index used by
the sliding window (use the existing windowStart variable if present or compute
startIndex = Math.max(0, cursor - Math.floor(pageSize/2))) and base currentPage
on that startIndex (currentPage = Math.floor(startIndex / pageSize) + 1) while
keeping totalPages = Math.ceil(filteredChoices.length / pageSize), then use
those values when pushing the pagination line; reference variables:
filteredChoices, pageSize, cursor, and windowStart/startIndex.

Comment on lines +131 to +140
let frameIndex = 0;
let running = true;
let isFirstRender = true;

// Content height for cursor movement between frames
const numContentLines = Math.max(WELCOME_ANIMATION.frames[0].length, textLines.length);
const frameHeight = numContentLines + 1; // internal newlines (11) + trailing newlines (2) = 13

// Total height including initial newline (for cleanup)
const totalHeight = frameHeight + 1; // 14
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unused variable totalHeight calculation contains misleading comment.

Line 137 comment says // internal newlines (11) + trailing newlines (2) = 13 but frameHeight = numContentLines + 1 doesn't match this arithmetic. The comment appears stale or incorrect relative to the actual calculation.

🔧 Suggested fix
  // Content height for cursor movement between frames
  const numContentLines = Math.max(WELCOME_ANIMATION.frames[0].length, textLines.length);
-  const frameHeight = numContentLines + 1; // internal newlines (11) + trailing newlines (2) = 13
-
-  // Total height including initial newline (for cleanup)
-  const totalHeight = frameHeight + 1; // 14
+  const frameHeight = numContentLines + 2; // content lines + 2 trailing newlines from render
+  
+  // Total height including initial newline (for cleanup)
+  const totalHeight = frameHeight + 1; // +1 for initial newline
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let frameIndex = 0;
let running = true;
let isFirstRender = true;
// Content height for cursor movement between frames
const numContentLines = Math.max(WELCOME_ANIMATION.frames[0].length, textLines.length);
const frameHeight = numContentLines + 1; // internal newlines (11) + trailing newlines (2) = 13
// Total height including initial newline (for cleanup)
const totalHeight = frameHeight + 1; // 14
let frameIndex = 0;
let running = true;
let isFirstRender = true;
// Content height for cursor movement between frames
const numContentLines = Math.max(WELCOME_ANIMATION.frames[0].length, textLines.length);
const frameHeight = numContentLines + 2; // content lines + 2 trailing newlines from render
// Total height including initial newline (for cleanup)
const totalHeight = frameHeight + 1; // +1 for initial newline
🤖 Prompt for AI Agents
In `@src/ui/welcome-screen.ts` around lines 131 - 140, The computed totalHeight
and its explanatory comment are stale/misleading: either remove the unused
totalHeight variable and its outdated comment, or if totalHeight is intended to
be used, correct the arithmetic and comment to match the actual calculation
(ensure frameHeight = numContentLines + 1 and that the comment reflects those
values) and keep numContentLines, frameHeight and totalHeight consistent with
WELCOME_ANIMATION.frames and textLines; update or remove the line with
totalHeight accordingly.

- Update version from 0.20.0 to 0.23.0 to match package.json
- Set pnpmDeps hash to empty string to trigger rebuild
- Fix update-flake.sh to work on macOS (use portable grep/sed)

CI will fail with correct hash which we'll then apply.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@flake.nix`:
- Around line 26-31: The pnpmDeps block uses pkgs.fetchPnpmDeps and currently
has an empty hash which breaks nix builds; run the repository's pnpm deps/hash
update script to regenerate the correct integrity/hash for pnpmDeps, replace the
empty string in the hash attribute under pnpmDeps (the pkgs.fetchPnpmDeps block)
with the computed value, and commit the updated flake.nix so pnpm = pkgs.pnpm_9
and fetcherVersion remain unchanged.

- Continue setup for remaining tools when one fails
- Collect and report all failures at the end
- Only throw if all tools fail
- Show partial success summary (configured vs failed)
@TabishB TabishB merged commit ae83b4e into main Jan 23, 2026
9 checks passed
@TabishB TabishB deleted the feat/interactive-artifact-setup branch January 23, 2026 02:39
Sign up for free to 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.

2 participants