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.67",
"version": "0.1.69",
"description": "AI-powered coding agent CLI",
"main": "dist/main.js",
"bin": {
Expand Down
84 changes: 84 additions & 0 deletions apps/supercode-cli/server/package.json.bak2
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"name": "supercode-cli",
"version": "0.1.58",
"description": "AI-powered coding agent CLI",
"main": "dist/main.js",
"bin": {
"supercode": "dist/main.js"
},
"scripts": {
"dev": "bun src/index.ts",
"dev:cli": "bun src/cli/main.ts",
"start": "bun src/index.ts",
"build": "bun run build:cli",
"build:cli": "bun build --target node --packages bundle --outdir dist --entry ./src/cli/main.ts --external @modelcontextprotocol/sdk && sed -i '' 's|#!/usr/bin/env bun|#!/usr/bin/env node|' dist/main.js",
"test": "bun test",
"typecheck": "tsc --noEmit",
"db:generate": "prisma generate",
"prepublishOnly": "bun run build:cli"
},
"type": "module",
"files": [
"dist/main.js",
"README.md"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/yashdev9274/supercli.git"
},
"keywords": [
"ai",
"cli",
"coding-agent",
"terminal"
],
"author": "Yash Dewasthale",
"license": "MIT",
"engines": {
"node": ">=18"
},
"dependencies": {
"@ai-sdk/google": "^3.0.80",
"@ai-sdk/openai-compatible": "^2.0.48",
"@modelcontextprotocol/sdk": "^1.9.0",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"@prisma/client-runtime-utils": "^7.8.0",
"@prisma/driver-adapter-utils": "^7.8.0",
"@vercel/connect": "^0.3.2",
"ai": "^6.0.195",
"better-auth": "^1.5.5",
"boxen": "^8.0.1",
"chalk": "^5.6.2",
"commander": "^15.0.0",
"cors": "^2.8.5",
"eve": "^0.22.5",
"express": "^5.1.0",
"marked": "^18.0.4",
"marked-terminal": "^7.3.0",
"open": "^11.0.0",
"vercel-minimax-ai-provider": "^0.0.2",
"yocto-spinner": "^1.2.0",
"zod": "^3.25.2",
"zod-to-json-schema": "^3.25.2"
},
"devDependencies": {
"@clack/prompts": "^1.5.0",
"@openrouter/ai-sdk-provider": "^2.9.0",
"@openrouter/sdk": "^0.12.79",
"@super/db-terminal": "workspace:*",
"@types/bun": "^1.3.14",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/node": "^22.0.0",
"@types/pg": "^8.18.0",
"api": "^6.1.3",
"dotenv": "^17.3.1",
"oas": "^34.0.1",
"prisma": "^7.4.2",
"typescript": "^5.7.0"
}
}
Comment on lines +1 to +84

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if the active package.json contains the macOS-specific sed command or the dotenv dependency.

echo "Checking for macOS-specific sed:"
grep "sed -i ''" apps/supercode-cli/server/package.json || echo "None found."

echo "Checking for dotenv:"
grep "\"dotenv\"" apps/supercode-cli/server/package.json || echo "None found."

Repository: yashdev9274/supercli

Length of output: 448


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Tracked files:"
git ls-files 'apps/supercode-cli/server/package.json*' || true

echo
echo "File stats:"
wc -l apps/supercode-cli/server/package.json apps/supercode-cli/server/package.json.bak2 2>/dev/null || true

echo
echo "Top-level comparison:"
python3 - <<'PY'
import json, pathlib, sys
for path in ["apps/supercode-cli/server/package.json", "apps/supercode-cli/server/package.json.bak2"]:
    p = pathlib.Path(path)
    print(f"\n== {path} ==")
    data = json.loads(p.read_text())
    print("name:", data.get("name"))
    print("version:", data.get("version"))
    print("has sed -i '' in build:cli:", "sed -i ''" in data.get("scripts", {}).get("build:cli", ""))
    print("has dotenv:", any("dotenv" in k or "dotenv" in str(v) for k, v in data.get("dependencies", {}).items()) or any("dotenv" in k or "dotenv" in str(v) for k, v in data.get("devDependencies", {}).items()))
