Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2d590f7
feat(cli): v1 surface + backend prereqs
saddlepaddle Apr 30, 2026
ab86059
ci(cli): build all four release targets on native runners
saddlepaddle Apr 30, 2026
bf3da2a
refactor(cli): promote host start/stop/status to top-level, rename au…
saddlepaddle Apr 30, 2026
fcb44ea
ci(cli): pass SUPERSET_WEB_URL at build time and verify native modules
saddlepaddle Apr 30, 2026
863c0bc
refactor: rename CLOUD_API_URL → SUPERSET_API_URL, read URLs from gh …
saddlepaddle Apr 30, 2026
2e6fdb5
feat(cli): publish to rolling cli-latest release; simplify update cha…
saddlepaddle Apr 30, 2026
1072047
feat(cli): superset update --to <version> for pinning
saddlepaddle Apr 30, 2026
c8525c2
feat(cli-framework): let commands declare their own --version, rename…
saddlepaddle Apr 30, 2026
767cc8d
fix(cli): send sk_live_ api keys via x-api-key, not Authorization: Be…
saddlepaddle Apr 30, 2026
30c6e0b
docs(cli): add CLI docs product with structured Command component
saddlepaddle Apr 30, 2026
8b3eaf5
fix(ci): make Linux CLI builds work end-to-end
saddlepaddle Apr 30, 2026
2f9246e
feat(mcp-v2): cloud-first MCP server with API key + OAuth 2.1 auth
saddlepaddle Apr 30, 2026
04a6dbd
ci(cli): drop darwin-x64, expand homebrew formula to linux-arm64
saddlepaddle Apr 30, 2026
48b042c
ci(cli): mark releases as --prerelease to dodge /releases/latest shadow
saddlepaddle Apr 30, 2026
b1874cc
docs(cli): add v1 plans for distribution scope, audit, and release ch…
saddlepaddle Apr 30, 2026
abaa3e3
WIP
saddlepaddle Apr 30, 2026
aa54459
ci(cli): split into reusable build + release workflows; smoke test in…
saddlepaddle Apr 30, 2026
017b8f6
chore(db): add oauth pkce/subject_type and subscription billing fields
saddlepaddle Apr 30, 2026
ea17dc7
Merge remote-tracking branch 'origin/main' into cli-v1
saddlepaddle Apr 30, 2026
a07d982
feat(cli): replace implicit local-host default with --host/--local flags
saddlepaddle Apr 30, 2026
12a3a96
chore(web): drop dead pendingAuthRedirect cookie helper
saddlepaddle Apr 30, 2026
894d12d
chore(mcp-v2): drop empty test script
saddlepaddle Apr 30, 2026
e6c27ca
chore(web): drop stale cookie comment in cli/authorize page
saddlepaddle Apr 30, 2026
cb724cf
feat(db): add better-auth 1.6.5 schema columns
saddlepaddle Apr 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 43 additions & 42 deletions .github/workflows/build-cli.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
name: Build CLI Distribution
name: Build CLI

# Reusable build workflow. Callers (ci-cli.yml, release-cli.yml) pass a
# matrix as JSON to control which targets to build.

on:
push:
tags: ["cli-v*"]
workflow_dispatch:
workflow_call:
inputs:
targets:
description: 'JSON array of {os, target} build matrix entries'
required: true
type: string

