Commit 1d630e8
Changed files (1)
modules
dev
agents
crush
modules/dev/agents/crush/provider-cline.sh
@@ -3,36 +3,23 @@ provider add cline \
--base-url "https://api.cline.bot/api/v1/" \
--discover-models false
+_cline_pass_ttl=86400
+_cline_pass_file="${XDG_DATA_HOME:-$HOME/.local/share}/crush/cline.json"
+
# The feed lists ids without capabilities, so join it with OpenRouter for
# capabilities and reasoning levels.
-_cline_pass_models="$(curl -fsSL --max-time 20 \
- "https://api.cline.bot/api/v1/ai/cline/recommended-models" || true)"
-if [ -n "$_cline_pass_models" ]; then
- curl -fsSL --max-time 30 \
- "https://openrouter.ai/api/v1/models" |
- jq -r --argjson cp "$_cline_pass_models" '
- (.data // []
- | map({key: (.id | split("/") | last), value: .})
- | from_entries) as $or
- | $cp.clinePass[]?
- | (.id | sub("^cline-pass/"; "")) as $slug
- | ($or[$slug] // {}) as $m
- | [
- $slug,
- (($m.name // $slug) | sub("^[^:]+: "; "")),
- ($m.context_length // 128000),
- ($m.top_provider.max_completion_tokens // 8192),
- (($m | length) == 0
- or (($m.supported_parameters // []) | index("reasoning")) != null
- or $m.reasoning != null),
- ((($m.architecture.input_modalities // []) | index("image")) != null),
- (($m.reasoning.supported_efforts // []) | join(",")),
- ($m.reasoning.default_effort // "")
- ]
- | @tsv
- ' |
- while IFS=$'\t' read -r slug name ctx maxtokens canreason images levels effort; do
- args=("cline/cline-pass/$slug" --name "$name" \
+_emit_cline_pass_models() {
+ jq -r '
+ .models[]
+ | [ .id, .name, .context_window, .default_max_tokens, .can_reason,
+ .supports_attachments,
+ ((.reasoning_levels // []) | join(",")),
+ (.default_reasoning_effort // "")
+ ]
+ | @tsv
+ ' "$_cline_pass_file" |
+ while IFS=$'\t' read -r id name ctx maxtokens canreason images levels effort; do
+ args=("cline/$id" --name "$name" \
--context-window "$ctx" \
--default-max-tokens "$maxtokens" \
--can-reason "$canreason" \
@@ -41,4 +28,60 @@ if [ -n "$_cline_pass_models" ]; then
if [ -n "$effort" ]; then args+=(--reasoning-effort "$effort"); fi
model add "${args[@]}" </dev/null
done
+}
+
+_cline_pass_usable() {
+ [ -f "$_cline_pass_file" ] &&
+ jq -e '.models | length > 0' "$_cline_pass_file" >/dev/null 2>&1
+}
+
+_cline_pass_fresh=false
+if _cline_pass_usable; then
+ if [ $(( $(date +%s) - $(stat -c %Y "$_cline_pass_file") )) -lt "$_cline_pass_ttl" ]; then
+ _cline_pass_fresh=true
+ fi
+fi
+
+if [ "$_cline_pass_fresh" = false ]; then
+ _cline_pass_models="$(curl -fsSL --max-time 8 \
+ "https://api.cline.bot/api/v1/ai/cline/recommended-models" || true)"
+ if [ -n "$_cline_pass_models" ]; then
+ _cline_pass_json="$(curl -fsSL --max-time 12 \
+ "https://openrouter.ai/api/v1/models" |
+ jq -c --argjson cp "$_cline_pass_models" '
+ (.data // [] | map({key: (.id | split("/") | last), value: .}) | from_entries) as $or
+ | {
+ name: "Cline", id: "cline", type: "openai-compat",
+ api_endpoint: "https://api.cline.bot/api/v1/",
+ models: [
+ $cp.clinePass[]?
+ | (.id | sub("^cline-pass/"; "")) as $slug
+ | ($or[$slug] // {}) as $m
+ | {
+ id: ("cline-pass/" + $slug),
+ name: (($m.name // $slug) | sub("^[^:]+: "; "")),
+ context_window: ($m.context_length // 128000),
+ default_max_tokens: ($m.top_provider.max_completion_tokens // 8192),
+ can_reason: (($m | length) == 0
+ or (($m.supported_parameters // []) | index("reasoning")) != null
+ or $m.reasoning != null),
+ reasoning_levels: ($m.reasoning.supported_efforts // []),
+ default_reasoning_effort: ($m.reasoning.default_effort // ""),
+ supports_attachments:
+ ((($m.architecture.input_modalities // []) | index("image")) != null)
+ }
+ ]
+ }')"
+ if [ -n "$_cline_pass_json" ] &&
+ [ "$(printf '%s' "$_cline_pass_json" | jq '.models | length')" -gt 0 ]; then
+ mkdir -p "$(dirname "$_cline_pass_file")"
+ printf '%s\n' "$_cline_pass_json" > "$_cline_pass_file.tmp" &&
+ mv "$_cline_pass_file.tmp" "$_cline_pass_file"
+ fi
+ fi
+fi
+
+# Falls back to a stale cache when the fetch above did not refresh it.
+if _cline_pass_usable; then
+ _emit_cline_pass_models
fi