diff --git a/AGENTS.md b/AGENTS.md index e949195..23732dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,223 +1,92 @@ -# AGENTS.md — open-attachments +# AGENTS.md — `@hasna/attachments` -AI agent reference guide for `@hasna/attachments` and its MCP server. +Agent-facing quick reference. Use the maintained references for complete +behavior: ---- +- [CLI](docs/cli.md) +- [MCP](docs/mcp.md) +- [HTTP APIs](docs/api.md) +- [Configuration and deployment](docs/configuration.md) -## MCP Setup - -Install all tools into Claude Code (user scope): +## Setup ```bash +npm install -g @hasna/attachments attachments mcp --claude -``` - -For Codex or Gemini: - -```bash attachments mcp --codex attachments mcp --gemini -attachments mcp --all # all three agents at once +attachments mcp --all attachments mcp --uninstall --all ``` ---- - -## Token Optimization — ATTACHMENTS_PROFILE - -Set `ATTACHMENTS_PROFILE` before starting the MCP server to control how many tools appear in `tools/list`. Lean stubs are always served; use `describe_tools` to get full schemas on demand. - -| Profile | Tools exposed | Best for | -|---------|--------------|----------| -| `minimal` | 3 | Agents that only upload/download | -| `standard` | 7 (default) | General-purpose agent workflows | -| `full` | 14 | Power users, auditing, batch ops | - -```bash -ATTACHMENTS_PROFILE=minimal attachments-mcp -ATTACHMENTS_PROFILE=full attachments-mcp -``` - ---- - -## Environment Variables - -| Variable | Default | Description | -|----------|---------|-------------| -| `ATTACHMENTS_PROFILE` | `standard` | Tool set: `minimal`, `standard`, or `full` | -| `ATTACHMENTS_TRACK_COSTS` | _(unset)_ | Set to `1` to log economy/cost data | -| `ATTACHMENTS_MAX_SIZE` | `10737418240` (10 GB) | Max upload size in bytes; returns 413 above this | -| `ATTACHMENTS_ECONOMY_URL` | `http://localhost:3460` | Economy service base URL | -| `HASNA_ATTACHMENTS_DB_PATH` | `~/.hasna/attachments/db.sqlite` | Override SQLite metadata DB path | - ---- - -## REST API - -Default port: **3459** -Default bind: `localhost` -Default public route: `/a/` - -```bash -attachments serve --port 3459 -``` - -For a shared redirect domain, use a path route such as `/a/*` for attachments -before the generic shortlinks route. Export a provider-neutral or Cloudflare plan -without credentials: - -```bash -attachments domain plan --format json -attachments domain plan --format cloudflare -attachments domain verify --format json -``` - ---- - -## All 14 MCP Tools - -Quick-reference table. Use `describe_tools` (with `tool_name`) to get the full JSON Schema for any tool. - -| Tool | Profile | Required params | Description | -|------|---------|-----------------|-------------| -| `upload_attachment` | minimal+ | `path` OR `url` | Upload a local file or URL → shareable link | -| `download_attachment` | minimal+ | `id_or_url` | Download attachment to local disk | -| `get_link` | minimal+ | `id` | Get (or regenerate) shareable link | -| `list_attachments` | standard+ | _(none)_ | List attachments; filter by `tag` | -| `delete_attachment` | standard+ | `id` | Delete attachment record from DB | -| `complete_task_with_files` | standard+ | `task_id`, `paths` | Upload files then complete a todos task with them as evidence | -| `save_session` | standard+ | `session_id` | Snapshot a session transcript → attachment link | -| `upload_attachments` | full | `paths` | Batch upload multiple files | -| `configure_s3` | full | `bucket`, `region`, `access_key`, `secret_key` | Save S3 credentials to config | -| `presign_upload` | full | `filename` | Generate a presigned PUT URL for direct client-to-S3 upload | -| `link_to_task` | full | `attachment_id`, `task_id` | Link an attachment to a todos task metadata | -| `check_attachment_health` | full | _(none)_ | Audit all links (expired / dead / healthy); `fix:true` to regenerate | -| `describe_tools` | standard+ | _(none)_ | Return full verbose JSON Schema for one or all tools | -| `search_tools` | standard+ | `query` | Search tool names by keyword | - -### Optional params common to several tools - -- `expiry` — link lifetime: `"30m"`, `"24h"`, `"7d"`, `"never"` -- `tag` — string tag attached to the attachment record -- `todos_url` — todos REST base URL (default `http://localhost:3000`) -- `sessions_url` — sessions REST base URL (default `http://localhost:3458`) - ---- - -## Tag Conventions - -Tags are free-form strings. Recommended conventions: - -| Tag format | Example | Purpose | -|------------|---------|---------| -| `task:ID` | `task:TASK-042` | Link evidence to a task | -| `session:ID` | `session:abc123` | Group attachments from a session | -| `project:NAME` | `project:alumia` | Namespace by project | -| `agent:NAME` | `agent:maximus` | Track which agent uploaded | - -CLI: `attachments upload file.png --tag task:TASK-001` -MCP: pass `tag: "task:TASK-001"` to any upload tool -REST: `?tag=task:TASK-001` on `GET /api/attachments` - ---- - -## Standard Agent Workflow - -### Session start - -``` -1. Run health check — verify S3 + DB are reachable - attachments health-check - -2. Recall any relevant attachments for this session - → list_attachments (tag: "session:") -``` +`attachments-mcp` defaults to Streamable HTTP at +`http://127.0.0.1:8850/mcp`. Use `--stdio` or `MCP_STDIO=1` for stdio clients. -### Upload evidence +## MCP Profiles -``` -3. Upload a file or screenshot - → upload_attachment { path: "/tmp/screenshot.png", tag: "task:TASK-007" } +`tools/list` returns lean schemas. Set `ATTACHMENTS_PROFILE` before startup. -4. Link it to the task (optional, or use complete_task_with_files) - → link_to_task { attachment_id: "att_xxx", task_id: "TASK-007" } -``` +| Profile | Count | Scope | +|---------|------:|-------| +| `minimal` | 3 | Upload, download, link retrieval | +| `standard` | 13 | Default transfer, task, report, context, and agent tools | +| `full` | 22 | Every MCP tool | -### Complete a task with evidence +Use `describe_tools` for verbose attachment-workflow schemas and `search_tools` +to find tools by name. The full list is in [docs/mcp.md](docs/mcp.md). -``` -5. Upload files and complete in one step - → complete_task_with_files { - task_id: "TASK-007", - paths: ["/tmp/report.pdf", "/tmp/screenshot.png"], - notes: "Implementation verified, tests passing" - } -``` +## Standard Workflow -### Snapshot session - -``` -6. Save session transcript as an attachment - → save_session { session_id: "", expiry: "7d", tag: "session:" } +```text +1. Upload evidence with upload_attachment or attachments upload. +2. Tag it with task:ID, session:ID, project:NAME, or agent:NAME. +3. Use link_to_task, or complete_task_with_files to upload and complete. +4. Use save_session when a transcript must be preserved. +5. Run check_attachment_health or attachments health-check periodically. ``` ---- +Todos integrations default to `http://localhost:3000`; sessions defaults to +`http://localhost:3458`. Supply `todos_url` or `sessions_url` to override them. -## Integration Commands (CLI) +## Storage -| Command | Description | -|---------|-------------| -| `attachments health-check` | Check S3 + DB connectivity and link health | -| `attachments watch` | Watch for todo tasks with attachment links and react | -| `attachments link-task ` | Link an attachment to a todos task | -| `attachments complete-task ` | Upload files and complete task with evidence | -| `attachments snapshot-session ` | Snapshot a session transcript → S3 | -| `attachments task-journal` | Append task activity to a running journal attachment | -| `attachments status` | Show DB stats, S3 config, and last few attachments | -| `attachments clean` | Remove expired DB records | -| `attachments whoami` | Show configured S3 identity and bucket | -| `attachments presign-upload ` | Generate a presigned PUT URL | +Fresh local installs use SQLite and local objects without S3 configuration. +With an S3 bucket and region, `auto` storage selects S3. The default link +preference is `presigned`, with automatic server-link fallback for local +objects, protected links, download limits, email gates, encryption, long +expiry, and non-expiring links. ---- +The `attachments-serve` binary is pure remote: it reads/writes Postgres and +object storage directly. There are no attachment `storage push`, `pull`, or +`sync` commands and no MCP storage-sync tools. -## Configuration - -Stored at `~/.hasna/attachments/config.json`. Set via CLI or MCP: +## Local Service ```bash -attachments config set --bucket my-bucket --region us-east-1 -attachments config show -attachments config test # verify S3 connectivity -``` - -SQLite database: `~/.hasna/attachments/db.sqlite` - ---- - -## Context Injection (System Prompt) - -Set `ATTACHMENTS_URL=http://localhost:3459` in your agent to enable automatic context injection. - -``` -GET http://localhost:3459/api/context -``` - -Returns a compact text summary for system prompts: -``` -Attachments: 42 total (39 active, 3 expired) -⚠ Expiring in 24h: 2 (report.pdf, data.csv) -Recent: report.pdf (att_abc), output.json (att_def) +attachments serve --port 3459 ``` -Or use the MCP tool `get_context` (standard profile) for the same result. +- Public health: `GET /api/health` +- Authenticated-when-configured API: `/api/*` +- Public shares: `/a/` +- Context: `GET /api/context` +- Report: `GET /api/report` ---- +Set `ATTACHMENTS_API_TOKEN` or `HASNA_ATTACHMENTS_API_TOKEN` to require bearer +or `X-API-Key` authentication for local `/api/*` routes other than health. -## Health & Reporting +## Useful Commands ```bash -GET http://localhost:3459/api/health # status + counts -GET http://localhost:3459/api/report # detailed activity -GET http://localhost:3459/api/context # system prompt text -attachments report [--days 7] # CLI equivalent -``` +attachments status +attachments whoami +attachments doctor +attachments list --tag task:TASK-042 +attachments complete-task TASK-042 --file report.pdf --notes "Verified" +attachments snapshot-session SESSION-ID --tag session:SESSION-ID +attachments health-check --fix +attachments report --days 7 --format markdown +``` + +Configuration lives at `~/.hasna/attachments/config.json`; SQLite defaults to +`~/.hasna/attachments/db.sqlite` and honors `HASNA_ATTACHMENTS_DB_PATH`. diff --git a/CLAUDE.md b/CLAUDE.md index 8b4600d..328b9bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,179 +1,56 @@ -# @hasna/attachments — Agent Guide +# `@hasna/attachments` — Claude Guide -WeTransfer for AI agents. Upload files, get shareable links, backed by your own S3 bucket (AWS, MinIO, Cloudflare R2, or any S3-compatible storage). +Start with [README.md](README.md). Detailed references: -- **CLI** — `attachments upload`, `download`, `list`, `delete`, `link`, `config`, `serve`, `mcp` -- **MCP server** — lean-stub tools for Claude Code, Codex, Gemini, and any MCP-compatible agent -- **REST API** — 8 endpoints via Hono, runs on port **3459** -- **TypeScript SDK** — `@hasna/attachments-sdk`, zero dependencies +- [CLI commands](docs/cli.md) +- [MCP server and tools](docs/mcp.md) +- [Local and hosted APIs](docs/api.md) +- [Configuration and deployment](docs/configuration.md) ---- - -## Key Commands +## Common Commands ```bash -# Install globally npm install -g @hasna/attachments -# Configure S3 (one time) -attachments config set --bucket my-bucket --region us-east-1 \ - --access-key AKIAIOSFODNN7EXAMPLE \ - --secret-key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - -# Upload a file — prints link to stdout -attachments upload ./report.pdf - -# Upload with expiry and tag -attachments upload ./output.json --expiry 30d --tag task-evidence - -# Download by ID -attachments download att_abc123 --out ./local-copy.pdf - -# List uploads -attachments list - -# Delete by ID -attachments delete att_abc123 - -# Get a fresh link for an existing upload -attachments link att_abc123 - -# Start REST API server (port 3459) +attachments upload ./report.pdf --tag task:TASK-042 --expiry 7d +attachments download att_xxx --output ./downloads/ +attachments list --tag task:TASK-042 +attachments link att_xxx --regenerate --expiry 24h attachments serve -# Start MCP server -attachments mcp -``` - -### Build & Test - -```bash -bun run build # Build all targets -bun run build:cli # Build CLI only -bun run test # Run test suite -bun run test:coverage # Run with coverage -bun run dashboard # Start dashboard dev server -``` - ---- - -## Standard Agent Workflow - -### Session Start - -```bash -mementos memory-inject --project open-attachments --format compact # Load context (compact = 60% token savings) +attachments complete-task TASK-042 --file ./report.pdf --notes "Complete" +attachments snapshot-session SESSION-ID --tag session:SESSION-ID +attachments health-check --fix ``` -### Uploading Evidence (during work) +## MCP ```bash -# Upload a file and get a shareable link -attachments upload ./output.json --tag "task-evidence" --expiry 30d - -# Link to your current todo task -attachments link-task +attachments mcp --claude +attachments-mcp # Streamable HTTP, 127.0.0.1:8850 +attachments-mcp --stdio # stdio transport ``` -### Session End (completing a task with evidence) +Profiles expose 3 (`minimal`), 13 (`standard`, default), or 22 (`full`) tools. +Set `ATTACHMENTS_PROFILE` before starting the server. -```bash -# Upload files AND complete the task in one command -attachments complete-task --file ./report.pdf --file ./output.json --notes "implementation complete" +## Runtime Model -# Archive the session transcript -attachments snapshot-session --tag "$(date +%Y-%m-%d)" -``` +- Local CLI/API: SQLite plus local or private S3 object storage. +- Hosted `attachments-serve`: direct Postgres plus S3-compatible storage. +- No local/cloud sync or cache mode. +- Default link preference: presigned; protected, constrained, local, long-lived, + and non-expiring links automatically use server-hosted routes. +- Local API: `/api`; hosted API: `/v1` with `/openapi.json`. -### Health Check (periodic maintenance) +## Development ```bash -# Check for expired attachment links -attachments health-check --fix -``` - -### Environment Variables - -- `ATTACHMENTS_PROFILE=minimal|standard|full` (default: `standard`, controls MCP tools exposed) -- `ATTACHMENTS_TRACK_COSTS=1` (enable @hasna/economy cost tracking) -- `ATTACHMENTS_ECONOMY_URL=http://localhost:3456` - -### Integration Points - -- **todos**: `complete-task` uploads files + marks task done with `attachment_ids` -- **sessions**: `snapshot-session` exports session transcript as shareable link -- **economy**: `ATTACHMENTS_TRACK_COSTS=1` logs upload costs to @hasna/economy -- **mementos**: tag uploads with project/session for organized storage -- **Tag conventions**: use `task:TASK-ID`, `session:SESSION-ID`, `project:NAME`, `agent:NAME` for cross-agent discovery (e.g. `attachments list --tag task:OPE-00123`) - ---- - -## MCP Tool Profiles - -| Tool | minimal | standard | full | -|------|---------|----------|------| -| `upload_attachment` | yes | yes | yes | -| `download_attachment` | yes | yes | yes | -| `list_attachments` | — | yes | yes | -| `delete_attachment` | — | yes | yes | -| `get_link` | — | yes | yes | -| `configure_s3` | — | — | yes | -| `search_tools` | — | yes | yes | -| `describe_tools` | — | yes | yes | - -Set profile via `ATTACHMENTS_PROFILE` env var before starting the MCP server. - ---- - -## REST API (port 3459) - -| Method | Path | Description | -|--------|------|-------------| -| `POST` | `/upload` | Upload a file | -| `GET` | `/download/:id` | Download by attachment ID | -| `GET` | `/list` | List all attachments | -| `DELETE` | `/:id` | Delete by ID | -| `GET` | `/link/:id` | Get a fresh presigned link | -| `GET` | `/health` | Health check | -| `POST` | `/configure` | Update S3 config at runtime | -| `GET` | `/config` | View current config (keys redacted) | - -Start with: `attachments serve` or `bun run src/cli/index.ts serve` - ---- - -## Project Structure - +bun install --frozen-lockfile +bun run typecheck +bun run test +bun run build ``` -src/ - cli/ — CLI entry point (commander) - mcp/ — MCP server (stub tools) - api/ — Hono REST API - s3/ — S3 client wrapper - config/ — Config file management (~/.hasna/attachments/config.json) - types/ — Shared TypeScript types -sdk/ — @hasna/attachments-sdk (standalone, zero-dep) -dashboard/ — Web UI (Bun + Vite) -scripts/ — test.sh and utility scripts -dist/ — Build output -``` - ---- - -## Config File -Stored at `~/.hasna/attachments/config.json`. Existing `~/.open-attachments/` -or `~/.attachments/` data is copied forward on first use. Managed via -`attachments config set/get/reset`. - -```json -{ - "bucket": "my-bucket", - "region": "us-east-1", - "accessKey": "AKIA...", - "secretKey": "...", - "endpoint": "https://s3.amazonaws.com", - "linkMode": "presigned", - "defaultExpiry": "7d" -} -``` +Do not edit `src/generated/storage-kit/` manually. Regenerate it through +`@hasna/contracts` as described in its generated README. diff --git a/README.md b/README.md index 96f6566..6734f51 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # @hasna/attachments -Open-source attachment transfer for agents and teams — local or private S3 storage, app-hosted share links, CLI + MCP + REST API. +Open-source attachment transfer for agents and teams with local or private S3 +object storage, share links, a CLI, an MCP server, and local and hosted REST +APIs. [![npm](https://img.shields.io/npm/v/@hasna/attachments)](https://www.npmjs.com/package/@hasna/attachments) [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE) @@ -11,115 +13,195 @@ Open-source attachment transfer for agents and teams — local or private S3 sto npm install -g @hasna/attachments ``` -## CLI Usage +The package installs three binaries: + +- `attachments` — local-first CLI and REST server +- `attachments-mcp` — MCP server over Streamable HTTP or stdio +- `attachments-serve` — self-hosted Postgres + S3 service + +## Quick Start + +Fresh installs need no S3 configuration. Files are stored under +`~/.hasna/attachments/objects`, metadata is stored in SQLite, and public links +use the local server. ```bash -attachments --help attachments upload report.pdf -attachments upload report.pdf --expiry 24h --password "$ATTACHMENT_PASSWORD" -attachments upload archive.zip --encrypt --password "$ATTACHMENT_PASSWORD" --max-downloads 1 -attachments serve --host 0.0.0.0 --port 3459 +attachments list +attachments serve +``` + +The server listens on `http://localhost:3459` by default. Open the URL returned +by `attachments upload`, or download by attachment ID: + +```bash +attachments download att_xxx --output ./downloads/ ``` -Fresh installs work without S3. Objects are stored under -`~/.hasna/attachments/objects`, metadata is stored in local SQLite, and share -links are app-hosted URLs such as `http://localhost:3459/a/`. +## CLI + +Run `attachments --help` for complete options. -For hosted deployments, keep the bucket private and let the app serve public -download pages and byte streams from `/a/`. Direct presigned S3 links -remain available for explicit admin workflows, but server links are the default. +| Area | Commands | +|------|----------| +| Transfer | `upload`, `download`, `list`, `delete`, `remove`, `link` | +| Direct S3 upload | `presign-upload`, `presign-complete` | +| Configuration | `config show`, `config set`, `config test`, `domain` | +| Service and diagnostics | `serve`, `status`, `whoami`, `doctor`, `health-check`, `clean`, `report` | +| Agent integrations | `mcp`, `init`, `heartbeat`, `focus`, `link-task`, `complete-task`, `resolve-evidence`, `snapshot-session`, `task-journal`, `watch` | -## Storage +Common upload controls include expiry, tags, passwords, encrypted storage, +download limits, email gates, JSON output, stdin input, and internal-network +links: + +```bash +attachments upload report.pdf --expiry 24h --tag task:TASK-042 +attachments upload archive.zip --password "$ATTACHMENT_PASSWORD" --encrypt --max-downloads 1 +printf 'hello\n' | attachments upload --stdin --filename greeting.txt --format json +``` + +## Storage and Links + +Storage defaults to `auto`: it uses S3 when a bucket and region are configured, +and local object storage otherwise. ```bash attachments config set --storage-backend local attachments config set --storage-backend s3 --bucket my-bucket --region us-east-1 -attachments config set --max-size 10737418240 # 10 GB +attachments config set --max-size 10737418240 # 10 GiB ``` -`--storage-backend auto` uses S3 when S3 credentials are configured and falls -back to local object storage otherwise. +The configured default link type is `presigned`. A request automatically uses +an app-hosted server link when a presigned S3 URL cannot represent it, including +local storage, expiry beyond seven days, non-expiring links, passwords, +encryption, download limits, and email gates. The S3 bucket can remain private +in both modes. + +Use `--link-type presigned|server` on uploads, or change the default: + +```bash +attachments config set --link-type server --expiry 7d +``` + +## Local REST API + +`attachments serve` starts the local SQLite-backed API on port `3459`. +`GET /api/health` is public. Other `/api/*` routes require a bearer token only +when `ATTACHMENTS_API_TOKEN` or `HASNA_ATTACHMENTS_API_TOKEN` is set. + +| Surface | Path | +|---------|------| +| Attachments API | `/api/attachments` | +| Health, context, report | `/api/health`, `/api/context`, `/api/report` | +| Public share page | `/a/` | +| Legacy public download | `/d/` | + +The attachment API supports JSON/base64, raw-body, multipart form, multipart +S3 upload, presigned upload completion, listing, metadata, deletion, downloads, +and link regeneration. + +## Self-Hosted Service + +`attachments-serve` is the hosted API. It reads and writes Postgres and object +storage directly; there is no local/cloud sync engine or cache mode. + +```bash +export HASNA_ATTACHMENTS_STORAGE_MODE=cloud +export HASNA_ATTACHMENTS_DATABASE_URL=postgres://... +export HASNA_ATTACHMENTS_API_SIGNING_KEY=replace-me +export ATTACHMENTS_S3_BUCKET=my-bucket +export ATTACHMENTS_PUBLIC_BASE_URL=https://files.example.com + +attachments-serve +``` + +It runs migrations before serving unless `--no-migrate` or +`ATTACHMENTS_SKIP_MIGRATE=1` is set. Use `attachments-serve migrate` for a +one-shot migration. The hosted surface exposes public `/health`, `/ready`, +`/version`, and `/openapi.json` endpoints plus the authenticated `/v1` API. ## Public Domains -Domain support is declarative and does not depend on `@hasna/domains` at -runtime. Configure a public base URL and export a DNS plan for manual, -Cloudflare, OpenDomains, or other automation. +Domain support is declarative and does not mutate DNS. Configure public routing, +export a credential-free plan, then probe the deployed attachment prefix: ```bash attachments domain configure \ --hostname files.example.com \ - --base-url https://files.example.com \ - --path-prefix /a \ - --provider cloudflare \ --attachments-origin https://attachments-origin.example.com \ --fallback-origin https://shortlinks-origin.example.com \ - --zone example.com \ - --record CNAME \ - --name files \ - --target attachments.example.net \ - --proxied - -attachments domain plan --format json -attachments domain plan --format opendomains + --provider cloudflare + attachments domain plan --format cloudflare attachments domain verify --format json ``` -The generated plan contains no credentials and does not mutate DNS. For shared -domains, route the attachment prefix before any generic redirect/shortlink -route; for example, `files.example.com/a/*` should target the attachments app -and `files.example.com/*` can remain pointed at an existing shortlink service. -`attachments domain verify` probes the configured `.../a/__attachments_probe__` -URL and fails if the prefix is still handled by a shortlink route. +On a shared hostname, route `/a/*` to attachments before a generic `/*` +shortlink or redirect route. ## MCP Server +`attachments-mcp` defaults to Streamable HTTP on `127.0.0.1:8850`. Use `--stdio` +for clients that launch one MCP process per session. + ```bash -attachments-mcp +attachments-mcp # http://127.0.0.1:8850/mcp +attachments-mcp --port 9000 # HTTP port override +attachments-mcp --stdio # stdio transport +MCP_STDIO=1 attachments-mcp # stdio transport via environment ``` -## HTTP mode +- Health: `GET http://127.0.0.1:8850/health` +- MCP: `http://127.0.0.1:8850/mcp` +- Port environment override: `MCP_HTTP_PORT` + +`ATTACHMENTS_PROFILE` controls the lean schemas returned by `tools/list`: + +| Profile | Tools | Intended use | +|---------|------:|--------------| +| `minimal` | 3 | Upload, download, and link retrieval | +| `standard` | 13 | Default transfer, reporting, task, context, and agent workflow | +| `full` | 22 | Every tool, including S3 setup, presigned upload, health, search, and feedback | -Run a long-lived Streamable HTTP MCP server on `127.0.0.1` (default port **8800**): +Install MCP configuration for supported clients with: ```bash -attachments-mcp --http -# or: MCP_HTTP=1 attachments-mcp -# port override: --port 8800 or MCP_HTTP_PORT=8800 +attachments mcp --claude +attachments mcp --codex +attachments mcp --gemini +attachments mcp --all +attachments mcp --uninstall --all ``` -- Health: `GET http://127.0.0.1:8800/health` → `{"status":"ok","name":"attachments"}` -- MCP: `http://127.0.0.1:8800/mcp` +## Configuration and Data -Stdio remains the default when no `--http` / `MCP_HTTP=1` is set. +Configuration is stored in `~/.hasna/attachments/config.json`; secrets are +masked by `attachments config show`. SQLite metadata defaults to +`~/.hasna/attachments/db.sqlite` and can be overridden with +`HASNA_ATTACHMENTS_DB_PATH`. -## Storage Sync +On first use, missing files from legacy `~/.open-attachments/` and +`~/.attachments/` directories are copied into the canonical directory without +overwriting existing files. -This package supports local SQLite by default. For cloud storage, point the -attachments service at its repo-owned AWS Postgres/RDS database. +## Development -```bash -export HASNA_ATTACHMENTS_DATABASE_URL=postgres://... -export HASNA_ATTACHMENTS_STORAGE_MODE=cloud # local | cloud (AWS) +Requires Bun. -attachments storage status -attachments storage push -attachments storage pull -attachments storage sync +```bash +bun install --frozen-lockfile +bun run typecheck +bun run test +bun run build ``` -MCP exposes the same flow through `storage_status`, `storage_push`, -`storage_pull`, and `storage_sync`. - -## Data Directory +## Documentation -Data is stored in `~/.hasna/attachments/`. Local object storage defaults to -`~/.hasna/attachments/objects`. On first run, missing files from legacy -`~/.open-attachments/` and `~/.attachments/` directories are copied into the -canonical directory without overwriting existing canonical files. Override the -SQLite metadata path with `HASNA_ATTACHMENTS_DB_PATH=/path/to/db.sqlite`. +- [CLI reference](docs/cli.md) +- [MCP reference](docs/mcp.md) +- [HTTP API reference](docs/api.md) +- [Configuration and deployment](docs/configuration.md) ## License -Apache-2.0 -- see [LICENSE](LICENSE) +Apache-2.0 — see [LICENSE](LICENSE). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..73b7b2a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,19 @@ +# Attachments Documentation + +The root [README](../README.md) is the quick start. These pages document the +current executable surfaces in more detail. + +- [CLI reference](cli.md) — commands, arguments, options, and integrations +- [MCP reference](mcp.md) — transports, profiles, and all 22 tools +- [HTTP API reference](api.md) — local and self-hosted APIs +- [Configuration and deployment](configuration.md) — storage, links, + environment variables, domains, and hosted operation + +The package has two HTTP applications with intentionally different contracts: + +- `attachments serve` is the local-first SQLite API under `/api`. +- `attachments-serve` is the self-hosted Postgres API under `/v1` and publishes + its OpenAPI document at `/openapi.json`. + +There is no local/cloud synchronization workflow. Local mode uses SQLite; +self-hosted cloud mode reads and writes Postgres directly. diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..11febb6 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,80 @@ +# HTTP API Reference + +Attachments ships two Hono applications with different persistence and route +contracts. + +## Local API: `attachments serve` + +The local API uses the selected local/S3 object store and SQLite metadata. It +listens on `localhost:3459` by default. Authentication is disabled unless +`ATTACHMENTS_API_TOKEN` or `HASNA_ATTACHMENTS_API_TOKEN` is set. When set, send +`Authorization: Bearer ` or `X-API-Key: `. `/api/health` stays +public. + +### System Routes + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/api/health` | Storage status and attachment counts | +| `GET` | `/api/deployment` | Current public routing plan | +| `GET` | `/api/context` | Compact text or JSON context | +| `GET` | `/api/report` | Detailed activity report | + +### Attachment Routes + +| Method | Path | Description | +|--------|------|-------------| +| `POST` | `/api/attachments` | JSON/base64 attachment upload | +| `PUT` | `/api/attachments` | Raw request-body upload | +| `GET` | `/api/attachments` | List, filter, or project fields | +| `GET` | `/api/attachments/:id` | Get metadata | +| `DELETE` | `/api/attachments/:id` | Delete bytes and metadata | +| `GET` | `/api/attachments/:id/download` | Stream bytes | +| `GET` | `/api/attachments/:id/link` | Get current link | +| `POST` | `/api/attachments/:id/link` | Regenerate link | +| `POST` | `/api/attachments/multipart` | Begin multipart S3 upload | +| `POST` | `/api/attachments/:id/multipart/part` | Presign a multipart part | +| `POST` | `/api/attachments/:id/multipart/complete` | Complete multipart upload | +| `POST` | `/api/attachments/:id/multipart/abort` | Abort multipart upload | +| `POST` | `/api/attachments/presign-upload` | Begin direct S3 upload | +| `POST` | `/api/attachments/:id/presign-upload/complete` | Finalize direct upload | + +Uploads enforce `ATTACHMENTS_MAX_SIZE` when set, otherwise the configured +10-GiB default. Oversized requests return `413`. + +### Public Routes + +`/a/:token` renders a share/download page. `/a/:token/download` accepts GET, +HEAD, and password-form POST. Email gates use `/a/:token/request-access`. The +configured public path is registered in addition to `/a`. `/d/:id` supports +legacy public downloads. + +Unencrypted downloads support byte ranges. HEAD and unconfirmed GET probes do +not consume limited-use links. + +## Self-Hosted API: `attachments-serve` + +The hosted service reads/writes Postgres directly and stores bytes in +S3-compatible storage. It does not use local SQLite. + +| Method | Path | Authentication | +|--------|------|----------------| +| `GET` | `/health` | Public liveness/database probe | +| `GET` | `/ready` | Public migration readiness probe | +| `GET` | `/version` | Public version and mode | +| `GET` | `/openapi.json` | Public OpenAPI 3.1 document | +| `GET`, `POST` | `/v1/attachments` | API key | +| `GET`, `DELETE` | `/v1/attachments/:id` | API key | +| `GET` | `/v1/attachments/:id/download` | API key | +| `GET`, `POST` | `/v1/attachments/:id/link` | API key | +| `POST` | `/v1/feedback` | API key | + +Create accepts JSON/base64, multipart form data, or raw bytes. Signed API keys +are checked for app, scope, expiry, and revocation; read-only keys cannot write. + +The hosted service also registers public share routes at the configured path. +Email-gated links fail closed there because the cloud public-route +implementation does not configure email delivery. + +Use `/openapi.json` as the authoritative machine-readable `/v1` contract and to +regenerate the TypeScript SDK. diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 0000000..f91b0f6 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,127 @@ +# CLI Reference + +The `attachments` binary uses Commander. Run `attachments --help` or +`attachments --help` for the installed version's generated help. + +## Transfer Commands + +### `upload [files...]` + +Uploads paths, or one stdin file with `--stdin --filename `. + +| Option | Behavior | +|--------|----------| +| `--expiry