feat(keys): one-paste harness setup — Claude Code, Codex, OpenCode, Continue, Cline
The "connect your app" card in the API-keys view now leads with auto-setup
scripts for popular coding harnesses, each pre-filled with this gateway's URL
and the freshly created key:
Claude Code — env exports (the gateway speaks Anthropic /v1/messages
natively; the protocol is now ON by default, and when a config has it
off the tab shows a warning with a one-click Enable button)
Codex — appends a [model_providers.cortiq] block to ~/.codex/config.toml
OpenCode — writes the provider into opencode.json
Continue — appends the model to ~/.continue/config.yaml
Cline — the exact values to paste into the extension settings
The gateway also detects which harnesses are already present on its machine
(binaries across PATH + common install dirs, config files under home) via
GET /admin/api/harnesses: installed tabs get a ✓, and the status line under
the snippet says "installed" or "already configured for this gateway" when
the harness config mentions cortiq. Works on Linux/macOS/Windows.
curl / python / javascript SDK snippets stay as before. 16 new strings in
all 7 languages.
This commit is contained in:
@@ -71,7 +71,7 @@ openai_chat = true
|
||||
openai_completions = true
|
||||
openai_embeddings = true
|
||||
openai_models = true
|
||||
anthropic_messages = false
|
||||
anthropic_messages = true
|
||||
mcp = false
|
||||
native_passthrough = true
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ pub fn api_routes(admin_token: String) -> Router<SharedState> {
|
||||
"/admin/api/protocols",
|
||||
get(get_protocols).put(put_protocols),
|
||||
)
|
||||
.route("/admin/api/harnesses", get(get_harnesses))
|
||||
.route("/admin/api/settings", get(get_settings).put(put_settings))
|
||||
.route("/admin/api/keys", get(list_keys).post(create_key))
|
||||
.route("/admin/api/keys/:key", axum::routing::delete(delete_key))
|
||||
@@ -989,6 +990,84 @@ async fn put_routing(State(state): State<SharedState>, Json(body): Json<RoutingB
|
||||
ok(json!({ "ok": true }))
|
||||
}
|
||||
|
||||
// ──────────────────────────── harness detection ────────────────────────────
|
||||
// Which coding harnesses are present on the machine the gateway runs on, so the
|
||||
// API-keys view can mark tabs "installed" / "already configured for cortiq".
|
||||
// Detection is best-effort: binaries in PATH + common install dirs, config
|
||||
// files under the user's home. Works on Linux/macOS/Windows.
|
||||
|
||||
fn harness_home() -> Option<std::path::PathBuf> {
|
||||
std::env::var_os("HOME")
|
||||
.or_else(|| std::env::var_os("USERPROFILE"))
|
||||
.map(std::path::PathBuf::from)
|
||||
}
|
||||
|
||||
fn find_bin(name: &str) -> bool {
|
||||
let exts: &[&str] = if cfg!(windows) {
|
||||
&[".exe", ".cmd", ".bat", ""]
|
||||
} else {
|
||||
&[""]
|
||||
};
|
||||
let mut dirs: Vec<std::path::PathBuf> = std::env::var_os("PATH")
|
||||
.map(|p| std::env::split_paths(&p).collect())
|
||||
.unwrap_or_default();
|
||||
if let Some(home) = harness_home() {
|
||||
// GUI-launched processes often miss the shell PATH — add usual suspects
|
||||
for extra in [
|
||||
".local/bin",
|
||||
".cargo/bin",
|
||||
"bin",
|
||||
".bun/bin",
|
||||
".npm-global/bin",
|
||||
"AppData/Roaming/npm",
|
||||
] {
|
||||
dirs.push(home.join(extra));
|
||||
}
|
||||
}
|
||||
dirs.extend(["/usr/local/bin", "/opt/homebrew/bin"].map(std::path::PathBuf::from));
|
||||
dirs.iter()
|
||||
.any(|d| exts.iter().any(|e| d.join(format!("{name}{e}")).is_file()))
|
||||
}
|
||||
|
||||
/// Does the harness config mention cortiq (i.e. was it pointed at this gateway)?
|
||||
fn config_mentions_cortiq(rel: &str) -> bool {
|
||||
harness_home()
|
||||
.map(|h| h.join(rel))
|
||||
.and_then(|p| std::fs::read_to_string(p).ok())
|
||||
.map(|c| c.to_lowercase().contains("cortiq"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
async fn get_harnesses() -> ApiResult {
|
||||
let harnesses = tokio::task::spawn_blocking(|| {
|
||||
let vscode_ext_glob = |marker: &str| -> bool {
|
||||
let Some(home) = harness_home() else {
|
||||
return false;
|
||||
};
|
||||
for extdir in [".vscode/extensions", ".vscode-server/extensions"] {
|
||||
if let Ok(rd) = std::fs::read_dir(home.join(extdir)) {
|
||||
for e in rd.flatten() {
|
||||
if e.file_name().to_string_lossy().starts_with(marker) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
json!({
|
||||
"Claude Code": { "installed": find_bin("claude"), "configured": serde_json::Value::Null },
|
||||
"Codex": { "installed": find_bin("codex"), "configured": config_mentions_cortiq(".codex/config.toml") },
|
||||
"OpenCode": { "installed": find_bin("opencode"), "configured": config_mentions_cortiq(".config/opencode/opencode.json") },
|
||||
"Continue": { "installed": harness_home().map(|h| h.join(".continue").is_dir()).unwrap_or(false), "configured": config_mentions_cortiq(".continue/config.yaml") },
|
||||
"Cline": { "installed": vscode_ext_glob("saoudrizwan.claude-dev"), "configured": serde_json::Value::Null },
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|_| json!({}));
|
||||
ok(harnesses)
|
||||
}
|
||||
|
||||
// ───────────────────────────── protocols ───────────────────────────────────
|
||||
|
||||
async fn get_protocols(State(state): State<SharedState>) -> ApiResult {
|
||||
|
||||
+3
-1
@@ -757,7 +757,9 @@ impl Default for Config {
|
||||
openai_completions: true,
|
||||
openai_embeddings: true,
|
||||
openai_models: true,
|
||||
anthropic_messages: false,
|
||||
// on by default: the API-keys view ships a Claude Code
|
||||
// auto-setup snippet that targets /v1/messages
|
||||
anthropic_messages: true,
|
||||
mcp: false,
|
||||
native_passthrough: true,
|
||||
},
|
||||
|
||||
Vendored
+4
@@ -588,6 +588,10 @@ body.modal-open { overflow: hidden; }
|
||||
/* Money: 4 decimals, last two de-emphasized */
|
||||
.money-tail { font-size: .72em; opacity: .75; margin-left: 1px; }
|
||||
|
||||
.seg { flex-wrap: wrap; }
|
||||
|
||||
.ok { color: var(--good); }
|
||||
|
||||
/* Benchmark view */
|
||||
@keyframes bm-in { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
|
||||
tbody tr:last-child { animation: bm-in .25s ease; }
|
||||
|
||||
Vendored
+1
@@ -73,6 +73,7 @@ export const api = {
|
||||
|
||||
// protocols
|
||||
getProtocols: () => j("GET", "/protocols"),
|
||||
harnesses: () => j("GET", "/harnesses"),
|
||||
putProtocols: (b) => j("PUT", "/protocols", b),
|
||||
|
||||
// settings
|
||||
|
||||
Vendored
+14
@@ -602,4 +602,18 @@ export default {
|
||||
"bm.spent": "ausgegeben",
|
||||
"bm.verdict": "🏁 Fertig: Antworten {p}, ausgegeben {s}",
|
||||
"bm.clearHistory": "Alle löschen",
|
||||
|
||||
// harness auto-setup (API keys view)
|
||||
"keys.h.claude": "Claude Code — ins Terminal einfügen (das Gateway spricht das Anthropic-Protokoll nativ):",
|
||||
"keys.h.codex": "Codex — hängt den Provider an ~/.codex/config.toml an und startet:",
|
||||
"keys.h.opencode": "OpenCode — schreibt ~/.config/opencode/opencode.json (überschreibt!) und startet:",
|
||||
"keys.h.continue": "Continue (VS Code / JetBrains) — hängt das Modell an ~/.continue/config.yaml an:",
|
||||
"keys.h.cline1": "Cline (VS Code) — in den Erweiterungseinstellungen wählen:",
|
||||
"keys.h.cline2": "Einstellungen → API Provider → OpenAI Compatible, dann die Werte oben einfügen.",
|
||||
"keys.h.note": "cortiq-auto = smartes Routing: der Router wählt das Modell pro Anfrage. Für ein festes Modell den exakten Namen angeben. Unter Windows die Skripte in Git Bash / WSL ausführen oder dieselben Variablen über die Systemumgebung setzen.",
|
||||
"keys.h.protoOff": "Das Anthropic-Protokoll (/v1/messages) ist deaktiviert — Claude Code kann sich nicht verbinden.",
|
||||
"keys.h.enableProto": "Aktivieren",
|
||||
"keys.h.st.installed": "Auf der Gateway-Maschine installiert",
|
||||
"keys.h.st.configured": "Bereits für dieses Gateway konfiguriert",
|
||||
"keys.h.st.notFound": "Auf der Gateway-Maschine nicht gefunden",
|
||||
};
|
||||
|
||||
Vendored
+14
@@ -595,4 +595,18 @@ export default {
|
||||
"bm.spent": "spent",
|
||||
"bm.verdict": "🏁 Done: answers {p}, spent {s}",
|
||||
"bm.clearHistory": "Clear all",
|
||||
|
||||
// harness auto-setup (API keys view)
|
||||
"keys.h.claude": "Claude Code — paste into the terminal (the gateway speaks the Anthropic protocol natively):",
|
||||
"keys.h.codex": "Codex — appends the provider to ~/.codex/config.toml and starts:",
|
||||
"keys.h.opencode": "OpenCode — writes ~/.config/opencode/opencode.json (overwrites!) and starts:",
|
||||
"keys.h.continue": "Continue (VS Code / JetBrains) — appends the model to ~/.continue/config.yaml:",
|
||||
"keys.h.cline1": "Cline (VS Code) — in extension settings pick:",
|
||||
"keys.h.cline2": "Settings → API Provider → OpenAI Compatible, then paste the values above.",
|
||||
"keys.h.note": "cortiq-auto = smart routing: the router picks the model per request. Use an exact model name to pin one. On Windows run the scripts in Git Bash / WSL, or set the same variables via the system environment settings.",
|
||||
"keys.h.protoOff": "The Anthropic protocol (/v1/messages) is disabled — Claude Code will not connect.",
|
||||
"keys.h.enableProto": "Enable",
|
||||
"keys.h.st.installed": "Installed on the gateway machine",
|
||||
"keys.h.st.configured": "Already configured for this gateway",
|
||||
"keys.h.st.notFound": "Not found on the gateway machine",
|
||||
};
|
||||
|
||||
Vendored
+14
@@ -602,4 +602,18 @@ export default {
|
||||
"bm.spent": "gastado",
|
||||
"bm.verdict": "🏁 Listo: respuestas {p}, gastado {s}",
|
||||
"bm.clearHistory": "Borrar todo",
|
||||
|
||||
// harness auto-setup (API keys view)
|
||||
"keys.h.claude": "Claude Code — pega en la terminal (la pasarela habla el protocolo Anthropic de forma nativa):",
|
||||
"keys.h.codex": "Codex — añade el proveedor a ~/.codex/config.toml y arranca:",
|
||||
"keys.h.opencode": "OpenCode — escribe ~/.config/opencode/opencode.json (¡sobrescribe!) y arranca:",
|
||||
"keys.h.continue": "Continue (VS Code / JetBrains) — añade el modelo a ~/.continue/config.yaml:",
|
||||
"keys.h.cline1": "Cline (VS Code) — en los ajustes de la extensión elige:",
|
||||
"keys.h.cline2": "Ajustes → API Provider → OpenAI Compatible, luego pega los valores de arriba.",
|
||||
"keys.h.note": "cortiq-auto = enrutado inteligente: el router elige el modelo en cada petición. Para fijar uno, indica su nombre exacto. En Windows ejecuta los scripts en Git Bash / WSL o define las mismas variables en el entorno del sistema.",
|
||||
"keys.h.protoOff": "El protocolo Anthropic (/v1/messages) está desactivado — Claude Code no podrá conectarse.",
|
||||
"keys.h.enableProto": "Activar",
|
||||
"keys.h.st.installed": "Instalado en la máquina de la pasarela",
|
||||
"keys.h.st.configured": "Ya configurado para esta pasarela",
|
||||
"keys.h.st.notFound": "No encontrado en la máquina de la pasarela",
|
||||
};
|
||||
|
||||
Vendored
+14
@@ -602,4 +602,18 @@ export default {
|
||||
"bm.spent": "dépensé",
|
||||
"bm.verdict": "🏁 Terminé : réponses {p}, dépensé {s}",
|
||||
"bm.clearHistory": "Tout effacer",
|
||||
|
||||
// harness auto-setup (API keys view)
|
||||
"keys.h.claude": "Claude Code — collez dans le terminal (la passerelle parle nativement le protocole Anthropic) :",
|
||||
"keys.h.codex": "Codex — ajoute le fournisseur à ~/.codex/config.toml et démarre :",
|
||||
"keys.h.opencode": "OpenCode — écrit ~/.config/opencode/opencode.json (écrase !) et démarre :",
|
||||
"keys.h.continue": "Continue (VS Code / JetBrains) — ajoute le modèle à ~/.continue/config.yaml :",
|
||||
"keys.h.cline1": "Cline (VS Code) — dans les réglages de l'extension, choisissez :",
|
||||
"keys.h.cline2": "Réglages → API Provider → OpenAI Compatible, puis collez les valeurs ci-dessus.",
|
||||
"keys.h.note": "cortiq-auto = routage intelligent : le routeur choisit le modèle à chaque requête. Pour en fixer un, indiquez son nom exact. Sous Windows, lancez les scripts dans Git Bash / WSL ou définissez les mêmes variables via l'environnement système.",
|
||||
"keys.h.protoOff": "Le protocole Anthropic (/v1/messages) est désactivé — Claude Code ne pourra pas se connecter.",
|
||||
"keys.h.enableProto": "Activer",
|
||||
"keys.h.st.installed": "Installé sur la machine de la passerelle",
|
||||
"keys.h.st.configured": "Déjà configuré pour cette passerelle",
|
||||
"keys.h.st.notFound": "Introuvable sur la machine de la passerelle",
|
||||
};
|
||||
|
||||
Vendored
+14
@@ -594,4 +594,18 @@ export default {
|
||||
"bm.spent": "потрачено",
|
||||
"bm.verdict": "🏁 Готово: ответы {p}, потрачено {s}",
|
||||
"bm.clearHistory": "Очистить всё",
|
||||
|
||||
// harness auto-setup (API keys view)
|
||||
"keys.h.claude": "Claude Code — вставьте в терминал (шлюз говорит на протоколе Anthropic нативно):",
|
||||
"keys.h.codex": "Codex — добавит провайдера в ~/.codex/config.toml и запустит:",
|
||||
"keys.h.opencode": "OpenCode — запишет ~/.config/opencode/opencode.json (перезапишет!) и запустит:",
|
||||
"keys.h.continue": "Continue (VS Code / JetBrains) — добавит модель в ~/.continue/config.yaml:",
|
||||
"keys.h.cline1": "Cline (VS Code) — в настройках расширения укажите:",
|
||||
"keys.h.cline2": "Настройки → API Provider → OpenAI Compatible, затем вставьте значения выше.",
|
||||
"keys.h.note": "cortiq-auto — умный роутинг: роутер сам выбирает модель под каждый запрос. Чтобы закрепить конкретную, укажите её точное имя. На Windows запускайте скрипты в Git Bash / WSL или задайте те же переменные через настройки окружения системы.",
|
||||
"keys.h.protoOff": "Протокол Anthropic (/v1/messages) выключен — Claude Code не подключится.",
|
||||
"keys.h.enableProto": "Включить",
|
||||
"keys.h.st.installed": "Установлен на машине шлюза",
|
||||
"keys.h.st.configured": "Уже настроен на этот шлюз",
|
||||
"keys.h.st.notFound": "Не найден на машине шлюза",
|
||||
};
|
||||
|
||||
Vendored
+14
@@ -602,4 +602,18 @@ export default {
|
||||
"bm.spent": "harcanan",
|
||||
"bm.verdict": "🏁 Bitti: yanıtlar {p}, harcanan {s}",
|
||||
"bm.clearHistory": "Tümünü temizle",
|
||||
|
||||
// harness auto-setup (API keys view)
|
||||
"keys.h.claude": "Claude Code — terminale yapıştırın (ağ geçidi Anthropic protokolünü doğal olarak konuşur):",
|
||||
"keys.h.codex": "Codex — sağlayıcıyı ~/.codex/config.toml dosyasına ekler ve başlatır:",
|
||||
"keys.h.opencode": "OpenCode — ~/.config/opencode/opencode.json dosyasını yazar (üzerine yazar!) ve başlatır:",
|
||||
"keys.h.continue": "Continue (VS Code / JetBrains) — modeli ~/.continue/config.yaml dosyasına ekler:",
|
||||
"keys.h.cline1": "Cline (VS Code) — eklenti ayarlarında şunları seçin:",
|
||||
"keys.h.cline2": "Ayarlar → API Provider → OpenAI Compatible, ardından yukarıdaki değerleri yapıştırın.",
|
||||
"keys.h.note": "cortiq-auto = akıllı yönlendirme: yönlendirici her istek için modeli kendisi seçer. Belirli bir model için tam adını yazın. Windows'ta betikleri Git Bash / WSL içinde çalıştırın veya aynı değişkenleri sistem ortamından ayarlayın.",
|
||||
"keys.h.protoOff": "Anthropic protokolü (/v1/messages) kapalı — Claude Code bağlanamaz.",
|
||||
"keys.h.enableProto": "Etkinleştir",
|
||||
"keys.h.st.installed": "Ağ geçidi makinesinde kurulu",
|
||||
"keys.h.st.configured": "Bu ağ geçidi için zaten yapılandırılmış",
|
||||
"keys.h.st.notFound": "Ağ geçidi makinesinde bulunamadı",
|
||||
};
|
||||
|
||||
Vendored
+14
@@ -602,4 +602,18 @@ export default {
|
||||
"bm.spent": "花费",
|
||||
"bm.verdict": "🏁 完成:回答 {p},花费 {s}",
|
||||
"bm.clearHistory": "全部清除",
|
||||
|
||||
// harness auto-setup (API keys view)
|
||||
"keys.h.claude": "Claude Code — 粘贴到终端(网关原生支持 Anthropic 协议):",
|
||||
"keys.h.codex": "Codex — 将提供商追加到 ~/.codex/config.toml 并启动:",
|
||||
"keys.h.opencode": "OpenCode — 写入 ~/.config/opencode/opencode.json(会覆盖!)并启动:",
|
||||
"keys.h.continue": "Continue(VS Code / JetBrains)— 将模型追加到 ~/.continue/config.yaml:",
|
||||
"keys.h.cline1": "Cline(VS Code)— 在扩展设置中选择:",
|
||||
"keys.h.cline2": "设置 → API Provider → OpenAI Compatible,然后粘贴上面的值。",
|
||||
"keys.h.note": "cortiq-auto = 智能路由:路由器为每个请求选择模型。要固定某个模型,请填写其精确名称。Windows 上请在 Git Bash / WSL 中运行脚本,或通过系统环境变量设置相同的值。",
|
||||
"keys.h.protoOff": "Anthropic 协议(/v1/messages)已关闭 — Claude Code 无法连接。",
|
||||
"keys.h.enableProto": "启用",
|
||||
"keys.h.st.installed": "已安装在网关所在机器上",
|
||||
"keys.h.st.configured": "已配置为使用此网关",
|
||||
"keys.h.st.notFound": "网关所在机器上未找到",
|
||||
};
|
||||
|
||||
Vendored
+109
-6
@@ -58,9 +58,64 @@ export async function renderKeys() {
|
||||
// Keys are stored hashed-masked in the list, so the key itself is a
|
||||
// placeholder unless one was just created on this page.
|
||||
function snippetsCard(openMode) {
|
||||
const base = location.origin + "/v1";
|
||||
const origin = location.origin;
|
||||
const base = origin + "/v1";
|
||||
const key = lastCreatedKey || (openMode ? "sk-anything" : "<" + t("keys.snip.keyPh") + ">");
|
||||
// Harness auto-setup scripts first (the gateway speaks OpenAI at /v1/* and
|
||||
// Anthropic at /v1/messages, so Claude Code connects natively), SDKs after.
|
||||
const snips = {
|
||||
"Claude Code": `# ${t("keys.h.claude")}
|
||||
export ANTHROPIC_BASE_URL="${origin}"
|
||||
export ANTHROPIC_AUTH_TOKEN="${key}"
|
||||
export ANTHROPIC_MODEL="cortiq-auto"
|
||||
|
||||
claude`,
|
||||
Codex: `# ${t("keys.h.codex")}
|
||||
mkdir -p ~/.codex && cat >> ~/.codex/config.toml <<'EOF'
|
||||
model = "cortiq-auto"
|
||||
model_provider = "cortiq"
|
||||
|
||||
[model_providers.cortiq]
|
||||
name = "Cortiq Gateway"
|
||||
base_url = "${base}"
|
||||
env_key = "CORTIQ_API_KEY"
|
||||
wire_api = "chat"
|
||||
EOF
|
||||
export CORTIQ_API_KEY="${key}"
|
||||
|
||||
codex`,
|
||||
OpenCode: `# ${t("keys.h.opencode")}
|
||||
mkdir -p ~/.config/opencode && cat > ~/.config/opencode/opencode.json <<'EOF'
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": {
|
||||
"cortiq": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"name": "Cortiq Gateway",
|
||||
"options": { "baseURL": "${base}", "apiKey": "${key}" },
|
||||
"models": { "cortiq-auto": { "name": "Cortiq Auto (smart routing)" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
opencode`,
|
||||
Continue: `# ${t("keys.h.continue")}
|
||||
mkdir -p ~/.continue && cat >> ~/.continue/config.yaml <<'EOF'
|
||||
models:
|
||||
- name: Cortiq Auto
|
||||
provider: openai
|
||||
model: cortiq-auto
|
||||
apiBase: ${base}
|
||||
apiKey: ${key}
|
||||
EOF`,
|
||||
Cline: `${t("keys.h.cline1")}
|
||||
API Provider: OpenAI Compatible
|
||||
Base URL: ${base}
|
||||
API Key: ${key}
|
||||
Model ID: cortiq-auto
|
||||
|
||||
${t("keys.h.cline2")}`,
|
||||
curl: `curl ${base}/chat/completions \\
|
||||
-H "Authorization: Bearer ${key}" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
@@ -82,20 +137,65 @@ const resp = await client.chat.completions.create({
|
||||
});
|
||||
console.log(resp.choices[0].message.content);`,
|
||||
};
|
||||
let cur = "curl";
|
||||
let cur = "Claude Code";
|
||||
const pre = h("pre", { class: "conv-log snippet" }, snips[cur]);
|
||||
const seg = h(
|
||||
"div",
|
||||
{ class: "seg" },
|
||||
...Object.keys(snips).map((k, i) =>
|
||||
h("button", { class: i === 0 ? "active" : "", onClick: (e) => {
|
||||
h("button", { class: (i === 0 ? "active" : "") + (["curl", "python", "javascript"].includes(k) ? " sdk" : ""), "data-snip": k, onClick: (e) => {
|
||||
cur = k;
|
||||
pre.textContent = snips[k];
|
||||
protoWarn.style.display = k === "Claude Code" && protoWarn.childNodes.length ? "" : "none";
|
||||
paintStatus();
|
||||
seg.querySelectorAll("button").forEach((b) => b.classList.remove("active"));
|
||||
e.target.classList.add("active");
|
||||
e.currentTarget.classList.add("active");
|
||||
} }, k)
|
||||
)
|
||||
);
|
||||
// Claude Code needs the anthropic_messages protocol — offer a one-click
|
||||
// enable when it is off, right where the user will hit the problem.
|
||||
const protoWarn = h("div", { style: "display:none; margin-top:8px" });
|
||||
api.getProtocols().then((prot) => {
|
||||
if (prot.anthropic_messages) return;
|
||||
const fixBtn = h("button", { class: "btn sm primary" }, t("keys.h.enableProto"));
|
||||
fixBtn.addEventListener("click", async () => {
|
||||
try {
|
||||
await api.putProtocols({ ...prot, anthropic_messages: true });
|
||||
protoWarn.style.display = "none";
|
||||
toast(t("common.saved"), "ok");
|
||||
} catch (e) {
|
||||
toast(String(e.message || e), "err");
|
||||
}
|
||||
});
|
||||
mount(protoWarn, h("div", { class: "callout warn" }, h("span", {}, "⚠ " + t("keys.h.protoOff")), " ", fixBtn));
|
||||
if (cur === "Claude Code") protoWarn.style.display = "";
|
||||
}).catch(() => {});
|
||||
|
||||
// Detected harnesses: ✓ on the tab when installed on the gateway machine,
|
||||
// status line under the snippet ("installed" / "already configured").
|
||||
let detected = {};
|
||||
const statusHost = h("div", { class: "small", style: "margin-top:6px" });
|
||||
function paintStatus() {
|
||||
const d = detected[cur];
|
||||
if (!d) {
|
||||
statusHost.textContent = "";
|
||||
return;
|
||||
}
|
||||
statusHost.innerHTML = "";
|
||||
if (d.configured) mount(statusHost, h("span", { class: "ok" }, "✓ " + t("keys.h.st.configured")));
|
||||
else if (d.installed) mount(statusHost, h("span", { class: "ok" }, "✓ " + t("keys.h.st.installed")));
|
||||
else mount(statusHost, h("span", { class: "muted" }, "○ " + t("keys.h.st.notFound")));
|
||||
}
|
||||
api.harnesses().then((d) => {
|
||||
detected = d || {};
|
||||
seg.querySelectorAll("button").forEach((b) => {
|
||||
const st = detected[b.dataset.snip];
|
||||
if (st && st.installed) b.append(" ✓");
|
||||
});
|
||||
paintStatus();
|
||||
}).catch(() => {});
|
||||
|
||||
const copy = h("button", { class: "btn ghost sm" }, "⧉ " + t("common.copy"));
|
||||
copy.addEventListener("click", async () => {
|
||||
await navigator.clipboard.writeText(pre.textContent);
|
||||
@@ -107,8 +207,11 @@ console.log(resp.choices[0].message.content);`,
|
||||
h("div", { class: "card-title" }, "🔌 " + t("keys.snip.title")),
|
||||
h("div", { class: "muted small", style: "margin-bottom:8px" },
|
||||
lastCreatedKey ? t("keys.snip.withKey") : openMode ? t("keys.snip.openMode") : t("keys.snip.hint")),
|
||||
h("div", { class: "flex", style: "gap:8px; margin-bottom:8px" }, seg, h("div", { class: "grow" }), copy),
|
||||
pre
|
||||
h("div", { class: "flex", style: "gap:8px; margin-bottom:8px; flex-wrap:wrap" }, seg, h("div", { class: "grow" }), copy),
|
||||
pre,
|
||||
statusHost,
|
||||
protoWarn,
|
||||
h("div", { class: "muted small", style: "margin-top:6px" }, t("keys.h.note"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user