Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
1ff9189
docs: add ssh-mcp v4 redesign design spec
hunchom May 17, 2026
88b8180
docs: add ssh-mcp v4 foundation implementation plan
hunchom May 17, 2026
a17ce6c
build: add v4 schema-cost pre-build gate
hunchom May 17, 2026
c75dc4c
feat: add renderHeader v4 render primitive
hunchom May 17, 2026
97f62c9
feat: add indentBody v4 render primitive
hunchom May 17, 2026
3a69fb5
feat: add renderKV v4 render primitive
hunchom May 17, 2026
609cabc
feat: add renderRows v4 render primitive
hunchom May 17, 2026
bac9fbf
fix: harden render primitives against malformed rows
hunchom May 17, 2026
7da1820
docs: add ssh-mcp v4 output-rewrite implementation plan
hunchom May 17, 2026
a280608
refactor: rewrite defaultRender onto v4 render primitives
hunchom May 17, 2026
7234ec6
refactor: rewrite renderMarkdown onto v4 render primitives
hunchom May 17, 2026
a67ee1f
feat: make compact the default v4 output format
hunchom May 17, 2026
db77399
fix: restore value-level test assertions and harden kvRows
hunchom May 17, 2026
e408e51
docs: add ssh-mcp v4 compressors implementation plan
hunchom May 17, 2026
50c7540
feat: add command-output compressor module with ls compressor
hunchom May 17, 2026
87f324d
feat: add ps command-output compressor
hunchom May 17, 2026
4982cf6
feat: run command-output compression in formatExecResult
hunchom May 17, 2026
8edfff6
fix: correct compressPs trailing-newline handling and unify compresso…
hunchom May 17, 2026
20b8d7a
docs: add ssh-mcp v4 plans 4-6 (facade, capabilities, adoption)
hunchom May 17, 2026
792e9f0
feat: add per-action arg validation helper for v4 dispatchers
hunchom May 17, 2026
31dad56
feat: add ctx-factory helper for v4 dispatchers
hunchom May 17, 2026
2462207
feat: add ssh_run v4 dispatcher (exec, sudo, fleet)
hunchom May 17, 2026
8c0da4e
feat: add ssh_file v4 dispatcher
hunchom May 17, 2026
6a601ad
fix: correct timeout remap and forward dropped args in v4 dispatchers
hunchom May 17, 2026
ecd9874
feat: add ssh_logs v4 dispatcher
hunchom May 17, 2026
20af91f
feat: add ssh_service v4 dispatcher
hunchom May 17, 2026
8d06e4a
feat: add ssh_health v4 dispatcher
hunchom May 17, 2026
22e8830
feat: add ssh_db v4 dispatcher
hunchom May 17, 2026
8154a0c
feat: add ssh_backup v4 dispatcher
hunchom May 17, 2026
127a7b9
feat: add ssh_session v4 dispatcher
hunchom May 17, 2026
28c16c4
feat: add ssh_net v4 dispatcher
hunchom May 17, 2026
8ef6eef
feat: add ssh_docker v4 dispatcher
hunchom May 17, 2026
5b130f6
feat: add ssh_fleet v4 dispatcher
hunchom May 17, 2026
4152e4f
feat: add ssh_plan v4 dispatcher with step-enum-keyed dispatch
hunchom May 17, 2026
01ae413
fix: correct arg mapping and drop dead args in v4 facade-2 dispatchers
hunchom May 17, 2026
7ceff99
refactor: rewrite tool registry for 12-tool v4 surface
hunchom May 17, 2026
732c670
refactor: rewrite tool annotations for 12 fat v4 tools
hunchom May 17, 2026
fbfaea6
refactor: lift inline ssh_fleet handler bodies into fleet-adapters mo…
hunchom May 17, 2026
2cd1499
feat: cut MCP surface over to 12 fat v4 verb-tools
hunchom May 17, 2026
fc925c9
test: update tool-config-manager suite for 12-tool v4 registry
hunchom May 17, 2026
ec2745d
fix: restore command aliases and align v4 tool schemas with dispatchers
hunchom May 17, 2026
5cdfbaa
feat: add ssh_find search constants and path guard
hunchom May 17, 2026
4535030
feat: add bounded buildGrepCommand for ssh_find
hunchom May 17, 2026
4b813ba
feat: add buildLocateCommand and buildLsCommand for ssh_find
hunchom May 17, 2026
00a6e41
feat: add ssh_find output parsers for grep, locate, ls
hunchom May 17, 2026
f421912
fix: clamp timeout and match cap in remote-search builders
hunchom May 17, 2026
cef1dee
feat: add buildScriptCommand for ssh_run action script
hunchom May 17, 2026
fc8a02e
feat: add job-tracker detach launch builder
hunchom May 17, 2026
c13ad2f
fix: validate job ids, clamp log offset, nonce-bind script sentinels
hunchom May 17, 2026
7f174c4
feat: add synchronous isAlive liveness check to SSHManager
hunchom May 17, 2026
27e1a24
perf: reuse pooled connections without a per-call ping probe
hunchom May 17, 2026
eeeaf1d
feat: add wrapWithTimeout OS-timeout-utility helper
hunchom May 17, 2026
c2c1b6c
feat: escalate command timeout from INT to KILL after a grace window
hunchom May 17, 2026
67d7f03
feat: wrap non-raw commands in the OS timeout utility
hunchom May 17, 2026
38d0053
fix: run timeout-wrapped commands through a shell and restore raw bypass
hunchom May 17, 2026
265359f
docs: add ssh-mcp v4 wiring plan (ssh_find + ssh_run jobs)
hunchom May 17, 2026
61e138f
feat(ssh-find): add ssh_find dispatcher with grep action
hunchom May 17, 2026
5ef08c1
feat(ssh-find): register ssh_find as the 13th v4 tool
hunchom May 17, 2026
5b13e87
feat(ssh-run): add script action threading the script-runner nonce
hunchom May 17, 2026
b6210a7
feat(ssh-run): add detach action launching a setsid background job
hunchom May 17, 2026
1d7806a
feat(ssh-run): add job-status and job-kill actions over job-tracker
hunchom May 17, 2026
de0b201
feat(ssh-run): advertise script/detach/job actions in the inputSchema
hunchom May 17, 2026
1ce7741
refactor(ssh-run): merge duplicate structured-result import
hunchom May 17, 2026
c37d5cf
chore: sync gitnexus index symbol counts
hunchom May 17, 2026
49454fa
feat: selling v4 tool descriptions that name the bash they replace
hunchom May 17, 2026
747de27
docs: add CLAUDE.md rule to prefer ssh_* MCP tools over raw ssh
hunchom May 17, 2026
fb3907c
feat: add PreToolUse Bash-nudge detector for raw ssh invocations
hunchom May 17, 2026
8ed2dab
feat: register PreToolUse Bash-nudge hook in .claude/settings.json
hunchom May 17, 2026
b753885
docs: correct stale tool and test counts to the v4 surface
hunchom May 17, 2026
4b0cc9a
fix: align v4 tool descriptions with the schema, tidy adoption artifacts
hunchom May 17, 2026
4708fb9
fix: close v4 final-review findings
hunchom May 17, 2026
5dcdd01
fix: validate target_host in ssh_net port-test against shell injection
hunchom May 17, 2026
297407c
chore: sync package-lock.json with package.json node engine and dep p…
hunchom May 17, 2026
9351167
fix: require db_type for all ssh_db actions at the dispatcher
hunchom May 17, 2026
4ca37d7
fix: reject leading-dash hosts in ssh_net port-test
hunchom May 17, 2026
de14ef1
fix(plan): risk override may only raise, never lower step risk
hunchom May 17, 2026
3440429
fix(session): drain all waiters, drop shared decoder, indent send output
hunchom May 17, 2026
e4aefcc
fix(config): default config groups now mirror the v4 group set
hunchom May 17, 2026
6734a3e
fix(keys): exact host+port matching in known_hosts lookup
hunchom May 17, 2026
72b8021
fix: prevent ssh_run fleet OOM crash on group object shape and bad names
hunchom May 17, 2026
87ab7ce
fix: treat ssh_file edit old_text as literal, not a regex pattern
hunchom May 17, 2026
bd2de63
fix: ssh_file deploy -- drop dead args, expose rollback_hook
hunchom May 17, 2026
c02f164
docs: correct ssh-run header -- script/detach/job-* use raw exec
hunchom May 17, 2026
8852167
fix(compress): ls footer names the dropped total-line, not a count
hunchom May 17, 2026
0c719be
chore: delete dead deploy-helper.js (command injection risk)
hunchom May 17, 2026
3b1f862
fix(alerts): ssh_alert_setup check now reads real health-check fields
hunchom May 17, 2026
55e41fa
fix(monitor): guard JSON.parse in ssh_monitor overview path
hunchom May 17, 2026
8e5120d
fix(tail): reap closed follow-sessions instead of leaking them
hunchom May 17, 2026
4655bf7
fix(db): drop dead host/port args from ssh_db
hunchom May 17, 2026
0effbe2
test(session): model stderr PTY-folding faithfully in the shell fake
hunchom May 17, 2026
8380db2
docs: reword wiring-plan check to drop literal attribution markers
hunchom May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions .claude/hooks/ssh-bash-nudge.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env node
/**
* PreToolUse hook for the Bash tool. Detects a simple ssh/scp/rsync invocation
* against a configured server and prints a soft, non-blocking nudge toward the
* matching ssh_* MCP tool. Best-effort: simple shapes nudged, complex command
* lines passed through. Fail-open -- any error exits 0 with no nudge.
*
* Wired in .claude/settings.json under hooks.PreToolUse, matcher "Bash".
*/
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';

