Open-source marketing research and Reddit engagement library — demand reports from real conversations, usable as a Python library, CLI, MCP server, or Claude Code plugin.
Project description
metalworks
Go from a startup idea to launch, grounded in real demand.
Give metalworks one sentence about what you want to build. It reads real Reddit conversations to tell you whether people actually want it, then turns that into the things you need to launch: your positioning, the competitors to beat, a design system, a build plan for your coding agent, and launch copy. Every claim links back to a real comment you can click — nothing is invented.
A Python library (also a CLI, an MCP server, and a Claude Code plugin). MIT licensed and built to be embedded — every layer (LLM, search, embeddings, storage, data source) is a swappable protocol.
Status: pre-release (0.0.4). APIs are unstable below 1.0. The stable surface is the
Metalworksfacade, themetalworks.contractPydantic models, and the MCP tool contracts — everything else may change in any 0.x release. Everything described in this README runs today.
Read the USAGE_POLICY before you use the Reddit side. Short version: authentic, disclosed engagement only. No fake personas, no vote manipulation, no coordinated inauthentic behavior.
Quickstart
Install metalworks with a provider SDK and set one key — any provider works:
pip install "metalworks[openai,research]" # or [google,research], [anthropic,research]
export OPENAI_API_KEY=... # or ANTHROPIC_API_KEY / GOOGLE_API_KEY / OPENROUTER_API_KEY
Embeddings need no separate key: with a Google or OpenAI key metalworks uses theirs, otherwise
it falls back to a small local model (fastembed, bundled with [research], downloaded once).
So a single chat key — Anthropic, OpenRouter, anything — gets you a full run.
metalworks models warm pre-downloads the local model.
Prefer Vertex AI over an API key? Set GOOGLE_GENAI_USE_VERTEXAI=true plus
VERTEX_PROJECT_ID and VERTEX_LOCATION and the Google adapters authenticate
via Application Default Credentials. See
docs/configuration.md.
from metalworks import Metalworks
mw = Metalworks() # provider inferred; or Metalworks(model="anthropic/claude-opus-4-8")
research = mw.research("Is there demand for a focus supplement aimed at developers?",
subreddits=["Nootropics", "Supplements"])
report = research.demand
Every quote in report.ranked_clusters is the exact text of a real Reddit
comment, and every web finding carries its real source URL — never model prose.
Anything metalworks can't back with a real quote, it drops. See
why you can trust the output.
The Metalworks facade is the easy path over run_research / run_discovery
and the protocols — drop down to those whenever you want more control. Submissions
come from the Hugging Face open-index/arctic Parquet mirror; comments from the
live Arctic Shift API. Set HF_TOKEN for windows beyond a few months. To read
the submission corpus from a Supabase Storage bucket instead (no HF runtime
dependency), install metalworks[supabase] and set ARCTIC_SHIFT_SOURCE=mirror,
or bring your own corpus to skip Arctic Shift.
Extras
Core stays lean (pydantic, httpx, typer, rich). Everything that pulls a provider
SDK or a heavy dependency lives behind an extra, so you install only what
matches the keys you have. Adapters lazy-import their SDK and raise
MissingExtraError with the exact pip install command when it is absent.
pip install "metalworks[google]"
pip install "metalworks[research,reddit]"
pip install "metalworks[all]"
| Extra | Pulls in | For |
|---|---|---|
anthropic |
anthropic |
Claude ChatModel adapter |
openai |
openai |
OpenAI ChatModel + embedding adapters |
google |
google-genai |
Gemini ChatModel (native grounding) + embeddings |
litellm |
litellm |
Optional long-tail provider routing |
reddit |
redditwarp, cryptography |
Reddit search, OAuth, posting, token encryption |
arctic |
duckdb |
Read Arctic Shift Parquet shards (submissions corpus) |
research |
arctic + rank-bm25 |
The full demand-report pipeline |
supabase |
arctic + supabase |
ArcticMirrorReader — Arctic corpus from a Supabase Storage bucket (ARCTIC_SHIFT_SOURCE=mirror) |
exa |
exa-py |
Exa SearchProvider adapter |
tavily |
tavily-python |
Tavily SearchProvider adapter |
browser |
playwright |
Owned headless Chromium PageRenderer (competitor teardowns, design review). Post-install step: metalworks browser install. On a server, FIRECRAWL_API_KEY renders without a local browser. |
mcp |
mcp[cli] |
MCP server surface |
all |
everything above | Kitchen sink |
dev |
pytest, ruff, pyright, respx | Contributors |
A bare import metalworks pulls in no provider modules; CI asserts this.
Architecture
metalworks owns small, versioned protocols and ships thin adapters over official provider SDKs. It does not route every provider through LiteLLM by default. The protocols are the seam your code and the pipeline speak through:
ChatModel—complete_text/complete_structured, model bound at adapter construction.GroundedChatModeladds model-native web grounding with full provenance (chunks plus character-offset supports).SearchProvider— external web search (Exa, Tavily).EmbeddingProvider— embeddings with a hard index-identity guard.- The typed repos (
CorpusRepo,BriefRepo,RunRepo,AccountRepo,OpportunityRepo,InboxRepo) are the storage protocol.MemoryStoresandSqliteStoresship in core; hosted backends (Postgres/PostgREST) are a custom store you implement downstream — see docs/custom-store.md.
See docs/protocols.md for signatures.
Two verticals sit on top of those protocols:
- Research (
metalworks.research) — turns an idea into a clusteredDemandReportof real, permalinked Reddit quotes. Entry point:run_research(deps, brief=...). Several functions build on a finished report, each linking its output back to that report's real quotes: positioning (build_positioning_brief), the landscape (run_landscape), distribution (build_channel_strategy/build_channel_assets/build_data_asset/build_geo_plan/plan_distribution), and a build plan + scaffold (build_spec_from_report/scaffold) — which also picks the surface and sketches feature-grounded screens. - Reddit (
metalworks.reddit) — OAuth, search, subreddit intel, inbox, posting, in-library rate limiting, and a deterministic compliance gate (heuristic_check) that runs offline on reply and post text.
Four form factors share that contract:
- Library —
from metalworks import Metalworks, or the functions and protocols underneath. - CLI —
metalworks research|reddit|arctic|discovery run, the report commands (metalworks research position|landscape,metalworks distribution strategy|assets|data-report|geo|requirements|plan|measure|engage,metalworks build init),metalworks doctor,metalworks mcp serve. - MCP server — zero-key data tools plus key-gated pipeline tools, over stdio or SSE.
- Claude Code plugin —
/demand-reportand friends (/plugin marketplace add Lab2A/metalworks).
Testing your own adapters and backends
The conformance suites metalworks holds itself to ship as a public module:
from metalworks.testing import FakeChatModel, FakeEmbedding, check_all_repos
def test_my_backend():
check_all_repos(MyBackend()) # includes the >1000-row pagination case
See docs/custom-chatmodel.md and docs/custom-store.md.
Docs
Full docs: metalworks.lab2a.ai
- Installation · Quickstart · Build a startup, end to end
- Capabilities: demand research · positioning & competitors · design system · build spec · distribution · GEO / LLM-citability · Reddit engagement
- Why you can trust the output · Data model
- Reference: Python SDK · CLI · MCP tools · Configuration · Using with AI agents
- Extending: overview · protocols · custom model/corpus/store
Contributing & development
Collaborators welcome. metalworks is contract-first: one set of Pydantic models
(metalworks.contract) is the stable spine, and every surface speaks it — so a capability lands on
the Python facade, the CLI, the MCP server, and the Claude Code plugin together, never just one.
The architecture page is the mental model;
CONTRIBUTING.md is the operational
guide.
git clone https://github.com/Lab2A/metalworks && cd metalworks
uv venv && uv pip install -e ".[all,dev]"
# the gate — everything must pass before a PR:
uv run ruff check . && uv run ruff format --check . && uv run pyright && uv run pytest -q
Where things live:
| Path | What |
|---|---|
src/metalworks/contract/ |
the Pydantic models every surface speaks — the stable spine |
src/metalworks/research/, reddit/ |
the two cores: demand research + Reddit engagement |
src/metalworks/client.py |
the Metalworks facade — surface 1 |
src/metalworks/cli/ |
the metalworks CLI — surface 2 |
src/metalworks/mcp/ |
the MCP tool bodies + server — surface 3 |
plugin/skills/ |
the Claude Code plugin skills — surface 4 |
scripts/gen_ts_types.py |
regenerates ts/contract.ts + JSON schema snapshots from the contract |
tests/ |
offline by default (pytest-socket; fakes for the LLM / embeddings / stores) |
The golden rule: a change to a primitive moves all four surfaces, the contract registry
(gen_ts_types + contract/__init__), and the docs — together. Run /pr-ready (the Claude Code
skill in .claude/skills/) before opening a PR: it runs the gate, the contract-drift check CI
doesn't, and walks the parity / docs / CHANGELOG checklist.
Project
- License: MIT.
- Usage policy: USAGE_POLICY.md.
- Security: SECURITY.md.
- Contributing: CONTRIBUTING.md.
- Changes: CHANGELOG.md.
- Org: Lab2A.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file metalworks-0.2.1.tar.gz.
File metadata
- Download URL: metalworks-0.2.1.tar.gz
- Upload date:
- Size: 890.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f7abb4042a0cfbbd25a3064d0027caee34029210cad557798c72e086881e632
|
|
| MD5 |
af388ce9886e335cc08fcde27e818b0b
|
|
| BLAKE2b-256 |
c1d85ee45110628fc94042538050693b1f9b1304de63d0a9c388a86fa16e56dc
|
Provenance
The following attestation bundles were made for metalworks-0.2.1.tar.gz:
Publisher:
release.yml on Lab2A/metalworks
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
metalworks-0.2.1.tar.gz -
Subject digest:
6f7abb4042a0cfbbd25a3064d0027caee34029210cad557798c72e086881e632 - Sigstore transparency entry: 1949704810
- Sigstore integration time:
-
Permalink:
Lab2A/metalworks@3999bae6d7548e2aeed0108bd6375cc006dd859d -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Lab2A
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3999bae6d7548e2aeed0108bd6375cc006dd859d -
Trigger Event:
push
-
Statement type:
File details
Details for the file metalworks-0.2.1-py3-none-any.whl.
File metadata
- Download URL: metalworks-0.2.1-py3-none-any.whl
- Upload date:
- Size: 608.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
973e51abce28595b3e114cf3a0f93bf1f6fe733401510c254c53c147a9abcb68
|
|
| MD5 |
0f7853cf7431be319388fc5b628fe7d7
|
|
| BLAKE2b-256 |
6d23d68d9c09c5c7fa9c7e4c597f1b69e2860530cf6b45d5c18b56ad6bb8b8d6
|
Provenance
The following attestation bundles were made for metalworks-0.2.1-py3-none-any.whl:
Publisher:
release.yml on Lab2A/metalworks
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
metalworks-0.2.1-py3-none-any.whl -
Subject digest:
973e51abce28595b3e114cf3a0f93bf1f6fe733401510c254c53c147a9abcb68 - Sigstore transparency entry: 1949704909
- Sigstore integration time:
-
Permalink:
Lab2A/metalworks@3999bae6d7548e2aeed0108bd6375cc006dd859d -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Lab2A
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3999bae6d7548e2aeed0108bd6375cc006dd859d -
Trigger Event:
push
-
Statement type: