sprucelab 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sprucelab-0.1.0/.gitignore +291 -0
- sprucelab-0.1.0/PKG-INFO +188 -0
- sprucelab-0.1.0/README.md +158 -0
- sprucelab-0.1.0/pyproject.toml +64 -0
- sprucelab-0.1.0/pytest.ini +13 -0
- sprucelab-0.1.0/sprucelab/__init__.py +19 -0
- sprucelab-0.1.0/sprucelab/cli/__init__.py +3 -0
- sprucelab-0.1.0/sprucelab/cli/__main__.py +5 -0
- sprucelab-0.1.0/sprucelab/cli/_auth.py +44 -0
- sprucelab-0.1.0/sprucelab/cli/_errors.py +174 -0
- sprucelab-0.1.0/sprucelab/cli/api_client.py +226 -0
- sprucelab-0.1.0/sprucelab/cli/app.py +547 -0
- sprucelab-0.1.0/sprucelab/cli/capabilities.py +159 -0
- sprucelab-0.1.0/sprucelab/cli/cde/__init__.py +1 -0
- sprucelab-0.1.0/sprucelab/cli/claims.py +330 -0
- sprucelab-0.1.0/sprucelab/cli/config.py +99 -0
- sprucelab-0.1.0/sprucelab/cli/dev.py +441 -0
- sprucelab-0.1.0/sprucelab/cli/embed.py +211 -0
- sprucelab-0.1.0/sprucelab/cli/executor.py +343 -0
- sprucelab-0.1.0/sprucelab/cli/files.py +915 -0
- sprucelab-0.1.0/sprucelab/cli/issues.py +271 -0
- sprucelab-0.1.0/sprucelab/cli/log.py +160 -0
- sprucelab-0.1.0/sprucelab/cli/models.py +102 -0
- sprucelab-0.1.0/sprucelab/cli/projects.py +839 -0
- sprucelab-0.1.0/sprucelab/cli/scopes.py +148 -0
- sprucelab-0.1.0/sprucelab/cli/scripts.py +146 -0
- sprucelab-0.1.0/sprucelab/cli/steps/__init__.py +1 -0
- sprucelab-0.1.0/sprucelab/cli/types.py +319 -0
- sprucelab-0.1.0/sprucelab/cli/utils/__init__.py +1 -0
- sprucelab-0.1.0/sprucelab/cli/verify.py +100 -0
- sprucelab-0.1.0/sprucelab/cli/webhooks.py +524 -0
- sprucelab-0.1.0/sprucelab/mcp/__init__.py +8 -0
- sprucelab-0.1.0/sprucelab/mcp/__main__.py +6 -0
- sprucelab-0.1.0/sprucelab/mcp/client.py +102 -0
- sprucelab-0.1.0/sprucelab/mcp/server.py +653 -0
- sprucelab-0.1.0/tests/__init__.py +0 -0
- sprucelab-0.1.0/tests/conftest.py +57 -0
- sprucelab-0.1.0/tests/integration/__init__.py +0 -0
- sprucelab-0.1.0/tests/integration/conftest.py +57 -0
- sprucelab-0.1.0/tests/integration/test_smoke_live.py +253 -0
- sprucelab-0.1.0/tests/integration/test_smoke_live_full_chain.py +432 -0
- sprucelab-0.1.0/tests/integration/test_smoke_live_tenant_loop.py +203 -0
- sprucelab-0.1.0/tests/test_auth_tokens_cli.py +88 -0
- sprucelab-0.1.0/tests/test_capabilities_cli.py +101 -0
- sprucelab-0.1.0/tests/test_claims_cli.py +422 -0
- sprucelab-0.1.0/tests/test_errors.py +230 -0
- sprucelab-0.1.0/tests/test_files_cli.py +1013 -0
- sprucelab-0.1.0/tests/test_issues_cli.py +149 -0
- sprucelab-0.1.0/tests/test_log_cli.py +189 -0
- sprucelab-0.1.0/tests/test_mcp_server_tools.py +339 -0
- sprucelab-0.1.0/tests/test_models_cli.py +153 -0
- sprucelab-0.1.0/tests/test_projects_cli.py +623 -0
- sprucelab-0.1.0/tests/test_scopes_cli.py +58 -0
- sprucelab-0.1.0/tests/test_scripts_cli.py +162 -0
- sprucelab-0.1.0/tests/test_types_cli.py +312 -0
- sprucelab-0.1.0/tests/test_verify_cli.py +192 -0
- sprucelab-0.1.0/tests/test_webhooks_cli.py +528 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
/lib/
|
|
14
|
+
/lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
pip-wheel-metadata/
|
|
20
|
+
share/python-wheels/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
.installed.cfg
|
|
23
|
+
*.egg
|
|
24
|
+
MANIFEST
|
|
25
|
+
|
|
26
|
+
# Django
|
|
27
|
+
*.log
|
|
28
|
+
local_settings.py
|
|
29
|
+
db.sqlite3
|
|
30
|
+
db.sqlite3-journal
|
|
31
|
+
database.sqlite
|
|
32
|
+
database.sqlite-journal
|
|
33
|
+
/media
|
|
34
|
+
/staticfiles
|
|
35
|
+
/backend/logs/
|
|
36
|
+
/backend/media/
|
|
37
|
+
/backend/staticfiles/
|
|
38
|
+
/backend/database.sqlite*
|
|
39
|
+
|
|
40
|
+
# Flask
|
|
41
|
+
instance/
|
|
42
|
+
.webassets-cache
|
|
43
|
+
|
|
44
|
+
# Scrapy
|
|
45
|
+
.scrapy
|
|
46
|
+
|
|
47
|
+
# Sphinx documentation
|
|
48
|
+
docs/_build/
|
|
49
|
+
|
|
50
|
+
# PyBuilder
|
|
51
|
+
target/
|
|
52
|
+
|
|
53
|
+
# Jupyter Notebook
|
|
54
|
+
.ipynb_checkpoints
|
|
55
|
+
|
|
56
|
+
# IPython
|
|
57
|
+
profile_default/
|
|
58
|
+
ipython_config.py
|
|
59
|
+
|
|
60
|
+
# pyenv
|
|
61
|
+
.python-version
|
|
62
|
+
|
|
63
|
+
# pipenv
|
|
64
|
+
Pipfile.lock
|
|
65
|
+
|
|
66
|
+
# PEP 582
|
|
67
|
+
__pypackages__/
|
|
68
|
+
|
|
69
|
+
# Celery
|
|
70
|
+
celerybeat-schedule
|
|
71
|
+
celerybeat.pid
|
|
72
|
+
|
|
73
|
+
# SageMath
|
|
74
|
+
*.sage.py
|
|
75
|
+
|
|
76
|
+
# Environments
|
|
77
|
+
.env
|
|
78
|
+
.venv
|
|
79
|
+
env/
|
|
80
|
+
venv/
|
|
81
|
+
ENV/
|
|
82
|
+
env.bak/
|
|
83
|
+
venv.bak/
|
|
84
|
+
|
|
85
|
+
# Spyder
|
|
86
|
+
.spyderproject
|
|
87
|
+
.spyproject
|
|
88
|
+
|
|
89
|
+
# Rope
|
|
90
|
+
.ropeproject
|
|
91
|
+
|
|
92
|
+
# mkdocs
|
|
93
|
+
/site
|
|
94
|
+
|
|
95
|
+
# mypy
|
|
96
|
+
.mypy_cache/
|
|
97
|
+
.dmypy.json
|
|
98
|
+
dmypy.json
|
|
99
|
+
|
|
100
|
+
# Pyre
|
|
101
|
+
.pyre/
|
|
102
|
+
|
|
103
|
+
# Conda
|
|
104
|
+
.conda/
|
|
105
|
+
|
|
106
|
+
# Node.js / Frontend
|
|
107
|
+
node_modules/
|
|
108
|
+
npm-debug.log*
|
|
109
|
+
yarn-debug.log*
|
|
110
|
+
yarn-error.log*
|
|
111
|
+
lerna-debug.log*
|
|
112
|
+
.pnpm-debug.log*
|
|
113
|
+
|
|
114
|
+
# Diagnostic reports
|
|
115
|
+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
116
|
+
|
|
117
|
+
# Runtime data
|
|
118
|
+
pids
|
|
119
|
+
*.pid
|
|
120
|
+
*.seed
|
|
121
|
+
*.pid.lock
|
|
122
|
+
|
|
123
|
+
# Directory for instrumented libs
|
|
124
|
+
lib-cov
|
|
125
|
+
|
|
126
|
+
# Coverage
|
|
127
|
+
coverage
|
|
128
|
+
*.lcov
|
|
129
|
+
.nyc_output
|
|
130
|
+
|
|
131
|
+
# Grunt
|
|
132
|
+
.grunt
|
|
133
|
+
|
|
134
|
+
# Bower
|
|
135
|
+
bower_components
|
|
136
|
+
|
|
137
|
+
# node-waf
|
|
138
|
+
.lock-wscript
|
|
139
|
+
|
|
140
|
+
# Compiled binary addons
|
|
141
|
+
build/Release
|
|
142
|
+
|
|
143
|
+
# Dependency directories
|
|
144
|
+
jspm_packages/
|
|
145
|
+
|
|
146
|
+
# Snowpack
|
|
147
|
+
web_modules/
|
|
148
|
+
|
|
149
|
+
# TypeScript cache
|
|
150
|
+
*.tsbuildinfo
|
|
151
|
+
|
|
152
|
+
# Optional npm cache
|
|
153
|
+
.npm
|
|
154
|
+
|
|
155
|
+
# Optional eslint cache
|
|
156
|
+
.eslintcache
|
|
157
|
+
|
|
158
|
+
# Optional stylelint cache
|
|
159
|
+
.stylelintcache
|
|
160
|
+
|
|
161
|
+
# Microbundle cache
|
|
162
|
+
.rpt2_cache/
|
|
163
|
+
.rts2_cache_cjs/
|
|
164
|
+
.rts2_cache_es/
|
|
165
|
+
.rts2_cache_umd/
|
|
166
|
+
|
|
167
|
+
# Optional REPL history
|
|
168
|
+
.node_repl_history
|
|
169
|
+
|
|
170
|
+
# Output of 'npm pack'
|
|
171
|
+
*.tgz
|
|
172
|
+
|
|
173
|
+
# Yarn
|
|
174
|
+
.yarn-integrity
|
|
175
|
+
.yarn/cache
|
|
176
|
+
.yarn/unplugged
|
|
177
|
+
.yarn/build-state.yml
|
|
178
|
+
.yarn/install-state.gz
|
|
179
|
+
.pnp.*
|
|
180
|
+
|
|
181
|
+
# parcel-bundler
|
|
182
|
+
.cache
|
|
183
|
+
.parcel-cache
|
|
184
|
+
|
|
185
|
+
# Next.js
|
|
186
|
+
.next/
|
|
187
|
+
out/
|
|
188
|
+
|
|
189
|
+
# Nuxt.js
|
|
190
|
+
.nuxt
|
|
191
|
+
dist
|
|
192
|
+
|
|
193
|
+
# Gatsby
|
|
194
|
+
.cache/
|
|
195
|
+
public
|
|
196
|
+
|
|
197
|
+
# vuepress
|
|
198
|
+
.vuepress/dist
|
|
199
|
+
|
|
200
|
+
# Serverless
|
|
201
|
+
.serverless/
|
|
202
|
+
|
|
203
|
+
# FuseBox
|
|
204
|
+
.fusebox/
|
|
205
|
+
|
|
206
|
+
# DynamoDB Local
|
|
207
|
+
.dynamodb/
|
|
208
|
+
|
|
209
|
+
# TernJS
|
|
210
|
+
.tern-port
|
|
211
|
+
|
|
212
|
+
# Stores VSCode versions
|
|
213
|
+
.vscode-test
|
|
214
|
+
|
|
215
|
+
# yarn v2
|
|
216
|
+
.yarn/cache
|
|
217
|
+
.yarn/unplugged
|
|
218
|
+
.yarn/build-state.yml
|
|
219
|
+
.yarn/install-state.gz
|
|
220
|
+
.pnp.*
|
|
221
|
+
|
|
222
|
+
# IDEs
|
|
223
|
+
.vscode/
|
|
224
|
+
.idea/
|
|
225
|
+
*.swp
|
|
226
|
+
*.swo
|
|
227
|
+
*~
|
|
228
|
+
.DS_Store
|
|
229
|
+
|
|
230
|
+
# Vercel CLI link (project metadata, not for commit)
|
|
231
|
+
.vercel/
|
|
232
|
+
|
|
233
|
+
# Project specific
|
|
234
|
+
/output/
|
|
235
|
+
/versions/
|
|
236
|
+
*.ifc
|
|
237
|
+
/output/*.json
|
|
238
|
+
*.npz
|
|
239
|
+
simplified*.ifc
|
|
240
|
+
LBK_RIV_C*.ifc
|
|
241
|
+
LBK_RIV_C*.json
|
|
242
|
+
|
|
243
|
+
# Exception: checked-in test fixtures for the live smoke harness
|
|
244
|
+
!cli/tests/integration/fixtures/*.ifc
|
|
245
|
+
|
|
246
|
+
# Temp files
|
|
247
|
+
*.tmp
|
|
248
|
+
*.bak
|
|
249
|
+
*.log
|
|
250
|
+
|
|
251
|
+
# OS files
|
|
252
|
+
Thumbs.db
|
|
253
|
+
.DS_Store
|
|
254
|
+
|
|
255
|
+
# Vite
|
|
256
|
+
dist-ssr
|
|
257
|
+
*.local
|
|
258
|
+
|
|
259
|
+
# Frontend build
|
|
260
|
+
/frontend/dist/
|
|
261
|
+
/frontend/build/
|
|
262
|
+
/frontend/.vite/
|
|
263
|
+
|
|
264
|
+
# Environment files (keep .env.example, ignore others)
|
|
265
|
+
.env.local
|
|
266
|
+
.env.dev
|
|
267
|
+
.env.development.local
|
|
268
|
+
.env.test.local
|
|
269
|
+
.env.production.local
|
|
270
|
+
.env.production
|
|
271
|
+
frontend/.env.production
|
|
272
|
+
backend/.env*
|
|
273
|
+
|
|
274
|
+
# Test coverage
|
|
275
|
+
htmlcov/
|
|
276
|
+
.coverage
|
|
277
|
+
.coverage.*
|
|
278
|
+
|
|
279
|
+
# Generated by supabase CLI
|
|
280
|
+
supabase/
|
|
281
|
+
backend/supabase/
|
|
282
|
+
|
|
283
|
+
# Claude Code runtime state
|
|
284
|
+
.claude/scheduled_tasks.lock
|
|
285
|
+
|
|
286
|
+
# Stray install logs
|
|
287
|
+
sunshine-install-log.txt
|
|
288
|
+
|
|
289
|
+
# Claude Code agent worktrees (transient git worktrees from subagent runs)
|
|
290
|
+
.claude/worktrees/
|
|
291
|
+
|
sprucelab-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sprucelab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent-first surface for Sprucelab — typed CLI + MCP server over the same public HTTP API.
|
|
5
|
+
Project-URL: Homepage, https://www.sprucelab.io/agents
|
|
6
|
+
Project-URL: Benchmarks, https://www.sprucelab.io/benchmarks
|
|
7
|
+
Project-URL: Source, https://github.com/EdvardGK/sprucelab
|
|
8
|
+
Author: Sprucelab
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: agent,agentic,automation,bim,claude,cli,cursor,ifc,mcp
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: httpx>=0.25.0
|
|
20
|
+
Requires-Dist: keyring>=24.0.0
|
|
21
|
+
Requires-Dist: mcp>=1.0.0
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Requires-Dist: rich>=13.0.0
|
|
24
|
+
Requires-Dist: typer>=0.9.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: respx>=0.20.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# sprucelab
|
|
32
|
+
|
|
33
|
+
> Agent-first surface for Sprucelab. One install ships a typed CLI and an MCP
|
|
34
|
+
> server — both wrapping the same public HTTP API. Drop in whichever transport
|
|
35
|
+
> your agent host speaks.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install sprucelab
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Two transports, one package
|
|
42
|
+
|
|
43
|
+
### `spruce` — typed CLI
|
|
44
|
+
|
|
45
|
+
For terminal-resident agents (Claude Code, Aider, anything that drives a
|
|
46
|
+
shell) and humans at the prompt. Every command supports `--json` for
|
|
47
|
+
machine-readable output; mutations support `--dry-run` where applicable.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
spruce auth register --token <YOUR_KEY> --url https://www.sprucelab.io
|
|
51
|
+
spruce projects list --json
|
|
52
|
+
spruce files upload ./models/foo.ifc --project-id <uuid> --wait
|
|
53
|
+
spruce verify --model-id <uuid> --dry-run
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Agents move through `spruce` faster than humans do — the rendering is for
|
|
57
|
+
humans; agents read the JSON. Once an agent has worked out the right sequence
|
|
58
|
+
of calls, the same commands run unattended from cron / GitHub Actions /
|
|
59
|
+
Airflow without burning tokens — that's the whole point of headless-first.
|
|
60
|
+
The AI budget stays reserved for actual reasoning work (claim extraction,
|
|
61
|
+
type classification, verification), not for re-deriving the same script every
|
|
62
|
+
run. See `spruce --help` for the full command tree.
|
|
63
|
+
|
|
64
|
+
### `sprucelab-mcp` — MCP server over stdio
|
|
65
|
+
|
|
66
|
+
For host-resident agents (Claude Desktop, Cursor, Continue, …) that launch
|
|
67
|
+
tools as subprocesses and call them as typed tool calls.
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"sprucelab": {
|
|
73
|
+
"command": "sprucelab-mcp",
|
|
74
|
+
"env": {
|
|
75
|
+
"SPRUCELAB_API_URL": "https://www.sprucelab.io",
|
|
76
|
+
"SPRUCELAB_API_TOKEN": "your-token-here"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The MCP server is a thin wrapper over the same HTTP surface the CLI talks
|
|
84
|
+
to — the one advertised at <https://www.sprucelab.io/api/capabilities/>. No
|
|
85
|
+
custom protocol, no surprises. If a tool fails, the error body is the literal
|
|
86
|
+
API response.
|
|
87
|
+
|
|
88
|
+
## Auth
|
|
89
|
+
|
|
90
|
+
Read-only discovery (`capabilities`, `agent_tools_manifest`, EIR catalog,
|
|
91
|
+
EIR presets) works without auth on either transport. Everything else needs
|
|
92
|
+
a token.
|
|
93
|
+
|
|
94
|
+
Get one via the web app at <https://www.sprucelab.io/agents>, then either:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
spruce auth register --token <YOUR_KEY> --url https://www.sprucelab.io
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
…or set `SPRUCELAB_API_TOKEN` in the MCP env block above. The CLI and the
|
|
101
|
+
MCP server read the same token store.
|
|
102
|
+
|
|
103
|
+
For experimentation, the public sandbox token (read-only) is published on
|
|
104
|
+
<https://www.sprucelab.io/agents>.
|
|
105
|
+
|
|
106
|
+
## MCP tools
|
|
107
|
+
|
|
108
|
+
Read-only discovery (no auth):
|
|
109
|
+
|
|
110
|
+
| Tool | Purpose |
|
|
111
|
+
| ---- | ------- |
|
|
112
|
+
| `capabilities()` | Fetch the full capabilities manifest. Always start here. |
|
|
113
|
+
| `agent_tools_manifest()` | Fetch `/.well-known/agent-tools.json`. |
|
|
114
|
+
| `eir_catalog()` | EIR rule grammar — every kind, fields, singleton flag. |
|
|
115
|
+
| `eir_presets(preset?)` | Ready-to-seed EIR bundles. |
|
|
116
|
+
|
|
117
|
+
Projects + EIR (Bearer):
|
|
118
|
+
|
|
119
|
+
| Tool | Purpose |
|
|
120
|
+
| ---- | ------- |
|
|
121
|
+
| `list_projects()`, `show_project(id)` | Project listing + detail. |
|
|
122
|
+
| `scaffold_project(name, …)` | Create project + seed EIR in one call. |
|
|
123
|
+
| `list_members(id)`, `add_member(id, email, role)`, `remove_member(id, member_id)` | Per-project membership. |
|
|
124
|
+
| `mint_tenant_token(id, name, scope)`, `list_tenant_tokens(id)`, `revoke_tenant_token(id, token_id)` | Project-scoped agent tokens. |
|
|
125
|
+
| `eir_rules(id)` | List the active EIR config's rules. |
|
|
126
|
+
| `eir_apply_preset(id, preset, mode)` | Apply preset in merge or replace mode. |
|
|
127
|
+
| `eir_add_rule(id, kind, config, upsert)` | Append one rule; singleton 409 unless `upsert=True`. |
|
|
128
|
+
| `eir_remove_rule(id, rule_id)` | Idempotent rule delete. |
|
|
129
|
+
| `eir_set_rules(id, rules)` | Atomic full replace. |
|
|
130
|
+
|
|
131
|
+
Models + types + verification (Bearer):
|
|
132
|
+
|
|
133
|
+
| Tool | Purpose |
|
|
134
|
+
| ---- | ------- |
|
|
135
|
+
| `list_models(project_id?)` | List models in a project. |
|
|
136
|
+
| `list_types(model_id)`, `classify_types(mappings)` | Type list + batch classify. |
|
|
137
|
+
| `verify_dry_run(model_id)`, `verify_model(model_id)` | Verification, with persistence opt-in. |
|
|
138
|
+
|
|
139
|
+
Files (Bearer):
|
|
140
|
+
|
|
141
|
+
| Tool | Purpose |
|
|
142
|
+
| ---- | ------- |
|
|
143
|
+
| `list_files(project_id?)`, `show_file(id)` | List + detail. |
|
|
144
|
+
| `upload_file(project_id, file_path, on_duplicate)` | Multipart upload from a local path. |
|
|
145
|
+
| `reprocess_file(id)` | Trigger a fresh ExtractionRun. |
|
|
146
|
+
| `extraction_log(file_id, run_id?)` | Full ExtractionRun (log_entries + quality_report). |
|
|
147
|
+
| `list_observations(project_id?)` | Layer-1 observation log. |
|
|
148
|
+
|
|
149
|
+
Issues + scopes (Bearer):
|
|
150
|
+
|
|
151
|
+
| Tool | Purpose |
|
|
152
|
+
| ---- | ------- |
|
|
153
|
+
| `list_issues(…)`, `show_issue(id)`, `create_issue(…)`, `update_issue(…)`, `resolve_issue(id)` | Issue CRUD. |
|
|
154
|
+
| `issues_from_verification(model_id)` | Route verification failures into issues. |
|
|
155
|
+
| `list_scopes(project_id, as_tree)`, `create_scope(…)` | Federation scopes. |
|
|
156
|
+
|
|
157
|
+
Webhooks (Bearer):
|
|
158
|
+
|
|
159
|
+
| Tool | Purpose |
|
|
160
|
+
| ---- | ------- |
|
|
161
|
+
| `list_webhooks(project_id?)` | List subscriptions. |
|
|
162
|
+
| `create_webhook(project_id, target_url, events)` | Subscribe + receive signing secret ONCE. |
|
|
163
|
+
| `disable_webhook(id, enable?)` | Toggle `is_active`. |
|
|
164
|
+
| `delete_webhook(id)` | Permanent delete. |
|
|
165
|
+
| `list_deliveries(id)`, `redeliver(delivery_id)`, `test_webhook(id)` | Delivery log + replay + synthetic test. |
|
|
166
|
+
|
|
167
|
+
## Local-dev surface (`spruce dev …`)
|
|
168
|
+
|
|
169
|
+
The CLI also ships a `dev` subcommand that bypasses the API and talks
|
|
170
|
+
directly to a local Django install — for working *on* Sprucelab, not against
|
|
171
|
+
prod. Same agent-first invariants (`--json` everywhere, `--dry-run` on
|
|
172
|
+
mutations).
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
spruce dev env # repo + service status
|
|
176
|
+
spruce dev db stats --json # row counts for key tables
|
|
177
|
+
spruce dev db materials --project <uuid> --top 15 # top materials per project
|
|
178
|
+
spruce dev seed materials --project <uuid> --dry-run # preview seed plan
|
|
179
|
+
spruce dev test tsc # frontend type-check
|
|
180
|
+
spruce dev test e2e materials --headed # Playwright smoke
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Powered by
|
|
184
|
+
|
|
185
|
+
Sprucelab's IFC extraction layer is powered by
|
|
186
|
+
[`ifcfast`](https://github.com/EdvardGK/ifcfast) — 25–47× faster than
|
|
187
|
+
`ifcopenshell` on production IFCs. See
|
|
188
|
+
<https://www.sprucelab.io/benchmarks>.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# sprucelab
|
|
2
|
+
|
|
3
|
+
> Agent-first surface for Sprucelab. One install ships a typed CLI and an MCP
|
|
4
|
+
> server — both wrapping the same public HTTP API. Drop in whichever transport
|
|
5
|
+
> your agent host speaks.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install sprucelab
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Two transports, one package
|
|
12
|
+
|
|
13
|
+
### `spruce` — typed CLI
|
|
14
|
+
|
|
15
|
+
For terminal-resident agents (Claude Code, Aider, anything that drives a
|
|
16
|
+
shell) and humans at the prompt. Every command supports `--json` for
|
|
17
|
+
machine-readable output; mutations support `--dry-run` where applicable.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
spruce auth register --token <YOUR_KEY> --url https://www.sprucelab.io
|
|
21
|
+
spruce projects list --json
|
|
22
|
+
spruce files upload ./models/foo.ifc --project-id <uuid> --wait
|
|
23
|
+
spruce verify --model-id <uuid> --dry-run
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Agents move through `spruce` faster than humans do — the rendering is for
|
|
27
|
+
humans; agents read the JSON. Once an agent has worked out the right sequence
|
|
28
|
+
of calls, the same commands run unattended from cron / GitHub Actions /
|
|
29
|
+
Airflow without burning tokens — that's the whole point of headless-first.
|
|
30
|
+
The AI budget stays reserved for actual reasoning work (claim extraction,
|
|
31
|
+
type classification, verification), not for re-deriving the same script every
|
|
32
|
+
run. See `spruce --help` for the full command tree.
|
|
33
|
+
|
|
34
|
+
### `sprucelab-mcp` — MCP server over stdio
|
|
35
|
+
|
|
36
|
+
For host-resident agents (Claude Desktop, Cursor, Continue, …) that launch
|
|
37
|
+
tools as subprocesses and call them as typed tool calls.
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"sprucelab": {
|
|
43
|
+
"command": "sprucelab-mcp",
|
|
44
|
+
"env": {
|
|
45
|
+
"SPRUCELAB_API_URL": "https://www.sprucelab.io",
|
|
46
|
+
"SPRUCELAB_API_TOKEN": "your-token-here"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The MCP server is a thin wrapper over the same HTTP surface the CLI talks
|
|
54
|
+
to — the one advertised at <https://www.sprucelab.io/api/capabilities/>. No
|
|
55
|
+
custom protocol, no surprises. If a tool fails, the error body is the literal
|
|
56
|
+
API response.
|
|
57
|
+
|
|
58
|
+
## Auth
|
|
59
|
+
|
|
60
|
+
Read-only discovery (`capabilities`, `agent_tools_manifest`, EIR catalog,
|
|
61
|
+
EIR presets) works without auth on either transport. Everything else needs
|
|
62
|
+
a token.
|
|
63
|
+
|
|
64
|
+
Get one via the web app at <https://www.sprucelab.io/agents>, then either:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
spruce auth register --token <YOUR_KEY> --url https://www.sprucelab.io
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
…or set `SPRUCELAB_API_TOKEN` in the MCP env block above. The CLI and the
|
|
71
|
+
MCP server read the same token store.
|
|
72
|
+
|
|
73
|
+
For experimentation, the public sandbox token (read-only) is published on
|
|
74
|
+
<https://www.sprucelab.io/agents>.
|
|
75
|
+
|
|
76
|
+
## MCP tools
|
|
77
|
+
|
|
78
|
+
Read-only discovery (no auth):
|
|
79
|
+
|
|
80
|
+
| Tool | Purpose |
|
|
81
|
+
| ---- | ------- |
|
|
82
|
+
| `capabilities()` | Fetch the full capabilities manifest. Always start here. |
|
|
83
|
+
| `agent_tools_manifest()` | Fetch `/.well-known/agent-tools.json`. |
|
|
84
|
+
| `eir_catalog()` | EIR rule grammar — every kind, fields, singleton flag. |
|
|
85
|
+
| `eir_presets(preset?)` | Ready-to-seed EIR bundles. |
|
|
86
|
+
|
|
87
|
+
Projects + EIR (Bearer):
|
|
88
|
+
|
|
89
|
+
| Tool | Purpose |
|
|
90
|
+
| ---- | ------- |
|
|
91
|
+
| `list_projects()`, `show_project(id)` | Project listing + detail. |
|
|
92
|
+
| `scaffold_project(name, …)` | Create project + seed EIR in one call. |
|
|
93
|
+
| `list_members(id)`, `add_member(id, email, role)`, `remove_member(id, member_id)` | Per-project membership. |
|
|
94
|
+
| `mint_tenant_token(id, name, scope)`, `list_tenant_tokens(id)`, `revoke_tenant_token(id, token_id)` | Project-scoped agent tokens. |
|
|
95
|
+
| `eir_rules(id)` | List the active EIR config's rules. |
|
|
96
|
+
| `eir_apply_preset(id, preset, mode)` | Apply preset in merge or replace mode. |
|
|
97
|
+
| `eir_add_rule(id, kind, config, upsert)` | Append one rule; singleton 409 unless `upsert=True`. |
|
|
98
|
+
| `eir_remove_rule(id, rule_id)` | Idempotent rule delete. |
|
|
99
|
+
| `eir_set_rules(id, rules)` | Atomic full replace. |
|
|
100
|
+
|
|
101
|
+
Models + types + verification (Bearer):
|
|
102
|
+
|
|
103
|
+
| Tool | Purpose |
|
|
104
|
+
| ---- | ------- |
|
|
105
|
+
| `list_models(project_id?)` | List models in a project. |
|
|
106
|
+
| `list_types(model_id)`, `classify_types(mappings)` | Type list + batch classify. |
|
|
107
|
+
| `verify_dry_run(model_id)`, `verify_model(model_id)` | Verification, with persistence opt-in. |
|
|
108
|
+
|
|
109
|
+
Files (Bearer):
|
|
110
|
+
|
|
111
|
+
| Tool | Purpose |
|
|
112
|
+
| ---- | ------- |
|
|
113
|
+
| `list_files(project_id?)`, `show_file(id)` | List + detail. |
|
|
114
|
+
| `upload_file(project_id, file_path, on_duplicate)` | Multipart upload from a local path. |
|
|
115
|
+
| `reprocess_file(id)` | Trigger a fresh ExtractionRun. |
|
|
116
|
+
| `extraction_log(file_id, run_id?)` | Full ExtractionRun (log_entries + quality_report). |
|
|
117
|
+
| `list_observations(project_id?)` | Layer-1 observation log. |
|
|
118
|
+
|
|
119
|
+
Issues + scopes (Bearer):
|
|
120
|
+
|
|
121
|
+
| Tool | Purpose |
|
|
122
|
+
| ---- | ------- |
|
|
123
|
+
| `list_issues(…)`, `show_issue(id)`, `create_issue(…)`, `update_issue(…)`, `resolve_issue(id)` | Issue CRUD. |
|
|
124
|
+
| `issues_from_verification(model_id)` | Route verification failures into issues. |
|
|
125
|
+
| `list_scopes(project_id, as_tree)`, `create_scope(…)` | Federation scopes. |
|
|
126
|
+
|
|
127
|
+
Webhooks (Bearer):
|
|
128
|
+
|
|
129
|
+
| Tool | Purpose |
|
|
130
|
+
| ---- | ------- |
|
|
131
|
+
| `list_webhooks(project_id?)` | List subscriptions. |
|
|
132
|
+
| `create_webhook(project_id, target_url, events)` | Subscribe + receive signing secret ONCE. |
|
|
133
|
+
| `disable_webhook(id, enable?)` | Toggle `is_active`. |
|
|
134
|
+
| `delete_webhook(id)` | Permanent delete. |
|
|
135
|
+
| `list_deliveries(id)`, `redeliver(delivery_id)`, `test_webhook(id)` | Delivery log + replay + synthetic test. |
|
|
136
|
+
|
|
137
|
+
## Local-dev surface (`spruce dev …`)
|
|
138
|
+
|
|
139
|
+
The CLI also ships a `dev` subcommand that bypasses the API and talks
|
|
140
|
+
directly to a local Django install — for working *on* Sprucelab, not against
|
|
141
|
+
prod. Same agent-first invariants (`--json` everywhere, `--dry-run` on
|
|
142
|
+
mutations).
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
spruce dev env # repo + service status
|
|
146
|
+
spruce dev db stats --json # row counts for key tables
|
|
147
|
+
spruce dev db materials --project <uuid> --top 15 # top materials per project
|
|
148
|
+
spruce dev seed materials --project <uuid> --dry-run # preview seed plan
|
|
149
|
+
spruce dev test tsc # frontend type-check
|
|
150
|
+
spruce dev test e2e materials --headed # Playwright smoke
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Powered by
|
|
154
|
+
|
|
155
|
+
Sprucelab's IFC extraction layer is powered by
|
|
156
|
+
[`ifcfast`](https://github.com/EdvardGK/ifcfast) — 25–47× faster than
|
|
157
|
+
`ifcopenshell` on production IFCs. See
|
|
158
|
+
<https://www.sprucelab.io/benchmarks>.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sprucelab"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Agent-first surface for Sprucelab — typed CLI + MCP server over the same public HTTP API."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "Sprucelab" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"bim",
|
|
15
|
+
"ifc",
|
|
16
|
+
"mcp",
|
|
17
|
+
"agent",
|
|
18
|
+
"agentic",
|
|
19
|
+
"automation",
|
|
20
|
+
"claude",
|
|
21
|
+
"cursor",
|
|
22
|
+
"cli",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 4 - Beta",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
dependencies = [
|
|
35
|
+
# MCP transport — host-resident agents (Claude Desktop, Cursor, …).
|
|
36
|
+
"mcp>=1.0.0",
|
|
37
|
+
# CLI transport — terminal-resident agents (Claude Code, Aider, …)
|
|
38
|
+
# and humans at the prompt.
|
|
39
|
+
"typer>=0.9.0",
|
|
40
|
+
"rich>=13.0.0",
|
|
41
|
+
"pyyaml>=6.0",
|
|
42
|
+
"keyring>=24.0.0",
|
|
43
|
+
# Shared HTTP layer.
|
|
44
|
+
"httpx>=0.25.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
dev = [
|
|
49
|
+
"pytest>=7.0.0",
|
|
50
|
+
"pytest-asyncio>=0.21.0",
|
|
51
|
+
"respx>=0.20.0",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[project.urls]
|
|
55
|
+
Homepage = "https://www.sprucelab.io/agents"
|
|
56
|
+
Benchmarks = "https://www.sprucelab.io/benchmarks"
|
|
57
|
+
Source = "https://github.com/EdvardGK/sprucelab"
|
|
58
|
+
|
|
59
|
+
[project.scripts]
|
|
60
|
+
sprucelab-mcp = "sprucelab.mcp.server:main"
|
|
61
|
+
spruce = "sprucelab.cli.app:app"
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.wheel]
|
|
64
|
+
packages = ["sprucelab"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[pytest]
|
|
2
|
+
python_files = test_*.py
|
|
3
|
+
python_classes = Test*
|
|
4
|
+
python_functions = test_*
|
|
5
|
+
testpaths = tests
|
|
6
|
+
addopts =
|
|
7
|
+
-ra
|
|
8
|
+
--strict-markers
|
|
9
|
+
--tb=short
|
|
10
|
+
markers =
|
|
11
|
+
live: opt-in live-API smoke test (requires SPRUCE_LIVE_API_URL; never runs in CI)
|
|
12
|
+
filterwarnings =
|
|
13
|
+
ignore::DeprecationWarning
|