npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aimform/apps

v0.1.2

Published

Aimform App Store SDK — define, validate, and publish apps for the Aimform platform

Readme

@aimform/apps

The official SDK for building, validating, and publishing apps on the Aimform platform.

@aimform/apps gives third-party developers everything they need to define app manifests, entity types, tools, semantic Capability implementations, onboarding flows, and profile sections — all with full TypeScript support and runtime validation via Zod.

The package also owns the pure platform-ingestion boundary. AppManifestInput and validateAppManifestInput() preserve the permissive shape accepted by the platform upload route, while projectAppManifest() converts scopes, tools, and entity declarations, and Function-level semantic declarations into provider capabilities, schema projections, and CapabilityImplementation records. The API persists those projections but does not redefine their semantics.

Authentication may be oauth2, api_key, basic, bearer, mTLS, custom, or none. Authentication configuration describes the connection UI and provider protocol only; credential material is supplied when a Connection is created and is stored behind an opaque Vault handle, never in the manifest.

Aimform-configured Apps can declare connectionOptions to expose a self-service BYOD path while keeping the managed Aimform Connection as the default. BYOD fields are Form Intake metadata only: every field is marked secret, values are validated at connection time, and the resulting material is stored in Vault. manualSteps documents provider actions such as creating an API application or adding an egress allowlist. The catalog and chat setup surface expose the billing mode and these instructions, but never credential values.

connectionOptions: {
  defaultMode: "managed",
  managed: { enabled: true, billingLabel: "Billed through Aimform" },
  byod: {
    enabled: true,
    credentialFields: [
      { key: "apiKey", label: "API key", inputType: "password", secret: true, required: true },
    ],
    manualSteps: [{
      id: "create-key",
      title: "Create a restricted key",
      description: "Create only the provider permissions this App needs.",
    }],
  },
},

Connection credentials are represented by ConnectionCredentialHandle and ConnectionCapabilityGrant metadata. Functions and Workflows must receive explicit capability grants rather than inheriting every capability of a Connection.

The public catalog includes provider-specific DataImpulse, Decodo, IPRoyal, and Webshare Apps. Each uses the shared browser.egress.residential capability and exposes managed/BYOD connection metadata. Browser Use and other Apps/Tools consume the provider-neutral capability rather than coupling to a vendor. BYOD proxy endpoint, username, password, and optional rotation fields are stored behind Vault; the App catalog and connection health API only return safe status and billing metadata.

ManagedProviderProfile is host-owned account configuration used to power a managed Connection. Its secretRef is an opaque vault:// reference and is never included in customer-facing Connection or runtime responses.

Apps declare executable Functions and their required capabilities. A selected Function may be projected to AI with exposedToAi; the Workflow Specification and its durable coordination nodes remain an @aimform/workflows concern.

Use findAppEvent() and findAppWebhook() when an App runtime needs to map a provider callback to a declared normalized event. The API verifies the Connection-scoped signature and emits the resulting event through @aimform/workflows.

App Manifest v2

Manifest v2 packages the complete public integration contract in one App:

const app = defineApp({
  // identity, entryPoint, auth, entity types, scopes, and onboarding...
  id: "warehouse",
  name: "Warehouse",
  version: "1.0.0",
  avatar: "https://example.com/warehouse.png",
  description: "Warehouse operations",
  category: "operations",
  entryPoint: { type: "external_call", url: "https://warehouse.example.com/functions" },
  auth: { type: "api_key", name: "X-Warehouse-Key" },
  declaresEntityTypes: [{ name: "shipment", properties: [{ name: "id", type: "string" }] }],
  scopes: { read: ["shipment"] },
  functions: [
    {
      name: "getShipment",
      description: "Read a shipment",
      inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
      outputSchema: { type: "object" },
      requiredCapabilities: ["shipment.read"],
      implements: [{ capability: "aimform://capabilities/logistics.shipment.get@^1" }],
      requires: [{ capability: "aimform://capabilities/logistics.shipment.lookup@^1" }],
      riskTier: "auto",
      exposedToAi: true,
      idempotent: true,
    },
  ],
  events: [{ name: "warehouse.shipment.updated" }],
  webhooks: [{ id: "shipments", path: "/webhooks/shipments", events: ["warehouse.shipment.updated"] }],
  egress: { allowlist: ["warehouse.example.com"] },
  rateLimits: { requests: 60, windowSeconds: 60, keyBy: "connection" },
  onboarding: { steps: [{ id: "connect", type: "api_key_form", title: "Connect Warehouse" }] },
});

