Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
branches: [main]

jobs:
# Fast unit-test job — Node matrix, no browser binaries needed.
test:
runs-on: ubuntu-latest
strategy:
Expand All @@ -26,8 +27,39 @@ jobs:
- run: npm run build
- run: npm test

# Integration job — installs Chromium and runs the SDK against a real
# browser. Slower so kept separate; runs on Node 20 only and on Linux.
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install --no-audit --no-fund
- name: Install Chromium for playwright-core
run: npx playwright-core install --with-deps chromium
- run: npm run build
- name: Run integration tests
env:
AGENTMARK_INTEGRATION: '1'
run: npx vitest run test/runtime.integration.test.ts

# macOS smoke test — catches platform-specific regressions in the
# converter and serializer paths. Unit tests only (browser-free).
macos-smoke:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install --no-audit --no-fund
- run: npm run build
- run: npm test

publish:
needs: test
needs: [test, integration, macos-smoke]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
permissions:
Expand Down
101 changes: 101 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Changelog

All notable changes to `@thinkfleet/agentmark` will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.0] — 2026-05-10

This is the **first production-ready release**. Adds the high-level SDK
surface, structured error hierarchy, observability hooks, and session
persistence on top of the v0.2 wire-format conversion.

### Added

- **High-level SDK** — `createBrowser()`, `Browser`, `Page` wrappers with a
small surface (`page.goto()`, `page.snapshot()`, `page.execute()`) that
hides Playwright details from typical callers while keeping `.raw`
escape hatches for advanced use.
- **Action executor** (`executeAction`) — full coverage of all 17
`ActionType`s with a single `execute(actionId, value?)` entry point.
Resolves binding, dispatches the right Playwright operation, validates
value types, classifies errors, disposes element handles in `finally`.
- **Error hierarchy** — `AgentMarkError` (base) → `SnapshotError`,
`ExecutionError` (with `ActionNotFoundError`, `ActionDisabledError`,
`ActionTypeError`, `ElementNotFoundError`, `ExecutionTimeoutError`),
`SessionError`. All errors carry stable `code` strings, preserve the
prototype chain, and pass through `isAgentMarkError()` type guard.
- **Branded ID types** — `ActionId`, `MediaId`, `RegionId` for nominal
type safety on identifiers. Zero runtime overhead.
- **Pluggable structured logger** — `Logger` interface with `noopLogger`
(default, zero overhead) and `consoleLogger` (JSON-lines for dev).
Threaded through `Browser` → `Page` → executor; emits typed events
(catalog in `AgentMarkEvent`).
- **Session persistence** — `browser.saveSession(path)` /
`createBrowser({ sessionPath })` for cookie + storageState round-trips.
Atomic write via temp+rename to prevent partial files on crash.
Versioned file format (`session_format: '1'`).
- **Honeypot refusal** — actions marked `honeypot: true` (bot-trap fields)
throw `ActionDisabledError` instead of executing.

### Changed

- Public exports re-organized: `src/runtime` is now the canonical module
for SDK surface (`createBrowser`, `Browser`, `Page`, `executeAction`).
Existing `convertPage` + `InMemoryActionBinding` continue to work.

### Tests

- 130 tests passing (was 90 in v0.2). 30 new unit tests cover the
executor, errors, branded types, and session file format.
- 10 new real-Chromium integration tests gated on
`AGENTMARK_INTEGRATION=1`. Cover snapshot capture, form fill + submit
+ redirect, disabled-action refusal, navigation invalidation, session
round-trip across browser instances, idempotent close, end-to-end
logger event flow.

### Production-readiness gates cleared

- Type safety: zero `any` in new code; branded IDs prevent type confusion
- Error taxonomy: full hierarchy with stable codes, prototype-chain safe
- Observability: every public op emits structured events; default no-op
- Atomic writes: sessions never leave partial files
- Backwards compatibility: all v0.2 tests still passing
- Cross-platform: build clean; CI matrix Node 20+22

## [0.2.0] — 2026-04-26

### Added

