Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Snowflake AI Kit

Connect your AI coding agent to Snowflake. Plugins for **Claude Code** and **OpenAI Codex** that automatically detect Snowflake prompts and route them to [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code-cli) — where 55+ built-in skills handle SQL, data governance, dynamic tables, ML, and more.
Connect your AI coding agent to Snowflake. Plugins for **Claude Code**, **OpenAI Codex**, and **opencode** that automatically detect Snowflake prompts and route them to [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code-cli) — where 55+ built-in skills handle SQL, data governance, dynamic tables, ML, and more.

[![Claude Code](https://img.shields.io/badge/Claude%20Code-Marketplace-8A2BE2)](https://claude.com/plugins/snowflake-cortex-code)
[![OpenAI Codex](https://img.shields.io/badge/OpenAI%20Codex-Marketplace-orange)](https://github.com/Snowflake-Labs/snowflake-ai-kit#openai-codex)
Expand All @@ -27,6 +27,14 @@ codex plugin add snowflake-cortex-code@snowflake-ai-kit

Or inside Codex, open `/plugins` and install "Snowflake Cortex Code" from the Snowflake AI Kit marketplace.

### opencode

```bash
bash install.sh --with-opencode
```

Installs the `cortex_run` tool globally to `~/.config/opencode/plugins/` plus routing rules to `~/.config/opencode/rules/`.

### That's it

Ask naturally — the plugin handles routing:
Expand All @@ -41,7 +49,7 @@ Non-Snowflake prompts ("fix the bug in auth.py", "write a unit test") stay in yo
## How It Works

```
You → Claude Code / Codex → [Plugin detects Snowflake intent] → Cortex Code CLI → Snowflake
You → Claude Code / Codex / opencode → [Plugin detects Snowflake intent] → Cortex Code CLI → Snowflake
```

1. A lightweight keyword filter runs on every prompt (~50ms, no network)
Expand Down Expand Up @@ -88,6 +96,7 @@ The bundled installer sets up both Snowflake CLI (`snow`) and Cortex Code CLI (`
| `--check` / `-Check` | Check installation status without installing |
| `--with-claude` / `-WithClaude` | Also install Claude Code CLI + plugin |
| `--with-codex` / `-WithCodex` | Also install OpenAI Codex CLI + plugin |
| `--with-opencode` / `-WithOpencode` | Also install opencode CLI + plugin |
| `--help` / `-Help` | Show help |

## Skills
Expand Down
71 changes: 64 additions & 7 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# Usage:
# bash install.sh
# bash install.sh --check
# bash install.sh --with-claude --with-codex
# bash install.sh --with-claude --with-codex --with-opencode
#

set -e
Expand Down Expand Up @@ -115,11 +115,55 @@ install_codex_cli() {
return 1
}

install_opencode_cli() {
if check_cmd opencode; then
ok "opencode CLI already installed"
return 0
fi

msg "Installing opencode CLI..."
if check_cmd npm; then
npm install -g opencode-ai 2>/dev/null && ok "opencode CLI installed via npm" && return 0
fi
warn "Could not install opencode CLI (requires Node.js + npm)."
msg " Install manually: npm install -g opencode-ai"
return 1
}

# Copy the Snowflake Cortex Code plugin to the global opencode config dir
install_opencode_plugin() {
local SCRIPT_DIR
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local PLUGIN_SRC="$SCRIPT_DIR/plugins/cortex-code"

# Resolve opencode global config directory (respects XDG_CONFIG_HOME)
local OPENCODE_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"

# --- plugin TS file ---
mkdir -p "$OPENCODE_CONFIG/plugins"
cp "$PLUGIN_SRC/.opencode/plugins/snowflake-cortex.ts" \
"$OPENCODE_CONFIG/plugins/snowflake-cortex.ts"
ok "opencode plugin installed ($OPENCODE_CONFIG/plugins/snowflake-cortex.ts)"

# --- router scripts (needed by the TS plugin via import.meta.dir) ---
local ROUTER_DEST="$OPENCODE_CONFIG/plugins/cortex-code-router/scripts/router"
mkdir -p "$ROUTER_DEST"
cp -r "$PLUGIN_SRC/scripts/router/." "$ROUTER_DEST/"
ok "opencode router scripts installed ($OPENCODE_CONFIG/plugins/cortex-code-router/scripts/router/)"

# --- rules file (global, applies to all opencode sessions) ---
mkdir -p "$OPENCODE_CONFIG/rules"
cp "$PLUGIN_SRC/.opencode/rules/snowflake.md" \
"$OPENCODE_CONFIG/rules/snowflake.md"
ok "opencode routing rules installed ($OPENCODE_CONFIG/rules/snowflake.md)"
}

# ─── Parse arguments ────────────────────────────────────────

CHECK_ONLY=false
WITH_CLAUDE=false
WITH_CODEX=false
WITH_OPENCODE=false

while [ $# -gt 0 ]; do
case $1 in
Expand All @@ -135,6 +179,10 @@ while [ $# -gt 0 ]; do
WITH_CODEX=true
shift
;;
--with-opencode)
WITH_OPENCODE=true
shift
;;
--help|-h)
echo "Snowflake AI Kit — Installer"
echo ""
Expand All @@ -147,6 +195,7 @@ while [ $# -gt 0 ]; do
echo " --check, -c Check installation status without installing"
echo " --with-claude Also install Claude Code CLI"
echo " --with-codex Also install OpenAI Codex CLI"
echo " --with-opencode Also install opencode CLI and Snowflake plugin"
echo " --help, -h Show this help"
exit 0
;;
Expand All @@ -165,10 +214,11 @@ echo ""

if $CHECK_ONLY; then
step "Checking installation status..."
check_cmd snow && ok "Snowflake CLI (snow) installed" || warn "Snowflake CLI (snow) not found"
check_cmd cortex && ok "Cortex Code CLI (cortex) installed" || warn "Cortex Code CLI (cortex) not found"
check_cmd claude && ok "Claude Code CLI (claude) installed" || warn "Claude Code CLI (claude) not found"
check_cmd codex && ok "OpenAI Codex CLI (codex) installed" || warn "OpenAI Codex CLI (codex) not found"
check_cmd snow && ok "Snowflake CLI (snow) installed" || warn "Snowflake CLI (snow) not found"
check_cmd cortex && ok "Cortex Code CLI (cortex) installed" || warn "Cortex Code CLI (cortex) not found"
check_cmd claude && ok "Claude Code CLI (claude) installed" || warn "Claude Code CLI (claude) not found"
check_cmd codex && ok "OpenAI Codex CLI (codex) installed" || warn "OpenAI Codex CLI (codex) not found"
check_cmd opencode && ok "opencode CLI installed" || warn "opencode CLI not found"
check_snowflake_auth || true
echo ""
exit 0
Expand All @@ -190,6 +240,13 @@ if $WITH_CODEX || check_cmd codex; then
install_codex_cli || true
fi

# Optional: opencode CLI + plugin
if $WITH_OPENCODE || check_cmd opencode; then
step "Installing opencode CLI and Snowflake plugin..."
install_opencode_cli || true
install_opencode_plugin || warn "Could not install opencode plugin (run: bash install.sh --with-opencode from the repo root)"
fi

# Plugin setup: add marketplace sources if the agent CLIs are present
step "Setting up plugins..."

Expand Down Expand Up @@ -227,8 +284,8 @@ if check_cmd claude; then
fi
fi

if ! check_cmd codex && ! check_cmd claude; then
msg " No agent CLI found. Install one with --with-claude or --with-codex"
if ! check_cmd codex && ! check_cmd claude && ! check_cmd opencode; then
msg " No agent CLI found. Install one with --with-claude, --with-codex, or --with-opencode"
fi

step "Checking Snowflake connection..."
Expand Down
111 changes: 111 additions & 0 deletions plugins/cortex-code/.opencode/plugins/snowflake-cortex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Snowflake Cortex Code plugin for opencode.
*
* Exposes a `cortex_run` custom tool that routes Snowflake-related prompts to
* Cortex Code CLI, plus a `tool.execute.before` hook that intercepts direct
* Snowflake CLI (snow/snowsql) bash commands and redirects them through cortex.
*
* Install via: bash install.sh --with-opencode
* Docs: https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code-cli
*/

import { type Plugin, tool } from "@opencode-ai/plugin"
import { existsSync } from "fs"
import path from "path"

// ---------------------------------------------------------------------------
// Script directory resolution — works in repo dev AND after install.sh
// ---------------------------------------------------------------------------

function findScriptDir(): string {
const candidates = [
// Repo dev: plugin lives at plugins/cortex-code/.opencode/plugins/
path.join(import.meta.dir, "../../scripts/router"),
// After install.sh --with-opencode (global):
// ~/.config/opencode/plugins/cortex-code-router/scripts/router/
path.join(import.meta.dir, "cortex-code-router/scripts/router"),
// Absolute fallback
path.join(process.env.HOME ?? "~", ".config", "opencode", "plugins", "cortex-code-router", "scripts", "router"),
]
return candidates.find(existsSync) ?? candidates[0]
}

const SCRIPT_DIR = findScriptDir()

// ---------------------------------------------------------------------------
// Snowflake CLI pattern — used by the tool.execute.before hook
// ---------------------------------------------------------------------------

// Matches direct Snowflake CLI invocations that should go through cortex instead
const DIRECT_SNOW_CLI = /\b(snow\s+(sql|object|warehouse|database|schema|table|stage|task|streamlit|connection\s+test)|snowsql)\b/i

// ---------------------------------------------------------------------------
// Plugin export
// ---------------------------------------------------------------------------

export const SnowflakeCortexCode: Plugin = async () => ({
// -------------------------------------------------------------------------
// Custom tool: cortex_run
// -------------------------------------------------------------------------
tool: {
cortex_run: tool({
description:
"Execute Snowflake work via Cortex Code CLI. " +
"Use for ANY Snowflake-related request: SQL queries, databases, warehouses, schemas, " +
"tables, data governance, dynamic tables, Cortex AI, machine learning, streaming, " +
"cost analysis, semantic views, Snowpark, native apps, Streamlit in Snowflake, " +
"iceberg tables, and more. " +
"To force routing, prefix the prompt with '$cortex-run'.",
args: {
prompt: tool.schema.string().describe("The Snowflake request to execute via Cortex Code"),
envelope: tool.schema
.enum(["RO", "RW", "RESEARCH", "DEPLOY"])
.default("RW")
.describe(
"Security envelope: RO=read-only (SELECT/SHOW/DESCRIBE), " +
"RW=read-write (default, DDL/DML allowed), " +
"RESEARCH=read+web, DEPLOY=full access"
),
resume_last: tool.schema
.boolean()
.default(false)
.describe("Resume the previous Cortex session for multi-turn continuation"),
connection: tool.schema
.string()
.optional()
.describe("Snowflake connection name from connections.toml (uses default if omitted)"),
},
async execute(args) {
const scriptPath = path.join(SCRIPT_DIR, "execute_cortex.py")
const cmdArgs: string[] = [
"--prompt", args.prompt,
"--envelope", args.envelope ?? "RW",
"--codex",
]
if (args.resume_last) cmdArgs.push("--resume-last")
if (args.connection) cmdArgs.push("--connection", args.connection)

const result = await Bun.$`python3 ${scriptPath} ${cmdArgs}`
.env({ ...process.env, OPENCODE: "1" })
.text()
return result.trim()
},
}),
},

// -------------------------------------------------------------------------
// Hook: redirect direct Snowflake CLI bash calls through cortex
// -------------------------------------------------------------------------
"tool.execute.before": async (input, output) => {
if (input.tool !== "bash") return
const command: string = (output as { args?: { command?: string } }).args?.command ?? ""
if (!DIRECT_SNOW_CLI.test(command)) return

throw new Error(
"[Snowflake Cortex Plugin] Direct Snowflake CLI commands bypass Cortex Code skills and security envelopes. " +
"Use the cortex_run tool instead — it provides 55+ built-in Snowflake skills, " +
"envelope-based permission control, and proper session management. " +
"Example: call cortex_run with your prompt and envelope='RO' for read-only operations."
)
},
})
45 changes: 45 additions & 0 deletions plugins/cortex-code/.opencode/rules/snowflake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Snowflake Routing via Cortex Code

When a prompt involves Snowflake, call the `cortex_run` tool instead of answering
directly. Cortex Code CLI has 55+ built-in skills for SQL, data governance, dynamic
tables, ML, streaming, cost intelligence, and more.

## Route to `cortex_run` when the prompt mentions

- **Snowflake platform**: Snowflake, Cortex, Snowpark, Snowsight, SnowSQL, `snow` CLI
- **Objects**: warehouses, databases, schemas, tables, stages, streams, tasks (in a Snowflake context)
- **Features**: dynamic tables, iceberg tables, semantic views, native apps
- **AI/ML**: Cortex Search, Cortex Analyst, Cortex Agent, Cortex AI functions, Snowflake ML
- **Governance**: data quality, data governance, data classification, lineage, masking policies
- **Compute/cost**: credits, compute costs, cost intelligence, query performance, warehouse sizing
- **Ingestion**: Snowpipe, Snowpipe Streaming, dynamic tables, OpenFlow
- **Dev tools**: Streamlit in Snowflake, Snowflake Notebooks, Snowpark Python
- **Patterns**: "show me my warehouses/databases/schemas/tables", "list all ...", "what access do I have"

## Do NOT route to `cortex_run` for

- Local file edits ("fix the bug in auth.py", "read config.json")
- Git operations ("git commit", "git push", "git status")
- Writing unit tests for local code unrelated to Snowflake
- npm, pip, package management for non-Snowflake code

## Security envelopes

Choose based on the operation:
- `RO` — read-only (SELECT, SHOW, DESCRIBE)
- `RW` — read-write (default; DDL, DML, CREATE, ALTER, DROP)
- `RESEARCH` — read + web access, no writes
- `DEPLOY` — full access (use only when explicitly requested)

When in doubt, use `RW`.

## Multi-turn context

For follow-up questions on a previous Cortex answer — "keep going", "drill in",
"also show me", "and for last quarter", "fix that" — pass `resume_last: true` so
Cortex sees the prior conversation.

## Explicit trigger

If the user prefixes their message with `$cortex-run`, always call `cortex_run`
regardless of content.
16 changes: 14 additions & 2 deletions plugins/cortex-code/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Cortex Code plugin for Claude Code and OpenAI Codex
# Cortex Code plugin for Claude Code, OpenAI Codex, and opencode

Route Snowflake work from Claude Code or OpenAI Codex to Cortex Code automatically. Ask about your data naturally — the plugin detects Snowflake intent and delegates to Cortex Code where 55+ built-in skills handle the work. Non-Snowflake prompts stay in your current agent.
Route Snowflake work from Claude Code, OpenAI Codex, or opencode to Cortex Code automatically. Ask about your data naturally — the plugin detects Snowflake intent and delegates to Cortex Code where 55+ built-in skills handle the work. Non-Snowflake prompts stay in your current agent.

## How It Works

Expand Down Expand Up @@ -55,6 +55,18 @@ codex plugin add snowflake-cortex-code@snowflake-ai-kit

Or inside Codex, open `/plugins` and install "Snowflake Cortex Code" from the Snowflake AI Kit marketplace.

### opencode

```bash
bash install.sh --with-opencode
```

This copies the plugin to `~/.config/opencode/plugins/` and routing rules to `~/.config/opencode/rules/` — both are loaded globally for all opencode sessions.

> **Note:** opencode has no pre-LLM hook equivalent to Claude Code's `UserPromptSubmit`, so routing is LLM-driven via the rules file plus a `tool.execute.before` hook that blocks direct `snow sql`/`snowsql` bash calls. In practice the LLM reliably calls `cortex_run` for Snowflake work given the rules file.

To update, re-run `bash install.sh --with-opencode`.

## Security Model

The router wraps Cortex execution with a security layer. Three approval modes:
Expand Down
7 changes: 6 additions & 1 deletion plugins/cortex-code/scripts/router/execute_cortex.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ def execute_cortex_streaming(prompt: str, connection: Optional[str] = None,
env["CORTEX_CODE_ENTRYPOINT"] = "Claude Code Plugin"
elif os.environ.get("PLUGIN_ROOT") or os.environ.get("CLAUDE_PLUGIN_ROOT"):
env["CORTEX_CODE_ENTRYPOINT"] = "Codex Plugin"
elif os.environ.get("OPENCODE"):
env["CORTEX_CODE_ENTRYPOINT"] = "OpenCode Plugin"
else:
env["CORTEX_CODE_ENTRYPOINT"] = "Unknown"

Expand Down Expand Up @@ -592,7 +594,10 @@ def _run_codex_mode(args):
}) + "\n"

env = os.environ.copy()
env["CORTEX_CODE_ENTRYPOINT"] = "Codex Plugin"
if os.environ.get("OPENCODE"):
env["CORTEX_CODE_ENTRYPOINT"] = "OpenCode Plugin"
else:
env["CORTEX_CODE_ENTRYPOINT"] = "Codex Plugin"

perm_mode = "--dangerously-allow-all-tool-calls --no-mcp"
debug_cmd = f"cortex --output-format stream-json --input-format stream-json {perm_mode} (envelope={args.envelope})"
Expand Down
Loading