ghost-api-cli 0.2.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.
Files changed (125) hide show
  1. ghost_api/__init__.py +3 -0
  2. ghost_api/__main__.py +5 -0
  3. ghost_api/adopt.py +173 -0
  4. ghost_api/agent/__init__.py +0 -0
  5. ghost_api/agent/orchestrator.py +413 -0
  6. ghost_api/agent/prompts.py +149 -0
  7. ghost_api/agent/tools.py +84 -0
  8. ghost_api/analysis/__init__.py +0 -0
  9. ghost_api/analysis/analyzer.py +130 -0
  10. ghost_api/analysis/detectors.py +188 -0
  11. ghost_api/analysis/exposure.py +80 -0
  12. ghost_api/analysis/graphql.py +67 -0
  13. ghost_api/analysis/models.py +64 -0
  14. ghost_api/analysis/schema.py +109 -0
  15. ghost_api/analysis/url_grouping.py +217 -0
  16. ghost_api/api.py +264 -0
  17. ghost_api/audit.py +78 -0
  18. ghost_api/browser/__init__.py +0 -0
  19. ghost_api/browser/auth_state.py +172 -0
  20. ghost_api/browser/driver.py +273 -0
  21. ghost_api/browser/egress.py +183 -0
  22. ghost_api/browser/gate.py +127 -0
  23. ghost_api/browser/install.py +28 -0
  24. ghost_api/browser/snapshot.py +221 -0
  25. ghost_api/capture/__init__.py +0 -0
  26. ghost_api/capture/filters.py +255 -0
  27. ghost_api/capture/har.py +148 -0
  28. ghost_api/capture/models.py +41 -0
  29. ghost_api/capture/personal.py +188 -0
  30. ghost_api/capture/recorder.py +181 -0
  31. ghost_api/cli/__init__.py +44 -0
  32. ghost_api/cli/_common.py +219 -0
  33. ghost_api/cli/client.py +487 -0
  34. ghost_api/cli/data.py +492 -0
  35. ghost_api/cli/explore.py +379 -0
  36. ghost_api/cli/manage.py +437 -0
  37. ghost_api/config.py +132 -0
  38. ghost_api/drift.py +344 -0
  39. ghost_api/drive.py +211 -0
  40. ghost_api/errors.py +106 -0
  41. ghost_api/events.py +213 -0
  42. ghost_api/exitcodes.py +21 -0
  43. ghost_api/export/__init__.py +71 -0
  44. ghost_api/export/confidence.py +45 -0
  45. ghost_api/export/html.py +41 -0
  46. ghost_api/export/openapi.py +201 -0
  47. ghost_api/export/report.py +227 -0
  48. ghost_api/export/site_data.py +196 -0
  49. ghost_api/export/tabular.py +88 -0
  50. ghost_api/export/viewer/report.html +286 -0
  51. ghost_api/generator/__init__.py +221 -0
  52. ghost_api/generator/cassettes.py +80 -0
  53. ghost_api/generator/models_gen.py +144 -0
  54. ghost_api/generator/naming.py +174 -0
  55. ghost_api/generator/package.py +197 -0
  56. ghost_api/generator/python_gen.py +172 -0
  57. ghost_api/generator/spec.py +486 -0
  58. ghost_api/generator/templates/client.py.j2 +518 -0
  59. ghost_api/generator/templates/client.test.ts.j2 +106 -0
  60. ghost_api/generator/templates/client.ts.j2 +339 -0
  61. ghost_api/generator/templates/models.py.j2 +45 -0
  62. ghost_api/generator/templates/package_readme.md.j2 +111 -0
  63. ghost_api/generator/templates/pyproject.toml.j2 +22 -0
  64. ghost_api/generator/templates/test_client.py.j2 +105 -0
  65. ghost_api/generator/ts_gen.py +232 -0
  66. ghost_api/integrations/__init__.py +0 -0
  67. ghost_api/integrations/claude_code/commands/ghost-data.md +10 -0
  68. ghost_api/integrations/claude_code/commands/ghost-diff.md +10 -0
  69. ghost_api/integrations/claude_code/commands/ghost-explore.md +10 -0
  70. ghost_api/integrations/claude_code/skills/ghost-api/SKILL.md +46 -0
  71. ghost_api/integrations/setup.py +84 -0
  72. ghost_api/llm/__init__.py +0 -0
  73. ghost_api/llm/doctor.py +60 -0
  74. ghost_api/llm/gateway.py +476 -0
  75. ghost_api/llm/providers.py +124 -0
  76. ghost_api/memory/__init__.py +0 -0
  77. ghost_api/memory/knowledge.py +157 -0
  78. ghost_api/memory/store.py +883 -0
  79. ghost_api/net.py +235 -0
  80. ghost_api/netguard.py +203 -0
  81. ghost_api/pipeline.py +745 -0
  82. ghost_api/probe/__init__.py +0 -0
  83. ghost_api/probe/prober.py +311 -0
  84. ghost_api/probe/replay.py +313 -0
  85. ghost_api/project.py +186 -0
  86. ghost_api/py.typed +0 -0
  87. ghost_api/runtime.py +397 -0
  88. ghost_api/serve/__init__.py +101 -0
  89. ghost_api/serve/explorer_mcp.py +305 -0
  90. ghost_api/serve/mcp.py +121 -0
  91. ghost_api/serve/mock.py +130 -0
  92. ghost_api/serve/rest.py +132 -0
  93. ghost_api/site.py +84 -0
  94. ghost_api/specdiff.py +122 -0
  95. ghost_api/ui/__init__.py +0 -0
  96. ghost_api/ui/keys.py +134 -0
  97. ghost_api/ui/repl.py +453 -0
  98. ghost_api/ui/terminal.py +573 -0
  99. ghost_api/verify/__init__.py +0 -0
  100. ghost_api/verify/classify.py +84 -0
  101. ghost_api/verify/repair.py +40 -0
  102. ghost_api/verify/runner.py +110 -0
  103. ghost_api/verify/verifier.py +291 -0
  104. ghost_api/watch.py +313 -0
  105. ghost_api/web/__init__.py +1 -0
  106. ghost_api/web/server.py +690 -0
  107. ghost_api/web/static/assets/Endpoints-zPnjlcM8.js +46 -0
  108. ghost_api/web/static/assets/Run-D85zWHey.js +91 -0
  109. ghost_api/web/static/assets/Settings-D0aWHFAt.js +11 -0
  110. ghost_api/web/static/assets/Site-a88shEmP.js +71 -0
  111. ghost_api/web/static/assets/Watches-CqZWIa9G.js +16 -0
  112. ghost_api/web/static/assets/circle-x-BcTE_1tr.js +11 -0
  113. ghost_api/web/static/assets/index-Ds2e5B9f.js +141 -0
  114. ghost_api/web/static/assets/index-xQpyIBbG.css +1 -0
  115. ghost_api/web/static/assets/minus-C2xqP1eQ.js +6 -0
  116. ghost_api/web/static/assets/refresh-cw-CxdR_8Oi.js +6 -0
  117. ghost_api/web/static/favicon.svg +1 -0
  118. ghost_api/web/static/index.html +26 -0
  119. ghost_api/workflows.py +203 -0
  120. ghost_api_cli-0.2.0.dist-info/METADATA +1019 -0
  121. ghost_api_cli-0.2.0.dist-info/RECORD +125 -0
  122. ghost_api_cli-0.2.0.dist-info/WHEEL +5 -0
  123. ghost_api_cli-0.2.0.dist-info/entry_points.txt +2 -0
  124. ghost_api_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
  125. ghost_api_cli-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1019 @@