- Tables → GFM markdown extraction
- iframe content traversal
- Shadow DOM piercing
- Cross-platform CI workflow (`npm install` workaround for npm/cli#4828)

### Fixed

- DOM-race hardening (body-existence guards in wait strategy)
- Type-import alignment

## [0.1.0] — 2026-04-26

Initial release of `@thinkfleet/agentmark`.

### Added

- Reference implementation of agentmark v0.1 spec
- DOM extractor (Playwright Page → AgentMark)
- YAML frontmatter, body-text, and JSON serializers
- Schema validator (Ajv-based)
- Wait strategies: `fast`, `smart` (default), `aggressive`
- Mutation observer for SPA stability detection
- Anti-bot challenge resolver (Cloudflare, reCAPTCHA, hCaptcha)
- Cookie banner auto-dismissal (OneTrust, Cookiebot, Quantcast, Osano,
Didomi, Iubenda, generic, fallback)
- In-memory action binding
- 90 tests, npm provenance auto-publish

[0.3.0]: https://github.com/ThinkfleetAI/agentmark/releases/tag/v0.3.0
[0.2.0]: https://github.com/ThinkfleetAI/agentmark/releases/tag/v0.2.0
[0.1.0]: https://github.com/ThinkfleetAI/agentmark/releases/tag/v0.1.0
98 changes: 90 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,59 @@ actions:
## Install

```bash
npm install @thinkfleet/agentmark
npm install @thinkfleet/agentmark playwright-core
npx playwright-core install chromium # one-time browser install
```

`playwright` is a peer dependency (the converter operates on a Playwright `Page`).
`playwright-core` is a peer dependency. AgentMark wraps a Playwright `Browser`
under the hood and exposes a small SDK that any AI (Claude, GPT, your own
agent loop) can drive.

## Quick Start
## Quick Start — drive a real page

```ts
import { createBrowser } from '@thinkfleet/agentmark'

const browser = await createBrowser({ launch: { headless: true } })
const page = await browser.newPage()
await page.goto('https://example.com/login')

// Capture a compact AgentMark snapshot — pipe to any LLM
const snap = await page.snapshot()
console.log(snap.agentmark)

// LLM (or you) picks an action ID from the snapshot
await page.execute('act_email', 'user@example.com')
await page.execute('act_password', 'hunter2')
await page.execute('act_submit')

await browser.saveSession('./session.json') // persist cookies + storage
await browser.close()

// Later — resume the same authenticated session
const browser2 = await createBrowser({ sessionPath: './session.json' })
```

That's the whole API. AgentMark itself is **library-only** — no agent loop,
no LLM client, no prompts. The caller (you, Claude, GPT, an Activepieces
flow, etc.) brings the loop. AgentMark just exposes great browser primitives.

## Why AgentMark

- **5–10× smaller than raw HTML.** Pages become compact markdown with a
small action vocabulary. Cheaper to send to LLMs, faster to read.
- **Stable action IDs.** Refs survive layout shifts and re-renders — no
CSS selectors leaking into prompts that break next week.
- **Sensitive fields auto-redacted.** Password/token/SSN inputs are
marked `(redacted)` in the snapshot. Values never reach the LLM.
- **Cookie banners and anti-bot challenges handled.** OneTrust, Cookiebot,
Cloudflare, reCAPTCHA, hCaptcha auto-resolved before snapshot.
- **Library, not a framework.** Bring your own model, prompts, and loop.

## Lower-level APIs

For callers who want direct control over conversion or want to feed AgentMark
into a custom Playwright pipeline:

### Serialize a Snapshot

Expand All @@ -64,7 +111,6 @@ const snapshot: Snapshot = {
}

const text = serializeSnapshot(snapshot)
// → "---\nagentmark: \"0.1\"\nurl: \"https://example.com/\"\n...\n---\n\n# Welcome\n\n[ACTION:act_login]\n"
```

### Parse + Validate
Expand All @@ -85,14 +131,50 @@ if (!result.valid) {
import { convertToJson } from '@thinkfleet/agentmark'

const { snapshot, body_nodes } = convertToJson(text)
// snapshot — full envelope + body
// body_nodes — pre-tokenized [{kind: 'text'} | {kind: 'tag', tag, ref}]
```

## Observability

Pass a logger to see structured events. Default is silent.

```ts
import { createBrowser, consoleLogger } from '@thinkfleet/agentmark'

const browser = await createBrowser({ logger: consoleLogger })
// Emits JSON lines: navigation.start / navigation.complete /
// snapshot.captured / action.execute.complete / session.saved / etc.
```

## Error handling

All AgentMark errors extend `AgentMarkError` and carry stable `code` strings.

```ts
import {
isAgentMarkError,
ActionNotFoundError,
ActionDisabledError,
ElementNotFoundError,
ExecutionTimeoutError,
} from '@thinkfleet/agentmark'

try {
await page.execute('act_submit')
} catch (err) {
if (err instanceof ActionDisabledError) { /* button is disabled */ }
else if (err instanceof ElementNotFoundError) { /* snapshot stale */ }
else if (err instanceof ExecutionTimeoutError) { /* page hung */ }
else if (isAgentMarkError(err)) { console.error(err.code, err.message) }
}
```

## Status

- **v0.1** — draft, unstable. Breaking changes possible until v1.0.
- Reference DOM converter (Playwright Page → agentmark) is in active development.
- **v0.3.0** — first production-ready release. Stable SDK surface; backwards
compatible upgrades thereafter. Spec extension to v0.2 (PDF + form support)
in active development.

See [CHANGELOG.md](./CHANGELOG.md) for full release notes.

## License

Expand Down
35 changes: 35 additions & 0 deletions examples/basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Basic AgentMark usage — capture a snapshot, print it, fill a form.
*
* npx tsx examples/basic.ts
*/

import { createBrowser, consoleLogger } from '../src'

async function main() {
const browser = await createBrowser({
launch: { headless: false }, // set true for CI
logger: consoleLogger, // structured event stream
})

try {
const page = await browser.newPage()
await page.goto('https://example.com')

const snap = await page.snapshot()

console.log('\n────── AgentMark snapshot ──────\n')
console.log(snap.agentmark)
console.log('\n────── Available actions ──────\n')
for (const [id, action] of Object.entries(snap.snapshot.actions ?? {})) {
console.log(` ${id}: [${action.type}] ${action.label}`)
}
} finally {
await browser.close()
}
}

main().catch((err) => {
console.error(err)
process.exit(1)
})
Loading
Loading