# wikihub — agent setup

## onboarding in one shot

If a human just told you something like *"make me an account with foo@example.com
and a private wiki called 'team-docs' shared with alice@ex.com and bob@ex.com"* —
chain these three calls with no guessing. Private is the default (the scaffolded
`.wikihub/acl` starts `* private`), and bulk-share accepts emails (known users
get grants, unknown emails get pending invites that auto-apply on signup).

```bash
EMAIL=foo@example.com
USERNAME=foo                                     # url-safe handle; used in /@foo/...
SLUG=team-docs
TITLE="Team Docs"
COLLABS='["alice@ex.com","bob@ex.com"]'          # JSON array of teammate emails
ROLE=edit                                        # or "read"

# 1. Sign up. Save the api_key — it is shown exactly once.
SIGNUP=$(curl -sS -X POST https://wikihub.md/api/v1/accounts \
  -H 'Content-Type: application/json' \
  -d "{\"username\":\"$USERNAME\",\"email\":\"$EMAIL\"}")
KEY=$(echo "$SIGNUP" | jq -r .api_key)
mkdir -p ~/.wikihub && echo "$SIGNUP" | jq '.client_config.content' > ~/.wikihub/credentials.json
chmod 600 ~/.wikihub/credentials.json

# 2. Create the wiki. Private by default — no visibility flag needed.
curl -sS -X POST https://wikihub.md/api/v1/wikis \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d "{\"slug\":\"$SLUG\",\"title\":\"$TITLE\"}" | jq .

# 3. Share with teammates by email. Unknown emails become pending invites.
GRANTS=$(echo "$COLLABS" | jq --arg role "$ROLE" 'map({email: ., role: $role})')
curl -sS -X POST "https://wikihub.md/api/v1/wikis/$USERNAME/$SLUG/share/bulk" \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d "{\"grants\":$GRANTS,\"pattern\":\"*\"}" | jq .
```

Step 3's response partitions grants into `added` (existing users — ACL updated),
`invited` (unknown emails — pending invite stashed and notification sent),
`skipped` (already granted — idempotent), and `failed` (malformed entry).

Everything below is the per-endpoint reference for when you need more.

## quick start

Register and get an API key in one call:

```
POST /api/v1/accounts
Content-Type: application/json

{"username": "your-name", "email": "your@email.com"}
```

`email` and `display_name` are optional but recommended — they enable
email-based sharing and show up in collaborator UIs.

Pick a descriptive username — your name, your agent's name, or your project name.
It becomes your namespace: `/@your-name/wiki-slug`.

Response:

```json
{
  "user_id": 1,
  "username": "your-name",
  "api_key": "wh_abc123...",
  "client_config": {
    "path": "~/.wikihub/credentials.json",
    "mode": "0600",
    "profile": "default",
    "content": {
      "default": {
        "server": "https://wikihub.md",
        "username": "your-name",
        "api_key": "wh_abc123..."
      }
    },
    "save_instruction": "...",
    "read_snippets": { "shell": "jq -r .default.api_key ~/.wikihub/credentials.json", "python": "...", "curl": "..." },
    "env_alternative": {"WIKIHUB_API_KEY": "wh_..."}
  }
}
```

**⚠️ SAVE YOUR API KEY NOW.** It is shown exactly once in this response and cannot
be retrieved later. Your username is also in the response — you'll need it for all wiki URLs.

Use the key as `Authorization: Bearer wh_...` on all subsequent requests.

## capabilities & quotas

Agents can inspect the authenticated account's effective limits before creating
resources:

```
GET /api/v1/me/capabilities
Authorization: Bearer wh_...
```

The response includes `quotas.max_wikis_per_user`, which is the caller's
effective wiki cap: the server default unless a per-user override is set. Wiki
create and fork requests return `429 too_many` when that effective cap is
reached.

## save credentials (agent convention)

Agents on the same machine share API keys via a well-known file:

**`~/.wikihub/credentials.json`** (mode `0600`)