Functions are the canonical executable App operation. A Function may declare semantic implements and requires entries: the App owns the resulting implementation metadata, while the referenced Capability contract remains owned by its Capability catalog. Aimform projects only Functions with exposedToAi: true into the compatibility AI-tool registry. When implements is present, that semantic contract is also the Function's catalog identity; the legacy function:${name} capability is not projected. Name-shaped capabilities remain only for older manifests that have no semantic implementation declaration. Implementation declarations may include deterministic input/output mappings ({ version, rules }) from @aimform/capabilities; hosted conformance runs evaluate those mappings against canonical fixtures without serializing code. Exact versioned Capability URIs are retained as capabilityVersion metadata so compatible Journey requests such as ^1 resolve correctly. Connection-scoped invocation validates the Function input schema, checks the explicit Connection grant, resolves credentials inside Vault, enforces egress and rate-limit policy, and returns normalized upstream errors without exposing credential material.

Sandboxed generated Apps

entryPoint.type === "sandboxed_code" carries immutable sandboxedCode metadata: a SHA-256 source digest, optional artifact reference, language, entry function, and egress policy. The API dispatches source-backed candidates to the dedicated Compute Worker, where code runs with default-deny egress, bounded input/output, timeout, and Guard admission. Artifact-backed execution remains fail-closed until the blob/package runtime is enabled.

The generation seam creates private candidates, runs bounded tests, and repairs by producing new immutable versions. Candidate test success is not a substitute for Capability Verification or binding; those remain explicit control-plane gates.

Public catalog Apps

The package ships the first reusable public App contracts through the same manifest and publishing path used by third-party Apps:

import {
  PUBLIC_AIMFORM_APP_MANIFESTS,
  SCALEFORCE_WEBSHOP_APP_MANIFEST,
} from "@aimform/apps/public";

PUBLIC_AIMFORM_APP_MANIFESTS.map((app) => app.id);
// ["cloudflare", "dataimpulse-residential-proxy", "decodo-residential-proxy", "iproyal-residential-proxy", "webshare-residential-proxy", "gocardless", "otp-bank", "aliexpress", "resend"]

SCALEFORCE_WEBSHOP_APP_MANIFEST.id;
// "scaleforce-webshop" — private Organisation App

The public manifests declare canonical Functions, entity types, events, connection authentication, egress, rate limits, and health checks. They do not contain API keys, OAuth tokens, certificates, or other credential material. The API projects them into the published catalog idempotently; an Organisation can discover and install them through the normal App connection flow.

The package also owns the portable provider clients used by the API runtime. They accept an injected fetch implementation and never persist secrets:

import { GoCardlessProvider } from "@aimform/apps";

const client = new GoCardlessProvider({ fetchFn });
const tokens = await client.createAccessToken({ secretId, secretKey });
const institutions = await client.listInstitutions(tokens.accessToken, "GB");

GoCardless uses the server-side token → end-user agreement → requisition redirect flow. AliExpress uses seller OAuth and signed Open Platform requests. The API keeps deployment credentials in Worker secrets and stores each Organisation's resulting connection tokens behind a Vault handle.

AliExpressCommerceProvider adapts the AliExpress App Function surface to the generic CommerceProvider port from @aimform/capabilities. It maps product, freight, supplier-order, and shipment responses into the shared contract shape while retaining the managed Connection as the only credential boundary. A Procurement Skill can therefore replace AliExpress with another App through a Capability Binding without changing its policy or orchestration source.

GoCardless authorizations are independent Connections, so one Organisation can connect multiple banks. Its account, balance, transaction, detail, and refresh Functions are invoked through the selected Connection and cannot read another Connection's accounts.

After a consent is completed, GoCardless may take a short time to prepare the account snapshot. The provider client polls the account resource and retries a 409 readiness response a bounded number of times; rate-limit responses are returned without retrying so a provider quota is not consumed repeatedly.

Transaction reads without an explicit date filter request the full 90-day consent window (date_from and date_to), matching the reliable ingestion flow used by Spendbook instead of relying on a bank-specific default window.

