Files
my-pi/pi-ssh
云服务部-叶林立andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> ba164896d2 feat: 系统提示词改为单一入口统一管理
多个扩展各自整段改写系统提示词并按加载顺序接力,任一扩展定死提示词后
其余只能做字符串手术,且各上游的工具说明与组合包实际工具格局冲突。

- 新增 extensions/system-prompt.ts 作为唯一出口(加载列表最后),按
  config/prompt/(preamble.md、tools.jsonc、routing.jsonc、sections.jsonc)
  组合开场白、工具简介/规则与 <tool_routing> 分区;替代原封面与
  tool-routing 扩展,/dump-system-prompt 输出最终提示词。
- pi-chrome、pi-ssh、pi-hermes-memory 改为写命名分区,pi-rtk-optimizer
  改写 promptGuidelines,pi-permission-system 直接过滤结构化 skills,
  均不再返回 systemPrompt;pi-chrome 未授权时不再每轮冻结提示词。
- 新增 prompt-guard 代理加载第三方扩展(Hashline、FFF、LSP、
  context-mode):拦截直接改写提示词并按目录覆盖工具说明;context-mode
  去掉与组合包路由冲突的锚点段,保留记忆与恢复快照。
- 消除冲突:bash 简介不再建议 grep/find;Hashline edit 不再称可用 grep
  锚点、不再介绍已关闭的 replace_text;context-mode 读写/网页路由提示。
- 守门测试:工具目录覆盖率、禁用用法检查、context-mode 上游原文锁定;
  AGENTS.md 新增「系统提示词单一所有权」。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-20 17:53:04 -07:00
..
2026-08-21 19:51:43 +08:00

pi-ssh

pi-ssh keeps Pi and its local tools on the local machine while exposing explicit remote tools over a persistent Node ssh2 connection:

  • ssh_connect
  • ssh_cd
  • ssh_read
  • ssh_write
  • ssh_edit
  • ssh_find
  • ssh_grep
  • ssh_bash

The extension does not override Pi's local read, write, edit, find, grep, or bash tools.

Architecture

Runtime connections are pure ssh2; the extension does not spawn OpenSSH and does not require sshpass, ControlMaster, or passwordless login. Authentication can use a private-key file, a password, or an SSH agent socket such as 1Password's agent. Remote file operations use SFTP and remote shell commands use an SSH exec channel. The SSH transport persists, but commands intentionally use fresh non-interactive Bash processes rather than a hidden stateful PTY.

Hosts must be explicitly imported before use. OpenSSH remains only an import source: the configuration helper runs ssh -G <alias> once to resolve the selected alias, then stores the resulting endpoint and authentication data in the pi-ssh vault. Later changes to ~/.ssh/config require re-importing the host.

Connections are agent-controlled: when the user explicitly names an imported host as part of a concrete remote task, the model calls ssh_connect. The connection request enters the normal permission and auto-review chain before any network connection is opened. The extension does not register /ssh, --ssh, automatic session resume, or remote user-! overrides.

ProxyJump and ProxyCommand are intentionally rejected in the first ssh2 release.

Configure hosts

From the installed bundle or this repository:

./configure.sh ssh import
./configure.sh ssh import packaging-server

The helper:

  1. lets you select concrete aliases from ~/.ssh/config;
  2. uses ssh -G to resolve HostName, User, Port, IdentityFile, and IdentityAgent;
  3. asks whether the selected host uses an SSH agent, private key, or password;
  4. validates the selected agent socket or asks for a private-key passphrase when needed;
  5. obtains and displays the server's SHA256 host-key fingerprint;
  6. connects with ssh2 to verify authentication;
  7. optionally assigns a display label and group;
  8. writes the encrypted vault.

Other commands:

./configure.sh ssh list
./configure.sh ssh update packaging-server
./configure.sh ssh remove packaging-server
./configure.sh ssh rotate-key

Secret prompts require an interactive terminal. Passwords and passphrases are never passed as command-line arguments.

Vault

The default paths are:

${XDG_CONFIG_HOME:-$HOME/.config}/my-pi/pi-ssh/
├── hosts.enc
└── vault.key

On POSIX systems the directory is mode 700 and both files are mode 600. hosts.enc is encrypted and authenticated with AES-256-GCM; vault.key contains the adjacent random 256-bit key. Writes use a fresh IV and an atomic temporary-file rename. Plaintext configuration is never written to a temporary file.

This is deliberately a casual-disclosure boundary, not protection against compromise of the local account: anyone who can read both files can decrypt the vault. Encryption prevents the host configuration and passwords from being exposed by accidentally viewing or copying hosts.enc alone.

The encrypted payload contains host endpoints, pinned host-key fingerprints, groups, and one of:

  • an SSH agent socket path;
  • a private-key path plus optional passphrase; or
  • the server password.

Private-key contents are not copied into the vault. Agent-backed private keys remain inside the agent (including 1Password); pi-ssh stores only the socket path and asks ssh2 to authenticate through it.

Usage

Ask Pi for a concrete task on an imported host, for example:

Connect to packaging-server, inspect the API logs, and identify the recent 500 errors.

The model first calls:

ssh_connect({ hostId: "packaging-server" })