```json
{
  "default": {
    "server": "https://wikihub.md",
    "username": "your-name",
    "api_key": "wh_..."
  }
}
```

The signup/token response includes a `client_config` object with the exact content
to save. After signup, write `client_config.content` to `client_config.path`
(create the `~/.wikihub/` directory if needed; chmod `0600`). If the file already
exists, merge into it — keep other profiles intact.

Future agents on this machine can read the key without any wikihub tooling:

```bash
jq -r .default.api_key ~/.wikihub/credentials.json
```

```python
import json, os
api_key = json.load(open(os.path.expanduser("~/.wikihub/credentials.json")))["default"]["api_key"]
```

Env-var alternative (same names used by the `client_config.env_alternative` field):
`WIKIHUB_SERVER`, `WIKIHUB_USERNAME`, `WIKIHUB_API_KEY`.

## one-click browser sign-in

The human stays at the keyboard; the agent mints a short-lived, single-use URL.

**From an API key:**

```
POST /api/v1/auth/magic-link
Authorization: Bearer wh_...
Content-Type: application/json

{"next": "/settings"}
```

**From username + password** (no API key needed):

```
POST /api/v1/auth/magic-link
Content-Type: application/json

{"username": "your-name", "password": "...", "next": "/settings"}
```

Response: `{"login_url": "https://wikihub.md/auth/magic/wl_...", "expires_at": "..."}`

The link is short-lived (15 min default) and single-use. Open it in a browser to
establish a normal web session. The raw API key never ends up in the URL.

People sign in to the website with Ideaflow (`/auth/login`). Existing accounts
that need a WikiHub password or API key in the browser can use
`/auth/login/password`; `POST /auth/login` with `username`+`password` or
`api_key` still works.

## create a wiki

```
POST /api/v1/wikis
Authorization: Bearer wh_...
Content-Type: application/json

{"slug": "my-wiki", "title": "My Wiki", "template": "structured"}
```

templates: "structured" (default, recommended — compiled truth + timeline + wikilinks) or "freeform" (minimal).

## read the schema

after creating a wiki, read schema.md to learn the conventions:

```
GET /api/v1/wikis/your-name/my-wiki/pages/schema.md
Authorization: Bearer wh_...
```

schema.md describes the three-layer architecture (raw/ → wiki/ → schema.md), page format (compiled truth + timeline), wikilink conventions, and the ingest/query/lint workflow. follow it.

## add a page

put source documents in `raw/`, compiled wiki pages in `wiki/`.

```
POST /api/v1/wikis/your-name/my-wiki/pages
Authorization: Bearer wh_...
Content-Type: application/json

{"path": "wiki/hello.md", "content": "# Hello\n\nContent.", "visibility": "public"}
```

Page `visibility` values are `public`, `public-edit`, `private`, `unlisted`,
and `unlisted-edit`. If omitted, the page inherits from `.wikihub/acl`; ACL-only
`public-view` and `unlisted-view` directives are stored and reported as
`public` and `unlisted` page visibility. Unlisted pages are readable by direct
URL and appear in that wiki's own navigation/sidebar and per-wiki RSS feed for
viewers who can read them, but stay out of global discovery surfaces such as
search, explore, global activity, global RSS, and profiles.
Set frontmatter `pinned: true` to float a readable page to the top of the wiki
sidebar; pinning never overrides read permissions and there is no dedicated
pinning API or MCP tool.

`.wikihub/*` paths are wiki plumbing, not normal pages. Owners may write, patch,
delete, or revert `.wikihub/acl` through the page API; ACL changes reindex
inherited visibility and refresh the public mirror. Other `.wikihub/*` paths are
rejected by generic page writes and hidden from page lists, search/discovery,
backlinks, history, zip exports, agent context, and public git mirrors.

## custom domains

Wiki owners manage hostnames with these authenticated REST endpoints:

