Developer Preview: This quickstart shows the planned authoring flow. Package names and test helpers may change before the runtime ships.
What you will build
A one-file plugin that:
- declares one provider
- runs
setup - waits for one email OTP
- exposes one read-only skill through the authenticated browser session
- runs in the local test harness
Install
npm init -y
npm install @atomic/plugin-preview zod
npm install -D typescript vitestUse this package.json shape:
{
"name": "atomic-plugin-sample-market",
"version": "0.1.0",
"type": "module",
"private": true,
"scripts": {
"test": "vitest run"
}
}Plugin in 100 lines
Create src/plugin.ts.
import type { SetupHandler, SkillHandler } from '@atomic/plugin-preview';
export const manifest = {
id: 'sample-market',
name: 'Sample Market',
version: '0.1.0',
runtimeRange: '^0.1',
capabilities: {
browser: true,
otp: { email: true },
state: true,
logs: true,
http: { egressDomains: ['api.sample.example'] },
},
email: {
allowedSenderDomains: ['sample.example', 'mail.sample.example'],
},
setup: { required: true },
skills: [
{
name: 'check_order',
description: 'Check an order status.',
mutating: false,
timeoutMs: 30_000,
maxConcurrency: 4,
inputSchema: {
type: 'object',
properties: {
orderRef: { type: 'string' },
},
required: ['orderRef'],
additionalProperties: false,
},
},
],
} as const;
export const setup: SetupHandler = async ({ agent, browser, otp, state, log }) => {
await browser.navigate('https://sample.example/signup');
await browser.act(`Create an account for ${agent.email}.`);
const code = await otp.waitForEmailCode({
service: 'sample-market',
timeoutMs: 120_000,
});
await browser.act(`Enter verification code ${code}.`);
await state.merge('setup', {
stage: 'active',
updatedAt: new Date().toISOString(),
});
log.info('Prepared Sample Market account');
return { status: 'active' };
};
type CheckOrderInput = {
orderRef: string;
};
export const checkOrder: SkillHandler<CheckOrderInput> = async ({
input,
browser,
state,
log,
}) => {
const setupState = await state.get<{ stage?: string }>('setup');
if (setupState?.stage !== 'active') {
return {
ok: false,
errorCode: 'setup_required',
message: 'Setup has not completed for this provider.',
retryable: false,
};
}
const response = await browser.sessionFetch('https://api.sample.example/orders/status', {
method: 'POST',
json: { orderRef: input.orderRef },
});
const status = await response.json();
log.info('Checked order status', { orderRef: input.orderRef });
return { ok: true, data: { status } };
};Test it
Create test/plugin.test.ts.
import { describe, expect, it } from 'vitest';
import { createPluginTestHarness } from '@atomic/plugin-preview/testing';
import { checkOrder, manifest, setup } from '../src/plugin';
describe('sample-market plugin', () => {
it('runs setup and a read-only skill', async () => {
const harness = createPluginTestHarness({
manifest,
handlers: {
setup,
skills: { check_order: checkOrder },
},
agent: {
email: 'agent@demo.atomic.bond',
phone: '+15555550100',
},
browser: {
actions: [
{ ok: true, summary: 'Account form submitted' },
{ ok: true, summary: 'Verification code submitted' },
],
},
otp: {
email: {
'sample-market': ['123456'],
},
},
http: {
'https://api.sample.example/orders/status': {
status: 200,
json: { status: 'delivered' },
},
},
});
await harness.validateManifest(manifest);
await expect(harness.runSetup({})).resolves.toMatchObject({
status: 'active',
});
await expect(
harness.runSkill('check_order', { orderRef: 'SM-100' })
).resolves.toMatchObject({
ok: true,
data: { status: { status: 'delivered' } },
});
});
});Run:
npm testWhat to read next
Read Build a Plugin when this passes. Use Reference when you need exact types.