jobs:
build:
name: Build ${{ matrix.target }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
target: darwin-arm64
- os: ubuntu-latest
target: linux-x64

include: ${{ fromJSON(inputs.targets) }}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
Expand All @@ -32,48 +33,48 @@ jobs:
with:
node-version: 22

- name: Install dependencies
- name: Install dependencies (Linux — skip scripts, rebuild manually)
if: startsWith(matrix.target, 'linux-')
run: |
# Bun's install-script runner has a cache materialization race
# against node-pty's compile-from-source path on Linux (node-pty
# ships no Linux prebuilds). Skip scripts at install, then rebuild
# native deps explicitly via npm/node-gyp.
set -euo pipefail
bun install --frozen --ignore-scripts
PTY_DIR=$(ls -d node_modules/.bun/node-pty@*/node_modules/node-pty)
(cd "$PTY_DIR" && npx --yes node-gyp rebuild)
npm rebuild better-sqlite3
npm rebuild @parcel/watcher

- name: Install dependencies (macOS)
if: startsWith(matrix.target, 'darwin-')
run: bun install --frozen

- name: Build distribution
working-directory: packages/cli
env:
RELAY_URL: https://relay.superset.sh
CLOUD_API_URL: https://api.superset.sh
RELAY_URL: ${{ secrets.RELAY_URL }}
SUPERSET_API_URL: ${{ vars.SUPERSET_API_URL }}
SUPERSET_WEB_URL: ${{ vars.SUPERSET_WEB_URL }}
run: bun run build:dist --target=${{ matrix.target }}

- name: Smoke test binary
run: |
DIST="./packages/cli/dist/superset-${{ matrix.target }}"
"$DIST/bin/superset" --version
"$DIST/bin/superset" --help
"$DIST/lib/node" --version
NODE_PATH="$DIST/lib/node_modules" "$DIST/lib/node" -e '
for (const m of ["better-sqlite3", "node-pty", "@parcel/watcher", "libsql"]) {
require(m);
console.log(m, "OK");
}
'

- name: Upload tarball
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: superset-${{ matrix.target }}
path: packages/cli/dist/superset-${{ matrix.target }}.tar.gz
if-no-files-found: error

release:
name: Create GitHub Release
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/cli-v')
permissions:
contents: write

steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1

- name: Download all artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: release-artifacts
pattern: superset-*
merge-multiple: true

- name: Create Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ github.ref_name }}" \
release-artifacts/*.tar.gz \
--title "Superset CLI ${{ github.ref_name }}" \
--generate-notes \
--draft
8 changes: 7 additions & 1 deletion .github/workflows/bump-homebrew.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
for target in darwin-arm64 linux-x64; do
for target in darwin-arm64 linux-x64 linux-arm64; do
url="https://github.com/superset-sh/superset/releases/download/${TAG}/superset-${target}.tar.gz"
echo "Fetching SHA for $url"
tmp=$(mktemp)
Expand All @@ -65,6 +65,7 @@ jobs:
VERSION: ${{ steps.version.outputs.version }}
DARWIN_ARM64_SHA: ${{ steps.shas.outputs.darwin_arm64_sha }}
LINUX_X64_SHA: ${{ steps.shas.outputs.linux_x64_sha }}
LINUX_ARM64_SHA: ${{ steps.shas.outputs.linux_arm64_sha }}
run: |
set -euo pipefail
python3 - <<'PYEOF' > homebrew-tap/Formula/superset.rb
Expand All @@ -87,6 +88,10 @@ jobs:
url "https://github.com/superset-sh/superset/releases/download/cli-v#{{version}}/superset-linux-x64.tar.gz"
sha256 "{linux_x64}"
end
on_arm do
url "https://github.com/superset-sh/superset/releases/download/cli-v#{{version}}/superset-linux-arm64.tar.gz"
sha256 "{linux_arm64}"
end
end

def install
Expand All @@ -104,6 +109,7 @@ jobs:
version=os.environ["VERSION"],
darwin_arm64=os.environ["DARWIN_ARM64_SHA"],
linux_x64=os.environ["LINUX_X64_SHA"],
linux_arm64=os.environ["LINUX_ARM64_SHA"],
), end="")
PYEOF

Expand Down
33 changes: 31 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:

- name: Test
env:
RELAY_URL: https://relay.superset.sh
RELAY_URL: ${{ secrets.RELAY_URL }}
run: bun run test

typecheck:
Expand Down Expand Up @@ -147,5 +147,34 @@ jobs:

- name: Build Desktop
env:
RELAY_URL: https://relay.superset.sh
RELAY_URL: ${{ secrets.RELAY_URL }}
run: bun turbo run build --filter=@superset/desktop

build-cli:
name: Build CLI
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Setup Bun
id: setup-bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: .bun-version

- name: Cache dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ steps.setup-bun.outputs.bun-revision }}-${{ hashFiles('bun.lock') }}

- name: Install dependencies
run: bun install --frozen --ignore-scripts

- name: Build CLI
env:
RELAY_URL: ${{ secrets.RELAY_URL }}
SUPERSET_API_URL: ${{ vars.SUPERSET_API_URL }}
SUPERSET_WEB_URL: ${{ vars.SUPERSET_WEB_URL }}
run: bun turbo run build --filter=@superset/cli
82 changes: 82 additions & 0 deletions .github/workflows/release-cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Release CLI

# Fires on cli-v* tag push. Builds the full 3-target matrix and publishes
# a draft GitHub Release plus a rolling cli-latest pointer. workflow_dispatch
# is the manual escape hatch for testing the full pipeline without cutting
# a tag (the release job is gated to tag pushes only).

on:
push:
tags: ["cli-v*"]
workflow_dispatch:

jobs:
build:
uses: ./.github/workflows/build-cli.yml
with:
targets: '[{"os":"ubuntu-latest","target":"linux-x64"},{"os":"macos-14","target":"darwin-arm64"},{"os":"ubuntu-24.04-arm","target":"linux-arm64"}]'
secrets: inherit

release:
name: Create GitHub Release
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/cli-v')
permissions:
contents: write

steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1

- name: Download all artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
path: release-artifacts
pattern: superset-*
merge-multiple: true