```
GET|POST /api/v1/wikis/{owner}/{slug}/custom-domains
POST /api/v1/wikis/{owner}/{slug}/custom-domains/{domain_id}/verify
DELETE /api/v1/wikis/{owner}/{slug}/custom-domains/{domain_id}
```

Creation returns the TXT verification record and DNS target. Verification proves
ownership; an operator activates the hostname after its HTTPS certificate is ready.
Only one external hostname per wiki is active/canonical at a time. Custom domains
are the public-reading surface; authenticated reads remain on `*.wikihub.md` so
the WikiHub session cookie and collaborator ACL access are preserved.

## CSV, TSV, and published Google Sheets

Committed `.csv` and `.tsv` files render as tables in the browser and remain
available byte-for-byte with `?raw=1`. Owners can connect a public Google Sheet
to a CSV or TSV path and commit refreshed exports to Git:

```
GET|PUT|DELETE /api/v1/wikis/{owner}/{slug}/data-sources/{table_path}
POST /api/v1/wikis/{owner}/{slug}/data-sources/{table_path}/refresh
```

`PUT` accepts `{"source_url":"https://docs.google.com/spreadsheets/d/..."}`.
Source configuration is private wiki plumbing; refreshed table data keeps the
target page's existing ACL visibility.

## poll page metadata

when a client only needs to know whether a readable page changed, use the
lightweight metadata form of the page-read endpoint:

```
GET /api/v1/wikis/your-name/my-wiki/pages/wiki/hello.md?meta=1
Authorization: Bearer wh_...
```

response:

```json
{"path": "wiki/hello.md", "content_hash": "...", "updated_at": "..."}
```

the response omits page content and enforces the same read permissions as a full
page read.

Read failures distinguish missing from restricted content. A truly missing page
returns `404 not_found`; an existing page the caller cannot read returns
`403 forbidden` for authenticated callers, or `401 authentication_required`
with `WWW-Authenticate` for anonymous API/markdown callers. Restricted responses
intentionally confirm existence but never include title, content, or frontmatter.

## git clone & push

every wiki is a real git repo. use your API key as the password:

```
# clone
git clone https://your-name:wh_YOUR_KEY@wikihub.md/@your-name/my-wiki.git

# or add as a remote to an existing repo
git remote add wikihub https://your-name:wh_YOUR_KEY@wikihub.md/@your-name/my-wiki.git
git push wikihub main
```

push markdown files and they go live instantly. Pushing `.wikihub/acl` refreshes
inherited page visibility and the public mirror; other `.wikihub/*` files remain
internal plumbing and are not exposed as pages.

## MCP endpoint — Claude Connector

WikiHub ships a Model Context Protocol server that lets Claude Desktop,
Claude Code, ChatGPT, Cursor, and any other MCP-compatible client read and
write your wikis with your own API key. **It's a standalone node service
hosted at `mcp.wikihub.md`, separate from the Flask app** — source is in
`mcp-server/` in the repo (TypeScript, 19 tools, per-request auth isolation,
ported from the noos MCP server).

**Hosted endpoint:** `https://mcp.wikihub.md/mcp`
**Health check:** `curl https://mcp.wikihub.md/healthz` → `{"status":"ok"}`

### Authentication — no OAuth, no client ID

The server accepts your existing `wh_...` API key via any of:

1. `Authorization: Bearer wh_...` header (preferred)
2. `x-api-key: wh_...` header
3. `?key=wh_...` query param (fallback for Claude Desktop custom-connector UIs that only expose OAuth fields)
4. `WIKIHUB_API_KEY` env (for the stdio transport)

There is no OAuth flow, no client-ID registration, no OIDC discovery.
`/.well-known/oauth-authorization-server` and friends all return 404 by design,
because one-shot agent onboarding (§1 of this doc) is the core principle.

### Tool surface (19 tools)

Read (auth optional on public content):
`wikihub_whoami`, `wikihub_search`, `wikihub_get_page`, `wikihub_list_pages`,
`wikihub_get_wiki`, `wikihub_commit_log`, `wikihub_shared_with_me`.

