> ## Documentation Index
> Fetch the complete documentation index at: https://better-pm.fdarian.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Shell Integration

> Enable directory navigation and enhanced completions with Better PM shell integration

Shell integration unlocks Better PM's full potential by enabling the `pm cd` command to actually change your shell's working directory, rather than just printing a path.

## Quick Setup

Add this line to your shell configuration file:

<CodeGroup>
  ```bash ~/.zshrc theme={null}
  eval "$(pm activate zsh)"
  ```

  ```bash ~/.bashrc theme={null}
  eval "$(pm activate bash)"
  ```
</CodeGroup>

Then reload your shell:

```bash theme={null}
source ~/.zshrc  # or ~/.bashrc
```

## What Gets Activated

The `pm activate` command outputs two things:

1. **Shell wrapper function** - Intercepts `pm cd` to enable directory navigation
2. **Enhanced completions** - Adds package name completions for `pm cd`

### The Shell Wrapper

Here's what the wrapper function does (from `src/commands/activate.ts`):

```bash Shell wrapper (simplified) theme={null}
pm() {
  if [ "$1" = "cd" ]; then
    # Special handling for pm cd
    shift;
    for arg in "$@"; do
      case "$arg" in
        -*) command pm cd "$@"; return;;  # If flags, just print
      esac;
    done;
    local dir;
    dir=$(command pm cd "$@");  # Get directory from CLI
    if [ $? -eq 0 ] && [ -d "$dir" ]; then
      builtin cd "$dir";  # Actually change directory
    fi;
  else
    command pm "$@";  # Pass through all other commands
  fi;
};
```

**How it works:**

<Steps>
  <Step title="Intercepts pm cd">
    When you run `pm cd @myapp/web`, the shell function catches it before the actual CLI runs.
  </Step>

  <Step title="Executes the CLI command">
    Calls `command pm cd @myapp/web` to get the package directory path from the Better PM CLI.
  </Step>

  <Step title="Changes directory">
    If the CLI succeeds and returns a valid directory, uses `builtin cd` to navigate there.
  </Step>

  <Step title="Handles flags correctly">
    If you pass flags like `--completions`, it just executes the command normally without changing directory.
  </Step>
</Steps>

### Enhanced Completions

The activation also sets up intelligent completions that list your workspace packages when you type:

```bash theme={null}
pm cd <TAB>
```

This shows all available workspace package names, making navigation much faster.

## Zsh Integration

For zsh, the activation adds custom completion logic:

```zsh Zsh completions theme={null}
eval "$(command pm --completions zsh)";
if (( $+functions[_pm_zsh_completions] )); then
  functions[_pm_zsh_completions_base]=$functions[_pm_zsh_completions];
  _pm_zsh_completions() {
    if [[ $words[2] == cd ]] && (( CURRENT == 3 )); then
      compadd -- ${(f)"$(command pm cd --completions 2>/dev/null)"};
    else
      _pm_zsh_completions_base "$@";
    fi;
  };
fi;
```

**What this does:**

* First loads the base completions generated by Better PM's CLI framework
* Wraps the completion function to add custom behavior for `pm cd`
* When completing the first argument after `pm cd`, calls `pm cd --completions` to get package names
* For all other commands, delegates to the base completion function

<Info>
  The `--completions` flag is handled by `src/commands/cd.ts` and outputs one package name per line.
</Info>

## Bash Integration

Bash uses a similar but slightly different approach:

```bash Bash completions theme={null}
eval "$(command pm --completions bash)";
_pm_custom_completions() {
  if [[ "${COMP_WORDS[1]}" == "cd" ]] && [[ $COMP_CWORD -eq 2 ]]; then
    COMPREPLY=($(compgen -W "$(command pm cd --completions 2>/dev/null)" -- "${COMP_WORDS[$COMP_CWORD]}"));
    return;
  fi;
  _pm_bash_completions;
};
complete -F _pm_custom_completions -o nosort -o bashdefault -o default pm;
```

**Key differences from zsh:**

* Uses `COMP_WORDS` array instead of zsh's `$words`
* Uses `compgen` for completion generation
* Registers with the `complete` builtin instead of zsh's completion system

## Using pm cd

Once shell integration is active, you can navigate your workspace:

<CodeGroup>
  ```bash Jump to a package theme={null}
  pm cd @myapp/web
  # Now in: ~/my-monorepo/apps/web/
  ```

  ```bash Return to root theme={null}
  pm cd
  # Now in: ~/my-monorepo/
  ```

  ```bash With tab completion theme={null}
  pm cd @my<TAB>
  # Completes to: pm cd @myapp/
  # Continue: pm cd @myapp/w<TAB>
  # Completes to: pm cd @myapp/web
  ```
</CodeGroup>

### Without Shell Integration

If you haven't activated shell integration, `pm cd` still works but only prints the path:

```bash theme={null}
pm cd @myapp/web
# Prints: /home/user/my-monorepo/apps/web
# (but doesn't change directory)
```