PY

Repository: yashdev9274/supercli

Length of output: 665


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Tracked files:"
git ls-files 'apps/supercode-cli/server/package.json*' || true

echo
echo "File stats:"
wc -l apps/supercode-cli/server/package.json apps/supercode-cli/server/package.json.bak2 2>/dev/null || true

echo
echo "Top-level comparison:"
python3 - <<'PY'
import json, pathlib
for path in ["apps/supercode-cli/server/package.json", "apps/supercode-cli/server/package.json.bak2"]:
    p = pathlib.Path(path)
    print(f"\n== {path} ==")
    data = json.loads(p.read_text())
    print("name:", data.get("name"))
    print("version:", data.get("version"))
    print("has sed -i '' in build:cli:", "sed -i ''" in data.get("scripts", {}).get("build:cli", ""))
    print("has dotenv:", any("dotenv" in k or "dotenv" in str(v) for k, v in data.get("dependencies", {}).items()) or any("dotenv" in k or "dotenv" in str(v) for k, v in data.get("devDependencies", {}).items()))
PY

Repository: yashdev9274/supercli

Length of output: 665


Delete the committed backup file and clean up the active manifest

  • apps/supercode-cli/server/package.json.bak2 is a backup artifact and should be removed.
  • apps/supercode-cli/server/package.json still uses sed -i '' in build:cli, which breaks on Linux.
  • dotenv is still listed in devDependencies; Bun already loads .env files without it.
