Add AI profiles and harness adapters (claude-code, codex, opencode, native)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
This commit is contained in:
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# omarchy:summary=Launch the AI harness configured by a profile
|
||||||
|
# omarchy:args=[profile]
|
||||||
|
# omarchy:examples=omarchycn ai launch | omarchycn ai launch work
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||||
|
|
||||||
|
profile="${1:-$(cn_ai_current_profile)}"
|
||||||
|
|
||||||
|
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
|
||||||
|
echo "No such profile: $profile" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
harness=$(cn_ai_profile_field "$profile" harness)
|
||||||
|
provider=$(cn_ai_profile_field "$profile" provider)
|
||||||
|
model=$(cn_ai_profile_field "$profile" model)
|
||||||
|
command=$(cn_ai_harness_field "$harness" command)
|
||||||
|
config_method=$(cn_ai_harness_field "$harness" config_method)
|
||||||
|
|
||||||
|
if ! command -v "$command" > /dev/null 2>&1; then
|
||||||
|
install=$(cn_ai_harness_field "$harness" install)
|
||||||
|
if [[ $install == doc:* ]]; then
|
||||||
|
echo "$harness 未安装,安装方法见官方文档: ${install#doc:}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Installing $harness: $install"
|
||||||
|
$install
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$config_method" in
|
||||||
|
env)
|
||||||
|
key=$(omarchy-cn-ai-secret get "$provider")
|
||||||
|
if [[ $harness == "codex" ]]; then
|
||||||
|
cn_ai_render_codex_config "$provider" "$model" "$key"
|
||||||
|
exec "$command"
|
||||||
|
fi
|
||||||
|
eval "$(cn_ai_render_env "$harness" "$provider" "$model" "$key")"
|
||||||
|
exec "$command"
|
||||||
|
;;
|
||||||
|
native)
|
||||||
|
# Native harnesses (kimi-code, deep-code) manage their own auth
|
||||||
|
exec "$command"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown config method: $config_method" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+57
@@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# omarchy:summary=Create an AI profile binding a harness, provider, and model
|
||||||
|
# omarchy:args=<name> <harness> <provider> <model|default-coding|fast>
|
||||||
|
# omarchy:examples=omarchycn ai profile create work claude-code deepseek default-coding
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||||
|
|
||||||
|
name="${1:?usage: omarchycn ai profile create <name> <harness> <provider> <model>}"
|
||||||
|
harness="${2:?missing harness}"
|
||||||
|
provider="${3:?missing provider}"
|
||||||
|
model="${4:?missing model}"
|
||||||
|
|
||||||
|
if [[ ! $name =~ ^[a-z0-9-]+$ ]]; then
|
||||||
|
echo "Profile name must be lowercase alphanumeric/dashes: $name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cn_ai_harness_field "$harness" command > /dev/null || {
|
||||||
|
echo "Unknown harness: $harness (known: $(cn_ai_harness_ids | tr '\n' ' '))" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
jq -e --arg p "$provider" '.providers[$p]' "$CN_AI_PROVIDERS" > /dev/null || {
|
||||||
|
echo "Unknown provider: $provider (known: $(cn_ai_provider_ids | tr '\n' ' '))" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
level=$(cn_ai_combo_level "$harness" "$provider")
|
||||||
|
if [[ $level == "unsupported" ]]; then
|
||||||
|
echo "Combo $harness x $provider is not in the compatibility matrix" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolved=$(cn_ai_model_by_alias "$provider" "$model" 2>/dev/null || true)
|
||||||
|
if [[ -n $resolved ]]; then
|
||||||
|
model="$resolved"
|
||||||
|
elif ! cn_ai_models "$provider" | grep -qxF "$model"; then
|
||||||
|
echo "Unknown model for $provider: $model" >&2
|
||||||
|
echo "Available: $(cn_ai_models "$provider" | tr '\n' ' ')" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$CN_AI_PROFILE_DIR"
|
||||||
|
cat > "$CN_AI_PROFILE_DIR/$name.toml" <<EOF
|
||||||
|
schema_version = 1
|
||||||
|
name = "$name"
|
||||||
|
harness = "$harness"
|
||||||
|
provider = "$provider"
|
||||||
|
model = "$model"
|
||||||
|
secret_ref = "omarchycn://ai/$provider"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Profile $name: $harness x $provider x $model (compat: $level)"
|
||||||
|
if ! omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
|
||||||
|
echo "提示: 尚未存储 $provider 的 API Key,运行: omarchycn ai secret set $provider"
|
||||||
|
fi
|
||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# omarchy:summary=List AI profiles and the current default
|
||||||
|
# omarchy:examples=omarchycn ai profile list
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||||
|
|
||||||
|
current=""
|
||||||
|
if [[ -f $CN_AI_CURRENT ]]; then
|
||||||
|
current=$(<"$CN_AI_CURRENT")
|
||||||
|
fi
|
||||||
|
|
||||||
|
found="false"
|
||||||
|
for f in "$CN_AI_PROFILE_DIR"/*.toml; do
|
||||||
|
[[ -f $f ]] || continue
|
||||||
|
found="true"
|
||||||
|
name="${f##*/}"
|
||||||
|
name="${name%.toml}"
|
||||||
|
marker=" "
|
||||||
|
[[ $name == "$current" ]] && marker="*"
|
||||||
|
printf '%s %-14s %s x %s x %s\n' "$marker" "$name" \
|
||||||
|
"$(cn_ai_profile_field "$name" harness)" \
|
||||||
|
"$(cn_ai_profile_field "$name" provider)" \
|
||||||
|
"$(cn_ai_profile_field "$name" model)"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $found == "false" ]]; then
|
||||||
|
echo "No profiles (create: omarchycn ai profile create <name> <harness> <provider> <model>)"
|
||||||
|
fi
|
||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# omarchy:summary=Set the default AI profile
|
||||||
|
# omarchy:args=<name>
|
||||||
|
# omarchy:examples=omarchycn ai profile use work
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$OMARCHY_PATH/cn/lib/ai.sh"
|
||||||
|
|
||||||
|
name="${1:?usage: omarchycn ai profile use <name>}"
|
||||||
|
|
||||||
|
if [[ ! -f $CN_AI_PROFILE_DIR/$name.toml ]]; then
|
||||||
|
echo "No such profile: $name (see: omarchycn ai profile list)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${CN_AI_CURRENT%/*}"
|
||||||
|
echo "$name" > "$CN_AI_CURRENT"
|
||||||
|
echo "Default AI profile: $name"
|
||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
|
# Sourced helpers for omarchy-cn-ai-* commands
|
||||||
|
|
||||||
|
CN_AI_PROVIDERS="$OMARCHY_PATH/cn/registry/ai-providers.json"
|
||||||
|
CN_AI_HARNESSES="$OMARCHY_PATH/cn/registry/ai-harnesses.json"
|
||||||
|
CN_AI_COMPAT="$OMARCHY_PATH/cn/registry/ai-compatibility.json"
|
||||||
|
CN_AI_PROFILE_DIR="$HOME/.config/omarchycn/ai/profiles"
|
||||||
|
CN_AI_CURRENT="$HOME/.config/omarchycn/ai/current"
|
||||||
|
|
||||||
|
cn_ai_provider_ids() { jq -r '.providers | keys[]' "$CN_AI_PROVIDERS"; }
|
||||||
|
cn_ai_harness_ids() { jq -r '.harnesses | keys[]' "$CN_AI_HARNESSES"; }
|
||||||
|
|
||||||
|
cn_ai_endpoint() {
|
||||||
|
jq -re --arg p "$1" --arg proto "$2" '.providers[$p].endpoints[$proto]' "$CN_AI_PROVIDERS"
|
||||||
|
}
|
||||||
|
|
||||||
|
cn_ai_models() {
|
||||||
|
jq -r --arg p "$1" '.providers[$p].models[].id' "$CN_AI_PROVIDERS"
|
||||||
|
}
|
||||||
|
|
||||||
|
cn_ai_model_by_alias() {
|
||||||
|
jq -re --arg p "$1" --arg a "$2" \
|
||||||
|
'.providers[$p].models[] | select(.aliases // [] | index($a)) | .id' "$CN_AI_PROVIDERS"
|
||||||
|
}
|
||||||
|
|
||||||
|
cn_ai_fast_model() {
|
||||||
|
cn_ai_model_by_alias "$1" fast 2>/dev/null || cn_ai_model_by_alias "$1" default-coding
|
||||||
|
}
|
||||||
|
|
||||||
|
cn_ai_harness_field() {
|
||||||
|
jq -re --arg h "$1" --arg f "$2" '.harnesses[$h][$f]' "$CN_AI_HARNESSES"
|
||||||
|
}
|
||||||
|
|
||||||
|
cn_ai_combo_level() {
|
||||||
|
jq -r --arg h "$1" --arg p "$2" '.combos[$h][$p].level // "unsupported"' "$CN_AI_COMPAT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Profile files are flat key="value" lines
|
||||||
|
cn_ai_profile_field() {
|
||||||
|
local file="$CN_AI_PROFILE_DIR/$1.toml" key="$2"
|
||||||
|
grep -E "^$key = " "$file" | head -1 | cut -d\" -f2
|
||||||
|
}
|
||||||
|
|
||||||
|
cn_ai_current_profile() {
|
||||||
|
if [[ ! -f $CN_AI_CURRENT ]]; then
|
||||||
|
echo "No default AI profile set (run: omarchycn ai profile use <name>)" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
cat "$CN_AI_CURRENT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print export statements for the profile's harness x provider combo.
|
||||||
|
# Secrets are resolved at launch time only; nothing is written to disk.
|
||||||
|
cn_ai_render_env() {
|
||||||
|
local harness="$1" provider="$2" model="$3" key="$4"
|
||||||
|
local base fast
|
||||||
|
|
||||||
|
case "$harness" in
|
||||||
|
claude-code)
|
||||||
|
base=$(cn_ai_endpoint "$provider" anthropic)
|
||||||
|
fast=$(cn_ai_fast_model "$provider")
|
||||||
|
printf 'export ANTHROPIC_BASE_URL=%q\n' "$base"
|
||||||
|
printf 'export ANTHROPIC_AUTH_TOKEN=%q\n' "$key"
|
||||||
|
printf 'export ANTHROPIC_MODEL=%q\n' "$model"
|
||||||
|
printf 'export ANTHROPIC_DEFAULT_SONNET_MODEL=%q\n' "$model"
|
||||||
|
printf 'export ANTHROPIC_DEFAULT_OPUS_MODEL=%q\n' "$model"
|
||||||
|
printf 'export ANTHROPIC_DEFAULT_HAIKU_MODEL=%q\n' "$fast"
|
||||||
|
;;
|
||||||
|
opencode)
|
||||||
|
if [[ $provider != "deepseek" ]]; then
|
||||||
|
echo "opencode adapter renders only the deepseek combo (native provider)" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf 'export DEEPSEEK_API_KEY=%q\n' "$key"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "No env renderer for harness: $harness" >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write ~/.codex/config.toml provider block (official DeepSeek codex format)
|
||||||
|
cn_ai_render_codex_config() {
|
||||||
|
local provider="$1" model="$2" key="$3"
|
||||||
|
local base cfg="$HOME/.codex/config.toml"
|
||||||
|
|
||||||
|
if [[ $provider != "deepseek" ]]; then
|
||||||
|
echo "codex adapter renders only the deepseek combo (official integration)" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
base="https://api.deepseek.com/"
|
||||||
|
|
||||||
|
mkdir -p "${cfg%/*}"
|
||||||
|
touch "$cfg"
|
||||||
|
if [[ -s $cfg ]]; then
|
||||||
|
cp "$cfg" "$cfg.omarchycn-bak-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Top-level keys must precede the first [section]; provider table goes last.
|
||||||
|
# Strip our old blocks and any pre-section model keys, keep the rest intact.
|
||||||
|
awk '
|
||||||
|
/^# OmarchyCN ai (model|provider) begin$/ { skip = 1; next }
|
||||||
|
/^# OmarchyCN ai (model|provider) end$/ { skip = 0; next }
|
||||||
|
skip { next }
|
||||||
|
/^\[/ { in_section = 1 }
|
||||||
|
!in_section && /^(model|model_provider) = / { next }
|
||||||
|
{ print }
|
||||||
|
' "$cfg" > "$cfg.omarchycn-tmp"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# OmarchyCN ai model begin"
|
||||||
|
echo "model = \"$model\""
|
||||||
|
echo "model_provider = \"$provider\""
|
||||||
|
echo "# OmarchyCN ai model end"
|
||||||
|
cat "$cfg.omarchycn-tmp"
|
||||||
|
echo "# OmarchyCN ai provider begin"
|
||||||
|
echo "[model_providers.$provider]"
|
||||||
|
echo "name = \"$provider\""
|
||||||
|
echo "base_url = \"$base\""
|
||||||
|
echo "experimental_bearer_token = \"$key\""
|
||||||
|
echo "# OmarchyCN ai provider end"
|
||||||
|
} > "$cfg"
|
||||||
|
rm -f "$cfg.omarchycn-tmp"
|
||||||
|
chmod 600 "$cfg"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user