Integration toolkit

Build workflow and agent integration apps with @lunnoa/toolkit.

@lunnoa/toolkit is the TypeScript SDK for defining Lunnoa Automate integration apps: the actions, triggers, connections, and input forms that appear in workflows and AI agents.

Use it when you are building a publishable app package (for example @lunnoa-automate/app-http) that a Lunnoa server installs at runtime, or a private integration for your organisation.

The toolkit provides factories and types only. Your app's run handlers execute inside the Lunnoa Automate server, which supplies runtime services (database, HTTP, OAuth, logging, and more).

Requirements

  • Node.js 20 or newer
  • A Lunnoa Automate deployment to install and test the app against
  • TypeScript recommended (the factories are typed)

Install

For a local experiment:

install.sh
npm install @lunnoa/toolkit

For publishable app packages, declare the toolkit as a peer dependency. Do not bundle it. The host server resolves @lunnoa/toolkit from its own node_modules at runtime.

package.json
{  "peerDependencies": {    "@lunnoa/toolkit": "^1.0.0"  }}

Quickstart

A minimal app with one action. Export the createApp(...) result as your package default export. The platform loads installable apps with require() and registers them in the workflow app catalogue.

index.ts
import {  createApp,  createAction,  createTextInputField,} from '@lunnoa/toolkit';import { z } from 'zod/v3';const greet = createAction({  id: 'hello_action_greet',  name: 'Greet',  description: 'Return a greeting for the given name.',  inputConfig: [    createTextInputField({      id: 'name',      label: 'Name',      required: {        missingMessage: 'Name is required',        missingStatus: 'warning',      },    }),  ],  aiSchema: z.object({    name: z.string().describe('Person to greet'),  }),  needsConnection: false,  async run({ configValue }) {    return { message: `Hello, ${configValue.name}!` };  },  async mockRun({ configValue }) {    return { message: `Hello, ${configValue.name}!` };  },});export default createApp({  id: 'hello',  name: 'Hello',  description: 'A minimal example integration app.',  logoUrl: 'https://example.com/logo.svg',  actions: [greet],  triggers: [],  connections: [],});

Recommended folder structure

Mirror the layout used by built-in apps (for example Slack and HTTP). Keep one file per action, trigger, and connection; put shared helpers in shared/; wire everything in a single app entry file.

Source layout

folder-structure.txt
my-service/  package.json              # peerDependency on @lunnoa/toolkit + lunnoaApp  tsconfig.json  src/    index.ts                # export default createApp(...)    my-service.app.ts       # optional thin re-export of createApp    actions/      send-message.action.ts      list-channels.action.ts    triggers/      new-message.trigger.ts    connections/      oauth2.connection.ts    shared/      client.ts             # HTTP client, auth helpers, constants      types.ts  dist/                     # compiled output pointed to by package.json main

Naming conventions

PathConventionExample
App entry<app-id>.app.ts or index.ts that calls createAppslack.app.ts
Action file<kebab-name>.action.tssend-message.action.ts
Trigger file<kebab-name>.trigger.tsnew-message.trigger.ts
Connection file<type>.connection.ts or <provider>.oauth2.tsoauth2.connection.ts
Shared codeshared/<topic>.tsshared/client.ts

Practices that scale

  • One export per file. Each action/trigger/connection file exports a single factory result; the app entry imports and registers them.
  • No business logic in the app entry. createApp should only set metadata and arrays.
  • Put API clients in shared/. Reuse them from run / mockRun instead of duplicating fetch logic.
  • Keep ids aligned with folders. File send-message.action.ts pairs with id my-service_action_send-message.
  • Colocate tests next to shared helpers when you need them (shared/ssrf-guards.spec.ts), not next to every action unless the case is action-specific.
  • Compile to dist/ and point package.json main at the compiled default export. See Packaging.

A tiny app can start as a single index.ts. Split into actions/, triggers/, and connections/ as soon as you add a second action or any shared client code.

Core concepts

PieceFactoryRole
AppcreateAppTop-level definition: metadata, actions, triggers, connections
ActioncreateActionOne operation in a workflow step or agent tool call
TriggercreateManualTrigger, poll/webhook factories, …Starts or schedules workflow runs
ConnectioncreateApiKeyConnection, OAuth helpers, …Auth config for third-party APIs
Input configcreateTextInputField, createSelectInputField, …Form fields shared by actions, triggers, and connections

ID conventions

IDs are stable registry keys. Follow these formats exactly:

KindFormatExample
Appkebab-case slughello, slack, http
Action<app-id>_action_<action-name>hello_action_greet
Trigger<app-id>_trigger_<trigger-name>flow-control_trigger_manually-run
Connection<app-id>_connection_<connection-name>slack_connection_oauth2

Guides in this section

GuideWhat you will do
ActionsDefine createAction with run, mockRun, and aiSchema
Input configBuild configuration forms with field factories
TriggersStart workflows with manual, schedule, poll, or webhook triggers
ConnectionsAdd API key, basic auth, OAuth, and other auth methods
PackagingShip a publishable npm app with the lunnoaApp manifest

Related