🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@useatlas/plugin-sdk

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@useatlas/plugin-sdk - npm Package Compare versions

Comparing version
0.0.2
to
0.0.3
+109
dist/testing.d.ts
/**
* Testing utilities for Atlas plugin authors.
*
* Provides mock factories for AtlasPluginContext, PluginDBConnection, and
* PluginExploreBackend so plugin tests can avoid duplicating boilerplate.
*
* @example
* ```typescript
* import { createMockContext } from "@useatlas/plugin-sdk/testing";
*
* const { ctx, logs, registeredTools } = createMockContext();
* await plugin.initialize(ctx);
* expect(logs.some(l => l.msg.includes("initialized"))).toBe(true);
* ```
*/
import type { AtlasPluginContext, PluginDBConnection, PluginExploreBackend, PluginExecResult, PluginLogger, PluginQueryResult } from "./types";
export interface CapturedLog {
level: keyof PluginLogger;
msg: string;
obj?: Record<string, unknown>;
}
/**
* Create a PluginLogger that captures all log calls to an array.
* Optionally pass an existing array to append to (useful for sharing
* a log sink across multiple loggers).
*/
export declare function createMockLogger(logs?: CapturedLog[]): {
logger: PluginLogger;
logs: CapturedLog[];
};
export interface MockConnectionOptions {
/** The result to return from query(). Can be overridden later via mockQueryResult(). */
queryResult?: PluginQueryResult;
/** If set, query() rejects with this error. Resets after one throw. */
queryError?: Error;
}
export interface MockConnection extends PluginDBConnection {
/** All calls made to query(), in order (read-only — populated by query()). */
readonly queryCalls: ReadonlyArray<{
sql: string;
timeoutMs?: number;
}>;
/** Whether close() has been called. */
readonly closed: boolean;
/** Set the query result for all subsequent calls. Also clears any pending error. */
mockQueryResult(result: PluginQueryResult): void;
/** Make the next query() call reject with this error. Resets after one throw. */
mockQueryError(error: Error): void;
}
/**
* Create a mock PluginDBConnection with configurable query results
* and call tracking.
*/
export declare function createMockConnection(opts?: MockConnectionOptions): MockConnection;
export interface MockExploreBackendOptions {
/** The result to return from exec(). Can be overridden later via mockExecResult(). */
execResult?: PluginExecResult;
/** If set, exec() rejects with this error. Resets after one throw. */
execError?: Error;
}
export interface MockExploreBackend extends PluginExploreBackend {
/** All commands passed to exec(), in order (read-only — populated by exec()). */
readonly execCalls: ReadonlyArray<string>;
/** Whether close() has been called. */
readonly closed: boolean;
/** Set the exec result for all subsequent calls. Also clears any pending error. */
mockExecResult(result: PluginExecResult): void;
/** Make the next exec() call reject with this error. Resets after one throw. */
mockExecError(error: Error): void;
}
/**
* Create a mock PluginExploreBackend with configurable exec results
* and call tracking.
*/
export declare function createMockExploreBackend(opts?: MockExploreBackendOptions): MockExploreBackend;
export interface MockContextOverrides {
/** Override internal db. Pass `null` to simulate no DATABASE_URL. */
db?: AtlasPluginContext["db"] | null;
/** Override the connections registry. */
connections?: Partial<AtlasPluginContext["connections"]>;
/** Override the tools registry. */
tools?: Partial<AtlasPluginContext["tools"]>;
/** Override the logger. */
logger?: PluginLogger;
/** Override the config record. */
config?: Record<string, unknown>;
}
export interface RegisteredTool {
name: string;
description: string;
tool: unknown;
}
export interface MockContextResult {
ctx: AtlasPluginContext;
/** All log entries captured by the mock logger. */
logs: CapturedLog[];
/** All tools registered via ctx.tools.register(). */
registeredTools: RegisteredTool[];
}
/**
* Create a mock AtlasPluginContext with sensible defaults.
* All fields can be overridden.
*
* Returns the context along with captured log entries and registered tools
* for easy assertions. Note: when `logger` or `tools` are overridden, the
* returned `logs` and `registeredTools` arrays are not connected to the
* overridden implementations.
*/
export declare function createMockContext(overrides?: MockContextOverrides): MockContextResult;
// src/testing.ts
function createMockLogger(logs) {
const captured = logs ?? [];
function makeHandler(level) {
return (...args) => {
if (typeof args[0] === "string") {
captured.push({ level, msg: args[0] });
} else if (typeof args[0] === "object" && args[0] !== null) {
captured.push({
level,
obj: args[0],
msg: typeof args[1] === "string" ? args[1] : ""
});
} else {
captured.push({ level, msg: String(args[0]) });
}
};
}
const logger = {
info: makeHandler("info"),
warn: makeHandler("warn"),
error: makeHandler("error"),
debug: makeHandler("debug")
};
return { logger, logs: captured };
}
function createMockConnection(opts = {}) {
let nextResult = opts.queryResult ?? {
columns: [],
rows: []
};
let nextError = opts.queryError;
const queryCalls = [];
const conn = {
queryCalls,
closed: false,
async query(sql, timeoutMs) {
queryCalls.push({ sql, timeoutMs });
if (nextError) {
const err = nextError;
nextError = undefined;
throw err;
}
return nextResult;
},
async close() {
conn.closed = true;
},
mockQueryResult(result) {
nextResult = result;
nextError = undefined;
},
mockQueryError(error) {
nextError = error;
}
};
return conn;
}
function createMockExploreBackend(opts = {}) {
let nextResult = opts.execResult ?? {
stdout: "",
stderr: "",
exitCode: 0
};
let nextError = opts.execError;
const execCalls = [];
const backend = {
execCalls,
closed: false,
async exec(command) {
execCalls.push(command);
if (nextError) {
const err = nextError;
nextError = undefined;
throw err;
}
return nextResult;
},
async close() {
backend.closed = true;
},
mockExecResult(result) {
nextResult = result;
nextError = undefined;
},
mockExecError(error) {
nextError = error;
}
};
return backend;
}
function createMockContext(overrides = {}) {
const logs = [];
const registeredTools = [];
const { logger } = createMockLogger(logs);
const ctx = {
db: overrides.db ?? null,
connections: {
get: overrides.connections?.get ?? ((id) => {
throw new Error(`No connection "${id}" registered in mock context. ` + `Pass a connections override to createMockContext() to provide one.`);
}),
list: overrides.connections?.list ?? (() => [])
},
tools: {
register: overrides.tools?.register ?? ((tool) => {
registeredTools.push(tool);
})
},
logger: overrides.logger ?? logger,
config: overrides.config ?? {}
};
return { ctx, logs, registeredTools };
}
export {
createMockLogger,
createMockExploreBackend,
createMockContext,
createMockConnection
};
+2
-2