1
+ Metadata-Version: 2.4
2
+ Name: ghost-api-cli
3
+ Version: 0.2.0
4
+ Summary: An AI agent that turns any website into a typed, tested API client.
5
+ Author: Danielraj
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Danielraj5605/Ghost-api
8
+ Project-URL: Documentation, https://github.com/Danielraj5605/Ghost-api#readme
9
+ Project-URL: Changelog, https://github.com/Danielraj5605/Ghost-api/blob/main/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/Danielraj5605/Ghost-api/issues
11
+ Project-URL: Security, https://github.com/Danielraj5605/Ghost-api/blob/main/SECURITY.md
12
+ Keywords: api,reverse-engineering,api-client,openapi,code-generation,playwright,ai-agent,llm,web-scraping,sdk-generator
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Code Generators
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: playwright>=1.45
28
+ Requires-Dist: httpx>=0.27
29
+ Requires-Dist: pydantic>=2.7
30
+ Requires-Dist: typer>=0.12
31
+ Requires-Dist: rich>=13.7
32
+ Requires-Dist: python-dotenv>=1.0
33
+ Requires-Dist: genson>=1.3
34
+ Requires-Dist: jinja2>=3.1
35
+ Requires-Dist: pyyaml>=6.0
36
+ Requires-Dist: fastapi>=0.110
37
+ Requires-Dist: uvicorn>=0.29
38
+ Requires-Dist: openpyxl>=3.1
39
+ Requires-Dist: prompt_toolkit>=3.0.43
40
+ Provides-Extra: mcp
41
+ Requires-Dist: mcp>=1.2; extra == "mcp"
42
+ Provides-Extra: notify
43
+ Requires-Dist: plyer>=2.1; extra == "notify"
44
+ Provides-Extra: docs
45
+ Requires-Dist: mkdocs<2,>=1.6; extra == "docs"
46
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
47
+ Provides-Extra: dev
48
+ Requires-Dist: pytest>=8; extra == "dev"
49
+ Requires-Dist: ruff>=0.6; extra == "dev"
50
+ Requires-Dist: mypy>=1.11; extra == "dev"
51
+ Requires-Dist: openapi-spec-validator>=0.7; extra == "dev"
52
+ Requires-Dist: types-PyYAML; extra == "dev"
53
+ Requires-Dist: psutil>=5.9; extra == "dev"
54
+ Requires-Dist: types-psutil; extra == "dev"
55
+ Requires-Dist: pywinpty>=2.0; sys_platform == "win32" and extra == "dev"
56
+ Dynamic: license-file
57
+
58
+ # Ghost API
59
+
60
+ **An AI agent that turns any website into a typed, tested API client.**
61
+
62
+ Give Ghost API a website and a plain-English goal. It opens the site in a real browser, watches the hidden requests the site makes to its own server, works out how that hidden API behaves, and writes a clean Python (or TypeScript) client library for it, with tests.
63
+
64
+ ![The Ghost API dashboard during a real run on quotes.toscrape.com](https://raw.githubusercontent.com/Danielraj5605/Ghost-api/main/docs/images/dashboard-demo.gif)
65
+
66
+ <sub>A real run on quotes.toscrape.com from the web dashboard (`ghost-api ui`): the agent loads the page, the API call is tested, a client is generated and verified, and "Try it" calls the endpoint. 2 free LLM calls, about 20 seconds.</sub>
67
+
68
+ > **Status:** Version 0.2.0. All 11 build stages are done and evaluated on four sites (see [Evaluation](#evaluation)): every goal endpoint found, nothing invented, every generated client test passing, for 1–10 free LLM calls per site. It has a live terminal UI, a local web dashboard and a shareable one-file HTML report. See [IMPLEMENTATION.md](IMPLEMENTATION.md) for how it was built.
69
+
70
+ ---
71
+
72
+ **Documentation:** guides, the command reference and the Python API are in [docs/](docs/index.md)
73
+ (build the site with `pip install -e ".[docs]"` then `mkdocs serve`).
74
+
75
+ ## Table of contents
76
+
77
+ - [The simple explanation](#the-simple-explanation)
78
+ - [The problem](#the-problem)
79
+ - [What Ghost API does](#what-ghost-api-does)
80
+ - [Example](#example)
81
+ - [How it works](#how-it-works)
82
+ - [Why it's an agent, not a script](#why-its-an-agent-not-a-script)
83
+ - [Memory](#memory)
84
+ - [Features](#features)
85
+ - [Tech stack](#tech-stack)
86
+ - [Getting started](#getting-started)
87
+ - [What can I do with it?](#what-can-i-do-with-it)
88
+ - [Three ways to see the results](#three-ways-to-see-the-results)
89
+ - [Usage](#usage)
90
+ - [Output](#output)
91
+ - [Project structure](#project-structure)
92
+ - [Running on free LLM tiers](#running-on-free-llm-tiers)
93
+ - [Responsible use](#responsible-use)
94
+ - [Evaluation](#evaluation)
95
+ - [Limitations](#limitations)
96
+ - [Roadmap](#roadmap)
97
+
98
+ ---
99
+
100
+ ## The simple explanation
101
+
102
+ Picture a restaurant with **no menu**. You can eat there, but only by pointing at other people's plates. Behind the scenes, every time someone orders, the waiter hands the kitchen a neat order slip:
103
+
104
+ > "Table 4: 1x Pasta, no cheese, large."
105
+
106
+ The kitchen understands those slips perfectly. You just never see them.
107
+
108
+ **Ghost API sits in the restaurant, watches every order slip going to the kitchen, and writes the menu for you.** After that you can order directly, without pointing at plates.
109
+
110
+ | In the analogy | In real life |
111
+ |---|---|
112
+ | The restaurant | Any website (a bookstore, a weather site, a job board) |
113
+ | Pointing at plates | Copying data by hand, or fragile "scraper" programs that break when the design changes |
114
+ | The order slips | The hidden requests a website makes to its own server |
115
+ | The menu | A small code library that gets the data directly, cleanly and reliably |
116
+
117
+ **What makes it "AI":** Ghost API doesn't just record. Like a curious person, it:
118
+
119
+ 1. **Explores** the site by clicking, searching and scrolling
120
+ 2. **Notices patterns**, such as "every time I open a book, the site asks for `/books/<number>`"
121
+ 3. **Tests its guesses**, such as "if I ask for page 2, do I get different results?"
122
+ 4. **Fixes its mistakes** when a guess turns out wrong
123
+ 5. **Remembers** what it learned, so next time is faster
124
+
125
+ ---
126
+
127
+ ## The problem
128
+
129
+ Almost every modern website loads its data through a **hidden internal API**. When you open a product page, your browser quietly calls something like:
130
+
131
+ ```
132
+ GET https://shop.example.com/api/v2/products/123?include=reviews
133
+ -> { "id": 123, "name": "Blue Shoes", "price": 59.99, "reviews": [...] }
134
+ ```
135
+
136
+ Developers often want that data in their own code. Their options today are poor:
137
+
138
+ | Approach | Why it's poor |
139
+ |---|---|
140
+ | Scrape the HTML | Breaks whenever the page layout changes, and is slow |
141
+ | Reverse-engineer by hand in browser DevTools | Takes hours: clicking around, copying requests, guessing what each field means |
142
+ | Tools like mitmproxy or Postman | Record raw traffic, but don't understand it or write any code |
143
+
144
+ No existing tool both **discovers** these hidden APIs and **produces working code** for them.
145
+
146
+ ---
147
+
148
+ ## What Ghost API does
149
+
150
+ | Input | Output |
151
+ |---|---|
152
+ | A website URL | A typed Python client library (`httpx` + `pydantic`) |
153
+ | A plain-English goal ("search books and get details") | A pytest test suite that runs against the live site |
154
+ | | An OpenAPI 3 spec |
155
+ | | A report showing what was found, how confident it is, and the agent's reasoning |
156
+
157
+ ---
158
+
159
+ ## Example
160
+
161
+ Real output from the practice site `quotes.toscrape.com/scroll` (abridged):
162
+
163
+ ```
164
+ $ ghost-api run https://quotes.toscrape.com/scroll --goal "list quotes with their authors and tags"
165
+
166
+ Exploring site...
167
+ GET 200 /api/quotes?page=1
168
+ Step 1 finish(goal_covered=True) The discovered endpoint already returns quotes, their authors, and tags
169
+
170
+ Testing 1 endpoint(s) with direct calls...
171
+ GET /api/quotes
172
+ confirmed can be called directly, outside the browser HTTP 200, 10 records
173
+ confirmed paginates with page parameter 'page' the next page returned 10 different records
174
+ confirmed needs none of its custom headers or cookies
175
+
176
+ Verifying the generated client against the live API...
177
+ Round 1: 2 passed
178
+
179
+ Generated QuotesToscrapeClient in output/quotes.toscrape.com
180
+ get_quotes() -> QuotesResponse + iter_get_quotes()
181
+ ```
182
+
183
+ The generated client:
184
+
185
+ ```python
186
+ from quotes_toscrape_client import QuotesToscrapeClient
187
+
188
+ with QuotesToscrapeClient() as api:
189
+ page = api.get_quotes() # -> QuotesResponse (pydantic)
190
+ print(page.quotes[0].author.name) # nested models: Quote, Author
191
+ for p in api.iter_get_quotes(max_pages=3): # pagination handled for you
192
+ ...
193
+ ```
194
+
195
+ The same client in TypeScript (`--lang typescript`):
196
+
197
+ ```ts
198
+ import { QuotesToscrapeClient } from "./quotes_toscrape_client.ts";
199
+
200
+ const api = new QuotesToscrapeClient();
201
+ const page = await api.getQuotes(); // Promise<QuotesResponse>
202
+ for await (const p of api.iterGetQuotes({ maxPages: 3 })) console.log(p.page);
203
+ ```
204
+
205
+ The TypeScript client is made for **Node 18+** (servers, scripts, serverless functions). In a
206
+ web page it mostly won't work: browsers block reading another site's replies unless that site
207
+ allows it (CORS), and they don't let code set the identifying User-Agent header. For a web
208
+ app, call the client from your own small server and let the page call that:
209
+
210
+ ```ts
211
+ // server.ts (Node): the page calls /api/quotes on your server, your server calls the site.
212
+ import { createServer } from "node:http";
213
+ import { QuotesToscrapeClient } from "./quotes_toscrape_client.ts";
214
+
215
+ const api = new QuotesToscrapeClient();
216
+ createServer(async (req, res) => {
217
+ if (req.url !== "/api/quotes") return void res.writeHead(404).end();
218
+ res.setHeader("Content-Type", "application/json");
219
+ res.end(JSON.stringify(await api.getQuotes()));
220
+ }).listen(3000);
221
+ ```
222
+
223
+ A harder case: on `hn.algolia.com` the agent switched the search filter to "Comments", found
224
+ that the real API is a POST to Algolia's servers (a third-party domain), and the prober
225
+ confirmed that paging works in the JSON body and that the `x-algolia-api-key` URL parameter
226
+ is required (HTTP 403 without it). The client it generated:
227
+
228
+ ```python
229
+ from hn_algolia_client import HnAlgoliaClient # key from HN_ALGOLIA_X_ALGOLIA_API_KEY
230
+
231
+ with HnAlgoliaClient() as api:
232
+ results = api.search_stories(index="Item_dev", query="python") # -> SearchResults
233
+ ```
234
+
235
+ ---
236
+
237
+ ## How it works
238
+
239
+ ```
240
+ +---------------------+
241
+ | START |
242
+ | URL + goal + lang |
243
+ +----------+----------+
244
+ v
245
+ +---------------------+
246
+ | 0. PRE-CHECKS |
247
+ | robots.txt, memory |---- known site? ---> load past
248
+ | lookup, budgets | knowledge
249
+ +----------+----------+
250
+ v
251
+ +----------------------------------------------------+
252
+ | AGENT LOOP |
253
+ | 1. OBSERVE page snapshot + captured traffic |
254
+ | 2. DECIDE LLM picks: explore / test / ask / |
255
+ | finish |
256
+ | 3. ACT run a tool (click, type, call API) |
257
+ | 4. LEARN update endpoint map + schemas |
258
+ | goal covered? budget left? --no--> back to 1 |
259
+ +----------+-----------------------------------------+
260
+ | yes
261
+ v
262
+ +---------------------+
263
+ | 5. GENERATE CODE | client + models + tests
264
+ +----------+----------+
265
+ v
266
+ +---------------------+ fail +--------------+
267
+ | 6. VERIFY (tests) |--------------->| 7. DIAGNOSE |--> back to
268
+ +----------+----------+ | & REPLAN | agent loop
269
+ | pass +--------------+ (max 3x)
270
+ v
271
+ +---------------------+
272
+ | 8. SAVE MEMORY + |
273
+ | WRITE REPORT |
274
+ +---------------------+
275
+ ```
276
+
277
+ ### Step by step
278
+
279
+ **0. Pre-checks.** It reads `robots.txt`, looks the domain up in memory, and sets budgets (maximum LLM calls, steps and minutes).
280
+
281
+ **1. Observe.** The agent gets a compact view of the page: the clickable elements and inputs with short IDs (such as `[12] button "Search"`), a summary of the new network requests, the endpoints found so far, and which parts of the goal are still uncovered.
282
+
283
+ **2. Decide.** The LLM picks **one** action and explains why:
284
+
285
+ ```json
286
+ {
287
+ "thought": "I found the book list endpoint but not book details. Clicking a book should trigger it.",
288
+ "action": "click",
289
+ "args": { "element_id": 23 }
290
+ }
291
+ ```
292
+
293
+ **3. Act.** The chosen tool runs: click, type, scroll, navigate, call an endpoint directly, ask the user, or finish.
294
+
295
+ **4. Learn.** Ordinary code, not the LLM, processes the traffic:
296
+ - Filters out noise (images, CSS, fonts, analytics, ads)
297
+ - Groups URLs into templates: `/api/books/123` and `/api/books/456` become `/api/books/{id}`
298
+ - Infers JSON schemas from real responses and merges them across samples
299
+ - Classifies query parameters as required, optional or constant
300
+
301
+ **5. Generate.** The LLM gives endpoints and fields readable names. Jinja2 templates produce the client, pydantic models, tests and an OpenAPI spec.
302
+
303
+ **6. Verify.** The generated tests run against the live site and check that each response matches its schema.
304
+
305
+ **7. Diagnose and replan.** If a test fails, the agent works out why and recovers:
306
+
307
+ | Failure | Likely cause | Agent's response |
308
+ |---|---|---|
309
+ | 401 or 403 | Missing cookie, token or header | Retry with the browser's cookies, then find which header is required |
310
+ | Schema mismatch | Too few samples | Explore more, then widen the schema |
311
+ | 404 | Wrong URL template | Re-check how the URL was grouped |
312
+ | 429 | Rate limited | Back off and slow down |
313
+ | Timeout | Slow endpoint | Retry, then mark as unstable |
314
+
315
+ **8. Save and report.** It saves what it learned to memory and writes `report.md`.
316
+
317
+ ---
318
+
319
+ ## Why it's an agent, not a script
320
+
321
+ A fixed script can record traffic. Ghost API has to **make decisions**:
322
+
323
+ | Decision | Example |
324
+ |---|---|
325
+ | Where to explore next | "I've found search but not reviews. Click the reviews tab." |
326
+ | When it has seen enough | "Three similar responses. The schema is stable, so stop." |
327
+ | Which guesses to test | "This looks like cursor pagination, not page numbers. Test it." |
328
+ | How to recover from failure | "Direct call returned 403. Retry with the browser's cookies." |
329
+ | When to ask you | "This needs a login. Please log in in the browser window, then press Enter." |
330
+
331
+ **Design principle:** the LLM decides and names things. Deterministic code does the heavy lifting: capture, grouping, schema inference and code generation. This keeps it cheap, fast, and reliable even with small free models.
332
+
333
+ ---
334
+
335
+ ## Memory
336
+
337
+ Ghost API improves with use:
338
+
339
+ - **Per-site memory:** endpoints, schemas, auth style and pagination style for each domain. A second run re-verifies instead of re-exploring.
340
+ - **Lessons learned:** notes such as "this site needs the X-CSRF header" are fed back into the prompt on later runs.
341
+ - **LLM cache:** identical prompts return cached answers, which saves free-tier quota.
342
+
343
+ ---
344
+
345
+ ## Features
346
+
347
+ - **One command:** URL and goal in; endpoint map, typed client, tests, report and OpenAPI spec out
348
+ - **AI exploration** in a real browser (Playwright), one decision per step, with live reasoning
349
+ - **Capture** of the site's JSON API traffic, including third-party APIs (for example Algolia),
350
+ with analytics, health checks and infrastructure filtered out
351
+ - **Redaction** of cookies, tokens and keys before anything is stored, logged or sent to an LLM
352
+ - **Analysis:** URL templates (`/books/{id}`), parameter types, merged JSON schemas
353
+ - **Hypothesis testing** by direct calls: pagination (page, offset, cursor), required headers,
354
+ API keys, auth style
355
+ - **Python client** with pydantic models, rate limiting, retries, credentials from env vars and
356
+ `iter_...()` helpers for pagination; passes `mypy --strict`
357
+ - **Verification:** the generated tests run against the live API; failures are diagnosed and
358
+ repaired (up to 3 rounds), and every endpoint is marked verified, unverified or failed
359
+ - **Memory:** `refresh` re-checks a known site with as little work as possible
360
+ - **Report and OpenAPI 3.1** export, plus a replayable log of every run
361
+ - **Free:** runs on free LLM tiers with fallback between providers, caching and budgets
362
+ - **Live terminal UI** in the style of Claude Code: phase stepper, compact step log, a status
363
+ line that updates in place, and a summary card with a copy-paste "use it" line
364
+ - **Web dashboard** (`ghost-api ui`): start runs, watch the agent's browser and reasoning live,
365
+ browse endpoints with their schemas, call them with "Try it", read and download the code
366
+ - **Shareable report:** every run writes `report.html`, one file that works offline
367
+ - **Data download, no code:** `ghost-api fetch` and the dashboard's Download data:
368
+ all pages to CSV, Excel or JSON
369
+ - **Watch & alerts:** new / changed / gone items, to your phone, a webhook or the desktop
370
+ - **REST and MCP servers:** use sites from Excel, any language, or an AI assistant
371
+ - **Personal-data leak check:** spots and hides emails, phones, PAN/GST/Aadhaar, UPI and
372
+ bank details, and reports which endpoints expose them
373
+
374
+ - **TypeScript client** too (`--lang typescript`): interfaces, a fetch-based client for Node 18+
375
+ (for web pages, call it from your own server), async page iterators, and Node tests; passes
376
+ `tsc --strict`
377
+
378
+ - **GraphQL:** each named operation becomes its own typed method (`get_user(id)`), with the
379
+ query built in and the variables as arguments; mutations are never replayed or generated
380
+
381
+ **Not yet:** WebSocket and server-sent-event APIs.
382
+
383
+ ---
384
+
385
+ ## Tech stack
386
+
387
+ | Part | Tool |
388
+ |---|---|
389
+ | Language | Python 3.11+ |
390
+ | Browser automation | Playwright (Chromium) |
391
+ | LLM access | Small built-in gateway over the OpenAI-compatible APIs of Gemini (main), Groq and OpenRouter (fallbacks) |
392
+ | HTTP | httpx |
393
+ | Data models | pydantic |
394
+ | Schema inference | genson |
395
+ | Code generation | Jinja2 |
396
+ | CLI and terminal UI | Typer + Rich |
397
+ | Web dashboard | FastAPI + uvicorn (local server), React + Vite + Tailwind CSS (frontend, prebuilt) |
398
+ | Tests | pytest |
399
+ | Storage | SQLite |
400
+
401
+ Runs on an ordinary laptop: 8 GB RAM, no GPU.
402
+
403
+ ---
404
+
405
+ ## Getting started
406
+
407
+ **You need:** Python 3.11+ and, for the AI agent, one free LLM key from
408
+ [Google AI Studio](https://aistudio.google.com/apikey) (Gemini), [Groq](https://console.groq.com/keys)
409
+ or [OpenRouter](https://openrouter.ai/keys). Without a key, runs still load and scroll pages
410
+ and map what they see (`--no-agent`).
411
+
412
+ **1. Install** (once; any OS). [pipx](https://pipx.pypa.io) or [uv](https://docs.astral.sh/uv/)
413
+ put the `ghost-api` command on your PATH in its own environment:
414
+
415
+ ```bash
416
+ pipx install git+https://github.com/Danielraj5605/Ghost-api
417
+ # or: uv tool install git+https://github.com/Danielraj5605/Ghost-api
418
+ ```
419
+
420
+ (After the first PyPI release this becomes `pipx install ghost-api-cli`.)
421
+
422
+ **2. Set up a folder** for your work. `init` creates the project file, downloads the
423
+ browser Ghost API drives (Chromium, about 150 MB, once), asks for an LLM key (optional,
424
+ typed hidden, saved in `.env`) and keeps keys and data out of git:
425
+
426
+ ```bash
427
+ mkdir my-apis && cd my-apis
428
+ ghost-api init
429
+ ghost-api doctor # checks Python, Chromium, the project and each key
430
+ ```
431
+
432
+ **3. Explore a site:**
433
+
434
+ ```bash
435
+ ghost-api run https://quotes.toscrape.com/scroll --goal "list quotes with their authors"
436
+ ghost-api # or the interactive session: paste a site, say what you want
437
+ ```
438
+
439
+ ### With Claude Code (no LLM key needed)
440
+
441
+ ```bash
442
+ ghost-api integrate claude-code # also: cursor, vscode, desktop
443
+ ```
444
+
445
+ Then in Claude Code: `/ghost-explore https://quotes.toscrape.com/scroll quotes with authors`.
446
+ Claude reads each page and decides what to click; Ghost API acts under its rules, maps the
447
+ hidden API, tests every endpoint, and writes the typed client, report and OpenAPI spec. In a
448
+ test on the demo shop, Claude mapped all five endpoints (all verified) in 12 turns. Claude
449
+ alone found them too (7 turns): on easy sites Ghost API's value is what comes after, such as
450
+ checking the live API again in seconds with no LLM, offline tests, change detection, rules and
451
+ an audit trail. Measured in [docs/benchmark.md](docs/benchmark.md).
452
+ Real runs on public practice sites: [docs/gallery.md](docs/gallery.md).
453
+
454
+ ### From Python
455
+
456
+ Everything the command does is also a function, with the same rules:
457
+
458
+ ```python
459
+ from ghost_api.api import discover, fetch, diff
460
+
461
+ result = discover("https://quotes.toscrape.com/scroll", "list quotes with their authors")
462
+ print(result.status, [e["path"] for e in result.endpoints], result.client_files)
463
+
464
+ rows = fetch("quotes.toscrape.com/scroll", "get_quotes", pages=3) # list of records
465
+ changes = diff("quotes.toscrape.com/scroll", live=True) # breaking or not
466
+ ```
467
+
468
+ Also `refresh`, `generate`, `operations` and `sites`. Errors are exceptions you can catch:
469
+ `NotAllowed` (robots.txt), `NoLLMKey`, `NothingRemembered`, `GhostApiError`.
470
+
471
+ ### Project settings
472
+
473
+ `ghost-api init` writes `ghost-api.toml`. Every command run in that folder, or any folder
474
+ below it, uses the project's database and output folder, and its defaults:
475
+
476
+ ```toml
477
+ [project]
478
+ database = ".ghost-api/ghost_api.db" # runs, traffic, endpoints (keep it private)
479
+ output = "output" # reports, clients, downloads
480
+
481
+ [defaults] # any command option, by name
482
+ lang = "typescript"
483
+ rate_limit = 0.5
484
+ max_steps = 30
485
+ ```
486
+
487
+ Options on the command line win over the file, and `GHOST_API_DB` wins over both. Keys are
488
+ read from `.env` in the current folder, the project folder, then `~/.ghost-api/.env`
489
+ (handy for one set of keys everywhere).
490
+
491
+ ### Scripts and CI: `--json` and exit codes
492
+
493
+ Add `--json` anywhere on a command to get its result as JSON on stdout; progress and
494
+ messages go to stderr, so it pipes cleanly:
495
+
496
+ ```bash
497
+ ghost-api run https://quotes.toscrape.com/scroll -g "list quotes" --no-agent --json > run.json
498
+ ghost-api inspect quotes.toscrape.com/scroll --json | jq '.endpoints[].path'
499
+ ghost-api fetch quotes.toscrape.com/scroll get_quotes --pages 3 --json | jq '.rows'
500
+ ```
501
+
502
+ | Exit code | Meaning |
503
+ |---|---|
504
+ | 0 | Done (a run may still have found nothing; check the JSON or report) |
505
+ | 1 | Failed: an error, failed checks, or nothing stored to work on |
506
+ | 2 | Wrong command line or setup: bad option, missing LLM key, unreadable `ghost-api.toml` |
507
+ | 3 | The site said no: robots.txt disallows it, or a bot-protection page |
508
+ | 130 | Stopped with Ctrl+C |
509
+
510
+ ### From source (contributors)
511
+
512
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the developer setup. In short, pick the block for
513
+ your terminal (no Node.js needed: the dashboard comes prebuilt):
514
+
515
+ **Windows, Command Prompt (cmd)**
516
+
517
+ ```bat
518
+ git clone https://github.com/Danielraj5605/Ghost-api ghost-api
519
+ cd ghost-api
520
+ python -m venv .venv
521
+ .venv\Scripts\activate.bat
522
+ pip install -e .
523
+ playwright install chromium
524
+ copy .env.example .env
525
+ ```
526
+
527
+ **Windows, PowerShell**
528
+
529
+ ```powershell
530
+ git clone https://github.com/Danielraj5605/Ghost-api ghost-api
531
+ cd ghost-api
532
+ python -m venv .venv
533
+ .venv\Scripts\Activate.ps1
534
+ pip install -e .
535
+ playwright install chromium
536
+ Copy-Item .env.example .env
537
+ ```
538
+
539
+ If PowerShell says running scripts is disabled, run
540
+ `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once, or use Command Prompt.
541
+
542
+ **macOS / Linux**
543
+
544
+ ```bash
545
+ git clone https://github.com/Danielraj5605/Ghost-api ghost-api
546
+ cd ghost-api
547
+ python3 -m venv .venv
548
+ source .venv/bin/activate
549
+ pip install -e .
550
+ playwright install chromium
551
+ cp .env.example .env
552
+ ```
553
+
554
+ Then put at least one key in `.env` and check it:
555
+
556
+ ```bash
557
+ ghost-api doctor # checks each key with one tiny call
558
+ ```
559
+
560
+ You'll know the environment is active when your prompt starts with `(.venv)`. Use
561
+ `pip install -e ".[dev]"` instead if you want the tests and linters. If the `ghost-api`
562
+ command is blocked or not found, `python -m ghost_api` does the same thing
563
+ (for example `python -m ghost_api doctor`).
564
+
565
+ **Run it from any folder (Windows).** To start Ghost API by just typing `ghost-api` in
566
+ any Command Prompt or PowerShell window, the way you start `claude`, run this once from
567
+ the project folder:
568
+
569
+ ```bat
570
+ scripts\install-command.cmd
571
+ ```
572
+
573
+ It adds a small `ghost-api` launcher to `%USERPROFILE%\.local\bin` (and puts that folder
574
+ on your PATH if needed). The launcher uses the project's virtual environment, so there's
575
+ nothing to activate, and always works with the project's `.env`, memory and `output\`
576
+ folder. Then open a new window and type `ghost-api` (it asks for a website and a goal),
577
+ `ghost-api ui` for the dashboard, or any command below. On macOS/Linux, `pipx install -e .`
578
+ does the same job.
579
+
580
+ What `.env` looks like:
581
+
582
+ ```
583
+ GEMINI_API_KEY=your_key_here
584
+ GROQ_API_KEY=optional_fallback_key
585
+ OPENROUTER_API_KEY=optional_second_fallback
586
+ ```
587
+
588
+ ---
589
+
590
+ ## What can I do with it?
591
+
592
+ Ghost API first learns a website's hidden API (`ghost-api run ...`). After that, you can use
593
+ what it learned in five ways, most of them without writing any code:
594
+
595
+ | I want to... | Do this |
596
+ |---|---|
597
+ | **Get the data in a spreadsheet** | `ghost-api fetch turftown.in/chennai get_nearby_venues area=velachery sport=football --format xlsx` follows all the pages and saves an Excel, CSV or JSON file. In the dashboard: open an endpoint and press **Download data**. `ghost-api fetch SITE` lists what you can fetch. |
598
+ | **Know when something changes** | `ghost-api watch add quotes.toscrape.com list_quotes --every 6h --notify ntfy:my-long-private-topic` re-checks on a schedule and tells you what's new, what changed (a price, a name...) and what's gone, on your phone ([ntfy](https://ntfy.sh)), a webhook (Discord, Slack...) or a desktop notification. `ghost-api watch schedule` keeps it running in the background on Windows. Dashboard: **Watch this for changes**, and the **Watches** page. |
599
+ | **Use it from Excel, scripts or any language** | `ghost-api serve turftown.in/chennai` gives every endpoint a clean local URL, e.g. `http://127.0.0.1:8900/turftown.in~chennai/get_nearby_venues?area=velachery&format=csv`, with interactive docs at `/docs`. |
600
+ | **Let an AI assistant use the site** | `claude mcp add ghost-turftown -- ghost-api mcp turftown.in/chennai` (after `pip install -e ".[mcp]"`). Claude can then answer "cheapest football turfs near Velachery?" by calling the site's endpoints itself. |
601
+ | **Check a site for personal-data leaks** | Every run looks for emails, phone numbers, PAN/GST/Aadhaar numbers, UPI IDs and bank details in the replies. They are **hidden** before anything is stored, and the report, the terminal and the dashboard say which endpoints hand them out, and whether a login is needed. Use it on your own site, or one you're allowed to test. |
602
+ | **Write code with it** | Use the generated, tested Python or TypeScript client (see [Output](#output)), or the examples in [`examples/`](examples). |
603
+
604
+ All of these only **read** data: they go through the same rules as the explorer (robots.txt,
605
+ one request per second, nothing that looks like it changes data unless you say
606
+ `generate --allow "POST /path"` or press "I checked: it only reads" in the dashboard), and
607
+ they keep personal data hidden unless you pass `--include-sensitive` for your own site.
608
+
609
+ ---
610
+
611
+ ## Three ways to see the results
612
+
613
+ **1. The terminal.** Type `ghost-api` and a session opens, like Claude Code or Gemini CLI.
614
+ Paste a website and what you want ("quotes.toscrape.com/scroll list quotes with their
615
+ authors"), watch each phase and agent step as it happens, and get a summary card with
616
+ **clickable links** to the report and the files (Ctrl+click in Windows Terminal or VS Code;
617
+ the full `file:///` link is shown either way). Then keep going: `/fetch` the data, `/watch
618
+ add` alerts, `/open` the report, or paste another site. The session stays open until you
619
+ type `/exit` or press **Ctrl+C twice**; a single Ctrl+C only stops what's running. `/help`
620
+ lists the commands, Tab completes them, and the arrow keys bring back earlier lines.
621
+ `ghost-api run ...` still does a single run, and `--plain` gives simple lines for logs.
622
+
623
+ ![The terminal UI after a real run on quotes.toscrape.com](https://raw.githubusercontent.com/Danielraj5605/Ghost-api/main/docs/images/terminal-run.svg)
624
+
625
+ **2. The web dashboard.** `ghost-api ui` opens it in your browser (local only, at
626
+ `http://127.0.0.1:8800`). Start a run, watch the agent's browser and reasoning live, then
627
+ browse everything it found. Every past run can be replayed.
628
+
629
+ | Start a run | Watch it live |
630
+ |---|---|
631
+ | ![Home page with the URL and goal form](https://raw.githubusercontent.com/Danielraj5605/Ghost-api/main/docs/images/dashboard-home.png) | ![Live run with the browser view, activity and endpoints](https://raw.githubusercontent.com/Danielraj5605/Ghost-api/main/docs/images/dashboard-run.png) |
632
+ | **Endpoints and their fields** | **"Try it": call an endpoint with your own parameters** |
633
+ | ![Site page with the endpoint list and details](https://raw.githubusercontent.com/Danielraj5605/Ghost-api/main/docs/images/dashboard-site.png) | ![Try it showing a live response](https://raw.githubusercontent.com/Danielraj5605/Ghost-api/main/docs/images/dashboard-try-it.png) |
634
+ | **The generated client** | **On a phone** |
635
+ | ![Client code tab with Python and TypeScript](https://raw.githubusercontent.com/Danielraj5605/Ghost-api/main/docs/images/dashboard-code.png) | <img src="https://raw.githubusercontent.com/Danielraj5605/Ghost-api/main/docs/images/dashboard-phone.png" alt="The site page on a phone" width="260"> |
636
+
637
+ "Try it" only calls endpoints that read data. Anything that looks like it changes data
638
+ (add to cart, delete, update...) is refused.
639
+
640
+ **3. The shareable report.** Every run writes `output/<site>/report.html`: one file with
641
+ the endpoints, the run's activity, the generated code, the OpenAPI spec and the summary. It
642
+ works offline and needs no server, so you can email it or attach it to an issue. Example
643
+ values of sensitive parameters (keys, tokens) are left out.
644
+
645
+ ![report.html opened from disk](https://raw.githubusercontent.com/Danielraj5605/Ghost-api/main/docs/images/report-html.png)
646
+
647
+ ---
648
+
649
+ ## Usage
650
+
651
+ ```bash
652
+ # Basic run
653
+ ghost-api run https://quotes.toscrape.com/scroll --goal "list quotes with their authors and tags"
654
+
655
+ # Watch the browser while it works
656
+ ghost-api run https://quotes.toscrape.com/scroll --goal "list quotes" --headed
657
+
658
+ # Tighter budget
659
+ ghost-api run https://example.com --goal "search products" --max-llm-calls 30 --max-steps 25
660
+
661
+ # Re-check a known site from memory: replay first, browse only if needed
662
+ ghost-api refresh quotes.toscrape.com
663
+ ghost-api refresh quotes.toscrape.com --no-llm # never call the LLM
664
+
665
+ # Show what's stored for a site, or replay one run step by step
666
+ ghost-api inspect quotes.toscrape.com
667
+ ghost-api inspect --run 12
668
+
669
+ # Write report.md, report.html and openapi.yaml from stored data
670
+ ghost-api report quotes.toscrape.com
671
+
672
+ # Regenerate the client from stored data (no browser; --no-llm skips the naming call)
673
+ ghost-api generate quotes.toscrape.com
674
+ ghost-api generate quotes.toscrape.com --lang typescript # TypeScript instead
675
+ # Include a POST endpoint you checked only reads data (Ghost API skips those by itself)
676
+ ghost-api generate turftown.in/chennai --allow "POST /api/v2/venue/web/venues_around_you"
677
+
678
+ # Re-run the generated client's tests against the live API (and repair if needed)
679
+ ghost-api verify quotes.toscrape.com
680
+
681
+ # Check that your LLM keys work
682
+ ghost-api doctor
683
+
684
+ # Delete everything stored about a site (asks first; see SECURITY.md)
685
+ ghost-api forget quotes.toscrape.com
686
+
687
+ # Download data (no code): list what's available, then fetch all pages to a file
688
+ ghost-api fetch quotes.toscrape.com
689
+ ghost-api fetch quotes.toscrape.com list_quotes --pages 10 --format xlsx
690
+
691
+ # Watch for changes and get alerts
692
+ ghost-api watch add quotes.toscrape.com list_quotes --every 6h --notify ntfy:my-long-private-topic
693
+ ghost-api watch list
694
+ ghost-api watch run # check now
695
+ ghost-api watch schedule # keep checking in the background (Windows Task Scheduler)
696
+
697
+ # Use sites from other tools
698
+ ghost-api serve quotes.toscrape.com # REST API on http://127.0.0.1:8900 (docs at /docs)
699
+ ghost-api mcp quotes.toscrape.com # MCP server for AI assistants (stdio)
700
+
701
+ # Open the web dashboard (local only)
702
+ ghost-api ui
703
+ ghost-api ui --port 8900 --no-open
704
+
705
+ # The interactive session (stays open until /exit or Ctrl+C twice)
706
+ ghost-api
707
+
708
+ # Simple line-by-line output for logs and CI
709
+ ghost-api --plain run https://quotes.toscrape.com/scroll --goal "list quotes"
710
+
711
+ # Capture without the AI agent (just load and scroll)
712
+ ghost-api run https://quotes.toscrape.com/scroll --goal "list quotes" --no-agent
713
+ ```
714
+
715
+ Inside the session:
716
+
717
+ | Type | It does |
718
+ |---|---|
719
+ | `quotes.toscrape.com/scroll list quotes with their authors` | Explores the site for that goal |
720
+ | more words after a run | A new goal for the same site |
721
+ | `/fetch [SITE] [OPERATION] [name=value ...]` | Downloads data (the current site if none is given) |
722
+ | `/watch add ...`, `/watch` | Alerts, and your list of watches |
723
+ | `/open`, `/report` | Opens or rewrites the current site's report |
724
+ | `/sites`, `/inspect`, `/refresh`, `/generate`, `/serve`, `/ui`, `/doctor` | Same as the `ghost-api` commands |
725
+ | `/lang typescript`, `/agent off` | Settings for the next runs |
726
+ | `/help`, `/clear`, `/exit` | Help, clear the screen, end (or Ctrl+C twice) |
727
+
728
+ | Option | Default | Meaning |
729
+ |---|---|---|
730
+ | `--goal` | required | What you want the client to do |
731
+ | `--lang` | `python` | Client language: `python` or `typescript` |
732
+ | `--headed` | off | Show the browser window |
733
+ | `--max-steps` | 40 | Maximum agent actions |
734
+ | `--max-llm-calls` | 50 | Maximum LLM calls |
735
+ | `--max-minutes` | 15 | Time limit |
736
+ | `--rate-limit` | 1.0 | Maximum requests per second to the target site |
737
+ | `--ignore-robots` | off | Skip the robots.txt check (use responsibly) |
738
+ | `--no-agent` | off | Don't use the LLM; just load and scroll the page |
739
+ | `--no-probe` | off | Don't test endpoints with direct calls |
740
+ | `--max-probes` | 60 | Maximum direct calls for testing endpoints |
741
+ | `--no-generate` | off | Don't generate a Python client |
742
+ | `--no-verify` | off | Don't run the generated tests against the live API |
743
+ | `--out` | `output` | Folder for generated clients |
744
+
745
+ **Example projects** built on generated clients, each with a plain-language README:
746
+ [`examples/quote_book`](examples/quote_book) (a searchable book of quotes) and
747
+ [`examples/turf_finder`](examples/turf_finder) (find sports venues near you in Chennai).
748
+
749
+ ---
750
+
751
+ ## Output
752
+
753
+ Results are kept per **site**: the domain, plus the first part of the start URL's path when
754
+ it names a section of the site. `https://hn.algolia.com/` is `hn.algolia.com`, while
755
+ `https://www.google.com/maps` is `www.google.com/maps`, so Maps and Search on the same domain
756
+ keep separate memory and files. IDs, file names (`index.html`) and language codes (`/en/`)
757
+ don't count as sections. Commands accept the site, a URL, or just the domain when it has only
758
+ one site (`ghost-api refresh quotes.toscrape.com`); with several, Ghost API lists them.
759
+
760
+ ```
761
+ output/quotes.toscrape.com/scroll/
762
+ |-- quotes_toscrape_client.py # The client: sync and async classes, typed errors
763
+ |-- models.py # Pydantic data models
764
+ |-- test_quotes_toscrape_client.py # Live tests (run automatically by `run` and `verify`)
765
+ |-- package/python/ # The same client as an installable, typed package
766
+ |-- package/typescript/ # (with --lang typescript) package.json, tsconfig, src/
767
+ |-- openapi.yaml # OpenAPI 3.1 spec (imports into Postman, Bruno, Insomnia)
768
+ |-- report.md # Endpoints, confidence, evidence, reasoning log
769
+ +-- report.html # The same and more, as one shareable offline page
770
+ ```
771
+
772
+ **Tests that run anywhere.** The generated tests replay recorded replies (`cassettes.json`)
773
+ by default, so they pass offline and in CI; `GHOST_API_LIVE=1` runs them against the real API.
774
+
775
+ **Use the client the way you prefer.** Copy the loose files into a project, or install the
776
+ package (`pip install output/quotes.toscrape.com/scroll/package/python`, or
777
+ `npm install .../package/typescript`); the import is the same either way. Each package has a
778
+ README with every method, a `curl` command per endpoint, and the error types. Generated code
779
+ passes `mypy --strict` and `tsc --strict`.
780
+
781
+ ```python
782
+ from quotes_toscrape_client import AsyncQuotesToscrapeClient, QuotesToscrapeNotFoundError
783
+
784
+ async with AsyncQuotesToscrapeClient() as api: # same methods as the sync client
785
+ async for page in api.iter_get_quotes(max_pages=3):
786
+ ...
787
+ ```
788
+
789
+ Errors are typed: catch `QuotesToscrapeError` for any failure, or `...AuthError` (401/403),
790
+ `...NotFoundError` (404), `...RateLimitError` (429) or `...ServerError` (5xx) for one kind.
791
+
792
+ **Know when the site changes.** Every client has a version. When a refresh finds the API
793
+ changed, the client is regenerated and its version goes up: major when code using it could
794
+ break (a field removed, a type changed), minor when something was added. Each package has a
795
+ `CHANGELOG.md`.
796
+
797
+ ```bash
798
+ ghost-api diff quotes.toscrape.com/scroll --live # call each endpoint once and compare
799
+ ghost-api diff quotes.toscrape.com/scroll --history # every client version
800
+ ```
801
+
802
+ To get a pull request when that happens, use the GitHub Action: see
803
+ [docs/github-action.md](docs/github-action.md).
804
+
805
+ **Sites Ghost API can't browse by itself** (a login, a bot check, a long flow): browse them
806
+ yourself with DevTools open, use **Network > Save all as HAR**, then
807
+
808
+ ```bash
809
+ ghost-api import session.har --goal "my orders" # add --offline to send nothing to the site
810
+ ```
811
+
812
+ Cookies, tokens and personal data are removed before anything is stored. Delete the `.har`
813
+ file afterwards: it holds them in plain text.
814
+
815
+ **Sites that need a login.** Log in once yourself; Ghost API keeps the session (encrypted
816
+ on Windows) and later runs, refreshes and fetches use it:
817
+
818
+ ```bash
819
+ ghost-api login https://example.com/account # a browser opens: log in, press Enter
820
+ ghost-api run https://example.com/account --goal "my orders"
821
+ ghost-api logout example.com # delete it
822
+ ```
823
+
824
+ **Work offline.** `ghost-api mock quotes.toscrape.com/scroll` serves the captured replies at
825
+ the same paths on `http://127.0.0.1:8901`: point a front end or a test at it, with no traffic
826
+ to the real site.
827
+
828
+ Excerpt from a real `report.md`:
829
+
830
+ ```
831
+ ## Endpoints
832
+
833
+ | Endpoint | Status | Confidence | Samples | Auth | Paging | Notes |
834
+ |-------------------|----------|------------|---------|------|--------------|-----------------------|
835
+ | `GET /api/quotes` | verified | High | 6 | none | page: `page` | 2 live test(s) passed |
836
+
837
+ ## Hypotheses tested
838
+
839
+ - confirmed: can be called directly, outside the browser (HTTP 200, 10 records)
840
+ - confirmed: paginates with page parameter 'page' (the next page returned 10 different records)
841
+ - confirmed: needs none of its custom headers or cookies
842
+ ```
843
+
844
+ ---
845
+
846
+ ## Project structure
847
+
848
+ ```
849
+ ghost-api/
850
+ |-- ghost_api/
851
+ | |-- cli/ # The command line: explore, client, data and manage commands
852
+ | |-- api.py # The same features as Python functions
853
+ | |-- workflows.py # Run and refresh, shared by the CLI and the API
854
+ | |-- drift.py # Client versions and API changes
855
+ | |-- pipeline.py # A run from start to finish, as events (no printing)
856
+ | |-- events.py # The events every interface shows (stored for replay)
857
+ | |-- ui/terminal.py # Live terminal UI (Rich)
858
+ | |-- web/ # Dashboard server (FastAPI) and the prebuilt frontend
859
+ | |-- config.py # Settings and budgets
860
+ | |-- errors.py # Plain-language error messages
861
+ | |-- net.py # robots.txt and rate limiting
862
+ | |-- netguard.py # Network boundary: no private addresses, reply size limits
863
+ | |-- agent/ # Exploration loop, prompts, actions
864
+ | |-- browser/ # Playwright control, snapshots, safety filter, egress proxy, request gate
865
+ | |-- capture/ # Network recording, noise filters, redaction
866
+ | |-- analysis/ # URL templates, schemas, detectors (pagination, auth, bots)
867
+ | |-- probe/ # Safe replays and hypothesis testing
868
+ | |-- generator/ # Naming, models, client spec, Jinja2 templates
869
+ | |-- verify/ # Run generated tests, classify failures, repair
870
+ | |-- export/ # report.md, report.html, OpenAPI 3.1, CSV/Excel, confidence
871
+ | |-- runtime.py # Call a site's endpoints by name, follow pages, get rows
872
+ | |-- watch.py # Watches: compare snapshots, notifications
873
+ | |-- serve/ # REST API and MCP server
874
+ | |-- llm/ # Provider gateway: fallback, cooldown, validation, cache
875
+ | +-- memory/ # SQLite store, lessons, memory for refresh
876
+ |-- tests/ # 400+ tests (no LLM quota needed)
877
+ |-- evals/ # Demo shop, evaluation sites, scoring script
878
+ |-- web/ # Dashboard and report frontend source (React + Vite + Tailwind)
879
+ |-- docs/images/ # README screenshots
880
+ |-- .env.example
881
+ |-- pyproject.toml
882
+ |-- README.md
883
+ +-- IMPLEMENTATION.md
884
+ ```
885
+
886
+ ---
887
+
888
+ ## Running on free LLM tiers
889
+
890
+ Ghost API is designed to cost **$0**:
891
+
892
+ - **Small prompts.** Pages and traffic are compressed into short summaries (under 8k tokens per call).
893
+ - **Few calls.** 1–10 LLM calls per site in the [evaluation](#evaluation), because code does the heavy work and the agent stops as soon as the goal is covered. `refresh` often needs none.
894
+ - **Fallback chain.** Each provider has several candidate models. A rate-limited or overloaded model rests while another is used; a retired model is skipped; a bad key disables its provider. Order: Gemini, Groq, Gemini Lite, OpenRouter. Run `ghost-api doctor` to check your keys.
895
+ - **Caching.** Repeated prompts are served from a local SQLite cache.
896
+
897
+ > Free-tier limits change often, so check each provider's current pricing page. Free tiers may use prompts for training. Ghost API redacts cookies and tokens before anything reaches an LLM, but don't point it at private data.
898
+
899
+ ---
900
+
901
+ ## Responsible use
902
+
903
+ Ghost API is built to behave like a polite, careful developer:
904
+
905
+ - Respects `robots.txt` by default, for its own requests and for the pages and API calls the
906
+ browser makes for the site (checked before they are sent)
907
+ - Rate-limits its own requests and the site's API calls in the browser (1 request per second
908
+ by default)
909
+ - Never lets a website steer it to your computer or local network: every browser connection,
910
+ redirects included, goes through a local proxy that refuses private addresses (unless the
911
+ site you gave it is itself local)
912
+ - Stops reading any reply bigger than 5 MB
913
+ - Never tries to bypass CAPTCHAs or bot protection; it detects them and stops
914
+ - Never clicks destructive or purchase actions (delete, buy, pay)
915
+ - Never stores passwords; for logins, you log in by hand in the browser window
916
+ - Redacts cookies and tokens from logs, reports and LLM prompts
917
+ - Uses an honest user-agent string
918
+
919
+ Check a site's terms of service before generating a client for it. For demos and testing, use practice sites such as `quotes.toscrape.com/scroll`, which loads its data from a JSON API. (`books.toscrape.com` renders on the server, so it has no API to discover.)
920
+
921
+ Also:
922
+
923
+ - **Personal data is hidden, always.** Emails, phone numbers, PAN/GST/Aadhaar numbers, UPI IDs
924
+ and bank details in a site's replies are replaced before Ghost API stores anything, and stay
925
+ hidden in fetches, watches, the REST API and MCP tools. `--include-sensitive` turns that off,
926
+ for auditing your own site only. If you find a site exposing such data, tell the owner
927
+ privately instead of collecting it.
928
+ - **Watches and servers keep the same manners:** read-only, one request per second per site,
929
+ robots.txt respected, checks no more often than every 5 minutes.
930
+ - **Your data stays on your machine** except what the AI agent sends to your LLM provider.
931
+ [SECURITY.md](SECURITY.md) lists what is stored, what is sent where, and how to delete it:
932
+ `ghost-api forget SITE` removes everything stored about a site.
933
+
934
+ ---
935
+
936
+ ## Evaluation
937
+
938
+ Four sites, run end to end with free LLM tiers on a Windows laptop with 8 GB of RAM
939
+ (full table and method: [evals/results/results.md](evals/results/results.md)).
940
+
941
+ | Site | What it tests | Recall | Precision | Tests passing | LLM calls | Time | Peak memory |
942
+ |---|---|---|---|---|---|---|---|
943
+ | demo-shop (local) | Known answer: 5 endpoints, page + cursor paging, traps | 100% | 100% | 100% | 6 | 36 s | 415 MB |
944
+ | quotes.toscrape.com | Infinite-scroll JSON API | 100% | 100% | 100% | 2 | 18 s | 423 MB |
945
+ | hn.algolia.com | Third-party POST API with a key | 100% | 100% | 100% | 7 | 123 s* | 494 MB |
946
+ | books.toscrape.com | No API at all (must not invent one) | n/a | 100% | n/a | 1 | 9 s | 452 MB |
947
+
948
+ - **Recall:** share of the goal's real endpoints found. **Precision:** share of reported
949
+ endpoints that are real (nothing was invented). **Tests passing:** generated client tests
950
+ that passed against the live API. Peak memory includes Chromium.
951
+ - The demo shop's "Add to cart" (a POST that changes data) was never called and never generated.
952
+ - \*Re-measured after the gateway learned to switch away from overloaded models at once;
953
+ the earlier run took 259 s and 10 calls, mostly retrying an overloaded Gemini model.
954
+ - The first attempt failed on two sites because two providers' free models had been
955
+ retired; that led to the per-model fallback routing described above.
956
+
957
+ ### The quality gate (every change, no LLM)
958
+
959
+ Ten local test sites with known answers ([evals/fixtures.py](evals/fixtures.py)), each built
960
+ around one hard case, are scored on every change in CI. A score below
961
+ [evals/thresholds.json](evals/thresholds.json) fails the build:
962
+
963
+ | Test site | Must hold |
964
+ |---|---|
965
+ | offset-paging, cursor-paging | The endpoint is found and its paging style is detected |
966
+ | api-key-header | The `x-api-key` header is found to be required |
967
+ | robots-disallow | The API path robots.txt disallows is **never requested**, by the browser or by replays |
968
+ | huge-reply | A 9 MB reply is dropped as `response-too-large`; the small endpoint is still found |
969
+ | xssi-guard | A reply behind Google-style `)]}'` is still read |
970
+ | no-api, bot-wall | Nothing is invented; a bot-protection page stops the run (`bot-protection`) |
971
+ | personal-data | The email and phone in the replies are **never stored** |
972
+ | prompt-injection | (agent runs) The planted "delete all data" instruction is never followed |
973
+ | demo-shop | All endpoints; the cart (a POST that changes data) is never called or generated |
974
+
975
+ The first run of this gate found a real bug: cursor paging went undetected when the newest
976
+ captured page was the last one. It is fixed, and the gate keeps it fixed.
977
+
978
+ Run it yourself: `python -m evals.run_eval --suite local --no-agent --check`. The full set,
979
+ with the agent and the live sites above, is `python -m evals.run_eval` (uses LLM calls); it
980
+ also runs weekly in CI, dated, without blocking builds.
981
+
982
+ ---
983
+
984
+ ## Limitations
985
+
986
+ The full, prioritised list of known problems is in [KNOWN_ISSUES.md](KNOWN_ISSUES.md). The main ones:
987
+
988
+ - Sites with strong bot protection (Cloudflare challenges, CAPTCHAs) will be skipped; you can
989
+ browse them yourself and `ghost-api import` the HAR file
990
+ - Sites whose `robots.txt` forbids their API paths for bots are respected, so Ghost API will not call those endpoints directly. Example: Wikipedia disallows `/w/` (where its search suggestions live) and refuses clients that don't identify themselves, so the run stops at the rules check. Wikipedia offers an official API for that purpose. If a site just wants to know who you are, set `GHOST_API_CONTACT` in `.env` (your own email or a URL; it is never filled in for you)
991
+ - Server-rendered sites with no JSON API have nothing to discover
992
+ - WebSocket, server-sent-event and gRPC APIs are not mapped yet; GraphQL is (one method per
993
+ operation), except batches of several operations in one request
994
+ - Small free models sometimes make poor exploration choices; budgets and stopping rules limit the damage
995
+ - Generated clients can break when the site changes its internal API: `ghost-api diff --live`
996
+ and the GitHub Action notice it, and `refresh` makes a new client version
997
+
998
+ ---
999
+
1000
+ ## Roadmap
1001
+
1002
+ | Stage | Focus | Status |
1003
+ |---|---|---|
1004
+ | 0 | Project setup | Done |
1005
+ | 1 | Traffic capture | Done |
1006
+ | 2 | API analysis (URL grouping, schemas) | Done |
1007
+ | 3 | LLM gateway (fallback + cache) | Done |
1008
+ | 4 | Agent exploration loop | Done |
1009
+ | 5 | Hypothesis testing | Done |
1010
+ | 6 | Code generation | Done |
1011
+ | 7 | Verification and replanning | Done |
1012
+ | 8 | Memory | Done |
1013
+ | 9 | Reporting and OpenAPI export | Done |
1014
+ | 10 | Evaluation and polish | Done |
1015
+ | UI 0–6 | Event pipeline, live terminal, web dashboard, report.html, docs | Done |
1016
+ | Use 1–6 | Data download, watch & alerts, REST + MCP servers, personal-data leak check | Done |
1017
+
1018
+ Full details are in [IMPLEMENTATION.md](IMPLEMENTATION.md). To work on the dashboard itself,
1019
+ see [web/README.md](web/README.md).