@axiom_ai/api 1.0.1 → 1.0.2

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/AGENTS.md ADDED
@@ -0,0 +1,3 @@
1
+ # AGENTS.md
2
+
3
+ This package documents its agent conventions, public API surface, common patterns, and error semantics in [`CLAUDE.md`](./CLAUDE.md) — read that file first, regardless of which agent you're running. Runnable examples live in [`examples/`](./examples/).
package/CLAUDE.md ADDED
@@ -0,0 +1,154 @@
1
+ # CLAUDE.md — `@axiom_ai/api` agent instructions
2
+
3
+ Reference doc Claude (and other agents following the AGENTS.md convention) read when working with this package — both inside this repo and after `npm install @axiom_ai/api` puts a copy at `node_modules/@axiom_ai/api/CLAUDE.md` in a consumer project. Keep this terse and code-truth-aligned; the actual source-of-truth is [`src/axiom-api.js`](./src/axiom-api.js).
4
+
5
+ ## What this package is
6
+
7
+ A small Node library that drives Axiom.ai's cloud browser pool over a REST API (`/api/v5/*`). It is **not** a Puppeteer/Playwright wrapper and **does not** speak CDP — it calls named step functions on a Laravel backend (`https://lar.axiom.ai`) that run inside the cloud browser. If you want raw CDP, that's the separate Chrome API surface — see [the website's API overview](https://axiom.ai/docs/developer-hub/api).
8
+
9
+ Bundled in the npm tarball alongside source-built `dist/`:
10
+ - This file (`CLAUDE.md`) — surface + patterns.
11
+ - `AGENTS.md` — redirect to here for non-Claude agents.
12
+ - `examples/` — three runnable Node scripts: `simple-scrape.js`, `login-then-extract.js`, `parallel-sessions.js`.
13
+
14
+ ## Public method surface
15
+
16
+ Every method below is on the `AxiomApi` class. Construct with `new AxiomApi(apiKey)`; the key comes from the user's account (see "Auth + signup" below).
17
+
18
+ ### Lifecycle (always pair these)
19
+
20
+ | Method | Notes |
21
+ |---|---|
22
+ | `browserOpen()` | Opens a cloud browser session. Returns the CDP endpoint and caches it on the instance as `axiom.cdpLink`. |
23
+ | `browserClose(cdpLink?)` | Closes the session. Pass `cdpLink` only if closing a session from a different instance (rare). |
24
+
25
+ **Wrap every script in `try { … } finally { await axiom.browserClose() }`** so a thrown step doesn't leak a cloud session.
26
+
27
+ ### Navigation
28
+
29
+ | Method | Notes |
30
+ |---|---|
31
+ | `goto(url, doNotShareLocalstorage?, openInNewTab?)` | Navigate the current tab (or a new one if third arg is `true`). |
32
+ | `switchBrowserTab(selectTab)` | Switch focus to a tab by name/URL substring. |
33
+
34
+ ### Interaction
35
+
36
+ | Method | Notes |
37
+ |---|---|
38
+ | `click(select, leftClickRightClick?, optionalClick?)` | Single click. `optionalClick: true` no-ops when the selector doesn't match. |
39
+ | `clickMultiple(select, leftClickRightClick?, maxClicks?)` | Click every match up to `maxClicks`. |
40
+ | `clickEngagementButton(select, setValueToCheck)` | Idempotent like/follow/subscribe toggle. |
41
+ | `hover(select)` | Mouse-over an element. |
42
+ | `clickAndDrag(startCoordinates, endCoordinates)` | Mouse-down at one point, release at another. Coordinates are `{scrollX, scrollY, clientX, clientY}` objects. |
43
+ | `enterText(selectTextField, text, delay?, appendExisting?, customLineBreak?, optionalText?)` | Type into an input field. |
44
+ | `pressKeys(key, delimiter?, delay?)` | Fire keyboard events (Enter, Tab, arrow keys, modifier combos). |
45
+ | `selectList(select, text)` | Pick an option from a `<select>` by visible text. |
46
+ | `datePicker(selectMonth, selectMonthChangeButton, changeMonthTo, changeDayOfMonthTo)` | Step through a calendar widget. |
47
+
48
+ ### Data
49
+
50
+ | Method | Notes |
51
+ |---|---|
52
+ | `scrape(url, selector, pager, max_results, settings)` | Smart-scrape rows. Pass `null` for `url` to scrape the current page. Returns an array of objects. |
53
+ | `scrapeMetadata(metadata)` | Extract structured page-level fields (title, OG tags, schema.org blocks). |
54
+ | `getClipboardContents()` | Read the cloud browser's clipboard (after a copy step). |
55
+
56
+ ### AI and utility
57
+
58
+ | Method | Notes |
59
+ |---|---|
60
+ | `integrateAI(aiOptions)` | Inline LLM call (extract or generate). `aiOptions: {aiFunction, key?, model?, prompt?, extractData?, promptExtract?}`. |
61
+ | `solveCaptcha(apiKey?)` | Hand the current page's captcha to a third-party solver. Omit `apiKey` to use the one stored on the account. |
62
+ | `wait(time)` | Pause **on the cloud pod** (keeps the session alive) for `time` ms. Use this rather than client-side `setTimeout`. |
63
+ | `restartBrowser()` | Restart Chrome within the same session — useful between unrelated flows. |
64
+
65
+ ## Methods NOT to call
66
+
67
+ These exist on the class but are not part of the public surface — don't emit them in generated code:
68
+
69
+ - `step(mode, method, params, cdpLink)` — the internal dispatcher every public method routes through. If you ever feel like calling `step()` directly, that means a named wrapper is missing; file an issue, don't paper over it.
70
+ - `_sleep(ms)` — client-side sleep helper. Internal; use `wait(time)` (cloud-side) for browser pacing.
71
+ - `_pollStepResult(...)` / `_shouldFallBackToPolling(...)` — error-recovery internals.
72
+
73
+ ## Common patterns
74
+
75
+ ### Simplest scrape — open, navigate, scrape, close
76
+
77
+ ```javascript
78
+ import { AxiomApi } from '@axiom_ai/api'
79
+
80
+ const axiom = new AxiomApi(process.env.AXIOM_API_KEY)
81
+ await axiom.browserOpen()
82
+ try {
83
+ await axiom.goto('https://example.com/products')
84
+ const rows = await axiom.scrape(null, '.product-card', null, 50, {})
85
+ console.log(`Found ${rows.length} products`)
86
+ } finally {
87
+ await axiom.browserClose()
88
+ }
89
+ ```
90
+
91
+ ### Login then extract
92
+
93
+ ```javascript
94
+ await axiom.goto('https://example.com/login')
95
+ await axiom.enterText('input[name=email]', process.env.LOGIN_EMAIL)
96
+ await axiom.enterText('input[name=password]', process.env.LOGIN_PASSWORD)
97
+ await axiom.click('button[type=submit]')
98
+ await axiom.wait(2000) // settle on the dashboard
99
+ const rows = await axiom.scrape(null, '.row', null, 100, {})
100
+ ```
101
+
102
+ ### Parallel sessions
103
+
104
+ Create independent `AxiomApi` instances — each calls `browserOpen()` and `browserClose()` independently. Don't share `cdpLink` between them.
105
+
106
+ ```javascript
107
+ const a = new AxiomApi(KEY); const b = new AxiomApi(KEY)
108
+ await Promise.all([a.browserOpen(), b.browserOpen()])
109
+ try {
110
+ const [rowsA, rowsB] = await Promise.all([
111
+ a.goto('https://a.test').then(() => a.scrape(null, '.row', null, 50, {})),
112
+ b.goto('https://b.test').then(() => b.scrape(null, '.row', null, 50, {})),
113
+ ])
114
+ } finally {
115
+ await Promise.all([a.browserClose(), b.browserClose()])
116
+ }
117
+ ```
118
+
119
+ See [`examples/`](./examples/) for runnable versions of these.
120
+
121
+ ## Error semantics
122
+
123
+ The library throws `AxiomHttpError` on non-2xx responses. Common shapes:
124
+
125
+ | Status | Meaning | What to do |
126
+ |---|---|---|
127
+ | 401 | API key invalid / expired | Re-mint the key — don't retry. |
128
+ | 403 | Account-level block (quota, abuse, plan) | Don't retry; surface to the user. |
129
+ | 409 | A step is already in flight on this `cdpLink` | Library auto-polls `/api/v5/step/result` and returns the result transparently. You shouldn't normally see 409 propagate. |
130
+ | 502 / 503 / 504 | Pod transient | Library auto-falls-back to polling. If still failing after polling, retry the whole `browserOpen → … → browserClose` flow. |
131
+ | Other 4xx | Bad params (wrong selector type, missing required arg) | Inspect the error body and fix the call. |
132
+
133
+ ## Auth + signup
134
+
135
+ The `AXIOM_API_KEY` env var holds the long-lived key. Two ways to get one:
136
+
137
+ 1. **Interactive** — sign up at [axiom.ai](https://axiom.ai), open the dashboard, copy the key.
138
+ 2. **Programmatic** — three-call flow documented at [`/docs/developer-hub/api/programmatic-signup`](https://axiom.ai/docs/developer-hub/api/programmatic-signup): `POST /api/user/create` → `POST /api/user/login` → `GET /api/user/key/create`. Suitable for headless onboarding.
139
+
140
+ ## Relationship to `@axiom_ai/claude-skill`
141
+
142
+ If the user is asking Claude to **build an axiom from a prompt** (e.g. "scrape this site daily and email me", "build me a bot that…"), the explicit marketplace plugin [`@axiom_ai/claude-skill`](https://github.com/axiom-browser-automation/claude-skill) gives Claude a richer guided experience — it covers both the *coded* path (this package) and the *no-code* path (saving to the user's dashboard). Use it when the user wants Claude to *invent* an axiom; this CLAUDE.md is enough when the user is *writing their own code* and asking Claude for help with the API surface.
143
+
144
+ ## Build + test (for maintainers working in this repo)
145
+
146
+ ```bash
147
+ npm ci # install deps
148
+ npm test # jest, 26 tests, runs against nock mocks (no live calls)
149
+ npm run build # vite produces dist/{es,cjs}/index.js; tsc emits .d.ts only
150
+ ```
151
+
152
+ **Watch-out from v1.0.1**: `tsconfig.json` is set to `emitDeclarationOnly: true`. If you change that, `tsc` will start emitting multi-file `.js` into `dist/es/` and clobber vite's single-bundle build — the result imports fine under CJS but fails under modern Node ESM with `ERR_MODULE_NOT_FOUND` on extensionless internal imports. Leave the flag alone unless you also wire up vite/tsc differently.
153
+
154
+ Publishing is via the Jenkins job `Publishing/axiom-api-publish` on the team's Jenkins; manual trigger only.
package/README.md CHANGED
@@ -30,4 +30,14 @@ async function run() {
30
30
  }
31
31
 
32
32
  run();
33
- ```
33
+ ```
34
+
35
+ ## Working with Claude Code (or other agents)
36
+
37
+ This package bundles agent-targeted documentation alongside the runtime:
38
+
39
+ - [`CLAUDE.md`](./CLAUDE.md) — full method surface, error semantics, common patterns, auth + signup, and the methods *not* to call. Read by Claude automatically when this package is installed in a project.
40
+ - [`AGENTS.md`](./AGENTS.md) — same content, surfaced for non-Claude agents that follow the `AGENTS.md` convention.
41
+ - [`examples/`](./examples/) — three runnable scripts (`simple-scrape.js`, `login-then-extract.js`, `parallel-sessions.js`).
42
+
43
+ If you want Claude to *invent* an axiom from a prompt rather than help you write API calls yourself, also see [`@axiom_ai/claude-skill`](https://github.com/axiom-browser-automation/claude-skill) — the marketplace plugin covers both this library and the no-code dashboard path.
package/dist/cjs/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const k="https://lar.axiom.ai";class c extends Error{constructor(t,e,n){super(t),this.name="AxiomHttpError",this.status=e,this.body=n}}class m{async post(t,e,n,i={}){const s=new AbortController;let o=null;i.timeoutMs&&i.timeoutMs>0&&(o=setTimeout(()=>s.abort(),i.timeoutMs));let r;try{r=await fetch(k+t,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","X-API-KEY":e},body:JSON.stringify(n),signal:s.signal})}finally{o&&clearTimeout(o)}if(!r.ok){const h=await r.text();let a=null;try{a=JSON.parse(h)}catch{}if(r.status===401)throw new c("Failed to authenticate - please check your token.",401,a);const w=a&&a.message?a.message:`HTTP ${r.status} error: ${h}`;throw new c(w,r.status,a)}return r.json()}}const y=12e4,u=36e5,f=3e3,l=3e4,d=1.5;class g{constructor(t){this.cdpLink="",this.token=t,this.http=new m}async browserOpen(){const t=await this.http.post("/api/v5/browser/open",this.token);return this.cdpLink=t.endpoint,t.endpoint}async browserClose(t=""){t||(t=this.cdpLink);const e=await this.http.post("/api/v5/browser/close",this.token,{cdpLink:t});return this.cdpLink="",e.message}async step(t,e,n,i=""){try{const s=await this.http.post("/api/v5/step",this.token,{mode:t,method:e,params:n,cdpLink:i},{timeoutMs:y});return s&&s.message!==void 0?s.message:s}catch(s){if(!i||!this._shouldFallBackToPolling(s))throw s;return this._pollStepResult(i,s)}}_shouldFallBackToPolling(t){return!!(t&&t.name==="AbortError"||t&&t.name==="TypeError"||t instanceof c&&(t.status===502||t.status===503||t.status===504||t.status===409&&typeof t.message=="string"&&t.message.indexOf("Step already in progress")!==-1))}async _pollStepResult(t,e){const n=Date.now()+u;let i=f;for(;Date.now()<n;){await this._sleep(i);try{const s=await this.http.post("/api/v5/step/result",this.token,{cdpLink:t});if(s&&s.status==="complete")return s.result;if(s&&s.status==="running"){i=Math.min(i*d,l);continue}throw s&&s.status==="none"?new Error("Step did not start on the pod. Original error: "+(e&&e.message?e.message:"unknown")):new Error("Unexpected /step/result response: "+JSON.stringify(s))}catch(s){if(s instanceof c&&s.status===409)throw new Error("Step failed on the pod — check the task report. "+(s.message||""));if(s instanceof c&&s.status>=400&&s.status<500&&s.status!==502&&s.status!==503&&s.status!==504)throw s;i=Math.min(i*d,l)}}throw new Error("Step polling exceeded "+u/1e3+"s. Original error: "+(e&&e.message?e.message:"unknown"))}async scrape(t,e,n,i,s){return this.step("browser","AxiomApiSmartScrapeV440",[t,e,n,i,s],this.cdpLink)}async integrateAI(t){return this.step("browser","AxiomApiAiGeneric",[t],this.cdpLink)}async datePicker(t,e,n,i){return this.step("driver","datePicker",[t,e,n,i],this.cdpLink)}async click(t,e,n){return this.step("driver","clickV3130",[t,e,n],this.cdpLink)}async clickEngagementButton(t,e){return this.step("driver","clickEngagementButton",[t,e],this.cdpLink)}async clickMultiple(t,e,n){return this.step("driver","multiClickV3170",[t,e,n],this.cdpLink)}async getClipboardContents(){return this.step("driver","readClipboardContents",[],this.cdpLink)}async enterText(t,e,n=0,i=!1,s=null,o=!1){return this.step("driver","enterTextV4500",[t,e,n,i,s,o],this.cdpLink)}async goto(t,e=!1,n=!1){return this.step("driver","gotoV4070",[t,null,e,n],this.cdpLink)}async pressKeys(t,e,n){return this.step("driver","keydownV3120",[t,null,e,n],this.cdpLink)}async clickAndDrag(t,e){return this.step("driver","mouseClickDragV0300",[t,e],this.cdpLink)}async scrapeMetadata(t){return this.step("driver","scrapeMetadata",[t],this.cdpLink)}async selectList(t,e){return this.step("driver","selectList",[t,e],this.cdpLink)}async solveCaptcha(t){return this.step("driver","solveCaptchaV450",[null,null,t],this.cdpLink)}async switchBrowserTab(t){return this.step("driver","switchBrowserTab",[t],this.cdpLink)}async wait(t){return this.cdpLink?this.step("driver","wait",[t],this.cdpLink):this._sleep(t)}_sleep(t){return new Promise(e=>setTimeout(e,t))}async hover(t){return this.step("driver","hover",[t],this.cdpLink)}async restartBrowser(){return this.step("browser","AxiomApiRestartBrowser",[],this.cdpLink)}}exports.AxiomApi=g;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const k="https://lar.axiom.ai";class c extends Error{constructor(t,e,n){super(t),this.name="AxiomHttpError",this.status=e,this.body=n}}class m{async post(t,e,n,i={}){const s=new AbortController;let o=null;i.timeoutMs&&i.timeoutMs>0&&(o=setTimeout(()=>s.abort(),i.timeoutMs));let r;try{r=await fetch(k+t,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","X-API-KEY":e},body:JSON.stringify(n),signal:s.signal})}finally{o&&clearTimeout(o)}if(!r.ok){const h=await r.text();let a=null;try{a=JSON.parse(h)}catch{}if(r.status===401)throw new c("Failed to authenticate - please check your token.",401,a);const w=a&&a.message?a.message:`HTTP ${r.status} error: ${h}`;throw new c(w,r.status,a)}return r.json()}}const y=12e4,u=36e5,f=3e3,l=3e4,d=1.5;class g{constructor(t){this.cdpLink="",this.token=t,this.http=new m}async browserOpen(){const t=await this.http.post("/api/v5/browser/open",this.token);return this.cdpLink=t.endpoint,t.endpoint}async browserClose(t=""){t||(t=this.cdpLink);const e=await this.http.post("/api/v5/browser/close",this.token,{cdpLink:t});return this.cdpLink="",e.message}async step(t,e,n,i=""){try{const s=await this.http.post("/api/v5/step",this.token,{mode:t,method:e,params:n,cdpLink:i},{timeoutMs:y});return s&&s.message!==void 0?s.message:s}catch(s){if(!i||!this._shouldFallBackToPolling(s))throw s;return this._pollStepResult(i,s)}}_shouldFallBackToPolling(t){return!!(t&&t.name==="AbortError"||t&&t.name==="TypeError"||t instanceof c&&(t.status===502||t.status===503||t.status===504||t.status===409&&typeof t.message=="string"&&t.message.indexOf("Step already in progress")!==-1))}async _pollStepResult(t,e){const n=Date.now()+u;let i=f;for(;Date.now()<n;){await this._sleep(i);try{const s=await this.http.post("/api/v5/step/result",this.token,{cdpLink:t});if(s&&s.status==="complete")return s.result;if(s&&s.status==="running"){i=Math.min(i*d,l);continue}throw s&&s.status==="none"?new Error("Step did not start on the pod. Original error: "+(e&&e.message?e.message:"unknown")):new Error("Unexpected /step/result response: "+JSON.stringify(s))}catch(s){if(s instanceof c&&s.status===409)throw new Error("Step failed on the pod — check the task report. "+(s.message||""));if(s instanceof c&&s.status>=400&&s.status<500&&s.status!==502&&s.status!==503&&s.status!==504)throw s;i=Math.min(i*d,l)}}throw new Error("Step polling exceeded "+u/1e3+"s. Original error: "+(e&&e.message?e.message:"unknown"))}async scrape(t,e,n,i,s){return this.step("browser","AxiomApiSmartScrapeV4400",[t,e,n,i,s],this.cdpLink)}async integrateAI(t){return this.step("browser","AxiomApiAiGeneric",[t],this.cdpLink)}async datePicker(t,e,n,i){return this.step("driver","datePicker",[t,e,n,i],this.cdpLink)}async click(t,e,n){return this.step("driver","clickV3130",[t,e,n],this.cdpLink)}async clickEngagementButton(t,e){return this.step("driver","clickEngagementButton",[t,e],this.cdpLink)}async clickMultiple(t,e,n){return this.step("driver","multiClickV3170",[t,e,n],this.cdpLink)}async getClipboardContents(){return this.step("driver","readClipboardContents",[],this.cdpLink)}async enterText(t,e,n=0,i=!1,s=null,o=!1){return this.step("driver","enterTextV4500",[t,e,n,i,s,o],this.cdpLink)}async goto(t,e=!1,n=!1){return this.step("driver","gotoV4070",[t,null,e,n],this.cdpLink)}async pressKeys(t,e,n){return this.step("driver","keydownV3120",[t,null,e,n],this.cdpLink)}async clickAndDrag(t,e){return this.step("driver","mouseClickDragV0300",[t,e],this.cdpLink)}async scrapeMetadata(t){return this.step("driver","scrapeMetadata",[t],this.cdpLink)}async selectList(t,e){return this.step("driver","selectList",[t,e],this.cdpLink)}async solveCaptcha(t){return this.step("driver","solveCaptchaV450",[null,null,t],this.cdpLink)}async switchBrowserTab(t){return this.step("driver","switchBrowserTab",[t],this.cdpLink)}async wait(t){return this.cdpLink?this.step("driver","wait",[t],this.cdpLink):this._sleep(t)}_sleep(t){return new Promise(e=>setTimeout(e,t))}async hover(t){return this.step("driver","hover",[t],this.cdpLink)}async restartBrowser(){return this.step("browser","AxiomApiRestartBrowser",[],this.cdpLink)}}exports.AxiomApi=g;
package/dist/es/index.js CHANGED
@@ -100,7 +100,7 @@ class _ {
100
100
  async scrape(t, e, n, i, s) {
101
101
  return this.step(
102
102
  "browser",
103
- "AxiomApiSmartScrapeV440",
103
+ "AxiomApiSmartScrapeV4400",
104
104
  [t, e, n, i, s],
105
105
  this.cdpLink
106
106
  );
@@ -0,0 +1,17 @@
1
+ # Examples
2
+
3
+ Runnable Node scripts demonstrating common `@axiom_ai/api` patterns. Each is small (20-30 lines), ES-module syntax, and reads its API key from `AXIOM_API_KEY` in the environment.
4
+
5
+ | File | Demonstrates |
6
+ |---|---|
7
+ | [`simple-scrape.js`](./simple-scrape.js) | Smallest valid axiom — `browserOpen → goto → scrape → browserClose` with `try/finally`. |
8
+ | [`login-then-extract.js`](./login-then-extract.js) | `enterText` + `click` + `wait` for an auth flow, then scrape behind the login. |
9
+ | [`parallel-sessions.js`](./parallel-sessions.js) | Two `AxiomApi` instances side by side — each has its own lifecycle. |
10
+
11
+ Run any of them with:
12
+
13
+ ```bash
14
+ AXIOM_API_KEY=your-key node simple-scrape.js
15
+ ```
16
+
17
+ The full method surface, error semantics, and patterns reference is in [`../CLAUDE.md`](../CLAUDE.md).
@@ -0,0 +1,25 @@
1
+ // Log into a site, then scrape behind auth. Demonstrates: enterText, click, wait.
2
+
3
+ import { AxiomApi } from '@axiom_ai/api'
4
+
5
+ async function main() {
6
+ const axiom = new AxiomApi(process.env.AXIOM_API_KEY)
7
+
8
+ await axiom.browserOpen()
9
+ try {
10
+ await axiom.goto('https://example.com/login')
11
+ await axiom.enterText('#email', process.env.SITE_EMAIL)
12
+ await axiom.enterText('#password', process.env.SITE_PASSWORD)
13
+ await axiom.click('button[type=submit]')
14
+ // Wait on the pod (keeps the session alive) rather than a local setTimeout.
15
+ await axiom.wait(2000)
16
+
17
+ await axiom.goto('https://example.com/dashboard')
18
+ const rows = await axiom.scrape(null, '.report-row', null, 100, {})
19
+ return rows
20
+ } finally {
21
+ await axiom.browserClose()
22
+ }
23
+ }
24
+
25
+ await main()
@@ -0,0 +1,26 @@
1
+ // Drive two sessions in parallel. Each gets its own AxiomApi instance + lifecycle.
2
+ // Demonstrates that browserClose() is per-instance, not global.
3
+
4
+ import { AxiomApi } from '@axiom_ai/api'
5
+
6
+ async function scrapeOne(url) {
7
+ const axiom = new AxiomApi(process.env.AXIOM_API_KEY)
8
+ await axiom.browserOpen()
9
+ try {
10
+ await axiom.goto(url)
11
+ return await axiom.scrape(null, '.item', null, 20, {})
12
+ } finally {
13
+ await axiom.browserClose()
14
+ }
15
+ }
16
+
17
+ async function main() {
18
+ const urls = [
19
+ 'https://example.com/category/a',
20
+ 'https://example.com/category/b'
21
+ ]
22
+ const results = await Promise.all(urls.map(scrapeOne))
23
+ console.log('Total rows:', results.flat().length)
24
+ }
25
+
26
+ await main()
@@ -0,0 +1,20 @@
1
+ // Smallest valid axiom: open browser, navigate, scrape, close.
2
+ // Validator checks: methods on allowlist, try/finally lifecycle, no hardcoded token.
3
+
4
+ import { AxiomApi } from '@axiom_ai/api'
5
+
6
+ async function main() {
7
+ const axiom = new AxiomApi(process.env.AXIOM_API_KEY)
8
+
9
+ await axiom.browserOpen()
10
+ try {
11
+ await axiom.goto('https://example.com/products')
12
+ const rows = await axiom.scrape(null, '.product-card', null, 50, {})
13
+ console.log(`Found ${rows.length} products`)
14
+ return rows
15
+ } finally {
16
+ await axiom.browserClose()
17
+ }
18
+ }
19
+
20
+ await main()
package/package.json CHANGED
@@ -2,13 +2,16 @@
2
2
  "name": "@axiom_ai/api",
3
3
  "author": "Axiom.ai",
4
4
  "type": "module",
5
- "version": "1.0.1",
5
+ "version": "1.0.2",
6
6
  "description": "Library for interacting with Axiom.ai browser infrastructure",
7
7
  "main": "dist/cjs/index.js",
8
8
  "module": "dist/es/index.js",
9
9
  "types": "dist/es/index.d.ts",
10
10
  "files": [
11
- "dist"
11
+ "dist",
12
+ "CLAUDE.md",
13
+ "AGENTS.md",
14
+ "examples"
12
15
  ],
13
16
  "scripts": {
14
17
  "build:js": "vite build --mode production",
@@ -56,7 +59,11 @@
56
59
  "axiom.ai",
57
60
  "sdk",
58
61
  "browser",
59
- "automation"
62
+ "automation",
63
+ "claude",
64
+ "claude-code",
65
+ "agents-md",
66
+ "llm-friendly"
60
67
  ],
61
68
  "license": "ISC",
62
69
  "bugs": {