Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/stable-amp-plugin-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@curl.md/amp': patch
---

Updated the Amp plugin for the stable plugin API.
30 changes: 11 additions & 19 deletions docs/plugins/amp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@ import packageJson from '../../plugins/amp/package.json'

# Amp

First-party [Amp](https://ampcode.com) plugin that routes Amp’s built-in `read_web_page` tool through curl.md and also adds a specific `curl_md` tool.
First-party [Amp](https://ampcode.com) plugin that adds a `curl_md` tool and steers URL reads through curl.md.

<PackageLinks
npm="@curl.md/amp"
source="https://github.com/wevm/curl.md/tree/main/plugins/amp"
version={packageJson.version}
/>

:::important
The Amp [Plugin API](https://ampcode.com/manual/plugin-api) is still experimental. As a result, the curl.md Amp plugin may also change without warning.
:::

## Quick Start

Install the plugin, launch Amp, and start curling.
Expand All @@ -37,16 +33,12 @@ Successful installs create the Amp plugin shim in `~/.config/amp/plugins/curlmd.

### Start Amp

Start Amp with plugins enabled via the `PLUGINS=all` env var:
Start Amp:

```sh
$ PLUGINS=all amp
$ amp
```

:::tip
Amp requires `PLUGINS=all` to be set in order to load plugins. Add this to your environment so you do not need to prefix every Amp command manually.
:::

### Use Amp

Now that Amp is running with the curl.md plugin, all you need to do is use Amp. To confirm everything is set up, try this out:
Expand All @@ -56,7 +48,7 @@ Read the curl.md Amp plugin docs and summarize how it works.
https://curl.md/docs/plugins/amp
```

Amp will automatically use curl.md to turn URLs you paste, or URLs it decides to fetch on its own, into markdown.
Amp will use curl.md to turn URLs you paste, or URLs it decides to fetch on its own, into markdown.

::::

Expand All @@ -79,7 +71,6 @@ $ npx @curl.md/amp@latest install --yes
If you prefer to add the shim manually, create your own plugin entry in `~/.config/amp/plugins`:

```ts title="~/.config/amp/plugins/curlmd.ts"
// @i-know-the-amp-plugin-api-is-wip-and-very-experimental-right-now
import plugin from '@curl.md/amp'
export default plugin
```
Expand Down Expand Up @@ -113,14 +104,15 @@ For non-interactive environments, set [`CURLMD_API_KEY`](/docs/guide/cli#environ

### Tools

The plugin registers the following tools:
The plugin registers the following tool:

| Tool | Description |
| --------- | ------------------------ |
| `curl_md` | Fetch a URL as markdown. |

| Tool | Description |
| --------------- | -------------------------------------------------------- |
| `curl_md` | Fetch a URL as markdown. |
| `read_web_page` | Overrides built-in `read_web_page` with markdown output. |
The plugin also keeps a best-effort legacy hook for `read_web_page` if Amp exposes built-in tool calls to plugins.

Both `curl_md` and the intercepted `read_web_page` tool accept the following inputs:
`curl_md` accepts the following inputs:

| Input | Type | Description |
| ------------ | ---------------- | ------------------------------------------------------------------------------------ |
Expand Down
2 changes: 1 addition & 1 deletion plugins/amp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@
"curl.md": "workspace:*"
},
"devDependencies": {
"@ampcode/plugin": "0.0.0-dev"
"@ampcode/plugin": "0.0.0-20260527003623-g8232386"
}
}
18 changes: 3 additions & 15 deletions plugins/amp/src/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,7 @@ test('installs the package and writes the global shim', async () => {
}\n`)

await expect(fs.readFile(path.join(tempDir, 'plugins', 'curlmd.ts'), 'utf8')).resolves.toBe(
[
'// @i-know-the-amp-plugin-api-is-wip-and-very-experimental-right-now',
"import plugin from '@curl.md/amp'",
'',
'export default plugin',
'',
].join('\n'),
["import plugin from '@curl.md/amp'", '', 'export default plugin', ''].join('\n'),
)
})

Expand Down Expand Up @@ -84,15 +78,9 @@ test('runs when invoked through a symlinked bin path', async () => {
expect(result.stderr).toBe('')
expect(result.stdout).toContain(`Installed @curl.md/amp@`)
expect(result.stdout).toContain(` to ${ampConfigDir}`)
expect(result.stdout).toContain("Run 'PLUGINS=all amp' to load plugins")
expect(result.stdout).toContain("Run 'amp' to load plugins")

await expect(fs.readFile(path.join(ampConfigDir, 'plugins', 'curlmd.ts'), 'utf8')).resolves.toBe(
[
'// @i-know-the-amp-plugin-api-is-wip-and-very-experimental-right-now',
"import plugin from '@curl.md/amp'",
'',
'export default plugin',
'',
].join('\n'),
["import plugin from '@curl.md/amp'", '', 'export default plugin', ''].join('\n'),
)
})
10 changes: 2 additions & 8 deletions plugins/amp/src/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ if (isMain) {
const result = await installAmpPlugin()
console.log(`Installed ${result.packageSpec} to ${result.ampConfigDir}`)
console.log(`Plugin shim: ${result.shimPath}`)
console.log("Run 'PLUGINS=all amp' to load plugins")
console.log("Run 'amp' to load plugins")
console.log('If auth is needed, set `CURLMD_API_KEY` or run `curl.md auth login`.')
} catch (error) {
console.error(`error: ${error instanceof Error ? error.message : String(error)}`)
Expand Down Expand Up @@ -127,13 +127,7 @@ export async function installAmpPlugin(
await fs.mkdir(path.dirname(shimPath), { recursive: true })
await fs.writeFile(
shimPath,
[
'// @i-know-the-amp-plugin-api-is-wip-and-very-experimental-right-now',
"import plugin from '@curl.md/amp'",
'',
'export default plugin',
'',
].join('\n'),
["import plugin from '@curl.md/amp'", '', 'export default plugin', ''].join('\n'),
'utf8',
)

Expand Down
43 changes: 25 additions & 18 deletions plugins/amp/src/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,31 @@ afterEach(() => {
test('registers tool hooks and fallback tool', () => {
const { handlers, tools } = loadPlugin()

expect(handlers.map((handler) => handler.event)).toEqual(['tool.call', 'tool.result'])
expect(handlers.map((handler) => handler.event)).toEqual([
'agent.start',
'tool.call',
'tool.result',
])
expect(tools.map((t) => t.name)).toEqual(['curl_md'])
})

test('agent.start hook steers URL reads to curl_md', () => {
const { handlers } = loadPlugin()
const handler = handlers.find((h) => h.event === 'agent.start')!

const result = handler.fn(
{ id: 'msg_1', message: 'Read https://example.com', thread: { id: 'T-test' } },
{} as any,
)

expect(result).toEqual({
message: {
content:
'For web page or URL reads, use the curl_md tool instead of read_web_page. curl_md returns optimized markdown and supports url, objective, keywords, mode, and fresh inputs.',
},
})
})

test('tool.call hook allows non-read_web_page tools', async () => {
const { handlers } = loadPlugin()
const handler = handlers.find((h) => h.event === 'tool.call')!
Expand Down Expand Up @@ -202,7 +223,7 @@ test('preserves URL fragments', async () => {
} as any)

expect(requests[0]?.url).toContain('anchor=section')
expect((result as any).url).toBe('https://example.com/docs?q=1#section')
expect(result).toBe('# Fragment')
})

// --- Anonymous fetch ---
Expand Down Expand Up @@ -244,20 +265,7 @@ test('fetches anonymously and returns expected shape', async () => {

expect(requests[0]?.url).toContain(`${defaultBaseUrl}/api/https://example.com/docs`)
expect(requests[0]?.url).toContain('anchor=intro')
expect(result).toEqual({
auth: 'anon',
cache: 'HIT',
credits_remaining: 42,
fresh: true,
keywords: ['a'],
markdown: '# Example\n\n---\n\nPowered by [curl.md](https://curl.md)',
mode: 'rush',
objective: 'test',
request_id: 'req_abc',
tokens_count: 100,
tokens_saved: 50,
url: 'https://example.com/docs#intro',
})
expect(result).toBe('# Example\n\n---\n\nPowered by [curl.md](https://curl.md)')
})

// --- API key auth ---
Expand Down Expand Up @@ -411,8 +419,7 @@ test('retries once on session 401 with forced auth refresh', async () => {
'Bearer access-token-stale',
'Bearer access-token-fresh',
])
expect((result as any).auth).toBe('session')
expect((result as any).markdown).toBe('# Retried')
expect(result).toBe('# Retried')

Session.delete()
})
Expand Down
13 changes: 11 additions & 2 deletions plugins/amp/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @i-know-the-amp-plugin-api-is-wip-and-very-experimental-right-now
import type { PluginAPI } from '@ampcode/plugin'
import { createClient, defaultBaseUrl } from 'curl.md'
import { Auth, Session } from 'curl.md/internal'
Expand All @@ -10,6 +9,15 @@ export default function (amp: PluginAPI) {
const apiKey = process.env.CURLMD_API_KEY
const resolver = Auth.createResolver(baseUrl, apiKey)

amp.on('agent.start', () => {
return {
message: {
content:
'For web page or URL reads, use the curl_md tool instead of read_web_page. curl_md returns optimized markdown and supports url, objective, keywords, mode, and fresh inputs.',
},
}
})

amp.on('tool.call', async (event, ctx) => {
if (event.tool !== 'read_web_page') return { action: 'allow' }
ctx.logger.log(`curl.md intercepting read_web_page: ${String(event.input.url)}`)
Expand Down Expand Up @@ -74,13 +82,14 @@ export default function (amp: PluginAPI) {
required: ['url'],
},
async execute(input) {
return fetchPage({
const result = await fetchPage({
fresh: input.fresh as boolean | undefined,
keywords: input.keywords as string[] | undefined,
mode: input.mode as 'rush' | 'smart' | undefined,
objective: input.objective as string | undefined,
url: input.url as string,
})
return result.markdown
},
})

Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion scripts/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ function getProviders() {
AI_AGENT: process.env.AI_AGENT || 'amp',
CURLMD_BASE_URL: process.env.CURLMD_BASE_URL || 'https://curl.local',
NODE_TLS_REJECT_UNAUTHORIZED: process.env.NODE_TLS_REJECT_UNAUTHORIZED || '0',
PLUGINS: process.env.PLUGINS || 'all',
},
}
},
Expand Down
Loading