api-medic 1.0.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.
- api_medic-1.0.0/.gitattributes +23 -0
- api_medic-1.0.0/.github/workflows/ci.yml +139 -0
- api_medic-1.0.0/.github/workflows/deploy-demo.yml +103 -0
- api_medic-1.0.0/.github/workflows/publish-pypi.yml +78 -0
- api_medic-1.0.0/.gitignore +85 -0
- api_medic-1.0.0/CHANGELOG.md +78 -0
- api_medic-1.0.0/CLAUDE.md +62 -0
- api_medic-1.0.0/LICENSE +21 -0
- api_medic-1.0.0/Makefile +52 -0
- api_medic-1.0.0/PKG-INFO +135 -0
- api_medic-1.0.0/README.md +75 -0
- api_medic-1.0.0/deploy/README.md +111 -0
- api_medic-1.0.0/deploy/iam/inline-policy.json +247 -0
- api_medic-1.0.0/deploy/iam/trust-policy.json +19 -0
- api_medic-1.0.0/deploy/lambda/Makefile +16 -0
- api_medic-1.0.0/deploy/lambda/handler.py +173 -0
- api_medic-1.0.0/deploy/lambda/requirements.txt +17 -0
- api_medic-1.0.0/deploy/pypi-trusted-publishing.md +89 -0
- api_medic-1.0.0/deploy/samconfig.toml +12 -0
- api_medic-1.0.0/deploy/template.yaml +237 -0
- api_medic-1.0.0/docs/api-medic-ui-reference.html +281 -0
- api_medic-1.0.0/docs/architecture.md +511 -0
- api_medic-1.0.0/docs/examples/www.markandrewmarquez.com.har +4065 -0
- api_medic-1.0.0/extension/.gitkeep +0 -0
- api_medic-1.0.0/frontend/.gitignore +10 -0
- api_medic-1.0.0/frontend/index.html +12 -0
- api_medic-1.0.0/frontend/package-lock.json +4285 -0
- api_medic-1.0.0/frontend/package.json +33 -0
- api_medic-1.0.0/frontend/postcss.config.js +6 -0
- api_medic-1.0.0/frontend/src/App.test.tsx +104 -0
- api_medic-1.0.0/frontend/src/App.tsx +82 -0
- api_medic-1.0.0/frontend/src/components/FixtureBrowser.tsx +80 -0
- api_medic-1.0.0/frontend/src/components/HarUpload.test.tsx +100 -0
- api_medic-1.0.0/frontend/src/components/HarUpload.tsx +187 -0
- api_medic-1.0.0/frontend/src/components/ReportView.test.tsx +109 -0
- api_medic-1.0.0/frontend/src/components/ReportView.tsx +66 -0
- api_medic-1.0.0/frontend/src/components/RequestComposer.test.tsx +77 -0
- api_medic-1.0.0/frontend/src/components/RequestComposer.tsx +161 -0
- api_medic-1.0.0/frontend/src/components/Tabs.tsx +44 -0
- api_medic-1.0.0/frontend/src/components/report/FindingCard.tsx +43 -0
- api_medic-1.0.0/frontend/src/components/report/HeaderBar.tsx +33 -0
- api_medic-1.0.0/frontend/src/components/report/MetricsGrid.tsx +83 -0
- api_medic-1.0.0/frontend/src/components/report/RequestLine.tsx +38 -0
- api_medic-1.0.0/frontend/src/components/report/SeverityPill.tsx +23 -0
- api_medic-1.0.0/frontend/src/components/report/TimingWaterfall.tsx +54 -0
- api_medic-1.0.0/frontend/src/index.css +14 -0
- api_medic-1.0.0/frontend/src/lib/api.test.ts +81 -0
- api_medic-1.0.0/frontend/src/lib/api.ts +89 -0
- api_medic-1.0.0/frontend/src/lib/fixtures-contract.test.ts +53 -0
- api_medic-1.0.0/frontend/src/lib/fixtures.test.ts +69 -0
- api_medic-1.0.0/frontend/src/lib/fixtures.ts +27 -0
- api_medic-1.0.0/frontend/src/lib/harStrip.test.ts +138 -0
- api_medic-1.0.0/frontend/src/lib/harStrip.ts +143 -0
- api_medic-1.0.0/frontend/src/lib/types.ts +110 -0
- api_medic-1.0.0/frontend/src/main.tsx +13 -0
- api_medic-1.0.0/frontend/src/test/setup.ts +1 -0
- api_medic-1.0.0/frontend/src/vite-env.d.ts +1 -0
- api_medic-1.0.0/frontend/tailwind.config.js +35 -0
- api_medic-1.0.0/frontend/tsconfig.json +27 -0
- api_medic-1.0.0/frontend/tsconfig.node.json +15 -0
- api_medic-1.0.0/frontend/tsconfig.node.tsbuildinfo +1 -0
- api_medic-1.0.0/frontend/tsconfig.tsbuildinfo +1 -0
- api_medic-1.0.0/frontend/vite.config.d.ts +2 -0
- api_medic-1.0.0/frontend/vite.config.js +79 -0
- api_medic-1.0.0/frontend/vite.config.ts +85 -0
- api_medic-1.0.0/pyproject.toml +128 -0
- api_medic-1.0.0/src/api_medic/__init__.py +3 -0
- api_medic-1.0.0/src/api_medic/cli/__init__.py +1 -0
- api_medic-1.0.0/src/api_medic/cli/main.py +306 -0
- api_medic-1.0.0/src/api_medic/core/__init__.py +23 -0
- api_medic-1.0.0/src/api_medic/core/captured.py +94 -0
- api_medic-1.0.0/src/api_medic/core/checks/__init__.py +52 -0
- api_medic-1.0.0/src/api_medic/core/checks/auth.py +248 -0
- api_medic-1.0.0/src/api_medic/core/checks/body.py +139 -0
- api_medic-1.0.0/src/api_medic/core/checks/http.py +208 -0
- api_medic-1.0.0/src/api_medic/core/checks/network.py +215 -0
- api_medic-1.0.0/src/api_medic/core/checks/rate_limit.py +129 -0
- api_medic-1.0.0/src/api_medic/core/engine.py +72 -0
- api_medic-1.0.0/src/api_medic/core/export_schema.py +24 -0
- api_medic-1.0.0/src/api_medic/core/models.py +139 -0
- api_medic-1.0.0/src/api_medic/core/parser.py +141 -0
- api_medic-1.0.0/src/api_medic/core/render/__init__.py +8 -0
- api_medic-1.0.0/src/api_medic/core/render/html.py +205 -0
- api_medic-1.0.0/src/api_medic/core/render/json.py +14 -0
- api_medic-1.0.0/src/api_medic/core/render/markdown.py +136 -0
- api_medic-1.0.0/src/api_medic/core/render/terminal.py +159 -0
- api_medic-1.0.0/src/api_medic/core/runner.py +248 -0
- api_medic-1.0.0/src/api_medic/core/runner_safety.py +97 -0
- api_medic-1.0.0/src/api_medic/web/__init__.py +1 -0
- api_medic-1.0.0/src/api_medic/web/__main__.py +5 -0
- api_medic-1.0.0/src/api_medic/web/app.py +87 -0
- api_medic-1.0.0/src/api_medic/web/frontend/assets/index-B9VfTj3I.css +1 -0
- api_medic-1.0.0/src/api_medic/web/frontend/assets/index-DdtPIxCH.js +40 -0
- api_medic-1.0.0/src/api_medic/web/frontend/fixtures/01-healthy.json +49 -0
- api_medic-1.0.0/src/api_medic/web/frontend/fixtures/02-jwt-expired.json +62 -0
- api_medic-1.0.0/src/api_medic/web/frontend/fixtures/03-rate-limited.json +53 -0
- api_medic-1.0.0/src/api_medic/web/frontend/fixtures/04-cors-misconfigured.json +63 -0
- api_medic-1.0.0/src/api_medic/web/frontend/fixtures/05-tls-expiring.json +50 -0
- api_medic-1.0.0/src/api_medic/web/frontend/fixtures/06-malformed-body.json +52 -0
- api_medic-1.0.0/src/api_medic/web/frontend/fixtures/07-redirect-loop.json +42 -0
- api_medic-1.0.0/src/api_medic/web/frontend/fixtures/08-slow-tls.json +60 -0
- api_medic-1.0.0/src/api_medic/web/frontend/fixtures/index.json +1 -0
- api_medic-1.0.0/src/api_medic/web/frontend/index.html +13 -0
- api_medic-1.0.0/src/api_medic/web/server.py +25 -0
- api_medic-1.0.0/tests/__init__.py +0 -0
- api_medic-1.0.0/tests/fixtures/reports/01-healthy.json +49 -0
- api_medic-1.0.0/tests/fixtures/reports/02-jwt-expired.json +62 -0
- api_medic-1.0.0/tests/fixtures/reports/03-rate-limited.json +53 -0
- api_medic-1.0.0/tests/fixtures/reports/04-cors-misconfigured.json +63 -0
- api_medic-1.0.0/tests/fixtures/reports/05-tls-expiring.json +50 -0
- api_medic-1.0.0/tests/fixtures/reports/06-malformed-body.json +52 -0
- api_medic-1.0.0/tests/fixtures/reports/07-redirect-loop.json +42 -0
- api_medic-1.0.0/tests/fixtures/reports/08-slow-tls.json +60 -0
- api_medic-1.0.0/tests/integration/__init__.py +0 -0
- api_medic-1.0.0/tests/integration/test_engine_live.py +64 -0
- api_medic-1.0.0/tests/unit/__init__.py +0 -0
- api_medic-1.0.0/tests/unit/test_checks_auth.py +259 -0
- api_medic-1.0.0/tests/unit/test_checks_body.py +175 -0
- api_medic-1.0.0/tests/unit/test_checks_http.py +246 -0
- api_medic-1.0.0/tests/unit/test_checks_network.py +227 -0
- api_medic-1.0.0/tests/unit/test_checks_rate_limit.py +153 -0
- api_medic-1.0.0/tests/unit/test_checks_registry.py +76 -0
- api_medic-1.0.0/tests/unit/test_cli.py +347 -0
- api_medic-1.0.0/tests/unit/test_engine.py +73 -0
- api_medic-1.0.0/tests/unit/test_engine_smoke.py +81 -0
- api_medic-1.0.0/tests/unit/test_fixtures.py +80 -0
- api_medic-1.0.0/tests/unit/test_lambda_handler.py +168 -0
- api_medic-1.0.0/tests/unit/test_lambda_imports.py +172 -0
- api_medic-1.0.0/tests/unit/test_models.py +197 -0
- api_medic-1.0.0/tests/unit/test_parser.py +148 -0
- api_medic-1.0.0/tests/unit/test_render_json.py +58 -0
- api_medic-1.0.0/tests/unit/test_renderers.py +183 -0
- api_medic-1.0.0/tests/unit/test_runner.py +415 -0
- api_medic-1.0.0/tests/unit/test_runner_safety.py +122 -0
- api_medic-1.0.0/tests/unit/test_web_app.py +146 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
2
|
+
*.py text eol=lf
|
|
3
|
+
*.toml text eol=lf
|
|
4
|
+
*.yml text eol=lf
|
|
5
|
+
*.yaml text eol=lf
|
|
6
|
+
*.json text eol=lf
|
|
7
|
+
*.md text eol=lf
|
|
8
|
+
*.sh text eol=lf
|
|
9
|
+
*.ts text eol=lf
|
|
10
|
+
*.tsx text eol=lf
|
|
11
|
+
*.js text eol=lf
|
|
12
|
+
*.jsx text eol=lf
|
|
13
|
+
*.css text eol=lf
|
|
14
|
+
*.html text eol=lf
|
|
15
|
+
Makefile text eol=lf
|
|
16
|
+
LICENSE text eol=lf
|
|
17
|
+
*.png binary
|
|
18
|
+
*.jpg binary
|
|
19
|
+
*.gif binary
|
|
20
|
+
*.ico binary
|
|
21
|
+
*.pdf binary
|
|
22
|
+
*.zip binary
|
|
23
|
+
*.tar.gz binary
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
16
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
cache: pip
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
pip install -e ".[dev]"
|
|
31
|
+
|
|
32
|
+
- name: Lint with ruff
|
|
33
|
+
run: ruff check .
|
|
34
|
+
|
|
35
|
+
- name: Format check with ruff
|
|
36
|
+
run: ruff format --check .
|
|
37
|
+
|
|
38
|
+
- name: Type-check with mypy
|
|
39
|
+
run: mypy src
|
|
40
|
+
|
|
41
|
+
- name: Run tests
|
|
42
|
+
run: pytest -m "not integration" --cov --cov-report=term-missing
|
|
43
|
+
|
|
44
|
+
checks-coverage:
|
|
45
|
+
name: 80% coverage gate on core/checks
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
|
|
50
|
+
- uses: actions/setup-python@v5
|
|
51
|
+
with:
|
|
52
|
+
python-version: "3.12"
|
|
53
|
+
cache: pip
|
|
54
|
+
|
|
55
|
+
- name: Install dependencies
|
|
56
|
+
run: |
|
|
57
|
+
python -m pip install --upgrade pip
|
|
58
|
+
pip install -e ".[dev]"
|
|
59
|
+
|
|
60
|
+
- name: Coverage gate (core/checks ≥ 80%)
|
|
61
|
+
run: pytest -m "not integration" --cov=src/api_medic/core/checks --cov-report=term-missing --cov-fail-under=80
|
|
62
|
+
|
|
63
|
+
integration:
|
|
64
|
+
name: Integration tests (live, flake-tolerant)
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
continue-on-error: true
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
|
|
70
|
+
- uses: actions/setup-python@v5
|
|
71
|
+
with:
|
|
72
|
+
python-version: "3.12"
|
|
73
|
+
cache: pip
|
|
74
|
+
|
|
75
|
+
- name: Install dependencies
|
|
76
|
+
run: |
|
|
77
|
+
python -m pip install --upgrade pip
|
|
78
|
+
pip install -e ".[dev]"
|
|
79
|
+
|
|
80
|
+
- name: Run integration tests
|
|
81
|
+
run: pytest -m integration -v
|
|
82
|
+
|
|
83
|
+
frontend:
|
|
84
|
+
name: Frontend (typecheck, test, build)
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
defaults:
|
|
87
|
+
run:
|
|
88
|
+
working-directory: frontend
|
|
89
|
+
steps:
|
|
90
|
+
- uses: actions/checkout@v4
|
|
91
|
+
|
|
92
|
+
- uses: actions/setup-node@v4
|
|
93
|
+
with:
|
|
94
|
+
node-version: "20"
|
|
95
|
+
cache: npm
|
|
96
|
+
cache-dependency-path: frontend/package-lock.json
|
|
97
|
+
|
|
98
|
+
- name: Install dependencies
|
|
99
|
+
run: npm ci
|
|
100
|
+
|
|
101
|
+
- name: Type-check
|
|
102
|
+
run: npm run typecheck
|
|
103
|
+
|
|
104
|
+
- name: Run tests
|
|
105
|
+
run: npm test
|
|
106
|
+
|
|
107
|
+
- name: Build
|
|
108
|
+
run: npm run build
|
|
109
|
+
|
|
110
|
+
schema-check:
|
|
111
|
+
name: Verify TypeScript types are in sync with Pydantic schema
|
|
112
|
+
runs-on: ubuntu-latest
|
|
113
|
+
steps:
|
|
114
|
+
- uses: actions/checkout@v4
|
|
115
|
+
|
|
116
|
+
- uses: actions/setup-python@v5
|
|
117
|
+
with:
|
|
118
|
+
python-version: "3.12"
|
|
119
|
+
cache: pip
|
|
120
|
+
|
|
121
|
+
- uses: actions/setup-node@v4
|
|
122
|
+
with:
|
|
123
|
+
node-version: "20"
|
|
124
|
+
|
|
125
|
+
- name: Install Python dependencies
|
|
126
|
+
run: |
|
|
127
|
+
python -m pip install --upgrade pip
|
|
128
|
+
pip install -e .
|
|
129
|
+
|
|
130
|
+
- name: Regenerate TypeScript types
|
|
131
|
+
run: make types
|
|
132
|
+
|
|
133
|
+
- name: Fail if generated types changed
|
|
134
|
+
run: |
|
|
135
|
+
if ! git diff --exit-code frontend/src/lib/types.ts; then
|
|
136
|
+
echo "::error::Generated TypeScript types are out of sync."
|
|
137
|
+
echo "::error::Run 'make types' locally and commit the result."
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
name: Deploy hosted demo
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write # for OIDC role assumption
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
deploy:
|
|
14
|
+
name: Deploy api-medic-demo to AWS
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
# Repo variable DEMO_DEPLOY_ENABLED gates this — set it to 'true' in
|
|
17
|
+
# repo Settings → Variables once AWS prerequisites are in place
|
|
18
|
+
# (see deploy/README.md). Manual dispatch always runs so it can be
|
|
19
|
+
# tested before flipping the gate.
|
|
20
|
+
if: vars.DEMO_DEPLOY_ENABLED == 'true' || github.event_name == 'workflow_dispatch'
|
|
21
|
+
env:
|
|
22
|
+
AWS_REGION: us-east-1
|
|
23
|
+
STACK_NAME: api-medic-demo
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Configure AWS credentials (OIDC)
|
|
28
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
29
|
+
with:
|
|
30
|
+
role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
31
|
+
aws-region: ${{ env.AWS_REGION }}
|
|
32
|
+
|
|
33
|
+
- name: Set up Python
|
|
34
|
+
uses: actions/setup-python@v5
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.12"
|
|
37
|
+
|
|
38
|
+
- name: Set up Node
|
|
39
|
+
uses: actions/setup-node@v4
|
|
40
|
+
with:
|
|
41
|
+
node-version: "20"
|
|
42
|
+
cache: npm
|
|
43
|
+
cache-dependency-path: frontend/package-lock.json
|
|
44
|
+
|
|
45
|
+
- name: Install AWS SAM CLI
|
|
46
|
+
uses: aws-actions/setup-sam@v2
|
|
47
|
+
with:
|
|
48
|
+
use-installer: true
|
|
49
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
50
|
+
|
|
51
|
+
- name: Build frontend (captured-mode)
|
|
52
|
+
working-directory: frontend
|
|
53
|
+
env:
|
|
54
|
+
VITE_DEMO_MODE: "1"
|
|
55
|
+
run: |
|
|
56
|
+
npm ci
|
|
57
|
+
npm run build
|
|
58
|
+
|
|
59
|
+
- name: Vendor api_medic into Lambda build context
|
|
60
|
+
run: cp -r src/api_medic deploy/lambda/api_medic
|
|
61
|
+
|
|
62
|
+
- name: SAM build
|
|
63
|
+
working-directory: deploy
|
|
64
|
+
run: sam build
|
|
65
|
+
|
|
66
|
+
- name: SAM deploy
|
|
67
|
+
working-directory: deploy
|
|
68
|
+
run: |
|
|
69
|
+
sam deploy \
|
|
70
|
+
--no-confirm-changeset \
|
|
71
|
+
--no-fail-on-empty-changeset \
|
|
72
|
+
--parameter-overrides \
|
|
73
|
+
HostedZoneId=${{ secrets.ROUTE53_HOSTED_ZONE_ID }} \
|
|
74
|
+
CertificateArn=${{ secrets.ACM_CERTIFICATE_ARN }}
|
|
75
|
+
|
|
76
|
+
- name: Read stack outputs
|
|
77
|
+
id: outputs
|
|
78
|
+
run: |
|
|
79
|
+
BUCKET=$(aws cloudformation describe-stacks \
|
|
80
|
+
--stack-name "$STACK_NAME" \
|
|
81
|
+
--query 'Stacks[0].Outputs[?OutputKey==`WebBucketName`].OutputValue' \
|
|
82
|
+
--output text)
|
|
83
|
+
DIST=$(aws cloudformation describe-stacks \
|
|
84
|
+
--stack-name "$STACK_NAME" \
|
|
85
|
+
--query 'Stacks[0].Outputs[?OutputKey==`DistributionId`].OutputValue' \
|
|
86
|
+
--output text)
|
|
87
|
+
echo "bucket=$BUCKET" >> "$GITHUB_OUTPUT"
|
|
88
|
+
echo "distribution=$DIST" >> "$GITHUB_OUTPUT"
|
|
89
|
+
|
|
90
|
+
- name: Sync frontend to S3
|
|
91
|
+
run: |
|
|
92
|
+
aws s3 sync frontend/dist/ "s3://${{ steps.outputs.outputs.bucket }}/" \
|
|
93
|
+
--delete
|
|
94
|
+
|
|
95
|
+
- name: Invalidate CloudFront
|
|
96
|
+
run: |
|
|
97
|
+
aws cloudfront create-invalidation \
|
|
98
|
+
--distribution-id "${{ steps.outputs.outputs.distribution }}" \
|
|
99
|
+
--paths "/*"
|
|
100
|
+
|
|
101
|
+
- name: Print public URL
|
|
102
|
+
run: |
|
|
103
|
+
echo "::notice::Deployed to https://api-medic.markandrewmarquez.com"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Fires on every v* tag push (e.g. v1.0.0, v1.1.0, v2.0.0-rc1) and via
|
|
4
|
+
# manual dispatch — useful to publish an existing tag without re-tagging.
|
|
5
|
+
#
|
|
6
|
+
# Trusted publishing: no API token in repo secrets. PyPI verifies the
|
|
7
|
+
# upload via an OIDC token that GitHub mints for this workflow. The
|
|
8
|
+
# binding is configured once in PyPI's web UI under Account → Publishing.
|
|
9
|
+
# See deploy/pypi-trusted-publishing.md for the exact one-time steps.
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
tags:
|
|
14
|
+
- "v*"
|
|
15
|
+
workflow_dispatch:
|
|
16
|
+
inputs:
|
|
17
|
+
ref:
|
|
18
|
+
description: "Tag or branch to publish from (e.g. v1.0.0)"
|
|
19
|
+
required: true
|
|
20
|
+
default: "v1.0.0"
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
build-and-publish:
|
|
24
|
+
name: Build wheel + sdist and publish to PyPI
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
environment: pypi
|
|
27
|
+
permissions:
|
|
28
|
+
id-token: write # required for trusted publishing
|
|
29
|
+
contents: read
|
|
30
|
+
steps:
|
|
31
|
+
- name: Checkout
|
|
32
|
+
uses: actions/checkout@v4
|
|
33
|
+
with:
|
|
34
|
+
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }}
|
|
35
|
+
|
|
36
|
+
- name: Set up Python
|
|
37
|
+
uses: actions/setup-python@v5
|
|
38
|
+
with:
|
|
39
|
+
python-version: "3.12"
|
|
40
|
+
|
|
41
|
+
- name: Set up Node
|
|
42
|
+
uses: actions/setup-node@v4
|
|
43
|
+
with:
|
|
44
|
+
node-version: "20"
|
|
45
|
+
cache: npm
|
|
46
|
+
cache-dependency-path: frontend/package-lock.json
|
|
47
|
+
|
|
48
|
+
- name: Build frontend bundle
|
|
49
|
+
# Local-web-UI build (no VITE_DEMO_MODE) — the wheel ships the
|
|
50
|
+
# full-feature UI; the demo-mode build is hosted-demo-only.
|
|
51
|
+
working-directory: frontend
|
|
52
|
+
run: |
|
|
53
|
+
npm ci
|
|
54
|
+
npm run build
|
|
55
|
+
|
|
56
|
+
- name: Stage frontend into wheel source tree
|
|
57
|
+
run: |
|
|
58
|
+
# pyproject.toml force-includes src/api_medic/web/frontend/ → wheel.
|
|
59
|
+
# That dir is gitignored at runtime; populate it from the build.
|
|
60
|
+
rm -f src/api_medic/web/frontend/.gitkeep
|
|
61
|
+
cp -r frontend/dist/* src/api_medic/web/frontend/
|
|
62
|
+
|
|
63
|
+
- name: Build wheel and sdist
|
|
64
|
+
run: |
|
|
65
|
+
python -m pip install --upgrade pip build
|
|
66
|
+
python -m build
|
|
67
|
+
|
|
68
|
+
- name: Smoke-check the built distributions
|
|
69
|
+
run: |
|
|
70
|
+
ls -lh dist/
|
|
71
|
+
python -m pip install dist/*.whl
|
|
72
|
+
python -c "import api_medic; print('imported api_medic', api_medic.__name__)"
|
|
73
|
+
api-medic --help | head -10
|
|
74
|
+
|
|
75
|
+
- name: Publish to PyPI
|
|
76
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
77
|
+
# No `with:` block — trusted publishing reads the project name
|
|
78
|
+
# from the wheel metadata and verifies via OIDC.
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
MANIFEST
|
|
23
|
+
|
|
24
|
+
# Virtual environments
|
|
25
|
+
.venv/
|
|
26
|
+
venv/
|
|
27
|
+
env/
|
|
28
|
+
ENV/
|
|
29
|
+
|
|
30
|
+
# Testing
|
|
31
|
+
.coverage
|
|
32
|
+
.coverage.*
|
|
33
|
+
htmlcov/
|
|
34
|
+
.pytest_cache/
|
|
35
|
+
.mypy_cache/
|
|
36
|
+
.ruff_cache/
|
|
37
|
+
coverage.xml
|
|
38
|
+
*.cover
|
|
39
|
+
|
|
40
|
+
# IDE / agent state
|
|
41
|
+
.idea/
|
|
42
|
+
.vscode/
|
|
43
|
+
.claude/
|
|
44
|
+
*.swp
|
|
45
|
+
*.swo
|
|
46
|
+
.DS_Store
|
|
47
|
+
|
|
48
|
+
# Node / frontend
|
|
49
|
+
node_modules/
|
|
50
|
+
frontend/dist/
|
|
51
|
+
frontend/.vite/
|
|
52
|
+
*.log
|
|
53
|
+
.npm/
|
|
54
|
+
|
|
55
|
+
# Built frontend, shipped in wheel — populated at build time, not checked in.
|
|
56
|
+
# .gitkeep keeps the dir in tree so the wheel build's force-include works
|
|
57
|
+
# before Phase 2 has anything to bundle.
|
|
58
|
+
src/api_medic/web/frontend/*
|
|
59
|
+
!src/api_medic/web/frontend/.gitkeep
|
|
60
|
+
|
|
61
|
+
# AWS / SAM
|
|
62
|
+
.aws-sam/
|
|
63
|
+
samconfig.toml.local
|
|
64
|
+
packaged.yaml
|
|
65
|
+
|
|
66
|
+
# Secrets — never commit these
|
|
67
|
+
.env
|
|
68
|
+
.env.local
|
|
69
|
+
*.pem
|
|
70
|
+
aws-credentials*
|
|
71
|
+
|
|
72
|
+
# OS
|
|
73
|
+
Thumbs.db
|
|
74
|
+
|
|
75
|
+
# SAM build context: vendored copy of api_medic (created before sam build)
|
|
76
|
+
deploy/lambda/api_medic/
|
|
77
|
+
|
|
78
|
+
# Cert / DNS validation scratch (generated during one-shot ACM setup,
|
|
79
|
+
# not used by builds or deploys)
|
|
80
|
+
/acm-validation.json
|
|
81
|
+
/cert-arn.txt
|
|
82
|
+
|
|
83
|
+
# One-shot deploy operations scratch (templates fetched from CFN,
|
|
84
|
+
# import maps, etc.)
|
|
85
|
+
/.deploy-tmp/
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to api-medic are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2026-04-29
|
|
8
|
+
|
|
9
|
+
First public release. Three input surfaces (CLI, local web UI, hosted demo) sharing one core engine and a single `Report` shape.
|
|
10
|
+
|
|
11
|
+
### Added — Core engine
|
|
12
|
+
|
|
13
|
+
- Pydantic v2 data model: `Report`, `Finding`, `RequestSummary`, `ResponseSummary`, `TimingBreakdown`, `CapturedRequest`, `CapturedResponse`, `CapturedDns`, `CapturedTls`. `schema_version` bump-tracked.
|
|
14
|
+
- Parser: HAR 1.2 archives and curl command strings normalized to the same `CapturedRequest` shape.
|
|
15
|
+
- Runner: live HTTP execution via `httpx`, with `dnspython` pre-flight DNS probe and a separate `cryptography`-based TLS cert/protocol probe. Captures DNS / TLS / TTFB / total timing, redirect chain, raw headers (preserving duplicate `Set-Cookie`).
|
|
16
|
+
- Check battery — 20+ diagnostic checks across:
|
|
17
|
+
- **Network** — `dns.no_records`, `dns.address_class`
|
|
18
|
+
- **TLS** — `tls.expired`, `tls.expiring_soon`, `tls.hostname_mismatch`, `tls.weak_protocol`
|
|
19
|
+
- **Transport** — `transport.redirect_loop`, `transport.redirect_to_http`, `transport.slow_tls`
|
|
20
|
+
- **Auth** — `auth.jwt_expired`, `auth.missing_authorization`, `auth.malformed_authorization`, `auth.suspicious_jwt_signature`
|
|
21
|
+
- **CORS** — `cors.preflight_failed`, `cors.origin_not_allowed`, `cors.credentials_misconfigured`
|
|
22
|
+
- **Body** — `body.malformed_json`, `body.content_length_mismatch`, `body.encoding_mismatch`
|
|
23
|
+
- **Rate limit** — `ratelimit.too_many_requests` (with Retry-After context)
|
|
24
|
+
- **Status** — `status.client_error`, `status.server_error`
|
|
25
|
+
- Renderers: terminal (rich), JSON, Markdown, HTML — pluggable via `--output`.
|
|
26
|
+
|
|
27
|
+
### Added — CLI
|
|
28
|
+
|
|
29
|
+
- `api-medic <URL>` quick GET diagnosis (default subcommand).
|
|
30
|
+
- `api-medic run <URL>` full request with method, headers, body, auth.
|
|
31
|
+
- `api-medic from-curl '<command>'` parse + analyze (or just analyze) a curl command.
|
|
32
|
+
- `api-medic from-har <file.har>` analyze a captured HAR.
|
|
33
|
+
- `api-medic serve [--port 8765]` launch the local web UI.
|
|
34
|
+
- Output flags: `--output {terminal,json,markdown,html}`, `--save <path>`, `--no-color`, `--verbose`.
|
|
35
|
+
|
|
36
|
+
### Added — Local web UI
|
|
37
|
+
|
|
38
|
+
- Vite + React 18 + TypeScript + Tailwind, no design system library, no state management library.
|
|
39
|
+
- Three tabs: Demos (fixture browser), Run (request composer hitting `/api/run`), HAR (file upload hitting `/api/analyze`).
|
|
40
|
+
- TypeScript types auto-generated from Pydantic JSON Schema via `make types`; CI fails if regenerated types drift from the committed file.
|
|
41
|
+
- React bundle ships inside the Python wheel — no separate install step for the local UI.
|
|
42
|
+
|
|
43
|
+
### Added — Hosted demo (AWS)
|
|
44
|
+
|
|
45
|
+
- `https://api-medic.markandrewmarquez.com` on S3 + CloudFront + API Gateway HTTP API + Lambda.
|
|
46
|
+
- Routes: `POST /api/analyze` (captured-mode parser+engine), `POST /api/run` (live runner), `GET /api/health`.
|
|
47
|
+
- `/api/run` is SSRF-guarded (blocks RFC1918, link-local incl. EC2 metadata, multicast, loopback; HTTPS-only) with a 10-second per-request timeout.
|
|
48
|
+
- API Gateway throttling on `/api/run` (2 req/sec sustained, burst 5) plus a $10/month AWS Budget with 50%/100%/forecast email alerts.
|
|
49
|
+
- Lambda log group is stack-managed with 14-day retention.
|
|
50
|
+
- Same Report shape as the CLI and local web UI — byte-identical given the same input.
|
|
51
|
+
- Deploy via SAM, OIDC-based GitHub Actions role with least-privilege inline policy (committed at `deploy/iam/`).
|
|
52
|
+
|
|
53
|
+
### Added — Demo polish
|
|
54
|
+
|
|
55
|
+
- Client-side HAR stripping in three tiers (passthrough → strip request/response bodies → first-entry only) so 12 MB browser exports survive API Gateway's 10 MB body cap. UI banner explains what was dropped.
|
|
56
|
+
- `VITE_DEMO_MODE=1` build path uses same-origin API base so CloudFront routes `/api/*` to the Lambda.
|
|
57
|
+
|
|
58
|
+
### Added — Quality
|
|
59
|
+
|
|
60
|
+
- 311 Python tests + 60 frontend tests.
|
|
61
|
+
- CI matrix: 3 operating systems × 3 Python versions = 9 cells.
|
|
62
|
+
- 80% coverage gate on `core/checks`.
|
|
63
|
+
- Static-import audit (`tests/unit/test_lambda_imports.py`) enforces Lambda dep boundaries — no fastapi/uvicorn/typer/rich, and explicit positive assertion that the Lambda surface DOES include the runner + SSRF guard so `/api/run` can't silently regress.
|
|
64
|
+
- Schema-sync gate: CI regenerates `frontend/src/lib/types.ts` from Pydantic and fails if it differs from the committed file.
|
|
65
|
+
|
|
66
|
+
### Added — Project
|
|
67
|
+
|
|
68
|
+
- 8 hand-crafted fixture reports covering distinct diagnostic scenarios; double as UI dev data, schema validation, and integration smoke tests.
|
|
69
|
+
- MIT license, public repo, full architecture spec at `docs/architecture.md`.
|
|
70
|
+
|
|
71
|
+
### Architectural decisions
|
|
72
|
+
|
|
73
|
+
- **Stateless by design.** No DynamoDB, no S3 writes for user data, no CloudWatch persistence beyond default logs.
|
|
74
|
+
- **No FastAPI / uvicorn on Lambda.** The Lambda dispatches routes inline via `lambda_handler` to keep cold-start budget intact even with the runner deps (httpx, dnspython, cryptography).
|
|
75
|
+
- **Same `Report` shape from every surface** — the contract that makes the future browser extension a thin capture layer rather than a parallel implementation.
|
|
76
|
+
|
|
77
|
+
[Unreleased]: https://github.com/marky224/api-medic/compare/v1.0.0...HEAD
|
|
78
|
+
[1.0.0]: https://github.com/marky224/api-medic/releases/tag/v1.0.0
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# CLAUDE.md — operating brief for Claude Code in this repo
|
|
2
|
+
|
|
3
|
+
This is api-medic, an open-source HTTP API troubleshooting tool. Mark Marquez directs the build; Claude implements alongside him.
|
|
4
|
+
|
|
5
|
+
## Source of truth
|
|
6
|
+
- `docs/architecture.md` — the full v1 build spec. Tech stack, repo structure, data model, invariants, phases. Consult actively.
|
|
7
|
+
- The Pydantic models in `src/api_medic/core/models.py` are the contract every surface produces and consumes. Don''t break them without bumping `schema_version`.
|
|
8
|
+
- `frontend/src/lib/types.ts` is auto-generated from those models via `make types`. Never edit by hand.
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
v1 is shipped. All seven phases in `docs/architecture.md` are complete:
|
|
12
|
+
data model, fixtures, web UI, core engine, CLI, hosted demo on AWS,
|
|
13
|
+
Phase 6 polish. The hosted demo at `https://api-medic.markandrewmarquez.com`
|
|
14
|
+
exposes Demos / Run / HAR tabs and the Lambda runs both captured-mode
|
|
15
|
+
and live requests (SSRF-guarded, throttled).
|
|
16
|
+
|
|
17
|
+
Default work mode is now polish, bug fixes, and post-launch items.
|
|
18
|
+
Phase 7 (browser extension) is the only remaining planned phase and
|
|
19
|
+
is explicitly post-v1.
|
|
20
|
+
|
|
21
|
+
## Working style
|
|
22
|
+
- Default to producing actual code, not pseudocode.
|
|
23
|
+
- Write tests alongside the code.
|
|
24
|
+
- Push back when you disagree.
|
|
25
|
+
- One question at a time when scoping.
|
|
26
|
+
- Don''t expand scope without flagging.
|
|
27
|
+
- No emojis unless Mark uses them first.
|
|
28
|
+
- Skip sycophantic openers ("Great question!", etc.).
|
|
29
|
+
|
|
30
|
+
## Architectural invariants (don''t change without sign-off)
|
|
31
|
+
- Same Report shape from every surface (CLI, web, Lambda, extension).
|
|
32
|
+
- Lambda surface ships parser + checks + renderers + the live runner.
|
|
33
|
+
Outbound HTTP from the Lambda is allowed and must apply SSRF mitigations
|
|
34
|
+
(block RFC1918, link-local incl. 169.254.169.254 metadata, multicast,
|
|
35
|
+
loopback; resolve hostname once and re-check the IP before connecting)
|
|
36
|
+
plus a conservative per-request timeout (≤10s). FastAPI and uvicorn
|
|
37
|
+
remain excluded — the Lambda dispatches routes inline via
|
|
38
|
+
`lambda_handler`, no web framework.
|
|
39
|
+
- Hosted demo exposes both captured-mode (HAR/curl) and live-run tabs.
|
|
40
|
+
Live runs are bounded by API Gateway throttling, Lambda reserved
|
|
41
|
+
concurrency, and the AWS Budget; assume any URL the user submits
|
|
42
|
+
will be fetched by the Lambda.
|
|
43
|
+
- Stateless: no DynamoDB, no S3 writes for user data, no persistence.
|
|
44
|
+
- Public repo, MIT license, no public timelines anywhere.
|
|
45
|
+
|
|
46
|
+
## Tech stack (locked)
|
|
47
|
+
- Python 3.10+ (3.12 target), httpx, dnspython, cryptography, pydantic v2, typer, rich, fastapi, uvicorn
|
|
48
|
+
- TypeScript + React 18 + Vite + Tailwind. No design system library. No state management library.
|
|
49
|
+
- AWS SAM in us-east-1, API Gateway HTTP API → Lambda, CloudFront + S3
|
|
50
|
+
- GitHub Actions CI
|
|
51
|
+
|
|
52
|
+
## Quality gates (must pass before pushing)
|
|
53
|
+
- `pytest` — all green
|
|
54
|
+
- `ruff check .` and `ruff format --check .`
|
|
55
|
+
- `mypy` — clean
|
|
56
|
+
- `make types` produces no diff vs committed `types.ts` (also verified by CI)
|
|
57
|
+
|
|
58
|
+
## Out of scope for v1
|
|
59
|
+
History/share links, comparison mode, OpenAPI validation, OAuth simulation, WebSocket support, plugin system, pre-built binaries, telemetry. Don''t build these unless Mark explicitly asks.
|
|
60
|
+
|
|
61
|
+
## Audience
|
|
62
|
+
README and marketing copy frame this as a tool for technical support engineers triaging customer-reported API issues. Tool itself is general-purpose.
|
api_medic-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Marquez
|
|
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.
|
api_medic-1.0.0/Makefile
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.PHONY: help install types lint format typecheck test test-cov clean
|
|
2
|
+
|
|
3
|
+
help: ## Show this help
|
|
4
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
|
|
5
|
+
|
|
6
|
+
install: ## Install package + dev dependencies
|
|
7
|
+
pip install -e ".[dev]"
|
|
8
|
+
|
|
9
|
+
types: ## Regenerate TypeScript types from Pydantic JSON Schema
|
|
10
|
+
@echo "Exporting JSON Schema from Pydantic models..."
|
|
11
|
+
@python -m api_medic.core.export_schema > /tmp/api-medic-schema.json
|
|
12
|
+
@echo "Generating TypeScript types via quicktype..."
|
|
13
|
+
@mkdir -p frontend/src/lib
|
|
14
|
+
@npx --yes quicktype@23 \
|
|
15
|
+
--src-lang schema \
|
|
16
|
+
--lang typescript \
|
|
17
|
+
--just-types \
|
|
18
|
+
--prefer-unions \
|
|
19
|
+
--no-date-times \
|
|
20
|
+
--top-level Report \
|
|
21
|
+
-o frontend/src/lib/types.ts \
|
|
22
|
+
/tmp/api-medic-schema.json
|
|
23
|
+
@echo "/* eslint-disable */" > /tmp/types.header
|
|
24
|
+
@echo "/**" >> /tmp/types.header
|
|
25
|
+
@echo " * AUTO-GENERATED by 'make types' from Pydantic models in src/api_medic/core/models.py." >> /tmp/types.header
|
|
26
|
+
@echo " * Do not edit by hand. Run 'make types' to regenerate." >> /tmp/types.header
|
|
27
|
+
@echo " */" >> /tmp/types.header
|
|
28
|
+
@cat /tmp/types.header frontend/src/lib/types.ts > /tmp/types.combined
|
|
29
|
+
@mv /tmp/types.combined frontend/src/lib/types.ts
|
|
30
|
+
@rm /tmp/types.header /tmp/api-medic-schema.json
|
|
31
|
+
@echo "Wrote frontend/src/lib/types.ts"
|
|
32
|
+
|
|
33
|
+
lint: ## Run ruff lint
|
|
34
|
+
ruff check .
|
|
35
|
+
|
|
36
|
+
format: ## Format code with ruff
|
|
37
|
+
ruff format .
|
|
38
|
+
|
|
39
|
+
typecheck: ## Run mypy
|
|
40
|
+
mypy src
|
|
41
|
+
|
|
42
|
+
test: ## Run tests
|
|
43
|
+
pytest
|
|
44
|
+
|
|
45
|
+
test-cov: ## Run tests with coverage
|
|
46
|
+
pytest --cov --cov-report=term-missing --cov-report=html
|
|
47
|
+
|
|
48
|
+
clean: ## Remove build artifacts and caches
|
|
49
|
+
rm -rf build dist *.egg-info
|
|
50
|
+
rm -rf .pytest_cache .mypy_cache .ruff_cache htmlcov
|
|
51
|
+
find . -type d -name __pycache__ -exec rm -rf {} +
|
|
52
|
+
find . -type f -name "*.pyc" -delete
|