patent-watch 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ .venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Calidran
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.
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.5
2
+ Name: patent-watch
3
+ Version: 0.1.0
4
+ Summary: Watch a company's US patent applications: office actions, allowances, abandonments, grants, assignments, PTAB petitions — to Slack, email, or a webhook.
5
+ Project-URL: Homepage, https://patentdata.ai
6
+ Project-URL: Source, https://github.com/calidran/patent-watch
7
+ Project-URL: API docs, https://api.patentdata.ai/docs
8
+ Author-email: Calidran <rish@patentdata.ai>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,mcp,office-action,patent-status,patents,uspto
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Legal Industry
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Office/Business
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: click>=8.1
19
+ Requires-Dist: httpx>=0.27
20
+ Description-Content-Type: text/markdown
21
+
22
+ # patent-watch
23
+
24
+ Watch a company's US patent portfolio and get told when something happens: an office action, a notice of
25
+ allowance, an abandonment, a grant, an assignment, a PTAB petition. Built on the
26
+ [Patent Data API](https://patentdata.ai) (USPTO bulk data, refreshed daily; per-request pricing,
27
+ no seats, redistribution allowed).
28
+
29
+ ## If you are an agent
30
+
31
+ You can set yourself up without a human, except for paying:
32
+
33
+ ```bash
34
+ pip install patent-watch
35
+ patent-watch signup your-owner@example.com # mints a key, saves it to ~/.patent-watch.json, prints a Stripe link
36
+ # hand the link to your human; $20 = 1,000 requests, credits land in seconds
37
+ patent-watch resolve "Impossible Foods" # → ent_... Impossible Foods Inc. apps=412 patents=180 pending=95
38
+ patent-watch pending ent_...
39
+ patent-watch poll ent_... --slack https://hooks.slack.com/services/... # run daily from cron
40
+ ```
41
+
42
+ Or skip the polling and let the API push to you:
43
+
44
+ ```bash
45
+ patent-watch monitor ent_... --webhook https://your-agent.example.com/hooks/patents
46
+ ```
47
+
48
+ Deliveries carry `X-Signature: sha256=<hmac-sha256(secret, body)>`.
49
+
50
+ ## If you use Claude Code
51
+
52
+ ```bash
53
+ claude mcp add --transport http patent-data https://api.patentdata.ai/mcp --header "Authorization: Bearer $PATENT_API_KEY"
54
+ ```
55
+
56
+ Then: *"What's the status of application 17942951 and who owns it?"*
57
+
58
+ ## Cost
59
+
60
+ `poll` costs 1 request per pending application per run plus 1 for the list (per 100 rows). A portfolio of 100
61
+ pending applications polled daily is ~3,000 requests/month ≈ $60 in credits, or use `monitor` (1 request per
62
+ delivered event).
63
+
64
+ ## Data
65
+
66
+ Every answer carries the USPTO product and file date it came from (`source`) and the API's ingest date (`as_of`).
67
+ USPTO is the source; this tool and the API are derivations.
@@ -0,0 +1,46 @@
1
+ # patent-watch
2
+
3
+ Watch a company's US patent portfolio and get told when something happens: an office action, a notice of
4
+ allowance, an abandonment, a grant, an assignment, a PTAB petition. Built on the
5
+ [Patent Data API](https://patentdata.ai) (USPTO bulk data, refreshed daily; per-request pricing,
6
+ no seats, redistribution allowed).
7
+
8
+ ## If you are an agent
9
+
10
+ You can set yourself up without a human, except for paying:
11
+
12
+ ```bash
13
+ pip install patent-watch
14
+ patent-watch signup your-owner@example.com # mints a key, saves it to ~/.patent-watch.json, prints a Stripe link
15
+ # hand the link to your human; $20 = 1,000 requests, credits land in seconds
16
+ patent-watch resolve "Impossible Foods" # → ent_... Impossible Foods Inc. apps=412 patents=180 pending=95
17
+ patent-watch pending ent_...
18
+ patent-watch poll ent_... --slack https://hooks.slack.com/services/... # run daily from cron
19
+ ```
20
+
21
+ Or skip the polling and let the API push to you:
22
+
23
+ ```bash
24
+ patent-watch monitor ent_... --webhook https://your-agent.example.com/hooks/patents
25
+ ```
26
+
27
+ Deliveries carry `X-Signature: sha256=<hmac-sha256(secret, body)>`.
28
+
29
+ ## If you use Claude Code
30
+
31
+ ```bash
32
+ claude mcp add --transport http patent-data https://api.patentdata.ai/mcp --header "Authorization: Bearer $PATENT_API_KEY"
33
+ ```
34
+
35
+ Then: *"What's the status of application 17942951 and who owns it?"*
36
+
37
+ ## Cost
38
+
39
+ `poll` costs 1 request per pending application per run plus 1 for the list (per 100 rows). A portfolio of 100
40
+ pending applications polled daily is ~3,000 requests/month ≈ $60 in credits, or use `monitor` (1 request per
41
+ delivered event).
42
+
43
+ ## Data
44
+
45
+ Every answer carries the USPTO product and file date it came from (`source`) and the API's ingest date (`as_of`).
46
+ USPTO is the source; this tool and the API are derivations.
@@ -0,0 +1,166 @@
1
+ """
2
+ patent-watch — a small agent over the patent-status API (https://github.com/calidran/patent-api).
3
+
4
+ patent-watch signup you@example.com # gets a key; prints the Stripe link to fund it ($20 = 1,000 requests)
5
+ patent-watch resolve "Impossible Foods" # company name -> entity id + counts
6
+ patent-watch pending ent_... # the entity's pending applications
7
+ patent-watch poll ent_... --slack $WEBHOOK # diff against last run; notify on new events (run from cron)
8
+ patent-watch monitor ent_... --webhook URL # server-side monitor: the API pushes signed events to your URL
9
+
10
+ Config: PATENT_API_KEY (or ~/.patent-watch.json written by `signup`), PATENT_API_URL (default https://api.patentdata.ai).
11
+ State for `poll` lives in ~/.patent-watch-state.json (last seen transaction per application).
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import sys
18
+ from datetime import date
19
+ from pathlib import Path
20
+
21
+ import click
22
+ import httpx
23
+
24
+ CONFIG = Path.home() / ".patent-watch.json"
25
+ STATE = Path.home() / ".patent-watch-state.json"
26
+ EVENTS = ("office_action", "allowance", "abandonment", "grant", "publication", "assignment", "ptab_petition")
27
+
28
+
29
+ def base_url() -> str:
30
+ return os.environ.get("PATENT_API_URL", "").strip() or _config().get("url") or "https://api.patentdata.ai"
31
+
32
+
33
+ def _config() -> dict:
34
+ return json.loads(CONFIG.read_text()) if CONFIG.exists() else {}
35
+
36
+
37
+ def api_key() -> str:
38
+ key = os.environ.get("PATENT_API_KEY", "").strip() or _config().get("api_key")
39
+ if not key:
40
+ raise click.ClickException("no API key: run `patent-watch signup <owner-email>` or set PATENT_API_KEY")
41
+ return key
42
+
43
+
44
+ def client() -> httpx.Client:
45
+ return httpx.Client(base_url=base_url(), headers={"X-API-KEY": api_key()}, timeout=30)
46
+
47
+
48
+ def get(c: httpx.Client, path: str, **params) -> dict:
49
+ r = c.get(path, params={k: v for k, v in params.items() if v is not None})
50
+ if r.status_code == 402:
51
+ body = r.json()
52
+ raise click.ClickException(f"key not funded: {body['checkout']} (min ${body['minimum_usd']}); run `patent-watch fund`")
53
+ if r.status_code >= 400:
54
+ raise click.ClickException(f"{r.status_code} {r.text[:300]}")
55
+ return r.json()
56
+
57
+
58
+ @click.group(help=__doc__)
59
+ def cli() -> None:
60
+ pass
61
+
62
+
63
+ @cli.command(help="Create an API key for this agent under your (human) email. Prints the funding link.")
64
+ @click.argument("owner_email")
65
+ @click.option("--agent-name", default="patent-watch")
66
+ @click.option("--url", default=None, help="API base URL")
67
+ def signup(owner_email: str, agent_name: str, url: str | None) -> None:
68
+ u = url or base_url()
69
+ r = httpx.post(f"{u}/agent/signup", json={"owner_email": owner_email, "agent_name": agent_name}, timeout=30)
70
+ if r.status_code >= 400:
71
+ raise click.ClickException(f"{r.status_code} {r.text[:300]}")
72
+ data = r.json()
73
+ CONFIG.write_text(json.dumps({"api_key": data["api_key"], "org_id": data["org_id"], "url": u}, indent=1))
74
+ CONFIG.chmod(0o600)
75
+ click.echo(f"saved key to {CONFIG} (org {data['org_id']})")
76
+ fund_link(u, data["api_key"])
77
+
78
+
79
+ def fund_link(u: str, key: str, amount: int = 20) -> None:
80
+ r = httpx.post(f"{u}/agent/checkout-link", json={"amount_usd": amount}, headers={"X-API-KEY": key}, timeout=30)
81
+ if r.status_code == 503:
82
+ click.echo("billing is not configured on this server yet; ask the operator for credits")
83
+ return
84
+ r.raise_for_status()
85
+ click.echo(f"fund it here (${amount} = {amount * 50} requests, no expiry): {r.json()['url']}")
86
+
87
+
88
+ @cli.command(help="Print a Stripe link to add credits.")
89
+ @click.option("--amount", default=20, type=int)
90
+ def fund(amount: int) -> None:
91
+ fund_link(base_url(), api_key(), amount)
92
+
93
+
94
+ @cli.command(help="Resolve a company or inventor name to entity ids.")
95
+ @click.argument("name")
96
+ @click.option("--type", "type_", type=click.Choice(["assignee", "inventor"]), default=None)
97
+ def resolve(name: str, type_: str | None) -> None:
98
+ with client() as c:
99
+ for e in get(c, "/v1/entities/search", q=name, type=type_, limit=10)["entities"]:
100
+ cnt = e["counts"]
101
+ click.echo(f"{e['entity_id']} {e['canonical_name']:50s} apps={cnt['applications']} patents={cnt['patents']} pending={cnt['pending']} ({e['source']})")
102
+
103
+
104
+ @cli.command(help="List an entity's pending applications.")
105
+ @click.argument("entity_id")
106
+ def pending(entity_id: str) -> None:
107
+ with client() as c:
108
+ for a in _all_pending(c, entity_id):
109
+ click.echo(f"{a['application_number']} {a['filing_date']} {a['status']['description'][:45]:45s} {a['title'][:60]}")
110
+
111
+
112
+ def _all_pending(c: httpx.Client, entity_id: str) -> list[dict]:
113
+ out, cursor = [], None
114
+ while True:
115
+ page = get(c, f"/v1/entities/{entity_id}/applications", status="pending", limit=100, cursor=cursor)
116
+ out += page["applications"]
117
+ cursor = page.get("next_cursor")
118
+ if not cursor:
119
+ return out
120
+
121
+
122
+ @cli.command(help="Diff each pending application's timeline against the last run and notify on new events. Cron-friendly.")
123
+ @click.argument("entity_id")
124
+ @click.option("--slack", "slack_webhook", default=None, help="Slack incoming-webhook URL")
125
+ @click.option("--since-days", default=30, help="On first run, look back this many days")
126
+ def poll(entity_id: str, slack_webhook: str | None, since_days: int) -> None:
127
+ state = json.loads(STATE.read_text()) if STATE.exists() else {}
128
+ seen: dict[str, str] = state.setdefault(entity_id, {})
129
+ lines = []
130
+ with client() as c:
131
+ for a in _all_pending(c, entity_id):
132
+ n = a["application_number"]
133
+ since = seen.get(n) or (date.fromordinal(date.today().toordinal() - since_days)).isoformat()
134
+ tl = get(c, f"/v1/applications/{n}/transactions", since=since)
135
+ new = [t for t in tl["transactions"] if t["event_type"] in EVENTS and (t["date"] > since or n not in seen)]
136
+ for t in sorted(new, key=lambda t: t["date"]):
137
+ lines.append(f"{t['date']} {t['event_type']:14s} {n} {a['title'][:50]} ({t['code']}: {t['description']})")
138
+ if tl["transactions"]:
139
+ seen[n] = max(t["date"] for t in tl["transactions"])
140
+ elif n not in seen:
141
+ seen[n] = since
142
+ STATE.write_text(json.dumps(state, indent=1))
143
+ if not lines:
144
+ click.echo("no new events")
145
+ return
146
+ text = "\n".join(lines)
147
+ click.echo(text)
148
+ if slack_webhook:
149
+ httpx.post(slack_webhook, json={"text": f"patent-watch {entity_id}:\n```{text}```"}, timeout=30).raise_for_status()
150
+
151
+
152
+ @cli.command(help="Create a server-side monitor: the API POSTs signed events to your webhook the night they appear.")
153
+ @click.argument("entity_id")
154
+ @click.option("--webhook", required=True)
155
+ @click.option("--events", default=",".join(EVENTS), show_default=True)
156
+ def monitor(entity_id: str, webhook: str, events: str) -> None:
157
+ with client() as c:
158
+ r = c.post("/v1/monitors", json={"targets": {"entity_ids": [entity_id]}, "events": events.split(","), "webhook_url": webhook})
159
+ if r.status_code >= 400:
160
+ raise click.ClickException(f"{r.status_code} {r.text[:300]}")
161
+ m = r.json()
162
+ click.echo(f"monitor {m['monitor_id']} created; verify X-Signature with secret: {m['secret']}")
163
+
164
+
165
+ if __name__ == "__main__":
166
+ cli()
@@ -0,0 +1,33 @@
1
+ [project]
2
+ name = "patent-watch"
3
+ version = "0.1.0"
4
+ description = "Watch a company's US patent applications: office actions, allowances, abandonments, grants, assignments, PTAB petitions — to Slack, email, or a webhook."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Calidran", email = "rish@patentdata.ai" }]
9
+ keywords = ["patents", "uspto", "patent-status", "office-action", "mcp", "agents"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Intended Audience :: Developers",
13
+ "Intended Audience :: Legal Industry",
14
+ "Programming Language :: Python :: 3",
15
+ "Topic :: Office/Business",
16
+ ]
17
+ requires-python = ">=3.11"
18
+ dependencies = ["httpx>=0.27", "click>=8.1"]
19
+
20
+ [project.urls]
21
+ Homepage = "https://patentdata.ai"
22
+ Source = "https://github.com/calidran/patent-watch"
23
+ "API docs" = "https://api.patentdata.ai/docs"
24
+
25
+ [project.scripts]
26
+ patent-watch = "patent_watch:cli"
27
+
28
+ [build-system]
29
+ requires = ["hatchling"]
30
+ build-backend = "hatchling.build"
31
+
32
+ [tool.hatch.build.targets.wheel]
33
+ packages = ["patent_watch.py"]
@@ -0,0 +1,104 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.11"
4
+
5
+ [[package]]
6
+ name = "anyio"
7
+ version = "4.15.1"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ dependencies = [
10
+ { name = "idna" },
11
+ { name = "typing-extensions", marker = "python_full_version < '3.15'" },
12
+ ]
13
+ sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
14
+ wheels = [
15
+ { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
16
+ ]
17
+
18
+ [[package]]
19
+ name = "certifi"
20
+ version = "2026.7.22"
21
+ source = { registry = "https://pypi.org/simple" }
22
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
23
+ wheels = [
24
+ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
25
+ ]
26
+
27
+ [[package]]
28
+ name = "click"
29
+ version = "8.5.0"
30
+ source = { registry = "https://pypi.org/simple" }
31
+ sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
32
+ wheels = [
33
+ { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
34
+ ]
35
+
36
+ [[package]]
37
+ name = "h11"
38
+ version = "0.16.0"
39
+ source = { registry = "https://pypi.org/simple" }
40
+ sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
41
+ wheels = [
42
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
43
+ ]
44
+
45
+ [[package]]
46
+ name = "httpcore"
47
+ version = "1.0.9"
48
+ source = { registry = "https://pypi.org/simple" }
49
+ dependencies = [
50
+ { name = "certifi" },
51
+ { name = "h11" },
52
+ ]
53
+ sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
54
+ wheels = [
55
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
56
+ ]
57
+
58
+ [[package]]
59
+ name = "httpx"
60
+ version = "0.28.1"
61
+ source = { registry = "https://pypi.org/simple" }
62
+ dependencies = [
63
+ { name = "anyio" },
64
+ { name = "certifi" },
65
+ { name = "httpcore" },
66
+ { name = "idna" },
67
+ ]
68
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
69
+ wheels = [
70
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
71
+ ]
72
+
73
+ [[package]]
74
+ name = "idna"
75
+ version = "3.19"
76
+ source = { registry = "https://pypi.org/simple" }
77
+ sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
78
+ wheels = [
79
+ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
80
+ ]
81
+
82
+ [[package]]
83
+ name = "patent-watch"
84
+ version = "0.1.0"
85
+ source = { editable = "." }
86
+ dependencies = [
87
+ { name = "click" },
88
+ { name = "httpx" },
89
+ ]
90
+
91
+ [package.metadata]
92
+ requires-dist = [
93
+ { name = "click", specifier = ">=8.1" },
94
+ { name = "httpx", specifier = ">=0.27" },
95
+ ]
96
+
97
+ [[package]]
98
+ name = "typing-extensions"
99
+ version = "4.16.0"
100
+ source = { registry = "https://pypi.org/simple" }
101
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
102
+ wheels = [
103
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
104
+ ]