Use runManifestConformance() for package-only checks. The API upload and publication path also runs the manifest scan; the hosted App conformance test covers migration, public discovery, cross-Organisation installation, Function execution, disconnect/reconnect, capability grants, and rate limiting.

Installation

npm install @aimform/apps
# or
pnpm add @aimform/apps

Quick Start

Use defineApp() to create a complete app manifest:

import { defineApp } from "@aimform/apps";

const myApp = defineApp({
  id: "my-slack-bot",
  name: "Slack Bot",
  version: "1.0.0",
  avatar: "https://example.com/slack-icon.png",
  description: "Bring Slack messages into Aimform.",
  category: "communication",
  entryPoint: {
    type: "mcp",
    url: "https://slack-bot.example.com/mcp",
  },
  auth: {
    type: "oauth2",
    provider: "slack",
  },
  declaresEntityTypes: [
    {
      name: "slack_message",
      properties: [
        { name: "text", type: "text", label: "Message", required: true },
        { name: "channel", type: "string", label: "Channel" },
        { name: "sent_at", type: "date", label: "Sent" },
      ],
    },
  ],
  scopes: {
    read: ["slack_message"],
    write: ["slack_message"],
  },
  tools: [
    {
      name: "send_slack_message",
      description: "Send a message to a Slack channel",
      riskTier: "confirm",
      exposedToAi: true,
      inputSchema: {
        type: "object",
        properties: {
          channel: { type: "string" },
          text: { type: "string" },
        },
        required: ["channel", "text"],
      },
    },
  ],
  onboarding: {
    steps: [
      {
        id: "connect-slack",
        type: "oauth_connect",
        title: "Connect your Slack workspace",
        provider: "slack",
      },
    ],
  },
  profile: {
    sections: [
      {
        id: "recent-messages",
        type: "view",
        title: "Recent Messages",
        entityType: "slack_message",
        fields: ["text", "channel", "sent_at"],
        defaultSort: "sent_at",
      },
    ],
  },
});

Entity Types

Entity types define the data model your app brings into Aimform. Use the fluent builder or inline array syntax:

import { defineEntityType } from "@aimform/apps";

// Fluent builder
const taskEntity = defineEntityType("task")
  .addProperty("title", "string", { label: "Title", required: true, searchable: true })
  .addProperty("status", "string", { label: "Status" })
  .addProperty("due_date", "date", { label: "Due Date" })
  .build();

// Inline array
const noteEntity = defineEntityType("note", [
  { name: "title", type: "string", label: "Title", required: true },
  { name: "content", type: "text", label: "Content", searchable: true },
  { name: "created_at", type: "date", label: "Created" },
]);

Tools

Tools are actions exposed by your app that users and the AI assistant can invoke:

import { defineTool } from "@aimform/apps";

// Safe read-only tool (auto-executable)
const searchTasks = defineTool({
  name: "search_tasks",
  description: "Search tasks by title or status",
  riskTier: "auto",
  exposedToAi: true,
  inputSchema: {
    type: "object",
    properties: {
      query: { type: "string", description: "Search query" },
    },
    required: ["query"],
  },
});

// Write operation (requires user confirmation)
const createTask = defineTool({
  name: "create_task",
  description: "Create a new task",
  riskTier: "confirm",
  exposedToAi: true,
  inputSchema: {
    type: "object",
    properties: {
      title: { type: "string", description: "Task title" },
      dueDate: { type: "string", description: "Due date (ISO 8601)" },
    },
    required: ["title"],
  },
});

// Blocked tool (requires explicit user action)
const deleteTask = defineTool({
  name: "delete_task",
  description: "Permanently delete a task",
  riskTier: "blocked",
  exposedToAi: false,
  inputSchema: {
    type: "object",
    properties: {
      taskId: { type: "string", description: "Task ID" },
    },
    required: ["taskId"],
  },
});

Risk Tiers

| Tier | Behavior | |------|----------| | auto | Executes automatically without user confirmation. Use for safe read-only operations. | | confirm | Requires user confirmation before execution. Use for write/delete operations. | | blocked | Never auto-executes. Requires explicit user trigger. Use for dangerous or irreversible actions. |

Onboarding Steps

Onboarding steps guide users through connecting your app:

import { defineOnboardingStep } from "@aimform/apps";