// Shell metacharacters => the command line is not a simple invocation. Bail.
const COMPLEX = /[|&;<>`]|\$\(/;

/** Configured server names from the project .env (best-effort, never throws). */
export function configuredServers(envPath) {
try {
const text = readFileSync(envPath, 'utf8');
const names = new Set();
for (const line of text.split('\n')) {
// SSH_SERVER_<NAME>_HOST=... -- <NAME> is the server identifier.
const m = /^\s*SSH_SERVER_([A-Za-z0-9]+)_HOST\s*=/.exec(line);
if (m) names.add(m[1].toLowerCase());
}
return [...names];
} catch {
return [];
}
}

/** Strip a leading user@ and return the bare host token, lowercased. */
function bareHost(token) {
const at = token.lastIndexOf('@');
return (at === -1 ? token : token.slice(at + 1)).toLowerCase();
}

/**
* Inspect a Bash command string. Returns { tool, message } when it is a simple
* ssh/scp/rsync call against a configured server, else null. Never throws.
*/
export function detectSshNudge(command, servers) {
try {
if (!command || typeof command !== 'string') return null;
if (!Array.isArray(servers) || servers.length === 0) return null;
if (COMPLEX.test(command)) return null;

const set = new Set(servers.map((s) => String(s).toLowerCase()));
const tokens = command.trim().split(/\s+/);
const head = tokens[0];

if (head === 'ssh') {
// First token after the flags that is not a flag or a flag-value is the host.
for (let i = 1; i < tokens.length; i++) {
const t = tokens[i];
if (t === '-p' || t === '-i' || t === '-l' || t === '-o' || t === '-F') {
i++; // skip this flag's value
continue;
}
if (t.startsWith('-')) continue;
return set.has(bareHost(t))
? { tool: 'ssh_run', message: nudgeText(bareHost(t), 'ssh_run', 'ssh') }
: null;
}
return null;
}

if (head === 'scp' || head === 'rsync') {
// Any non-flag token of the form host:path against a configured server.
for (let i = 1; i < tokens.length; i++) {
const t = tokens[i];
if (t.startsWith('-')) continue;
const colon = t.indexOf(':');
if (colon > 0 && set.has(bareHost(t.slice(0, colon)))) {
const host = bareHost(t.slice(0, colon));
return { tool: 'ssh_file', message: nudgeText(host, 'ssh_file', head) };
}
}
return null;
}

return null;
} catch {
return null;
}
}

/** The soft nudge text shown in the PreToolUse hook output. */
function nudgeText(host, tool, rawCmd) {
return `[ssh-manager] '${host}' is a configured server. Consider the `
+ `${tool} MCP tool instead of raw \`${rawCmd}\` -- pooled connection, `
+ `bounded output, structured result. (This is a hint, not a block.)`;
}