@@ -17,3 +17,3 @@ /**

* id: "my-datasource",
* type: "datasource",
* types: ["datasource"],
* version: "1.0.0",

@@ -68,3 +68,3 @@ * connection: {

* id: "bigquery",
* type: "datasource" as const,
* types: ["datasource"] as const,
* version: "1.0.0",

@@ -71,0 +71,0 @@ * config,

@@ -10,6 +10,11 @@ // src/helpers.ts

}
if (!VALID_TYPES.has(plugin.type)) {
throw new Error(`Invalid plugin type "${plugin.type}" — must be one of: datasource, context, interaction, action, sandbox`);
if (!Array.isArray(plugin.types) || plugin.types.length === 0) {
throw new Error(`Plugin "types" must be a non-empty array of plugin types`);
}
if (plugin.type === "datasource") {
for (const t of plugin.types) {
if (!VALID_TYPES.has(t)) {
throw new Error(`Invalid plugin type "${t}" — must be one of: datasource, context, interaction, action, sandbox`);
}
}
if (plugin.types.includes("datasource")) {
const ds = plugin;

@@ -35,2 +40,15 @@ if (!ds.connection || typeof ds.connection !== "object") {

}
if (ds.connection.parserDialect !== undefined && (typeof ds.connection.parserDialect !== "string" || !ds.connection.parserDialect.trim())) {
throw new Error('Datasource plugin connection "parserDialect" must be a non-empty string');
}
if (ds.connection.forbiddenPatterns !== undefined) {
if (!Array.isArray(ds.connection.forbiddenPatterns)) {
throw new Error('Datasource plugin connection "forbiddenPatterns" must be an array of RegExp');
}
for (const p of ds.connection.forbiddenPatterns) {
if (!(p instanceof RegExp)) {
throw new Error('Datasource plugin connection "forbiddenPatterns" entries must each be a RegExp');
}
}
}
if (ds.dialect !== undefined && (typeof ds.dialect !== "string" || !ds.dialect.trim())) {

@@ -40,3 +58,3 @@ throw new Error('Datasource plugin "dialect" must be a non-empty string');

}
if (plugin.type === "context") {
if (plugin.types.includes("context")) {
const ctx = plugin;

@@ -47,3 +65,3 @@ if (!ctx.contextProvider || typeof ctx.contextProvider !== "object") {

}
if (plugin.type === "interaction") {
if (plugin.types.includes("interaction")) {
const int = plugin;

@@ -54,3 +72,3 @@ if (int.routes !== undefined && typeof int.routes !== "function") {

}
if (plugin.type === "action") {
if (plugin.types.includes("action")) {
const act = plugin;

@@ -61,3 +79,3 @@ if (!Array.isArray(act.actions)) {

}
if (plugin.type === "sandbox") {
if (plugin.types.includes("sandbox")) {
const sb = plugin;

@@ -100,15 +118,15 @@ if (!sb.sandbox || typeof sb.sandbox !== "object") {

function isDatasourcePlugin(plugin) {
return plugin.type === "datasource";
return plugin.types.includes("datasource");
}
function isContextPlugin(plugin) {
return plugin.type === "context";
return plugin.types.includes("context");
}
function isInteractionPlugin(plugin) {
return plugin.type === "interaction";
return plugin.types.includes("interaction");
}
function isActionPlugin(plugin) {
return plugin.type === "action";
return plugin.types.includes("action");
}
function isSandboxPlugin(plugin) {
return plugin.type === "sandbox";
return plugin.types.includes("sandbox");
}

@@ -115,0 +133,0 @@ export {

/**
* @useatlas/plugin-sdk — Public API for authoring Atlas plugins.
*/
export type { PluginQueryResult, PluginDBConnection, QueryValidationResult, PluginDBType, PluginType, PluginStatus, PluginHealthResult, PluginLogger, AtlasPluginContext, PluginHookEntry, QueryHookContext, QueryHookMutation, AfterQueryHookContext, ExploreHookContext, ExploreHookMutation, AfterExploreHookContext, RequestHookContext, ResponseHookContext, PluginHooks, PluginFieldDefinition, PluginTableDefinition, AtlasPluginBase, PluginEntity, EntityProvider, AtlasDatasourcePlugin, AtlasContextPlugin, AtlasInteractionPlugin, PluginAction, AtlasActionPlugin, PluginExecResult, PluginExploreBackend, AtlasSandboxPlugin, ActionApprovalMode, AtlasPlugin, $InferServerPlugin, } from "./types";
export type { PluginQueryResult, PluginDBConnection, QueryValidationResult, PluginDBType, ParserDialect, PluginType, PluginStatus, PluginHealthResult, PluginLogger, AtlasPluginContext, PluginHookEntry, QueryHookContext, QueryHookMutation, AfterQueryHookContext, ExploreHookContext, ExploreHookMutation, AfterExploreHookContext, RequestHookContext, ResponseHookContext, PluginHooks, PluginFieldDefinition, PluginTableDefinition, AtlasPluginBase, PluginEntity, EntityProvider, AtlasDatasourcePlugin, AtlasContextPlugin, AtlasInteractionPlugin, PluginAction, AtlasActionPlugin, PluginExecResult, PluginExploreBackend, AtlasSandboxPlugin, ActionApprovalMode, AtlasPlugin, $InferServerPlugin, } from "./types";
export { SANDBOX_DEFAULT_PRIORITY } from "./types";
export { definePlugin, createPlugin, isDatasourcePlugin, isContextPlugin, isInteractionPlugin, isActionPlugin, isSandboxPlugin, } from "./helpers";
export type { CreatePluginOptions } from "./helpers";

