Skip to content

Commit 4b43860

Browse files
Add Supabase provider support and CLI smoke testing
1 parent ae8e326 commit 4b43860

38 files changed

Lines changed: 2458 additions & 68 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@agentpond/supabase": minor
3+
"agentpond": minor
4+
---
5+
6+
Add automatic Supabase project setup and private Storage-backed trace export.

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ jobs:
4444
- name: Run build
4545
run: pnpm build
4646

47+
- name: Setup Deno
48+
uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5
49+
with:
50+
deno-version: 2.7.14
51+
52+
- name: Run Supabase Deno smoke test
53+
run: pnpm --filter @agentpond/supabase test:deno
54+
4755
- name: Run tests
4856
run: pnpm test
4957

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<a href="https://www.npmjs.com/package/agentpond"><img src="https://img.shields.io/node/v/agentpond.svg" alt="Node.js version"></a>
1515
</p>
1616

17-
AgentPond is a lightweight trace backend and CLI for AI applications. It keeps raw traces in object storage you control and syncs them into a local DuckDB cache for fast analysis by developers and coding agents. Use it with Firebase Storage, Amazon S3, Google Cloud Storage, Vercel Blob, or custom infrastructure. Firebase and Vercel include automated setup; other deployments use the manual setup path.
17+
AgentPond is a lightweight trace backend and CLI for AI applications. It keeps raw traces in object storage you control and syncs them into a local DuckDB cache for fast analysis by developers and coding agents. Use it with Firebase Storage, Supabase Storage, Amazon S3, Google Cloud Storage, Vercel Blob, or custom infrastructure. Firebase, Supabase, and Vercel include automated setup; other deployments use the manual setup path.
1818

1919
## How it works
2020

@@ -26,12 +26,13 @@ Object storage is the durable source of truth. The local DuckDB database is a re
2626

2727
Start with the [Manual deployment setup](./docs/getting-started/manual-setup.md) to choose a write path, configure object storage, instrument the application, and sync its traces into AgentPond.
2828

29-
For Firebase and Vercel, AgentPond also provides automated quick starts:
29+
For Firebase, Supabase, and Vercel, AgentPond also provides automated quick starts:
3030

3131
- [Firebase quick start](./docs/getting-started/firebase.md)
32+
- [Supabase quick start](./docs/getting-started/supabase.md)
3233
- [Vercel quick start](./docs/getting-started/vercel.md)
3334

34-
Both require Node.js 22 or newer. From the Firebase or Vercel project, run:
35+
All require Node.js 22 or newer for the CLI. From the provider project, run:
3536

