Compare commits

..

3 Commits

Author SHA1 Message Date
Saoud Rizwan b7f955c78b chore(cli): release v3.0.26 2026-06-16 21:07:22 -07:00
Saoud Rizwan 8433549327 chore(sdk): release v0.0.49 2026-06-16 20:43:23 -07:00
Saoud Rizwan 860f544ab8 Revert "Make sections expandable and add ClinePass models (#11582)" (#11608)
This reverts commit 2fd944cb84.
2026-06-16 20:33:44 -07:00
20 changed files with 278 additions and 963 deletions
+4
View File
@@ -1,5 +1,9 @@
# Cline CLI Changelog
## 3.0.26
- Reverted the expandable model picker sections and ClinePass models, restoring the previous model-selection UI
## 3.0.25
- Added ClinePass support, with selectable ClinePass models in the model picker
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@cline/cli",
"displayName": "cline",
"version": "3.0.25",
"version": "3.0.26",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"type": "module",
"publishConfig": {
@@ -1,286 +0,0 @@
import type {
ClineRecommendedModel,
ClineRecommendedModelsData,
} from "@cline/core";
export interface ClineModelPickerBrowse {
kind: "browse";
}
export type ClineModelsEntry = {
kind: "model";
tier: ClineModelTier;
model: ClineRecommendedModel;
};
export type ClineModelPickerEntry = ClineModelsEntry | ClineModelPickerBrowse;
export type ClineModelTier = "recommended" | "free" | "clinePass";
export type ClineModelPickerExpandedTiers = Record<ClineModelTier, boolean>;
export type ClineModelProviderId = "cline" | "cline-pass";
export interface BuildClineModelEntriesOptions {
includeClinePass?: boolean;
}
const DEFAULT_EXPANDED_TIERS: ClineModelPickerExpandedTiers = {
clinePass: false,
recommended: false,
free: false,
};
export function resolveClineModelDisplayName(
modelId: string,
knownModels?: Record<string, unknown>,
): string {
if (knownModels) {
const candidates = [modelId, modelId.split("/").pop()];
for (const key of candidates) {
if (!key) continue;
const hit = knownModels[key] as { name?: string } | undefined;
if (hit?.name) return hit.name;
}
}
return modelId.includes("/")
? (modelId.split("/").pop() ?? modelId)
: modelId;
}
export function buildClineModelEntries(
data: ClineRecommendedModelsData,
options: BuildClineModelEntriesOptions = {},
): ClineModelPickerEntry[] {
const entries: ClineModelPickerEntry[] = [
...(options.includeClinePass
? data.clinePass.map((model) => ({
kind: "model" as const,
tier: "clinePass" as const,
model,
}))
: []),
...data.recommended.map((model) => ({
kind: "model" as const,
tier: "recommended" as const,
model,
})),
...data.free.map((model) => ({
kind: "model" as const,
tier: "free" as const,
model,
})),
{
kind: "browse",
},
];
return entries;
}
export function resolveClineModelProviderId(
tier: ClineModelTier,
): ClineModelProviderId {
return tier === "clinePass" ? "cline-pass" : "cline";
}
export function resolveClineModelEntryProviderId(
entry: ClineModelPickerEntry,
): ClineModelProviderId | undefined {
return entry.kind === "model"
? resolveClineModelProviderId(entry.tier)
: undefined;
}
export type ClineModelPickerDisplayRow =
| {
kind: "header";
key: string;
label: string;
tier: ClineModelTier;
isExpanded: boolean;
count: number;
focusIndex: number;
}
| {
kind: "model";
key: string;
label: string;
tags: string[];
isCurrent: boolean;
entryIndex: number;
selectableIndex: number;
focusIndex: number;
}
| {
kind: "browse";
key: string;
label: string;
entryIndex: number;
selectableIndex: number;
focusIndex: number;
};
function normalizeExpandedTiers(
expandedTiers?: Partial<ClineModelPickerExpandedTiers>,
): ClineModelPickerExpandedTiers {
return {
recommended:
expandedTiers?.recommended ?? DEFAULT_EXPANDED_TIERS.recommended,
free: expandedTiers?.free ?? DEFAULT_EXPANDED_TIERS.free,
clinePass: expandedTiers?.clinePass ?? DEFAULT_EXPANDED_TIERS.clinePass,
};
}
function countModelsInTier(
entries: ClineModelPickerEntry[],
tier: ClineModelTier,
): number {
return entries.reduce(
(count, entry) =>
count + (entry.kind === "model" && entry.tier === tier ? 1 : 0),
0,
);
}
export function getVisibleClineModelPickerEntries(
entries: ClineModelPickerEntry[],
expandedTiers?: Partial<ClineModelPickerExpandedTiers>,
): ClineModelPickerEntry[] {
const expanded = normalizeExpandedTiers(expandedTiers);
return entries.filter((entry) => {
if (entry.kind === "browse") return true;
return expanded[entry.tier];
});
}
const TierTitle: Record<ClineModelTier, string> = {
recommended: "Recommended",
clinePass: "Cline Pass",
free: "Free",
};
export function buildClineModelPickerDisplayRows(
entries: ClineModelPickerEntry[],
knownModels?: Record<string, unknown>,
currentModelId?: string,
expandedTiers?: Partial<ClineModelPickerExpandedTiers>,
): ClineModelPickerDisplayRow[] {
const rows: ClineModelPickerDisplayRow[] = [];
const expanded = normalizeExpandedTiers(expandedTiers);
let lastTier: ClineModelTier | null = null;
let selectableIndex = 0;
let focusIndex = 0;
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (!entry) continue;
if (entry.kind === "model") {
if (entry.tier !== lastTier) {
lastTier = entry.tier;
rows.push({
kind: "header",
key: `tier-${entry.tier}`,
label: TierTitle[entry.tier],
tier: entry.tier,
isExpanded: expanded[entry.tier],
count: countModelsInTier(entries, entry.tier),
focusIndex,
});
focusIndex++;
}
if (!expanded[entry.tier]) continue;
rows.push({
kind: "model",
key: `${entry.tier}-${entry.model.id}-${i}`,
label: resolveClineModelDisplayName(entry.model.id, knownModels),
tags: entry.model.tags,
isCurrent: currentModelId === entry.model.id,
entryIndex: i,
selectableIndex,
focusIndex,
});
selectableIndex++;
focusIndex++;
continue;
}
rows.push({
kind: "browse",
key: "browse-all",
label: "Browse all models...",
entryIndex: i,
selectableIndex,
focusIndex,
});
selectableIndex++;
focusIndex++;
}
return rows;
}
function countFocusableRows(rows: ClineModelPickerDisplayRow[]): number {
return rows.length;
}
export function getClineModelPickerDisplayRowsWindow(
rows: ClineModelPickerDisplayRow[],
selected: number,
maxVisibleRows: number,
) {
if (rows.length <= maxVisibleRows) {
return {
visibleRows: rows,
aboveCount: 0,
belowCount: 0,
showAbove: false,
showBelow: false,
};
}
const selectedRowIndex = Math.max(
0,
rows.findIndex((row) => row.focusIndex === selected),
);
let visibleLimit = maxVisibleRows;
let start = 0;
let end = rows.length;
for (let i = 0; i < 3; i++) {
const showAbove = start > 0;
const showBelow = end < rows.length;
visibleLimit = Math.max(
1,
maxVisibleRows - (showAbove ? 1 : 0) - (showBelow ? 1 : 0),
);
start = Math.max(0, selectedRowIndex - Math.floor(visibleLimit / 2));
if (start + visibleLimit > rows.length) {
start = Math.max(0, rows.length - visibleLimit);
}
end = Math.min(rows.length, start + visibleLimit);
}
if (start > 0 && countFocusableRows(rows.slice(0, start)) === 0) {
start = 0;
const showBelow = rows.length > maxVisibleRows;
visibleLimit = Math.max(1, maxVisibleRows - (showBelow ? 1 : 0));
end = Math.min(rows.length, visibleLimit);
}
const aboveCount = countFocusableRows(rows.slice(0, start));
const belowCount = countFocusableRows(rows.slice(end));
return {
visibleRows: rows.slice(start, end),
aboveCount,
belowCount,
showAbove: aboveCount > 0,
showBelow: belowCount > 0,
};
}
export function getClineModelPickerRowByFocusIndex(
rows: ClineModelPickerDisplayRow[],
focusIndex: number,
): ClineModelPickerDisplayRow | undefined {
return rows.find((row) => row.focusIndex === focusIndex);
}
@@ -1,186 +0,0 @@
import type { ClineRecommendedModelsData } from "@cline/core";
import { describe, expect, it } from "vitest";
import {
buildClineModelEntries,
buildClineModelPickerDisplayRows,
getClineModelPickerDisplayRowsWindow,
getVisibleClineModelPickerEntries,
resolveClineModelProviderId,
} from "./cline-model-picker-utils";
const data: ClineRecommendedModelsData = {
clinePass: [
{
id: "cline-pass/glm-5.1",
name: "GLM 5.1",
description: "Cline Pass model",
tags: ["PASS"],
},
],
recommended: [
{
id: "anthropic/claude-sonnet-4.6",
name: "Claude Sonnet 4.6",
description: "Recommended Claude model",
tags: ["BEST"],
},
{
id: "openai/gpt-5.3-codex",
name: "GPT-5.3 Codex",
description: "Recommended Codex model",
tags: [],
},
],
free: [
{
id: "deepseek/deepseek-chat",
name: "DeepSeek Chat",
description: "Free DeepSeek model",
tags: ["FREE"],
},
{
id: "qwen/qwen3-coder",
name: "Qwen3 Coder",
description: "Free Qwen model",
tags: ["FREE"],
},
],
};
describe("cline model picker helpers", () => {
it("builds tiered entries with browse action at the end while hiding Cline Pass by default", () => {
expect(buildClineModelEntries(data)).toMatchObject([
{
kind: "model",
tier: "recommended",
model: { id: "anthropic/claude-sonnet-4.6" },
},
{
kind: "model",
tier: "recommended",
model: { id: "openai/gpt-5.3-codex" },
},
{
kind: "model",
tier: "free",
model: { id: "deepseek/deepseek-chat" },
},
{
kind: "model",
tier: "free",
model: { id: "qwen/qwen3-coder" },
},
{ kind: "browse" },
]);
});
it("includes Cline Pass models when enabled", () => {
expect(
buildClineModelEntries(data, { includeClinePass: true }).slice(0, 2),
).toMatchObject([
{
kind: "model",
tier: "clinePass",
model: { id: "cline-pass/glm-5.1" },
},
{
kind: "model",
tier: "recommended",
model: { id: "anthropic/claude-sonnet-4.6" },
},
]);
});
it("maps Cline Pass models to the cline-pass provider", () => {
expect(resolveClineModelProviderId("clinePass")).toBe("cline-pass");
expect(resolveClineModelProviderId("recommended")).toBe("cline");
expect(resolveClineModelProviderId("free")).toBe("cline");
});
it("keeps section header focus indexes stable when sections expand or collapse", () => {
const entries = buildClineModelEntries(data, { includeClinePass: true });
const collapsedRows = buildClineModelPickerDisplayRows(
entries,
undefined,
undefined,
{
clinePass: false,
recommended: true,
free: true,
},
);
const expandedRows = buildClineModelPickerDisplayRows(
entries,
undefined,
undefined,
{
clinePass: true,
recommended: true,
free: true,
},
);
expect(
collapsedRows.find(
(row) => row.kind === "header" && row.tier === "clinePass",
)?.focusIndex,
).toBe(0);
expect(
expandedRows.find(
(row) => row.kind === "header" && row.tier === "clinePass",
)?.focusIndex,
).toBe(0);
});
it("filters collapsed tiers while keeping browse selectable", () => {
const visible = getVisibleClineModelPickerEntries(
buildClineModelEntries(data),
{
recommended: true,
free: false,
},
);
expect(
visible.map((entry) =>
entry.kind === "model" ? entry.model.id : "browse",
),
).toEqual([
"anthropic/claude-sonnet-4.6",
"openai/gpt-5.3-codex",
"browse",
]);
});
it("windows display rows around focus indexes, including expandable headers", () => {
const entries = buildClineModelEntries(data);
const rows = buildClineModelPickerDisplayRows(
entries,
undefined,
undefined,
{
recommended: false,
free: true,
},
);
const headers = rows.filter((row) => row.kind === "header");
expect(headers).toMatchObject([
{ tier: "recommended", focusIndex: 0, isExpanded: false },
{ tier: "free", focusIndex: 1, isExpanded: true },
]);
const freeRows = rows.filter((row) => row.kind === "model");
expect(freeRows).toMatchObject([
{ entryIndex: 2, selectableIndex: 0, focusIndex: 2 },
{ entryIndex: 3, selectableIndex: 1, focusIndex: 3 },
]);
const window = getClineModelPickerDisplayRowsWindow(rows, 3, 3);
expect(
window.visibleRows.some(
(row) => row.kind === "model" && row.entryIndex === 3,
),
).toBe(true);
});
});
@@ -1,38 +1,28 @@
// @jsxImportSource @opentui/react
import {
type ClineRecommendedModel,
type ClineRecommendedModelsData,
fetchClineRecommendedModels,
} from "@cline/core";
import type { ReactNode } from "react";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import "opentui-spinner/react";
import { palette } from "../../palette";
import {
buildClineModelPickerDisplayRows,
type ClineModelPickerDisplayRow,
type ClineModelPickerEntry,
type ClineModelPickerExpandedTiers,
type ClineModelTier,
getClineModelPickerDisplayRowsWindow,
} from "./cline-model-picker-utils";
export {
buildClineModelEntries,
buildClineModelPickerDisplayRows,
type ClineModelPickerDisplayRow,
type ClineModelPickerEntry,
type ClineModelPickerExpandedTiers,
type ClineModelProviderId,
type ClineModelTier,
getClineModelPickerDisplayRowsWindow,
getClineModelPickerRowByFocusIndex,
getVisibleClineModelPickerEntries,
resolveClineModelEntryProviderId,
resolveClineModelProviderId,
} from "./cline-model-picker-utils";
export interface ClineModelPickerItem {
kind: "model";
model: ClineRecommendedModel;
tier: "recommended" | "free";
}
const MAX_VISIBLE_ROWS = 10;
export interface ClineModelPickerBrowse {
kind: "browse";
}
export type ClineModelPickerEntry =
| ClineModelPickerItem
| ClineModelPickerBrowse;
function tagColor(tag: string): string {
if (tag === "FREE") return palette.success;
@@ -40,6 +30,23 @@ function tagColor(tag: string): string {
return "cyan";
}
function resolveDisplayName(
modelId: string,
knownModels?: Record<string, unknown>,
): string {
if (knownModels) {
const candidates = [modelId, modelId.split("/").pop()];
for (const key of candidates) {
if (!key) continue;
const hit = knownModels[key] as { name?: string } | undefined;
if (hit?.name) return hit.name;
}
}
return modelId.includes("/")
? (modelId.split("/").pop() ?? modelId)
: modelId;
}
export function useClineRecommendedModels() {
const [data, setData] = useState<ClineRecommendedModelsData | null>(null);
const [loading, setLoading] = useState(true);
@@ -61,56 +68,18 @@ export function useClineRecommendedModels() {
return { data, loading };
}
function ClineModelRow(props: {
row: Extract<ClineModelPickerDisplayRow, { kind: "model" | "browse" }>;
isSelected: boolean;
onSelect?: (selectableIndex: number, entry: ClineModelPickerEntry) => void;
entry: ClineModelPickerEntry;
}) {
const { row, isSelected, onSelect, entry } = props;
const isBrowse = row.kind === "browse";
return (
<box
key={row.key}
paddingX={1}
flexDirection="row"
gap={1}
backgroundColor={isSelected ? palette.selection : undefined}
marginTop={isBrowse ? 1 : 0}
onMouseDown={() => onSelect?.(row.selectableIndex, entry)}
overflow="hidden"
height={1}
>
<text fg={isSelected ? palette.textOnSelection : "gray"} flexShrink={0}>
{isSelected ? "\u276f" : " "}
</text>
<text
fg={
isSelected ? palette.textOnSelection : isBrowse ? "gray" : undefined
}
>
{row.label}
</text>
{row.kind === "model" &&
row.tags.map((t) => (
<text
key={t}
fg={isSelected ? palette.textOnSelection : tagColor(t)}
flexShrink={0}
>
{t}
</text>
))}
{row.kind === "model" && row.isCurrent && (
<text
fg={isSelected ? palette.textOnSelection : palette.success}
flexShrink={0}
>
(current)
</text>
)}
</box>
);
export function buildClineModelEntries(
data: ClineRecommendedModelsData,
): ClineModelPickerEntry[] {
const entries: ClineModelPickerEntry[] = [];
for (const m of data.recommended) {
entries.push({ kind: "model", model: m, tier: "recommended" });
}
for (const m of data.free) {
entries.push({ kind: "model", model: m, tier: "free" });
}
entries.push({ kind: "browse" });
return entries;
}
export function ClineModelPicker(props: {
@@ -119,35 +88,8 @@ export function ClineModelPicker(props: {
loading?: boolean;
knownModels?: Record<string, unknown>;
currentModelId?: string;
expandedTiers?: Partial<ClineModelPickerExpandedTiers>;
maxVisibleRows?: number;
onEntrySelect?: (
selectableIndex: number,
entry: ClineModelPickerEntry,
) => void;
onToggleTier?: (tier: ClineModelTier) => void;
}) {
const {
entries,
selected,
loading,
knownModels,
currentModelId,
expandedTiers,
maxVisibleRows = MAX_VISIBLE_ROWS,
onEntrySelect,
onToggleTier,
} = props;
const displayRows = useMemo(
() =>
buildClineModelPickerDisplayRows(
entries,
knownModels,
currentModelId,
expandedTiers,
),
[entries, knownModels, currentModelId, expandedTiers],
);
const { entries, selected, loading, knownModels, currentModelId } = props;
if (loading) {
return (
@@ -158,81 +100,82 @@ export function ClineModelPicker(props: {
);
}
const { visibleRows, aboveCount, belowCount, showAbove, showBelow } =
getClineModelPickerDisplayRowsWindow(displayRows, selected, maxVisibleRows);
let lastTier: string | null = null;
let isFirstHeader = true;
const rows: ReactNode[] = [];
if (showAbove) {
rows.push(
<box key="more-above" paddingX={1} justifyContent="center" height={1}>
<text fg="gray">
{"\u25b2"} {aboveCount} more
</text>
</box>,
);
}
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (!entry) continue;
const isSel = i === selected;
for (const row of visibleRows) {
if (row.kind === "header") {
const isSelected = row.focusIndex === selected;
if (entry.kind === "model") {
if (entry.tier !== lastTier) {
lastTier = entry.tier;
const label = entry.tier === "recommended" ? "Recommended" : "Free";
rows.push(
<box
key={`tier-${entry.tier}`}
paddingX={1}
marginTop={isFirstHeader ? 0 : 1}
>
<text fg="gray">{label}</text>
</box>,
);
isFirstHeader = false;
}
const tags = entry.model.tags;
const name = resolveDisplayName(entry.model.id, knownModels);
const isCurrent = currentModelId === entry.model.id;
rows.push(
<box
key={row.key}
key={entry.model.id}
paddingX={1}
height={1}
flexDirection="row"
gap={1}
backgroundColor={isSelected ? palette.selection : undefined}
onMouseDown={() => onToggleTier?.(row.tier)}
backgroundColor={isSel ? palette.selection : undefined}
>
<text
fg={isSelected ? palette.textOnSelection : "gray"}
flexShrink={0}
>
{isSelected ? "\u276f" : " "}
<text fg={isSel ? palette.textOnSelection : "gray"} flexShrink={0}>
{isSel ? "\u276f" : " "}
</text>
<text
fg={isSelected ? palette.textOnSelection : "gray"}
flexShrink={0}
>
{row.isExpanded ? "▾" : "▸"}
<text fg={isSel ? palette.textOnSelection : undefined}>{name}</text>
{tags.map((t) => (
<text
key={t}
fg={isSel ? palette.textOnSelection : tagColor(t)}
flexShrink={0}
>
{t}
</text>
))}
{isCurrent && (
<text fg={isSel ? palette.textOnSelection : "gray"} flexShrink={0}>
(current)
</text>
)}
</box>,
);
} else {
rows.push(
<box
key="browse-all"
paddingX={1}
flexDirection="row"
gap={1}
backgroundColor={isSel ? palette.selection : undefined}
marginTop={1}
>
<text fg={isSel ? palette.textOnSelection : "gray"} flexShrink={0}>
{isSel ? "\u276f" : " "}
</text>
<text fg={isSelected ? palette.textOnSelection : "gray"}>
{row.label}
<text fg={isSel ? palette.textOnSelection : "gray"}>
Browse all models...
</text>
</box>,
);
continue;
}
const entry = entries[row.entryIndex];
if (!entry) continue;
rows.push(
<box marginLeft={4}>
<ClineModelRow
key={row.key}
row={row}
entry={entry}
isSelected={row.focusIndex === selected}
onSelect={onEntrySelect}
/>
</box>,
);
}
if (showBelow) {
rows.push(
<box key="more-below" paddingX={1} justifyContent="center" height={1}>
<text fg="gray">
{"\u25bc"} {belowCount} more
</text>
</box>,
);
}
return (
<box flexDirection="column" paddingTop={1}>
{rows}
</box>
);
return <box flexDirection="column">{rows}</box>;
}
@@ -3,32 +3,11 @@ import type { ChoiceContext } from "@opentui-ui/dialog";
import { useDialogKeyboard } from "@opentui-ui/dialog/react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { palette } from "../../palette";
import {
buildClineModelPickerDisplayRows,
type ClineModelPickerEntry,
type ClineModelPickerExpandedTiers,
type ClineModelProviderId,
type ClineModelTier,
getClineModelPickerDisplayRowsWindow,
getClineModelPickerRowByFocusIndex,
getVisibleClineModelPickerEntries,
resolveClineModelEntryProviderId,
} from "./cline-model-picker";
import type { ClineModelPickerEntry } from "./cline-model-picker";
import { CHANGE_PROVIDER_ACTION } from "./model-selector";
import { ProviderRow } from "./provider-row";
export const BROWSE_ALL_ACTION = "__browse_all__";
const MAX_VISIBLE_ROWS = 10;
export interface ClineModelSelection {
modelId: string;
providerId: ClineModelProviderId;
}
export type ClineModelSelectorResult =
| ClineModelSelection
| typeof BROWSE_ALL_ACTION
| typeof CHANGE_PROVIDER_ACTION;
type ClineModelEntriesState =
| { status: "loading"; message: string }
@@ -41,8 +20,25 @@ function tagColor(tag: string): string {
return "cyan";
}
function resolveDisplayName(
modelId: string,
knownModels?: Record<string, unknown>,
): string {
if (knownModels) {
const candidates = [modelId, modelId.split("/").pop()];
for (const key of candidates) {
if (!key) continue;
const hit = knownModels[key] as { name?: string } | undefined;
if (hit?.name) return hit.name;
}
}
return modelId.includes("/")
? (modelId.split("/").pop() ?? modelId)
: modelId;
}
export function ClineModelSelectorContent(
props: ChoiceContext<ClineModelSelectorResult> & {
props: ChoiceContext<string> & {
currentModel: string;
currentProviderName: string;
knownModels?: Record<string, unknown>;
@@ -60,55 +56,53 @@ export function ClineModelSelectorContent(
} = props;
const [selected, setSelected] = useState(0);
const [onProvider, setOnProvider] = useState(false);
const [expandedTiers, setExpandedTiers] =
useState<ClineModelPickerExpandedTiers>({
clinePass: false,
recommended: true,
free: true,
});
const visibleEntries = useMemo(
() => getVisibleClineModelPickerEntries(entries, expandedTiers),
[entries, expandedTiers],
);
const displayRows = useMemo(() => {
return buildClineModelPickerDisplayRows(
entries,
knownModels,
currentModel,
expandedTiers,
);
}, [entries, knownModels, currentModel, expandedTiers]);
useEffect(() => {
setSelected((value) =>
Math.min(value, Math.max(0, displayRows.length - 1)),
);
}, [displayRows.length]);
const toggleTier = (tier: ClineModelTier) => {
setExpandedTiers((prev) => ({ ...prev, [tier]: !prev[tier] }));
};
const { visibleRows, aboveCount, belowCount, showAbove, showBelow } =
getClineModelPickerDisplayRowsWindow(
displayRows,
selected,
MAX_VISIBLE_ROWS,
);
const resolveEntry = (selectableIndex: number) => {
const entry = visibleEntries[selectableIndex];
if (!entry) return;
if (entry.kind === "model") {
resolve({
modelId: entry.model.id,
providerId: resolveClineModelEntryProviderId(entry) ?? "cline",
});
} else {
resolve(BROWSE_ALL_ACTION);
const rows: {
key: string;
kind: "header" | "model" | "browse";
label: string;
tags: string[];
isCurrent: boolean;
entryIndex: number;
}[] = [];
let lastTier: string | null = null;
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (!entry) continue;
if (entry.kind === "model") {
if (entry.tier !== lastTier) {
lastTier = entry.tier;
rows.push({
key: `tier-${entry.tier}`,
kind: "header",
label: entry.tier === "recommended" ? "Recommended" : "Free",
tags: [],
isCurrent: false,
entryIndex: -1,
});
}
rows.push({
key: entry.model.id,
kind: "model",
label: resolveDisplayName(entry.model.id, knownModels),
tags: entry.model.tags,
isCurrent: currentModel === entry.model.id,
entryIndex: i,
});
} else {
rows.push({
key: "browse-all",
kind: "browse",
label: "Browse all models...",
tags: [],
isCurrent: false,
entryIndex: i,
});
}
}
};
return rows;
}, [entries, knownModels, currentModel]);
useDialogKeyboard((key) => {
if (key.name === "escape") {
@@ -124,20 +118,20 @@ export function ClineModelSelectorContent(
resolve(CHANGE_PROVIDER_ACTION);
return;
}
const row = getClineModelPickerRowByFocusIndex(displayRows, selected);
if (!row) return;
if (row.kind === "header") {
toggleTier(row.tier);
return;
const entry = entries[selected];
if (!entry) return;
if (entry.kind === "model") {
resolve(entry.model.id);
} else {
resolve(BROWSE_ALL_ACTION);
}
resolveEntry(row.selectableIndex);
return;
}
const total = displayRows.length;
const total = entries.length;
if (total === 0) return;
if (key.name === "up" || (key.ctrl && key.name === "p")) {
if (!onProvider) {
setSelected((s) => (s <= 0 ? total - 1 : Math.min(s - 1, total - 1)));
setSelected((s) => (s <= 0 ? total - 1 : s - 1));
}
return;
}
@@ -158,45 +152,16 @@ export function ClineModelSelectorContent(
<ProviderRow providerName={currentProviderName} focused={onProvider} />
<box flexDirection="column">
{showAbove && (
<box paddingX={1} justifyContent="center" height={1}>
<text fg="gray">
{"\u25b2"} {aboveCount} more
</text>
</box>
)}
{visibleRows.map((row) => {
{displayRows.map((row, idx) => {
if (row.kind === "header") {
const isSel = row.focusIndex === selected && !onProvider;
const isFirst = idx === 0;
return (
<box
key={row.key}
paddingX={1}
height={1}
flexDirection="row"
gap={1}
backgroundColor={isSel ? palette.selection : undefined}
onMouseDown={() => toggleTier(row.tier)}
>
<text
fg={isSel ? palette.textOnSelection : "gray"}
flexShrink={0}
>
{isSel ? "\u276f" : " "}
</text>
<text
fg={isSel ? palette.textOnSelection : "gray"}
flexShrink={0}
>
{row.isExpanded ? "▾" : "▸"}
</text>
<text fg={isSel ? palette.textOnSelection : "gray"}>
{row.label}
</text>
<box key={row.key} paddingX={1} marginTop={isFirst ? 0 : 1}>
<text fg="gray">{row.label}</text>
</box>
);
}
const isSel = row.focusIndex === selected && !onProvider;
const isSel = row.entryIndex === selected && !onProvider;
const isGray = row.kind === "browse";
return (
<box
@@ -206,9 +171,6 @@ export function ClineModelSelectorContent(
gap={1}
backgroundColor={isSel ? palette.selection : undefined}
marginTop={row.kind === "browse" ? 1 : 0}
onMouseDown={() => resolveEntry(row.selectableIndex)}
overflow="hidden"
height={1}
>
<text
fg={isSel ? palette.textOnSelection : "gray"}
@@ -223,17 +185,16 @@ export function ClineModelSelectorContent(
>
{row.label}
</text>
{row.kind === "model" &&
row.tags.map((t) => (
<text
key={t}
fg={isSel ? palette.textOnSelection : tagColor(t)}
flexShrink={0}
>
{t}
</text>
))}
{row.kind === "model" && row.isCurrent && (
{row.tags.map((t) => (
<text
key={t}
fg={isSel ? palette.textOnSelection : tagColor(t)}
flexShrink={0}
>
{t}
</text>
))}
{row.isCurrent && (
<text
fg={isSel ? palette.textOnSelection : "gray"}
flexShrink={0}
@@ -244,13 +205,6 @@ export function ClineModelSelectorContent(
</box>
);
})}
{showBelow && (
<box paddingX={1} justifyContent="center" height={1}>
<text fg="gray">
{"\u25bc"} {belowCount} more
</text>
</box>
)}
</box>
<text fg="gray">
@@ -261,7 +215,7 @@ export function ClineModelSelectorContent(
}
export function ClineModelSelectorDialogContent(
props: ChoiceContext<ClineModelSelectorResult> & {
props: ChoiceContext<string> & {
currentModel: string;
currentProviderName: string;
knownModels?: Record<string, unknown>;
@@ -134,7 +134,6 @@ export function ModelSelectorContent(
currentModel: string;
currentProviderName: string;
models: ModelOption[];
allowCustomModel?: boolean;
},
) {
const {
@@ -144,7 +143,6 @@ export function ModelSelectorContent(
currentModel,
currentProviderName,
models,
allowCustomModel = true,
} = props;
const [search, setSearch] = useState("");
const [selected, setSelected] = useState(() => {
@@ -166,7 +164,7 @@ export function ModelSelectorContent(
return scored.map((r) => r.model);
}, [models, search]);
const optionCount = filtered.length + (allowCustomModel ? 1 : 0);
const optionCount = filtered.length + 1;
const safeSelected = Math.min(selected, Math.max(0, optionCount - 1));
useDialogKeyboard((key) => {
@@ -190,7 +188,7 @@ export function ModelSelectorContent(
resolve(model.key);
return;
}
if (allowCustomModel && safeSelected === filtered.length) {
if (safeSelected === filtered.length) {
setIsCreatingCustomModel(true);
setCustomModelId("");
setCustomModelError("");
@@ -292,7 +290,6 @@ export function ModelSelectorContent(
dimmed={onProvider}
currentModel={currentModel}
onSelect={resolve}
allowCustomModel={allowCustomModel}
onCreateCustomModel={() => {
setIsCreatingCustomModel(true);
setCustomModelId("");
@@ -411,7 +408,6 @@ function ModelList(props: {
dimmed?: boolean;
currentModel: string;
onSelect: (key: string) => void;
allowCustomModel: boolean;
onCreateCustomModel: () => void;
}) {
const {
@@ -420,12 +416,11 @@ function ModelList(props: {
dimmed,
currentModel,
onSelect,
allowCustomModel,
onCreateCustomModel,
} = props;
const rows: ({ type: "model"; model: ModelOption } | { type: "custom" })[] = [
...items.map((model) => ({ type: "model" as const, model })),
...(allowCustomModel ? [{ type: "custom" as const }] : []),
{ type: "custom" as const },
];
if (rows.length <= MAX_VISIBLE) {
+6 -38
View File
@@ -10,7 +10,6 @@ import type { ChoiceContext } from "@opentui-ui/dialog";
import type { DialogActions } from "@opentui-ui/dialog/react";
import { useCallback } from "react";
import { isOpenAICodexCliProvider } from "../../utils/codex-cli";
import { getCliFeatureFlagsService } from "../../utils/feature-flags";
import {
getPersistedProviderApiKey,
isOAuthProvider,
@@ -30,7 +29,6 @@ import { buildClineModelEntries } from "../components/model-selector/cline-model
import {
BROWSE_ALL_ACTION,
ClineModelSelectorDialogContent,
type ClineModelSelectorResult,
} from "../components/model-selector/cline-model-selector";
import {
buildModelOptions,
@@ -80,10 +78,6 @@ function usesModelIdInput(providerId: string): boolean {
return providerId === "openai-compatible";
}
function usesClineModelSelector(providerId: string): boolean {
return providerId === "cline";
}
async function runProviderChange(
dialog: DialogActions,
config: Config,
@@ -270,8 +264,6 @@ export function useModelSelector(opts: {
}
let pickingModel = true;
const isClinePassEnabled =
getCliFeatureFlagsService().getBooleanFlagEnabled("ext-cline-pass");
while (pickingModel) {
if (usesModelIdInput(config.providerId)) {
@@ -299,19 +291,17 @@ export function useModelSelector(opts: {
continue;
}
if (usesClineModelSelector(config.providerId)) {
const clineResult = await dialog.choice<ClineModelSelectorResult>({
if (config.providerId === "cline") {
const clineResult = await dialog.choice<string>({
style: { maxHeight: termHeight - 2 },
content: (ctx: ChoiceContext<ClineModelSelectorResult>) => (
content: (ctx: ChoiceContext<string>) => (
<ClineModelSelectorDialogContent
{...ctx}
currentModel={config.modelId}
currentProviderName={providerDisplayName}
knownModels={config.knownModels as Record<string, unknown>}
loadEntries={async () =>
buildClineModelEntries(await fetchClineRecommendedModels(), {
includeClinePass: isClinePassEnabled,
})
buildClineModelEntries(await fetchClineRecommendedModels())
}
/>
),
@@ -333,7 +323,6 @@ export function useModelSelector(opts: {
currentModel={config.modelId}
currentProviderName={providerDisplayName}
models={modelOptions}
allowCustomModel={false}
/>
),
});
@@ -343,7 +332,6 @@ export function useModelSelector(opts: {
continue;
}
config.modelId = browseResult;
config.providerId = "cline";
const browseModel = modelOptions.find(
(m: ModelOption) => m.key === browseResult,
);
@@ -380,28 +368,9 @@ export function useModelSelector(opts: {
continue;
}
if (config.providerId !== clineResult.providerId) {
const manager = new ProviderSettingsManager();
const resolved = await resolveProviderConfig(
clineResult.providerId,
{
loadLatestOnInit: true,
loadPrivateOnAuth: true,
failOnError: false,
},
manager.getProviderConfig(clineResult.providerId, {
includeKnownModels: false,
}),
);
modelOptions = buildModelOptions(
resolved?.knownModels as Record<string, Llms.ModelInfo>,
);
config.knownModels = resolved?.knownModels;
}
config.modelId = clineResult.modelId;
config.providerId = clineResult.providerId;
config.modelId = clineResult;
const selectedModel = modelOptions.find(
(m: ModelOption) => m.key === clineResult.modelId,
(m: ModelOption) => m.key === clineResult,
);
if (selectedModel?.supportsReasoning) {
const currentLevel: ThinkingLevel = config.reasoningEffort
@@ -444,7 +413,6 @@ export function useModelSelector(opts: {
currentModel={config.modelId}
currentProviderName={providerDisplayName}
models={modelOptions}
allowCustomModel={config.providerId !== "cline-pass"}
/>
),
});
+13 -88
View File
@@ -15,19 +15,12 @@ import {
checkCodexCliInstalled,
isOpenAICodexCliProvider,
} from "../../../utils/codex-cli";
import { getCliFeatureFlagsService } from "../../../utils/feature-flags";
import { getPersistedProviderApiKey } from "../../../utils/provider-auth";
import { listLocalProviders } from "../../../utils/provider-catalog";
import { getCliTelemetryService } from "../../../utils/telemetry";
import {
buildClineModelEntries,
buildClineModelPickerDisplayRows,
type ClineModelPickerEntry,
type ClineModelPickerExpandedTiers,
type ClineModelProviderId,
type ClineModelTier,
getVisibleClineModelPickerEntries,
resolveClineModelEntryProviderId,
useClineRecommendedModels,
} from "../../components/model-selector/cline-model-picker";
import {
@@ -160,7 +153,6 @@ export function useOnboardingController(props: OnboardingControllerProps) {
const createCustomModelItem = useCallback(
(_search: string, filteredItems: SearchableItem[]) => {
if (activeProviderId === "cline-pass") return undefined;
if (filteredItems.some((item) => item.key === CUSTOM_MODEL_ID_ACTION)) {
return undefined;
}
@@ -171,56 +163,24 @@ export function useOnboardingController(props: OnboardingControllerProps) {
searchText: "create custom model id manual entry",
} satisfies SearchableItem;
},
[activeProviderId],
[],
);
const modelList = useSearchableList(modelItems, createCustomModelItem);
// Cline featured model picker
const recommended = useClineRecommendedModels();
const isClinePassEnabled =
getCliFeatureFlagsService().getBooleanFlagEnabled("ext-cline-pass");
const clineEntries: ClineModelPickerEntry[] = useMemo(
() =>
recommended.data
? buildClineModelEntries(recommended.data, {
includeClinePass: isClinePassEnabled,
})
: [],
[recommended.data, isClinePassEnabled],
);
const [clineExpandedTiers, setClineExpandedTiers] =
useState<ClineModelPickerExpandedTiers>({
clinePass: false,
recommended: true,
free: true,
});
const visibleClineEntries = useMemo(
() => getVisibleClineModelPickerEntries(clineEntries, clineExpandedTiers),
[clineEntries, clineExpandedTiers],
);
const [clineKnownModels, setClineKnownModels] = useState<
Record<string, unknown> | undefined
>(undefined);
const clineDisplayRows = useMemo(
() =>
buildClineModelPickerDisplayRows(
clineEntries,
clineKnownModels,
undefined,
clineExpandedTiers,
),
[clineEntries, clineKnownModels, clineExpandedTiers],
() => (recommended.data ? buildClineModelEntries(recommended.data) : []),
[recommended.data],
);
const [clineModelSelected, setClineModelSelected] = useState(0);
useEffect(() => {
setClineModelSelected((selected) =>
Math.min(selected, Math.max(0, clineDisplayRows.length - 1)),
);
}, [clineDisplayRows.length]);
const [clineModelReasoningIds, setClineModelReasoningIds] = useState<
Set<string>
>(new Set());
const [clineKnownModels, setClineKnownModels] = useState<
Record<string, unknown> | undefined
>(undefined);
useEffect(() => {
getLocalProviderModels("cline")
@@ -576,16 +536,16 @@ export function useOnboardingController(props: OnboardingControllerProps) {
}, [customModelId, completeModelSelection]);
const saveClineModelSelection = useCallback(
(modelId: string, modelName: string, providerId: ClineModelProviderId) => {
const existing = providerSettingsManager.getProviderSettings(providerId);
(modelId: string, modelName: string) => {
const existing =
providerSettingsManager.getProviderSettings(activeProviderId);
providerSettingsManager.saveProviderSettings(
{
...(existing ?? { provider: providerId }),
...(existing ?? { provider: activeProviderId }),
model: modelId,
},
{ setLastUsed: true },
);
setActiveProviderId(providerId);
setSelectedModelId(modelId);
if (clineModelReasoningIds.has(modelId)) {
setSelectedModelName(modelName);
@@ -595,34 +555,9 @@ export function useOnboardingController(props: OnboardingControllerProps) {
setStep("done");
}
},
[clineModelReasoningIds, providerSettingsManager],
[activeProviderId, clineModelReasoningIds, providerSettingsManager],
);
const selectClineModelEntry = useCallback(
(entry: ClineModelPickerEntry | undefined) => {
if (!entry) return;
if (entry.kind === "model") {
const providerId = resolveClineModelEntryProviderId(entry) ?? "cline";
if (providerId === "cline-pass") {
setActiveProviderId(providerId);
setActiveProviderName("Cline Pass");
setStep("model_picker");
loadModelsForProvider(providerId);
return;
}
saveClineModelSelection(entry.model.id, entry.model.name, providerId);
return;
}
setStep("model_picker");
loadModelsForProvider(activeProviderId);
},
[activeProviderId, loadModelsForProvider, saveClineModelSelection],
);
const toggleClineModelTier = useCallback((tier: ClineModelTier) => {
setClineExpandedTiers((prev) => ({ ...prev, [tier]: !prev[tier] }));
}, []);
const saveThinkingLevel = useCallback(
(level: ThinkingLevel) => {
const existing =
@@ -679,8 +614,7 @@ export function useOnboardingController(props: OnboardingControllerProps) {
menuSelected,
providerList,
modelList,
clineEntries: visibleClineEntries,
clineDisplayRows,
clineEntries,
clineModelSelected,
thinkingSelected,
setStep,
@@ -711,8 +645,7 @@ export function useOnboardingController(props: OnboardingControllerProps) {
startDeviceCodeFlow,
selectProvider,
loadModelsForProvider,
selectClineModelEntry,
toggleClineModelTier,
saveClineModelSelection,
saveCodexCliConfig,
saveByoConfig,
saveModelSelection,
@@ -731,7 +664,6 @@ export function useOnboardingController(props: OnboardingControllerProps) {
codexCliChecking,
codexCliStatus,
clineEntries,
clineExpandedTiers,
clineKnownModels,
clineModelSelected,
deviceError,
@@ -751,13 +683,6 @@ export function useOnboardingController(props: OnboardingControllerProps) {
setCustomModelId(value);
setCustomModelError("");
},
handleClineModelEntrySelect: (
_selectableIndex: number,
entry: ClineModelPickerEntry,
) => {
selectClineModelEntry(entry);
},
handleClineModelTierToggle: toggleClineModelTier,
handleModelItemSelect: selectModelItem,
menuSelected,
modelItems,
+10 -15
View File
@@ -1,11 +1,7 @@
import type { ProviderConfigFieldKey } from "@cline/core";
import { useKeyboard } from "@opentui/react";
import type { Dispatch, SetStateAction } from "react";
import type {
ClineModelPickerDisplayRow,
ClineModelPickerEntry,
ClineModelTier,
} from "../../components/model-selector/cline-model-picker";
import type { ClineModelPickerEntry } from "../../components/model-selector/cline-model-picker";
import type { SearchableListState } from "../../components/searchable-list";
import type { OnboardingOAuthProviderId } from "./auth";
import { FIELD_ORDER } from "./fields";
@@ -25,7 +21,6 @@ export function useOnboardingKeyboard(input: {
providerList: SearchableListState;
modelList: SearchableListState;
clineEntries: ClineModelPickerEntry[];
clineDisplayRows: ClineModelPickerDisplayRow[];
clineModelSelected: number;
thinkingSelected: number;
setStep: (step: OnboardingStep) => void;
@@ -48,8 +43,7 @@ export function useOnboardingKeyboard(input: {
startDeviceCodeFlow: (providerId: OnboardingOAuthProviderId) => void;
selectProvider: (providerId: string) => void;
loadModelsForProvider: (providerId: string) => void;
selectClineModelEntry: (entry: ClineModelPickerEntry | undefined) => void;
toggleClineModelTier: (tier: ClineModelTier) => void;
saveClineModelSelection: (modelId: string, modelName: string) => void;
saveCodexCliConfig: () => void;
saveByoConfig: () => void;
saveModelSelection: () => void;
@@ -203,7 +197,7 @@ export function useOnboardingKeyboard(input: {
}
if (input.step === "cline_model") {
const total = input.clineDisplayRows.length;
const total = input.clineEntries.length;
if (total === 0) return;
if (key.name === "up" || (key.ctrl && key.name === "p")) {
input.setClineModelSelected((s) => (s <= 0 ? total - 1 : s - 1));
@@ -214,13 +208,14 @@ export function useOnboardingKeyboard(input: {
return;
}
if (key.name === "return") {
const row = input.clineDisplayRows[input.clineModelSelected];
if (!row) return;
if (row.kind === "header") {
input.toggleClineModelTier(row.tier);
return;
const entry = input.clineEntries[input.clineModelSelected];
if (!entry) return;
if (entry.kind === "model") {
input.saveClineModelSelection(entry.model.id, entry.model.name);
} else {
input.setStep("model_picker");
input.loadModelsForProvider(input.activeProviderId);
}
input.selectClineModelEntry(input.clineEntries[row.selectableIndex]);
}
return;
}
@@ -7,8 +7,6 @@ import {
import {
ClineModelPicker,
type ClineModelPickerEntry,
type ClineModelPickerExpandedTiers,
type ClineModelTier,
} from "../../components/model-selector/cline-model-picker";
import {
type SearchableItem,
@@ -435,17 +433,11 @@ export function OnboardingProviderPickerScreen(props: {
export function OnboardingClineModelScreen(props: {
clineEntries: ClineModelPickerEntry[];
clineExpandedTiers: ClineModelPickerExpandedTiers;
clineKnownModels: Record<string, unknown> | undefined;
clineModelSelected: number;
compact: boolean;
contentWidth: number;
mouse: MouseTrackerState;
onEntrySelect: (
selectableIndex: number,
entry: ClineModelPickerEntry,
) => void;
onToggleTier: (tier: ClineModelTier) => void;
recommendedLoading: boolean;
}) {
const defaultFg = useDefaultFg();
@@ -467,9 +459,6 @@ export function OnboardingClineModelScreen(props: {
selected={props.clineModelSelected}
loading={props.recommendedLoading}
knownModels={props.clineKnownModels}
expandedTiers={props.clineExpandedTiers}
onEntrySelect={props.onEntrySelect}
onToggleTier={props.onToggleTier}
/>
<text fg="gray" paddingX={1}>
@@ -111,13 +111,10 @@ export function OnboardingView(props: OnboardingViewProps) {
return (
<OnboardingClineModelScreen
clineEntries={state.clineEntries}
clineExpandedTiers={state.clineExpandedTiers}
clineKnownModels={state.clineKnownModels}
clineModelSelected={state.clineModelSelected}
compact={compact}
contentWidth={contentWidth}
onEntrySelect={state.handleClineModelEntrySelect}
onToggleTier={state.handleClineModelTierToggle}
mouse={mouse}
recommendedLoading={state.recommendedLoading}
/>
+38 -12
View File
@@ -19,7 +19,7 @@
},
"apps/cli": {
"name": "@cline/cli",
"version": "3.0.24",
"version": "3.0.25",
"bin": {
"cline": "src/index.ts",
},
@@ -372,7 +372,7 @@
},
"sdk/packages/agents": {
"name": "@cline/agents",
"version": "0.0.48",
"version": "0.0.49",
"dependencies": {
"@cline/llms": "workspace:*",
"@cline/shared": "workspace:*",
@@ -381,7 +381,7 @@
},
"sdk/packages/core": {
"name": "@cline/core",
"version": "0.0.48",
"version": "0.0.49",
"dependencies": {
"@cline/agents": "workspace:*",
"@cline/llms": "workspace:*",
@@ -419,7 +419,7 @@
},
"sdk/packages/llms": {
"name": "@cline/llms",
"version": "0.0.48",
"version": "0.0.49",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^4.0.89",
"@ai-sdk/anthropic": "^3.0.68",
@@ -453,14 +453,14 @@
},
"sdk/packages/sdk": {
"name": "@cline/sdk",
"version": "0.0.48",
"version": "0.0.49",
"dependencies": {
"@cline/core": "workspace:*",
},
},
"sdk/packages/shared": {
"name": "@cline/shared",
"version": "0.0.48",
"version": "0.0.49",
"dependencies": {
"aws4fetch": "^1.0.20",
"jsonrepair": "^3.13.2",
@@ -1028,7 +1028,7 @@
"@openai/codex-win32-x64": ["@openai/codex@0.130.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-FzMznm7fr5/nbjZgOujZ9Y9AbdGm7ji1FOoWiY3U+srqauvZaTgn6o6aCheSL7kuymu7nTLOO/cAyWV6NuesqQ=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.16.2", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-Z/xZ7q79dYeE0afqIk/yFEcRNGEQFcE+H8ssYivUiy+xGZ1mGwT72jpaQZKBwPn3JH4sRCu4KA2lcktBQfcOjg=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.17.0", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-a4Fw6lEZqXhjkyTH0LNInntrdLaWcz9M1+eqQgSVqI6PAlcqC/Jo069k8VdGcU3u4qbETqmURec8+VfhS06Sqg=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
@@ -3268,8 +3268,6 @@
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
@@ -3552,11 +3550,11 @@
"@streamdown/code/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
@@ -3806,6 +3804,8 @@
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"vaul/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.9", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-slot": "1.2.5", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw=="],
"webview/@types/node": ["@types/node@24.13.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -4066,6 +4066,28 @@
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"vaul/@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.4", "", {}, "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-escape-keydown": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.9", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-id": ["@radix-ui/react-id@1.1.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.11", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UEytdjgEh2tJGgD/gZK4FUx6t1rNIlM3U0DENhSrG7I75FGm1DnaDuVUWF1pWAWUwGmn1sCJ1VGHn8LhN1aTOw=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.6", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.5", "", { "dependencies": { "@radix-ui/react-slot": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg=="],
"webview/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -4086,6 +4108,10 @@
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="],
"vaul/@radix-ui/react-dialog/@radix-ui/react-focus-scope/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="],
"@radix-ui/react-accessible-icon/@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
}
}
+4
View File
@@ -1,5 +1,9 @@
# Cline SDK Changelog
## 0.0.49
- Reverted ClinePass recommended-models support, removing the `clinePass` field from the recommended models data
## 0.0.48
- Added ClinePass support and ClinePass models
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@cline/agents",
"version": "0.0.48",
"version": "0.0.49",
"repository": {
"type": "git",
"url": "https://github.com/cline/cline",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@cline/core",
"description": "Cline Core SDK for Node Runtime",
"version": "0.0.48",
"version": "0.0.49",
"repository": {
"type": "git",
"url": "https://github.com/cline/cline",
@@ -11,7 +11,6 @@ export interface ClineRecommendedModel {
export interface ClineRecommendedModelsData {
recommended: ClineRecommendedModel[];
free: ClineRecommendedModel[];
clinePass: ClineRecommendedModel[];
}
export interface FetchClineRecommendedModelsOptions {
@@ -27,7 +26,6 @@ export interface FetchClineRecommendedModelsOptions {
const DEFAULT_REQUEST_TIMEOUT_MS = 5_000;
export const FALLBACK_CLINE_RECOMMENDED_MODELS: ClineRecommendedModelsData = {
clinePass: [],
recommended: [
{
id: "anthropic/claude-opus-4.6",
@@ -74,10 +72,6 @@ function cloneRecommendedModels(
data: ClineRecommendedModelsData,
): ClineRecommendedModelsData {
return {
clinePass: data.clinePass.map((m) => ({
...m,
tags: [...m.tags],
})),
recommended: data.recommended.map((model) => ({
...model,
tags: [...model.tags],
@@ -110,21 +104,14 @@ function normalizeResponse(raw: unknown): ClineRecommendedModelsData | null {
? data.recommended
: [];
const freeRaw = Array.isArray(data.free) ? data.free : [];
const clinePassRaw = Array.isArray(data.clinePass) ? data.clinePass : [];
const recommended = recommendedRaw
.map(normalizeModel)
.filter((model): model is ClineRecommendedModel => model !== null);
const free = freeRaw
.map(normalizeModel)
.filter((model): model is ClineRecommendedModel => model !== null);
const clinePass = clinePassRaw
.map(normalizeModel)
.filter((model): model is ClineRecommendedModel => model !== null);
if (recommended.length === 0 && free.length === 0 && clinePass.length === 0)
return null;
return { recommended, free, clinePass };
if (recommended.length === 0 && free.length === 0) return null;
return { recommended, free };
}
function getConfiguredApiBaseUrl(
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@cline/llms",
"version": "0.0.48",
"version": "0.0.49",
"description": "Config-driven SDK for selecting, extending, and instantiating LLM providers and models",
"repository": {
"type": "git",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@cline/sdk",
"description": "Cline SDK - user-facing alias for @cline/core",
"version": "0.0.48",
"version": "0.0.49",
"repository": {
"type": "git",
"url": "https://github.com/cline/cline",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@cline/shared",
"version": "0.0.48",
"version": "0.0.49",
"description": "Shared utilities, types, and schemas for Cline packages",
"repository": {
"type": "git",