// OAuth2 connection
const connectStep = defineOnboardingStep({
  id: "connect",
  type: "oauth_connect",
  title: "Connect your account",
  description: "Authorize access to your data.",
  provider: "google",
});

// API key entry
const apiKeyStep = defineOnboardingStep({
  id: "api-key",
  type: "api_key_form",
  title: "Enter your API key",
  description: "Find your API key in your account settings.",
  apiKeyLabel: "API Key",
  apiKeyHelpText: "Paste the API key from your provider dashboard.",
});

// Permission review
const reviewStep = defineOnboardingStep({
  id: "review",
  type: "permission_review",
  title: "Review permissions",
  description: "This app will be able to read your emails.",
});

// Custom setup
const customStep = defineOnboardingStep({
  id: "setup",
  type: "custom",
  title: "Configure your workspace",
  customUrl: "https://my-app.example.com/setup",
});

Profile Sections

Profile sections display your app's data in Aimform entity detail views:

import { defineProfileSection } from "@aimform/apps";

// Table/list view
const listView = defineProfileSection({
  id: "recent-items",
  type: "view",
  title: "Recent Items",
  entityType: "task",
  fields: ["title", "status", "due_date"],
  defaultSort: "due_date",
});

// Document view (rich content)
const docView = defineProfileSection({
  id: "note-content",
  type: "document",
  title: "Note",
  entityType: "note",
  documentType: "text/markdown",
});

// Freeform board
const board = defineProfileSection({
  id: "kanban",
  type: "space",
  title: "Project Board",
  entityType: "task",
});

Full Gmail App Example

A complete Gmail app manifest using all the SDK features:

import {
  defineApp,
  defineEntityType,
  defineTool,
  defineOnboardingStep,
  defineProfileSection,
  defineScopes,
} from "@aimform/apps";

const gmail = defineApp({
  id: "gmail",
  name: "Gmail",
  version: "1.0.0",
  avatar: "https://example.com/gmail-icon.png",
  description: "Read, search, and send emails from Gmail.",
  category: "communication",
  entryPoint: {
    type: "mcp",
    url: "https://gmail-app.example.com/mcp",
  },
  auth: {
    type: "oauth2",
    provider: "google",
  },
  declaresEntityTypes: [
    defineEntityType("email_thread", [
      { name: "subject", type: "string", label: "Subject", required: true, searchable: true },
      { name: "snippet", type: "text", label: "Snippet", searchable: true },
      { name: "sender", type: "email", label: "Sender" },
      { name: "recipients", type: "json", label: "Recipients" },
      { name: "received_at", type: "date", label: "Received" },
      { name: "is_read", type: "boolean", label: "Read" },
      { name: "labels", type: "json", label: "Labels" },
    ]) as ReturnType<typeof defineEntityType>,
  ],
  scopes: defineScopes(["email_thread"], ["email_thread"]),
  tools: [
    defineTool({
      name: "search_emails",
      description: "Search Gmail messages by query",
      riskTier: "auto",
      exposedToAi: true,
      inputSchema: {
        type: "object",
        properties: {
          query: { type: "string", description: "Gmail search query (supports Gmail search operators)" },
          maxResults: { type: "number", description: "Maximum results to return", default: 10 },
        },
        required: ["query"],
      },
    }),
    defineTool({
      name: "get_email",
      description: "Get full content of a specific email",
      riskTier: "auto",
      exposedToAi: true,
      inputSchema: {
        type: "object",
        properties: {
          emailId: { type: "string", description: "Email message ID" },
        },
        required: ["emailId"],
      },
    }),
    defineTool({
      name: "send_email",
      description: "Send an email via Gmail",
      riskTier: "confirm",
      exposedToAi: true,
      inputSchema: {
        type: "object",
        properties: {
          to: { type: "string", description: "Recipient email address(es)" },
          cc: { type: "string", description: "CC recipients" },
          subject: { type: "string", description: "Email subject" },
          body: { type: "string", description: "Email body (plain text or HTML)" },
        },
        required: ["to", "subject", "body"],
      },
    }),
    defineTool({
      name: "delete_email",
      description: "Delete an email (moves to trash)",
      riskTier: "confirm",
      exposedToAi: true,
      inputSchema: {
        type: "object",
        properties: {
          emailId: { type: "string", description: "Email message ID" },
        },
        required: ["emailId"],
      },
    }),
  ],
  onboarding: {
    steps: [
      defineOnboardingStep({
        id: "connect-gmail",
        type: "oauth_connect",
        title: "Connect your Gmail account",
        description: "We'll redirect you to Google to authorize access to your Gmail.",
        provider: "google",
      }),
      defineOnboardingStep({
        id: "review-permissions",
        type: "permission_review",
        title: "Review permissions",
        description: "This app needs access to read, search, and send emails from your Gmail account.",
      }),
    ],
  },
  profile: {
    sections: [
      defineProfileSection({
        id: "recent-emails",
        type: "view",
        title: "Recent Emails",
        entityType: "email_thread",
        fields: ["subject", "snippet", "sender", "received_at", "is_read"],
        defaultSort: "received_at",
      }),
      defineProfileSection({
        id: "email-content",
        type: "document",
        title: "Email Body",
        entityType: "email_thread",
        documentType: "text/html",
      }),
    ],
  },
});

