website-api 1.0.1 → 1.0.3

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.
package/README.md CHANGED
@@ -1,241 +1,3 @@
1
1
  # website-api
2
2
 
3
- A TypeScript CLI and library published as `website-api` that uses [chrome-tools](https://github.com/user/chrome-tools) to extract decrypted Chrome cookies on macOS, then queries private/internal website APIs — no manual cookie copy-pasting needed.
4
-
5
- ---
6
-
7
- ## Features
8
-
9
- - 🔑 **Keychain Decryption** — Leverages `chrome-tools` to decrypt Chrome cookies via the macOS Keychain.
10
- - 📂 **Auto-Loading Adapters** — Drop a folder into `src/website/`, and the adapter is auto-discovered at runtime.
11
- - 🧩 **Zero-Boilerplate Adapters** — The `defineAdapter()` factory handles cookies, user-agent, fetch, and error handling. Simple adapters are ~10 lines.
12
- - 👥 **Multi-Profile Support** — Target specific Chrome profiles (e.g., `Default`, `Profile 1`) or custom user data paths.
13
- - 🚀 **Dual Interface** — Works as both an importable ES module library and a terminal CLI.
14
-
15
- ---
16
-
17
- ## Supported Website APIs
18
-
19
- | ID | Name | Domain | Description |
20
- |----|------|--------|-------------|
21
- | `codex-usage` | ChatGPT / Codex Usage | chatgpt.com | Fetches ChatGPT rate limit usage and quota details from the private wham/usage API. |
22
- | `cursor-usage` | Cursor Usage | cursor.com | Fetches the active Cursor usage summary from the private usage-summary API. |
23
- | `ollama-usage` | Ollama Usage | ollama.com | Fetches Ollama plan and usage details from the authenticated settings page. |
24
-
25
- Each adapter has a `request.md` file documenting the exact HTTP requests and expected responses.
26
-
27
- ---
28
-
29
- ## Installation & Setup
30
-
31
- ### Prerequisites
32
-
33
- - macOS with Google Chrome installed
34
- - Node.js ≥ 18
35
- - [pnpm](https://pnpm.io/) package manager
36
-
37
- ### Install
38
-
39
- ```bash
40
- pnpm install
41
- pnpm run build
42
- ```
43
-
44
- ### Link CLI globally (optional)
45
-
46
- ```bash
47
- pnpm link --global
48
- website-api --help
49
- ```
50
-
51
- ---
52
-
53
- ## CLI Usage
54
-
55
- ```bash
56
- # Show help
57
- node dist/bin/cli.js --help
58
-
59
- # List all supported adapters
60
- node dist/bin/cli.js list
61
-
62
- # Query a website API
63
- node dist/bin/cli.js codex-usage
64
- node dist/bin/cli.js cursor-usage
65
- node dist/bin/cli.js ollama-usage
66
-
67
- # Domain aliases still work too
68
- node dist/bin/cli.js chatgpt.com
69
- node dist/bin/cli.js cursor.com
70
- node dist/bin/cli.js ollama.com
71
- ```
72
-
73
- ### Options
74
-
75
- | Option | Description |
76
- |--------|-------------|
77
- | `--profile <name>` | Target a specific Chrome profile (e.g., `Default`, `Profile 1`) |
78
- | `--current-profile` | Show the currently resolved Chrome profile directory and name |
79
- | `-u, --user-agent <string>` | Custom User-Agent header for HTTP requests |
80
-
81
- ### Universal Fallback
82
-
83
- Any URL or domain not matching a registered adapter will be fetched directly with Chrome cookies:
84
-
85
- ```bash
86
- node dist/bin/cli.js https://example.com/api/data
87
- ```
88
-
89
- ---
90
-
91
- ## Library Usage (Programmatic API)
92
-
93
- ```typescript
94
- import { queryWebsite } from "website-api";
95
-
96
- const data = await queryWebsite("chatgpt.com", {
97
- profile: "Default",
98
- });
99
-
100
- // Command IDs also work:
101
- // await queryWebsite("codex-usage", { profile: "Default" });
102
-
103
- console.log(JSON.stringify(data, null, 2));
104
- ```
105
-
106
- ---
107
-
108
- ## How to Add a New Website Adapter
109
-
110
- ### Step 1: Create the adapter folder
111
-
112
- ```
113
- src/website/github.com/
114
- ├── github-adapter.ts ← adapter code
115
- └── request.md ← document the endpoint (required)
116
- ```
117
-
118
- ### Step 2: Write the adapter
119
-
120
- For **simple single-endpoint** APIs — just declare the endpoint:
121
-
122
- ```typescript
123
- // src/website/github.com/github-adapter.ts
124
- import { defineAdapter } from "../../base-adapter.js";
125
-
126
- export default defineAdapter({
127
- id: "github.com",
128
- name: "GitHub Notifications",
129
- domain: "github.com",
130
- description: "Fetches unread notifications from the GitHub API.",
131
- endpoints: [{ url: "https://api.github.com/notifications" }],
132
- });
133
- ```
134
-
135
- For **multi-step flows** (e.g., session token → API call) — override `fetchData`:
136
-
137
- ```typescript
138
- // src/website/example.com/example-adapter.ts
139
- import { defineAdapter } from "../../base-adapter.js";
140
-
141
- export default defineAdapter({
142
- id: "example.com",
143
- name: "Example Service",
144
- domain: "example.com",
145
- description: "Fetches data with a two-step auth flow.",
146
-
147
- async fetchData(cookies, options) {
148
- const cookieStr = this.buildCookieString(cookies); // shared helper
149
- const ua = this.resolveUserAgent(options); // shared helper
150
-
151
- // Step 1: get token
152
- const session = await this.fetchJson("https://example.com/auth", {
153
- headers: { Cookie: cookieStr, "User-Agent": ua },
154
- });
155
-
156
- // Step 2: use token
157
- return this.fetchJson("https://example.com/api/data", {
158
- headers: { Authorization: `Bearer ${session.token}` },
159
- });
160
- },
161
- });
162
- ```
163
-
164
- ### Step 3: Document the endpoint
165
-
166
- Create `request.md` in the adapter folder documenting the HTTP requests, headers, and example responses. See [chatgpt.com/request.md](src/website/chatgpt.com/request.md) for reference.
167
-
168
- ### Step 4: Build and test
169
-
170
- ```bash
171
- pnpm run build
172
- node dist/bin/cli.js list # verify it appears
173
- node dist/bin/cli.js github.com # test it
174
- ```
175
-
176
- That's it — **no registration needed**. The adapter is auto-discovered from the `website/` directory at runtime.
177
-
178
- ---
179
-
180
- ## Shared Adapter Helpers
181
-
182
- Every adapter created with `defineAdapter()` automatically gets these methods via `this`:
183
-
184
- | Method | Description |
185
- |--------|-------------|
186
- | `this.buildCookieString(cookies)` | Converts cookies array to `"name=value; ..."` header string |
187
- | `this.resolveUserAgent(options)` | Resolves User-Agent from options → `.env` → default |
188
- | `this.fetchJson(url, init?)` | Fetch with error handling + automatic JSON parsing |
189
-
190
- ---
191
-
192
- ## Configuration
193
-
194
- Create a `.env` file in the project root:
195
-
196
- ```env
197
- # Custom User-Agent (defaults to Chrome 148 on macOS)
198
- userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
199
-
200
- # Custom Chrome profile path (defaults to standard macOS location)
201
- PROFILE_PATH="/Users/yourname/chrome_profile/chrome"
202
- PROFILE_NAME="Default"
203
- ```
204
-
205
- ---
206
-
207
- ## Project Structure
208
-
209
- ```
210
- ├── src/
211
- │ ├── website-api.ts # Main entry — auto-loads adapters, queryWebsite()
212
- │ ├── base-adapter.ts # defineAdapter() factory + shared helpers
213
- │ ├── types.ts # TypeScript interfaces
214
- │ ├── env.ts # .env file loader
215
- │ ├── universal-adapter.ts # Fallback for unregistered domains
216
- │ └── website/
217
- │ ├── chatgpt.com/
218
- │ │ ├── chatgpt-adapter.ts
219
- │ │ └── request.md
220
- │ ├── cursor.com/
221
- │ ├── cursor-adapter.ts
222
- │ └── request.md
223
- │ └── ollama.com/
224
- │ ├── ollama-adapter.ts
225
- │ └── request.md
226
- ├── bin/
227
- │ └── cli.ts # CLI entry point
228
- ├── tsconfig.json
229
- ├── package.json
230
- └── .env # Local config (gitignored)
231
- ```
232
-
233
- ---
234
-
235
- ## Security Warning
236
-
237
- This utility is designed strictly for local development and authorized administrative use. macOS will request explicit Keychain authorization before allowing `chrome-tools` to access the Chrome Safe Storage Password. Only run this tool in trusted environments.
238
-
239
- ## License
240
-
241
- MIT License
3
+ CLI and library to fetch website API data
@@ -1 +1 @@
1
- const e={buildCookieString:e=>e.map(e=>`${e.name}=${e.value}`).join("; "),resolveUserAgent:e=>e.userAgent||process.env.userAgent||process.env.USER_AGENT||"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",async fetchJson(e,t){const n=await fetch(e,t);if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);const s=await n.text();try{return JSON.parse(s)}catch{return{response:s}}}};async function t(e,t){if(!this.endpoints||0===this.endpoints.length)throw new Error(`Adapter "${this.id}" has no endpoints defined and no fetchData override`);const n=this.endpoints[0],s=this.buildCookieString(e),o=this.resolveUserAgent(t);return this.fetchJson(n.url,{method:n.method||"GET",headers:{Cookie:s,"User-Agent":o,Accept:"application/json, text/plain, */*",...n.headers}})}export function defineAdapter(n){const s={...n,...e,fetchData:n.fetchData??t};return s.fetchData=s.fetchData.bind(s),s}
1
+ const t={buildCookieString:t=>t.map(t=>`${t.name}=${t.value}`).join("; "),resolveUserAgent:t=>t.userAgent||process.env.userAgent||process.env.USER_AGENT||"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",async fetchJson(t,e){const n=await fetch(t,e);if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);const r=await n.text();try{return JSON.parse(r)}catch{return{response:r}}},async fetchText(t,e){const n=await fetch(t,e);if(!n.ok)throw new Error(`HTTP ${n.status}: ${n.statusText}`);return n.text()},async fetchHtml(t,e){return this.fetchText(t,e)}};async function e(t,e){if(!this.endpoints||0===this.endpoints.length)throw new Error(`Adapter "${this.id}" has no endpoints defined and no fetchData override`);const n=this.endpoints[0],r=this.buildCookieString(t),s=this.resolveUserAgent(e),o=await async function(t,e,n){const r=await fetch(t.url,{method:t.method||"GET",headers:{Cookie:e,"User-Agent":n,Accept:"html"===t.responseType?"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8":"text"===t.responseType?"text/plain,*/*;q=0.8":"application/json, text/plain, */*",...t.headers}});if(!r.ok)throw new Error(`HTTP ${r.status}: ${r.statusText}`);const s=await r.text(),o=r.headers.get("content-type")?.toLowerCase()??"";if("json"===t.responseType||"text"!==t.responseType&&"html"!==t.responseType&&(o.includes("application/json")||o.includes("+json")))try{return JSON.parse(s)}catch(e){throw new Error(`Expected JSON from ${t.url}, but received invalid JSON: ${e instanceof Error?e.message:String(e)}`)}return s}(n,r,s);return n.transform?n.transform.call(this,o,t,e):o}export function defineAdapter(n){const r={...n,...t,fetchData:n.fetchData??e};return r.fetchData=r.fetchData.bind(r),r}
@@ -9,6 +9,13 @@ export interface Endpoint {
9
9
  method?: string;
10
10
  /** Additional headers to include in the request. */
11
11
  headers?: Record<string, string>;
12
+ /**
13
+ * Expected response body type. Defaults to auto-detect from content-type.
14
+ * Use "html" for text/html responses that need parsing.
15
+ */
16
+ responseType?: "auto" | "json" | "text" | "html";
17
+ /** Optional post-processing step for the parsed response body. */
18
+ transform?: (this: WebsiteAdapter, body: unknown, cookies: CookieEntry[], options: QueryOptions) => Promise<unknown> | unknown;
12
19
  }