🤖 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/package.json.bak2` around lines 1 - 84, Delete the
committed backup artifact package.json.bak2. In the active package.json, update
the build:cli script to use a Linux-compatible shebang replacement instead of
macOS-only sed -i '', and remove dotenv from devDependencies while leaving the
remaining manifest entries unchanged.

Source: Path instructions

19 changes: 14 additions & 5 deletions apps/supercode-cli/server/src/cli/ai/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ async function streamAIResponse(
// previous noisy per-tool debug lines.
const statusRow = new StepStatusRow()
const agentName = mode === "plan" ? "plan" : (mode === "chat" ? "chat" : "build")
statusRow.start(agentName, provider.modelName)
statusRow.start(agentName, provider.modelName, provider.connectionType)
// Publish to the module-scoped slot so the persistent footer's resize
// handler can notify us too — StepStatusRow reserves no row of its own,
// but its render math depends on the current terminal width.
Expand Down Expand Up @@ -827,8 +827,9 @@ async function streamAIResponse(
const elapsedStr =
elapsed < 1000 ? `${elapsed}ms` : `${(elapsed / 1000).toFixed(1)}s`
const modeLabel = mode === "plan" ? "plan" : (mode === "chat" ? "chat" : "build")
const connBadge = provider.connectionType === "direct" ? " 🔑" : " ☁️"
console.log(
` ${chalk.hex(theme.green)("▣")} ${chalk.hex(theme.greenMute)(modeLabel)} ${chalk.hex(theme.greenDim)("·")} ${chalk.hex(theme.muted)(provider.modelName)} ${chalk.hex(theme.greenDim)("·")} ${chalk.hex(theme.muted)(elapsedStr)}`,
` ${chalk.hex(theme.green)("▣")} ${chalk.hex(theme.greenMute)(modeLabel)} ${chalk.hex(theme.greenDim)("·")} ${chalk.hex(theme.muted)(provider.modelName)}${chalk.hex(theme.amber)(connBadge)} ${chalk.hex(theme.greenDim)("·")} ${chalk.hex(theme.muted)(elapsedStr)}`,
)
console.log()
return {
Expand Down Expand Up @@ -1902,17 +1903,25 @@ export async function chatLoop(
provider = newProvider
contextWindow = getContextWindow(provider.modelName)
const label = result.label || provider.modelName
footer.setModel(provider.modelName)
footer.setModel(provider.modelName)
footer.setConnectionType(provider.connectionType)
footer.setContextWindow(contextWindow)
process.stdout.write(`\r\n ${chalk.hex(theme.green)("◆")} switched to ${chalk.hex(theme.green)(label)}\r\n\n`)
saveCliConfig({ provider: result.provider!, model: result.model || provider.modelName, mode: conversation.mode as "chat" | "agent" })
}
} else if (result?.type === "connect") {
if (result.provider) {
const newProvider = createProvider(result.provider, provider.modelName)
const modelToUse = result.model || provider.modelName
const newProvider = createProvider(result.provider, modelToUse)
if (newProvider) {
provider = newProvider
contextWindow = getContextWindow(provider.modelName)
footer.setModel(provider.modelName)
footer.setConnectionType(provider.connectionType)
footer.setContextWindow(contextWindow)
const label = `${provider.name} · ${provider.modelName}`
process.stdout.write(`\r\n ${chalk.hex(theme.green)("◆")} switched to ${chalk.hex(theme.green)(label)}\r\n`)
saveCliConfig({ provider: result.provider!, model: modelToUse, mode: conversation.mode as "chat" | "agent" })
Comment on lines +1914 to +1924

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 | ⚡ Quick win

Prevent cross-provider model leakage.

When switching to a new provider via /connect, falling back to provider.modelName (the previous provider's model) will cause API errors if the user cancels or doesn't explicitly select a new model. The previous model name (e.g., deepseek-v4-flash) will be incorrectly passed to the new provider (e.g., google) and saved in the persistent config.

Instead, conditionally fall back to the existing model only if the provider remains the same, and use the newly resolved provider.modelName for saving the configuration.

🛠️ Proposed fix
-            const modelToUse = result.model || provider.modelName
-            const newProvider = createProvider(result.provider, modelToUse)
+            const modelToUse = result.model || (result.provider === provider.name ? provider.modelName : undefined)
+            const newProvider = createProvider(result.provider, modelToUse)
             if (newProvider) {
               provider = newProvider
               contextWindow = getContextWindow(provider.modelName)
               footer.setModel(provider.modelName)
               footer.setConnectionType(provider.connectionType)
               footer.setContextWindow(contextWindow)
               const label = `${provider.name} · ${provider.modelName}`
               process.stdout.write(`\r\n ${chalk.hex(theme.green)("◆")} switched to ${chalk.hex(theme.green)(label)}\r\n`)
-              saveCliConfig({ provider: result.provider!, model: modelToUse, mode: conversation.mode as "chat" | "agent" })
+              saveCliConfig({ provider: result.provider!, model: provider.modelName, mode: conversation.mode as "chat" | "agent" })
📝 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
const modelToUse = result.model || provider.modelName
const newProvider = createProvider(result.provider, modelToUse)
if (newProvider) {
provider = newProvider
contextWindow = getContextWindow(provider.modelName)
footer.setModel(provider.modelName)
footer.setConnectionType(provider.connectionType)
footer.setContextWindow(contextWindow)
const label = `${provider.name} · ${provider.modelName}`
process.stdout.write(`\r\n ${chalk.hex(theme.green)("◆")} switched to ${chalk.hex(theme.green)(label)}\r\n`)
saveCliConfig({ provider: result.provider!, model: modelToUse, mode: conversation.mode as "chat" | "agent" })
const modelToUse = result.model || (result.provider === provider.name ? provider.modelName : undefined)
const newProvider = createProvider(result.provider, modelToUse)
if (newProvider) {
provider = newProvider
contextWindow = getContextWindow(provider.modelName)
footer.setModel(provider.modelName)
footer.setConnectionType(provider.connectionType)
footer.setContextWindow(contextWindow)
const label = `${provider.name} · ${provider.modelName}`
process.stdout.write(`\r\n ${chalk.hex(theme.green)("◆")} switched to ${chalk.hex(theme.green)(label)}\r\n`)
saveCliConfig({ provider: result.provider!, model: provider.modelName, mode: conversation.mode as "chat" | "agent" })
🤖 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/ai/chat/chat.ts` around lines 1914 - 1924,
Update the /connect switching logic around createProvider so modelToUse falls
back to the previous provider.modelName only when result.provider matches the
current provider; otherwise use the newly created provider.modelName. Ensure
saveCliConfig persists the resolved new provider model rather than carrying the
previous provider’s model across providers.

}
}
process.stdout.write(`\r\n`)
Expand Down Expand Up @@ -2225,7 +2234,7 @@ export async function chatLoop(
}