@@ -13,6 +13,11 @@ // src/types.ts

}
if (!VALID_TYPES.has(plugin.type)) {
throw new Error(`Invalid plugin type "${plugin.type}" — must be one of: datasource, context, interaction, action, sandbox`);
if (!Array.isArray(plugin.types) || plugin.types.length === 0) {
throw new Error(`Plugin "types" must be a non-empty array of plugin types`);
}
if (plugin.type === "datasource") {
for (const t of plugin.types) {
if (!VALID_TYPES.has(t)) {
throw new Error(`Invalid plugin type "${t}" — must be one of: datasource, context, interaction, action, sandbox`);
}
}
if (plugin.types.includes("datasource")) {
const ds = plugin;

@@ -38,2 +43,15 @@ if (!ds.connection || typeof ds.connection !== "object") {

}
if (ds.connection.parserDialect !== undefined && (typeof ds.connection.parserDialect !== "string" || !ds.connection.parserDialect.trim())) {
throw new Error('Datasource plugin connection "parserDialect" must be a non-empty string');
}
if (ds.connection.forbiddenPatterns !== undefined) {
if (!Array.isArray(ds.connection.forbiddenPatterns)) {
throw new Error('Datasource plugin connection "forbiddenPatterns" must be an array of RegExp');
}
for (const p of ds.connection.forbiddenPatterns) {
if (!(p instanceof RegExp)) {
throw new Error('Datasource plugin connection "forbiddenPatterns" entries must each be a RegExp');
}
}
}
if (ds.dialect !== undefined && (typeof ds.dialect !== "string" || !ds.dialect.trim())) {

@@ -43,3 +61,3 @@ throw new Error('Datasource plugin "dialect" must be a non-empty string');

}
if (plugin.type === "context") {
if (plugin.types.includes("context")) {
const ctx = plugin;

@@ -50,3 +68,3 @@ if (!ctx.contextProvider || typeof ctx.contextProvider !== "object") {

}
if (plugin.type === "interaction") {
if (plugin.types.includes("interaction")) {
const int = plugin;

@@ -57,3 +75,3 @@ if (int.routes !== undefined && typeof int.routes !== "function") {

}
if (plugin.type === "action") {
if (plugin.types.includes("action")) {
const act = plugin;

@@ -64,3 +82,3 @@ if (!Array.isArray(act.actions)) {

}
if (plugin.type === "sandbox") {
if (plugin.types.includes("sandbox")) {
const sb = plugin;

@@ -103,15 +121,15 @@ if (!sb.sandbox || typeof sb.sandbox !== "object") {

function isDatasourcePlugin(plugin) {
return plugin.type === "datasource";
return plugin.types.includes("datasource");
}
function isContextPlugin(plugin) {
return plugin.type === "context";
return plugin.types.includes("context");
}
function isInteractionPlugin(plugin) {
return plugin.type === "interaction";
return plugin.types.includes("interaction");
}
function isActionPlugin(plugin) {
return plugin.type === "action";
return plugin.types.includes("action");
}
function isSandboxPlugin(plugin) {
return plugin.type === "sandbox";
return plugin.types.includes("sandbox");
}

@@ -118,0 +136,0 @@ export {

@@ -36,2 +36,7 @@ /**

export type PluginDBType = "postgres" | "mysql" | "clickhouse" | "snowflake" | "duckdb" | (string & {});
/**
* Known node-sql-parser dialect strings, plus an escape hatch for future dialects.
* Values are case-sensitive — use exactly as listed (e.g. "PostgresQL", not "postgresql").
*/
export type ParserDialect = "Athena" | "BigQuery" | "Db2" | "FlinkSQL" | "Hive" | "MariaDb" | "MySQL" | "NoQL" | "PostgresQL" | "Redshift" | "Snowflake" | "SQLite" | "TransactSQL" | "Trino" | (string & {});
export type PluginType = "datasource" | "context" | "interaction" | "action" | "sandbox";

@@ -176,3 +181,4 @@ export type PluginStatus = "registered" | "initializing" | "healthy" | "unhealthy" | "teardown";

readonly id: string;
readonly type: PluginType;
/** Plugin type(s). A plugin can implement multiple types (e.g. ["interaction", "action"]). */
readonly types: readonly PluginType[];
/** SemVer version string. */

@@ -204,3 +210,3 @@ readonly version: string;

* Declarative table definitions for the internal database.
* Tables are auto-migrated at boot (migration logic in #151).
* Tables are auto-migrated at boot (see packages/api/src/lib/plugins/migrate.ts).
*/

@@ -225,3 +231,2 @@ schema?: Record<string, PluginTableDefinition>;

export interface AtlasDatasourcePlugin<TConfig = undefined> extends AtlasPluginBase<TConfig> {
readonly type: "datasource";
readonly connection: {

@@ -242,2 +247,31 @@ /** Factory: create a DBConnection for the registry. */

validate?(query: string): QueryValidationResult;
/**
* node-sql-parser dialect string for SQL validation. When not provided,
* the core pipeline auto-detects from `dbType`. Override this when `dbType`
* uses the `string & {}` escape hatch or when the auto-detected dialect is
* wrong for your database.
*
* Values are case-sensitive (e.g. `"PostgresQL"`, not `"postgresql"`).
* Use patterns with the `/i` flag for case-insensitive matching.
*
* Ignored when a custom `validate` function is provided (which replaces
* the entire SQL validation pipeline).
*
* **Note:** Wired into the core pipeline in #15. Until then, this field
* is validated at registration time but not yet consumed at query time.
*/
parserDialect?: ParserDialect;
/**
* Additional regex patterns to block beyond the base DML/DDL guard.
* Each pattern is tested against the trimmed SQL string. Use `\b` word
* boundaries and the `/i` flag for case-insensitive matching, consistent
* with core forbidden patterns (see `FORBIDDEN_PATTERNS` in sql.ts).
*
* Ignored when a custom `validate` function is provided (which replaces
* the entire SQL validation pipeline).
*
* **Note:** Wired into the core pipeline in #15. Until then, this field
* is validated at registration time but not yet consumed at query time.
*/
forbiddenPatterns?: RegExp[];
};

@@ -257,3 +291,2 @@ /**

export interface AtlasContextPlugin<TConfig = undefined> extends AtlasPluginBase<TConfig> {
readonly type: "context";
readonly contextProvider: {

@@ -267,3 +300,2 @@ /** Load context (e.g. additional system prompt fragments, entity YAMLs). */

export interface AtlasInteractionPlugin<TConfig = undefined> extends AtlasPluginBase<TConfig> {
readonly type: "interaction";
/**

@@ -286,3 +318,2 @@ * Mount routes on the Hono app. Optional — not all interaction plugins

export interface AtlasActionPlugin<TConfig = undefined> extends AtlasPluginBase<TConfig> {
readonly type: "action";
readonly actions: PluginAction[];

@@ -319,3 +350,2 @@ }

export interface AtlasSandboxPlugin<TConfig = undefined> extends AtlasPluginBase<TConfig> {
readonly type: "sandbox";
readonly sandbox: {

@@ -369,5 +399,5 @@ /**

Config: C;
/** The plugin type literal. */
Type: P extends {
type: infer U;
/** The plugin type(s). */
Types: P extends {
types: infer U;
} ? U : never;

@@ -403,7 +433,7 @@ /** The plugin ID. */

* import type { $InferServerPlugin } from "@useatlas/plugin-sdk";
* import type { clickhousePlugin } from "@atlas/plugin-clickhouse-datasource";
* import type { clickhousePlugin } from "@useatlas/clickhouse";
*
* type CH = $InferServerPlugin<typeof clickhousePlugin>;
* // CH["Config"] → { url: string; database?: string }
* // CH["Type"] → "datasource"
* // CH["Types"] → readonly ["datasource"]
* // CH["Id"] → string

@@ -410,0 +440,0 @@ * ```

{
"name": "@useatlas/plugin-sdk",
"version": "0.0.2",
"version": "0.0.3",
"description": "Type definitions and helpers for authoring Atlas plugins",
"type": "module",
"scripts": {
"build": "rm -rf dist && bun build src/index.ts src/types.ts src/helpers.ts src/ai.ts src/hono.ts --outdir dist --target node --packages external && npx tsc -p tsconfig.build.json",
"build": "rm -rf dist && bun build src/index.ts src/types.ts src/helpers.ts src/ai.ts src/hono.ts src/testing.ts --outdir dist --target node --packages external && npx tsc -p tsconfig.build.json",
"prepublishOnly": "bun run build",
"test": "bun test src/__tests__/types.test.ts && bun test src/__tests__/infer.test.ts"
"test": "bun test src/__tests__/types.test.ts && bun test src/__tests__/infer.test.ts && bun test src/__tests__/testing.test.ts"
},

@@ -36,2 +36,7 @@ "exports": {

"default": "./dist/hono.js"
},
"./testing": {
"types": "./dist/testing.d.ts",
"import": "./dist/testing.js",
"default": "./dist/testing.js"
}

@@ -38,0 +43,0 @@ },

+23
-23

@@ -15,3 +15,3 @@ # @useatlas/plugin-sdk

id: "my-datasource",
type: "datasource" as const,
types: ["datasource"] as const,
version: "1.0.0",

@@ -53,3 +53,3 @@ config,

**Reference:** [`clickhouse-datasource`](../../plugins/clickhouse-datasource/index.ts)
**Reference:** [`clickhouse`](../../plugins/clickhouse/index.ts)

@@ -75,3 +75,3 @@ ### Context (`AtlasContextPlugin`)

**Reference:** [`slack-interaction`](../../plugins/slack-interaction/src/index.ts), [`mcp-interaction`](../../plugins/mcp-interaction/src/index.ts)
**Reference:** [`slack`](../../plugins/slack/src/index.ts), [`mcp`](../../plugins/mcp/src/index.ts)

@@ -92,3 +92,3 @@ ### Action (`AtlasActionPlugin`)

**Reference:** [`jira-action`](../../plugins/jira-action/index.ts), [`email-action`](../../plugins/email-action/index.ts)
**Reference:** [`jira`](../../plugins/jira/index.ts), [`email`](../../plugins/email/index.ts)

@@ -105,3 +105,3 @@ ### Sandbox (`AtlasSandboxPlugin`)

**Reference:** [`nsjail-sandbox`](../../plugins/nsjail-sandbox/index.ts), [`vercel-sandbox`](../../plugins/vercel-sandbox/index.ts)
**Reference:** [`nsjail`](../../plugins/nsjail/index.ts), [`vercel-sandbox`](../../plugins/vercel-sandbox/index.ts)

@@ -160,3 +160,3 @@ ## Base Fields

id: "my-datasource",
type: "datasource" as const,
types: ["datasource"] as const,
version: "1.0.0",

@@ -178,3 +178,3 @@ config,

id: "my-context",
type: "context",
types: ["context"],
version: "1.0.0",

@@ -246,3 +246,3 @@ contextProvider: { async load() { return "Extra context..."; } },

import type { $InferServerPlugin } from "@useatlas/plugin-sdk";
import type { clickhousePlugin } from "@atlas/plugin-clickhouse-datasource";
import type { clickhousePlugin } from "@useatlas/clickhouse";

@@ -289,20 +289,20 @@ type CH = $InferServerPlugin<typeof clickhousePlugin>;

> **Note:** These are internal workspace packages (`@atlas/plugin-*`) within the monorepo, not published to npm. Use them as reference when authoring your own plugins.
> **Note:** These are workspace packages (`@useatlas/*`) within the monorepo. Use them as reference when authoring your own plugins.
| Plugin | Type | Package | Description |
|--------|------|---------|-------------|
| [clickhouse-datasource](../../plugins/clickhouse-datasource/) | Datasource | `@atlas/plugin-clickhouse-datasource` | ClickHouse HTTP transport adapter |
| [mysql-datasource](../../plugins/mysql-datasource/) | Datasource | `@atlas/plugin-mysql-datasource` | MySQL pool-based adapter |
| [snowflake-datasource](../../plugins/snowflake-datasource/) | Datasource | `@atlas/plugin-snowflake-datasource` | Snowflake callback-based adapter |
| [duckdb-datasource](../../plugins/duckdb-datasource/) | Datasource | `@atlas/plugin-duckdb-datasource` | DuckDB in-process adapter |
| [yaml-context](../../plugins/yaml-context/) | Context | `@atlas/plugin-yaml-context` | YAML semantic layer context provider |
| [mcp-interaction](../../plugins/mcp-interaction/) | Interaction | `@atlas/plugin-mcp-interaction` | MCP server lifecycle (stdio + SSE) |
| [slack-interaction](../../plugins/slack-interaction/) | Interaction | `@atlas/plugin-slack-interaction` | Slack bot (slash commands, threads, OAuth) |
| [jira-action](../../plugins/jira-action/) | Action | `@atlas/plugin-jira-action` | Create JIRA tickets from analysis |
| [email-action](../../plugins/email-action/) | Action | `@atlas/plugin-email-action` | Send email reports via Resend |
| [nsjail-sandbox](../../plugins/nsjail-sandbox/) | Sandbox | `@atlas/plugin-nsjail-sandbox` | Linux namespace isolation via nsjail |
| [sidecar-sandbox](../../plugins/sidecar-sandbox/) | Sandbox | `@atlas/plugin-sidecar-sandbox` | HTTP-isolated container sidecar |
| [vercel-sandbox](../../plugins/vercel-sandbox/) | Sandbox | `@atlas/plugin-vercel-sandbox` | Firecracker microVM via @vercel/sandbox |
| [daytona-sandbox](../../plugins/daytona-sandbox/) | Sandbox | `@atlas/plugin-daytona-sandbox` | Daytona managed cloud sandbox |
| [e2b-sandbox](../../plugins/e2b-sandbox/) | Sandbox | `@atlas/plugin-e2b-sandbox` | E2B Firecracker microVM (managed) |
| [clickhouse](../../plugins/clickhouse/) | Datasource | `@useatlas/clickhouse` | ClickHouse HTTP transport adapter |
| [mysql](../../plugins/mysql/) | Datasource | `@useatlas/mysql` | MySQL pool-based adapter |
| [snowflake](../../plugins/snowflake/) | Datasource | `@useatlas/snowflake` | Snowflake callback-based adapter |
| [duckdb](../../plugins/duckdb/) | Datasource | `@useatlas/duckdb` | DuckDB in-process adapter |
| [yaml-context](../../plugins/yaml-context/) | Context | `@useatlas/yaml-context` | YAML semantic layer context provider |
| [mcp](../../plugins/mcp/) | Interaction | `@useatlas/mcp` | MCP server lifecycle (stdio + SSE) |
| [slack](../../plugins/slack/) | Interaction | `@useatlas/slack` | Slack bot (slash commands, threads, OAuth) |
| [jira](../../plugins/jira/) | Action | `@useatlas/jira` | Create JIRA tickets from analysis |
| [email](../../plugins/email/) | Action | `@useatlas/email` | Send email reports via Resend |
| [nsjail](../../plugins/nsjail/) | Sandbox | `@useatlas/nsjail` | Linux namespace isolation via nsjail |
| [sidecar](../../plugins/sidecar/) | Sandbox | `@useatlas/sidecar` | HTTP-isolated container sidecar |
| [vercel-sandbox](../../plugins/vercel-sandbox/) | Sandbox | `@useatlas/vercel-sandbox` | Firecracker microVM via @vercel/sandbox |
| [daytona](../../plugins/daytona/) | Sandbox | `@useatlas/daytona` | Daytona managed cloud sandbox |
| [e2b](../../plugins/e2b/) | Sandbox | `@useatlas/e2b` | E2B Firecracker microVM (managed) |

@@ -309,0 +309,0 @@ ## Source