Validation

The SDK includes runtime validation via Zod schemas, accessible from the server entry point:

import { validateManifest, scanApp } from "@aimform/apps/server";

// Validate a manifest loaded from JSON
const rawManifest = JSON.parse(
  await Deno.readTextFile("aimform.manifest.json"),
);
const result = validateManifest(rawManifest);

if (!result.success) {
  console.error("Manifest is invalid:");
  for (const error of result.errors) {
    console.error(`  - ${error}`);
  }
  process.exit(1);
}

// Deep scan for warnings and blocking issues
const report = scanApp(result.data);

if (!report.passed) {
  console.error("Scan found blocking issues:");
  for (const error of report.errors) {
    console.error(`  - ${error}`);
  }
}

if (report.warnings.length > 0) {
  console.warn("Warnings (non-blocking):");
  for (const warning of report.warnings) {
    console.warn(`  - ${warning}`);
  }
}

API Reference

Main Entry (@aimform/apps)

| Export | Description | |--------|-------------| | defineApp(manifest) | Create a complete app manifest | | defineEntityType(name, properties?) | Define an entity type (returns builder or declaration) | | defineTool(config) | Define a tool/action | | defineOnboardingStep(step) | Define an onboarding step | | defineProfileSection(section) | Define a profile section | | defineScopes(read, write?) | Define scoped permissions | | OAUTH2_PROVIDERS | Constants for known OAuth2 providers | | InferAppManifest<T> | Infer the type of a specific manifest |

Server Entry (@aimform/apps/server)

| Export | Description | |--------|-------------| | validateManifest(input) | Validate unknown input against the manifest schema | | scanApp(manifest) | Deep scan a manifest for warnings and errors | | aimformAppManifestSchema | Raw Zod schema for custom validation logic |

Client Entry (@aimform/apps/client)

Type-only exports for browser-safe imports. All manifest types are re-exported: AimformAppManifest, ToolDeclaration, EntityTypeDeclaration, AppScopes, etc.

Types

AimformAppManifest

The top-level manifest type. See the Quick Start example for full shape.

EntityTypeDeclaration

interface EntityTypeDeclaration {
  name: string;
  properties: PropertyDefinition[];
}

ToolDeclaration

interface ToolDeclaration {
  name: string;
  description?: string;
  riskTier: "auto" | "confirm" | "blocked";
  exposedToAi: boolean;
  inputSchema?: Record<string, unknown>;
}

OnboardingStep

interface OnboardingStep {
  id: string;
  type: "oauth_connect" | "api_key_form" | "permission_review" | "custom";
  title: string;
  description?: string;
  provider?: string;
  apiKeyLabel?: string;
  apiKeyHelpText?: string;
  customUrl?: string;
}

ProfileSection

interface ProfileSection {
  id: string;
  type: "view" | "document" | "space";
  title: string;
  entityType?: string;
  fields?: string[];
  defaultSort?: string;
  documentType?: string;
}

AuthConfig

interface AuthConfig {
  type: "oauth2" | "api_key" | "none";
  provider?: string;
}

EntryPointConfig

interface EntryPointConfig {
  type: "mcp" | "sandboxed_code" | "external_call";
  url?: string;
}

License

MIT

Provider-backed connection functions accept { guard } in their provider options. Production hosts must supply the connection-scoped binding so request and retry budgets are reserved before GoCardless or AliExpress is called.