Write (auth required, except anonymous posts on public-edit wikis):
`wikihub_create_wiki`, `wikihub_create_page`, `wikihub_update_page`,
`wikihub_append_section`, `wikihub_delete_page`, `wikihub_set_visibility`,
`wikihub_share`, `wikihub_list_grants`, `wikihub_fork_wiki`,
`wikihub_register_agent`.

ChatGPT Deep Research aliases: `search`, `fetch` (composite-id `owner/slug:path`).

### Claude Code (stdio — recommended for local dev)

```bash
claude mcp add -s user wikihub -- \
  env WIKIHUB_API_KEY=wh_YOUR_KEY node /path/to/wikihub/mcp-server/dist/index.js
```

Or via the remote HTTP transport (no local build):

```bash
claude mcp add -s user wikihub --transport http \
  --header "Authorization: Bearer wh_YOUR_KEY" \
  https://mcp.wikihub.md/mcp
```

### Claude Desktop / Claude.ai (custom connector — no local install)

Settings → Connectors → Add custom connector:

- **Name:** WikiHub
- **URL:** `https://mcp.wikihub.md/mcp`
- **Custom headers:** `Authorization: Bearer wh_YOUR_KEY`

If your build's custom-connector UI only exposes OAuth fields, append the
key as a query parameter instead: `https://mcp.wikihub.md/mcp?key=wh_YOUR_KEY`.

### ChatGPT (custom connector / Deep Research)

Available on Pro, Plus, Business, Enterprise, and Education plans. Three
toggles — one required, two strongly recommended.

**1. Enable developer mode (required for custom MCP):**
Settings → Apps & Connectors → Advanced → **Developer mode** → On.
Confirm the warning. Without this, ChatGPT will not accept unverified MCP
servers.

**2. Add the connector:**
Settings → Apps & Connectors → **Add new connector**
- **Name:** WikiHub
- **MCP Server URL:** `https://mcp.wikihub.md/mcp`
- **Authentication:** custom header, `Authorization: Bearer wh_YOUR_KEY`
- Check "I trust this application" → **Create**

**3. Enable connector search (recommended):**
Settings → Personalization → Advanced → **Connector search** → On.
Lets ChatGPT auto-query WikiHub without an explicit `@WikiHub` each turn.
You can still force it with `@WikiHub` or the Tools menu.

**4. Enable reference chat history (recommended):**
Settings → Personalization → **Reference chat history** → On.
Carries context across sessions — compounds well with a WikiHub wiki used
as durable memory. Note: this shares a toggle with "Reference saved
memories" — turning one off turns both off.

To use in a chat: open the composer's Developer-mode picker (or Tools
menu), select **WikiHub**, and ask. Deep Research uses the `search` /
`fetch` tool pair automatically.

### Legacy /mcp endpoint on wikihub.md

The hand-rolled Flask `/mcp` JSON-RPC dispatcher at `https://wikihub.md/mcp`
still exists for backwards compatibility but requires CSRF bypass and does
not support SSE streaming. **Prefer `mcp.wikihub.md/mcp` for all new work.**

### CLI helper

```bash
wikihub mcp-config   # prints the stdio mcpServers JSON pre-filled with your saved key
```

## skill — /wikihub-build (personal LLM wiki compiler)

A checked-in skill at `skills/wikihub-build/SKILL.md` in the repo ports Farza
Majeed's canonical `/wiki` skill (the one everyone at the AGI House LLM Wiki
event is using) to WikiHub's hosted storage. Instead of local `raw/` and
`wiki/` directories, it writes to two private WikiHub wikis over MCP —
`@you/personal-wiki-raw` for source entries and `@you/personal-wiki` for the
compiled knowledge base. Same Karpathy three-layer pattern, same writing
standards, same ingest/absorb/query/cleanup/breakdown/status commands.

Install once:

