diff --git a/README.md b/README.md
index c15185d8..f89e7c9c 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started.
- **AI Gateway**: Generate Text, Generate Image
+- **Axiom**: Query Logs, Ingest Events, Create Annotation, List Datasets
- **Blob**: Put Blob, List Blobs
- **Firecrawl**: Scrape URL, Search Web
- **GitHub**: Create Issue, List Issues, Get Issue, Update Issue
diff --git a/plugins/axiom/credentials.ts b/plugins/axiom/credentials.ts
new file mode 100644
index 00000000..b68ef36e
--- /dev/null
+++ b/plugins/axiom/credentials.ts
@@ -0,0 +1,4 @@
+export type AxiomCredentials = {
+ AXIOM_TOKEN?: string;
+ AXIOM_ORG_ID?: string;
+};
diff --git a/plugins/axiom/icon.tsx b/plugins/axiom/icon.tsx
new file mode 100644
index 00000000..b90f9ab8
--- /dev/null
+++ b/plugins/axiom/icon.tsx
@@ -0,0 +1,14 @@
+export function AxiomIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/plugins/axiom/index.ts b/plugins/axiom/index.ts
new file mode 100644
index 00000000..98fda4e3
--- /dev/null
+++ b/plugins/axiom/index.ts
@@ -0,0 +1,201 @@
+import type { IntegrationPlugin } from "../registry";
+import { registerIntegration } from "../registry";
+import { AxiomIcon } from "./icon";
+
+const axiomPlugin: IntegrationPlugin = {
+ type: "axiom",
+ label: "Axiom",
+ description: "Query logs, ingest events, and create annotations in Axiom",
+
+ icon: AxiomIcon,
+
+ formFields: [
+ {
+ id: "token",
+ label: "API Token",
+ type: "password",
+ placeholder: "xaat-...",
+ configKey: "token",
+ envVar: "AXIOM_TOKEN",
+ helpText: "Get your API token from ",
+ helpLink: {
+ text: "app.axiom.co/settings/api-tokens",
+ url: "https://app.axiom.co/settings/api-tokens",
+ },
+ },
+ {
+ id: "orgId",
+ label: "Organization ID",
+ type: "text",
+ placeholder: "my-org-123",
+ configKey: "orgId",
+ envVar: "AXIOM_ORG_ID",
+ helpText: "Required for personal tokens. Find it in your org settings.",
+ },
+ ],
+
+ testConfig: {
+ getTestFunction: async () => {
+ const { testAxiom } = await import("./test");
+ return testAxiom;
+ },
+ },
+
+ actions: [
+ {
+ slug: "query-logs",
+ label: "Query Logs",
+ description: "Run an APL query against an Axiom dataset",
+ category: "Axiom",
+ stepFunction: "queryLogsStep",
+ stepImportPath: "query-logs",
+ outputFields: [
+ { field: "matches", description: "Array of matching log entries" },
+ { field: "count", description: "Number of results returned" },
+ { field: "status.elapsedTime", description: "Query execution time" },
+ ],
+ configFields: [
+ {
+ key: "dataset",
+ label: "Dataset",
+ type: "template-input",
+ placeholder: "my-dataset",
+ example: "vercel",
+ required: true,
+ },
+ {
+ key: "apl",
+ label: "APL Query",
+ type: "template-textarea",
+ placeholder:
+ "['my-dataset'] | where level == 'error' | limit 100",
+ example: "['vercel'] | where level == 'error' | limit 10",
+ rows: 4,
+ required: true,
+ },
+ {
+ key: "startTime",
+ label: "Start Time",
+ type: "template-input",
+ placeholder: "2024-01-01T00:00:00Z or -1h",
+ example: "-1h",
+ },
+ {
+ key: "endTime",
+ label: "End Time",
+ type: "template-input",
+ placeholder: "2024-01-01T23:59:59Z or now",
+ example: "now",
+ },
+ ],
+ },
+ {
+ slug: "ingest-events",
+ label: "Ingest Events",
+ description: "Send log events to an Axiom dataset",
+ category: "Axiom",
+ stepFunction: "ingestEventsStep",
+ stepImportPath: "ingest-events",
+ outputFields: [
+ { field: "ingested", description: "Number of events ingested" },
+ { field: "processedBytes", description: "Bytes processed" },
+ ],
+ configFields: [
+ {
+ key: "dataset",
+ label: "Dataset",
+ type: "template-input",
+ placeholder: "my-dataset",
+ example: "workflow-logs",
+ required: true,
+ },
+ {
+ key: "events",
+ label: "Events (JSON)",
+ type: "template-textarea",
+ placeholder:
+ '[{"level": "info", "message": "Hello"}] or {{NodeName.data}}',
+ example: '[{"level": "info", "message": "Workflow executed"}]',
+ rows: 6,
+ required: true,
+ },
+ ],
+ },
+ {
+ slug: "create-annotation",
+ label: "Create Annotation",
+ description:
+ "Create an annotation to mark deployments, incidents, or events",
+ category: "Axiom",
+ stepFunction: "createAnnotationStep",
+ stepImportPath: "create-annotation",
+ outputFields: [
+ { field: "id", description: "Annotation ID" },
+ { field: "time", description: "Annotation timestamp" },
+ ],
+ configFields: [
+ {
+ key: "datasets",
+ label: "Datasets",
+ type: "template-input",
+ placeholder: "dataset1,dataset2",
+ example: "vercel,api-logs",
+ required: true,
+ },
+ {
+ key: "type",
+ label: "Type",
+ type: "select",
+ options: [
+ { value: "deploy", label: "Deployment" },
+ { value: "incident", label: "Incident" },
+ { value: "config-change", label: "Config Change" },
+ { value: "alert", label: "Alert" },
+ { value: "other", label: "Other" },
+ ],
+ defaultValue: "deploy",
+ },
+ {
+ key: "title",
+ label: "Title",
+ type: "template-input",
+ placeholder: "Production deployment v1.2.3",
+ example: "Deployed v1.2.3",
+ required: true,
+ },
+ {
+ key: "description",
+ label: "Description",
+ type: "template-textarea",
+ placeholder: "Additional details about this annotation",
+ example: "Deployed new feature: user authentication",
+ rows: 3,
+ },
+ {
+ key: "url",
+ label: "URL",
+ type: "template-input",
+ placeholder: "https://github.com/org/repo/releases/tag/v1.2.3",
+ example: "https://github.com/myorg/myrepo/releases",
+ },
+ ],
+ },
+ {
+ slug: "list-datasets",
+ label: "List Datasets",
+ description: "Get all available datasets in your Axiom organization",
+ category: "Axiom",
+ stepFunction: "listDatasetsStep",
+ stepImportPath: "list-datasets",
+ outputFields: [
+ { field: "datasets", description: "Array of dataset objects" },
+ { field: "count", description: "Number of datasets" },
+ ],
+ configFields: [],
+ },
+ ],
+};
+
+registerIntegration(axiomPlugin);
+
+export default axiomPlugin;
diff --git a/plugins/axiom/steps/create-annotation.ts b/plugins/axiom/steps/create-annotation.ts
new file mode 100644
index 00000000..036aafc4
--- /dev/null
+++ b/plugins/axiom/steps/create-annotation.ts
@@ -0,0 +1,146 @@
+import "server-only";
+
+import { fetchCredentials } from "@/lib/credential-fetcher";
+import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
+import { getErrorMessage } from "@/lib/utils";
+import type { AxiomCredentials } from "../credentials";
+
+const AXIOM_API_URL = "https://api.axiom.co";
+
+type AxiomAnnotationResponse = {
+ id: string;
+ datasets: string[];
+ type: string;
+ title: string;
+ description?: string;
+ url?: string;
+ time: string;
+ endTime?: string;
+};
+
+type CreateAnnotationResult =
+ | {
+ success: true;
+ id: string;
+ time: string;
+ datasets: string[];
+ }
+ | { success: false; error: string };
+
+export type CreateAnnotationCoreInput = {
+ datasets: string;
+ type: string;
+ title: string;
+ description?: string;
+ url?: string;
+};
+
+export type CreateAnnotationInput = StepInput &
+ CreateAnnotationCoreInput & {
+ integrationId?: string;
+ };
+
+async function stepHandler(
+ input: CreateAnnotationCoreInput,
+ credentials: AxiomCredentials
+): Promise {
+ const token = credentials.AXIOM_TOKEN;
+
+ if (!token) {
+ return {
+ success: false,
+ error:
+ "AXIOM_TOKEN is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ try {
+ // Parse comma-separated datasets
+ const datasets = input.datasets
+ .split(",")
+ .map((d) => d.trim())
+ .filter(Boolean);
+
+ if (datasets.length === 0) {
+ return {
+ success: false,
+ error: "At least one dataset is required",
+ };
+ }
+
+ const headers: Record = {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ };
+
+ if (credentials.AXIOM_ORG_ID) {
+ headers["X-Axiom-Org-Id"] = credentials.AXIOM_ORG_ID;
+ }
+
+ const body: Record = {
+ datasets,
+ type: input.type || "deploy",
+ title: input.title,
+ time: new Date().toISOString(),
+ };
+
+ if (input.description) {
+ body.description = input.description;
+ }
+
+ if (input.url) {
+ body.url = input.url;
+ }
+
+ const response = await fetch(`${AXIOM_API_URL}/v2/annotations`, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ let errorMessage = `HTTP ${response.status}`;
+ try {
+ const errorJson = JSON.parse(errorText) as { message?: string };
+ errorMessage = errorJson.message || errorMessage;
+ } catch {
+ if (errorText) {
+ errorMessage = errorText;
+ }
+ }
+ return {
+ success: false,
+ error: `Failed to create annotation: ${errorMessage}`,
+ };
+ }
+
+ const result = (await response.json()) as AxiomAnnotationResponse;
+
+ return {
+ success: true,
+ id: result.id,
+ time: result.time,
+ datasets: result.datasets,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to create annotation: ${getErrorMessage(error)}`,
+ };
+ }
+}
+
+export async function createAnnotationStep(
+ input: CreateAnnotationInput
+): Promise {
+ "use step";
+
+ const credentials = input.integrationId
+ ? await fetchCredentials(input.integrationId)
+ : {};
+
+ return withStepLogging(input, () => stepHandler(input, credentials));
+}
+
+export const _integrationType = "axiom";
diff --git a/plugins/axiom/steps/ingest-events.ts b/plugins/axiom/steps/ingest-events.ts
new file mode 100644
index 00000000..126c791a
--- /dev/null
+++ b/plugins/axiom/steps/ingest-events.ts
@@ -0,0 +1,152 @@
+import "server-only";
+
+import { fetchCredentials } from "@/lib/credential-fetcher";
+import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
+import { getErrorMessage } from "@/lib/utils";
+import type { AxiomCredentials } from "../credentials";
+
+const AXIOM_API_URL = "https://api.axiom.co";
+
+type AxiomIngestResponse = {
+ ingested: number;
+ failed: number;
+ failures?: Array<{ timestamp: string; error: string }>;
+ processedBytes: number;
+ blocksCreated: number;
+ walLength: number;
+};
+
+type IngestEventsResult =
+ | {
+ success: true;
+ ingested: number;
+ failed: number;
+ processedBytes: number;
+ }
+ | { success: false; error: string };
+
+export type IngestEventsCoreInput = {
+ dataset: string;
+ events: string;
+};
+
+export type IngestEventsInput = StepInput &
+ IngestEventsCoreInput & {
+ integrationId?: string;
+ };
+
+async function stepHandler(
+ input: IngestEventsCoreInput,
+ credentials: AxiomCredentials
+): Promise {
+ const token = credentials.AXIOM_TOKEN;
+
+ if (!token) {
+ return {
+ success: false,
+ error:
+ "AXIOM_TOKEN is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ try {
+ // Parse the events JSON string
+ let events: Array>;
+ try {
+ const parsed = JSON.parse(input.events) as unknown;
+ // Support both single object and array
+ events = Array.isArray(parsed) ? parsed : [parsed];
+ } catch {
+ return {
+ success: false,
+ error:
+ "Invalid JSON in events field. Expected an array of objects or a single object.",
+ };
+ }
+
+ if (events.length === 0) {
+ return {
+ success: false,
+ error: "No events to ingest. Events array is empty.",
+ };
+ }
+
+ // Add timestamp to events that don't have one
+ const eventsWithTimestamp = events.map((event) => {
+ if (!event._time && !event.timestamp) {
+ return { ...event, _time: new Date().toISOString() };
+ }
+ return event;
+ });
+
+ const headers: Record = {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ };
+
+ if (credentials.AXIOM_ORG_ID) {
+ headers["X-Axiom-Org-Id"] = credentials.AXIOM_ORG_ID;
+ }
+
+ const response = await fetch(
+ `${AXIOM_API_URL}/v1/datasets/${encodeURIComponent(input.dataset)}/ingest`,
+ {
+ method: "POST",
+ headers,
+ body: JSON.stringify(eventsWithTimestamp),
+ }
+ );
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ let errorMessage = `HTTP ${response.status}`;
+ try {
+ const errorJson = JSON.parse(errorText) as { message?: string };
+ errorMessage = errorJson.message || errorMessage;
+ } catch {
+ if (errorText) {
+ errorMessage = errorText;
+ }
+ }
+ return {
+ success: false,
+ error: `Ingest failed: ${errorMessage}`,
+ };
+ }
+
+ const result = (await response.json()) as AxiomIngestResponse;
+
+ if (result.failed > 0 && result.failures?.length) {
+ return {
+ success: false,
+ error: `Ingest partially failed: ${result.failures[0].error}`,
+ };
+ }
+
+ return {
+ success: true,
+ ingested: result.ingested,
+ failed: result.failed,
+ processedBytes: result.processedBytes,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to ingest events: ${getErrorMessage(error)}`,
+ };
+ }
+}
+
+export async function ingestEventsStep(
+ input: IngestEventsInput
+): Promise {
+ "use step";
+
+ const credentials = input.integrationId
+ ? await fetchCredentials(input.integrationId)
+ : {};
+
+ return withStepLogging(input, () => stepHandler(input, credentials));
+}
+
+export const _integrationType = "axiom";
diff --git a/plugins/axiom/steps/list-datasets.ts b/plugins/axiom/steps/list-datasets.ts
new file mode 100644
index 00000000..62397212
--- /dev/null
+++ b/plugins/axiom/steps/list-datasets.ts
@@ -0,0 +1,118 @@
+import "server-only";
+
+import { fetchCredentials } from "@/lib/credential-fetcher";
+import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
+import { getErrorMessage } from "@/lib/utils";
+import type { AxiomCredentials } from "../credentials";
+
+const AXIOM_API_URL = "https://api.axiom.co";
+
+type AxiomDataset = {
+ id: string;
+ name: string;
+ description?: string;
+ who?: string;
+ created: string;
+};
+
+type ListDatasetsResult =
+ | {
+ success: true;
+ datasets: Array<{
+ id: string;
+ name: string;
+ description?: string;
+ created: string;
+ }>;
+ count: number;
+ }
+ | { success: false; error: string };
+
+export type ListDatasetsCoreInput = Record;
+
+export type ListDatasetsInput = StepInput &
+ ListDatasetsCoreInput & {
+ integrationId?: string;
+ };
+
+async function stepHandler(
+ _input: ListDatasetsCoreInput,
+ credentials: AxiomCredentials
+): Promise {
+ const token = credentials.AXIOM_TOKEN;
+
+ if (!token) {
+ return {
+ success: false,
+ error:
+ "AXIOM_TOKEN is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ try {
+ const headers: Record = {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ };
+
+ if (credentials.AXIOM_ORG_ID) {
+ headers["X-Axiom-Org-Id"] = credentials.AXIOM_ORG_ID;
+ }
+
+ const response = await fetch(`${AXIOM_API_URL}/v1/datasets`, {
+ method: "GET",
+ headers,
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ let errorMessage = `HTTP ${response.status}`;
+ try {
+ const errorJson = JSON.parse(errorText) as { message?: string };
+ errorMessage = errorJson.message || errorMessage;
+ } catch {
+ if (errorText) {
+ errorMessage = errorText;
+ }
+ }
+ return {
+ success: false,
+ error: `Failed to list datasets: ${errorMessage}`,
+ };
+ }
+
+ const rawDatasets = (await response.json()) as AxiomDataset[];
+
+ const datasets = rawDatasets.map((ds) => ({
+ id: ds.id,
+ name: ds.name,
+ description: ds.description,
+ created: ds.created,
+ }));
+
+ return {
+ success: true,
+ datasets,
+ count: datasets.length,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to list datasets: ${getErrorMessage(error)}`,
+ };
+ }
+}
+
+export async function listDatasetsStep(
+ input: ListDatasetsInput
+): Promise {
+ "use step";
+
+ const credentials = input.integrationId
+ ? await fetchCredentials(input.integrationId)
+ : {};
+
+ return withStepLogging(input, () => stepHandler({}, credentials));
+}
+
+export const _integrationType = "axiom";
diff --git a/plugins/axiom/steps/query-logs.ts b/plugins/axiom/steps/query-logs.ts
new file mode 100644
index 00000000..e60e17df
--- /dev/null
+++ b/plugins/axiom/steps/query-logs.ts
@@ -0,0 +1,171 @@
+import "server-only";
+
+import { fetchCredentials } from "@/lib/credential-fetcher";
+import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
+import { getErrorMessage } from "@/lib/utils";
+import type { AxiomCredentials } from "../credentials";
+
+const AXIOM_API_URL = "https://api.axiom.co";
+
+type AxiomQueryStatus = {
+ elapsedTime: number;
+ blocksExamined: number;
+ rowsExamined: number;
+ rowsMatched: number;
+ numGroups: number;
+ isPartial: boolean;
+ minBlockTime: string;
+ maxBlockTime: string;
+};
+
+type AxiomQueryResponse = {
+ status: AxiomQueryStatus;
+ matches: Array>;
+ buckets?: {
+ totals?: Array>;
+ };
+};
+
+type QueryLogsResult =
+ | {
+ success: true;
+ matches: Array>;
+ count: number;
+ status: AxiomQueryStatus;
+ }
+ | { success: false; error: string };
+
+export type QueryLogsCoreInput = {
+ dataset: string;
+ apl: string;
+ startTime?: string;
+ endTime?: string;
+};
+
+export type QueryLogsInput = StepInput &
+ QueryLogsCoreInput & {
+ integrationId?: string;
+ };
+
+function parseRelativeTime(time: string): string {
+ if (!time || time === "now") {
+ return new Date().toISOString();
+ }
+
+ // Handle relative time like -1h, -30m, -7d
+ const match = time.match(/^-(\d+)([mhdw])$/);
+ if (match) {
+ const value = parseInt(match[1], 10);
+ const unit = match[2];
+ const now = new Date();
+
+ switch (unit) {
+ case "m":
+ now.setMinutes(now.getMinutes() - value);
+ break;
+ case "h":
+ now.setHours(now.getHours() - value);
+ break;
+ case "d":
+ now.setDate(now.getDate() - value);
+ break;
+ case "w":
+ now.setDate(now.getDate() - value * 7);
+ break;
+ }
+
+ return now.toISOString();
+ }
+
+ // Assume it's already an ISO date string
+ return time;
+}
+
+async function stepHandler(
+ input: QueryLogsCoreInput,
+ credentials: AxiomCredentials
+): Promise {
+ const token = credentials.AXIOM_TOKEN;
+
+ if (!token) {
+ return {
+ success: false,
+ error:
+ "AXIOM_TOKEN is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ try {
+ const headers: Record = {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ };
+
+ if (credentials.AXIOM_ORG_ID) {
+ headers["X-Axiom-Org-Id"] = credentials.AXIOM_ORG_ID;
+ }
+
+ const body: Record = {
+ apl: input.apl,
+ };
+
+ if (input.startTime) {
+ body.startTime = parseRelativeTime(input.startTime);
+ }
+
+ if (input.endTime) {
+ body.endTime = parseRelativeTime(input.endTime);
+ }
+
+ const response = await fetch(`${AXIOM_API_URL}/v1/datasets/_apl`, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ let errorMessage = `HTTP ${response.status}`;
+ try {
+ const errorJson = JSON.parse(errorText) as { message?: string };
+ errorMessage = errorJson.message || errorMessage;
+ } catch {
+ if (errorText) {
+ errorMessage = errorText;
+ }
+ }
+ return {
+ success: false,
+ error: `Query failed: ${errorMessage}`,
+ };
+ }
+
+ const result = (await response.json()) as AxiomQueryResponse;
+
+ return {
+ success: true,
+ matches: result.matches || [],
+ count: result.matches?.length || 0,
+ status: result.status,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to query logs: ${getErrorMessage(error)}`,
+ };
+ }
+}
+
+export async function queryLogsStep(
+ input: QueryLogsInput
+): Promise {
+ "use step";
+
+ const credentials = input.integrationId
+ ? await fetchCredentials(input.integrationId)
+ : {};
+
+ return withStepLogging(input, () => stepHandler(input, credentials));
+}
+
+export const _integrationType = "axiom";
diff --git a/plugins/axiom/test.ts b/plugins/axiom/test.ts
new file mode 100644
index 00000000..ad09041a
--- /dev/null
+++ b/plugins/axiom/test.ts
@@ -0,0 +1,61 @@
+const AXIOM_API_URL = "https://api.axiom.co";
+
+type AxiomUserResponse = {
+ id: string;
+ name: string;
+ email: string;
+};
+
+export async function testAxiom(credentials: Record) {
+ try {
+ const token = credentials.AXIOM_TOKEN;
+ const orgId = credentials.AXIOM_ORG_ID;
+
+ if (!token) {
+ return {
+ success: false,
+ error: "AXIOM_TOKEN is required",
+ };
+ }
+
+ const headers: Record = {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ };
+
+ if (orgId) {
+ headers["X-Axiom-Org-Id"] = orgId;
+ }
+
+ const response = await fetch(`${AXIOM_API_URL}/v1/user`, {
+ method: "GET",
+ headers,
+ });
+
+ if (!response.ok) {
+ if (response.status === 401) {
+ return { success: false, error: "Invalid API token" };
+ }
+ if (response.status === 403) {
+ return {
+ success: false,
+ error: "Access denied. Check your token permissions.",
+ };
+ }
+ return { success: false, error: `API error: HTTP ${response.status}` };
+ }
+
+ const user = (await response.json()) as AxiomUserResponse;
+
+ if (!user.id) {
+ return { success: false, error: "Invalid response from Axiom API" };
+ }
+
+ return { success: true };
+ } catch (error) {
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : String(error),
+ };
+ }
+}
diff --git a/plugins/index.ts b/plugins/index.ts
index af795cb5..f4f298ef 100644
--- a/plugins/index.ts
+++ b/plugins/index.ts
@@ -13,10 +13,11 @@
* 1. Delete the plugin directory
* 2. Run: pnpm discover-plugins (or it runs automatically on build)
*
- * Discovered plugins: ai-gateway, blob, firecrawl, github, linear, resend, slack, superagent, v0
+ * Discovered plugins: ai-gateway, axiom, blob, firecrawl, github, linear, resend, slack, superagent, v0
*/
import "./ai-gateway";
+import "./axiom";
import "./blob";
import "./firecrawl";
import "./github";