website-api 1.0.1
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 +241 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +2 -0
- package/dist/src/base-adapter.d.ts +19 -0
- package/dist/src/base-adapter.js +1 -0
- package/dist/src/env.d.ts +5 -0
- package/dist/src/env.js +1 -0
- package/dist/src/types.d.ts +51 -0
- package/dist/src/types.js +1 -0
- package/dist/src/universal-adapter.d.ts +10 -0
- package/dist/src/universal-adapter.js +1 -0
- package/dist/src/website/chatgpt.com/chatgpt-adapter.d.ts +11 -0
- package/dist/src/website/chatgpt.com/chatgpt-adapter.js +1 -0
- package/dist/src/website/cursor.com/cursor-adapter.d.ts +6 -0
- package/dist/src/website/cursor.com/cursor-adapter.js +1 -0
- package/dist/src/website/ollama.com/ollama-adapter.d.ts +2 -0
- package/dist/src/website/ollama.com/ollama-adapter.js +1 -0
- package/dist/src/website-api.d.ts +34 -0
- package/dist/src/website-api.js +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# website-api
|
|
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
|
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{readFileSync as o}from"node:fs";import{dirname as e,join as r}from"node:path";import{fileURLToPath as n}from"node:url";import{program as t}from"commander";import s from"chalk";import i from"cli-table3";import{getDefaultChromeDir as l}from"chrome-tools";import{queryWebsite as c,websites as a,loadAdapters as p}from"../src/website-api.js";const d=r(e(n(import.meta.url)),"..","..","package.json"),{version:m}=JSON.parse(o(d,"utf8"));process.on("unhandledRejection",()=>{console.error("No login found in browser"),process.exit(1)}),t.name("website-api").description("CLI to query website APIs using decrypted Chrome cookies on macOS").version(m),t.option("--profile <name>","specific Chrome profile directory (e.g., 'Default', 'Profile 1')").option("--current-profile","Show the currently resolved/selected Chrome profile directory and name").option("-u, --user-agent <string>","custom User-Agent header for HTTP requests"),t.command("list").description("List all supported website API adapters").action(async()=>{await p(),console.log(s.bold.green("\n🌐 Supported Website APIs:\n"));const o=new i({head:[s.bold.cyan("ID"),s.bold.cyan("Name"),s.bold.cyan("Domain"),s.bold.cyan("Description")],colWidths:[18,25,20,50],wordWrap:!0,style:{head:[],border:[]}});for(const e of a)o.push([s.yellow(e.id),e.name,s.underline(e.domain),e.description]);console.log(o.toString()),console.log(`\nTo run an API query, execute: ${s.bold.cyan("npx website-api <id>")}\n`)}),t.argument("[website]","website ID or domain to query (e.g. 'chatgpt.com')").action(async o=>{const e=t.opts();if(e.currentProfile){const o=process.env.PROFILE_PATH||process.env.CHROME_PROFILE_PATH||l(),r=e.profile||process.env.PROFILE_NAME||"Default";return console.log(s.bold.green("\n👤 Currently Resolved Profile:\n")),console.log(` ${s.bold("Path:")} ${o}`),void console.log(` ${s.bold("Name:")} ${r}\n`)}if(o)try{const r=await c(o,{profile:e.profile,userAgent:e.userAgent});console.log(JSON.stringify(r,null,2))}catch(o){console.error("No login found in browser"),process.exit(1)}else t.outputHelp()}),t.parse(process.argv);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AdapterConfig, WebsiteAdapter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Factory function to create a fully-equipped WebsiteAdapter from a minimal config.
|
|
4
|
+
*
|
|
5
|
+
* Simple adapters only need `id`, `name`, `domain`, `description`, and `endpoints`.
|
|
6
|
+
* Complex adapters can provide a custom `fetchData` that uses `this.buildCookieString()`,
|
|
7
|
+
* `this.resolveUserAgent()`, and `this.fetchJson()`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Simple adapter (~5 lines of config):
|
|
11
|
+
* export default defineAdapter({
|
|
12
|
+
* id: "cursor.com",
|
|
13
|
+
* name: "Cursor Usage",
|
|
14
|
+
* domain: "cursor.com",
|
|
15
|
+
* description: "Fetches Cursor usage summary.",
|
|
16
|
+
* endpoints: [{ url: "https://cursor.com/api/usage-summary" }],
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
export declare function defineAdapter(config: AdapterConfig): WebsiteAdapter;
|
|
@@ -0,0 +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}
|
package/dist/src/env.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync as t,existsSync as n}from"node:fs";import{dirname as o,resolve as r}from"node:path";import{fileURLToPath as s}from"node:url";const i=o(s(import.meta.url));export function loadEnv(){try{const o=r(i,"../../.env");if(!n(o))return;const s=t(o,"utf8");for(const t of s.split(/\r?\n/)){const n=t.trim();if(!n||n.startsWith("#"))continue;const o=n.indexOf("=");if(o<=0)continue;const r=n.substring(0,o).trim();let s=n.substring(o+1).trim();(s.startsWith('"')&&s.endsWith('"')||s.startsWith("'")&&s.endsWith("'"))&&(s=s.substring(1,s.length-1)),process.env[r]=s}}catch{}}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CookieEntry } from "chrome-tools";
|
|
2
|
+
/**
|
|
3
|
+
* Defines an API endpoint to be fetched by an adapter.
|
|
4
|
+
*/
|
|
5
|
+
export interface Endpoint {
|
|
6
|
+
/** Full URL of the API endpoint. */
|
|
7
|
+
url: string;
|
|
8
|
+
/** HTTP method. Defaults to "GET". */
|
|
9
|
+
method?: string;
|
|
10
|
+
/** Additional headers to include in the request. */
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for defining a website adapter.
|
|
15
|
+
* Simple adapters declare `endpoints` and get automatic fetch handling.
|
|
16
|
+
* Complex adapters override `fetchData` for custom multi-step flows.
|
|
17
|
+
*/
|
|
18
|
+
export interface AdapterConfig {
|
|
19
|
+
/** Unique identifier, typically the domain (e.g. "chatgpt.com"). */
|
|
20
|
+
id: string;
|
|
21
|
+
/** Human-readable name (e.g. "ChatGPT / Codex Usage"). */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Cookie domain to filter Chrome cookies for this site. */
|
|
24
|
+
domain: string;
|
|
25
|
+
/** Short description of what data this adapter fetches. */
|
|
26
|
+
description: string;
|
|
27
|
+
/** Endpoints for simple single-fetch adapters. First endpoint is used by default fetchData. */
|
|
28
|
+
endpoints?: Endpoint[];
|
|
29
|
+
/** Override for complex multi-step flows (e.g. session token → API call). */
|
|
30
|
+
fetchData?: (this: WebsiteAdapter, cookies: CookieEntry[], options: QueryOptions) => Promise<unknown>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A fully-instantiated website adapter with shared helper methods.
|
|
34
|
+
*/
|
|
35
|
+
export interface WebsiteAdapter extends AdapterConfig {
|
|
36
|
+
/** Converts a cookie array into a "name=value; ..." header string. */
|
|
37
|
+
buildCookieString(cookies: CookieEntry[]): string;
|
|
38
|
+
/** Resolves the User-Agent from options → env → default. */
|
|
39
|
+
resolveUserAgent(options: QueryOptions): string;
|
|
40
|
+
/** Fetches a URL and returns parsed JSON, with error handling. */
|
|
41
|
+
fetchJson(url: string, init?: RequestInit): Promise<any>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Options passed when querying a website API.
|
|
45
|
+
*/
|
|
46
|
+
export interface QueryOptions {
|
|
47
|
+
/** Chrome profile directory name (e.g. "Default", "Profile 1"). */
|
|
48
|
+
profile?: string;
|
|
49
|
+
/** Custom User-Agent header for HTTP requests. */
|
|
50
|
+
userAgent?: string;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WebsiteAdapter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a universal fallback adapter for any domain that doesn't have
|
|
4
|
+
* a dedicated adapter. Parses the websiteId as a URL and uses the default
|
|
5
|
+
* single-endpoint fetch flow.
|
|
6
|
+
*
|
|
7
|
+
* @param websiteId - A URL or domain string (e.g. "example.com/api/data")
|
|
8
|
+
* @returns A WebsiteAdapter if the URL is valid, or null otherwise.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createUniversalAdapter(websiteId: string): WebsiteAdapter | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineAdapter as t}from"./base-adapter.js";export function createUniversalAdapter(r){let e=r;e.startsWith("http://")||e.startsWith("https://")||(e="https://"+e);try{const a=new URL(e);return t({id:r,name:r,domain:a.hostname,description:`Universal adapter for ${a.hostname}`,endpoints:[{url:a.href}]})}catch{return null}}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGPT adapter — fetches rate limit usage from the wham/usage API.
|
|
3
|
+
*
|
|
4
|
+
* This is a multi-step flow:
|
|
5
|
+
* 1. Exchange Chrome cookies for a Bearer JWT via the Next-Auth session endpoint
|
|
6
|
+
* 2. Use the JWT to query the private wham/usage endpoint
|
|
7
|
+
*
|
|
8
|
+
* See request.md in this directory for full endpoint documentation.
|
|
9
|
+
*/
|
|
10
|
+
declare const _default: import("../../types.js").WebsiteAdapter;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineAdapter as e}from"../../base-adapter.js";export default e({id:"codex-usage",name:"ChatGPT / Codex Usage",domain:"chatgpt.com",description:"Fetches ChatGPT rate limit usage and quota details from the private wham/usage API.",async fetchData(e,t){const a=this.buildCookieString(e),s=this.resolveUserAgent(t),o=await this.fetchJson("https://chatgpt.com/api/auth/session",{headers:{Cookie:a,"User-Agent":s,Accept:"application/json"}});if(!o?.accessToken)throw new Error("No login found in browser");return this.fetchJson("https://chatgpt.com/backend-api/wham/usage",{headers:{authorization:`Bearer ${o.accessToken}`}})}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineAdapter as r}from"../../base-adapter.js";export default r({id:"cursor-usage",name:"Cursor Usage",domain:"cursor.com",description:"Fetches the active Cursor usage summary from the private usage-summary API.",endpoints:[{url:"https://cursor.com/api/usage-summary"}]});
|
|
@@ -0,0 +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"}}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { QueryOptions, WebsiteAdapter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* All loaded website adapters, populated by `loadAdapters()`.
|
|
4
|
+
*/
|
|
5
|
+
export declare let websites: WebsiteAdapter[];
|
|
6
|
+
/**
|
|
7
|
+
* Auto-discovers and loads all website adapters from the `website/` directory.
|
|
8
|
+
*
|
|
9
|
+
* Scans each subdirectory for a file matching `*-adapter.js` and dynamically
|
|
10
|
+
* imports it. Each adapter module must have a default export that is a
|
|
11
|
+
* WebsiteAdapter (created via `defineAdapter()`).
|
|
12
|
+
*
|
|
13
|
+
* This is called once on first use — no manual imports needed when adding
|
|
14
|
+
* new adapters.
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadAdapters(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Finds an adapter by ID or partial domain match.
|
|
19
|
+
*
|
|
20
|
+
* @param id - Website identifier (e.g. "chatgpt.com", "chatgpt", "cursor")
|
|
21
|
+
* @returns The matching adapter or null
|
|
22
|
+
*/
|
|
23
|
+
export declare function getWebsite(id: string): WebsiteAdapter | null;
|
|
24
|
+
/**
|
|
25
|
+
* Queries a registered website API using decrypted Google Chrome cookies on macOS.
|
|
26
|
+
*
|
|
27
|
+
* Cookie retrieval is simplified to a single `getCookies({ domain })` call —
|
|
28
|
+
* no manual extraction or domain matching needed.
|
|
29
|
+
*
|
|
30
|
+
* @param websiteId - The domain or identifier of the website (e.g. "chatgpt.com")
|
|
31
|
+
* @param options - Retrieval options (profile, userAgent)
|
|
32
|
+
* @returns The structured website API response
|
|
33
|
+
*/
|
|
34
|
+
export declare function queryWebsite(websiteId: string, options?: QueryOptions): Promise<unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readdirSync as o}from"node:fs";import{dirname as e,join as r}from"node:path";import{fileURLToPath as t,pathToFileURL as i}from"node:url";import{getCookies as n}from"chrome-tools";import{loadEnv as s}from"./env.js";import{createUniversalAdapter as a}from"./universal-adapter.js";s();const c=e(t(import.meta.url)),f=r(c,"website");export let websites=[];export async function loadAdapters(){if(!(websites.length>0))try{const e=o(f,{withFileTypes:!0});for(const t of e){if(!t.isDirectory())continue;const e=r(f,t.name),n=o(e).find(o=>o.endsWith("-adapter.js"));if(!n)continue;const s=i(r(e,n)).href,a=(await import(s)).default;a?.id&&websites.push(a)}}catch(o){}}export function getWebsite(o){if(!o)return null;const e=o.toLowerCase().trim();return websites.find(o=>o.id.toLowerCase()===e||o.id.toLowerCase().replace(".com","")===e)??null}export async function queryWebsite(o,e={}){await loadAdapters();const r=process.env.PROFILE_PATH||process.env.CHROME_PROFILE_PATH||void 0,t=e.profile||process.env.PROFILE_NAME||void 0;let i,s=getWebsite(o);if(!s&&(s=a(o),!s))throw new Error("No login found in browser");try{i=n({chromeDir:r,profile:t,domain:s.domain,decrypt:!0})}catch{throw new Error("No login found in browser")}if(!i||0===i.length)throw new Error("No login found in browser");return s.fetchData(i,e)}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
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",
|
|
5
|
+
"main": "./dist/src/website-api.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"website-api": "./dist/bin/cli.js",
|
|
9
|
+
"chrome-website-api": "./dist/bin/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"chrome",
|
|
13
|
+
"cookies",
|
|
14
|
+
"api",
|
|
15
|
+
"chatgpt",
|
|
16
|
+
"codex",
|
|
17
|
+
"macos",
|
|
18
|
+
"cli"
|
|
19
|
+
],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chalk": "^5.6.2",
|
|
24
|
+
"chrome-tools": "^1.0.6",
|
|
25
|
+
"cli-table3": "^0.6.5",
|
|
26
|
+
"commander": "^14.0.3"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.9.1",
|
|
30
|
+
"terser": "^5.43.1",
|
|
31
|
+
"typescript": "^6.0.3"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "node scripts/build.mjs",
|
|
38
|
+
"check:secrets": "node scripts/check-secrets.mjs",
|
|
39
|
+
"release": "pnpm version patch && pnpm publish --access public",
|
|
40
|
+
"start": "node dist/bin/cli.js"
|
|
41
|
+
}
|
|
42
|
+
}
|