History

Replace repository contents with v4 snapshot

Rhys Sullivan committed 7 months ago

Commit 4fcdba41 parent be4bbf5865 files · +36247 −404676Browse files

Showing the changes to examples (29 of 865 files). Show all

Unified · Split

29 files
  1. examples/mcp-elicitation-demo/CHANGELOG.md +0 −3
  2. examples/mcp-elicitation-demo/package.json +0 −21
  3. examples/mcp-elicitation-demo/src/index.ts +0 −4
  4. examples/mcp-elicitation-demo/src/main.ts +0 −20
  5. examples/mcp-elicitation-demo/src/server.ts +0 −222
  6. examples/mcp-elicitation-demo/tsconfig.json +0 −15
  7. examples/serve-skills-via-mcp/CHANGELOG.md +0 −3
  8. examples/serve-skills-via-mcp/README.md +0 −38
  9. examples/serve-skills-via-mcp/package.json +0 −21
  10. examples/serve-skills-via-mcp/skills/postgres-incident-triage/SKILL.md +0 −28
  11. examples/serve-skills-via-mcp/skills/postgres-incident-triage/references/error-codes.md +0 −6
  12. examples/serve-skills-via-mcp/skills/postgres-incident-triage/scripts/blocked-session-query.sql +0 −26
  13. examples/serve-skills-via-mcp/skills/release-notes-writer/SKILL.md +0 −28
  14. examples/serve-skills-via-mcp/skills/release-notes-writer/assets/release-template.md +0 −16
  15. examples/serve-skills-via-mcp/skills/release-notes-writer/references/style-guide.md +0 −7
  16. examples/serve-skills-via-mcp/skills/release-notes-writer/scripts/validate-headings.ts +0 −10
  17. examples/serve-skills-via-mcp/src/catalog.ts +0 −243
  18. examples/serve-skills-via-mcp/src/index.ts +0 −4
  19. examples/serve-skills-via-mcp/src/main.ts +0 −20
  20. examples/serve-skills-via-mcp/src/server.ts +0 −346
  21. examples/serve-skills-via-mcp/src/smoke.ts +0 −67
  22. examples/serve-skills-via-mcp/tsconfig.json +0 −15
  23. examples/sqlite-sdk-consumer/.gitignore +0 −1
  24. examples/sqlite-sdk-consumer/README.md +0 −41
  25. examples/sqlite-sdk-consumer/package.json +0 −22
  26. examples/sqlite-sdk-consumer/src/index.ts +0 −4
  27. examples/sqlite-sdk-consumer/src/main.ts +0 −54
  28. examples/sqlite-sdk-consumer/src/sqlite-backend.ts +0 −733
  29. examples/sqlite-sdk-consumer/tsconfig.json +0 −15
