proxy2model 2.0.0__py3-none-any.whl
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.
- proxy2model/__init__.py +19 -0
- proxy2model/__main__.py +6 -0
- proxy2model/atomic.py +68 -0
- proxy2model/cli/__init__.py +3 -0
- proxy2model/cli/analyze.py +501 -0
- proxy2model/cli/app.py +408 -0
- proxy2model/cli/browse.py +279 -0
- proxy2model/cli/build.py +399 -0
- proxy2model/cli/common.py +190 -0
- proxy2model/cli/history.py +224 -0
- proxy2model/cli/mcp.py +184 -0
- proxy2model/cli/ui.py +62 -0
- proxy2model/core/__init__.py +1 -0
- proxy2model/core/changes.py +124 -0
- proxy2model/core/context.py +571 -0
- proxy2model/core/diff.py +330 -0
- proxy2model/core/domains.py +98 -0
- proxy2model/core/graph.py +562 -0
- proxy2model/core/methodology.py +343 -0
- proxy2model/core/model.py +998 -0
- proxy2model/core/stack.py +218 -0
- proxy2model/data/fonts/LICENSES.md +16 -0
- proxy2model/data/fonts/inter-latin-wght-normal.woff2 +0 -0
- proxy2model/data/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
- proxy2model/data/fonts/nippo-200.woff2 +0 -0
- proxy2model/data/fonts/nippo-300.woff2 +0 -0
- proxy2model/data/fonts/nippo-400.woff2 +0 -0
- proxy2model/data/fonts/nippo-500.woff2 +0 -0
- proxy2model/data/fonts/nippo-700.woff2 +0 -0
- proxy2model/data/public_suffix_list.dat +16497 -0
- proxy2model/db/__init__.py +1 -0
- proxy2model/db/bql.py +710 -0
- proxy2model/db/bql_js.py +278 -0
- proxy2model/db/store.py +547 -0
- proxy2model/export/__init__.py +1 -0
- proxy2model/export/openapi.py +248 -0
- proxy2model/ingest/__init__.py +1 -0
- proxy2model/ingest/browse.py +181 -0
- proxy2model/ingest/browser.py +1058 -0
- proxy2model/ingest/crawl.py +673 -0
- proxy2model/ingest/extract.py +208 -0
- proxy2model/ingest/infer.py +75 -0
- proxy2model/ingest/journey.py +148 -0
- proxy2model/ingest/live.py +96 -0
- proxy2model/ingest/parse.py +1275 -0
- proxy2model/ingest/redact.py +535 -0
- proxy2model/py.typed +0 -0
- proxy2model/recon/__init__.py +1 -0
- proxy2model/recon/osint.py +607 -0
- proxy2model/report/__init__.py +1 -0
- proxy2model/report/animate.py +163 -0
- proxy2model/report/dash.py +469 -0
- proxy2model/report/fonts.py +51 -0
- proxy2model/report/html.py +988 -0
- proxy2model/report/inventory.py +258 -0
- proxy2model/report/map.py +515 -0
- proxy2model/report/pages.py +537 -0
- proxy2model/report/query.py +306 -0
- proxy2model/report/shot.py +55 -0
- proxy2model/report/view.py +213 -0
- proxy2model-2.0.0.dist-info/METADATA +148 -0
- proxy2model-2.0.0.dist-info/RECORD +66 -0
- proxy2model-2.0.0.dist-info/WHEEL +5 -0
- proxy2model-2.0.0.dist-info/entry_points.txt +2 -0
- proxy2model-2.0.0.dist-info/licenses/LICENSE +201 -0
- proxy2model-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The viral surface: turn a build into an animation people share.
|
|
3
|
+
|
|
4
|
+
Two outputs, no runtime dependencies:
|
|
5
|
+
|
|
6
|
+
render_terminal(model) -> prints a typed, colored build sequence to the
|
|
7
|
+
terminal (the `--animate` flag). Looks like a
|
|
8
|
+
live demo even when it's deterministic.
|
|
9
|
+
|
|
10
|
+
render_svg(model) -> a single self-contained animated SVG string that
|
|
11
|
+
loops in a GitHub README with no JS and no CDN.
|
|
12
|
+
This is the thing that gets screenshotted.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
from ..atomic import atomic_write
|
|
21
|
+
from ..core.model import Model
|
|
22
|
+
|
|
23
|
+
# brand
|
|
24
|
+
ORANGE = "\033[38;5;208m"
|
|
25
|
+
CYAN = "\033[38;5;39m"
|
|
26
|
+
VIOLET = "\033[38;5;99m"
|
|
27
|
+
DIM = "\033[38;5;244m"
|
|
28
|
+
WHITE = "\033[97m"
|
|
29
|
+
BOLD = "\033[1m"
|
|
30
|
+
RESET = "\033[0m"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _type(text: str, delay: float = 0.012, color: str = WHITE, nl: bool = True):
|
|
34
|
+
for ch in text:
|
|
35
|
+
sys.stdout.write(color + ch + RESET)
|
|
36
|
+
sys.stdout.flush()
|
|
37
|
+
time.sleep(delay)
|
|
38
|
+
if nl:
|
|
39
|
+
sys.stdout.write("\n")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def render_terminal(m: Model, fast: bool = False) -> None:
|
|
43
|
+
d = 0.0 if fast else 0.012
|
|
44
|
+
c = m.counts()
|
|
45
|
+
print()
|
|
46
|
+
_type("$ proxy2model history.xml --webapp " + m.name, d, ORANGE)
|
|
47
|
+
time.sleep(0.15 if not fast else 0)
|
|
48
|
+
for stage, note in [
|
|
49
|
+
("parsing the capture", "streaming exchanges, decoding"),
|
|
50
|
+
("redacting values", "tokens, cookies, keys, PII → fingerprints"),
|
|
51
|
+
("typing assets", "routes · scripts · endpoints · params"),
|
|
52
|
+
("relating nodes", "OBSERVED vs INFERRED edges"),
|
|
53
|
+
("reconciling", "code references vs runtime traffic"),
|
|
54
|
+
("naming unknowns", "what the capture could not answer"),
|
|
55
|
+
]:
|
|
56
|
+
_type(f" {DIM}▸{RESET} {stage:<20}{DIM}{note}{RESET}", d, WHITE, nl=True)
|
|
57
|
+
time.sleep(0.08 if not fast else 0)
|
|
58
|
+
print()
|
|
59
|
+
# the payoff line
|
|
60
|
+
static = c["api_state"].get("STATIC_ONLY", 0)
|
|
61
|
+
both = c["api_state"].get("BOTH", 0)
|
|
62
|
+
runtime = c["api_state"].get("RUNTIME_ONLY", 0)
|
|
63
|
+
_type(f"{BOLD} routes {c['routes']} · APIs {c['endpoints']} "
|
|
64
|
+
f"({both} both, {runtime} runtime-only, {static} static-only)"
|
|
65
|
+
f" · edges {c['edges']} · nodes {c['nodes']}{RESET}", d, CYAN)
|
|
66
|
+
_type(f" {VIOLET}{c['unknowns']} named unknowns{RESET} "
|
|
67
|
+
f"{DIM}absence = a gap in observation, never in the target{RESET}", d, WHITE)
|
|
68
|
+
print()
|
|
69
|
+
_type(" a model your AI can reason over — not a dump it hallucinates on", d, ORANGE)
|
|
70
|
+
print()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------- SVG --------
|
|
74
|
+
|
|
75
|
+
def render_svg(m: Model) -> str:
|
|
76
|
+
c = m.counts()
|
|
77
|
+
both = c["api_state"].get("BOTH", 0)
|
|
78
|
+
runtime = c["api_state"].get("RUNTIME_ONLY", 0)
|
|
79
|
+
static = c["api_state"].get("STATIC_ONLY", 0)
|
|
80
|
+
|
|
81
|
+
# the six build stages, revealed one at a time
|
|
82
|
+
stages = [
|
|
83
|
+
("parse", "streaming the capture"),
|
|
84
|
+
("redact", "values masked before any write"),
|
|
85
|
+
("type", "routes · scripts · endpoints"),
|
|
86
|
+
("relate", "observed vs inferred edges"),
|
|
87
|
+
("reconcile", "code vs runtime"),
|
|
88
|
+
("model", "an evidence-backed graph"),
|
|
89
|
+
]
|
|
90
|
+
W, H = 760, 480
|
|
91
|
+
line_h = 30
|
|
92
|
+
base_y = 150
|
|
93
|
+
dur_each = 0.9 # seconds per line reveal
|
|
94
|
+
|
|
95
|
+
parts = [
|
|
96
|
+
f'<svg viewBox="0 0 {W} {H}" xmlns="http://www.w3.org/2000/svg" '
|
|
97
|
+
f'font-family="ui-monospace,SFMono-Regular,Menlo,monospace" role="img" '
|
|
98
|
+
f'aria-label="proxy2model turning captured traffic into a web-app model">',
|
|
99
|
+
f'<rect width="{W}" height="{H}" rx="14" fill="#0d1117"/>',
|
|
100
|
+
# window chrome
|
|
101
|
+
'<circle cx="24" cy="26" r="6" fill="#ff5f56"/>',
|
|
102
|
+
'<circle cx="46" cy="26" r="6" fill="#ffbd2e"/>',
|
|
103
|
+
'<circle cx="68" cy="26" r="6" fill="#27c93f"/>',
|
|
104
|
+
f'<text x="{W/2}" y="30" fill="#8b98a8" font-size="12" text-anchor="middle">proxy2model</text>',
|
|
105
|
+
# prompt line (typed via width mask)
|
|
106
|
+
'<text x="28" y="78" fill="#e85002" font-size="15" font-weight="700">'
|
|
107
|
+
'$ proxy2model history.xml</text>',
|
|
108
|
+
'<rect x="28" y="66" width="0" height="18" fill="#0d1117">'
|
|
109
|
+
'<animate attributeName="width" from="360" to="0" dur="1s" fill="freeze"/></rect>',
|
|
110
|
+
'<text x="392" y="78" fill="#3a424e" font-size="15">'
|
|
111
|
+
'<animate attributeName="opacity" values="1;0;1" dur="0.8s" repeatCount="indefinite"/>▋</text>',
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
# stage lines, each fades in in sequence
|
|
115
|
+
for i, (stage, note) in enumerate(stages):
|
|
116
|
+
y = base_y + i * line_h
|
|
117
|
+
begin = 1.0 + i * dur_each
|
|
118
|
+
parts.append(
|
|
119
|
+
f'<g opacity="0"><animate attributeName="opacity" from="0" to="1" '
|
|
120
|
+
f'dur="0.35s" begin="{begin:.2f}s" fill="freeze"/>'
|
|
121
|
+
f'<text x="40" y="{y}" fill="#4fd1c5" font-size="14">▸</text>'
|
|
122
|
+
f'<text x="66" y="{y}" fill="#e6edf3" font-size="14">{stage}</text>'
|
|
123
|
+
f'<text x="180" y="{y}" fill="#8b98a8" font-size="13">{note}</text></g>'
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
reveal_all = 1.0 + len(stages) * dur_each + 0.3
|
|
127
|
+
# payoff panel
|
|
128
|
+
py = base_y + len(stages) * line_h + 26
|
|
129
|
+
parts.append(
|
|
130
|
+
f'<g opacity="0"><animate attributeName="opacity" from="0" to="1" '
|
|
131
|
+
f'dur="0.5s" begin="{reveal_all:.2f}s" fill="freeze"/>'
|
|
132
|
+
f'<rect x="28" y="{py-22}" width="{W-56}" height="96" rx="10" '
|
|
133
|
+
f'fill="#11161d" stroke="#1e2733"/>'
|
|
134
|
+
f'<text x="44" y="{py+2}" fill="#e6edf3" font-size="15" font-weight="700">'
|
|
135
|
+
f'routes {c["routes"]} · APIs {c["endpoints"]} · edges {c["edges"]}</text>'
|
|
136
|
+
f'<text x="44" y="{py+26}" fill="#39d0ff" font-size="13">'
|
|
137
|
+
f'{both} both · {runtime} runtime-only · </text>'
|
|
138
|
+
f'<text x="{44 + 8.0*len(str(both)+str(runtime)) + 165}" y="{py+26}" '
|
|
139
|
+
f'fill="#e85002" font-size="13" font-weight="700">{static} static-only ← look here</text>'
|
|
140
|
+
f'<text x="44" y="{py+50}" fill="#9b8cff" font-size="13">'
|
|
141
|
+
f'{c["unknowns"]} named unknowns — what the model does not know</text></g>'
|
|
142
|
+
)
|
|
143
|
+
# closing tagline
|
|
144
|
+
parts.append(
|
|
145
|
+
f'<text x="{W/2}" y="{H-24}" fill="#8b98a8" font-size="13" text-anchor="middle" '
|
|
146
|
+
f'opacity="0"><animate attributeName="opacity" from="0" to="1" dur="0.6s" '
|
|
147
|
+
f'begin="{reveal_all+0.6:.2f}s" fill="freeze"/>'
|
|
148
|
+
f'a model your AI reasons over — not a dump it hallucinates on</text>'
|
|
149
|
+
)
|
|
150
|
+
# loop: fade whole thing at the end and restart
|
|
151
|
+
total = reveal_all + 3.0
|
|
152
|
+
parts.append(
|
|
153
|
+
f'<rect width="{W}" height="{H}" fill="#0d1117" opacity="0">'
|
|
154
|
+
f'<animate attributeName="opacity" values="0;0;1;0" '
|
|
155
|
+
f'keyTimes="0;0.93;0.97;1" dur="{total:.1f}s" repeatCount="indefinite"/></rect>'
|
|
156
|
+
)
|
|
157
|
+
parts.append("</svg>")
|
|
158
|
+
return "\n".join(parts)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def write_svg(m: Model, path: str) -> None:
|
|
162
|
+
with atomic_write(path) as f:
|
|
163
|
+
f.write(render_svg(m))
|