Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/supercode-cli/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "supercode-cli",
"version": "0.1.94",
"version": "0.1.95",
"description": "AI-powered coding agent CLI",
"main": "dist/main.js",
"bin": {
Expand Down
6 changes: 5 additions & 1 deletion apps/supercode-cli/server/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,14 @@ const PLANS = [
]

const MODELS = [
// ── Spark / Spark Premium (minTier: "spark") ──
// ── Spark / Spark Premium (minTier: "spark") — all open / cloud free models ──
// Spark Premium uses the same open catalog (higher limits/credits); gating is by tier order.
{ slug: "deepseek-v4-flash", displayName: "DeepSeek V4 Flash", provider: "deepseek", minTier: "spark", inputPrice: 0.15, outputPrice: 0.60, cachedPrice: 0 },
{ slug: "deepseek/deepseek-v4-flash", displayName: "DeepSeek V4 Flash (OR)", provider: "openrouter", minTier: "spark", inputPrice: 0.15, outputPrice: 0.60, cachedPrice: 0 },
{ slug: "hy3", displayName: "Hunyuan Hy3", provider: "supercode", minTier: "spark", inputPrice: 0.15, outputPrice: 0.60, cachedPrice: 0 },
{ slug: "MiniMax-M3", displayName: "MiniMax M3", provider: "minimax", minTier: "spark", inputPrice: 0.20, outputPrice: 0.80, cachedPrice: 0.04 },
// CLI cloud picker uses this slug; keep both so plan-gate matches either form
{ slug: "minimax-m3", displayName: "MiniMax M3 (cloud)", provider: "supercode", minTier: "spark", inputPrice: 0.20, outputPrice: 0.80, cachedPrice: 0.04 },
Comment on lines +165 to +172

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

jq '{
  prisma: (.devDependencies.prisma // .dependencies.prisma),
  prismaClient: .dependencies["`@prisma/client`"]
}' apps/supercode-cli/server/package.json

fd -HI '^(bun\.lock|bun\.lockb)$' . -x rg -n -C 2 '"(prisma|`@prisma/client`)"' {}

Repository: yashdev9274/supercli

Length of output: 28499


🌐 Web query:

Prisma 7.8.0 @prisma/client 7.8.0 release notes compatibility prisma client runtime

💡 Result:

The Prisma 7.8.0 release, published on April 22, 2026, focuses on performance enhancements and bug fixes for the Prisma Client and migration tools [1][2]. A key addition in version 7.8.0 is the queryPlanCacheMaxSize option in the PrismaClient constructor [3][2][4]. This allows developers to control the query plan cache size: - Setting this to 0 disables the cache entirely [3][2][5]. - Omitting it uses the default cache size [3][2][5]. - Increasing the value can improve performance for applications executing many unique queries, while decreasing it can reduce memory usage [2][6][5]. Regarding Prisma Client runtime compatibility, Prisma maintains support for multiple environments. The internal runtime targets identified for the client include 'nodejs', 'workerd', 'vercel-edge', and 'deno' [7]. The public-facing runtime options supported by the client generator comprise 'nodejs', 'deno', 'bun', 'workerd', 'cloudflare', 'vercel-edge', and 'edge-light' [7][8]. The client handles environment detection to route requests to the appropriate runtime logic [7][9]. Bug fixes in this release include: - Resolved issues with equality filter panics and incorrect JSONB casts for PostgreSQL JSON list columns [1][2]. - Fixed case-insensitive JSON field filtering [1][2]. - Addressed incorrect parameterization of enums with @map and fixed a database parameter limit check (P2029) [1][6]. - Corrected a regression causing missing SQL Server VARCHAR casts [1][2]. - Fixed PostgreSQL introspection regarding sequence defaults [1][2]. - Included minor updates for the @prisma/adapter-d1 to handle savepoint operations as no-ops [1][2].

Citations:


🏁 Script executed:

#!/usr/bin/env sh
set -euo pipefail

echo "Server package declarations:"
jq '{
  devDependencies: { prisma: (.devDependencies.prisma // null) },
  dependencies: { prismaClient: (.dependencies["`@prisma/client`"] // null), adapterPg: (.dependencies["`@prisma/adapter-pg`"] // null) }
}' apps/supercode-cli/server/package.json

echo
echo "Generated Prisma client metadata, if present:"
if [ -f apps/supercode-cli/server/node_modules/.prisma/client/edge.js ]; then
  echo "edge.js exists"
  wc -l apps/supercode-cli/server/node_modules/.prisma/client/edge.js
elif [ -f apps/supercode-cli/server/node_modules/.prisma/client/index.js ]; then
  echo "index.js exists"
  wc -l apps/supercode-cli/server/node_modules/.prisma/client/index.js
else
  echo "no .prisma/client binaries under server"
fi

if [ -d apps/supercode-cli/server/node_modules/prisma/client ]; then
  echo
  echo "Installed prisma package version:"
  node -e 'const fs = require("fs"); const p=JSON.parse(fs.readFileSync("apps/supercode-cli/server/node_modules/prisma/package.json","utf8")); console.log(p.version)'
fi
if [ -d apps/supercode-cli/server/node_modules/@prisma/client ]; then
  echo
  echo "Installed `@prisma/client` version:"
  node -e 'const fs = require("fs"); const p=JSON.parse(fs.readFileSync("apps/supercode-cli/server/node_modules/@prisma/client/package.json","utf8")); console.log(p.version)'
fi

Repository: yashdev9274/supercli

Length of output: 409


🏁 Script executed:

#!/bin/sh
set -euo pipefail

echo "apps/supercode-cli/server/package.json:"
cat -n apps/supercode-cli/server/package.json

echo
echo "Package manager config files:"
for f in bun.lock bun.lockb pnpm-lock.yaml package-lock.json yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    wc -l "$f"
    rg -n -C 1 '"(prisma|`@prisma/client`)"|prisma@|`@prisma/client`@' "$f" | head -200
  fi
done

echo
echo "Top-level generated client locations:"
fd -HI '^(edge\.js|index\.js)$' node_modules/.prisma/client 2>/dev/null || true

Repository: yashdev9274/supercli

Length of output: 15566


Align the Prisma CLI version with the Prisma Client.

@prisma/client resolves to 7.8.0, but prisma resolves to 7.5.0. A db:generate + seed run will use the lower Prisma CLI if generated types were last produced by it; regenerate Prisma to 7.8.0 so the CLI, generated client, and seed path use the same client runtime.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/prisma/seed.ts` around lines 165 - 172, The Prisma
CLI and generated client versions must be aligned at 7.8.0. Update the project’s
Prisma CLI dependency and regenerate the client so db:generate and the seed path
use the same 7.8.0 runtime as `@prisma/client`; leave the model seed entries
unchanged.

{ slug: "mimo-v2.5", displayName: "MiMo v2.5", provider: "orcarouter", minTier: "spark", inputPrice: 0.15, outputPrice: 0.60, cachedPrice: 0 },
{ slug: "kimi-k2-6", displayName: "Kimi K2.6", provider: "openrouter", minTier: "spark", inputPrice: 0.15, outputPrice: 0.60, cachedPrice: 0 },
{ slug: "kimi-k2-7-code", displayName: "Kimi K2.7 Code", provider: "openrouter", minTier: "spark", inputPrice: 0.25, outputPrice: 1.00, cachedPrice: 0 },
Expand Down
23 changes: 23 additions & 0 deletions apps/supercode-cli/server/src/cli/ai/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ function isYashDewasthale(): boolean {
)
}

// Get user's plan tier for display
async function getUserPlanTier(): Promise<string> {
if (!currentUser) return ""
try {
const prisma = (await import("src/lib/prisma")).default
const subscription = await prisma.subscription.findFirst({
where: {
userId: currentUser.id,
status: { in: ["active", "trialing"] },
},
include: { plan: true },
orderBy: { createdAt: "desc" },
})
if (!subscription?.plan) return ""
return subscription.plan.tier
} catch {
return ""
}
}

export async function initConversation(userId: string, conversationId: string | null = null, mode = "chat") {
const thinking = createThinking("loading conversation")
const conversation = await getOrCreateConversation(conversationId, mode)
Expand Down Expand Up @@ -1956,6 +1976,9 @@ export async function chatLoop(
footer.setModel(provider.modelName)
footer.setContextWindow(contextWindow)
footer.setTokens(0)
// Set plan tier in footer
const planTier = await getUserPlanTier()
if (planTier) footer.setPlan(planTier)
footer.mount()

// Re-mount on terminal resize so the status row tracks the new bottom row.
Expand Down
167 changes: 112 additions & 55 deletions apps/supercode-cli/server/src/cli/commands/slashCommands/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import chalk from "chalk"
import { theme, heavyDivider, CARET } from "src/cli/utils/tui.ts"
import { type ModelProvider, providerMeta } from "src/cli/ai/provider.ts"
import { getCliConfig, saveCliConfig, saveProviderApiKey, getByokSessionKey } from "src/lib/cli-config.ts"
import { getStoredToken } from "src/lib/token"
import { getCurrentUser } from "src/lib/api-client"
import prisma from "src/lib/prisma"

interface ModelEntry {
value: string
Expand All @@ -13,6 +16,33 @@ interface ModelEntry {
desc: string
}

// ── Tier detection ──────────────────────────────────────────────
export type UserTier = "spark" | "spark-premium" | "pro" | "ultra" | "none"

async function getUserTier(): Promise<UserTier> {
try {
const token = await getStoredToken()
if (!token?.access_token) return "none"

const result = await getCurrentUser()
if (!result.ok) return "none"

const subscription = await prisma.subscription.findFirst({
where: {
userId: result.user.id,
status: { in: ["active", "trialing"] },
},
include: { plan: true },
orderBy: { createdAt: "desc" },
})

if (!subscription?.plan) return "none"
return subscription.plan.tier as UserTier
} catch {
return "none"
}
}

const SECTION_CLOUD = "__section_cloud__"
const SECTION_BYOK = "__section_byok__"
const SECTION_CONCENTRATEAI = "__section_concentrateai__"
Expand All @@ -22,15 +52,13 @@ const SECTION_MINIMAX = "__section_minimax__"
const SECTION_NVIDIA = "__section_nvidia__"
const SECTION_OPENROUTER = "__section_openrouter__"
const SECTION_ORCAROUTER = "__section_orcarouter__"
const SECTION_CLOUD_PREMIUM = "__section_cloud_premium__"

export const ALL_SECTIONS = new Set([
SECTION_CLOUD, SECTION_BYOK,
SECTION_CONCENTRATEAI, SECTION_MERGEDEV,
SECTION_GOOGLE, SECTION_MINIMAX,
SECTION_NVIDIA, SECTION_OPENROUTER,
SECTION_ORCAROUTER,
SECTION_CLOUD_PREMIUM,
])

const SECTION_LABELS: Record<string, string> = {
Expand All @@ -43,21 +71,24 @@ const SECTION_LABELS: Record<string, string> = {
[SECTION_NVIDIA]: "NVIDIA NIM",
[SECTION_OPENROUTER]: "OpenRouter",
[SECTION_ORCAROUTER]: "OrcaRouter",
[SECTION_CLOUD_PREMIUM]: "Supercode Cloud Premium",
}

const isMainSection = (v: string) => v === SECTION_CLOUD || v === SECTION_BYOK || v === SECTION_CLOUD_PREMIUM
const isMainSection = (v: string) => v === SECTION_CLOUD || v === SECTION_BYOK

// Models available through the Supercode cloud proxy (no API key needed)
export const CLOUD_MODELS: ModelEntry[] = [
{ value: "deepseek-v4-flash", label: "DeepSeek V4 Flash", provider: "supercode", cost: "free", desc: "Fast & capable" },
// { value: "glm-5.2", label: "GLM 5.2", provider: "supercode", cost: "free", desc: "Latest GLM" },
// { value: "glm-5.1", label: "GLM 5.1", provider: "supercode", cost: "free", desc: "Stable & reliable" },
// { value: "kimi-k2-6", label: "Kimi K2.6", provider: "supercode", cost: "free", desc: "Long context" },
// { value: "minimax-m3", label: "MiniMax M3", provider: "supercode", cost: "free", desc: "Fast & smart" },
{ value: "hy3", label: "Hunyuan Hy3", provider: "supercode", cost: "free", desc: "Tencent flagship" },
{ value: "mimo-v2.5", label: "Mimo v2.5", provider: "supercode", cost: "free", desc: "Novita" },
// { value: "fireworks/nemotron-3-ultra-nvfp4", label: "Nemotron 3 Ultra NVFP4", provider: "supercode", cost: "free", desc: "Fireworks" },
{ value: "deepseek-v4-flash", label: "DeepSeek V4 Flash", provider: "supercode", cost: "", desc: "Fast & capable" },
{ value: "kimi-k2-6", label: "Kimi K2.6", provider: "supercode", cost: "", desc: "Long context" },
{ value: "kimi-k2-7-code", label: "Kimi K2.7 Code", provider: "supercode", cost: "", desc: "Code specialist" },
{ value: "kimi-k3", label: "Kimi K3", provider: "supercode", cost: "", desc: "Moonshot latest" },
{ value: "minimax-m3", label: "MiniMax M3", provider: "supercode", cost: "", desc: "Fast & smart" },
{ value: "glm-5.2", label: "GLM 5.2", provider: "supercode", cost: "", desc: "Latest GLM" },
{ value: "glm-5.1", label: "GLM 5.1", provider: "supercode", cost: "", desc: "Stable & reliable" },
{ value: "mimo-v2.5", label: "Mimo v2.5", provider: "supercode", cost: "", desc: "Novita" },
{ value: "hy3", label: "Hunyuan Hy3", provider: "supercode", cost: "", desc: "Tencent flagship" },
{ value: "gemini-2.5-flash", label: "Gemini 2.5 Flash", provider: "supercode", cost: "", desc: "Google smart & fast" },
{ value: "meta/llama-3.3-70b-instruct", label: "Llama 3.3 70B", provider: "supercode", cost: "", desc: "Open weights" },
{ value: "orcarouter/auto", label: "OrcaRouter Auto", provider: "supercode", cost: "", desc: "Auto-pick cheapest" },
]

// Models available when you bring your own API key (BYOK)
Expand Down Expand Up @@ -154,7 +185,7 @@ export const BYOK_MODELS: ModelEntry[] = [
{ value: "openai/gpt-4.1-nano", label: "GPT-4.1 Nano", provider: "openrouter", cost: "0.3x", desc: "Tiny & fast" },
{ value: "openai/o3-mini", label: "o3-mini", provider: "openrouter", cost: "3x", desc: "Reasoning mini" },
{ value: "openai/o4-mini", label: "o4-mini", provider: "openrouter", cost: "3x", desc: "Reasoning v4 mini" },
{ value: "openai/gpt-oss-120b:free", label: "GPT OSS 120B", provider: "openrouter", cost: "free", desc: "Open-weight free" },
{ value: "openai/gpt-oss-120b:free", label: "GPT OSS 120B", provider: "openrouter", cost: "", desc: "Open-weight" },
{ value: "x-ai/grok-3", label: "Grok 3", provider: "openrouter", cost: "10x", desc: "xAI flagship" },
{ value: "x-ai/grok-3-mini", label: "Grok 3 Mini", provider: "openrouter", cost: "5x", desc: "Compact Grok" },
{ value: "x-ai/grok-3-mini-fast", label: "Grok 3 Mini Fast", provider: "openrouter", cost: "5x", desc: "Fast Grok" },
Expand Down Expand Up @@ -201,28 +232,56 @@ export const BYOK_MODELS: ModelEntry[] = [
{ value: "kimi/kimi-k3", label: "Kimi K3", provider: "orcarouter", cost: "", desc: "Moonshot latest" },
{ value: "kimi/kimi-k2.6", label: "Kimi K2.6", provider: "orcarouter", cost: "", desc: "Long context" },
{ value: "minimax/minimax-m3", label: "MiniMax M3", provider: "orcarouter", cost: "", desc: "Fast & smart" },
{ value: "orcarouter/auto", label: "OrcaRouter Auto", provider: "orcarouter", cost: "0x", desc: "Auto-pick cheapest" },
{ value: "orcarouter/auto", label: "OrcaRouter Auto", provider: "orcarouter", cost: "", desc: "Auto-pick cheapest" },
]

// Set of premium cloud models that require Supercode Cloud Premium
// Set of premium cloud models that require Pro or higher tier (frontier models)
export const PREMIUM_CLOUD_MODELS = new Set([
"glm-5.2",
"kimi-k2-7-code",
"kimi-k3",
"minimax-m3",
"anthropic/claude-sonnet-4.6",
"anthropic/claude-opus-4.7",
"anthropic/claude-opus-4-8",
"openai/gpt-5.5",
"grok/grok-4-fast-reasoning",
"gemini-2.5-pro",
"deepseek/deepseek-reasoner",
])

export const MODELS: ModelEntry[] = [
{ value: SECTION_CLOUD, label: "Supercode Cloud", provider: "supercode", cost: "", desc: "" },
...CLOUD_MODELS,
{ value: SECTION_CLOUD_PREMIUM, label: "Supercode Cloud Premium", provider: "supercode", cost: "", desc: "" },
{ value: "glm-5.2", label: "GLM 5.2", provider: "supercode", cost: "", desc: "Latest GLM" },
{ value: "kimi-k2-7-code", label: "Kimi K2.7 Code", provider: "supercode", cost: "", desc: "Code specialist" },
{ value: "kimi-k3", label: "Kimi K3", provider: "supercode", cost: "", desc: "Moonshot latest" },
{ value: "minimax-m3", label: "MiniMax M3", provider: "supercode", cost: "", desc: "Fast & smart" },
{ value: SECTION_BYOK, label: "Bring Your Own Key", provider: "supercode", cost: "", desc: "" },
...BYOK_MODELS,
]
// Tier-organized model lists
export const TIER_MODELS: Record<UserTier, ModelEntry[]> = {
"spark": [
{ value: SECTION_CLOUD, label: "Supercode Cloud", provider: "supercode", cost: "", desc: "" },
...CLOUD_MODELS,
{ value: SECTION_BYOK, label: "Bring Your Own Key", provider: "supercode", cost: "", desc: "" },
...BYOK_MODELS,
],
"spark-premium": [
{ value: SECTION_CLOUD, label: "Supercode Cloud", provider: "supercode", cost: "", desc: "" },
...CLOUD_MODELS,
{ value: SECTION_BYOK, label: "Bring Your Own Key", provider: "supercode", cost: "", desc: "" },
...BYOK_MODELS,
],
"pro": [
{ value: SECTION_CLOUD, label: "Supercode Cloud", provider: "supercode", cost: "", desc: "" },
...CLOUD_MODELS,
{ value: SECTION_BYOK, label: "Bring Your Own Key", provider: "supercode", cost: "", desc: "" },
...BYOK_MODELS,
],
"ultra": [
{ value: SECTION_CLOUD, label: "Supercode Cloud", provider: "supercode", cost: "", desc: "" },
...CLOUD_MODELS,
{ value: SECTION_BYOK, label: "Bring Your Own Key", provider: "supercode", cost: "", desc: "" },
...BYOK_MODELS,
],
"none": [
{ value: SECTION_CLOUD, label: "Supercode Cloud", provider: "supercode", cost: "", desc: "" },
...CLOUD_MODELS,
{ value: SECTION_BYOK, label: "Bring Your Own Key", provider: "supercode", cost: "", desc: "" },
...BYOK_MODELS,
],
}
Comment on lines +249 to +281

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

ast-grep outline apps/supercode-cli/server/src/cli/commands/slashCommands/model.ts --items all

rg -nP --type ts -C 5 '\bpickModel\s*\(' apps/supercode-cli/server/src
rg -nP --type ts -C 5 '\bisModelAllowedForTier\s*\(' apps/supercode-cli/server/src
rg -nP --type ts -C 5 '\b(saveCliConfig|getCliConfig)\s*\(' apps/supercode-cli/server/src

Repository: yashdev9274/supercli

Length of output: 43057


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== model.ts outline =="
wc -l apps/supercode-cli/server/src/cli/commands/slashCommands/model.ts
sed -n '1,140p' apps/supercode-cli/server/src/cli/commands/slashCommands/model.ts
printf '\n== model.ts tier and TIER_MODELS ==\n'
sed -n '140,310p' apps/supercode-cli/server/src/cli/commands/slashCommands/model.ts
printf '\n== model.ts ModelPicker and pickModel ==\n'
sed -n '540,755p' apps/supercode-cli/server/src/cli/commands/slashCommands/model.ts

echo "== model access and plan gate =="
sed -n '1,180p' apps/supercode-cli/server/src/lib/model-access.ts
sed -n '1,120p' apps/supercode-cli/server/src/lib/plan-gate.ts

echo "== usages of model selector and pickModel =="
rg -n --type ts 'pickModel\(|providerMeta\[[0-9a-z"]+\]|MIN_TIER|minTier|spark|h3|minimax-m3|model-access|plan-gate|getCurrentUser|isModelAllowedForTier' apps/supercode-cli/server/src

Repository: yashdev9274/supercli

Length of output: 42298


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== migration / seed / model data files =="
git ls-files 'apps/supercode-cli/server/**' | rg '(migrations|seed|prisma|data|supercode|model|minimax|hy3|tier)' | head -200

echo "== target model names in data/migrations =="
rg -n --hidden --glob '!.git/**' 'minimax-m3|h3|hy3|minTier|spark|seed' apps/supercode-cli/server -g '*.ts' -g '*.json' -g '*.prisma' -g '*.sql' | head -300

Repository: yashdev9274/supercli

Length of output: 26665


Do not expose Spark-gated cloud models to the none tier.

getUserTier() returns "none" when authentication or an active subscription is unavailable, but TIER_MODELS["none"] still includes every CLOUD_MODELS item. pickModel() then renders and can select from those entries without calling isModelAllowedForTier. Since hy3 and minimax-m3 are Spark tier-gated in the Model seed, omit gated cloud models from the "none" catalog, show an upgrade path, and keep entitlement enforcement at the execution boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/cli/commands/slashCommands/model.ts` around
lines 249 - 281, Update the "none" entry in TIER_MODELS so it excludes
Spark-gated cloud models such as hy3 and minimax-m3 instead of spreading all
CLOUD_MODELS. Preserve accessible cloud models and BYOK entries, add the
existing upgrade-path presentation for unavailable cloud access, and retain
isModelAllowedForTier enforcement at execution time.


// Default model list (fallback)
export const MODELS: ModelEntry[] = TIER_MODELS["none"]

export class ModelPicker {
items: ModelEntry[] = MODELS
Expand Down Expand Up @@ -377,28 +436,13 @@ export class ModelPicker {
const name = chalk.hex(
isCurrent ? theme.green : theme.greenGlow,
)(m.label.padEnd(22))
const cost = chalk.hex(
m.cost === "free" ? theme.greenGlow : theme.muted,
)(m.cost.padEnd(6))
const cost = chalk.hex(theme.muted)(m.cost.padEnd(6))
const desc = chalk.hex(theme.muted)(m.desc.padEnd(20))
const marker = isCurrent
? ` ${chalk.bgHex(theme.amber).hex(theme.black).bold(" current ")}`
: ""
const isPremium = PREMIUM_CLOUD_MODELS.has(m.value)
const cloudTag =
m.provider === "supercode" && !isCurrent && !isPremium
? ` ${chalk.bgHex(theme.green).hex(theme.black).bold(" CLOUD ")}`
: ""
const premiumTag =
isPremium && !isCurrent
? ` ${chalk.bgHex(theme.amber).hex(theme.black).bold(" PREMIUM ")}`
: ""
const freeTag =
!isCurrent && m.cost === "free" && m.provider !== "supercode"
? ` ${chalk.bgHex(theme.green).hex(theme.black).bold(" FREE ")}`
: ""

const label = `${prefix} ${name} ${cost}${desc}${marker}${cloudTag}${premiumTag}${freeTag}`

const label = `${prefix} ${name} ${cost}${desc}${marker}`

if (isSelected) {
const bg = chalk.bgHex(theme.greenDeep)
Expand Down Expand Up @@ -507,9 +551,12 @@ export async function pickModel(
const currentProvider = stored?.provider || "supercode"
const currentModel = stored?.model || "deepseek-v4-flash"

// Get user tier for model filtering
const userTier = await getUserTier()

const picker = new ModelPicker()
if (providerFilter) {
picker.items = MODELS.filter((m) => m.provider === providerFilter)
picker.items = TIER_MODELS[userTier].filter((m) => m.provider === providerFilter)
if (opts?.allowCustom) {
picker.items.push({
value: "__custom__",
Expand All @@ -520,6 +567,8 @@ export async function pickModel(
})
}
picker.setFilter("")
} else {
picker.items = TIER_MODELS[userTier]
}
const cols = process.stdout.columns ?? 80

Expand Down Expand Up @@ -608,15 +657,23 @@ export async function pickModel(
return { provider: selected.provider, model: trimmed }
}

// Premium cloud models require Supercode Cloud Premium
if (PREMIUM_CLOUD_MODELS.has(selected.value)) {
// Premium cloud models (frontier) require Pro or higher tier
const frontierModels = new Set([
"anthropic/claude-sonnet-4.6",
"anthropic/claude-opus-4.7",
"anthropic/claude-opus-4-8",
"openai/gpt-5.5",
"grok/grok-4-fast-reasoning",
"gemini-2.5-pro",
"deepseek/deepseek-reasoner",
])
if (frontierModels.has(selected.value) && userTier !== "pro" && userTier !== "ultra") {
process.stdout.write("\n")
process.stdout.write(
` ${chalk.hex(theme.amber)("◆")} ${chalk.hex(theme.green).bold("Supercode Cloud Premium")}
` ${chalk.hex(theme.amber)("◆")} ${chalk.hex(theme.green).bold("Pro Plan Required")}

${chalk.hex(theme.muted)("This model is available on Supercode Cloud Premium.")}
${chalk.hex(theme.muted)("Upgrade your plan to access premium models.")}
${chalk.hex(theme.muted)("→ https://supercode.ai/pricing")}
${chalk.hex(theme.muted)("This model requires a Pro or Ultra subscription.")}
${chalk.hex(theme.muted)("Run /upgrade to subscribe.")}

`
)
Expand Down
7 changes: 6 additions & 1 deletion apps/supercode-cli/server/src/cli/utils/tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ export class PersistentStatusBar {
private state = {
mode: "chat",
model: "",
plan: "",
connectionType: "",
cumulativeTokens: 0,
contextWindow: 0,
Expand Down Expand Up @@ -889,6 +890,10 @@ export class PersistentStatusBar {
this.update({ model })
}

setPlan(plan: string) {
this.update({ plan })
}

setConnectionType(type: string) {
this.update({ connectionType: type })
}
Expand Down Expand Up @@ -982,7 +987,7 @@ export class PersistentStatusBar {
parts.push(
this.state.isStreaming
? ansiColor(theme.greenDim, "esc interrupt")
: ansiColor(theme.greenDim, "tab mode"),
: ansiColor(theme.greenDim, this.state.plan || "tab mode"),
)

const inner = parts.join(sep)
Expand Down
23 changes: 19 additions & 4 deletions apps/supercode-cli/server/src/lib/model-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,28 @@ export function tierIndex(tier: string): number {
/**
* Checks whether a model slug is allowed for the user's plan tier.
* Model gating is backed by the `Model` table's `minTier` field.
*
* Tier ladder: spark < spark-premium < pro < ultra
* Open models use minTier "spark" so both Spark and Spark Premium get them.
* Spark Premium is the paid open-models tier (higher limits/credits, same catalog).
*/
export async function isModelAllowedForTier(
modelSlug: string,
userTier: string,
): Promise<boolean> {
try {
const models = await ensureCache()
const model = models.find(
(m) => m.slug === modelSlug || modelSlug.includes(m.slug),
)
const normalized = modelSlug.trim().toLowerCase()
const model = models.find((m) => {
const slug = m.slug.toLowerCase()
return (
slug === normalized ||
normalized === slug ||
normalized.endsWith(`/${slug}`) ||
normalized.includes(slug) ||
slug.includes(normalized)
)
Comment on lines +38 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

ast-grep outline apps/supercode-cli/server/src/lib/model-access.ts --items all
rg -nP --type ts -C 4 '\bisModelAllowedForTier\s*\(' apps/supercode-cli/server/src

Repository: yashdev9274/supercli

Length of output: 2279


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- model-access.ts ---'
cat -n apps/supercode-cli/server/src/lib/model-access.ts | sed -n '1,90p'

printf '%s\n' '--- plan-gate.ts relevant section ---'
cat -n apps/supercode-cli/server/src/lib/plan-gate.ts | sed -n '1,90p'

printf '%s\n' '--- occurrences of isModelAllowedForTier results usages ---'
rg -nP --type ts -C 3 'isModelAllowedForTier|modelAllowed|allowed' apps/supercode-cli/server/src

printf '%s\n' '--- behavior probe for current matching rules ---'
python3 - <<'PY'
models = [
    {"slug": "deepseek-v4-flash"},
    {"slug": "openai-gpt-4"},
    {"slug": "spark-premium"},
]
test_cases = [
    "deepseek-v4-flash-preview",
    "spark-premium-extra",
    "gpt-4",
    "openai",
    " openai-gpt-4 ",
    "  openai-gpt-4  /spark-premium ",
]
for modelSlug in test_cases:
    normalized = modelSlug.strip().lower()
    found = []
    for m in models:
        slug = m["slug"].lower()
        matches = (
            slug == normalized or
            normalized == slug or  # same as first
            normalized.endswith(f"/{slug}") or
            normalized.find(slug) != -1 or
            slug.find(normalized) != -1
        )
        if matches:
            found.append(m["slug"])
    print(f"{modelSlug!r} -> {found}")
PY

Repository: yashdev9274/supercli

Length of output: 35872


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- index.ts model handling relevant section ---'
cat -n apps/supercode-cli/server/src/index.ts | sed -n '55,90p'
cat -n apps/supercode-cli/server/src/index.ts | sed -n '320,345p'

printf '%s\n' '--- chat gate handling relevant sections ---'
cat -n apps/supercode-cli/server/src/cli/ai/chat/chat.ts | sed -n '2168,2195p'
cat -n apps/supercode-cli/server/src/cli/ai/chat/chat.ts | sed -n '2272,2298p'
cat -n apps/supercode-cli/server/src/cli/ai/chat/chat.ts | sed -n '2354,2375p'

printf '%s\n' '--- AI provider/model name handling relevant sections ---'
fd -a 'provider.ts' apps/supercode-cli/server/src/cli/ai | while read -r f; do
  printf '\n=== %s ===\n' "$f"
  cat -n "$f" | sed -n '1,120p'
done

printf '%s\n' '--- request-counter and credit-meter model usage ---'
cat -n apps/supercode-cli/server/src/lib/request-counter.ts | sed -n '1,90p'
cat -n apps/supercode-cli/server/src/lib/credit-meter.ts | sed -n '1,120p'

printf '%s\n' '--- deterministic probe of current matcher against catalog-like slugs ---'
python3 - <<'PY'
catalog = ["deepseek-v4-flash", "openai-gpt-4", "spark-premium", "claude-sonnet-4"]
cases = [
  ("deepseek-v4-flash-preview", False),
  ("openai-gpt-5", False),
  ("gpt-4", True),
  ("claude-sonnet", True),
  ("my/deepseek-v4-flash-preview", False),
  ("/ deepseek-v4-flash-preview", False),
]
for input_name, expected_partial in cases:
    normalized = input_name.strip().lower()
    hits = [m for m in catalog if (normalized == m or normalized.endswith(f"/{m}") or normalized.find(m) != -1 or m.find(normalized) != -1)]
    print(f"{input_name!r} -> hits={hits}, has_expected_partial={any(h in normalized or normalized.startswith(h) for h in hits)}")
PY

Repository: yashdev9274/supercli

Length of output: 19507


Restrict isModelAllowedForTier to canonical model slugs.

Current substring matching lets names that do not appear in the Model table access downstream provider/credit paths as known models. For example, deepseek-v4-flash-preview can match the catalog slug deepseek-v4-flash.

Allow only exact normalized slugs and explicit provider-qualified suffixes such as concentrateai/deepseek-v4-flash; register aliases when alternative spellings are required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/lib/model-access.ts` around lines 38 - 47,
Update the model lookup in isModelAllowedForTier to remove broad substring
matching via normalized.includes(slug) and slug.includes(normalized). Permit
only exact normalized slug matches or explicit provider-qualified values ending
with /${slug}; add registered aliases in the Model table or alias configuration
for any required alternative spellings.

})
if (!model) return false
return tierIndex(userTier) >= tierIndex(model.minTier)
} catch (error) {
Expand All @@ -43,8 +55,11 @@ export async function isModelAllowedForTier(
}

export function getUpgradeSuggestion(userTier: string): string {
if (tierIndex(userTier) < tierIndex("spark-premium")) {
return "To access more open models and higher limits, run /upgrade (Spark Premium)"
}
if (tierIndex(userTier) < tierIndex("pro")) {
return "To access premium models, run /upgrade"
return "To access premium models (Claude, GPT, etc.), run /upgrade"
Comment on lines +58 to +62

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the Spark Premium benefit text.

Spark Premium uses the same open-model catalog as Spark. The message at Line 59 incorrectly promises “more open models.” Describe higher limits and credits instead.

Proposed fix
-    return "To access more open models and higher limits, run /upgrade (Spark Premium)"
+    return "To access the paid open-model tier with higher limits and credits, run /upgrade (Spark Premium)"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (tierIndex(userTier) < tierIndex("spark-premium")) {
return "To access more open models and higher limits, run /upgrade (Spark Premium)"
}
if (tierIndex(userTier) < tierIndex("pro")) {
return "To access premium models, run /upgrade"
return "To access premium models (Claude, GPT, etc.), run /upgrade"
if (tierIndex(userTier) < tierIndex("spark-premium")) {
return "To access the paid open-model tier with higher limits and credits, run /upgrade (Spark Premium)"
}
if (tierIndex(userTier) < tierIndex("pro")) {
return "To access premium models (Claude, GPT, etc.), run /upgrade"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/lib/model-access.ts` around lines 58 - 62,
Update the Spark Premium message in the user-tier checks around tierIndex so it
no longer claims access to more open models; describe the correct benefits as
higher limits and credits while preserving the existing upgrade instruction and
Pro-tier message.

}
if (tierIndex(userTier) < tierIndex("ultra")) {
return "To access all models unrestricted, run /upgrade"
Expand Down
Loading