You can use this with command substitution:

```bash theme={null}
cd $(pm cd @myapp/web)
```

Or use the `-p` / `--path` flag explicitly:

```bash theme={null}
cd $(pm cd -p @myapp/web)
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="pm cd doesn't change directory">
    **Symptoms:** Running `pm cd @myapp/web` prints the path but doesn't navigate.

    **Diagnosis:**

    1. Check if the shell wrapper is loaded:
       ```bash theme={null}
       type pm
       ```
       Should show: `pm is a shell function` (not `pm is /path/to/pm`)

    2. Verify activation line is in your shell config:
       ```bash theme={null}
       grep "pm activate" ~/.zshrc  # or ~/.bashrc
       ```

    **Solution:**

    * Add `eval "$(pm activate zsh)"` to your `~/.zshrc` (or bash equivalent)
    * Reload: `source ~/.zshrc`
  </Accordion>

  <Accordion title="Completions don't show package names">
    **Symptoms:** Typing `pm cd <TAB>` doesn't show your workspace packages.

    **Diagnosis:**

    1. Test completions directly:
       ```bash theme={null}
       pm cd --completions
       ```
       Should list all workspace package names.

    2. Check if you're in a monorepo:
       ```bash theme={null}
       pm pls
       ```
       Should list your packages. If not, you may not be in a workspace.

    **Solution:**

    * Ensure you have workspace configuration (see [Monorepo Setup](/guides/monorepo-setup))
    * Reload shell after adding activation line
    * For zsh, ensure `compinit` runs after the activation line
  </Accordion>

  <Accordion title="Conflicts with other pm tools">
    **Symptoms:** The shell wrapper interferes with other tools named `pm`.

    **Solution:**
    You can selectively disable the wrapper for specific commands:

    ```bash theme={null}
    command pm cd @myapp/web  # Bypass wrapper, just print path
    \pm cd @myapp/web         # Alternative bypass syntax
    ```

    Or rename the Better PM binary during installation if conflicts are severe.
  </Accordion>

  <Accordion title="Slow completions in large monorepos">
    **Symptoms:** Tab completion for `pm cd` takes several seconds.

    **Cause:** The CLI is being invoked synchronously to generate completions.

    **Mitigation:**

    * Install via Homebrew (recommended): Uses native binary, completions resolve in \~60ms
    * If using npm install: Completions invoke Node.js which is slower
    * Large monorepos (100+ packages) may still see slight delays

    **Note:** This is listed in README as a known trade-off of the npm installation method.
  </Accordion>
</AccordionGroup>

## Advanced Usage

### Multiple Shells

If you use both zsh and bash, add activation to both config files:

```bash ~/.zshrc theme={null}
eval "$(pm activate zsh)"
```

```bash ~/.bashrc theme={null}
eval "$(pm activate bash)"
```

The `pm activate` command outputs shell-specific code, so each shell gets the correct syntax.

### Custom Prompts

The shell wrapper doesn't interfere with prompt customization. You can still use tools like:

* Starship
* Oh My Zsh themes
* Powerline
* Custom PS1/PROMPT variables

Just ensure the activation line comes before prompt setup in your config.

### Integration with Directory Jumpers

Better PM works alongside tools like:

* `z` / `zoxide` - Frecency-based jumping
* `autojump` - Smart directory navigation
* `j` - Jump to recent directories

Example workflow:

```bash theme={null}
# Use pm cd for exact workspace navigation
pm cd @myapp/web

# Use z/zoxide for fuzzy navigation
z api  # Jump to ~/my-monorepo/apps/api if visited before
```

### Scripting with pm cd

In scripts, use the `--path` flag to avoid shell wrapper complications:

```bash Script example theme={null}
#!/bin/bash

# Get package directory programmatically
WEB_DIR=$(pm cd --path @myapp/web)
cd "$WEB_DIR"

# Now in the package directory
npm run build
```

This works even without shell integration activated.

## Performance Considerations

The shell wrapper adds minimal overhead:

* **Function check:** \~0.1ms (checks if command is `cd`)
* **CLI invocation:** 60ms (Homebrew) or 200-500ms (npm install)
* **Directory change:** \<1ms

**Optimization tip:** Use Homebrew installation for fastest performance:

```bash theme={null}
brew install fdarian/tap/better-pm
```

The Homebrew version installs a native binary compiled from the Effect CLI framework, while npm installation runs through Node.js.

## Source Code Reference

The shell integration is implemented in `/src/commands/activate.ts` (lines 6-66):

* `shellWrapper` (lines 6-22): The bash/zsh function that intercepts `pm cd`
* `zshCompletions` (lines 24-34): Zsh-specific completion enhancement
* `bashCompletions` (lines 36-44): Bash-specific completion enhancement
* `activateCmd` (lines 57-66): Command that outputs the combined activation code

The `pm cd --completions` flag is handled in `/src/commands/cd.ts` (lines 31-36).