3637
```sh
3738
npx agentpond init
@@ -65,7 +66,7 @@ npx agentpond sql "select id, name, session_id, total_cost from traces order by
6566

6667
## Features
6768

68-
- Direct OpenTelemetry export to Firebase Storage, S3, GCS, and Vercel Blob
69+
- Direct OpenTelemetry export to Firebase Storage, Supabase Storage, S3, GCS, and Vercel Blob
6970
- Langfuse-compatible and OTLP HTTP ingestion adapters
7071
- Incremental object-store synchronization
7172
- Local DuckDB projections for traces, observations, sessions, and scores
@@ -80,6 +81,7 @@ AgentPond does not provide a web UI, hosted trace storage, prompt management, da
8081
## Documentation
8182

8283
- [Firebase setup](./docs/getting-started/firebase.md)
84+
- [Supabase setup](./docs/getting-started/supabase.md)
8385
- [Vercel setup](./docs/getting-started/vercel.md)
8486
- [Manual deployment setup](./docs/getting-started/manual-setup.md)
8587
- [CLI reference](./docs/cli.md)

apps/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@agentpond/fastify-ingest": "workspace:*",
3232
"@agentpond/firebase": "workspace:*",
3333
"@agentpond/google": "workspace:*",
34+
"@agentpond/supabase": "workspace:*",
3435
"@agentpond/vercel": "workspace:*",
3536
"@duckdb/node-api": "^1.5.4-r.1",
3637
"@inquirer/prompts": "8.5.2",

apps/cli/src/command-support.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { environmentContextForCommand } from "./environment-context.js";
1414
export type GlobalOptions = {
1515
env?: string;
1616
json?: boolean;
17+
platform?: string;
1718
};
1819

1920
export type CommandContext = {
@@ -24,18 +25,28 @@ export type CommandContext = {
2425
export function addGlobalOptions(command: Command): Command {
2526
return command
2627
.option("--env <name>", "use an environment for this command")
28+
.option(
29+
"--platform <platform>",
30+
"select a provider for this command: firebase, supabase, or vercel",
31+
)
2732
.option("--json", "print machine-readable JSON output");
2833
}
2934

3035
export function commandContext(options: GlobalOptions): CommandContext {
31-
const context = environmentContextForCommand({ envName: options.env });
36+
const context = environmentContextForCommand({
37+
envName: options.env,
38+
platform: options.platform,
39+
});
3240
const json = Boolean(options.json);
3341
logImplicitEnvironment(options, context.config, json);
3442
return { context, json };
3543
}
3644

3745
export function configForCommand(options: GlobalOptions): AgentPondConfig {
38-
return environmentContextForCommand({ envName: options.env }).config;
46+
return environmentContextForCommand({
47+
envName: options.env,
48+
platform: options.platform,
49+
}).config;
3950
}
4051

4152
export function cacheForRead(config: AgentPondConfig): AgentPondCache {

apps/cli/src/commands/dev.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { buildServer } from "@agentpond/fastify-ingest";
99
import type { Command } from "commander";
1010
import type { FastifyInstance, FastifyLoggerOptions } from "fastify";
1111
import { CliError, parsePort } from "../cli-support.js";
12-
import { addGlobalOptions } from "../command-support.js";
12+
import { addGlobalOptions, type GlobalOptions } from "../command-support.js";
1313
import { devSdkEnvironment } from "../dev-env.js";
1414
import { manualEnvironmentContextForCommand } from "../environment-context.js";
1515

16-
type DevOptions = {
16+
type DevOptions = GlobalOptions & {
1717
host?: string;
1818
port?: string;
1919
};
@@ -23,15 +23,22 @@ export function registerDevCommand(program: Command): void {
2323
.description("start a local Langfuse SDK-compatible ingestion server")
2424
.option("--host <host>", "host to bind", "127.0.0.1")
2525
.option("--port <port>", "port to bind", "4318")
26-
.action(async (options: DevOptions) => {
27-
await startDevServer(options);
26+
.action(async (options: DevOptions, command: Command) => {
27+
const globalOptions = command.optsWithGlobals<GlobalOptions>();
28+
await startDevServer({
29+
...options,
30+
platform: globalOptions.platform,
31+
});
2832
});
2933
}
3034

3135
export async function startDevServer(options: DevOptions): Promise<void> {
3236
const host = options.host ?? "127.0.0.1";
3337
const startPort = parsePort(options.port ?? "4318");
34-
const context = manualEnvironmentContextForCommand("dev", { envName: "dev" });
38+
const context = manualEnvironmentContextForCommand("dev", {
39+
envName: "dev",
40+
platform: options.platform,
41+
});
3542
const cwd = context.rootDir;
3643
const environment = initAgentPondEnvironment("dev", { cwd });
3744
selectAgentPondEnvironment(environment.name, { cwd });

apps/cli/src/commands/env.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export function registerEnvCommand(
5858
const globalOptions = command.optsWithGlobals<GlobalOptions>();
5959
const environment = environmentContextForCommand({
6060
envName: globalOptions.env,
61+
platform: globalOptions.platform,
6162
}).config.environment;
6263
if (!environment) throw new CliError("Missing environment configuration");
6364
if (globalOptions.json) {
@@ -70,9 +71,11 @@ export function registerEnvCommand(
7071
.description("print shell exports for a manual environment")
7172
.option("--langfuse", "print only Langfuse-compatible SDK exports")
7273
.option("--otel", "print only OpenTelemetry SDK exports")
73-
.action((name: string, commandOptions: EnvOptions) => {
74+
.action((name: string, commandOptions: EnvOptions, command: Command) => {
75+
const globalOptions = command.optsWithGlobals<GlobalOptions>();
7476
const context = manualEnvironmentContextForCommand("get", {
7577
envName: name,
78+
platform: globalOptions.platform,
7679
});
7780
printEnvironmentExports(name, commandOptions, context.rootDir);
7881
});
@@ -83,6 +86,7 @@ export function registerEnvCommand(
8386
const globalOptions = command.optsWithGlobals<GlobalOptions>();
8487
const context = manualEnvironmentContextForCommand("list", {
8588
envName: globalOptions.env,
89+
platform: globalOptions.platform,
8690
});
8791
const cwd = context.rootDir;
8892
const selected = context.config.environment?.name ?? "dev";
@@ -99,8 +103,10 @@ export function registerEnvCommand(
99103
.option("--store <store>", "object store: s3, gcs, or local")
100104
.action(
101105
async (name: string, commandOptions: EnvOptions, command: Command) => {
106+
const globalOptions = command.optsWithGlobals<GlobalOptions>();
102107
const context = manualEnvironmentContextForCommand("init", {
103108
envName: name,
109+
platform: globalOptions.platform,
104110
});
105111
const store =
106112
storeFromValue(commandOptions.store) ??
@@ -116,7 +122,7 @@ export function registerEnvCommand(
116122
dbPath: environment.dbPath,
117123
store,
118124
},
119-
Boolean(command.optsWithGlobals<GlobalOptions>().json),
125+
Boolean(globalOptions.json),
120126
);
121127
},
122128
);
@@ -129,20 +135,23 @@ export function registerEnvCommand(
129135
_commandOptions: EnvOptions,
130136
command: Command,
131137
) => {
132-
const selected = await selectEnvironmentForCommand(name, promptSelect);
133-
return print(
134-
{ selected },
135-
Boolean(command.optsWithGlobals<GlobalOptions>().json),
138+
const globalOptions = command.optsWithGlobals<GlobalOptions>();
139+
const selected = await selectEnvironmentForCommand(
140+
name,
141+
promptSelect,
142+
globalOptions,
136143
);
144+
return print({ selected }, Boolean(globalOptions.json));
137145
},
138146
);
139147
}
140148

141149
async function selectEnvironmentForCommand(
142150
name: string | undefined,
143151
promptSelect: SelectEnvironmentPrompt,
152+
options: GlobalOptions,
144153
): Promise<string> {
145-
const providerContext = providerForCommand();
154+
const providerContext = providerForCommand({ platform: options.platform });
146155
if (providerContext) {
147156
if (!name) throw new CliError("Missing environment name");
148157
try {
@@ -154,7 +163,10 @@ async function selectEnvironmentForCommand(
154163
}
155164
}
156165

157-
const context = environmentContextForCommand({ envName: name });
166+
const context = environmentContextForCommand({
167+
envName: name,
168+
platform: options.platform,
169+
});
158170
const selectedName =
159171
name ?? (await promptForEnvironmentName(promptSelect, context.rootDir));
160172
return selectAgentPondEnvironment(selectedName, {

apps/cli/src/commands/init.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import type { Command } from "commander";
66
import { CliError } from "../cli-support.js";
77
import type { GlobalOptions } from "../command-support.js";
88
import {
9-
AVAILABLE_PLATFORMS,
10-
initPlatformFromValue,
119
type ProviderProjectContext,
1210
providerForCommand,
1311
} from "../providers.js";
@@ -42,10 +40,6 @@ export function agentPondInitHeader(context: {
4240
].join("\n");
4341
}
4442

45-
type InitCommandOptions = {
46-
platform?: string;
47-
};
48-
4943
export type SkillsInstallRequest = {
5044
cwd: string;
5145
source: string;
@@ -71,22 +65,20 @@ export function registerInitCommand(
7165
program
7266
.command("init")
7367
.description("set up AgentPond for the current project")
74-
.option(
75-
"--platform <platform>",
76-
`setup platform: ${AVAILABLE_PLATFORMS.join(" or ")}`,
77-
)
78-
.action(async (commandOptions: InitCommandOptions, command: Command) => {
68+
.action(async (_commandOptions: GlobalOptions, command: Command) => {
7969
const globalOptions = command.optsWithGlobals<GlobalOptions>();
8070
if (globalOptions.json) {
8171
throw new CliError("--json is not supported by npx agentpond init");
8272
}
8373

84-
const platform = initPlatformFromValue(commandOptions.platform);
8574
let setup:
8675
| { context: ProviderProjectContext; projectLabel: string }
8776
| undefined;
8877
try {
89-
const context = providerForCommand({ platform });
78+
const context = providerForCommand({
79+
allowUnlinked: true,
80+
platform: globalOptions.platform,
81+
});
9082
setup = context
9183
? { context, projectLabel: context.project.projectLabel }
9284
: undefined;
@@ -98,7 +90,7 @@ export function registerInitCommand(
9890
if (!setup) {
9991
throw new CliError(
10092
[
101-
"Automatic AgentPond setup supports Firebase and Vercel projects.",
93+
"Automatic AgentPond setup supports Firebase, Supabase, and Vercel projects.",
10294
"",
10395
"For AWS, Google Cloud, and other deployment setups, see:",
10496
MANUAL_SETUP_URL,

apps/cli/src/environment-context.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { providerForCommand } from "./providers.js";
1212
export type EnvironmentContextOptions = {
1313
cwd?: string;
1414
envName?: string;
15+
platform?: string;
1516
};
1617

1718
export function environmentContextForCommand(
@@ -30,7 +31,10 @@ export function manualEnvironmentContextForCommand(
3031
action: "dev" | "get" | "init" | "list",
3132
options: EnvironmentContextOptions = {},
3233
): AgentPondEnvironmentContext {
33-
const providerContext = providerForCommand({ cwd: options.cwd });
34+
const providerContext = providerForCommand({
35+
cwd: options.cwd,
36+
platform: options.platform,
37+
});
3438
if (providerContext) {
3539
const alternative =
3640
action === "dev"

apps/cli/src/providers.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ import type {
33
AgentPondProviderProject,
44
} from "@agentpond/core";
55
import { firebaseProvider } from "@agentpond/firebase";
6+
import { supabaseProvider } from "@agentpond/supabase";
67
import { vercelProvider } from "@agentpond/vercel";
78
import { CliError } from "./cli-support.js";
89

9-
export const AVAILABLE_PLATFORMS = ["firebase", "vercel"] as const;
10+
export const AVAILABLE_PLATFORMS = ["firebase", "supabase", "vercel"] as const;
1011
export type InitPlatform = (typeof AVAILABLE_PLATFORMS)[number];
1112

1213
const PROVIDERS_BY_PLATFORM = {
1314
firebase: firebaseProvider,
15+
supabase: supabaseProvider,
1416
vercel: vercelProvider,
1517
} satisfies Record<InitPlatform, AgentPondProvider>;
1618

@@ -35,13 +37,14 @@ export function initPlatformFromValue(
3537
}
3638

3739
export function providerForCommand(
38-
options: { cwd?: string; platform?: InitPlatform } = {},
40+
options: { allowUnlinked?: boolean; cwd?: string; platform?: string } = {},
3941
): ProviderProjectContext | undefined {
40-
if (options.platform) {
41-
const provider = PROVIDERS_BY_PLATFORM[options.platform];
42+
const platform = initPlatformFromValue(options.platform);
43+
if (platform) {
44+
const provider = PROVIDERS_BY_PLATFORM[platform];
4245
const project = provider.openProject({
4346
cwd: options.cwd,
44-
allowUnlinked: true,
47+
allowUnlinked: options.allowUnlinked,
4548
});
4649
if (!project) {
4750
throw new CliError(
@@ -57,7 +60,7 @@ export function providerForCommand(
5760
});
5861
if (projects.length > 1) {
5962
throw new CliError(
60-
`Multiple AgentPond platforms were detected: ${projects.map(({ provider }) => provider.displayName).join(", ")}. Remove the unrelated project marker or select one with npx agentpond init --platform <platform>.`,
63+
`Multiple AgentPond platforms were detected: ${projects.map(({ provider }) => provider.displayName).join(", ")}. Pass --platform <platform> to select one for this command.`,
6164
);
6265
}
6366
return projects[0];

0 commit comments

Comments
 (0)