# @maxedapps/pi-ssh-session

A [Pi](https://github.com/earendil-works/pi) package that keeps a persistent OpenSSH ControlMaster connection and routes Pi's built-in file and shell tools to a remote host.

Connect once, work with Pi's normal tools on the remote machine, and disconnect when you want to return to local execution. The SSH connection is held in memory for the active Pi session.

## Security

Pi packages execute with your full user permissions. This extension can run arbitrary shell commands and read or modify files on the selected remote host. Local forwarding also opens listening ports on your machine.

Review the package source, SSH target, remote working directory, and forwarding specifications before connecting. OpenSSH performs its normal host-key verification; this package does not add a separate trust, authentication, or authorization layer.

## Requirements

### Local machine

- Node.js 22.19.0 or newer
- [Pi](https://github.com/earendil-works/pi)
- An OpenSSH-compatible `ssh` client
- Non-interactive SSH authentication, such as SSH keys or an SSH agent

Password and other interactive authentication prompts are not supported because connections use OpenSSH batch mode.

### Remote host

- `bash` for remote commands
- `rg` ([ripgrep](https://github.com/BurntSushi/ripgrep)) for Pi's `grep` tool
- `fd` for Pi's `find` tool
- `file` for image MIME detection by Pi's `read` tool (optional)
- Standard commands such as `cat`, `mkdir`, and `ls`

A connection can still open without `rg`, `fd`, or `file`, but the corresponding capability is unavailable.

## Install

Install globally through Pi:

```bash
pi install npm:@maxedapps/pi-ssh-session
```

Install for the current project instead:

```bash
pi install -l npm:@maxedapps/pi-ssh-session
```

Try the package for one Pi run without installing it:

```bash
pi -e npm:@maxedapps/pi-ssh-session
```

You can also install directly from Git:

```bash
pi install git:github.com/maxedapps/pi-ssh-session
```

Pi reads the package manifest and loads `src/pi-ssh-session.ts` directly through Jiti. No build output is required.

## Connect

### Startup flags

| Flag | Purpose |
|---|---|
| `--ssh <target>` | Connect on startup. Accepts an SSH host alias, `user@host`, or a target with a remote path such as `user@host:/srv/app`. |
| `--ssh-dir <path>` | Set or override the remote working directory used with `--ssh`. |
| `--ssh-forwards <specs>` | Add comma-separated local forwards in `localPort:host:hostPort` form. |

Examples:

```bash
pi --ssh my-vps --ssh-dir /srv/app
pi --ssh user@host:/srv/app --ssh-forwards 15432:127.0.0.1:5432
```

### Slash commands

```text
/ssh-connect <target> [remote-dir] [--forwards localPort:host:hostPort,...]
/ssh-status
/ssh-disconnect
```

Examples:

```text
/ssh-connect my-vps
/ssh-connect my-vps /srv/app
/ssh-connect user@host:/srv/app --forwards 15432:127.0.0.1:5432
```

- `/ssh-connect` opens a connection, switches hosts, or updates the working directory for the current target.
- `/ssh-status` reports the target, directory, forwards, detected capabilities, and control socket.
- `/ssh-disconnect` closes SSH and restores local tool execution.

### `ssh_session` agent tool

The package registers an agent-callable `ssh_session` tool:

| Action | Behavior |
|---|---|
| `connect` | Open or switch a session. `target` is required unless a target is already configured. Optional `remoteCwd` and `forwards` override configured values. |
| `status` | Return the current local, configured, or connected state. |
| `disconnect` | Close SSH and return Pi's built-in tools to the local machine. |

Example tool input:

```json
{
  "action": "connect",
  "target": "user@host:/srv/app",
  "remoteCwd": "/srv/app",
  "forwards": ["15432:127.0.0.1:5432"]
}
```

The agent should call `ssh_session` in its own step before using remote tools and disconnect before returning to local files or commands.

## Core tool routing

While connected, the package overrides Pi's normal tools with remote operations. Tool names and input shapes remain unchanged.

| Pi feature | Remote behavior | Remote requirement |
|---|---|---|
| `read` | Reads remote files and detects supported image types | `cat`; optional `file` for image MIME detection |
| `write` | Creates parent directories and writes remote files | `mkdir`, `cat` |
| `edit` | Reads and writes remote files through Pi's normal edit behavior | `cat`, `mkdir` |
| `bash` | Runs commands in the mapped remote working directory | `bash` |
| `ls` | Lists remote files and directories | `ls` |
| `find` | Performs remote glob searches | `fd` |
| `grep` | Searches remote content and preserves Pi-compatible result formatting | `rg` |
| User `!` commands | Executes user shell commands through the active SSH connection | `bash` |

When SSH is disconnected, all of these features use Pi's original local operations.

## Forwarding

Each forward uses OpenSSH local-forwarding syntax:

```text
localPort:host:hostPort
```

The startup flag and slash command accept comma-separated specifications. The `ssh_session` tool accepts an array. Empty entries are ignored, exact duplicates are removed while preserving order, and invalid specifications are rejected.

Examples:

```bash
pi --ssh my-vps --ssh-forwards 15432:127.0.0.1:5432
```

```text
/ssh-connect my-vps --forwards 15432:127.0.0.1:5432,16379:127.0.0.1:6379
```

Connections with forwards use OpenSSH's `ExitOnForwardFailure=yes`, so connection setup fails if a requested forward cannot be opened.

## Connection lifecycle

The package:

1. starts an OpenSSH ControlMaster when a connection is requested;
2. resolves the remote working directory;
3. detects remote `rg`, `fd`, and `file` capabilities;
4. routes Pi's core file and shell tools through the active control connection;
5. keeps the desired connection configuration in memory and attempts to reconnect before a remote tool call if the master closes unexpectedly;
6. updates Pi's status and system prompt with the active remote target;
7. closes the control connection on explicit disconnect or Pi session shutdown.

Connection state is not persisted across session replacement, reloads, or Pi runs.

## Troubleshooting

### Authentication fails or prompts for a password

Confirm that the target works non-interactively outside Pi:

```bash
ssh -o BatchMode=yes user@host true
```

Configure SSH keys, your SSH agent, and `~/.ssh/config` as needed. Interactive password, passphrase, and keyboard-interactive prompts cannot be handled by the extension.

### Host-key verification fails

Connect with OpenSSH directly and verify the host fingerprint. Do not bypass host-key checking for an untrusted host.

### `grep` or `find` fails after connecting

Install `rg` for `grep` and `fd` for `find` on the remote host. `/ssh-status` and `ssh_session` status output show detected capabilities.

### Image detection is unavailable

Install `file` on the remote host. Text and binary file reading can still work without image MIME detection.

### A local forward cannot be opened

Check whether the local port is already in use and whether the destination host and port are reachable from the remote machine. The SSH connection intentionally fails when forward setup fails.

### The control connection closes

The package keeps the requested configuration and attempts to reconnect before the next remote tool call. Use `/ssh-status`, reconnect explicitly, or `/ssh-disconnect` to return to local execution.

## Update or remove

Update the package:

```bash
pi update npm:@maxedapps/pi-ssh-session
```

Remove it:

```bash
pi remove npm:@maxedapps/pi-ssh-session
```

Use `-l` with `pi install` or `pi remove` when managing project-local package settings.

## Development

```bash
git clone https://github.com/maxedapps/pi-ssh-session.git
cd pi-ssh-session
corepack enable
pnpm install --frozen-lockfile
pnpm typecheck
pnpm test
pnpm check
```

Load the local checkout in isolation:

```bash
pi --no-extensions -e . --help
```

Tests are deterministic and require no network access, credentials, or SSH host. Real-host SSH integration is not part of the normal test suite.

Maintainers should follow [RELEASING.md](https://github.com/maxedapps/pi-ssh-session/blob/main/RELEASING.md) for the manual npm release process. This project does not publish from CI.

## License and attribution

MIT licensed. Portions are derived from Pi's official SSH extension example, Copyright (c) 2025 Mario Zechner. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