// --- CLI shell: invoked by Claude Code as a PreToolUse hook --------------
// Reads the hook JSON payload on stdin; prints a nudge on stdout if one
// applies; always exits 0 so the Bash call is never blocked.
function main() {
let raw = '';
try {
raw = readFileSync(0, 'utf8');
} catch {
process.exit(0); // no stdin -> nothing to inspect
}

let payload;
try {
payload = JSON.parse(raw);
} catch {
process.exit(0); // unparseable payload -> fail open
}

const command = payload && payload.tool_input && payload.tool_input.command;
const envPath = fileURLToPath(new URL('../../.env', import.meta.url));
const nudge = detectSshNudge(command, configuredServers(envPath));
if (nudge) console.log(nudge.message);
process.exit(0);
}

// Run main() only when executed directly, never when imported by a test.
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/ssh-bash-nudge.mjs\""
}
]
}
]
}
}
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence

This project is indexed by GitNexus as **claude-code-ssh** (1340 symbols, 3668 relationships, 111 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **claude-code-ssh** (1341 symbols, 3675 relationships, 111 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

Expand Down
25 changes: 20 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ This file provides guidance to Claude Code when working on this repository.

**claude-code-ssh** is an MCP server that gives Claude Code direct SSH access to a configured fleet of servers. The goal: Claude stops being a read-only assistant and becomes a hands-on operator — reading logs, editing configs, running backups, deploying, debugging — without a human typing commands between them.

51 tools, 7 groups, opt-in per user. Connection pooling, streaming exec, head+tail output truncation, ASCII-only rendering.
13 fat verb-tools, each covering one domain via an `action` enum. Always loaded (un-deferred). Connection pooling, streaming exec, head+tail output truncation, command-output compression, ASCII-only rendering.

## Architecture

- **`src/index.js`** — MCP server entry, registers all 51 tools via `registerToolConditional()`
- **`src/index.js`** — MCP server entry, registers the 13 v4 tools via `registerToolConditional()`; descriptions sourced from `src/tool-descriptions.js`
- **`src/tools/*.js`** — 17 modular handler files, one per logical tool area (exec, files, backup, db, etc.)
- **`src/tool-registry.js`** — tool metadata + group membership (core, sessions, monitoring, backup, database, advanced, gamechanger)
- **`src/tool-config-manager.js`** — per-user enablement via `~/.ssh-manager/tools-config.json`
Expand Down Expand Up @@ -58,14 +58,14 @@ ssh-manager tools export-claude # Export auto-approval config

**Tool Groups**: core (5), sessions (4), monitoring (6), backup (4), database (4), advanced (14)

**Modes**: all (37 tools, ~43.5k tokens), minimal (5 tools, ~3.5k tokens), custom (variable)
**Modes**: v4 surface is always loaded (13 tools, ~5k tokens); the per-group mode system is deprecated.

See [docs/TOOL_MANAGEMENT.md](docs/TOOL_MANAGEMENT.md) for complete guide.

### Development and Testing
```bash
npm start # Start MCP server (requires stdin)
npm test # Run 551 tests across 26 suites
npm test # Run 1028 tests
./scripts/validate.sh # Syntax + startup check
node --check src/index.js # JavaScript syntax only
```
Expand Down Expand Up @@ -204,10 +204,25 @@ claude mcp add ssh-manager node /absolute/path/to/claude-code-ssh/src/index.js

Configuration is stored in `~/.config/claude-code/claude_code_config.json`

## Using the SSH Tools

**For any server configured in this MCP server, use the `ssh_*` MCP tools — not raw `ssh`, `scp`, or `rsync` through the Bash tool.**

The 13 v4 tools (`ssh_run`, `ssh_file`, `ssh_find`, `ssh_logs`, `ssh_service`, `ssh_health`, `ssh_db`, `ssh_backup`, `ssh_session`, `ssh_net`, `ssh_docker`, `ssh_fleet`, `ssh_plan`) are not a read-only convenience layer — they are the intended way to operate the fleet. Reach for them first.

Why they beat raw `ssh` in Bash:

- **Connection pooling** — the MCP server holds persistent SSH connections, so there is no per-call handshake. Raw `ssh` in Bash reconnects every single time.
- **Bounded output** — results are compressed and head+tail truncated, so a noisy command (`journalctl`, `ps`, a 100k-line log) will not flood the context window. Raw `ssh` dumps everything.
- **Credential handling** — passwords and sudo passwords are passed via stdin or env, never leaked on the argv of a `ps`-visible process. Raw `ssh` with an inline password is exposed.
- **Structured results** — per-segment exit codes for command chains, typed service/health snapshots, SFTP transfers with sha256 verification. Raw `ssh` gives an unstructured terminal dump.

Raw `ssh` through Bash is acceptable only for a host that is **not** in the MCP configuration. Run `ssh_fleet action: servers` to see which servers are configured.

<!-- gitnexus:start -->
# GitNexus — Code Intelligence

This project is indexed by GitNexus as **claude-code-ssh** (1340 symbols, 3668 relationships, 111 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **claude-code-ssh** (1341 symbols, 3675 relationships, 111 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ flowchart LR
C[Claude Code]
end
subgraph mcp["claude-code-ssh (MCP server)"]
T[51 typed tools]
T[13 verb-tools]
P[ssh2 connection pool]
O[head+tail output]
T --> P
Expand All @@ -121,9 +121,9 @@ flowchart LR
B --> H1
```

- **51 typed tools across 7 groups** — shell, files, databases, backups, deploys, tunnels, sessions. Claude picks; you never enumerate.
- **13 fat verb-tools** — one per domain (run, files, logs, db, docker, services, ...), each with an action enum. Claude picks; you never enumerate.
- **Pooled connections** — 30-minute idle timeout. Reconnects cost zero.
- **Opt-in per group** — minimal mode (5 tools, ~3.5k tokens) to full mode (51 tools, ~43k tokens).
- **Always loaded** — the 13-tool schema is small enough (~5k tokens) to stay un-deferred. No per-group opt-in to manage.

## Install

Expand Down Expand Up @@ -224,8 +224,8 @@ Claude already has a bash tool. Why this server?
| Sudo password handling | argv / `echo pwd \| sudo -S` (leaks to `ps`) | stdin only, never argv |
| DB query safety | Claude can send `DROP TABLE` | token-level SQL parser, SELECT only |
| Host key verification | TOFU by default, no MITM check | SHA256 fingerprint match, strict mode available |
| Tool surface | 1 generic shell exec | 51 typed tools with JSON schemas |
| Context cost | unbounded per command | ~3.5k tokens minimal mode, ~43k full |
| Tool surface | 1 generic shell exec | 13 verb-tools with JSON schemas |
| Context cost | unbounded per command | ~5k tokens, always loaded |

The pitch isn't "Claude couldn't SSH before." The pitch is "Claude could SSH, but badly — and one bad command on prod is one too many."

Expand All @@ -241,7 +241,7 @@ What this doesn't do, today, honestly:
## Testing

```bash
npm test # 551 tests across 26 suites
npm test # 1031 tests
```

## Layout
Expand Down
51 changes: 18 additions & 33 deletions docs/TOOL_MANAGEMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,18 @@

## Overview

claude-code-ssh provides **37 tools** organized into **6 functional groups**. You can enable or disable tool groups to customize your experience and reduce context usage in Claude Code.

### Why Manage Tools?

- **Reduce Context Usage**: By default, all 37 tools consume ~43.5k tokens in Claude Code. Minimal mode uses only ~3.5k tokens (92% reduction)
- **Fewer Approval Prompts**: Only enabled tools require approval in Claude Code
- **Faster Loading**: Less tools mean faster MCP server startup
- **Cleaner Interface**: Only see the tools you actually use

## Quick Start

### View Current Configuration

```bash
ssh-manager tools list
```

### Interactive Configuration Wizard

```bash
ssh-manager tools configure
```

Choose from three modes:
1. **All tools** (37 tools) - Full feature set, recommended for most users
2. **Minimal** (5 tools) - Only core operations, maximum efficiency
3. **Custom** - Pick which groups to enable
> **v4 update:** the v4 surface is **13 fat verb-tools**, always loaded. The
> per-group enable/disable model described below belonged to the v3 51-tool
> surface and no longer applies — there are no tool *groups* in v4. The 13
> tools serialize to roughly 5k schema tokens, small enough that Claude Code
> keeps them loaded without `ToolSearch`. This guide is retained for historical
> reference; the `ssh-manager tools` CLI subcommands are deprecated.

claude-code-ssh provides **13 tools**, each a verb-tool covering one domain
through an `action` enum (`ssh_run`, `ssh_file`, `ssh_find`, `ssh_logs`,
`ssh_service`, `ssh_health`, `ssh_db`, `ssh_backup`, `ssh_session`, `ssh_net`,
`ssh_docker`, `ssh_fleet`, `ssh_plan`). All 13 are registered unconditionally —
there is nothing to enable or disable.

### Enable/Disable Specific Groups

Expand Down Expand Up @@ -157,8 +142,8 @@ Advanced features for power users:
}
```

- **Enabled tools**: 37/37
- **Context usage**: ~43.5k tokens
- **Enabled tools**: the full v3 tool set
- **Context usage**: the full v3 schema cost
- **Best for**: Users who need all features

### Minimal Mode
Expand Down Expand Up @@ -198,7 +183,7 @@ Advanced features for power users:
}
```

- **Enabled tools**: Custom (5-37 tools)
- **Enabled tools**: Custom (a hand-picked v3 subset)
- **Context usage**: Varies based on selection
- **Best for**: Tailoring to specific workflows

Expand Down Expand Up @@ -270,7 +255,7 @@ ssh-manager tools enable monitoring
ssh-manager tools configure # Choose "1) All tools"
```

**Result**: 37 tools = ~43.5k tokens
**Result**: the full v3 tool set loaded

### Scenario 3: Database Administrator

Expand Down Expand Up @@ -431,7 +416,7 @@ Add comments to your config file to remember why you enabled specific groups:

### Q: Will existing users see any changes?

**A**: No. If no configuration file exists, all 37 tools are enabled by default (current behavior).
**A**: No. Under the v3 model, with no configuration file every tool was enabled by default. The v4 surface is always fully loaded -- there is nothing to enable or disable.

### Q: Can I enable individual tools without enabling the whole group?

Expand All @@ -451,7 +436,7 @@ Add comments to your config file to remember why you enabled specific groups:

### Q: How much does minimal mode actually save?

**A**: Minimal mode (5 tools) uses ~3.5k tokens vs all tools (37 tools) at ~43.5k tokens. That's a **92% reduction** or **~40k tokens saved**.
**A**: Under the v3 model, minimal mode (5 tools) cut the schema cost sharply versus enabling the full tool set. This no longer applies -- the v4 surface is a flat 13-tool set, always loaded.

## Command Reference

Expand Down
Loading
Loading