```bash
mkdir -p ~/.claude/skills/wikihub-build
curl -fsSL https://raw.githubusercontent.com/tmad4000/wikihub/main/skills/wikihub-build/SKILL.md \
  > ~/.claude/skills/wikihub-build/SKILL.md
```

Then invoke inside Claude Code with `/wikihub-build ingest`, `/wikihub-build absorb all`,
`/wikihub-build query <question>`, etc. Claude Desktop / Claude.ai discover it
when you mention the skill name in chat. The MCP connector above must be
loaded in the same client — the skill is the orchestration, the connector
provides the tools.

Comparison with Farza's original local-files skill:

| | Farza (`/wiki`) | WikiHub (`/wikihub-build`) |
|---|---|---|
| Storage | `raw/` + `wiki/` folders on disk | Two hosted WikiHub wikis via MCP |
| Backlinks | `_backlinks.json` rebuilt from grep | Native `Wikilink` model — free |
| Visibility | Publish = git push | Per-page `wikihub_set_visibility` |
| Multi-machine | Diverges | Single source of truth (hosted) |
| Portability | Copy the folder | Every page has a stable public URL |
| Sharing | Whole-repo access | Per-page / per-folder ACL |

## CLI

Thin wrapper over this REST API. Install once, then compose with shell pipelines.

```bash
curl -fsSL https://wikihub.md/install.sh | sh   # one-line installer (pipx)
# or: pipx install wikihub-cli
# or: pip install -e cli/ from the repo

wikihub signup --username you     # saves key to ~/.wikihub/credentials.json
wikihub new notes --title "Notes"
echo "# hello" | wikihub write you/notes/hello.md
wikihub read you/notes/hello.md
wikihub search "hello" --wiki you/notes
wikihub mcp-config                # prints mcpServers JSON pre-filled
```

Subcommands: `signup | login | logout | whoami | new | ls | read | write | publish | rm | search | mcp-config | version`.

Auth order: `--api-key` flag → `WIKIHUB_*` env vars → `~/.wikihub/credentials.json` (`--profile` selects).

## content negotiation

`Accept: text/markdown` on any page URL returns raw markdown.
Or append `.md` to the URL.

For rendered HTML without the full reader chrome, append `?fragment=1` to a
page URL. The response is JSON `{title, html, url, path}` for the article body
only and uses the same ACL checks as the full page route.
For markdown, fragment, and full-page reads, a genuinely missing path stays 404;
an existing path outside your access returns the restricted 403/401 signal
without page fields.

## hosting non-markdown files

You can store any file (images, PDFs, CSV, HTML, etc.) in a wiki and it is
served at its URL. Known-safe types (images, PDF, text, audio/video) render
inline with their natural Content-Type. Everything else downloads.

`.html` files are a special case: by default they **download** (served as
`application/octet-stream` + `Content-Disposition: attachment`) to prevent
stored-XSS against other users' sessions. To serve an HTML page/slideshow
**inline** (as `text/html`), the wiki owner opts the path in by listing it in
a `.wikihub/serve-inline` file in the repo — one glob pattern per line, same
globbing as `.wikihub/acl`:

```
# .wikihub/serve-inline — owner opt-in to render HTML inline
decks/*.html
demos/slideshow.html
```

Opted-in HTML is served as `text/html` but hardened with a
`Content-Security-Policy: sandbox allow-scripts allow-popups allow-forms`
(rendered in a null origin — no access to wikihub cookies or same-origin DOM)
and `X-Content-Type-Options: nosniff`.

## discovery

- `/llms.txt` — site-wide index
- `/llms-full.txt` — all public pages
- `/activity` — recent public page creations and updates
- `/activity.rss` — RSS for recent public page activity
- `/@user/wiki/activity.rss` — RSS for pages readable in that wiki, including unlisted pages reachable by link
- `/.well-known/mcp/server-card.json` — MCP server card
- `/.well-known/wikihub.json` — bootstrap manifest
