mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
- Enhance passthrough function to support response inspection
- Add cursor tool configuration and update related components
This commit is contained in:
+2
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "9router-app",
|
"name": "9router-app",
|
||||||
"version": "0.3.57",
|
"version": "0.3.58",
|
||||||
"description": "9Router web dashboard",
|
"description": "9Router web dashboard",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
"@monaco-editor/react": "^4.7.0",
|
"@monaco-editor/react": "^4.7.0",
|
||||||
"@xyflow/react": "^12.10.1",
|
"@xyflow/react": "^12.10.1",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"better-sqlite3": "^12.6.2",
|
|
||||||
"confbox": "^0.2.4",
|
"confbox": "^0.2.4",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
@@ -34,16 +33,15 @@
|
|||||||
"recharts": "^3.7.0",
|
"recharts": "^3.7.0",
|
||||||
"selfsigned": "^5.5.0",
|
"selfsigned": "^5.5.0",
|
||||||
"socks-proxy-agent": "^8.0.5",
|
"socks-proxy-agent": "^8.0.5",
|
||||||
|
"sql.js": "^1.14.1",
|
||||||
"undici": "^7.19.2",
|
"undici": "^7.19.2",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"zustand": "^5.0.10"
|
"zustand": "^5.0.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
"esbuild": "^0.27.4",
|
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.1.6",
|
||||||
"javascript-obfuscator": "^5.3.0",
|
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"tailwindcss": "^4"
|
"tailwindcss": "^4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { ClaudeToolCard, CodexToolCard, DroidToolCard, OpenClawToolCard, Default
|
|||||||
|
|
||||||
const CLOUD_URL = process.env.NEXT_PUBLIC_CLOUD_URL;
|
const CLOUD_URL = process.env.NEXT_PUBLIC_CLOUD_URL;
|
||||||
|
|
||||||
// MITM tools are now on /dashboard/mitm — exclude from CLI Tools page
|
|
||||||
const MITM_TOOL_IDS = ["antigravity", "copilot"];
|
|
||||||
|
|
||||||
const STATUS_ENDPOINTS = {
|
const STATUS_ENDPOINTS = {
|
||||||
claude: "/api/cli-tools/claude-settings",
|
claude: "/api/cli-tools/claude-settings",
|
||||||
@@ -185,7 +183,7 @@ export default function CLIToolsPageClient({ machineId }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const regularTools = Object.entries(CLI_TOOLS).filter(([id]) => !MITM_TOOL_IDS.includes(id));
|
const regularTools = Object.entries(CLI_TOOLS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { CLI_TOOLS } from "@/shared/constants/cliTools";
|
import { MITM_TOOLS } from "@/shared/constants/cliTools";
|
||||||
import { getModelsByProviderId } from "@/shared/constants/models";
|
import { getModelsByProviderId } from "@/shared/constants/models";
|
||||||
import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers";
|
import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers";
|
||||||
import { MitmServerCard, MitmToolCard } from "@/app/(dashboard)/dashboard/cli-tools/components";
|
import { MitmServerCard, MitmToolCard } from "@/app/(dashboard)/dashboard/cli-tools/components";
|
||||||
|
|
||||||
const MITM_TOOL_IDS = ["antigravity", "copilot", "kiro"];
|
|
||||||
|
|
||||||
export default function MitmPageClient() {
|
export default function MitmPageClient() {
|
||||||
const [connections, setConnections] = useState([]);
|
const [connections, setConnections] = useState([]);
|
||||||
const [apiKeys, setApiKeys] = useState([]);
|
const [apiKeys, setApiKeys] = useState([]);
|
||||||
@@ -74,7 +72,7 @@ export default function MitmPageClient() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mitmTools = Object.entries(CLI_TOOLS).filter(([id]) => MITM_TOOL_IDS.includes(id));
|
const mitmTools = Object.entries(MITM_TOOLS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { access, constants } from "fs/promises";
|
import { access, constants, readFile } from "fs/promises";
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { execFile, execSync } from "child_process";
|
import { execFile } from "child_process";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import { createRequire } from "module";
|
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
const execFileAsync = promisify(execFile);
|
||||||
|
|
||||||
@@ -39,14 +38,7 @@ function getCandidatePaths(platform) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extract tokens using better-sqlite3 (stream-based, no RAM limit) */
|
const normalize = (value) => {
|
||||||
function extractTokens(db) {
|
|
||||||
const desiredKeys = [...ACCESS_TOKEN_KEYS, ...MACHINE_ID_KEYS];
|
|
||||||
const rows = db.prepare(
|
|
||||||
`SELECT key, value FROM itemTable WHERE key IN (${desiredKeys.map(() => "?").join(",")})`
|
|
||||||
).all(...desiredKeys);
|
|
||||||
|
|
||||||
const normalize = (value) => {
|
|
||||||
if (typeof value !== "string") return value;
|
if (typeof value !== "string") return value;
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(value);
|
const parsed = JSON.parse(value);
|
||||||
@@ -54,8 +46,27 @@ function extractTokens(db) {
|
|||||||
} catch {
|
} catch {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Extract tokens using sql.js (pure JS, cross-platform) */
|
||||||
|
async function extractTokens(dbPath) {
|
||||||
|
const initSqlJs = (await import("sql.js")).default;
|
||||||
|
const SQL = await initSqlJs();
|
||||||
|
const db = new SQL.Database(await readFile(dbPath));
|
||||||
|
|
||||||
|
const queryAll = (sql, params = []) => {
|
||||||
|
const stmt = db.prepare(sql);
|
||||||
|
stmt.bind(params);
|
||||||
|
const rows = [];
|
||||||
|
while (stmt.step()) rows.push(stmt.getAsObject());
|
||||||
|
stmt.free();
|
||||||
|
return rows;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const desiredKeys = [...ACCESS_TOKEN_KEYS, ...MACHINE_ID_KEYS];
|
||||||
|
const placeholders = desiredKeys.map(() => "?").join(",");
|
||||||
|
const rows = queryAll(`SELECT key, value FROM itemTable WHERE key IN (${placeholders})`, desiredKeys);
|
||||||
|
|
||||||
const tokens = {};
|
const tokens = {};
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (ACCESS_TOKEN_KEYS.includes(row.key) && !tokens.accessToken) {
|
if (ACCESS_TOKEN_KEYS.includes(row.key) && !tokens.accessToken) {
|
||||||
@@ -67,22 +78,18 @@ function extractTokens(db) {
|
|||||||
|
|
||||||
// Fuzzy fallback
|
// Fuzzy fallback
|
||||||
if (!tokens.accessToken || !tokens.machineId) {
|
if (!tokens.accessToken || !tokens.machineId) {
|
||||||
const fallbackRows = db.prepare(
|
const fallbackRows = queryAll(
|
||||||
"SELECT key, value FROM itemTable WHERE key LIKE '%cursorAuth/%' OR key LIKE '%machineId%' OR key LIKE '%serviceMachineId%'"
|
"SELECT key, value FROM itemTable WHERE key LIKE '%cursorAuth/%' OR key LIKE '%machineId%' OR key LIKE '%serviceMachineId%'"
|
||||||
).all();
|
);
|
||||||
|
|
||||||
for (const row of fallbackRows) {
|
for (const row of fallbackRows) {
|
||||||
const key = row.key || "";
|
const key = row.key || "";
|
||||||
const value = normalize(row.value);
|
const value = normalize(row.value);
|
||||||
if (!tokens.accessToken && key.toLowerCase().includes("accesstoken")) {
|
if (!tokens.accessToken && key.toLowerCase().includes("accesstoken")) tokens.accessToken = value;
|
||||||
tokens.accessToken = value;
|
if (!tokens.machineId && key.toLowerCase().includes("machineid")) tokens.machineId = value;
|
||||||
}
|
|
||||||
if (!tokens.machineId && key.toLowerCase().includes("machineid")) {
|
|
||||||
tokens.machineId = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.close();
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,34 +161,13 @@ export async function GET() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 1: better-sqlite3 bundled → then global install fallback
|
// Strategy 1: sql.js (pure JS WASM, cross-platform)
|
||||||
let Database = null;
|
|
||||||
try {
|
try {
|
||||||
const mod = await import("better-sqlite3");
|
const tokens = await extractTokens(dbPath);
|
||||||
Database = mod.default;
|
|
||||||
} catch {
|
|
||||||
// Try loading from global node_modules (user ran: npm i better-sqlite3 -g)
|
|
||||||
try {
|
|
||||||
const globalRoot = execSync("npm root -g", { timeout: 5000, windowsHide: true }).toString().trim();
|
|
||||||
const requireGlobal = createRequire(join(globalRoot, "better-sqlite3", "package.json"));
|
|
||||||
Database = requireGlobal("better-sqlite3");
|
|
||||||
} catch { /* fall through to sqlite3 CLI strategy */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Database) {
|
|
||||||
let db;
|
|
||||||
try {
|
|
||||||
db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
||||||
const tokens = extractTokens(db);
|
|
||||||
db.close();
|
|
||||||
|
|
||||||
if (tokens.accessToken && tokens.machineId) {
|
if (tokens.accessToken && tokens.machineId) {
|
||||||
return NextResponse.json({ found: true, accessToken: tokens.accessToken, machineId: tokens.machineId });
|
return NextResponse.json({ found: true, accessToken: tokens.accessToken, machineId: tokens.machineId });
|
||||||
}
|
}
|
||||||
} catch {
|
} catch { /* fall through to sqlite3 CLI strategy */ }
|
||||||
db?.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy 2: sqlite3 CLI (works on Windows if sqlite3 is installed)
|
// Strategy 2: sqlite3 CLI (works on Windows if sqlite3 is installed)
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ const TARGET_HOSTS = [
|
|||||||
"cloudcode-pa.googleapis.com",
|
"cloudcode-pa.googleapis.com",
|
||||||
"api.individual.githubcopilot.com",
|
"api.individual.githubcopilot.com",
|
||||||
"q.us-east-1.amazonaws.com",
|
"q.us-east-1.amazonaws.com",
|
||||||
|
"api2.cursor.sh",
|
||||||
];
|
];
|
||||||
|
|
||||||
const URL_PATTERNS = {
|
const URL_PATTERNS = {
|
||||||
antigravity: [":generateContent", ":streamGenerateContent"],
|
antigravity: [":generateContent", ":streamGenerateContent"],
|
||||||
copilot: ["/chat/completions", "/v1/messages", "/responses"],
|
copilot: ["/chat/completions", "/v1/messages", "/responses"],
|
||||||
kiro: ["/generateAssistantResponse"],
|
kiro: ["/generateAssistantResponse"],
|
||||||
|
cursor: ["/BidiAppend", "/RunSSE", "/RunPoll", "/Run"],
|
||||||
};
|
};
|
||||||
|
|
||||||
function getToolForHost(host) {
|
function getToolForHost(host) {
|
||||||
@@ -18,6 +20,7 @@ function getToolForHost(host) {
|
|||||||
if (h === "api.individual.githubcopilot.com") return "copilot";
|
if (h === "api.individual.githubcopilot.com") return "copilot";
|
||||||
if (h === "daily-cloudcode-pa.googleapis.com" || h === "cloudcode-pa.googleapis.com") return "antigravity";
|
if (h === "daily-cloudcode-pa.googleapis.com" || h === "cloudcode-pa.googleapis.com") return "antigravity";
|
||||||
if (h === "q.us-east-1.amazonaws.com") return "kiro";
|
if (h === "q.us-east-1.amazonaws.com") return "kiro";
|
||||||
|
if (h === "api2.cursor.sh") return "cursor";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule src/mitm/dev updated: 39f5376ac1...ebfb215130
@@ -9,6 +9,7 @@ const TOOL_HOSTS = {
|
|||||||
antigravity: ["daily-cloudcode-pa.googleapis.com", "cloudcode-pa.googleapis.com"],
|
antigravity: ["daily-cloudcode-pa.googleapis.com", "cloudcode-pa.googleapis.com"],
|
||||||
copilot: ["api.individual.githubcopilot.com"],
|
copilot: ["api.individual.githubcopilot.com"],
|
||||||
kiro: ["q.us-east-1.amazonaws.com", "codewhisperer.us-east-1.amazonaws.com"],
|
kiro: ["q.us-east-1.amazonaws.com", "codewhisperer.us-east-1.amazonaws.com"],
|
||||||
|
cursor: ["api2.cursor.sh"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const IS_WIN = process.platform === "win32";
|
const IS_WIN = process.platform === "win32";
|
||||||
|
|||||||
@@ -464,6 +464,11 @@ async function startServer(apiKey, sudoPassword) {
|
|||||||
err(msg);
|
err(msg);
|
||||||
startError = msg;
|
startError = msg;
|
||||||
}
|
}
|
||||||
|
// Detect wrong/missing password — clear cache and stop retry loop
|
||||||
|
if (!IS_WIN && (msg.includes("incorrect password") || msg.includes("no password was provided"))) {
|
||||||
|
setCachedPassword(null);
|
||||||
|
mitmIsRestarting = true; // prevent scheduleMitmRestart from firing
|
||||||
|
}
|
||||||
});
|
});
|
||||||
serverProcess.on("exit", (code) => {
|
serverProcess.on("exit", (code) => {
|
||||||
log(`Server exited (code: ${code})`);
|
log(`Server exited (code: ${code})`);
|
||||||
|
|||||||
+26
-1
@@ -26,6 +26,7 @@ const handlers = {
|
|||||||
antigravity: loadHandler("antigravity"),
|
antigravity: loadHandler("antigravity"),
|
||||||
copilot: loadHandler("copilot"),
|
copilot: loadHandler("copilot"),
|
||||||
kiro: loadHandler("kiro"),
|
kiro: loadHandler("kiro"),
|
||||||
|
cursor: loadHandler("cursor"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── SSL / SNI ─────────────────────────────────────────────────
|
// ── SSL / SNI ─────────────────────────────────────────────────
|
||||||
@@ -118,7 +119,12 @@ function saveRequestLog(url, bodyBuffer) {
|
|||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function passthrough(req, res, bodyBuffer) {
|
/**
|
||||||
|
* Forward request to real upstream.
|
||||||
|
* Optional onResponse(rawBuffer) callback — if provided, tees the response
|
||||||
|
* so it's both forwarded to client AND passed to the callback for inspection.
|
||||||
|
*/
|
||||||
|
async function passthrough(req, res, bodyBuffer, onResponse) {
|
||||||
const targetHost = (req.headers.host || TARGET_HOSTS[0]).split(":")[0];
|
const targetHost = (req.headers.host || TARGET_HOSTS[0]).split(":")[0];
|
||||||
const targetIP = await resolveTargetIP(targetHost);
|
const targetIP = await resolveTargetIP(targetHost);
|
||||||
|
|
||||||
@@ -132,7 +138,19 @@ async function passthrough(req, res, bodyBuffer) {
|
|||||||
rejectUnauthorized: false
|
rejectUnauthorized: false
|
||||||
}, (forwardRes) => {
|
}, (forwardRes) => {
|
||||||
res.writeHead(forwardRes.statusCode, forwardRes.headers);
|
res.writeHead(forwardRes.statusCode, forwardRes.headers);
|
||||||
|
|
||||||
|
if (!onResponse) {
|
||||||
forwardRes.pipe(res);
|
forwardRes.pipe(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tee: forward to client AND buffer for callback
|
||||||
|
const chunks = [];
|
||||||
|
forwardRes.on("data", chunk => { chunks.push(chunk); res.write(chunk); });
|
||||||
|
forwardRes.on("end", () => {
|
||||||
|
res.end();
|
||||||
|
try { onResponse(Buffer.concat(chunks), forwardRes.headers); } catch { /* ignore */ }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
forwardReq.on("error", (e) => {
|
forwardReq.on("error", (e) => {
|
||||||
@@ -172,6 +190,13 @@ const server = https.createServer(sslOptions, async (req, res) => {
|
|||||||
|
|
||||||
log(`🔍 [${tool}] url=${req.url} | bodyLen=${bodyBuffer.length}`);
|
log(`🔍 [${tool}] url=${req.url} | bodyLen=${bodyBuffer.length}`);
|
||||||
|
|
||||||
|
// Cursor uses binary proto — model extraction not possible at this layer.
|
||||||
|
// Delegate directly to handler which decodes proto internally.
|
||||||
|
if (tool === "cursor") {
|
||||||
|
log(`⚡ intercept | cursor | proto`);
|
||||||
|
return handlers[tool].intercept(req, res, bodyBuffer, null, passthrough);
|
||||||
|
}
|
||||||
|
|
||||||
const model = extractModel(req.url, bodyBuffer);
|
const model = extractModel(req.url, bodyBuffer);
|
||||||
log(`🔍 [${tool}] model="${model}"`);
|
log(`🔍 [${tool}] model="${model}"`);
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function Sidebar({ onClose }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<aside className="flex w-72 flex-col border-r border-black/5 dark:border-white/5 bg-vibrancy backdrop-blur-xl transition-colors duration-300">
|
<aside className="flex w-72 flex-col border-r border-black/5 dark:border-white/5 bg-vibrancy backdrop-blur-xl transition-colors duration-300 min-h-full">
|
||||||
{/* Traffic lights */}
|
{/* Traffic lights */}
|
||||||
<div className="flex items-center gap-2 px-6 pt-5 pb-2">
|
<div className="flex items-center gap-2 px-6 pt-5 pb-2">
|
||||||
<div className="w-3 h-3 rounded-full bg-[#FF5F56]" />
|
<div className="w-3 h-3 rounded-full bg-[#FF5F56]" />
|
||||||
|
|||||||
@@ -1,51 +1,5 @@
|
|||||||
// CLI Tools configuration
|
// MITM Tools — IDE tools intercepted via MITM proxy
|
||||||
export const CLI_TOOLS = {
|
export const MITM_TOOLS = {
|
||||||
claude: {
|
|
||||||
id: "claude",
|
|
||||||
name: "Claude Code",
|
|
||||||
icon: "terminal",
|
|
||||||
color: "#D97757",
|
|
||||||
description: "Anthropic Claude Code CLI",
|
|
||||||
configType: "env",
|
|
||||||
envVars: {
|
|
||||||
baseUrl: "ANTHROPIC_BASE_URL",
|
|
||||||
model: "ANTHROPIC_MODEL",
|
|
||||||
opusModel: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
||||||
sonnetModel: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
||||||
haikuModel: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
||||||
},
|
|
||||||
modelAliases: ["default", "sonnet", "opus", "haiku", "opusplan"],
|
|
||||||
settingsFile: "~/.claude/settings.json",
|
|
||||||
defaultModels: [
|
|
||||||
{ id: "opus", name: "Claude Opus", alias: "opus", envKey: "ANTHROPIC_DEFAULT_OPUS_MODEL", defaultValue: "cc/claude-opus-4-6" },
|
|
||||||
{ id: "sonnet", name: "Claude Sonnet", alias: "sonnet", envKey: "ANTHROPIC_DEFAULT_SONNET_MODEL", defaultValue: "cc/claude-sonnet-4-6" },
|
|
||||||
{ id: "haiku", name: "Claude Haiku", alias: "haiku", envKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL", defaultValue: "cc/claude-haiku-4-5-20251001" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
openclaw: {
|
|
||||||
id: "openclaw",
|
|
||||||
name: "Open Claw",
|
|
||||||
image: "/providers/openclaw.png",
|
|
||||||
color: "#FF6B35",
|
|
||||||
description: "Open Claw AI Assistant",
|
|
||||||
configType: "custom",
|
|
||||||
},
|
|
||||||
codex: {
|
|
||||||
id: "codex",
|
|
||||||
name: "OpenAI Codex CLI",
|
|
||||||
image: "/providers/codex.png",
|
|
||||||
color: "#10A37F",
|
|
||||||
description: "OpenAI Codex CLI",
|
|
||||||
configType: "custom",
|
|
||||||
},
|
|
||||||
opencode: {
|
|
||||||
id: "opencode",
|
|
||||||
name: "OpenCode",
|
|
||||||
image: "/providers/opencode.png",
|
|
||||||
color: "#E87040",
|
|
||||||
description: "OpenCode AI Terminal Assistant",
|
|
||||||
configType: "custom",
|
|
||||||
},
|
|
||||||
antigravity: {
|
antigravity: {
|
||||||
id: "antigravity",
|
id: "antigravity",
|
||||||
name: "Antigravity",
|
name: "Antigravity",
|
||||||
@@ -96,6 +50,70 @@ export const CLI_TOOLS = {
|
|||||||
{ id: "simple-task", name: "Qwen3 Coder Next", alias: "simple-task" },
|
{ id: "simple-task", name: "Qwen3 Coder Next", alias: "simple-task" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// cursor: {
|
||||||
|
// id: "cursor",
|
||||||
|
// name: "Cursor",
|
||||||
|
// image: "/providers/cursor.png",
|
||||||
|
// color: "#000000",
|
||||||
|
// description: "Cursor IDE with MITM",
|
||||||
|
// configType: "mitm",
|
||||||
|
// mitmDomain: "api2.cursor.sh",
|
||||||
|
// defaultModels: [
|
||||||
|
// { id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", alias: "claude-sonnet-4-5" },
|
||||||
|
// { id: "claude-opus-4", name: "Claude Opus 4", alias: "claude-opus-4" },
|
||||||
|
// { id: "gpt-4o", name: "GPT-4o", alias: "gpt-4o" },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
|
||||||
|
// CLI Tools configuration
|
||||||
|
export const CLI_TOOLS = {
|
||||||
|
claude: {
|
||||||
|
id: "claude",
|
||||||
|
name: "Claude Code",
|
||||||
|
icon: "terminal",
|
||||||
|
color: "#D97757",
|
||||||
|
description: "Anthropic Claude Code CLI",
|
||||||
|
configType: "env",
|
||||||
|
envVars: {
|
||||||
|
baseUrl: "ANTHROPIC_BASE_URL",
|
||||||
|
model: "ANTHROPIC_MODEL",
|
||||||
|
opusModel: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||||
|
sonnetModel: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
||||||
|
haikuModel: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
||||||
|
},
|
||||||
|
modelAliases: ["default", "sonnet", "opus", "haiku", "opusplan"],
|
||||||
|
settingsFile: "~/.claude/settings.json",
|
||||||
|
defaultModels: [
|
||||||
|
{ id: "opus", name: "Claude Opus", alias: "opus", envKey: "ANTHROPIC_DEFAULT_OPUS_MODEL", defaultValue: "cc/claude-opus-4-6" },
|
||||||
|
{ id: "sonnet", name: "Claude Sonnet", alias: "sonnet", envKey: "ANTHROPIC_DEFAULT_SONNET_MODEL", defaultValue: "cc/claude-sonnet-4-6" },
|
||||||
|
{ id: "haiku", name: "Claude Haiku", alias: "haiku", envKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL", defaultValue: "cc/claude-haiku-4-5-20251001" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
openclaw: {
|
||||||
|
id: "openclaw",
|
||||||
|
name: "Open Claw",
|
||||||
|
image: "/providers/openclaw.png",
|
||||||
|
color: "#FF6B35",
|
||||||
|
description: "Open Claw AI Assistant",
|
||||||
|
configType: "custom",
|
||||||
|
},
|
||||||
|
codex: {
|
||||||
|
id: "codex",
|
||||||
|
name: "OpenAI Codex CLI",
|
||||||
|
image: "/providers/codex.png",
|
||||||
|
color: "#10A37F",
|
||||||
|
description: "OpenAI Codex CLI",
|
||||||
|
configType: "custom",
|
||||||
|
},
|
||||||
|
opencode: {
|
||||||
|
id: "opencode",
|
||||||
|
name: "OpenCode",
|
||||||
|
image: "/providers/opencode.png",
|
||||||
|
color: "#E87040",
|
||||||
|
description: "OpenCode AI Terminal Assistant",
|
||||||
|
configType: "custom",
|
||||||
|
},
|
||||||
droid: {
|
droid: {
|
||||||
id: "droid",
|
id: "droid",
|
||||||
name: "Factory Droid",
|
name: "Factory Droid",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const g = global.__appSingleton ??= {
|
|||||||
lastWatchdogTick: Date.now(),
|
lastWatchdogTick: Date.now(),
|
||||||
lastTunnelRestartAt: 0,
|
lastTunnelRestartAt: 0,
|
||||||
tunnelRestartInProgress: false,
|
tunnelRestartInProgress: false,
|
||||||
|
mitmStartInProgress: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const WATCHDOG_INTERVAL_MS = 60000;
|
const WATCHDOG_INTERVAL_MS = 60000;
|
||||||
@@ -100,6 +101,8 @@ export async function initializeApp() {
|
|||||||
|
|
||||||
/** Auto-start MITM if it was enabled before restart */
|
/** Auto-start MITM if it was enabled before restart */
|
||||||
async function autoStartMitm() {
|
async function autoStartMitm() {
|
||||||
|
if (g.mitmStartInProgress) return;
|
||||||
|
g.mitmStartInProgress = true;
|
||||||
try {
|
try {
|
||||||
const settings = await getSettings();
|
const settings = await getSettings();
|
||||||
if (!settings.mitmEnabled) return;
|
if (!settings.mitmEnabled) return;
|
||||||
@@ -118,10 +121,12 @@ async function autoStartMitm() {
|
|||||||
const activeKey = keys.find(k => k.isActive !== false);
|
const activeKey = keys.find(k => k.isActive !== false);
|
||||||
|
|
||||||
console.log("[InitApp] MITM was enabled, auto-starting...");
|
console.log("[InitApp] MITM was enabled, auto-starting...");
|
||||||
await startMitm(activeKey?.key || "sk_9router", password || "");
|
await startMitm(activeKey?.key || "sk_9router", password);
|
||||||
console.log("[InitApp] MITM auto-started");
|
console.log("[InitApp] MITM auto-started");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[InitApp] MITM auto-start failed:", err.message);
|
console.log("[InitApp] MITM auto-start failed:", err.message);
|
||||||
|
} finally {
|
||||||
|
g.mitmStartInProgress = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user