It may set remotePath to an absolute path, ~, or a path beginning with ~/. The model calls sequential ssh_connect as a separate step and waits for the reviewed connection to succeed before using other ssh_* tools. ssh_cd explicitly changes the active remote workspace for subsequent shell, relative file, and relative search operations; it accepts absolute paths, paths relative to the current remote cwd, and ~/ paths relative to remote HOME. The model must also call ssh_cd as a separate step and wait for its successful result before issuing dependent remote operations. Only imported host IDs are accepted; arbitrary user@host targets are rejected. Unknown IDs report a bounded list of available imported IDs without exposing credentials. A new connection replaces the previous active connection, and session shutdown disconnects it.

Runtime behavior

  • ssh_connect is the only runtime connection surface; it is agent-callable, permission-reviewed, sequential, and cancellable so connection replacement cannot overlap another tool call or remain stuck after user cancellation.
  • One persistent ssh2 client is used for the active host; no interactive shell or PTY state is retained.
  • ssh_cd is a sequential state transition: it validates a remote directory and updates the active workspace without reconnecting. Dependent tool calls must wait for it to succeed.
  • HOME/cwd probes use bounded random-marker framing, require one absolute POSIX path, and preserve the previous state on malformed output, timeout, or cancellation.
  • Independent exec channels use bounded concurrency of four; shared SFTP operations remain serialized.
  • Each ssh_bash call opens a fresh exec channel and runs under bash -lc in the active remote cwd. A command-local cd is temporary, and cd, export, alias, function, or other shell state inside one call does not persist to the next call.
  • SFTP provides remote reads and writes.
  • Writes use a temporary remote file and prefer OpenSSH's atomic rename SFTP extension when the server supports it.
  • A pinned SHA256 host-key mismatch fails closed.
  • Connection loss fails closed; the extension does not silently replay a command.
  • Remote AGENTS.md and CLAUDE.md files are never discovered or injected.
  • ssh_find and ssh_grep perform capability detection inside each approved call and return at most 200 bounded result lines. Search targets are always separate from the execution cwd, so ssh_grep can target a single remote file without attempting to enter it as a directory. A 30-second timeout reports the resolved root and instructs the model to narrow it with ssh_find.
  • RTK treats only ssh_bash as a Bash output-compaction alias; it does not rewrite remote commands or process remote search/read results.
  • Permission previews display resolved absolute remote paths together with the original relative or ~/ request; remote paths never enter local filesystem gates.

ssh_find performs fixed-substring path matching with this backend order:

fd → fdfind → git ls-files → find

ssh_grep defaults to literal, case-insensitive content matching with this backend order. Hidden paths are excluded unless includeHidden is true, and include is a basename-only glob such as *.ts (globs containing / are rejected):

ripgrep → git grep → find + grep

No backend is installed or uploaded. The tools use what the server already provides, report the chosen backend and truncation state, and cap limit at 200. literal: false accepts the portable POSIX ERE subset shared by the backends; Git and fallback grep are explicitly run in ERE mode. Grep results use bounded NUL-delimited path/line/content records, so newline-containing filenames cannot corrupt match counts or truncation. Backend stderr is kept out of result rows and is reported as a warning on success or failure detail on error.

A genuine no-match result—including the find + grep fallback—succeeds with zero rows; invalid regexes, missing roots, and backend failures remain errors. Search paths accept absolute paths, relative paths, ~, and ~/...; ~user expansion is rejected. Narrow path and pattern when truncated rather than increasing the limit. Direct find, fd, grep, and rg commands remain forbidden through ssh_bash; use the structured search tools instead.

The remote host must provide bash. SFTP support is required for file tools.

Permission-system integration

All remote operations enter the bundle's existing permission chain:

  • ssh_connect starts as ask, so AutoReview can verify that the direct user request names the requested imported host;
  • ssh_cd, ssh_read, ssh_write, ssh_edit, ssh_find, and ssh_grep start as ask;
  • ssh_bash uses the full deterministic Bash policy and decisionFloor: "ask";
  • deterministic hard denies remain denies;
  • asks enter the configured auto-review authorizer;
  • reviewer failures defer to the normal terminal prompt.

Permission evidence includes the configured host ID, resolved endpoint, port, remote cwd, and a bounded operation summary. It never includes passwords, passphrases, private-key contents, or the vault key. Remote paths are not normalized as local filesystem paths.

Security notes

  • Import only servers you control or trust.
  • Verify host-key fingerprints through an independent channel before accepting them.
  • Treat both vault files as secrets even though hosts.enc is encrypted.
  • The extension's threat model does not protect credentials from malicious code already running as the same local user.
  • Password keyboard-interactive mode reuses the configured password for the server's prompts; use it only with a trusted pinned host.
  • Remote content reaches the model only through an explicit reviewed ssh_* tool call.

Development

npm test

Important files:

  • index.ts — Pi extension and tool registration
  • src/ssh2-transport.ts — persistent ssh2, exec, and SFTP transport
  • src/remote-bash.ts — stateless Bash adapter pinned to the active remote cwd
  • src/remote-cwd.ts — explicit remote workspace path resolution
  • src/config.ts — validated configuration types
  • src/vault.ts — AES-GCM vault
  • src/import.ts — ssh -G import helpers
  • scripts/ssh-config.mjs — interactive configuration CLI
  • permission-integration.ts — permission-system bridge

Upstream and license

This maintained fork originates from pansapiens/pi-ssh; see UPSTREAM.md. The pure ssh2 design also references the transport architecture in @99percentpeople/pi-ssh-remote without adopting its local-tool override model. Licensed under MIT; see LICENSE.