Files
OmniRoute/tests/unit/cli/setup-cline.test.ts
Diego Rodrigues de Sa e Souza 158a2246dd feat(cli): setup-cline — configure Cline for OmniRoute (CLI files + VS Code hints) (#4280)
CLI #4 of the series. Cline's VS Code extension keeps config in opaque VS Code
globalStorage (not file-writable); its CLI/standalone mode reads ~/.cline/data/.

`omniroute setup-cline`:
- writes ~/.cline/data/globalState.json (act/planModeApiProvider=openai,
  openAiBaseUrl = ROOT url WITHOUT /v1 — Cline appends /v1/chat/completions —
  openAiModelId + planModeOpenAiModelId) and ~/.cline/data/secrets.json
  (openAiApiKey), both merged to preserve existing state. Matches the dashboard
  cli-tools/cline-settings schema.
- remote-aware (--remote/--api-key → active context → localhost).
- model resolved via --model or an interactive pick from /v1/models (Cline has
  no model auto-discovery).
- prints the exact VS Code extension settings (Base URL/key/model) to paste,
  since the extension's storage can't be written directly.

Researched against the current Cline docs (saoudrizwan.claude-dev): confirmed
the openai-compatible keys, the Plan/Act split, and that openAiBaseUrl must be
the ROOT (no /v1). Cline's wire (/v1/chat/completions) already validated → "OK".

Tests: buildClineGlobalState (provider+root+model, merge-preserve),
buildClineSecrets (key + placeholder), resolveClineTarget (/v1 strip, key win).
6 unit tests; check:cli-i18n green.
2026-06-19 12:30:51 -03:00

47 lines
1.8 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import {
buildClineGlobalState,
buildClineSecrets,
resolveClineTarget,
} from "../../../bin/cli/commands/setup-cline.mjs";
test("buildClineGlobalState sets the openai provider for Plan + Act, root base URL, model", () => {
const gs = buildClineGlobalState({}, { baseUrl: "http://vps:20128", model: "glm/glm-5.2" });
assert.equal(gs.actModeApiProvider, "openai");
assert.equal(gs.planModeApiProvider, "openai");
assert.equal(gs.openAiBaseUrl, "http://vps:20128");
assert.equal(gs.openAiModelId, "glm/glm-5.2");
assert.equal(gs.planModeOpenAiModelId, "glm/glm-5.2");
});
test("buildClineGlobalState merges (preserves unrelated existing keys)", () => {
const gs = buildClineGlobalState(
{ telemetrySetting: "off", taskHistory: [1, 2, 3] },
{ baseUrl: "http://x:20128", model: "m" }
);
assert.equal(gs.telemetrySetting, "off");
assert.deepEqual(gs.taskHistory, [1, 2, 3]);
assert.equal(gs.openAiBaseUrl, "http://x:20128");
});
test("buildClineSecrets stores the key (separate secrets file), preserving others", () => {
const sec = buildClineSecrets({ anthropicApiKey: "keepme" }, { apiKey: "sk-omni" });
assert.equal(sec.openAiApiKey, "sk-omni");
assert.equal(sec.anthropicApiKey, "keepme");
});
test("buildClineSecrets falls back to a placeholder when no key", () => {
assert.equal(buildClineSecrets({}, { apiKey: "" }).openAiApiKey, "sk_omniroute");
});
test("resolveClineTarget strips /v1 from --remote (Cline wants the ROOT url)", () => {
const { baseUrl } = resolveClineTarget({ remote: "http://vps:20128/v1/" });
assert.equal(baseUrl, "http://vps:20128");
});
test("resolveClineTarget: explicit --api-key wins", () => {
const { apiKey } = resolveClineTarget({ remote: "http://x:20128", apiKey: "sk-explicit" });
assert.equal(apiKey, "sk-explicit");
});