examples/mcp-elicitation-demo/CHANGELOG.md+0−3
@@ -1,3 +0,0 @@
1−# @executor/mcp-elicitation-demo
2−
3−## null
examples/mcp-elicitation-demo/package.json+0−21
@@ -1,21 +0,0 @@
1−{
2− "name": "@executor/mcp-elicitation-demo",
3− "private": true,
4− "type": "module",
5− "exports": {
6− ".": "./src/index.ts"
7− },
8− "scripts": {
9− "typecheck": "bunx tsc --noEmit -p tsconfig.json",
10− "start": "bun src/main.ts"
11− },
12− "dependencies": {
13− "@modelcontextprotocol/sdk": "^1.26.0",
14− "zod": "catalog:"
15− },
16− "devDependencies": {
17− "@types/node": "catalog:",
18− "bun-types": "catalog:"
19− },
20− "version": "0.0.0"
21−}
examples/mcp-elicitation-demo/src/index.ts+0−4
@@ -1,4 +0,0 @@
1−export {
2− startMcpElicitationDemoServer,
3− type McpElicitationDemoServer,
4−} from "./server";
examples/mcp-elicitation-demo/src/main.ts+0−20
@@ -1,20 +0,0 @@
1−import { startMcpElicitationDemoServer } from "./server";
2−
3−const host = process.env.HOST ?? "127.0.0.1";
4−const port = process.env.PORT ? Number(process.env.PORT) : 58506;
5−
6−const server = await startMcpElicitationDemoServer({ host, port });
7−console.error(`mcp-elicitation-demo listening on ${server.endpoint}`);
8−
9−const shutdown = async () => {
10− await server.close();
11− process.exit(0);
12−};
13−
14−process.once("SIGINT", () => {
15− void shutdown();
16−});
17−
18−process.once("SIGTERM", () => {
19− void shutdown();
20−});
examples/mcp-elicitation-demo/src/server.ts+0−222
@@ -1,222 +0,0 @@
1−import { randomUUID } from "node:crypto";
2−
3−import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
4−import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5−import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6−import { z } from "zod/v4";
7−
8−export type McpElicitationDemoServer = {
9− readonly endpoint: string;
10− readonly close: () => Promise<void>;
11−};
12−
13−const registerDemoTools = (server: McpServer) => {
14− server.registerTool(
15− "gated_echo",
16− {
17− description: "Ask for approval before echoing the provided value.",
18− inputSchema: {
19− value: z.string(),
20− },
21− },
22− async ({ value }: { value: string }) => {
23− const response = await server.server.elicitInput({
24− mode: "form",
25− message: `Approve gated echo for ${value}?`,
26− requestedSchema: {
27− type: "object",
28− properties: {
29− approve: {
30− type: "boolean",
31− title: "Approve",
32− },
33− },
34− required: ["approve"],
35− },
36− });
37−
38− if (
39− response.action !== "accept"
40− || !response.content
41− || response.content.approve !== true
42− ) {
43− return {
44− content: [{ type: "text", text: "denied" }],
45− };
46− }
47−
48− return {
49− content: [{ type: "text", text: `approved:${value}` }],
50− };
51− },
52− );
53−};
54−
55−export const startMcpElicitationDemoServer = async (input: {
56− readonly host?: string;
57− readonly port?: number;
58−} = {}): Promise<McpElicitationDemoServer> => {
59− const host = input.host ?? "127.0.0.1";
60− const app = createMcpExpressApp({ host });
61− const transports: Record<string, StreamableHTTPServerTransport> = {};
62− const servers: Record<string, McpServer> = {};
63−
64− const createServer = () => {
65− const server = new McpServer(
66− {
67− name: "executor-mcp-elicitation-demo",
68− version: "1.0.0",
69− },
70− {
71− capabilities: {
72− tools: {},
73− },
74− },
75− );
76−
77− registerDemoTools(server);
78− return server;
79− };
80−
81− app.post("/mcp", async (req: any, res: any) => {
82− const sessionIdHeader = req.headers["mcp-session-id"];
83− const sessionId =
84− typeof sessionIdHeader === "string"
85− ? sessionIdHeader
86− : Array.isArray(sessionIdHeader)
87− ? sessionIdHeader[0]
88− : undefined;
89−
90− try {
91− let transport: StreamableHTTPServerTransport;
92−
93− if (sessionId && transports[sessionId]) {
94− transport = transports[sessionId];
95− } else {
96− transport = new StreamableHTTPServerTransport({
97− sessionIdGenerator: () => randomUUID(),
98− onsessioninitialized: (newSessionId) => {
99− transports[newSessionId] = transport;
100− },
101− });
102−
103− transport.onclose = () => {
104− const closedSessionId = transport.sessionId;
105− if (closedSessionId && transports[closedSessionId]) {
106− delete transports[closedSessionId];
107− }
108− if (closedSessionId && servers[closedSessionId]) {
109− void servers[closedSessionId].close().catch(() => undefined);
110− delete servers[closedSessionId];
111− }
112− };
113−
114− const server = createServer();
115− await server.connect(transport);
116− const newSessionId = transport.sessionId;
117− if (newSessionId) {
118− servers[newSessionId] = server;
119− }
120− }
121−
122− await transport.handleRequest(req, res, req.body);
123− } catch (error) {
124− if (!res.headersSent) {
125− res.status(500).json({
126− jsonrpc: "2.0",
127− error: {
128− code: -32603,
129− message: error instanceof Error ? error.message : "Internal server error",
130− },
131− id: null,
132− });
133− }
134− }
135− });
136−
137− app.get("/mcp", async (req: any, res: any) => {
138− const sessionIdHeader = req.headers["mcp-session-id"];
139− const sessionId =
140− typeof sessionIdHeader === "string"
141− ? sessionIdHeader
142− : Array.isArray(sessionIdHeader)
143− ? sessionIdHeader[0]
144− : undefined;
145−
146− if (!sessionId || !transports[sessionId]) {
147− res.status(400).send("Invalid or missing session ID");
148− return;
149− }
150−
151− await transports[sessionId].handleRequest(req, res);
152− });
153−
154− app.delete("/mcp", async (req: any, res: any) => {
155− const sessionIdHeader = req.headers["mcp-session-id"];
156− const sessionId =
157− typeof sessionIdHeader === "string"
158− ? sessionIdHeader
159− : Array.isArray(sessionIdHeader)
160− ? sessionIdHeader[0]
161− : undefined;
162−
163− if (!sessionId || !transports[sessionId]) {
164− res.status(400).send("Invalid or missing session ID");
165− return;
166− }
167−
168− const transport = transports[sessionId];
169− await transport.handleRequest(req, res, req.body);
170− await transport.close();
171− delete transports[sessionId];
172−
173− if (servers[sessionId]) {
174− await servers[sessionId].close().catch(() => undefined);
175− delete servers[sessionId];
176− }
177− });
178−
179− const listener = await new Promise<import("node:http").Server>((resolve, reject) => {
180− const server = app.listen(input.port ?? 0, host);
181−
182− const onListening = () => {
183− server.off("error", onError);
184− resolve(server);
185− };
186−
187− const onError = (error: Error) => {
188− server.off("listening", onListening);
189− reject(error);
190− };
191−
192− server.once("listening", onListening);
193− server.once("error", onError);
194− });
195−
196− const address = listener.address();
197− if (!address || typeof address === "string") {
198− if (listener.listening) {
199− await new Promise<void>((resolve, reject) => {
200− listener.close((error) => (error ? reject(error) : resolve()));
201− });
202− }
203− throw new Error("Failed to resolve MCP elicitation demo server address");
204− }
205−
206− return {
207− endpoint: `http://${host}:${address.port}/mcp`,
208− close: async () => {
209− for (const transport of Object.values(transports)) {
210− await transport.close().catch(() => undefined);
211− }
212−
213− for (const server of Object.values(servers)) {
214− await server.close().catch(() => undefined);
215− }
216−
217− await new Promise<void>((resolve, reject) => {
218− listener.close((error) => (error ? reject(error) : resolve()));
219− });
220− },
221− };
222−};
examples/mcp-elicitation-demo/tsconfig.json+0−15
@@ -1,15 +0,0 @@
1−{
2− "compilerOptions": {
3− "target": "ES2022",
4− "module": "ESNext",
5− "moduleResolution": "Bundler",
6− "strict": true,
7− "skipLibCheck": true,
8− "noEmit": true,
9− "lib": ["ES2022"],
10− "types": ["bun-types", "node"]
11− },
12− "include": [
13− "src/**/*.ts"
14− ]
15−}
examples/serve-skills-via-mcp/CHANGELOG.md+0−3
@@ -1,3 +0,0 @@
1−# @executor/serve-skills-via-mcp-demo
2−
3−## null
examples/serve-skills-via-mcp/README.md+0−38
@@ -1,38 +0,0 @@
1−# Serve Skills Via MCP
2−
3−This demo shows one practical way to distribute [Agent Skills](https://agentskills.io)
4−over MCP resources.
5−
6−It exposes:
7−
8−- a catalog resource that lists available skill bundles
9−- one manifest resource per skill bundle
10−- one direct `SKILL.md` resource per bundle
11−- a `skill://demo/{skill}/{version}/{+path}` resource template for bundled files
12−
13−The important boundary is that MCP resources only transport the bundle. The host still
14−owns activation logic:
15−
16−1. list resources
17−2. read the catalog or a manifest
18−3. choose a skill
19−4. read that skill's `SKILL.md`
20−5. fetch additional files from the manifest on demand
21−
22−## Run
23−
24−```bash
25−bun run --filter @executor/serve-skills-via-mcp-demo start
26−```
27−
28−The server listens on `http://127.0.0.1:<port>/mcp` and logs the final endpoint to
29−stderr.
30−
31−## Smoke Test
32−
33−```bash
34−bun run --filter @executor/serve-skills-via-mcp-demo smoke
35−```
36−
37−The smoke script starts the server, connects with an MCP client, lists resources,
38−reads a skill manifest, then reads `SKILL.md` and one support file.
examples/serve-skills-via-mcp/package.json+0−21
@@ -1,21 +0,0 @@
1−{
2− "name": "@executor/serve-skills-via-mcp-demo",
3− "private": true,
4− "type": "module",
5− "exports": {
6− ".": "./src/index.ts"
7− },
8− "scripts": {
9− "typecheck": "bunx tsc --noEmit -p tsconfig.json",
10− "start": "bun src/main.ts",
11− "smoke": "bun src/smoke.ts"
12− },
13− "dependencies": {
14− "@modelcontextprotocol/sdk": "^1.26.0"
15− },
16− "devDependencies": {
17− "@types/node": "catalog:",
18− "bun-types": "catalog:"
19− },
20− "version": "0.0.0"
21−}
examples/serve-skills-via-mcp/skills/postgres-incident-triage/SKILL.md+0−28
@@ -1,28 +0,0 @@
1−---
2−name: postgres-incident-triage
3−description: Triage production PostgreSQL incidents involving lock contention, slow queries, or saturation. Use when the user asks for database incident diagnosis or immediate next steps.
4−compatibility: Assumes SQL access to pg_stat_activity and pg_locks.
5−allowed-tools: Bash(psql:*) Read
6−---
7−
8−# Postgres Incident Triage
9−
10−## When to use this skill
11−
12−Use this skill for live operational diagnosis of PostgreSQL incidents, especially when
13−latency spikes or blocked sessions appear.
14−
15−## Workflow
16−
17−1. Read `references/error-codes.md` for common failure patterns.
18−2. Use `scripts/blocked-session-query.sql` to inspect blockers and waiters.
19−3. Separate symptoms from root cause: CPU saturation, I/O pressure, lock chains, or
20− connection pileups.
21−4. Recommend the least-destructive mitigation first.
22−5. If proposing session termination, identify the blocker and expected blast radius.
23−
24−## Output rules
25−
26−- Summarize the current condition in one paragraph.
27−- List the most likely root causes in order.
28−- Provide immediate next commands before longer-term remediation.
examples/serve-skills-via-mcp/skills/postgres-incident-triage/references/error-codes.md+0−6
@@ -1,6 +0,0 @@
1−# Common PostgreSQL Incident Signals
2−
3−- `55P03` often points to lock contention or explicit lock timeouts.
4−- `53300` indicates too many connections.
5−- Long idle-in-transaction sessions are frequently the hidden blocker behind lock trees.
6−- A high count of active queries is not enough to prove saturation; check wait events.
examples/serve-skills-via-mcp/skills/postgres-incident-triage/scripts/blocked-session-query.sql+0−26
@@ -1,26 +0,0 @@
1−select
2− blocked.pid as blocked_pid,
3− blocked.query as blocked_query,
4− blocking.pid as blocking_pid,
5− blocking.query as blocking_query,
6− blocked.wait_event_type,
7− blocked.wait_event
8−from pg_stat_activity blocked
9−join pg_locks blocked_locks
10− on blocked.pid = blocked_locks.pid
11−join pg_locks blocking_locks
12− on blocked_locks.locktype = blocking_locks.locktype
13− and blocked_locks.database is not distinct from blocking_locks.database
14− and blocked_locks.relation is not distinct from blocking_locks.relation
15− and blocked_locks.page is not distinct from blocking_locks.page
16− and blocked_locks.tuple is not distinct from blocking_locks.tuple
17− and blocked_locks.virtualxid is not distinct from blocking_locks.virtualxid
18− and blocked_locks.transactionid is not distinct from blocking_locks.transactionid
19− and blocked_locks.classid is not distinct from blocking_locks.classid
20− and blocked_locks.objid is not distinct from blocking_locks.objid
21− and blocked_locks.objsubid is not distinct from blocking_locks.objsubid
22−join pg_stat_activity blocking
23− on blocking.pid = blocking_locks.pid
24−where not blocked_locks.granted
25− and blocking_locks.granted
26− and blocked.pid <> blocking.pid;
examples/serve-skills-via-mcp/skills/release-notes-writer/SKILL.md+0−28
@@ -1,28 +0,0 @@
1−---
2−name: release-notes-writer
3−description: Draft product release notes from commits, changelogs, and issue summaries. Use when the user asks for release notes, launch notes, or upgrade summaries.
4−compatibility: Requires markdown output and access to bundled templates.
5−allowed-tools: Read Write
6−---
7−
8−# Release Notes Writer
9−
10−## When to use this skill
11−
12−Use this skill when the user wants polished release notes, changelog summaries, or
13−customer-facing upgrade notes.
14−
15−## Workflow
16−
17−1. Read the release template in `assets/release-template.md`.
18−2. Read the voice and structure rules in `references/style-guide.md`.
19−3. Group changes into user-facing themes instead of commit-by-commit narration.
20−4. Prefer concrete impact statements over internal implementation details.
21−5. Call out breaking changes, migrations, and feature flags explicitly.
22−
23−## Output rules
24−
25−- Start with a short overview paragraph.
26−- Include sections only for categories that actually changed.
27−- End with a short rollout or migration note when relevant.
28−- Keep internal ticket IDs out of the final draft unless the user asked for them.
examples/serve-skills-via-mcp/skills/release-notes-writer/assets/release-template.md+0−16
@@ -1,16 +0,0 @@
1−# [Release Name]
2−
3−## Overview
4−[Two to four sentences describing the release in user language.]
5−
6−## New
7−- [New capability]
8−
9−## Improved
10−- [Improvement]
11−
12−## Fixed
13−- [Bug fix]
14−
15−## Upgrade Notes
16−- [Migration or rollout note if needed]
examples/serve-skills-via-mcp/skills/release-notes-writer/references/style-guide.md+0−7
@@ -1,7 +0,0 @@
1−# Release Notes Style Guide
2−
3−- Write for product users first, operators second, engineers last.
4−- Lead with outcomes: faster setup, clearer workflows, lower failure rates.
5−- Replace vague claims like "enhanced performance" with specific behavior changes.
6−- Mention removals and breaking changes directly instead of hiding them in "Improved".
7−- Keep bullets parallel and concrete.
examples/serve-skills-via-mcp/skills/release-notes-writer/scripts/validate-headings.ts+0−10
@@ -1,10 +0,0 @@
1−const requiredHeadings = [
2− "## Overview",
3− "## New",
4− "## Improved",
5− "## Fixed",
6−];
7−
8−for (const heading of requiredHeadings) {
9− console.log(`Expect heading: ${heading}`);
10−}
examples/serve-skills-via-mcp/src/catalog.ts+0−243
@@ -1,243 +0,0 @@
1−import { readdir, readFile, stat } from "node:fs/promises";
2−import { extname, join, posix, relative } from "node:path";
3−import { fileURLToPath } from "node:url";
4−
5−export type SkillFrontmatter = {
6− readonly name: string;
7− readonly description: string;
8− readonly compatibility?: string;
9− readonly license?: string;
10− readonly allowedTools?: string;
11−};
12−
13−export type DistributedSkillFile = {
14− readonly path: string;
15− readonly uri: string;
16− readonly mimeType: string;
17− readonly size: number;
18− readonly role: "instructions" | "script" | "reference" | "asset" | "other";
19−};
20−
21−export type SkillManifest = {
22− readonly kind: "agent-skill-distribution";
23− readonly publisher: string;
24− readonly version: string;
25− readonly skill: SkillFrontmatter;
26− readonly instructionsUri: string;
27− readonly manifestUri: string;
28− readonly fileTemplate: string;
29− readonly files: readonly DistributedSkillFile[];
30−};
31−
32−export type DistributedSkillBundle = {
33− readonly publisher: string;
34− readonly version: string;
35− readonly rootDir: string;
36− readonly skill: SkillFrontmatter;
37− readonly manifestUri: string;
38− readonly instructionsUri: string;
39− readonly fileTemplate: string;
40− readonly files: readonly DistributedSkillFile[];
41− readonly manifest: SkillManifest;
42−};
43−
44−const demoPublisher = "demo";
45−const catalogUri = "skill://catalog/index.json";
46−const fileTemplate = "skill://demo/{skill}/{version}/{+path}";
47−
48−const bundleVersions = {
49− "postgres-incident-triage": "1.1.0",
50− "release-notes-writer": "1.0.0",
51−} as const;
52−
53−const skillsDir = fileURLToPath(new URL("../skills", import.meta.url));
54−
55−const mimeTypeFor = (filePath: string): string => {
56− switch (extname(filePath)) {
57− case ".json":
58− return "application/json";
59− case ".md":
60− return "text/markdown";
61− case ".sql":
62− return "application/sql";
63− case ".ts":
64− return "text/typescript";
65− default:
66− return "text/plain";
67− }
68−};
69−
70−const roleFor = (filePath: string): DistributedSkillFile["role"] => {
71− if (filePath === "SKILL.md") {
72− return "instructions";
73− }
74− if (filePath.startsWith("scripts/")) {
75− return "script";
76− }
77− if (filePath.startsWith("references/")) {
78− return "reference";
79− }
80− if (filePath.startsWith("assets/")) {
81− return "asset";
82− }
83− return "other";
84−};
85−
86−const parseFrontmatter = (content: string): SkillFrontmatter => {
87− const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/u.exec(content);
88− if (!match) {
89− throw new Error("Expected SKILL.md to start with YAML frontmatter");
90− }
91−
92− const fields: Record<string, string> = {};
93− for (const line of match[1].split(/\r?\n/u)) {
94− const trimmed = line.trim();
95− if (trimmed.length === 0 || trimmed.startsWith("#")) {
96− continue;
97− }
98−
99− const entry = /^([A-Za-z0-9-]+):\s*(.+)$/u.exec(trimmed);
100− if (!entry) {
101− continue;
102− }
103−
104− fields[entry[1]] = entry[2];
105− }
106−
107− if (!fields.name || !fields.description) {
108− throw new Error("Expected SKILL.md frontmatter to include name and description");
109− }
110−
111− return {
112− name: fields.name,
113− description: fields.description,
114− compatibility: fields.compatibility,
115− license: fields.license,
116− allowedTools: fields["allowed-tools"],
117− };
118−};
119−
120−const collectFiles = async (rootDir: string): Promise<string[]> => {
121− const output: string[] = [];
122−
123− const visit = async (directory: string) => {
124− const entries = await readdir(directory, { withFileTypes: true });
125− entries.sort((left, right) => left.name.localeCompare(right.name));
126−
127− for (const entry of entries) {
128− const absolutePath = join(directory, entry.name);
129− if (entry.isDirectory()) {
130− await visit(absolutePath);
131− continue;
132− }
133− output.push(posix.join(...relative(rootDir, absolutePath).split("\\").filter(Boolean)));
134− }
135− };
136−
137− await visit(rootDir);
138− return output;
139−};
140−
141−export const getCatalogUri = (): string => catalogUri;
142−
143−export const getFileTemplate = (): string => fileTemplate;
144−
145−export const loadDistributedSkillBundles = async (): Promise<readonly DistributedSkillBundle[]> => {
146− const bundles = await Promise.all(
147− Object.entries(bundleVersions).map(async ([directoryName, version]) => {
148− const rootDir = join(skillsDir, directoryName);
149− const skillContent = await readFile(join(rootDir, "SKILL.md"), "utf8");
150− const skill = parseFrontmatter(skillContent);
151− const files = await collectFiles(rootDir);
152−
153− const distributedFiles = await Promise.all(
154− files.map(async (filePath) => {
155− const absolutePath = join(rootDir, filePath);
156− const fileStats = await stat(absolutePath);
157− return {
158− path: filePath,
159− uri: `skill://${demoPublisher}/${skill.name}/${version}/${filePath}`,
160− mimeType: mimeTypeFor(filePath),
161− size: fileStats.size,
162− role: roleFor(filePath),
163− } satisfies DistributedSkillFile;
164− }),
165− );
166−
167− const manifestUri = `skill://${demoPublisher}/${skill.name}/${version}/manifest.json`;
168− const instructionsUri = `skill://${demoPublisher}/${skill.name}/${version}/SKILL.md`;
169−
170− const manifest: SkillManifest = {
171− kind: "agent-skill-distribution",
172− publisher: demoPublisher,
173− version,
174− skill,
175− instructionsUri,
176− manifestUri,
177− fileTemplate,
178− files: distributedFiles,
179− };
180−
181− return {
182− publisher: demoPublisher,
183− version,
184− rootDir,
185− skill,
186− manifestUri,
187− instructionsUri,
188− fileTemplate,
189− files: distributedFiles,
190− manifest,
191− } satisfies DistributedSkillBundle;
192− }),
193− );
194−
195− return bundles.sort((left, right) => left.skill.name.localeCompare(right.skill.name));
196−};
197−
198−export const renderCatalog = (bundles: readonly DistributedSkillBundle[]): string =>
199− JSON.stringify(
200− {
201− kind: "agent-skill-catalog",
202− distributionMethod: "mcp-resources",
203− catalogUri,
204− fileTemplate,
205− activationGuidance: [
206− "Use this catalog for discovery only.",
207− "Read a skill manifest next to find the concrete SKILL.md URI.",
208− "Load SKILL.md into the model only when the task matches the skill description.",
209− "Fetch support files from the manifest on demand.",
210− ],
211− skills: bundles.map((bundle) => ({
212− name: bundle.skill.name,
213− description: bundle.skill.description,
214− version: bundle.version,
215− manifestUri: bundle.manifestUri,
216− instructionsUri: bundle.instructionsUri,
217− })),
218− },
219− null,
220− 2,
221− );
222−
223−export const findBundleByName = (
224− bundles: readonly DistributedSkillBundle[],
225− skillName: string,
226−): DistributedSkillBundle | undefined =>
227− bundles.find((bundle) => bundle.skill.name === skillName);
228−
229−export const findBundleFile = (
230− bundles: readonly DistributedSkillBundle[],
231− skillName: string,
232− version: string,
233− filePath: string,
234−): DistributedSkillFile | undefined =>
235− bundles
236− .find((bundle) => bundle.skill.name === skillName && bundle.version === version)
237− ?.files.find((file) => file.path === filePath);
238−
239−export const readBundleFileText = async (
240− bundle: DistributedSkillBundle,
241− filePath: string,
242−): Promise<string> =>
243− readFile(join(bundle.rootDir, filePath), "utf8");
examples/serve-skills-via-mcp/src/index.ts+0−4
@@ -1,4 +0,0 @@
1−export {
2− startServeSkillsViaMcpDemoServer,
3− type ServeSkillsViaMcpDemoServer,
4−} from "./server";
examples/serve-skills-via-mcp/src/main.ts+0−20
@@ -1,20 +0,0 @@
1−import { startServeSkillsViaMcpDemoServer } from "./server";
2−
3−const host = process.env.HOST ?? "127.0.0.1";
4−const port = process.env.PORT ? Number(process.env.PORT) : 58507;
5−
6−const server = await startServeSkillsViaMcpDemoServer({ host, port });
7−console.error(`serve-skills-via-mcp listening on ${server.endpoint}`);
8−
9−const shutdown = async () => {
10− await server.close();
11− process.exit(0);
12−};
13−
14−process.once("SIGINT", () => {
15− void shutdown();
16−});
17−
18−process.once("SIGTERM", () => {
19− void shutdown();
20−});
examples/serve-skills-via-mcp/src/server.ts+0−346
@@ -1,346 +0,0 @@
1−import { randomUUID } from "node:crypto";
2−import type { Server as HttpServer } from "node:http";
3−
4−import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
5−import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
6−import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
7−
8−import {
9− type DistributedSkillBundle,
10− findBundleByName,
11− findBundleFile,
12− getCatalogUri,
13− getFileTemplate,
14− loadDistributedSkillBundles,
15− readBundleFileText,
16− renderCatalog,
17−} from "./catalog";
18−
19−export type ServeSkillsViaMcpDemoServer = {
20− readonly endpoint: string;
21− readonly close: () => Promise<void>;
22−};
23−
24−const registerSkillResources = (
25− server: McpServer,
26− bundles: readonly DistributedSkillBundle[],
27−) => {
28− server.registerResource(
29− "skill-catalog",
30− getCatalogUri(),
31− {
32− title: "Distributed Agent Skills Catalog",
33− description: "Catalog of skill bundles exposed over MCP resources.",
34− mimeType: "application/json",
35− annotations: {
36− audience: ["assistant", "user"],
37− priority: 0.7,
38− },
39− },
40− async () => ({
41− contents: [
42− {
43− uri: getCatalogUri(),
44− mimeType: "application/json",
45− text: renderCatalog(bundles),
46− },
47− ],
48− }),
49− );
50−
51− for (const bundle of bundles) {
52− server.registerResource(
53− `${bundle.skill.name}-manifest`,
54− bundle.manifestUri,
55− {
56− title: `${bundle.skill.name} manifest`,
57− description: `Distribution manifest for ${bundle.skill.name}.`,
58− mimeType: "application/json",
59− annotations: {
60− audience: ["assistant"],
61− priority: 0.85,
62− },
63− },
64− async () => ({
65− contents: [
66− {
67− uri: bundle.manifestUri,
68− mimeType: "application/json",
69− text: JSON.stringify(bundle.manifest, null, 2),
70− },
71− ],
72− }),
73− );
74−
75− server.registerResource(
76− `${bundle.skill.name}-instructions`,
77− bundle.instructionsUri,
78− {
79− title: `${bundle.skill.name} SKILL.md`,
80− description: bundle.skill.description,
81− mimeType: "text/markdown",
82− annotations: {
83− audience: ["assistant"],
84− priority: 1,
85− },
86− },
87− async () => ({
88− contents: [
89− {
90− uri: bundle.instructionsUri,
91− mimeType: "text/markdown",
92− text: await readBundleFileText(bundle, "SKILL.md"),
93− },
94− ],
95− }),
96− );
97− }
98−
99− server.registerResource(
100− "skill-bundle-file",
101− new ResourceTemplate(getFileTemplate(), {
102− list: undefined,
103− complete: {
104− skill: (value) =>
105− bundles
106− .map((bundle) => bundle.skill.name)
107− .filter((skillName) => skillName.startsWith(value)),
108− version: (value, context) => {
109− const skillName = context?.arguments?.skill;
110− if (!skillName) {
111− return bundles
112− .map((bundle) => bundle.version)
113− .filter((version) => version.startsWith(value));
114− }
115−
116− const bundle = findBundleByName(bundles, skillName);
117− if (!bundle) {
118− return [];
119− }
120−
121− return bundle.version.startsWith(value) ? [bundle.version] : [];
122− },
123− path: (value, context) => {
124− const skillName = context?.arguments?.skill;
125− const version = context?.arguments?.version;
126− if (!skillName || !version) {
127− return [];
128− }
129−
130− const bundle = bundles.find((entry) =>
131− entry.skill.name === skillName && entry.version === version
132− );
133− if (!bundle) {
134− return [];
135− }
136−
137− return bundle.files
138− .map((file) => file.path)
139− .filter((filePath) => filePath.startsWith(value));
140− },
141− },
142− }),
143− {
144− title: "Skill bundle files",
145− description: "Read any file inside a distributed skill bundle.",
146− mimeType: "text/plain",
147− annotations: {
148− audience: ["assistant"],
149− priority: 0.5,
150− },
151− },
152− async (_uri, variables) => {
153− const skillName = typeof variables.skill === "string" ? variables.skill : undefined;
154− const version = typeof variables.version === "string" ? variables.version : undefined;
155− const filePath = typeof variables.path === "string" ? variables.path : undefined;
156−
157− if (!skillName || !version || !filePath) {
158− throw new Error("Expected skill, version, and path variables");
159− }
160−
161− const bundle = bundles.find((entry) =>
162− entry.skill.name === skillName && entry.version === version
163− );
164− const file = findBundleFile(bundles, skillName, version, filePath);
165− if (!bundle || !file) {
166− throw new Error(`Unknown distributed skill file: ${skillName}@${version}/${filePath}`);
167− }
168−
169− return {
170− contents: [
171− {
172− uri: file.uri,
173− mimeType: file.mimeType,
174− text: await readBundleFileText(bundle, filePath),
175− },
176− ],
177− };
178− },
179− );
180−};
181−
182−const createSessionServer = (bundles: readonly DistributedSkillBundle[]): McpServer => {
183− const server = new McpServer(
184− {
185− name: "executor-serve-skills-via-mcp-demo",
186− version: "1.0.0",
187− },
188− {
189− capabilities: {
190− resources: {
191− listChanged: true,
192− },
193− },
194− instructions: [
195− "This server demonstrates one way to distribute Agent Skills over MCP resources.",
196− "Read the catalog and a skill manifest first, then load SKILL.md only when you decide to activate that skill.",
197− ].join(" "),
198− },
199− );
200−
201− registerSkillResources(server, bundles);
202− return server;
203−};
204−
205−const resolveSessionId = (headers: Record<string, string | string[] | undefined>): string | undefined => {
206− const value = headers["mcp-session-id"];
207− if (typeof value === "string") {
208− return value;
209− }
210− if (Array.isArray(value)) {
211− return value[0];
212− }
213− return undefined;
214−};
215−
216−const listen = async (app: ReturnType<typeof createMcpExpressApp>, port: number, host: string) =>
217− new Promise<HttpServer>((resolve, reject) => {
218− const listener = app.listen(port, host);
219−
220− const onListening = () => {
221− listener.off("error", onError);
222− resolve(listener);
223− };
224−
225− const onError = (error: Error) => {
226− listener.off("listening", onListening);
227− reject(error);
228− };
229−
230− listener.once("listening", onListening);
231− listener.once("error", onError);
232− });
233−
234−export const startServeSkillsViaMcpDemoServer = async (input: {
235− readonly host?: string;
236− readonly port?: number;
237−} = {}): Promise<ServeSkillsViaMcpDemoServer> => {
238− const host = input.host ?? "127.0.0.1";
239− const bundles = await loadDistributedSkillBundles();
240− const app = createMcpExpressApp({ host });
241− const transports: Record<string, StreamableHTTPServerTransport> = {};
242− const servers: Record<string, McpServer> = {};
243−
244− app.post("/mcp", async (req: any, res: any) => {
245− const sessionId = resolveSessionId(req.headers);
246−
247− try {
248− let transport: StreamableHTTPServerTransport;
249−
250− if (sessionId && transports[sessionId]) {
251− transport = transports[sessionId];
252− } else {
253− transport = new StreamableHTTPServerTransport({
254− sessionIdGenerator: () => randomUUID(),
255− onsessioninitialized: (newSessionId) => {
256− transports[newSessionId] = transport;
257− },
258− });
259−
260− transport.onclose = () => {
261− const closedSessionId = transport.sessionId;
262− if (closedSessionId && transports[closedSessionId]) {
263− delete transports[closedSessionId];
264− }
265− if (closedSessionId && servers[closedSessionId]) {
266− void servers[closedSessionId].close().catch(() => undefined);
267− delete servers[closedSessionId];
268− }
269− };
270−
271− const server = createSessionServer(bundles);
272− await server.connect(transport);
273− if (transport.sessionId) {
274− servers[transport.sessionId] = server;
275− }
276− }
277−
278− await transport.handleRequest(req, res, req.body);
279− } catch (error) {
280− if (!res.headersSent) {
281− res.status(500).json({
282− jsonrpc: "2.0",
283− error: {
284− code: -32603,
285− message: error instanceof Error ? error.message : "Internal server error",
286− },
287− id: null,
288− });
289− }
290− }
291− });
292−
293− app.get("/mcp", async (req: any, res: any) => {
294− const sessionId = resolveSessionId(req.headers);
295− if (!sessionId || !transports[sessionId]) {
296− res.status(400).send("Invalid or missing session ID");
297− return;
298− }
299−
300− await transports[sessionId].handleRequest(req, res);
301− });
302−
303− app.delete("/mcp", async (req: any, res: any) => {
304− const sessionId = resolveSessionId(req.headers);
305− if (!sessionId || !transports[sessionId]) {
306− res.status(400).send("Invalid or missing session ID");
307− return;
308− }
309−
310− const transport = transports[sessionId];
311− await transport.handleRequest(req, res, req.body);
312− await transport.close();
313− delete transports[sessionId];
314−
315− if (servers[sessionId]) {
316− await servers[sessionId].close().catch(() => undefined);
317− delete servers[sessionId];
318− }
319− });
320−
321− const listener = await listen(app, input.port ?? 0, host);
322− const address = listener.address();
323− if (!address || typeof address === "string") {
324− await new Promise<void>((resolve, reject) => {
325− listener.close((error) => (error ? reject(error) : resolve()));
326− });
327− throw new Error("Failed to resolve serve-skills-via-mcp demo address");
328− }
329−
330− return {
331− endpoint: `http://${host}:${address.port}/mcp`,
332− close: async () => {
333− for (const transport of Object.values(transports)) {
334− await transport.close().catch(() => undefined);
335− }
336−
337− for (const server of Object.values(servers)) {
338− await server.close().catch(() => undefined);
339− }
340−
341− await new Promise<void>((resolve, reject) => {
342− listener.close((error) => (error ? reject(error) : resolve()));
343− });
344− },
345− };
346−};
examples/serve-skills-via-mcp/src/smoke.ts+0−67
@@ -1,67 +0,0 @@
1−import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2−import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3−
4−import { startServeSkillsViaMcpDemoServer } from "./server";
5−
6−const server = await startServeSkillsViaMcpDemoServer();
7−const client = new Client({
8− name: "serve-skills-via-mcp-smoke",
9− version: "1.0.0",
10−});
11−const transport = new StreamableHTTPClientTransport(new URL(server.endpoint));
12−
13−try {
14− await client.connect(transport);
15−
16− const resources = await client.listResources();
17− console.log(`resources: ${resources.resources.length}`);
18− for (const resource of resources.resources) {
19− console.log(`- ${resource.uri}`);
20− }
21−
22− const templates = await client.listResourceTemplates();
23− console.log(`resourceTemplates: ${templates.resourceTemplates.length}`);
24− for (const template of templates.resourceTemplates) {
25− console.log(`- ${template.uriTemplate}`);
26− }
27−
28− const catalog = await client.readResource({ uri: "skill://catalog/index.json" });
29− const catalogText = "text" in catalog.contents[0]! ? catalog.contents[0]!.text : "";
30− const parsedCatalog = JSON.parse(catalogText) as {
31− readonly skills: ReadonlyArray<{
32− readonly name: string;
33− readonly manifestUri: string;
34− readonly instructionsUri: string;
35− }>;
36− };
37−
38− const selectedSkill = parsedCatalog.skills[0]!;
39− console.log(`selectedSkill: ${selectedSkill.name}`);
40−
41− const manifest = await client.readResource({ uri: selectedSkill.manifestUri });
42− const manifestText = "text" in manifest.contents[0]! ? manifest.contents[0]!.text : "";
43− const parsedManifest = JSON.parse(manifestText) as {
44− readonly instructionsUri: string;
45− readonly files: ReadonlyArray<{
46− readonly path: string;
47− readonly uri: string;
48− readonly role: string;
49− }>;
50− };
51−
52− const instructions = await client.readResource({ uri: parsedManifest.instructionsUri });
53− const instructionsText = "text" in instructions.contents[0]! ? instructions.contents[0]!.text : "";
54− console.log(`instructionsPreview: ${instructionsText.split("\n").slice(0, 6).join("\n")}`);
55−
56− const supportFile = parsedManifest.files.find((file) => file.role !== "instructions");
57− if (supportFile) {
58− const support = await client.readResource({ uri: supportFile.uri });
59− const supportText = "text" in support.contents[0]! ? support.contents[0]!.text : "";
60− console.log(`supportFile: ${supportFile.path}`);
61− console.log(`supportPreview: ${supportText.split("\n").slice(0, 4).join("\n")}`);
62− }
63−} finally {
64− await client.close().catch(() => undefined);
65− await transport.close().catch(() => undefined);
66− await server.close().catch(() => undefined);
67−}
examples/serve-skills-via-mcp/tsconfig.json+0−15
@@ -1,15 +0,0 @@
1−{
2− "compilerOptions": {
3− "target": "ES2022",
4− "module": "ESNext",
5− "moduleResolution": "Bundler",
6− "strict": true,
7− "skipLibCheck": true,
8− "noEmit": true,
9− "lib": ["ES2022"],
10− "types": ["bun-types", "node"]
11− },
12− "include": [
13− "src/**/*.ts"
14− ]
15−}
examples/sqlite-sdk-consumer/.gitignore+0−1
@@ -1 +0,0 @@
1−.data/
examples/sqlite-sdk-consumer/README.md+0−41
@@ -1,41 +0,0 @@
1−# SQLite SDK Consumer Example
2−
3−This example shows how an embedder can implement a custom SQLite-backed adapter
4−using the public Promise-facing `@executor/platform-sdk`.
5−
6−The adapter is intentionally simple:
7−
8−- installation state is stored in SQLite
9−- scope config and scope state are stored in SQLite
10−- source artifacts are stored in SQLite
11−- executor state is stored in SQLite
12−- secret material is stored in SQLite
13−
14−There is no direct `effect` usage in the example.
15−
16−## Run
17−
18−```sh
19−bun run --cwd examples/sqlite-sdk-consumer start
20−```
21−
22−To keep the demo ephemeral:
23−
24−```sh
25−DATABASE_PATH=:memory: bun run --cwd examples/sqlite-sdk-consumer start
26−```
27−
28−## Shape
29−
30−The important bit is the consumer-facing construction:
31−
32−```ts
33−import { createExecutor } from "@executor/platform-sdk";
34−import { createSqliteExecutorBackend } from "@executor/sqlite-sdk-consumer-example";
35−
36−const executor = await createExecutor({
37− backend: createSqliteExecutorBackend({
38− databasePath: "./executor.sqlite",
39− }),
40−});
41−```
examples/sqlite-sdk-consumer/package.json+0−22
@@ -1,22 +0,0 @@
1−{
2− "name": "@executor/sqlite-sdk-consumer-example",
3− "private": true,
4− "type": "module",
5− "exports": {
6− ".": "./src/index.ts"
7− },
8− "scripts": {
9− "typecheck": "bunx tsc --noEmit -p tsconfig.json",
10− "start": "bun src/main.ts"
11− },
12− "dependencies": {
13− "@executor/platform-sdk": "workspace:*",
14− "@executor/source-core": "workspace:*",
15− "drizzle-orm": "^0.44.5"
16− },
17− "devDependencies": {
18− "@types/node": "catalog:",
19− "bun-types": "catalog:"
20− },
21− "version": "0.0.0"
22−}
examples/sqlite-sdk-consumer/src/index.ts+0−4
@@ -1,4 +0,0 @@
1−export {
2− createSqliteExecutorBackend,
3− type CreateSqliteExecutorBackendOptions,
4−} from "./sqlite-backend";
examples/sqlite-sdk-consumer/src/main.ts+0−54
@@ -1,54 +0,0 @@
1−import { createExecutor } from "@executor/platform-sdk";
2−
3−import { createSqliteExecutorBackend } from "./sqlite-backend";
4−
5−const databasePath = process.env.DATABASE_PATH
6− ?? new URL("../.data/executor.sqlite", import.meta.url).pathname;
7−
8−const executor = await createExecutor({
9− backend: createSqliteExecutorBackend({
10− databasePath,
11− }),
12−});
13−
14−try {
15− const installation = await executor.local.installation();
16−
17− const policy = await executor.policies.create({
18− resourcePattern: "math.add",
19− effect: "allow",
20− approvalMode: "auto",
21− });
22−
23− const secret = await executor.secrets.create({
24− name: "demo-api-key",
25− value: "secret-value",
26− });
27−
28− const execution = await executor.executions.create({
29− code: "return 20 + 22;",
30− });
31−
32− const sources = await executor.sources.list();
33− const secrets = await executor.secrets.list();
34− const policies = await executor.policies.list();
35−
36− console.log(JSON.stringify({
37− databasePath,
38− installation,
39− createdPolicyId: policy.id,
40− createdSecretId: secret.id,
41− execution: {
42− id: execution.execution.id,
43− status: execution.execution.status,
44− resultJson: execution.execution.resultJson,
45− },
46− counts: {
47− sources: sources.length,
48− secrets: secrets.length,
49− policies: policies.length,
50− },
51− }, null, 2));
52−} finally {
53− await executor.close();
54−}
examples/sqlite-sdk-consumer/src/sqlite-backend.ts+0−733
@@ -1,733 +0,0 @@
1−import { createHash, randomUUID } from "node:crypto";
2−import { mkdirSync } from "node:fs";
3−import { dirname, resolve as resolvePath } from "node:path";
4−
5−import { Database } from "bun:sqlite";
6−import { and, eq } from "drizzle-orm";
7−import { drizzle } from "drizzle-orm/bun-sqlite";
8−import {
9− integer,
10− sqliteTable,
11− text,
12−} from "drizzle-orm/sqlite-core";
13−
14−import {
15− createExecutorBackend,
16− type ExecutorBackend,
17− type ExecutorBackendRepositories,
18− type ExecutorWorkspaceConfigRepository,
19− type ExecutorWorkspaceStateRepository,
20− type ExecutorWorkspaceSourceArtifactRepository,
21−} from "@executor/platform-sdk";
22−import type {
23− Execution,
24− ExecutionInteraction,
25− ExecutionStep,
26− ExecutorScopeConfig,
27− LocalInstallation,
28− SecretMaterial,
29− SecretRef,
30− SecretStore,
31−} from "@executor/platform-sdk/schema";
32−import {
33− ScopeIdSchema,
34− SecretMaterialIdSchema,
35− SourceCatalogIdSchema,
36− SourceCatalogRevisionIdSchema,
37−} from "@executor/platform-sdk/schema";
38−import type {
39− SecretMaterialStoredDataRecord,
40−} from "@executor/platform-sdk/runtime";
41−import {
42− contentHash,
43− snapshotFromSourceCatalogSyncResult,
44−} from "@executor/source-core";
45−
46−export type CreateSqliteExecutorBackendOptions = {
47− databasePath?: string;
48− scopeId?: string;
49− actorScopeId?: string;
50−};
51−
52−type ScopeConfig = Awaited<ReturnType<ExecutorWorkspaceConfigRepository["load"]>>;
53−type ScopeState = Awaited<ReturnType<ExecutorWorkspaceStateRepository["load"]>>;
54−type SourceArtifact =
55− ReturnType<ExecutorWorkspaceSourceArtifactRepository["build"]>;
56−type SourceArtifactBuildInput = Parameters<
57− ExecutorWorkspaceSourceArtifactRepository["build"]
58−>[0];
59−
60−type SecretMaterialSummary = {
61− id: string;
62− storeId: string;
63− name: string | null;
64− purpose: string;
65− createdAt: number;
66− updatedAt: number;
67−};
68−
69−type SqliteSecretStoredData = {
70− value: string;
71−};
72−
73−const SQLITE_SECRET_STORE_KIND = "sqlite";
74−const SQLITE_SECRET_STORE_ID = "sts_sqlite";
75−
76−const installations = sqliteTable("installations", {
77− key: text("key").primaryKey(),
78− scopeId: text("scope_id").notNull(),
79− actorScopeId: text("actor_scope_id").notNull(),
80−});
81−
82−const scopeConfigs = sqliteTable("scope_configs", {
83− key: text("key").primaryKey(),
84− projectConfigJson: text("project_config_json").notNull(),
85−});
86−
87−const scopeStates = sqliteTable("scope_states", {
88− key: text("key").primaryKey(),
89− stateJson: text("state_json").notNull(),
90−});
91−
92−const sourceArtifacts = sqliteTable("source_artifacts", {
93− sourceId: text("source_id").primaryKey(),
94− artifactJson: text("artifact_json").notNull(),
95−});
96−
97−const secretMaterials = sqliteTable("secret_materials", {
98− id: text("id").primaryKey(),
99− storeId: text("store_id").notNull(),
100− name: text("name"),
101− purpose: text("purpose").notNull(),
102− createdAt: integer("created_at").notNull(),
103− updatedAt: integer("updated_at").notNull(),
104− json: text("json").notNull(),
105−});
106−
107−const secretMaterialStoredData = sqliteTable("secret_material_stored_data", {
108− secretId: text("secret_id").primaryKey(),
109− json: text("json").notNull(),
110−});
111−
112−const executions = sqliteTable("executions", {
113− id: text("id").primaryKey(),
114− scopeId: text("scope_id").notNull(),
115− json: text("json").notNull(),
116−});
117−
118−const executionInteractions = sqliteTable("execution_interactions", {
119− id: text("id").primaryKey(),
120− executionId: text("execution_id").notNull(),
121− status: text("status").notNull(),
122− json: text("json").notNull(),
123−});
124−
125−const executionSteps = sqliteTable("execution_steps", {
126− id: text("id").primaryKey(),
127− executionId: text("execution_id").notNull(),
128− sequence: integer("sequence").notNull(),
129− json: text("json").notNull(),
130−});
131−
132−const makeHash = (value: string): string =>
133− createHash("sha256").update(value).digest("hex").slice(0, 24);
134−
135−const parseJson = <T>(value: string): T => JSON.parse(value) as T;
136−
137−const createSourceArtifact = (input: SourceArtifactBuildInput): SourceArtifact => {
138− const snapshot = snapshotFromSourceCatalogSyncResult(input.syncResult);
139− const sourceConfigJson = JSON.stringify({
140− kind: input.source.kind,
141− namespace: input.source.namespace,
142− name: input.source.name,
143− enabled: input.source.enabled,
144− });
145− const importMetadataJson = JSON.stringify(snapshot.import);
146− const catalogId = SourceCatalogIdSchema.make(`src_catalog_${makeHash(sourceConfigJson)}`);
147− const revisionId = SourceCatalogRevisionIdSchema.make(
148− `src_catalog_rev_${makeHash(sourceConfigJson)}`,
149− );
150−
151− return {
152− version: 4,
153− sourceId: input.source.id,
154− catalogId,
155− generatedAt: Date.now(),
156− revision: {
157− id: revisionId,
158− catalogId,
159− revisionNumber: 1,
160− sourceConfigJson,
161− importMetadataJson,
162− importMetadataHash: contentHash(importMetadataJson),
163− snapshotHash: contentHash(JSON.stringify(snapshot)),
164− createdAt: input.source.createdAt,
165− updatedAt: input.source.updatedAt,
166− },
167− snapshot,
168− };
169−};
170−
171−const defaultScopeState = (): ScopeState => ({
172− version: 1,
173− sources: {},
174− policies: {},
175−});
176−
177−const createInstallation = (
178− options: CreateSqliteExecutorBackendOptions,
179−): LocalInstallation => {
180− const scopeId = ScopeIdSchema.make(options.scopeId ?? "scope_sqlite_example");
181− const actorScopeId = ScopeIdSchema.make(
182− options.actorScopeId ?? "account_sqlite_example",
183− );
184−
185− return {
186− scopeId,
187− actorScopeId,
188− };
189−};
190−
191−const openSqliteStore = (databasePath: string) => {
192− if (databasePath !== ":memory:") {
193− mkdirSync(dirname(databasePath), { recursive: true });
194− }
195−
196− const sqlite = new Database(databasePath, { create: true, strict: true });
197− const db = drizzle(sqlite);
198−
199− sqlite.exec(`
200− CREATE TABLE IF NOT EXISTS installations (
201− key TEXT PRIMARY KEY NOT NULL,
202− scope_id TEXT NOT NULL,
203− actor_scope_id TEXT NOT NULL
204− );
205−
206− CREATE TABLE IF NOT EXISTS scope_configs (
207− key TEXT PRIMARY KEY NOT NULL,
208− project_config_json TEXT NOT NULL
209− );
210−
211− CREATE TABLE IF NOT EXISTS scope_states (
212− key TEXT PRIMARY KEY NOT NULL,
213− state_json TEXT NOT NULL
214− );
215−
216− CREATE TABLE IF NOT EXISTS source_artifacts (
217− source_id TEXT PRIMARY KEY NOT NULL,
218− artifact_json TEXT NOT NULL
219− );
220−
221− CREATE TABLE IF NOT EXISTS secret_materials (
222− id TEXT PRIMARY KEY NOT NULL,
223− store_id TEXT NOT NULL,
224− name TEXT,
225− purpose TEXT NOT NULL,
226− created_at INTEGER NOT NULL,
227− updated_at INTEGER NOT NULL,
228− json TEXT NOT NULL
229− );
230−
231− CREATE TABLE IF NOT EXISTS secret_material_stored_data (
232− secret_id TEXT PRIMARY KEY NOT NULL,
233− json TEXT NOT NULL
234− );
235−
236− CREATE TABLE IF NOT EXISTS executions (
237− id TEXT PRIMARY KEY NOT NULL,
238− scope_id TEXT NOT NULL,
239− json TEXT NOT NULL
240− );
241−
242− CREATE TABLE IF NOT EXISTS execution_interactions (
243− id TEXT PRIMARY KEY NOT NULL,
244− execution_id TEXT NOT NULL,
245− status TEXT NOT NULL,
246− json TEXT NOT NULL
247− );
248−
249− CREATE TABLE IF NOT EXISTS execution_steps (
250− id TEXT PRIMARY KEY NOT NULL,
251− execution_id TEXT NOT NULL,
252− sequence INTEGER NOT NULL,
253− json TEXT NOT NULL
254− );
255− `);
256−
257− return {
258− sqlite,
259− db,
260− close: () => {
261− sqlite.close();
262− },
263− };
264−};
265−
266−type SqliteStore = ReturnType<typeof openSqliteStore>;
267−
268−const createStorageDomains = (
269− store: SqliteStore,
270− installation: LocalInstallation,
271−) => ({
272− secrets: {
273− secretStores: {
274− getById: (id: SecretStore["id"]) =>
275− id === SQLITE_SECRET_STORE_ID
276− ? {
277− id: SQLITE_SECRET_STORE_ID as SecretStore["id"],
278− scopeId: installation.scopeId,
279− kind: SQLITE_SECRET_STORE_KIND,
280− name: "SQLite Store",
281− status: "connected" as const,
282− enabled: true,
283− createdAt: 0,
284− updatedAt: 0,
285− }
286− : null,
287− listAll: () => [{
288− id: SQLITE_SECRET_STORE_ID,
289− scopeId: installation.scopeId,
290− kind: SQLITE_SECRET_STORE_KIND,
291− name: "SQLite Store",
292− status: "connected",
293− enabled: true,
294− createdAt: 0,
295− updatedAt: 0,
296− }],
297− upsert: (_secretStore: SecretStore) => {},
298− updateById: (_id: SecretStore["id"], _update: Partial<Pick<SecretStore, "name" | "status" | "enabled">>) => null,
299− removeById: (_id: SecretStore["id"]) => false,
300− },
301− getById: (id: SecretMaterial["id"]) => {
302− const row = store.db.select().from(secretMaterials).where(eq(secretMaterials.id, id)).get();
303− return row ? parseJson<SecretMaterial>(row.json) : null;
304− },
305− listAll: (): readonly SecretMaterialSummary[] =>
306− store.db.select().from(secretMaterials).all().map((row) => ({
307− id: row.id,
308− storeId: row.storeId,
309− name: row.name,
310− purpose: row.purpose,
311− createdAt: row.createdAt,
312− updatedAt: row.updatedAt,
313− })),
314− upsert: (material: SecretMaterial) =>
315− store.db.insert(secretMaterials).values({
316− id: material.id,
317− storeId: material.storeId,
318− name: material.name,
319− purpose: material.purpose,
320− createdAt: material.createdAt,
321− updatedAt: material.updatedAt,
322− json: JSON.stringify(material),
323− }).onConflictDoUpdate({
324− target: secretMaterials.id,
325− set: {
326− storeId: material.storeId,
327− name: material.name,
328− purpose: material.purpose,
329− createdAt: material.createdAt,
330− updatedAt: material.updatedAt,
331− json: JSON.stringify(material),
332− },
333− }).run(),
334− updateById: (
335− id: SecretMaterial["id"],
336− update: { name?: string | null },
337− ) => {
338− const row = store.db.select().from(secretMaterials).where(eq(secretMaterials.id, id)).get();
339− if (!row) return null;
340− const current = parseJson<SecretMaterial>(row.json);
341− const next = {
342− ...current,
343− name: update.name === undefined ? current.name : update.name,
344− updatedAt: Date.now(),
345− };
346− store.db.update(secretMaterials).set({
347− storeId: next.storeId,
348− name: next.name,
349− purpose: next.purpose,
350− createdAt: next.createdAt,
351− updatedAt: next.updatedAt,
352− json: JSON.stringify(next),
353− }).where(eq(secretMaterials.id, id)).run();
354− return {
355− id: next.id,
356− storeId: next.storeId,
357− name: next.name,
358− purpose: next.purpose,
359− createdAt: next.createdAt,
360− updatedAt: next.updatedAt,
361− } satisfies SecretMaterialSummary;
362− },
363− removeById: (id: SecretMaterial["id"]) => {
364− const row = store.db.select({ id: secretMaterials.id }).from(secretMaterials).where(
365− eq(secretMaterials.id, id),
366− ).get();
367− if (!row) return false;
368− store.db.delete(secretMaterials).where(eq(secretMaterials.id, id)).run();
369− return true;
370− },
371− secretMaterialStoredData: {
372− getBySecretId: (secretId: SecretMaterial["id"]) => {
373− const row = store.db.select().from(secretMaterialStoredData).where(
374− eq(secretMaterialStoredData.secretId, secretId),
375− ).get();
376− return row
377− ? {
378− secretId,
379− data: parseJson<SqliteSecretStoredData>(row.json),
380− }
381− : null;
382− },
383− upsert: (record: SecretMaterialStoredDataRecord) => {
384− store.db.insert(secretMaterialStoredData).values({
385− secretId: record.secretId,
386− json: JSON.stringify(record.data),
387− }).onConflictDoUpdate({
388− target: secretMaterialStoredData.secretId,
389− set: {
390− json: JSON.stringify(record.data),
391− },
392− }).run();
393− },
394− removeBySecretId: (secretId: SecretMaterial["id"]) => {
395− const row = store.db.select({ secretId: secretMaterialStoredData.secretId }).from(
396− secretMaterialStoredData,
397− ).where(eq(secretMaterialStoredData.secretId, secretId)).get();
398− if (!row) return false;
399− store.db.delete(secretMaterialStoredData).where(
400− eq(secretMaterialStoredData.secretId, secretId),
401− ).run();
402− return true;
403− },
404− },
405− },
406− executions: {
407− runs: {
408− listByScope: (scopeId: Execution["scopeId"]) =>
409− store.db.select().from(executions).where(
410− eq(executions.scopeId, scopeId),
411− ).all()
412− .map((row) => parseJson<Execution>(row.json))
413− .sort((left, right) => right.createdAt - left.createdAt),
414− getById: (executionId: Execution["id"]) => {
415− const row = store.db.select().from(executions).where(eq(executions.id, executionId)).get();
416− return row ? parseJson<Execution>(row.json) : null;
417− },
418− getByScopeAndId: (
419− scopeId: Execution["scopeId"],
420− executionId: Execution["id"],
421− ) => {
422− const row = store.db.select().from(executions).where(
423− and(eq(executions.scopeId, scopeId), eq(executions.id, executionId)),
424− ).get();
425− return row ? parseJson<Execution>(row.json) : null;
426− },
427− insert: (execution: Execution) =>
428− store.db.insert(executions).values({
429− id: execution.id,
430− scopeId: execution.scopeId,
431− json: JSON.stringify(execution),
432− }).run(),
433− update: (
434− executionId: Execution["id"],
435− patch: Partial<Omit<Execution, "id" | "scopeId" | "createdByScopeId" | "createdAt">>,
436− ) => {
437− const row = store.db.select().from(executions).where(eq(executions.id, executionId)).get();
438− if (!row) return null;
439− const next = { ...parseJson<Execution>(row.json), ...patch };
440− store.db.update(executions).set({
441− scopeId: next.scopeId,
442− json: JSON.stringify(next),
443− }).where(eq(executions.id, executionId)).run();
444− return next;
445− },
446− },
447− interactions: {
448− getById: (interactionId: ExecutionInteraction["id"]) => {
449− const row = store.db.select().from(executionInteractions).where(
450− eq(executionInteractions.id, interactionId),
451− ).get();
452− return row ? parseJson<ExecutionInteraction>(row.json) : null;
453− },
454− listByExecutionId: (executionId: ExecutionInteraction["executionId"]) =>
455− store.db.select().from(executionInteractions).where(
456− eq(executionInteractions.executionId, executionId),
457− ).all().map((row) => parseJson<ExecutionInteraction>(row.json)),
458− getPendingByExecutionId: (executionId: ExecutionInteraction["executionId"]) => {
459− const row = store.db.select().from(executionInteractions).where(
460− and(
461− eq(executionInteractions.executionId, executionId),
462− eq(executionInteractions.status, "pending"),
463− ),
464− ).get();
465− return row ? parseJson<ExecutionInteraction>(row.json) : null;
466− },
467− insert: (interaction: ExecutionInteraction) =>
468− store.db.insert(executionInteractions).values({
469− id: interaction.id,
470− executionId: interaction.executionId,
471− status: interaction.status,
472− json: JSON.stringify(interaction),
473− }).run(),
474− update: (
475− interactionId: ExecutionInteraction["id"],
476− patch: Partial<Omit<ExecutionInteraction, "id" | "executionId" | "createdAt">>,
477− ) => {
478− const row = store.db.select().from(executionInteractions).where(
479− eq(executionInteractions.id, interactionId),
480− ).get();
481− if (!row) return null;
482− const next = { ...parseJson<ExecutionInteraction>(row.json), ...patch };
483− store.db.update(executionInteractions).set({
484− executionId: next.executionId,
485− status: next.status,
486− json: JSON.stringify(next),
487− }).where(eq(executionInteractions.id, interactionId)).run();
488− return next;
489− },
490− },
491− steps: {
492− getByExecutionAndSequence: (
493− executionId: ExecutionStep["executionId"],
494− sequence: ExecutionStep["sequence"],
495− ) => {
496− const row = store.db.select().from(executionSteps).where(
497− and(
498− eq(executionSteps.executionId, executionId),
499− eq(executionSteps.sequence, sequence),
500− ),
501− ).get();
502− return row ? parseJson<ExecutionStep>(row.json) : null;
503− },
504− listByExecutionId: (executionId: ExecutionStep["executionId"]) =>
505− store.db.select().from(executionSteps).where(
506− eq(executionSteps.executionId, executionId),
507− ).orderBy(executionSteps.sequence).all().map((row) => parseJson<ExecutionStep>(row.json)),
508− insert: (step: ExecutionStep) =>
509− store.db.insert(executionSteps).values({
510− id: step.id,
511− executionId: step.executionId,
512− sequence: step.sequence,
513− json: JSON.stringify(step),
514− }).run(),
515− deleteByExecutionId: (executionId: ExecutionStep["executionId"]) => {
516− store.db.delete(executionSteps).where(eq(executionSteps.executionId, executionId)).run();
517− },
518− updateByExecutionAndSequence: (
519− executionId: ExecutionStep["executionId"],
520− sequence: ExecutionStep["sequence"],
521− patch: Partial<Omit<ExecutionStep, "id" | "executionId" | "sequence" | "createdAt">>,
522− ) => {
523− const row = store.db.select().from(executionSteps).where(
524− and(
525− eq(executionSteps.executionId, executionId),
526− eq(executionSteps.sequence, sequence),
527− ),
528− ).get();
529− if (!row) return null;
530− const next = { ...parseJson<ExecutionStep>(row.json), ...patch };
531− store.db.update(executionSteps).set({
532− id: next.id,
533− executionId: next.executionId,
534− sequence: next.sequence,
535− json: JSON.stringify(next),
536− }).where(eq(executionSteps.id, row.id)).run();
537− return next;
538− },
539− },
540− },
541−});
542−
543−export const createSqliteExecutorBackend = (
544− options: CreateSqliteExecutorBackendOptions = {},
545−): ExecutorBackend => {
546− const databasePath = options.databasePath && options.databasePath !== ":memory:"
547− ? resolvePath(options.databasePath)
548− : (options.databasePath ?? ":memory:");
549−
550− return createExecutorBackend({
551− loadRepositories: () => {
552− const store = openSqliteStore(databasePath);
553− const installation = (() => {
554− const row = store.db.select().from(installations).where(eq(installations.key, "active")).get();
555− return row
556− ? {
557− scopeId: row.scopeId as LocalInstallation["scopeId"],
558− actorScopeId: row.actorScopeId as LocalInstallation["actorScopeId"],
559− }
560− : createInstallation(options);
561− })();
562− const { secrets, executions } = createStorageDomains(store, installation);
563−
564− return {
565− scope: {
566− metadata: {
567− kind: "sqlite",
568− databasePath,
569− },
570− },
571− installation: {
572− load: () => {
573− return installation;
574− },
575− getOrProvision: () => {
576− store.db.insert(installations).values({
577− key: "active",
578− scopeId: installation.scopeId,
579− actorScopeId: installation.actorScopeId,
580− }).onConflictDoUpdate({
581− target: installations.key,
582− set: {
583− scopeId: installation.scopeId,
584− actorScopeId: installation.actorScopeId,
585− },
586− }).run();
587− return installation;
588− },
589− },
590− workspace: {
591− config: {
592− load: () => {
593− const row = store.db.select().from(scopeConfigs).where(eq(scopeConfigs.key, "project")).get();
594− const projectConfig = row
595− ? parseJson<ExecutorScopeConfig>(row.projectConfigJson)
596− : {};
597− return {
598− config: projectConfig,
599− homeConfig: null,
600− projectConfig,
601− } satisfies ScopeConfig;
602− },
603− writeProject: (config) => {
604− store.db.insert(scopeConfigs).values({
605− key: "project",
606− projectConfigJson: JSON.stringify(config),
607− }).onConflictDoUpdate({
608− target: scopeConfigs.key,
609− set: { projectConfigJson: JSON.stringify(config) },
610− }).run();
611− },
612− },
613− state: {
614− load: () => {
615− const row = store.db.select().from(scopeStates).where(eq(scopeStates.key, "active")).get();
616− return row ? parseJson<ScopeState>(row.stateJson) : defaultScopeState();
617− },
618− write: (state) => {
619− store.db.insert(scopeStates).values({
620− key: "active",
621− stateJson: JSON.stringify(state),
622− }).onConflictDoUpdate({
623− target: scopeStates.key,
624− set: { stateJson: JSON.stringify(state) },
625− }).run();
626− },
627− },
628− sourceArtifacts: {
629− build: createSourceArtifact,
630− read: (sourceId) => {
631− const row = store.db.select().from(sourceArtifacts).where(
632− eq(sourceArtifacts.sourceId, sourceId),
633− ).get();
634− return row ? parseJson<SourceArtifact>(row.artifactJson) : null;
635− },
636− write: ({ sourceId, artifact }) => {
637− store.db.insert(sourceArtifacts).values({
638− sourceId,
639− artifactJson: JSON.stringify(artifact),
640− }).onConflictDoUpdate({
641− target: sourceArtifacts.sourceId,
642− set: { artifactJson: JSON.stringify(artifact) },
643− }).run();
644− },
645− remove: (sourceId) => {
646− store.db.delete(sourceArtifacts).where(eq(sourceArtifacts.sourceId, sourceId)).run();
647− },
648− },
649− },
650− secrets: {
651− ...secrets,
652− secretStores: secrets.secretStores,
653− secretMaterialStoredData: secrets.secretMaterialStoredData,
654− resolve: ({ ref }) => {
655− const material = secrets.getById(
656− ref.secretId as SecretMaterial["id"],
657− );
658− const stored = secrets.secretMaterialStoredData.getBySecretId(
659− ref.secretId as SecretMaterial["id"],
660− );
661− if (!material || !stored) {
662− throw new Error(`Missing secret material ${ref.secretId}`);
663− }
664− return (stored.data as SqliteSecretStoredData).value;
665− },
666− store: ({ purpose, value, name, storeId }) => {
667− const now = Date.now();
668− const id = SecretMaterialIdSchema.make(`secret_${randomUUID()}`);
669− const material: SecretMaterial = {
670− id,
671− storeId: (storeId ?? SQLITE_SECRET_STORE_ID) as SecretMaterial["storeId"],
672− name: name ?? null,
673− purpose,
674− createdAt: now,
675− updatedAt: now,
676− };
677− secrets.upsert(material);
678− secrets.secretMaterialStoredData.upsert({
679− secretId: id,
680− data: {
681− value,
682− } satisfies SqliteSecretStoredData,
683− });
684− return {
685− secretId: material.id,
686− } satisfies SecretRef;
687− },
688− delete: (ref) => {
689− secrets.secretMaterialStoredData.removeBySecretId(
690− ref.secretId as SecretMaterial["id"],
691− );
692− return secrets.removeById(ref.secretId as SecretMaterial["id"]);
693− },
694− update: ({ ref, name, value }) => {
695− const updated = secrets.updateById(
696− ref.secretId as SecretMaterial["id"],
697− { name },
698− );
699− if (!updated) {
700− throw new Error(`Missing secret material ${ref.secretId}`);
701− }
702− if (value !== undefined) {
703− secrets.secretMaterialStoredData.upsert({
704− secretId: ref.secretId as SecretMaterial["id"],
705− data: {
706− value,
707− } satisfies SqliteSecretStoredData,
708− });
709− }
710− return updated;
711− },
712− },
713− executions,
714− instanceConfig: {
715− resolve: () => ({
716− platform: "sqlite-sdk-example",
717− secretStorePlugins: [
718− {
719− kind: SQLITE_SECRET_STORE_KIND,
720− displayName: "SQLite",
721− canCreate: false,
722− },
723− ],
724− defaultSecretStoreId: SQLITE_SECRET_STORE_ID,
725− }),
726− },
727− close: async () => {
728− store.close();
729− },
730− } satisfies ExecutorBackendRepositories;
731− },
732− });
733−};
examples/sqlite-sdk-consumer/tsconfig.json+0−15
@@ -1,15 +0,0 @@
1−{
2− "compilerOptions": {
3− "target": "ES2022",
4− "module": "ESNext",
5− "moduleResolution": "Bundler",
6− "strict": true,
7− "skipLibCheck": true,
8− "noEmit": true,
9− "lib": ["ES2022"],
10− "types": ["bun-types", "node"]
11− },
12− "include": [
13− "src/**/*.ts"
14− ]
15−}