13
20
  /**
14
21
  * Configuration for defining a website adapter.
@@ -39,6 +46,10 @@ export interface WebsiteAdapter extends AdapterConfig {
39
46
  resolveUserAgent(options: QueryOptions): string;
40
47
  /** Fetches a URL and returns parsed JSON, with error handling. */
41
48
  fetchJson(url: string, init?: RequestInit): Promise<any>;
49
+ /** Fetches a URL and returns the raw response text, with error handling. */
50
+ fetchText(url: string, init?: RequestInit): Promise<string>;
51
+ /** Fetches an HTML document as text, with error handling. */
52
+ fetchHtml(url: string, init?: RequestInit): Promise<string>;
42
53
  }
43
54
  /**
44
55
  * Options passed when querying a website API.
@@ -1 +1 @@
1
- import{defineAdapter as e}from"../../base-adapter.js";export default e({id:"ollama-usage",name:"Ollama Usage",domain:"ollama.com",description:"Fetches Ollama plan and usage details from the authenticated settings page.",async fetchData(e,a){const n=this.buildCookieString(e),i=this.resolveUserAgent(a),o=await fetch("https://ollama.com/settings",{method:"GET",headers:{Cookie:n,"User-Agent":i,Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}});if(!o.ok)throw new Error(`HTTP ${o.status}: ${o.statusText}`);const r=await o.text(),l=s(r,"Session usage"),m=s(r,"Weekly usage");return{time:(new Date).toISOString(),Plan:t(r),"Session Usage":l.usage,"Session Reset":l.reset,"Weekly Usage":m.usage,"Weekly Reset":m.reset}}});function t(e){const t=e.match(/Cloud Usage[\s\S]*?<\/span>[\s\S]*?<span[^>]*>([\s\S]*?)<\/span/i);return t?.[1]?.trim()??"unknown"}function s(e,t){const s=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");const a=new RegExp(`<div[\\s\\S]*?<span[^>]*>\\s*${s}\\s*<\\/span>[\\s\\S]*?aria-label="${s}\\s+([^"]+)"[\\s\\S]*?data-time="([^"]+)"`,"i"),n=e.match(a);return{usage:n?.[1]?.replace(/\s+used$/i,"").trim()??"unknown",reset:n?.[2]?.trim()??"unknown"}}
1
+ import{defineAdapter as e}from"../../base-adapter.js";export default e({id:"ollama-usage",name:"Ollama Usage",domain:"ollama.com",description:"Fetches Ollama plan and usage details from the authenticated settings page.",endpoints:[{url:"https://ollama.com/settings",responseType:"html",headers:{Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},transform(e){const t="string"==typeof e?e:String(e),n=a(t,"Session usage"),i=a(t,"Weekly usage");return{time:(new Date).toISOString(),Plan:s(t),"Session Usage":n.usage,"Session Reset":n.reset,"Weekly Usage":i.usage,"Weekly Reset":i.reset}}}]});function s(e){const s=e.match(/Cloud Usage[\s\S]*?<\/span>[\s\S]*?<span[^>]*>([\s\S]*?)<\/span/i);return s?.[1]?.trim()??"unknown"}function a(e,s){const a=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");const t=new RegExp(`<div[\\s\\S]*?<span[^>]*>\\s*${a}\\s*<\\/span>[\\s\\S]*?aria-label="${a}\\s+([^"]+)"[\\s\\S]*?data-time="([^"]+)"`,"i"),n=e.match(t);return{usage:n?.[1]?.replace(/\s+used$/i,"").trim()??"unknown",reset:n?.[2]?.trim()??"unknown"}}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "website-api",
3
- "version": "1.0.1",
4
- "description": "CLI and library to fetch website API data using chrome-tools decrypted cookies on macOS",
3
+ "version": "1.0.3",
4
+ "description": "CLI and library to fetch website API data",
5
5
  "main": "./dist/src/website-api.js",
6
6
  "type": "module",
7
7
  "bin": {