# PortHub

PortHub is a local-first tool for storing and retrieving Markdown context by hierarchical keys via CLI and MCP.

## MVP Scope

- Local filesystem storage only (default: `~/.porthub`; configurable via `PORTHUB_HOME` or `--root`).
- CLI workflows plus an MCP stdio server.
- Five commands: `set`, `get`, `search`, `list`, `server`.
- Exact match for `get`.
- Case-insensitive substring match for `search` (with optional key-only/content-only modes).

## Install

Install the CLI tool:

```bash
uv tool install -U porthub
```

Or run directly without installation:

```bash
uvx porthub --help
```

For local development in this repository:

```bash
uv sync
```

You can run commands either through `uv`:

```bash
uv run porthub --help
```

Or through the installed script:

```bash
porthub --help
```

## Usage

Set Markdown content from an inline string:

```bash
porthub set python/typer "# Typer\nCLI framework for Python"
```

Set Markdown content from a file:

```bash
porthub set python/typer --file ./notes/typer.md
```

Set Markdown content from stdin:

```bash
cat ./notes/typer.md | porthub set python/typer --stdin
```

Get content by exact key:

```bash
porthub get python/typer
```

Search by key or content (case-insensitive):

```bash
porthub search typer
```

Search by key only:

```bash
porthub search typer --key-only
```

Search by content only:

```bash
porthub search typer --content-only
```

Limit search results:

```bash
porthub search typer --limit 10
```

List all keys:

```bash
porthub list
```

Use a custom storage root for any command:

```bash
porthub --help
porthub list --root ./tmp-porthub
porthub get python/typer --root ./tmp-porthub
```

Or set a default storage root via environment variable:

```bash
export PORTHUB_HOME=./tmp-porthub
porthub list
```

## MCP Server

Start MCP server over stdio:

```bash
uv run porthub server
uvx porthub server
```

Use custom storage root:

```bash
uv run porthub server --root ./tmp-porthub
```

Default MCP tools (namespaced):

- `porthub_get(key)`
- `porthub_set(key, value)`
- `porthub_search(query, mode=\"all\"|\"key\"|\"content\", limit=None)`
- `porthub_list()`

Tool responses are structured JSON objects with:

- `ok` (`true` or `false`)
- `error` (`null` or `{code, message}`)
- tool payload fields such as `key`, `content`, `matches`, `keys`

Example MCP client configuration (same pattern as `uvx + args`):

```json
{
  "mcpServers": {
    "porthub": {
      "command": "uvx",
      "args": ["porthub", "server"]
    }
  }
}
```

With custom storage root:

```json
{
  "mcpServers": {
    "porthub": {
      "command": "uvx",
      "args": ["porthub", "server", "--root", "./tmp-porthub"]
    }
  }
}
```

## Skills

This repository includes a local skill for package-documentation retrieval workflows:

- Install (method 1):

```bash
npx skills add narumiruna/porthub
```

- Install (method 2):

```bash
npx ctx7 skills install narumiruna/porthub
```

- Skill source: `skills/porthub/SKILL.md`

## Key Rules

A key is valid only when all rules pass:

1. It is not empty after trimming spaces.
2. It does not start or end with `/`.
3. It does not contain `//`.
4. It does not contain `..`.
5. It does not end with `.md`.

## Error Behavior

- Validation failures return non-zero exit code.
- `get` returns non-zero when a key is not found.
- `search` returns empty output with exit code `0` when no match exists.
- `search` returns non-zero when `--key-only` and `--content-only` are both provided.
- `search` returns non-zero when `--limit` is not greater than `0`.
- `set` returns non-zero unless exactly one content source is provided:
  - positional `value`
  - `--file`
  - `--stdin`