export async function startChat(
provider: ModelProvider = "concentrateai",
provider: ModelProvider = "supercode",
model?: string,
conversationId?: string | null,
workspaceInfo?: WorkspaceInfo,
Expand Down
2 changes: 1 addition & 1 deletion apps/supercode-cli/server/src/cli/ai/chat/chatAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ async function saveMessage(conversationId: string, role: string, content: string
}

export async function startAgentChat(
provider: ModelProvider = "concentrateai",
provider: ModelProvider = "supercode",
model?: string,
conversationId: string | null = null,
workspaceInfo?: WorkspaceInfo,
Expand Down
11 changes: 9 additions & 2 deletions apps/supercode-cli/server/src/cli/ai/chat/step-status-row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class StepStatusRow {
private frameIndex = 0
private agentName = ""
private modelName = ""
private connectionType = ""
private phase: StepPhase = "idle"
private currentToolName = ""
private currentToolArgs: unknown = undefined
Expand All @@ -53,11 +54,12 @@ export class StepStatusRow {
// Simple ETA: rolling average of tool durations per tool type (ms)
private toolDurationHistory = new Map<string, number[]>()

start(agentName: string, modelName: string) {
start(agentName: string, modelName: string, connectionType: string = "") {
if (this.running) return
this.running = true
this.agentName = agentName
this.modelName = modelName
this.connectionType = connectionType
this.phase = "thinking"
this.currentToolName = ""
this.currentToolArgs = undefined
Expand Down Expand Up @@ -171,7 +173,7 @@ export class StepStatusRow {
}
// Simple ETA: average of past durations × estimated remaining steps
const estimatedRemaining = this.estimateRemaining()
if (estimatedRemaining > 0) {
if (estimatedRemaining) {
parts.push(chalk.hex(theme.greenDim)(`~${estimatedRemaining}`))
}
} else if (this.agentName) {
Expand All @@ -180,6 +182,11 @@ export class StepStatusRow {
? `Model processing tool result`
: `▣ ${this.agentName}${this.modelName ? " · " + this.modelName : ""}`
parts.push(chalk.hex(theme.greenMute)(modelLabel))
if (this.connectionType === "direct") {
parts.push(chalk.hex(theme.amber)("🔑"))
} else if (this.connectionType === "proxy") {
parts.push(chalk.hex(theme.amber)("☁️"))
}
} else if (this.modelName) {
parts.push(chalk.hex(theme.greenGlow)(this.modelName))
}
Expand Down
26 changes: 23 additions & 3 deletions apps/supercode-cli/server/src/cli/ai/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import { openRouterConfig } from "../../config/openrouter.config.ts"
import { nvidiaConfig } from "../../config/nvidia.config.ts"
import { mergedevConfig } from "../../config/mergedev.config.ts"

export type ModelProvider = "google" | "minimax" | "openrouter" | "nvidia" | "concentrateai" | "mergedev"
export type ModelProvider = "supercode" | "google" | "minimax" | "openrouter" | "nvidia" | "concentrateai" | "mergedev"

export type ConnectionType = "direct" | "proxy"

export interface AIProvider {
readonly name: string
readonly modelName: string
readonly connectionType: ConnectionType
readonly model?: any
sendMessage(
messages: ModelMessage[],
Expand All @@ -36,7 +39,8 @@ export interface AIProvider {
generateObject?(schema: any, prompt: string): Promise<{ object: unknown }>
}

const providerMeta: Record<ModelProvider, { env: string; label: string; defaultModel: string; link?: string }> = {
export const providerMeta: Record<ModelProvider, { env: string; label: string; defaultModel: string; link?: string }> = {
supercode: { env: "", label: "Supercode Cloud", defaultModel: "deepseek-v4-flash" },
google: { env: "GOOGLE_GENERATIVE_AI_API_KEY", label: "Google Gemini", defaultModel: "gemini-2.5-flash", link: "https://aistudio.google.com/apikey" },
minimax: { env: "MINIMAX_API_KEY", label: "MiniMax", defaultModel: "MiniMax-M2" },
openrouter: { env: "OPENROUTER_API_KEY", label: "OpenRouter", defaultModel: "openai/gpt-oss-120b:free", link: "https://openrouter.ai/keys" },
Expand All @@ -46,6 +50,7 @@ const providerMeta: Record<ModelProvider, { env: string; label: string; defaultM
}

const providerConfigs: Record<ModelProvider, () => string> = {
supercode: () => "",
google: () => config.googleApiKey,
minimax: () => minimaxConfig.apiKey,
openrouter: () => openRouterConfig.apiKey,
Expand All @@ -57,15 +62,25 @@ const providerConfigs: Record<ModelProvider, () => string> = {
export function createProvider(provider: ModelProvider, model?: string): AIProvider {
const meta = providerMeta[provider]

// Only supercode cloud models are allowed through the proxy without an API key.
// All other providers require the user to connect their own key first.
if (!providerConfigs[provider]()) {
if (provider === "supercode") {
const svc = new ServerProxyService(provider, model || meta.defaultModel)
return {
name: provider,
modelName: model || meta.defaultModel,
connectionType: "proxy",
sendMessage: (messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult, onStepFinish) =>
svc.sendMessage(messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult, onStepFinish),
generateObject: (schema, prompt) => svc.generateObject(schema, prompt),
generateObject: (schema, prompt) => svc.generateObject(schema, prompt),
}
}
throw new Error(
`No API key configured for ${meta.label}. ` +
`Run \`/connect\` and select "${meta.label}" to save your key, ` +
`then select this model again.`
)
}

switch (provider) {
Expand All @@ -74,6 +89,7 @@ export function createProvider(provider: ModelProvider, model?: string): AIProvi
return {
name: "google",
modelName: svc.modelName,
connectionType: "direct",
model: svc.model,
sendMessage: (messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult) =>
svc.sendMessage(messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult),
Expand All @@ -84,6 +100,7 @@ export function createProvider(provider: ModelProvider, model?: string): AIProvi
return {
name: "openrouter",
modelName: svc.modelName,
connectionType: "direct",
sendMessage: (messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult) =>
svc.sendMessage(messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult),
}
Expand All @@ -93,6 +110,7 @@ export function createProvider(provider: ModelProvider, model?: string): AIProvi
return {
name: "nvidia",
modelName: svc.modelName,
connectionType: "direct",
model: svc.model,
sendMessage: (messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult, onStepFinish) =>
svc.sendMessage(messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult, onStepFinish),
Expand All @@ -103,6 +121,7 @@ export function createProvider(provider: ModelProvider, model?: string): AIProvi
return {
name: "concentrateai",
modelName: svc.modelName,
connectionType: "direct",
model: svc.model,
sendMessage: (messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult, onStepFinish, onStepBudget) =>
svc.sendMessage(messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult, onStepFinish, onStepBudget),
Expand All @@ -113,6 +132,7 @@ export function createProvider(provider: ModelProvider, model?: string): AIProvi
return {
name: "mergedev",
modelName: svc.modelName,
connectionType: "direct",
model: svc.model,
sendMessage: (messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult, onStepFinish) =>
svc.sendMessage(messages, onChunk, tools, onToolCall, signal, onReasoning, onToolResult, onStepFinish),
Expand Down
Loading
Loading