Metadata-Version: 2.4
Name: sprucelab
Version: 0.1.0
Summary: Agent-first surface for Sprucelab — typed CLI + MCP server over the same public HTTP API.
Project-URL: Homepage, https://www.sprucelab.io/agents
Project-URL: Benchmarks, https://www.sprucelab.io/benchmarks
Project-URL: Source, https://github.com/EdvardGK/sprucelab
Author: Sprucelab
License-Expression: MIT
Keywords: agent,agentic,automation,bim,claude,cli,cursor,ifc,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: httpx>=0.25.0
Requires-Dist: keyring>=24.0.0
Requires-Dist: mcp>=1.0.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0.0
Requires-Dist: typer>=0.9.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: respx>=0.20.0; extra == 'dev'
Description-Content-Type: text/markdown

# sprucelab

> Agent-first surface for Sprucelab. One install ships a typed CLI and an MCP
> server — both wrapping the same public HTTP API. Drop in whichever transport
> your agent host speaks.

```bash
pip install sprucelab
```

## Two transports, one package

### `spruce` — typed CLI

For terminal-resident agents (Claude Code, Aider, anything that drives a
shell) and humans at the prompt. Every command supports `--json` for
machine-readable output; mutations support `--dry-run` where applicable.

```bash
spruce auth register --token <YOUR_KEY> --url https://www.sprucelab.io
spruce projects list --json
spruce files upload ./models/foo.ifc --project-id <uuid> --wait
spruce verify --model-id <uuid> --dry-run
```

Agents move through `spruce` faster than humans do — the rendering is for
humans; agents read the JSON. Once an agent has worked out the right sequence
of calls, the same commands run unattended from cron / GitHub Actions /
Airflow without burning tokens — that's the whole point of headless-first.
The AI budget stays reserved for actual reasoning work (claim extraction,
type classification, verification), not for re-deriving the same script every
run. See `spruce --help` for the full command tree.

### `sprucelab-mcp` — MCP server over stdio

For host-resident agents (Claude Desktop, Cursor, Continue, …) that launch
tools as subprocesses and call them as typed tool calls.

```json
{
  "mcpServers": {
    "sprucelab": {
      "command": "sprucelab-mcp",
      "env": {
        "SPRUCELAB_API_URL": "https://www.sprucelab.io",
        "SPRUCELAB_API_TOKEN": "your-token-here"
      }
    }
  }
}
```

The MCP server is a thin wrapper over the same HTTP surface the CLI talks
to — the one advertised at <https://www.sprucelab.io/api/capabilities/>. No
custom protocol, no surprises. If a tool fails, the error body is the literal
API response.

## Auth

Read-only discovery (`capabilities`, `agent_tools_manifest`, EIR catalog,
EIR presets) works without auth on either transport. Everything else needs
a token.

Get one via the web app at <https://www.sprucelab.io/agents>, then either:

```bash
spruce auth register --token <YOUR_KEY> --url https://www.sprucelab.io
```

…or set `SPRUCELAB_API_TOKEN` in the MCP env block above. The CLI and the
MCP server read the same token store.

For experimentation, the public sandbox token (read-only) is published on
<https://www.sprucelab.io/agents>.

## MCP tools

Read-only discovery (no auth):

| Tool | Purpose |
| ---- | ------- |
| `capabilities()` | Fetch the full capabilities manifest. Always start here. |
| `agent_tools_manifest()` | Fetch `/.well-known/agent-tools.json`. |
| `eir_catalog()` | EIR rule grammar — every kind, fields, singleton flag. |
| `eir_presets(preset?)` | Ready-to-seed EIR bundles. |

Projects + EIR (Bearer):

| Tool | Purpose |
| ---- | ------- |
| `list_projects()`, `show_project(id)` | Project listing + detail. |
| `scaffold_project(name, …)` | Create project + seed EIR in one call. |
| `list_members(id)`, `add_member(id, email, role)`, `remove_member(id, member_id)` | Per-project membership. |
| `mint_tenant_token(id, name, scope)`, `list_tenant_tokens(id)`, `revoke_tenant_token(id, token_id)` | Project-scoped agent tokens. |
| `eir_rules(id)` | List the active EIR config's rules. |
| `eir_apply_preset(id, preset, mode)` | Apply preset in merge or replace mode. |
| `eir_add_rule(id, kind, config, upsert)` | Append one rule; singleton 409 unless `upsert=True`. |
| `eir_remove_rule(id, rule_id)` | Idempotent rule delete. |
| `eir_set_rules(id, rules)` | Atomic full replace. |

Models + types + verification (Bearer):

| Tool | Purpose |
| ---- | ------- |
| `list_models(project_id?)` | List models in a project. |
| `list_types(model_id)`, `classify_types(mappings)` | Type list + batch classify. |
| `verify_dry_run(model_id)`, `verify_model(model_id)` | Verification, with persistence opt-in. |

Files (Bearer):

| Tool | Purpose |
| ---- | ------- |
| `list_files(project_id?)`, `show_file(id)` | List + detail. |
| `upload_file(project_id, file_path, on_duplicate)` | Multipart upload from a local path. |
| `reprocess_file(id)` | Trigger a fresh ExtractionRun. |
| `extraction_log(file_id, run_id?)` | Full ExtractionRun (log_entries + quality_report). |
| `list_observations(project_id?)` | Layer-1 observation log. |

Issues + scopes (Bearer):

| Tool | Purpose |
| ---- | ------- |
| `list_issues(…)`, `show_issue(id)`, `create_issue(…)`, `update_issue(…)`, `resolve_issue(id)` | Issue CRUD. |
| `issues_from_verification(model_id)` | Route verification failures into issues. |
| `list_scopes(project_id, as_tree)`, `create_scope(…)` | Federation scopes. |

Webhooks (Bearer):

| Tool | Purpose |
| ---- | ------- |
| `list_webhooks(project_id?)` | List subscriptions. |
| `create_webhook(project_id, target_url, events)` | Subscribe + receive signing secret ONCE. |
| `disable_webhook(id, enable?)` | Toggle `is_active`. |
| `delete_webhook(id)` | Permanent delete. |
| `list_deliveries(id)`, `redeliver(delivery_id)`, `test_webhook(id)` | Delivery log + replay + synthetic test. |

## Local-dev surface (`spruce dev …`)

The CLI also ships a `dev` subcommand that bypasses the API and talks
directly to a local Django install — for working *on* Sprucelab, not against
prod. Same agent-first invariants (`--json` everywhere, `--dry-run` on
mutations).

```bash
spruce dev env                                        # repo + service status
spruce dev db stats --json                            # row counts for key tables
spruce dev db materials --project <uuid> --top 15     # top materials per project
spruce dev seed materials --project <uuid> --dry-run  # preview seed plan
spruce dev test tsc                                   # frontend type-check
spruce dev test e2e materials --headed                # Playwright smoke
```

## Powered by

Sprucelab's IFC extraction layer is powered by
[`ifcfast`](https://github.com/EdvardGK/ifcfast) — 25–47× faster than
`ifcopenshell` on production IFCs. See
<https://www.sprucelab.io/benchmarks>.
