metalworks 0.0.1__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.
- metalworks-0.0.1/.claude-plugin/marketplace.json +18 -0
- metalworks-0.0.1/.github/workflows/ci.yml +49 -0
- metalworks-0.0.1/.github/workflows/release.yml +36 -0
- metalworks-0.0.1/.gitignore +28 -0
- metalworks-0.0.1/CHANGELOG.md +188 -0
- metalworks-0.0.1/CODE_OF_CONDUCT.md +41 -0
- metalworks-0.0.1/CONTRIBUTING.md +110 -0
- metalworks-0.0.1/LICENSE +21 -0
- metalworks-0.0.1/PKG-INFO +265 -0
- metalworks-0.0.1/README.md +194 -0
- metalworks-0.0.1/SECURITY.md +53 -0
- metalworks-0.0.1/USAGE_POLICY.md +62 -0
- metalworks-0.0.1/docs/ai-agents.md +68 -0
- metalworks-0.0.1/docs/build-spec.md +124 -0
- metalworks-0.0.1/docs/build-your-own.md +84 -0
- metalworks-0.0.1/docs/claude-code.md +112 -0
- metalworks-0.0.1/docs/cli.md +134 -0
- metalworks-0.0.1/docs/configuration.md +102 -0
- metalworks-0.0.1/docs/content-seo.md +89 -0
- metalworks-0.0.1/docs/custom-chatmodel.md +74 -0
- metalworks-0.0.1/docs/custom-corpus.md +63 -0
- metalworks-0.0.1/docs/custom-store.md +55 -0
- metalworks-0.0.1/docs/data-model.md +89 -0
- metalworks-0.0.1/docs/demand-research.md +102 -0
- metalworks-0.0.1/docs/design.md +120 -0
- metalworks-0.0.1/docs/extending.md +97 -0
- metalworks-0.0.1/docs/how-it-works.md +48 -0
- metalworks-0.0.1/docs/index.md +85 -0
- metalworks-0.0.1/docs/installation.md +90 -0
- metalworks-0.0.1/docs/launch.md +92 -0
- metalworks-0.0.1/docs/mcp-tools.md +104 -0
- metalworks-0.0.1/docs/positioning.md +120 -0
- metalworks-0.0.1/docs/projects.md +111 -0
- metalworks-0.0.1/docs/protocols.md +101 -0
- metalworks-0.0.1/docs/python-sdk.md +325 -0
- metalworks-0.0.1/docs/quickstart.md +92 -0
- metalworks-0.0.1/docs/reddit-engagement.md +109 -0
- metalworks-0.0.1/docs/walkthrough.md +110 -0
- metalworks-0.0.1/docs.json +128 -0
- metalworks-0.0.1/llms.txt +68 -0
- metalworks-0.0.1/plugin/.claude-plugin/plugin.json +14 -0
- metalworks-0.0.1/plugin/.mcp.json +19 -0
- metalworks-0.0.1/plugin/README.md +81 -0
- metalworks-0.0.1/plugin/bin/launch +22 -0
- metalworks-0.0.1/plugin/hooks/hooks.json +14 -0
- metalworks-0.0.1/plugin/skills/build-spec/SKILL.md +66 -0
- metalworks-0.0.1/plugin/skills/competitor-map/SKILL.md +43 -0
- metalworks-0.0.1/plugin/skills/content-plan/SKILL.md +58 -0
- metalworks-0.0.1/plugin/skills/demand-report/SKILL.md +40 -0
- metalworks-0.0.1/plugin/skills/draft-reply/SKILL.md +43 -0
- metalworks-0.0.1/plugin/skills/find-threads/SKILL.md +32 -0
- metalworks-0.0.1/plugin/skills/generate-site/SKILL.md +59 -0
- metalworks-0.0.1/plugin/skills/launch-kit/SKILL.md +52 -0
- metalworks-0.0.1/plugin/skills/position-wedge/SKILL.md +48 -0
- metalworks-0.0.1/plugin/skills/subreddit-intel/SKILL.md +33 -0
- metalworks-0.0.1/plugin/skills/surface-and-ux/SKILL.md +48 -0
- metalworks-0.0.1/pyproject.toml +91 -0
- metalworks-0.0.1/scripts/gen_ts_types.py +334 -0
- metalworks-0.0.1/src/metalworks/__init__.py +24 -0
- metalworks-0.0.1/src/metalworks/_genai_client.py +64 -0
- metalworks-0.0.1/src/metalworks/build/__init__.py +29 -0
- metalworks-0.0.1/src/metalworks/build/_templates.py +212 -0
- metalworks-0.0.1/src/metalworks/build/scaffold.py +228 -0
- metalworks-0.0.1/src/metalworks/build/spec.py +235 -0
- metalworks-0.0.1/src/metalworks/cli/__init__.py +1226 -0
- metalworks-0.0.1/src/metalworks/cli/_demo/__init__.py +129 -0
- metalworks-0.0.1/src/metalworks/cli/_demo/scripted.py +317 -0
- metalworks-0.0.1/src/metalworks/client.py +601 -0
- metalworks-0.0.1/src/metalworks/config.py +338 -0
- metalworks-0.0.1/src/metalworks/contract/__init__.py +172 -0
- metalworks-0.0.1/src/metalworks/contract/build.py +76 -0
- metalworks-0.0.1/src/metalworks/contract/bundle.py +52 -0
- metalworks-0.0.1/src/metalworks/contract/evidence.py +65 -0
- metalworks-0.0.1/src/metalworks/contract/landscape.py +89 -0
- metalworks-0.0.1/src/metalworks/contract/launch.py +114 -0
- metalworks-0.0.1/src/metalworks/contract/marketing.py +116 -0
- metalworks-0.0.1/src/metalworks/contract/positioning.py +109 -0
- metalworks-0.0.1/src/metalworks/contract/reddit.py +216 -0
- metalworks-0.0.1/src/metalworks/contract/research.py +631 -0
- metalworks-0.0.1/src/metalworks/contract/schema/demand_report.schema.json +1119 -0
- metalworks-0.0.1/src/metalworks/contract/schema/discovery_context.schema.json +98 -0
- metalworks-0.0.1/src/metalworks/contract/schema/opportunity.schema.json +225 -0
- metalworks-0.0.1/src/metalworks/contract/schema/research_brief.schema.json +218 -0
- metalworks-0.0.1/src/metalworks/contract/site.py +94 -0
- metalworks-0.0.1/src/metalworks/contract/surface.py +129 -0
- metalworks-0.0.1/src/metalworks/discovery/__init__.py +34 -0
- metalworks-0.0.1/src/metalworks/discovery/deps.py +71 -0
- metalworks-0.0.1/src/metalworks/discovery/judge.py +99 -0
- metalworks-0.0.1/src/metalworks/discovery/prompts.py +349 -0
- metalworks-0.0.1/src/metalworks/discovery/service.py +296 -0
- metalworks-0.0.1/src/metalworks/embeddings/__init__.py +86 -0
- metalworks-0.0.1/src/metalworks/embeddings/adapters/__init__.py +5 -0
- metalworks-0.0.1/src/metalworks/embeddings/adapters/google.py +80 -0
- metalworks-0.0.1/src/metalworks/embeddings/adapters/openai.py +63 -0
- metalworks-0.0.1/src/metalworks/errors.py +147 -0
- metalworks-0.0.1/src/metalworks/llm/__init__.py +37 -0
- metalworks-0.0.1/src/metalworks/llm/adapters/__init__.py +6 -0
- metalworks-0.0.1/src/metalworks/llm/adapters/_retry.py +60 -0
- metalworks-0.0.1/src/metalworks/llm/adapters/anthropic.py +275 -0
- metalworks-0.0.1/src/metalworks/llm/adapters/google.py +310 -0
- metalworks-0.0.1/src/metalworks/llm/adapters/openai.py +260 -0
- metalworks-0.0.1/src/metalworks/llm/fake.py +121 -0
- metalworks-0.0.1/src/metalworks/llm/protocol.py +156 -0
- metalworks-0.0.1/src/metalworks/llm/structured.py +130 -0
- metalworks-0.0.1/src/metalworks/mcp/__init__.py +38 -0
- metalworks-0.0.1/src/metalworks/mcp/jobs.py +100 -0
- metalworks-0.0.1/src/metalworks/mcp/server.py +307 -0
- metalworks-0.0.1/src/metalworks/mcp/tools.py +759 -0
- metalworks-0.0.1/src/metalworks/project.py +183 -0
- metalworks-0.0.1/src/metalworks/py.typed +0 -0
- metalworks-0.0.1/src/metalworks/reddit/__init__.py +41 -0
- metalworks-0.0.1/src/metalworks/reddit/audit.py +37 -0
- metalworks-0.0.1/src/metalworks/reddit/compliance.py +287 -0
- metalworks-0.0.1/src/metalworks/reddit/fetcher.py +203 -0
- metalworks-0.0.1/src/metalworks/reddit/inbox.py +206 -0
- metalworks-0.0.1/src/metalworks/reddit/oauth.py +378 -0
- metalworks-0.0.1/src/metalworks/reddit/ratelimit.py +112 -0
- metalworks-0.0.1/src/metalworks/reddit/search.py +301 -0
- metalworks-0.0.1/src/metalworks/reddit/subreddit.py +231 -0
- metalworks-0.0.1/src/metalworks/research/__init__.py +41 -0
- metalworks-0.0.1/src/metalworks/research/arctic/__init__.py +27 -0
- metalworks-0.0.1/src/metalworks/research/arctic/api.py +227 -0
- metalworks-0.0.1/src/metalworks/research/arctic/hydration.py +202 -0
- metalworks-0.0.1/src/metalworks/research/arctic/mirror_reader.py +302 -0
- metalworks-0.0.1/src/metalworks/research/arctic/reader.py +256 -0
- metalworks-0.0.1/src/metalworks/research/deps.py +95 -0
- metalworks-0.0.1/src/metalworks/research/embedding_cache.py +53 -0
- metalworks-0.0.1/src/metalworks/research/exploration/__init__.py +111 -0
- metalworks-0.0.1/src/metalworks/research/exploration/corpus_shape.py +106 -0
- metalworks-0.0.1/src/metalworks/research/exploration/embedding_triage.py +235 -0
- metalworks-0.0.1/src/metalworks/research/exploration/llm_classifier.py +250 -0
- metalworks-0.0.1/src/metalworks/research/landscape.py +335 -0
- metalworks-0.0.1/src/metalworks/research/launch.py +312 -0
- metalworks-0.0.1/src/metalworks/research/marketing.py +193 -0
- metalworks-0.0.1/src/metalworks/research/pipeline.py +300 -0
- metalworks-0.0.1/src/metalworks/research/planner/__init__.py +33 -0
- metalworks-0.0.1/src/metalworks/research/planner/auto.py +84 -0
- metalworks-0.0.1/src/metalworks/research/planner/brief_assembler.py +163 -0
- metalworks-0.0.1/src/metalworks/research/planner/decision_brief.py +125 -0
- metalworks-0.0.1/src/metalworks/research/planner/llm_planner.py +482 -0
- metalworks-0.0.1/src/metalworks/research/planner/question_bank.py +200 -0
- metalworks-0.0.1/src/metalworks/research/planner/store.py +73 -0
- metalworks-0.0.1/src/metalworks/research/planner/subreddit_picker.py +151 -0
- metalworks-0.0.1/src/metalworks/research/site.py +420 -0
- metalworks-0.0.1/src/metalworks/research/surface.py +303 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/__init__.py +224 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/audience.py +213 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/cluster_ranker.py +234 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/embed_group.py +65 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/loader.py +98 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/market.py +39 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/positioning.py +295 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/pricing.py +149 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/segments.py +125 -0
- metalworks-0.0.1/src/metalworks/research/synthesis/verdict.py +45 -0
- metalworks-0.0.1/src/metalworks/research/triangulate/__init__.py +24 -0
- metalworks-0.0.1/src/metalworks/research/triangulate/confidence_weighter.py +81 -0
- metalworks-0.0.1/src/metalworks/research/triangulate/triangulator.py +359 -0
- metalworks-0.0.1/src/metalworks/research/types.py +176 -0
- metalworks-0.0.1/src/metalworks/research/web.py +394 -0
- metalworks-0.0.1/src/metalworks/runs.py +97 -0
- metalworks-0.0.1/src/metalworks/search/__init__.py +46 -0
- metalworks-0.0.1/src/metalworks/search/adapters/__init__.py +5 -0
- metalworks-0.0.1/src/metalworks/search/adapters/exa.py +66 -0
- metalworks-0.0.1/src/metalworks/search/adapters/tavily.py +63 -0
- metalworks-0.0.1/src/metalworks/stores/__init__.py +47 -0
- metalworks-0.0.1/src/metalworks/stores/crypto.py +70 -0
- metalworks-0.0.1/src/metalworks/stores/filestore.py +66 -0
- metalworks-0.0.1/src/metalworks/stores/memory.py +188 -0
- metalworks-0.0.1/src/metalworks/stores/repos.py +238 -0
- metalworks-0.0.1/src/metalworks/stores/sqlite.py +391 -0
- metalworks-0.0.1/src/metalworks/stores/vectors.py +74 -0
- metalworks-0.0.1/src/metalworks/testing/__init__.py +210 -0
- metalworks-0.0.1/tests/test_adapters.py +546 -0
- metalworks-0.0.1/tests/test_arctic_mirror_reader.py +276 -0
- metalworks-0.0.1/tests/test_build.py +481 -0
- metalworks-0.0.1/tests/test_cli.py +189 -0
- metalworks-0.0.1/tests/test_client.py +160 -0
- metalworks-0.0.1/tests/test_config_resolution.py +187 -0
- metalworks-0.0.1/tests/test_contract.py +89 -0
- metalworks-0.0.1/tests/test_crypto.py +54 -0
- metalworks-0.0.1/tests/test_discovery_prompts.py +100 -0
- metalworks-0.0.1/tests/test_discovery_service.py +389 -0
- metalworks-0.0.1/tests/test_embedding_cache.py +81 -0
- metalworks-0.0.1/tests/test_embeddings.py +38 -0
- metalworks-0.0.1/tests/test_embeddings_store.py +101 -0
- metalworks-0.0.1/tests/test_evidence.py +179 -0
- metalworks-0.0.1/tests/test_filestore.py +60 -0
- metalworks-0.0.1/tests/test_imports.py +58 -0
- metalworks-0.0.1/tests/test_llm_protocol.py +120 -0
- metalworks-0.0.1/tests/test_mcp_server.py +219 -0
- metalworks-0.0.1/tests/test_project.py +119 -0
- metalworks-0.0.1/tests/test_public_testing_module.py +34 -0
- metalworks-0.0.1/tests/test_reddit_compliance.py +107 -0
- metalworks-0.0.1/tests/test_reddit_fetcher.py +148 -0
- metalworks-0.0.1/tests/test_reddit_inbox.py +137 -0
- metalworks-0.0.1/tests/test_reddit_oauth.py +194 -0
- metalworks-0.0.1/tests/test_reddit_ratelimit.py +76 -0
- metalworks-0.0.1/tests/test_reddit_search.py +140 -0
- metalworks-0.0.1/tests/test_reddit_subreddit.py +143 -0
- metalworks-0.0.1/tests/test_research_arctic.py +467 -0
- metalworks-0.0.1/tests/test_research_exploration.py +258 -0
- metalworks-0.0.1/tests/test_research_landscape.py +259 -0
- metalworks-0.0.1/tests/test_research_launch.py +272 -0
- metalworks-0.0.1/tests/test_research_marketing.py +265 -0
- metalworks-0.0.1/tests/test_research_pipeline_e2e.py +268 -0
- metalworks-0.0.1/tests/test_research_planner.py +311 -0
- metalworks-0.0.1/tests/test_research_positioning.py +345 -0
- metalworks-0.0.1/tests/test_research_site.py +398 -0
- metalworks-0.0.1/tests/test_research_surface.py +273 -0
- metalworks-0.0.1/tests/test_research_synthesis.py +394 -0
- metalworks-0.0.1/tests/test_research_triangulate.py +264 -0
- metalworks-0.0.1/tests/test_research_web.py +200 -0
- metalworks-0.0.1/tests/test_runs.py +153 -0
- metalworks-0.0.1/tests/test_stores_conformance.py +244 -0
- metalworks-0.0.1/tests/test_vertex_support.py +241 -0
- metalworks-0.0.1/ts/contract.ts +722 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lab2a",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Lab2A",
|
|
5
|
+
"email": "hello@lab2a.dev"
|
|
6
|
+
},
|
|
7
|
+
"description": "Lab2A plugins. metalworks: Reddit demand research and engagement from Claude Code.",
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "metalworks",
|
|
11
|
+
"source": "./plugin",
|
|
12
|
+
"description": "Reddit demand reports, thread discovery, and gated reply drafting via the metalworks MCP server. Zero-key data tools plus key-gated pipelines.",
|
|
13
|
+
"repository": "https://github.com/Lab2A/metalworks",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"keywords": ["reddit", "marketing", "research", "demand", "mcp"]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: test (py${{ matrix.python }}, ${{ matrix.extras }})
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python: ["3.11", "3.12", "3.13"]
|
|
16
|
+
extras: ["bare", "all"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python }}
|
|
22
|
+
- run: uv venv --clear
|
|
23
|
+
- name: Install (bare)
|
|
24
|
+
if: matrix.extras == 'bare'
|
|
25
|
+
run: uv pip install -e ".[dev]"
|
|
26
|
+
- name: Install (all extras)
|
|
27
|
+
if: matrix.extras == 'all'
|
|
28
|
+
run: uv pip install -e ".[all,dev]"
|
|
29
|
+
# Offline by default: pytest-socket blocks all network in CI tests.
|
|
30
|
+
- name: Test
|
|
31
|
+
run: uv run pytest -q
|
|
32
|
+
- name: Console script smoke
|
|
33
|
+
run: uv run metalworks version
|
|
34
|
+
|
|
35
|
+
lint:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- uses: astral-sh/setup-uv@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: "3.12"
|
|
42
|
+
- run: uv venv --clear
|
|
43
|
+
# All extras so pyright resolves real provider SDK imports (crypto,
|
|
44
|
+
# supabase, etc.). Adapters keep SDK objects behind Any regardless, but
|
|
45
|
+
# the few real `from x import y` lines need x present to typecheck.
|
|
46
|
+
- run: uv pip install -e ".[all,dev]"
|
|
47
|
+
- run: uv run ruff check .
|
|
48
|
+
- run: uv run ruff format --check .
|
|
49
|
+
- run: uv run pyright
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Tag vX.Y.Z -> build -> publish to PyPI via trusted publishing (OIDC).
|
|
4
|
+
# Requires the PyPI project to have Lab2A/metalworks configured as a
|
|
5
|
+
# trusted publisher for this workflow file.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
- run: uv build
|
|
20
|
+
- uses: actions/upload-artifact@v4
|
|
21
|
+
with:
|
|
22
|
+
name: dist
|
|
23
|
+
path: dist/
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
needs: build
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment: pypi
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/download-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
/build/ # packaging output only — anchored so it never shadows src/metalworks/build/
|
|
7
|
+
.venv/
|
|
8
|
+
.python-version
|
|
9
|
+
|
|
10
|
+
# Tooling caches
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.pyright/
|
|
14
|
+
.coverage
|
|
15
|
+
|
|
16
|
+
# Env / secrets — never committed
|
|
17
|
+
.env
|
|
18
|
+
.env.*
|
|
19
|
+
!.env.example
|
|
20
|
+
|
|
21
|
+
# OS
|
|
22
|
+
.DS_Store
|
|
23
|
+
|
|
24
|
+
# gstack local runtime state (browse daemon, tokens) — never committed
|
|
25
|
+
.gstack/
|
|
26
|
+
|
|
27
|
+
# Gate / scratch run artifacts (local evidence, not committed)
|
|
28
|
+
.gate/
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to metalworks are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims
|
|
5
|
+
to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once it
|
|
6
|
+
reaches 1.0. Below 1.0, anything outside `metalworks.contract` and the MCP tool
|
|
7
|
+
contracts may change in any release.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
Pre-release foundations. Nothing here is stable yet.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Contract** (`metalworks.contract`): Pydantic models for the research and
|
|
16
|
+
Reddit surfaces (`DemandReport`, `ResearchBrief`, `Opportunity`,
|
|
17
|
+
`RedditPost`, `RedditComment`, `SubredditIntel`, `InboxItem`,
|
|
18
|
+
`ComplianceVerdict`, `DiscoveryContext`, ...), plus a TypeScript-type +
|
|
19
|
+
JSON-schema generator with diff-gated snapshots.
|
|
20
|
+
- **Protocols + adapters**: own versioned `ChatModel` / `GroundedChatModel` /
|
|
21
|
+
`SearchProvider` / `EmbeddingProvider` protocols with a structured-output
|
|
22
|
+
ladder, plus thin adapters over official SDKs behind extras
|
|
23
|
+
(`anthropic`, `openai`, `google`, `exa`, `tavily`). Google grounding converts
|
|
24
|
+
provider byte offsets to character offsets so non-ASCII provenance stays
|
|
25
|
+
correct.
|
|
26
|
+
- **Stores**: typed repos (`CorpusRepo`, `BriefRepo`, `RunRepo`, `AccountRepo`,
|
|
27
|
+
`OpportunityRepo`, `InboxRepo`) with `MemoryStores` + `SqliteStores` in core
|
|
28
|
+
(hosted backends live downstream via the same protocols). `CorpusRepo` carries
|
|
29
|
+
a local vector memory — embeddings as float64 blobs + brute-force numpy cosine,
|
|
30
|
+
no new core dependency. New `ArtifactStore` protocol + files-first `FileStore`
|
|
31
|
+
for Tier-2 pillar artifacts. `TokenCipher` for OAuth tokens at rest. Public
|
|
32
|
+
conformance suite in `metalworks.testing`.
|
|
33
|
+
- **Research vertical** (`metalworks.research`): brief planner, Arctic Shift
|
|
34
|
+
corpus reader (HF Parquet submissions + live API comments), hybrid triage,
|
|
35
|
+
clustered synthesis with exact-matched quotes, web research (grounded +
|
|
36
|
+
external), cross-stream triangulation, and `run_research` end to end.
|
|
37
|
+
- **Reddit core** (`metalworks.reddit`): rate limiter (token bucket honoring
|
|
38
|
+
Reddit's headers), OAuth + posting on httpx with typed errors, search,
|
|
39
|
+
metrics, inbox classification, subreddit intel, and a deterministic
|
|
40
|
+
compliance gate.
|
|
41
|
+
- **Project layer** (`.metalworks/`): a project is a directory like `.git`.
|
|
42
|
+
`metalworks init` creates it with a `project.json` manifest, a gitignored
|
|
43
|
+
`corpus.db` (corpus + embeddings), committed `runs/<id>/research.{md,json}`,
|
|
44
|
+
and an `artifacts/` tree. A casual `Metalworks().research(...)` with no project
|
|
45
|
+
present leaves zero footprint. Non-secret config lives in
|
|
46
|
+
`.metalworks/config.toml` (a legacy cwd `metalworks.toml` is still read).
|
|
47
|
+
- **Evidence chain**: content-addressed stable ids on `QuoteCitation` /
|
|
48
|
+
`WebFinding` / `PriceEvidence`, a `metalworks.contract.evidence` module
|
|
49
|
+
(`EvidenceRef` / `EvidenceRecord`), and a `DemandReport.evidence` accessor —
|
|
50
|
+
the spine downstream pillars resolve grounded claims against.
|
|
51
|
+
- **Embedding reuse**: the research pipeline persists corpus embeddings and
|
|
52
|
+
reuses them across runs (keyed on the embedding model identity), so a re-run on
|
|
53
|
+
the same corpus re-embeds only the query.
|
|
54
|
+
- **Front door**: `Metalworks().research(question, subreddits=...)` returns a
|
|
55
|
+
frozen `Research` bundle, and the CLI gains `metalworks research run
|
|
56
|
+
--question "..."` (no `brief.json` round-trip for the common case).
|
|
57
|
+
- **Vertex AI**: the Google chat + embedding adapters authenticate via Vertex
|
|
58
|
+
(Application Default Credentials) when `GOOGLE_GENAI_USE_VERTEXAI=true`
|
|
59
|
+
(`VERTEX_PROJECT_ID`/`GOOGLE_CLOUD_PROJECT` + `VERTEX_LOCATION`), not just an
|
|
60
|
+
API key — `build_genai_client` in `metalworks._genai_client`; provider
|
|
61
|
+
resolution routes to Google under Vertex even with no `GOOGLE_API_KEY`.
|
|
62
|
+
- **Marketing site (Pillar E)**: `build_marketing_site(deps, report, positioning=None)
|
|
63
|
+
-> MarketingSite` + `render_site_html(site, report)` (`metalworks.research.site`).
|
|
64
|
+
Top 3 clusters by demand_score; one constrained LLM call assigns each a
|
|
65
|
+
SiteSection role and picks a VERBATIM fragment; the builder re-runs exact-match
|
|
66
|
+
against the cluster's real `QuoteCitation.text` and DROPS any section that
|
|
67
|
+
isn't a verbatim substring (no-quote-no-section). Connective copy ships
|
|
68
|
+
`provenance="connective"` with no refs and is gated claim-free. Hero = the
|
|
69
|
+
highest-distinct-author cluster. `render_site_html` emits one self-contained
|
|
70
|
+
`index.html` with a permalink footnote (+ `data-evidence`) per verbatim
|
|
71
|
+
section. New contract `metalworks.contract.site` (`MarketingSite` /
|
|
72
|
+
`SiteSection`). CLI `metalworks research site`, MCP Tier-2 `site_render`, skill
|
|
73
|
+
`generate-site`.
|
|
74
|
+
- **Launch (Pillar F)**: `build_launch_assets(deps, report, positioning) ->
|
|
75
|
+
list[LaunchAsset]` + `plan_channels(report) -> ChannelPlan`
|
|
76
|
+
(`metalworks.research.launch`). Refuses (returns []) on a no-go report
|
|
77
|
+
(negative verdict or no cluster ≥2 distinct authors). One LLM call per surface
|
|
78
|
+
(Product Hunt / Show HN / X thread) → title + body + variants + claims; each
|
|
79
|
+
claim is grounded to a real quote and carries a `ClaimCitation` with char-offset
|
|
80
|
+
spans into the body (`body[span_start:span_end] == claim_text`) — unresolvable
|
|
81
|
+
claims are dropped. Bodies run through the compliance gate best-effort.
|
|
82
|
+
Drafting-only: `plan_channels` marks every `ChannelStep` `requires_human` +
|
|
83
|
+
`posting_gated`; the library never posts. New contract
|
|
84
|
+
`metalworks.contract.launch` (`LaunchAsset` / `ClaimCitation` / `ChannelPlan` /
|
|
85
|
+
`ChannelStep`). CLI `metalworks research launch`, MCP Tier-2 `launch_assets_build`
|
|
86
|
+
+ Tier-1 `channel_plan_build`, skill `launch-kit`.
|
|
87
|
+
- **Content/SEO (Pillar G)**: `content_plan_from_report(report) -> ContentPlan`
|
|
88
|
+
(`metalworks.research.marketing`) — PURE deterministic, zero-key, no LLM. One
|
|
89
|
+
`ContentPage` per cluster (normalized target_phrase, heuristic page_kind,
|
|
90
|
+
real-count `stat_anchors`, FAQ from `ResearchBrief.must_address` verbatim) plus
|
|
91
|
+
a `CitationStrategy` whose `reddit_targets` are real quote permalinks.
|
|
92
|
+
`render_content_markdown` + `render_faq_jsonld` (a mechanical FAQPage stub).
|
|
93
|
+
Makes no ranking promises; never invents a keyword or quote. New contract
|
|
94
|
+
`metalworks.contract.marketing` (`ContentPlan` / `ContentPage` / `FaqItem` /
|
|
95
|
+
`CitationStrategy`). CLI `metalworks research content-plan`, MCP **Tier-1**
|
|
96
|
+
(zero-key) `content_plan_from_report`, skill `content-plan`.
|
|
97
|
+
- **Build (Pillar D)**: `build_spec_from_report(deps, report, positioning=None,
|
|
98
|
+
surface="web", *, stack="empty") -> BuildSpec` + `scaffold(spec, report, dest,
|
|
99
|
+
*, base) -> list[Path]` (`metalworks.build`). One LLM call maps demand clusters
|
|
100
|
+
to candidate features; grounding is DETERMINISTIC — each feature is attached to
|
|
101
|
+
its `source_cluster_rank`'s verbatim quotes and DROPPED if that cluster is
|
|
102
|
+
invalid or quote-less (no-cite-no-feature), so the model cannot smuggle in an
|
|
103
|
+
un-grounded feature. Personas derive from the report's audience segments;
|
|
104
|
+
pricing tiers copy through from the report's price evidence (never recomputed).
|
|
105
|
+
An infra error (404/auth) propagates rather than being relabelled a thin-demand
|
|
106
|
+
`partial`. `scaffold` writes a deterministic build harness for the user's OWN
|
|
107
|
+
coding agent — `CLAUDE.md` (cite-or-die Rule 0), `docs/SPEC.md`, a frozen
|
|
108
|
+
`docs/EVIDENCE.md` quote+permalink table, a build-pack of skills
|
|
109
|
+
(`scaffold-startup` / `spec-from-report` / `cite-or-die`), a `cite_or_die.py`
|
|
110
|
+
PostToolUse lint, and `.mcp.json` — but writes NO product code (`--base` is a
|
|
111
|
+
stack hint, not vendored boilerplate). New contract `metalworks.contract.build`
|
|
112
|
+
(`BuildSpec` / `FeatureSpec` / `BuildPersona` / `PricingTier`). CLI `metalworks
|
|
113
|
+
build init`, MCP **Tier-2** `build_spec`, skill `build-spec`.
|
|
114
|
+
- **Surface + UX (Pillar C, Design stage)**: `decide_surface(deps, report,
|
|
115
|
+
positioning) -> SurfaceRecommendation` + `build_ux_skeleton(deps, report,
|
|
116
|
+
positioning, surface) -> UxSkeleton` (`metalworks.research.surface`). A FIXED
|
|
117
|
+
five-dimension rubric (where-are-the-users, technical sophistication, usage
|
|
118
|
+
frequency, realtime/hardware, distribution) drives the surface pick; one LLM
|
|
119
|
+
call phrases each dimension's finding + the chosen surface, and the service
|
|
120
|
+
GROUNDS each by cosine-matching to the report's real evidence — a dimension
|
|
121
|
+
with no match is marked `is_assumption`, and `confidence` is service-assigned
|
|
122
|
+
from grounded coverage. UX screens with no backing voice ship `validated=False`
|
|
123
|
+
(an explicit hypothesis). Text + structure only (no pixels); the `DesignBrief`
|
|
124
|
+
handoff is explicitly ungrounded. New contract `metalworks.contract.surface`
|
|
125
|
+
(`SurfaceRecommendation` / `UxSkeleton` / `RubricDimension` / `Screen` /
|
|
126
|
+
`TradeOff` / `DesignBrief`); `Research.competitors` and `.positioning` are both
|
|
127
|
+
real fields now. Surfaced via `metalworks surface <report_id>` CLI, the
|
|
128
|
+
synchronous `surface_recommend` + `ux_skeleton_build` MCP tools, and the
|
|
129
|
+
`surface-and-ux` skill.
|
|
130
|
+
- **Landscape (Pillar A)**: `run_competitor_map(deps, report) -> CompetitorMap`
|
|
131
|
+
(`metalworks.research.landscape`) maps the competitive set — direct, adjacent,
|
|
132
|
+
and the mandatory status-quo "do nothing" alternative — with an exploitable,
|
|
133
|
+
EVIDENCED gap per competitor. Four deterministic stages: grounded enumeration
|
|
134
|
+
(names with zero grounding chunks dropped; degrades to an ungrounded structured
|
|
135
|
+
call marked `partial`); per-competitor strength/gap harvest; cosine
|
|
136
|
+
complaint-matching of each gap against the report's real evidence (cluster
|
|
137
|
+
quotes first, then web findings); assemble, dropping any gap with no resolvable
|
|
138
|
+
evidence (no-quote-no-gap). `severity` is service-assigned from the matched
|
|
139
|
+
complaint's distinct-author breadth, never LLM. The status-quo alternative is
|
|
140
|
+
built deterministically from the top clusters (always verbatim-grounded). New
|
|
141
|
+
contract `metalworks.contract.landscape` (`CompetitorMap` / `Competitor` /
|
|
142
|
+
`GapClaim` / `StrengthClaim`), every gap an `EvidenceRef`; `Research.competitors`
|
|
143
|
+
is now a real optional field. Surfaced via the `metalworks competitor-map
|
|
144
|
+
<report_id>` CLI, the synchronous `competitor_map_from_report` MCP tool, and the
|
|
145
|
+
`competitor-map` skill.
|
|
146
|
+
- **Positioning (Pillar B)**: `build_positioning_brief(deps, report) -> PositioningBrief`
|
|
147
|
+
(`metalworks.research.synthesis.positioning`) turns a demand report into a
|
|
148
|
+
grounded Dunford wedge + price hypothesis. Wedge SELECTION is deterministic —
|
|
149
|
+
it stands on an `InsightCluster` the web stream is `silent_web`/`disagree` on at
|
|
150
|
+
≥ MEDIUM signal (a pain competitors miss), ranked by `demand_score`; no white
|
|
151
|
+
space → an honest null brief. Exactly one LLM call phrases the three free-text
|
|
152
|
+
slots (constrained to the Dunford template) and a second pass verifies each
|
|
153
|
+
clause is entailed by its cited quotes (marks the brief `partial` if not). The
|
|
154
|
+
price band is copied through from `PriceFinding`, never recomputed. New
|
|
155
|
+
contract `metalworks.contract.positioning` (`PositioningBrief` / `WedgeClaim` /
|
|
156
|
+
`PriceHypothesis`), every slot an `EvidenceRef`; `Research.positioning` is now a
|
|
157
|
+
real optional field. Surfaced via the `metalworks position <report_id>` CLI,
|
|
158
|
+
the synchronous `positioning_from_report` MCP tool, and the `position-wedge` skill.
|
|
159
|
+
- **Supabase mirror reader**: `ArcticMirrorReader` (`metalworks[supabase]`) reads
|
|
160
|
+
the Arctic submission corpus from a Supabase Storage bucket — months from the
|
|
161
|
+
`arctic_shift_pulls` table, shards listed and signed at query time, DuckDB
|
|
162
|
+
reading the signed URLs with `WHERE subreddit`/`id IN` pushdown. A faster
|
|
163
|
+
alternative to the HF mirror that removes HF as a runtime dependency;
|
|
164
|
+
implements `CorpusReader` and is selected at runtime with
|
|
165
|
+
`ARCTIC_SHIFT_SOURCE=mirror`.
|
|
166
|
+
|
|
167
|
+
### Changed
|
|
168
|
+
|
|
169
|
+
- `Metalworks.research()` now returns a `Research` bundle instead of a bare
|
|
170
|
+
`DemandReport`; the report is on `.demand` and the grounded evidence on
|
|
171
|
+
`.evidence`. This stabilizes the stage-1 front-door shape before the 0.1.0 tag.
|
|
172
|
+
- Renamed the low-level discovery export `generate_reply` → `draft_reply` (the
|
|
173
|
+
MCP tool name is unchanged).
|
|
174
|
+
- Cut `SupabaseStores` from the OSS core; hosted store backends bind to the
|
|
175
|
+
same repo protocols downstream. The `[supabase]` extra now scopes the Arctic
|
|
176
|
+
mirror reader (above) instead of the dropped stores.
|
|
177
|
+
- The Google adapters respect Vertex's request ceilings: embeddings are batched
|
|
178
|
+
at 100 instances/request (Vertex caps at 250) and chat clamps
|
|
179
|
+
`max_output_tokens` to 65536, so large triage/synthesis calls no longer 400.
|
|
180
|
+
- Tightened package public surfaces: demoted internal plumbing out of the
|
|
181
|
+
`reddit` / `research` / `discovery` package `__all__`s (still importable from
|
|
182
|
+
their submodules).
|
|
183
|
+
|
|
184
|
+
### Notes
|
|
185
|
+
|
|
186
|
+
- A bare `import metalworks` pulls in no provider SDKs; CI asserts this.
|
|
187
|
+
- The fabricated-persona / account-backstory tooling from the source pipeline
|
|
188
|
+
was deliberately not open-sourced. See `USAGE_POLICY.md`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and maintainers pledge to make participation in
|
|
6
|
+
the metalworks project a harassment-free experience for everyone, regardless of
|
|
7
|
+
age, body size, visible or invisible disability, ethnicity, sex
|
|
8
|
+
characteristics, gender identity and expression, level of experience,
|
|
9
|
+
education, socio-economic status, nationality, personal appearance, race,
|
|
10
|
+
religion, or sexual identity and orientation.
|
|
11
|
+
|
|
12
|
+
## Our standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to a positive environment:
|
|
15
|
+
|
|
16
|
+
- Being respectful of differing opinions, viewpoints, and experiences.
|
|
17
|
+
- Giving and gracefully accepting constructive feedback.
|
|
18
|
+
- Accepting responsibility, apologizing to those affected by our mistakes, and
|
|
19
|
+
learning from the experience.
|
|
20
|
+
- Focusing on what is best for the community.
|
|
21
|
+
|
|
22
|
+
Examples of unacceptable behavior:
|
|
23
|
+
|
|
24
|
+
- Sexualized language or imagery, and sexual attention or advances of any kind.
|
|
25
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks.
|
|
26
|
+
- Public or private harassment.
|
|
27
|
+
- Publishing others' private information without explicit permission.
|
|
28
|
+
- Other conduct which could reasonably be considered inappropriate in a
|
|
29
|
+
professional setting.
|
|
30
|
+
|
|
31
|
+
## Enforcement
|
|
32
|
+
|
|
33
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
34
|
+
reported to the maintainers at **conduct@lab2a.dev** (update this address).
|
|
35
|
+
All complaints will be reviewed and investigated promptly and fairly. The
|
|
36
|
+
maintainers are obligated to respect the privacy and security of the reporter.
|
|
37
|
+
|
|
38
|
+
## Attribution
|
|
39
|
+
|
|
40
|
+
This Code of Conduct is adapted from the
|
|
41
|
+
[Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Contributing to metalworks
|
|
2
|
+
|
|
3
|
+
Thanks for helping. metalworks is pre-release, so the most useful contributions
|
|
4
|
+
right now are provider adapters, storage backends, bug reports against the
|
|
5
|
+
shipped pieces, and docs fixes. Please read [USAGE_POLICY.md](USAGE_POLICY.md)
|
|
6
|
+
and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) first.
|
|
7
|
+
|
|
8
|
+
## Dev setup
|
|
9
|
+
|
|
10
|
+
We use [uv](https://docs.astral.sh/uv/).
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv venv
|
|
14
|
+
uv pip install -e ".[all,dev]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`[all,dev]` gives you every provider adapter plus the test and lint toolchain.
|
|
18
|
+
|
|
19
|
+
## The gate
|
|
20
|
+
|
|
21
|
+
Everything must pass before a PR lands:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ruff check .
|
|
25
|
+
ruff format --check .
|
|
26
|
+
pyright
|
|
27
|
+
pytest -q
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`pyright` runs in strict mode over `src`. The test suite runs offline:
|
|
31
|
+
`pytest-socket` disables network, recorded fixtures (respx cassettes, committed
|
|
32
|
+
Parquet shards, recorded grounding responses) stand in for live services.
|
|
33
|
+
|
|
34
|
+
## The extras model
|
|
35
|
+
|
|
36
|
+
Core depends only on pydantic, httpx, typing-extensions, typer, and rich.
|
|
37
|
+
Everything that pulls a provider SDK or a heavy dependency (duckdb, supabase,
|
|
38
|
+
the LLM SDKs) lives behind an extra.
|
|
39
|
+
|
|
40
|
+
Provider SDKs are **lazy-imported behind their extra**. The import happens
|
|
41
|
+
inside the method that needs it, not at module top level, so:
|
|
42
|
+
|
|
43
|
+
- `import metalworks` never requires a provider SDK. CI asserts a bare import
|
|
44
|
+
pulls in zero provider modules.
|
|
45
|
+
- A missing extra raises `MissingExtraError` carrying the exact
|
|
46
|
+
`pip install "metalworks[...]"` command, instead of a raw `ModuleNotFoundError`.
|
|
47
|
+
|
|
48
|
+
When you add an import of an optional dependency, wrap it:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
try:
|
|
52
|
+
import duckdb
|
|
53
|
+
except ImportError as exc:
|
|
54
|
+
raise MissingExtraError("arctic", package="duckdb") from exc
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Adding a provider adapter
|
|
58
|
+
|
|
59
|
+
1. Implement the protocol. For a chat adapter, implement `ChatModel`
|
|
60
|
+
(`complete_text`, `complete_structured`, the `model_id` /
|
|
61
|
+
`capabilities` / `protocol_version` attributes). Add `complete_grounded` and
|
|
62
|
+
set `capabilities.native_grounding` only if the provider does native web
|
|
63
|
+
grounding, and carry the full `GroundedResult` provenance (chunks plus
|
|
64
|
+
character-offset supports, converting from the provider's offsets).
|
|
65
|
+
2. Lazy-import the SDK behind your extra and raise `MissingExtraError` when it
|
|
66
|
+
is absent.
|
|
67
|
+
3. Read credentials from the environment, raise `MissingKeyError` (naming the
|
|
68
|
+
env var) when absent. Never read keys from config or at import time.
|
|
69
|
+
4. Run the conformance suite against your adapter:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from metalworks.testing import FakeChatModel # reference behavior
|
|
73
|
+
# repo backends:
|
|
74
|
+
from metalworks.testing import check_all_repos
|
|
75
|
+
check_all_repos(MyBackend())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`check_all_repos` includes the >1000-row pagination case that catches
|
|
79
|
+
silently-truncating backends. Match the semantics the fakes and built-in
|
|
80
|
+
backends demonstrate.
|
|
81
|
+
|
|
82
|
+
See [docs/how-to-custom-chatmodel.md](docs/how-to-custom-chatmodel.md) for a
|
|
83
|
+
worked example.
|
|
84
|
+
|
|
85
|
+
## Rules that are not negotiable
|
|
86
|
+
|
|
87
|
+
- **No module-level singletons.** No constructing clients, repos, or models at
|
|
88
|
+
import time.
|
|
89
|
+
- **No env reads at import.** Read environment variables inside functions, never
|
|
90
|
+
at module scope. A clean-env import test walks every submodule and asserts zero
|
|
91
|
+
exceptions and zero network with no environment variables set.
|
|
92
|
+
- **Protocols are versioned as a unit.** Additive keyword-only parameters with
|
|
93
|
+
defaults are a minor bump; anything breaking is a major bump.
|
|
94
|
+
|
|
95
|
+
## CI matrix
|
|
96
|
+
|
|
97
|
+
CI runs on Python 3.11, 3.12, and 3.13, each against a bare install and an
|
|
98
|
+
`[all]` install. The bare leg proves core imports clean with no provider
|
|
99
|
+
dependencies; the `[all]` leg runs the full suite.
|
|
100
|
+
|
|
101
|
+
## Releases and version pinning
|
|
102
|
+
|
|
103
|
+
The Claude Code plugin launches the MCP server with a version-pinned `uvx`
|
|
104
|
+
command. The plugin and the PyPI package bump in lockstep: when you cut a PyPI
|
|
105
|
+
release, the plugin's pinned version moves with it, in the same release. Do not
|
|
106
|
+
ship one without the other.
|
|
107
|
+
|
|
108
|
+
Follow the deprecation policy: emit a `DeprecationWarning` at least one minor
|
|
109
|
+
version before removing anything, and call out breaking changes in
|
|
110
|
+
[CHANGELOG.md](CHANGELOG.md) with migration notes.
|
metalworks-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lab2A
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|