SDK & aip CLI reference
The @arw/ai-plugin package — types, manifest/argument validation, the devRun local runner, and the aip command-line tool.
@arw/ai-plugin is the authoring package for Connect+ AI-sidebar plugins: the
TypeScript types for the manifest and the capability api, the manifest and
argument validators, a local dev runner, and the aip command-line tool. Its
validators are the exact checks the desktop runs — a parity test keeps the two
in lockstep, so "valid here" means "valid in the app".
New to plugins? Start with Create an AI plugin for the concepts; this page is the API reference.
Install
npm install --save-dev @arw/ai-pluginTypes
Import the types to get full typing on a tool and its api:
import type { Tool, PluginApi, PluginManifest } from '@arw/ai-plugin';
// Tool<Args> — an async (args, api) => value. Args is your parameter shape.
export const random_fact: Tool<{ maxLength?: number }> = async (args, api) => {
const res = await api.fetch(`https://catfact.ninja/fact?max_length=${args.maxLength ?? 140}`);
return (res.json() as { fact: string }).fact;
};| Type | What it is |
|---|---|
Tool<Args> | The signature of a tool export: (args: Args, api: PluginApi) => Promise<unknown>. |
PluginApi | The capability object a tool receives — fetch(url, init?) and secret(name). |
PluginManifest | The typed shape of plugin.json (id, name, version, entry, permissions, tools). |
Validation
import { validateManifest, validateArgs } from '@arw/ai-plugin';validateManifest(json)— the install-time check: id/entry rules, permission shapes (hostname allowlist,pagelevel,UPPER_SNAKEsecret names), and each tool's name + JSON-Schemaparameters. Returns the parsed manifest or throws with a precise message. Run it in CI to catch a bad manifest before you publish.validateArgs(schema, args)— validates a call's arguments against a tool'sparametersschema, exactly as the runtime does before your tool runs.
Local testing — devRun
Run a tool against a mock api (allowlisted fetch, declared-only secret,
argument validation) without launching the app:
import { devRun } from '@arw/ai-plugin';
import manifest from './my-plugin/plugin.json';
import * as mod from './my-plugin/index.js';
const out = await devRun(mod, manifest, 'random_fact', { maxLength: 80 }, {
// Optional. Omit `fetch` to use the real network (still host-allowlisted);
// supply it to stub responses in a unit test.
fetch: async (url) => ({ status: 200, headers: {}, body: '{"fact":"…"}' }),
secrets: { API_TOKEN: 'test-value' },
});devRun enforces the same guarantees as the desktop: a fetch to a host not in
permissions.network throws, and api.secret(name) throws unless name is in
permissions.secrets.
The aip CLI
aip validate ./my-plugin # check plugin.json + the entry module
aip dev ./my-plugin random_fact n=1 # run a tool against the mock apiaip validate <dir>— runsvalidateManifeston the folder and confirms the entry module exports a function for every declared tool.aip dev <dir> <tool> [key=value…]— invokes one tool. Arguments arekey=value, each JSON-parsed (n=1→ number,on=true→ boolean,s="hi"→ string). Supply secrets with--secret NAME=value. Uses the real network, still restricted to the manifest's host allowlist.
aip dev ./my-plugin notion_save db=Tasks --secret NOTION_TOKEN=secret_xxxInvoke without a global install via npx aip ….
Trust & publishing
A published plugin is a signed .aip package. Signed by the ARW root key →
first-party; signed by your author key → signed. The desktop re-verifies the
signature on every install and refuses a tampered, unsigned, or key-rotated package —
the store is only distribution, never the trust root. See
Create an AI plugin § Installing for the publish flow.