- name: Create Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# --prerelease is a workaround: GitHub's /releases/latest endpoint
# doesn't filter by tag prefix, so a published non-prerelease cli-v*
# release would shadow desktop's auto-updater (which currently reads
# /releases/latest/download/latest-{mac,linux}.yml). Tracked in
# plans/release-channels-spec.md — drop once desktop migrates to a
# desktop-latest rolling pointer.
gh release create "${{ github.ref_name }}" \
release-artifacts/*.tar.gz \
--title "Superset CLI ${{ github.ref_name }}" \
--generate-notes \
--draft \
--prerelease

- name: Publish version manifest
env:
VERSION_TAG: ${{ github.ref_name }}
run: |
# Strip the `cli-v` prefix; the manifest carries just the semver.
echo "${VERSION_TAG#cli-v}" > release-artifacts/version.txt

- name: Update rolling cli-latest release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION_TAG: ${{ github.ref_name }}
run: |
# `cli-latest` is a rolling tag/release that always points at the
# newest published CLI build. The update command fetches assets
# from `/releases/download/cli-latest/` so it never has to filter
# the repo's release list (where desktop releases would otherwise
# shadow the latest CLI release on the global /releases/latest
# endpoint). Recreate it on every CLI release.
set -euo pipefail
gh release delete cli-latest --yes --cleanup-tag || true
gh release create cli-latest \
release-artifacts/*.tar.gz \
release-artifacts/version.txt \
--title "Latest Superset CLI" \
--notes "Rolling pointer to the latest published CLI release. See [${VERSION_TAG}](https://github.com/${{ github.repository }}/releases/tag/${VERSION_TAG}) for changelog." \
--target "${{ github.sha }}" \
--prerelease
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@superset/auth": "workspace:*",
"@superset/db": "workspace:*",
"@superset/mcp": "workspace:*",
"@superset/mcp-v2": "workspace:*",
"@superset/shared": "workspace:*",
"@superset/trpc": "workspace:*",
"@t3-oss/env-nextjs": "^0.13.8",
Expand Down
69 changes: 69 additions & 0 deletions apps/api/src/app/api/v2/agent/[transport]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
import {
createMcpServer,
isMcpUnauthorized,
type McpContext,
resolveMcpContext,
} from "@superset/mcp-v2";
import { env } from "@/env";
import { posthog } from "@/lib/analytics";
import { getOAuthProtectedResourceMetadataUrl } from "@/lib/oauth-metadata";

function unauthorizedResponse(req: Request, message: string): Response {
return new Response(
JSON.stringify({ error: { code: "UNAUTHORIZED", message } }),
{
status: 401,
headers: {
"WWW-Authenticate": `Bearer realm="superset", resource_metadata="${getOAuthProtectedResourceMetadataUrl(req)}"`,
"Content-Type": "application/json",
},
},
);
}

async function handle(req: Request): Promise<Response> {
let ctx: McpContext;
try {
ctx = await resolveMcpContext(req, env.NEXT_PUBLIC_API_URL);
} catch (error) {
if (isMcpUnauthorized(error)) {
return unauthorizedResponse(req, error.message);
}
throw error;
}

const server = createMcpServer({
onToolCall: (event) => {
posthog.capture({
distinctId: event.userId,
event: "mcp_tool_called",
properties: {
tool: event.toolName,
organization_id: event.organizationId,
auth_source: event.source,
client_label: event.clientLabel,
duration_ms: event.durationMs,
success: event.success,
error_message: event.errorMessage,
mcp_server: "superset-v2",
mcp_server_version: "0.1.0",
},
groups: { organization: event.organizationId },
});
},
});
const transport = new WebStandardStreamableHTTPServerTransport();
await server.connect(transport);

return transport.handleRequest(req, {
authInfo: {
token: ctx.bearerToken,
clientId: ctx.source === "api-key" ? "api-key" : "oauth",
scopes: ["mcp:full"],
extra: { mcpContext: ctx },
},
});
}

export { handle as GET, handle as POST, handle as DELETE };
4 changes: 2 additions & 2 deletions apps/desktop/HOST_SERVICE_BOUNDARIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ import { LocalModelProvider } from "@superset/host-service/providers/desktop";
createApp({
config: {
dbPath: path.join(orgDir, "host.db"),
cloudApiUrl: env.CLOUD_API_URL,
cloudApiUrl: env.SUPERSET_API_URL,
migrationsPath: app.isPackaged
? path.join(process.resourcesPath, "resources/host-migrations")
: path.join(app.getAppPath(), "../../packages/host-service/drizzle"),
Expand All @@ -381,7 +381,7 @@ import { createApp, PskHostAuthProvider, JwtApiAuthProvider,
createApp({
config: {
dbPath: env.HOST_DB_PATH,
cloudApiUrl: env.CLOUD_API_URL,
cloudApiUrl: env.SUPERSET_API_URL,
migrationsPath: join(import.meta.dirname, "../../drizzle"),
allowedOrigins: env.CORS_ORIGINS,
},
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/host-service/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { z } from "zod";
export const env = createEnv({
server: {
AUTH_TOKEN: z.string().min(1),
CLOUD_API_URL: z.string().url(),
SUPERSET_API_URL: z.string().url(),
HOST_DB_PATH: z.string().min(1),
HOST_MIGRATIONS_FOLDER: z.string().min(1),
HOST_SERVICE_SECRET: z.string().min(1),
Expand Down
Loading
Loading