arkaos 4.3.2 → 4.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # ARKA OS — Doctor (Health Check System)
4
+ # Usage: arka doctor [--fix] [--json]
5
+ # 16 modular checks: fail = critical, warn = advisory
6
+ # ============================================================================
7
+ set -euo pipefail
8
+
9
+ # ─── Args ──────────────────────────────────────────────────────────────────
10
+ FIX_MODE=false
11
+ JSON_MODE=false
12
+ for arg in "$@"; do
13
+ case "$arg" in
14
+ --fix) FIX_MODE=true ;;
15
+ --json) JSON_MODE=true ;;
16
+ esac
17
+ done
18
+
19
+ # ─── Colors ────────────────────────────────────────────────────────────────
20
+ if [ "$JSON_MODE" = true ]; then
21
+ # No colors in JSON mode
22
+ G="" Y="" R="" C="" N="" B=""
23
+ else
24
+ G='\033[0;32m' # green
25
+ Y='\033[1;33m' # yellow
26
+ R='\033[0;31m' # red
27
+ C='\033[0;36m' # cyan
28
+ N='\033[0m' # reset
29
+ B='\033[1m' # bold
30
+ fi
31
+
32
+ ARKA_OS="${ARKA_OS:-$HOME/.claude/skills/arka}"
33
+ RESULTS=()
34
+ PASS_COUNT=0
35
+ WARN_COUNT=0
36
+ FAIL_COUNT=0
37
+
38
+ # ─── Check Runner ──────────────────────────────────────────────────────────
39
+ # run_check <name> <severity:fail|warn> <description> <pass:true|false> [fix_message]
40
+ run_check() {
41
+ local name="$1" severity="$2" desc="$3" passed="$4" fix_msg="${5:-}"
42
+ local status icon
43
+
44
+ if [ "$passed" = true ]; then
45
+ status="pass"
46
+ icon="${G}✓${N}"
47
+ PASS_COUNT=$((PASS_COUNT + 1))
48
+ elif [ "$severity" = "fail" ]; then
49
+ status="fail"
50
+ icon="${R}✗${N}"
51
+ FAIL_COUNT=$((FAIL_COUNT + 1))
52
+ else
53
+ status="warn"
54
+ icon="${Y}⚠${N}"
55
+ WARN_COUNT=$((WARN_COUNT + 1))
56
+ fi
57
+
58
+ if [ "$JSON_MODE" = true ]; then
59
+ RESULTS+=("{\"name\":\"$name\",\"status\":\"$status\",\"severity\":\"$severity\",\"description\":\"$desc\",\"fix\":\"$fix_msg\"}")
60
+ else
61
+ printf " %b %-20s %s" "$icon" "$name" "$desc"
62
+ if [ "$status" != "pass" ] && [ -n "$fix_msg" ]; then
63
+ printf " ${Y}(%s)${N}" "$fix_msg"
64
+ fi
65
+ echo ""
66
+ fi
67
+ }
68
+
69
+ # ─── Header ────────────────────────────────────────────────────────────────
70
+ if [ "$JSON_MODE" != true ]; then
71
+ echo ""
72
+ echo -e "${C}╔══════════════════════════════════════════════════╗${N}"
73
+ echo -e "${C}║${N} ${B}ARKA OS Doctor${N} — Health Check System ${C}║${N}"
74
+ echo -e "${C}╚══════════════════════════════════════════════════╝${N}"
75
+ echo ""
76
+ fi
77
+
78
+ # ─── Check 1: Claude CLI ──────────────────────────────────────────────────
79
+ CLAUDE_OK=false
80
+ if command -v claude &>/dev/null; then
81
+ CLAUDE_OK=true
82
+ fi
83
+ run_check "claude-cli" "fail" "Claude Code CLI installed" "$CLAUDE_OK" "Install: npm install -g @anthropic-ai/claude-code"
84
+
85
+ # ─── Check 2: ARKA OS Install ─────────────────────────────────────────────
86
+ ARKA_OK=false
87
+ ARKA_DESC="ARKA OS installed"
88
+ if [ -d "$ARKA_OS" ] && [ -f "$ARKA_OS/SKILL.md" ]; then
89
+ ARKA_OK=true
90
+ ARKA_VER=$(cat "$ARKA_OS/VERSION" 2>/dev/null || echo "?")
91
+ ARKA_DESC="ARKA OS v${ARKA_VER} installed"
92
+ fi
93
+ run_check "arka-install" "fail" "$ARKA_DESC" "$ARKA_OK" "Run: bash install.sh"
94
+
95
+ # ─── Check 3: jq ──────────────────────────────────────────────────────────
96
+ JQ_OK=false
97
+ if command -v jq &>/dev/null; then
98
+ JQ_OK=true
99
+ fi
100
+ run_check "jq" "fail" "jq available" "$JQ_OK" "Install: brew install jq"
101
+
102
+ # ─── Check 4: User Profile ────────────────────────────────────────────────
103
+ PROFILE_OK=false
104
+ PROFILE_DESC="User profile exists"
105
+ PROFILE_FILE="$HOME/.arka-os/profile.json"
106
+ if [ -f "$PROFILE_FILE" ]; then
107
+ PNAME=$(jq -r '.user_name // ""' "$PROFILE_FILE" 2>/dev/null)
108
+ if [ -n "$PNAME" ] && [ "$PNAME" != "null" ]; then
109
+ PROFILE_OK=true
110
+ PROFILE_DESC="User profile exists ($PNAME)"
111
+ else
112
+ PROFILE_DESC="User profile exists but missing name"
113
+ fi
114
+ fi
115
+ if [ "$FIX_MODE" = true ] && [ "$PROFILE_OK" = false ] && [ ! -f "$PROFILE_FILE" ]; then
116
+ mkdir -p "$HOME/.arka-os"
117
+ echo '{"user_name":"","company_name":"","role":"","onboarding_version":"1"}' > "$PROFILE_FILE"
118
+ PROFILE_DESC="User profile created (run /arka setup to complete)"
119
+ fi
120
+ run_check "profile" "warn" "$PROFILE_DESC" "$PROFILE_OK" "Run: /arka setup"
121
+
122
+ # ─── Check 5: Status Line ─────────────────────────────────────────────────
123
+ SL_OK=false
124
+ SL_DESC="Status line configured"
125
+ SETTINGS_FILE="$HOME/.claude/settings.json"
126
+ if [ -f "$SETTINGS_FILE" ] && jq -e '.statusLine.command' "$SETTINGS_FILE" &>/dev/null; then
127
+ SL_CMD=$(jq -r '.statusLine.command' "$SETTINGS_FILE")
128
+ if [ -f "$SL_CMD" ]; then
129
+ SL_OK=true
130
+ else
131
+ SL_DESC="Status line command not found: $SL_CMD"
132
+ fi
133
+ fi
134
+ if [ "$FIX_MODE" = true ] && [ "$SL_OK" = false ] && [ -f "$ARKA_OS/statusline.sh" ]; then
135
+ if [ -f "$SETTINGS_FILE" ] && command -v jq &>/dev/null; then
136
+ jq --arg cmd "$ARKA_OS/statusline.sh" '.statusLine = {"type":"command","command":$cmd,"padding":2}' \
137
+ "$SETTINGS_FILE" > "$SETTINGS_FILE.tmp" && mv "$SETTINGS_FILE.tmp" "$SETTINGS_FILE"
138
+ SL_OK=true
139
+ SL_DESC="Status line configured (fixed)"
140
+ fi
141
+ fi
142
+ run_check "statusline" "warn" "$SL_DESC" "$SL_OK" "Run: bash install.sh"
143
+
144
+ # ─── Check 6: Hooks ───────────────────────────────────────────────────────
145
+ HOOKS_OK=false
146
+ HOOKS_DESC="Hooks configured"
147
+ if [ -f "$SETTINGS_FILE" ] && jq -e '.hooks.UserPromptSubmit' "$SETTINGS_FILE" &>/dev/null; then
148
+ HOOKS_OK=true
149
+ HOOK_COUNT=$(jq '[.hooks | to_entries[].value[].hooks[]] | length' "$SETTINGS_FILE" 2>/dev/null || echo "0")
150
+ HOOKS_DESC="Hooks configured ($HOOK_COUNT hook(s))"
151
+ fi
152
+ if [ "$FIX_MODE" = true ] && [ "$HOOKS_OK" = false ]; then
153
+ HOOKS_DIR="$HOME/.claude/skills/arka/hooks"
154
+ if [ -d "$HOOKS_DIR" ] && [ -f "$SETTINGS_FILE" ] && command -v jq &>/dev/null; then
155
+ HOOKS_JSON=$(jq -n \
156
+ --arg ups "$HOOKS_DIR/user-prompt-submit.sh" \
157
+ --arg pc "$HOOKS_DIR/pre-compact.sh" \
158
+ --arg ptu "$HOOKS_DIR/post-tool-use.sh" \
159
+ '{
160
+ "UserPromptSubmit": [{"hooks":[{"type":"command","command":$ups,"timeout":10}]}],
161
+ "PreCompact": [{"hooks":[{"type":"command","command":$pc,"timeout":30}]}],
162
+ "PostToolUse": [{"hooks":[{"type":"command","command":$ptu,"timeout":5}]}]
163
+ }')
164
+ jq --argjson hooks "$HOOKS_JSON" '.hooks = $hooks' "$SETTINGS_FILE" > "$SETTINGS_FILE.tmp" && \
165
+ mv "$SETTINGS_FILE.tmp" "$SETTINGS_FILE"
166
+ HOOKS_OK=true
167
+ HOOKS_DESC="Hooks configured (fixed)"
168
+ fi
169
+ fi
170
+ run_check "hooks" "warn" "$HOOKS_DESC" "$HOOKS_OK" "Run: bash install.sh"
171
+
172
+ # ─── Check 7: Obsidian Vault ──────────────────────────────────────────────
173
+ OBS_OK=false
174
+ OBS_DESC="Obsidian vault configured"
175
+ OBS_CONFIG="$ARKA_OS/knowledge/obsidian-config.json"
176
+ if [ -f "$OBS_CONFIG" ]; then
177
+ VAULT_PATH=$(jq -r '.vault_path // ""' "$OBS_CONFIG" 2>/dev/null)
178
+ if [ -n "$VAULT_PATH" ] && [ -d "$VAULT_PATH" ] && [ -d "$VAULT_PATH/.obsidian" ]; then
179
+ OBS_OK=true
180
+ OBS_DESC="Obsidian vault: $(basename "$VAULT_PATH")"
181
+ elif [ -n "$VAULT_PATH" ]; then
182
+ OBS_DESC="Obsidian vault path invalid: $VAULT_PATH"
183
+ fi
184
+ fi
185
+ run_check "obsidian" "warn" "$OBS_DESC" "$OBS_OK" "Run: bash install.sh (will auto-detect)"
186
+
187
+ # ─── Check 8: Departments ─────────────────────────────────────────────────
188
+ DEPT_OK=false
189
+ DEPT_COUNT=0
190
+ for dept_dir in "$HOME/.claude/skills"/arka-*/; do
191
+ [ -d "$dept_dir" ] && [ -f "$dept_dir/SKILL.md" ] && DEPT_COUNT=$((DEPT_COUNT + 1))
192
+ done
193
+ # Subtract non-department skills (arka-scaffold, arka-mcp, etc. are sub-skills but still count as skills)
194
+ DEPT_DESC="$DEPT_COUNT department skills installed"
195
+ if [ "$DEPT_COUNT" -ge 7 ]; then
196
+ DEPT_OK=true
197
+ fi
198
+ run_check "departments" "warn" "$DEPT_DESC" "$DEPT_OK" "Run: bash install.sh"
199
+
200
+ # ─── Check 9: Personas ────────────────────────────────────────────────────
201
+ PERSONA_OK=false
202
+ PERSONA_COUNT=0
203
+ for agent in "$HOME/.claude/agents"/arka-*.md; do
204
+ [ -f "$agent" ] && PERSONA_COUNT=$((PERSONA_COUNT + 1))
205
+ done
206
+ PERSONA_DESC="$PERSONA_COUNT agent files installed"
207
+ if [ "$PERSONA_COUNT" -ge 10 ]; then
208
+ PERSONA_OK=true
209
+ fi
210
+ run_check "personas" "warn" "$PERSONA_DESC" "$PERSONA_OK" "Run: bash install.sh"
211
+
212
+ # ─── Check 10: MCP Registry ───────────────────────────────────────────────
213
+ MCP_OK=false
214
+ MCP_DESC="MCP registry present"
215
+ MCP_REG="$ARKA_OS/mcps/registry.json"
216
+ if [ -f "$MCP_REG" ]; then
217
+ MCP_COUNT=$(jq '.mcpServers | length' "$MCP_REG" 2>/dev/null || echo "0")
218
+ MCP_OK=true
219
+ MCP_DESC="MCP registry ($MCP_COUNT MCPs)"
220
+ fi
221
+ run_check "mcp-registry" "fail" "$MCP_DESC" "$MCP_OK" "Run: bash install.sh"
222
+
223
+ # ─── Check 11: Prerequisites ──────────────────────────────────────────────
224
+ PREREQ_OK=true
225
+ MISSING_PREREQS=""
226
+ for bin in yt-dlp ffmpeg python3; do
227
+ if ! command -v "$bin" &>/dev/null; then
228
+ MISSING_PREREQS="$MISSING_PREREQS $bin"
229
+ PREREQ_OK=false
230
+ fi
231
+ done
232
+ MISSING_PREREQS="${MISSING_PREREQS# }" # trim leading space
233
+ if [ "$PREREQ_OK" = true ]; then
234
+ PREREQ_DESC="yt-dlp, ffmpeg, python3 available"
235
+ else
236
+ PREREQ_DESC="Missing: $MISSING_PREREQS"
237
+ fi
238
+ run_check "prerequisites" "warn" "$PREREQ_DESC" "$PREREQ_OK" "brew install $MISSING_PREREQS"
239
+
240
+ # ─── Check 12: Capabilities ───────────────────────────────────────────────
241
+ CAPS_OK=false
242
+ CAPS_DESC="capabilities.json present"
243
+ CAPS_FILE="$HOME/.arka-os/capabilities.json"
244
+ if [ -f "$CAPS_FILE" ]; then
245
+ # Check age (< 7 days)
246
+ CAPS_AGE=$(( ($(date +%s) - $(stat -f%m "$CAPS_FILE" 2>/dev/null || stat -c%Y "$CAPS_FILE" 2>/dev/null || echo 0)) ))
247
+ CAPS_DAYS=$((CAPS_AGE / 86400))
248
+ if [ "$CAPS_DAYS" -lt 7 ]; then
249
+ CAPS_OK=true
250
+ CAPS_DESC="capabilities.json (${CAPS_DAYS}d old)"
251
+ else
252
+ CAPS_DESC="capabilities.json stale (${CAPS_DAYS}d old, max 7d)"
253
+ fi
254
+ fi
255
+ if [ "$FIX_MODE" = true ] && [ "$CAPS_OK" = false ]; then
256
+ CAPS_SCRIPT="$ARKA_OS/../arka-knowledge/scripts/kb-check-capabilities.sh"
257
+ # Also check repo path
258
+ REPO_PATH=$(cat "$ARKA_OS/.repo-path" 2>/dev/null || echo "")
259
+ if [ -n "$REPO_PATH" ] && [ -f "$REPO_PATH/departments/kb/scripts/kb-check-capabilities.sh" ]; then
260
+ CAPS_SCRIPT="$REPO_PATH/departments/kb/scripts/kb-check-capabilities.sh"
261
+ fi
262
+ if [ -f "$CAPS_SCRIPT" ]; then
263
+ bash "$CAPS_SCRIPT" >/dev/null 2>&1 && CAPS_OK=true && CAPS_DESC="capabilities.json (refreshed)"
264
+ fi
265
+ fi
266
+ run_check "capabilities" "warn" "$CAPS_DESC" "$CAPS_OK" "Run: arka kb capabilities"
267
+
268
+ # ─── Check 13: Agent Memory ─────────────────────────────────────────────
269
+ AMEM_OK=false
270
+ AMEM_COUNT=0
271
+ AMEM_DIR="$HOME/.claude/agent-memory"
272
+ if [ -d "$AMEM_DIR" ]; then
273
+ for mem_dir in "$AMEM_DIR"/arka-*/MEMORY.md; do
274
+ [ -f "$mem_dir" ] && AMEM_COUNT=$((AMEM_COUNT + 1))
275
+ done
276
+ fi
277
+ AMEM_DESC="$AMEM_COUNT/15 agent memory files"
278
+ if [ "$AMEM_COUNT" -ge 15 ]; then
279
+ AMEM_OK=true
280
+ fi
281
+ run_check "agent-memory" "warn" "$AMEM_DESC" "$AMEM_OK" "Run: bash install.sh"
282
+
283
+ # ─── Check 14: Install Manifest ─────────────────────────────────────────
284
+ MANIFEST_OK=false
285
+ MANIFEST_DESC="Install manifest present"
286
+ MANIFEST_FILE="$HOME/.arka-os/install-manifest.json"
287
+ if [ -f "$MANIFEST_FILE" ]; then
288
+ if jq empty "$MANIFEST_FILE" 2>/dev/null; then
289
+ MANIFEST_VER=$(jq -r '.version // "?"' "$MANIFEST_FILE")
290
+ FILE_COUNT=$(jq '.files | length' "$MANIFEST_FILE" 2>/dev/null || echo "0")
291
+ MANIFEST_OK=true
292
+ MANIFEST_DESC="Install manifest v${MANIFEST_VER} ($FILE_COUNT files)"
293
+ else
294
+ MANIFEST_DESC="Install manifest corrupted"
295
+ fi
296
+ fi
297
+ run_check "install-manifest" "warn" "$MANIFEST_DESC" "$MANIFEST_OK" "Run: bash install.sh"
298
+
299
+ # ─── Check 15: Gotchas ──────────────────────────────────────────────────
300
+ GOTCHAS_OK=false
301
+ GOTCHAS_DESC="Gotchas file present"
302
+ GOTCHAS_FILE="$HOME/.arka-os/gotchas.json"
303
+ if [ -f "$GOTCHAS_FILE" ]; then
304
+ if jq empty "$GOTCHAS_FILE" 2>/dev/null; then
305
+ GOTCHA_COUNT=$(jq 'length' "$GOTCHAS_FILE" 2>/dev/null || echo "0")
306
+ GOTCHAS_OK=true
307
+ GOTCHAS_DESC="Gotchas tracking ($GOTCHA_COUNT patterns)"
308
+ else
309
+ GOTCHAS_DESC="Gotchas file corrupted"
310
+ fi
311
+ else
312
+ # Create empty gotchas file if missing (auto-fix always)
313
+ mkdir -p "$HOME/.arka-os"
314
+ echo '[]' > "$GOTCHAS_FILE"
315
+ GOTCHAS_OK=true
316
+ GOTCHAS_DESC="Gotchas file created (empty)"
317
+ fi
318
+ run_check "gotchas" "warn" "$GOTCHAS_DESC" "$GOTCHAS_OK" "Run: bash install.sh"
319
+
320
+ # ─── Check 16: Plugins (Superpowers + Claude-Mem) ─────────────────────────
321
+ PLUGINS_OK=false
322
+ PLUGINS_DESC="Plugins not checked"
323
+ if command -v claude &>/dev/null; then
324
+ PLUGIN_LIST=$(claude plugin list 2>/dev/null || echo "")
325
+ HAS_SP=false
326
+ HAS_CM=false
327
+ echo "$PLUGIN_LIST" | grep -q "superpowers" && HAS_SP=true
328
+ echo "$PLUGIN_LIST" | grep -q "claude-mem" && HAS_CM=true
329
+
330
+ if [ "$HAS_SP" = true ] && [ "$HAS_CM" = true ]; then
331
+ PLUGINS_OK=true
332
+ PLUGINS_DESC="Plugins installed (Superpowers + Claude-Mem)"
333
+ elif [ "$HAS_SP" = true ]; then
334
+ PLUGINS_DESC="Missing Claude-Mem plugin"
335
+ elif [ "$HAS_CM" = true ]; then
336
+ PLUGINS_DESC="Missing Superpowers plugin"
337
+ else
338
+ PLUGINS_DESC="Missing both plugins (Superpowers + Claude-Mem)"
339
+ fi
340
+
341
+ # Auto-fix: install missing plugins
342
+ if [ "$FIX_MODE" = true ] && [ "$PLUGINS_OK" = false ]; then
343
+ if [ "$HAS_SP" = false ]; then
344
+ MARKETPLACES=$(claude plugin marketplace list 2>/dev/null || echo "")
345
+ echo "$MARKETPLACES" | grep -q "superpowers-marketplace" || \
346
+ claude plugin marketplace add obra/superpowers-marketplace >/dev/null 2>&1
347
+ claude plugin install superpowers@superpowers-marketplace >/dev/null 2>&1 && HAS_SP=true
348
+ fi
349
+ if [ "$HAS_CM" = false ]; then
350
+ MARKETPLACES=$(claude plugin marketplace list 2>/dev/null || echo "")
351
+ echo "$MARKETPLACES" | grep -q "thedotmack" || \
352
+ claude plugin marketplace add thedotmack/claude-mem >/dev/null 2>&1
353
+ claude plugin install claude-mem@thedotmack >/dev/null 2>&1 && HAS_CM=true
354
+ fi
355
+ if [ "$HAS_SP" = true ] && [ "$HAS_CM" = true ]; then
356
+ PLUGINS_OK=true
357
+ PLUGINS_DESC="Plugins installed (auto-fixed)"
358
+ fi
359
+ fi
360
+ else
361
+ PLUGINS_DESC="Claude CLI not found — cannot check plugins"
362
+ fi
363
+ run_check "plugins" "warn" "$PLUGINS_DESC" "$PLUGINS_OK" "Run: bash install.sh"
364
+
365
+ # ─── Output ────────────────────────────────────────────────────────────────
366
+ TOTAL=$((PASS_COUNT + WARN_COUNT + FAIL_COUNT))
367
+
368
+ if [ "$JSON_MODE" = true ]; then
369
+ # Build JSON array
370
+ echo -n "["
371
+ for i in "${!RESULTS[@]}"; do
372
+ [ "$i" -gt 0 ] && echo -n ","
373
+ echo -n "${RESULTS[$i]}"
374
+ done
375
+ echo "]"
376
+ else
377
+ echo ""
378
+ echo -e " ${B}Results:${N} ${G}${PASS_COUNT} passed${N}, ${Y}${WARN_COUNT} warnings${N}, ${R}${FAIL_COUNT} failed${N} (${TOTAL} checks)"
379
+
380
+ if [ "$FAIL_COUNT" -gt 0 ]; then
381
+ echo ""
382
+ echo -e " ${R}Some critical checks failed.${N} Run ${C}arka doctor --fix${N} to attempt auto-repair."
383
+ elif [ "$WARN_COUNT" -gt 0 ]; then
384
+ echo ""
385
+ echo -e " ${Y}Some warnings detected.${N} Run ${C}arka doctor --fix${N} to attempt auto-repair."
386
+ else
387
+ echo ""
388
+ echo -e " ${G}All checks passed!${N} ARKA OS is healthy."
389
+ fi
390
+ echo ""
391
+ fi
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # ARKA OS — Provider Registry Manager
4
+ # Manage external AI providers and models (image, video, text)
5
+ # Usage: arka providers [command] [args...]
6
+ # ============================================================================
7
+ set -e
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
11
+ REGISTRY="$REPO_DIR/config/providers-registry.json"
12
+ ENV_FILE="$HOME/.arka-os/.env"
13
+
14
+ CYAN='\033[0;36m'
15
+ GREEN='\033[0;32m'
16
+ YELLOW='\033[1;33m'
17
+ RED='\033[0;31m'
18
+ BLUE='\033[0;34m'
19
+ BOLD='\033[1m'
20
+ NC='\033[0m'
21
+
22
+ # Require jq
23
+ command -v jq &>/dev/null || { echo "Error: jq is required. Install: brew install jq"; exit 1; }
24
+
25
+ # Load env if present
26
+ [ -f "$ENV_FILE" ] && source "$ENV_FILE"
27
+
28
+ # ─── Helper: Check if API key is configured ─────────────────────────────────
29
+ is_key_configured() {
30
+ local var_name="$1"
31
+ local value="${!var_name:-}"
32
+ [ -n "$value" ] && [ ${#value} -ge 8 ]
33
+ }
34
+
35
+ # ─── List providers ──────────────────────────────────────────────────────────
36
+ cmd_list() {
37
+ echo ""
38
+ echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
39
+ echo -e "${CYAN}║${NC} ${BOLD}ARKA OS — AI Providers${NC} ${CYAN}║${NC}"
40
+ echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
41
+ echo ""
42
+
43
+ local providers
44
+ providers=$(jq -r '.providers | keys[]' "$REGISTRY")
45
+
46
+ for provider_id in $providers; do
47
+ local name auth_env
48
+ name=$(jq -r --arg id "$provider_id" '.providers[$id].name' "$REGISTRY")
49
+ auth_env=$(jq -r --arg id "$provider_id" '.providers[$id].auth_env' "$REGISTRY")
50
+
51
+ if is_key_configured "$auth_env"; then
52
+ echo -e " ${GREEN}●${NC} ${BOLD}$name${NC} ($provider_id) — ${GREEN}configured${NC} [$auth_env]"
53
+ else
54
+ echo -e " ${RED}○${NC} ${BOLD}$name${NC} ($provider_id) — ${YELLOW}not configured${NC} [$auth_env]"
55
+ fi
56
+
57
+ # List models
58
+ local models
59
+ models=$(jq -r --arg id "$provider_id" '.providers[$id].models | to_entries[] | " \(.value.type | ascii_upcase) \(.key) — \(.value.description)"' "$REGISTRY")
60
+ echo "$models"
61
+ echo ""
62
+ done
63
+
64
+ local total_providers total_models
65
+ total_providers=$(jq '.providers | length' "$REGISTRY")
66
+ total_models=$(jq '[.providers[].models | length] | add' "$REGISTRY")
67
+ echo -e " ${CYAN}$total_providers${NC} providers, ${CYAN}$total_models${NC} models"
68
+ echo ""
69
+ }
70
+
71
+ # ─── Add provider (interactive) ─────────────────────────────────────────────
72
+ cmd_add() {
73
+ local provider_id="${1:-}"
74
+ if [ -z "$provider_id" ]; then
75
+ echo -e "${BLUE}Add a new AI provider${NC}"
76
+ read -rp " Provider ID (lowercase, no spaces): " provider_id
77
+ fi
78
+ [ -z "$provider_id" ] && { echo "Error: Provider ID is required."; exit 1; }
79
+
80
+ # Check if already exists
81
+ if jq -e --arg id "$provider_id" '.providers[$id]' "$REGISTRY" &>/dev/null; then
82
+ echo -e "${YELLOW}Provider '$provider_id' already exists. Use 'add-model' to add models.${NC}"
83
+ exit 1
84
+ fi
85
+
86
+ read -rp " Display name: " name
87
+ read -rp " Base URL (e.g., https://api.example.com/v1): " base_url
88
+ read -rp " Auth env variable name (e.g., EXAMPLE_API_KEY): " auth_env
89
+ read -rp " Auth header format (e.g., Authorization: Bearer): " auth_header
90
+
91
+ [ -z "$name" ] || [ -z "$base_url" ] || [ -z "$auth_env" ] && { echo "Error: All fields are required."; exit 1; }
92
+ [ -z "$auth_header" ] && auth_header="Authorization: Bearer"
93
+
94
+ # Add to registry
95
+ local tmp
96
+ tmp=$(mktemp)
97
+ jq --arg id "$provider_id" \
98
+ --arg name "$name" \
99
+ --arg base_url "$base_url" \
100
+ --arg auth_env "$auth_env" \
101
+ --arg auth_header "$auth_header" \
102
+ '.providers[$id] = {name: $name, base_url: $base_url, auth_env: $auth_env, auth_header: $auth_header, models: {}}' \
103
+ "$REGISTRY" > "$tmp" && mv "$tmp" "$REGISTRY"
104
+
105
+ echo -e "${GREEN}✓${NC} Provider '$provider_id' added. Use 'arka providers add-model $provider_id <model-id>' to add models."
106
+ }
107
+
108
+ # ─── Remove provider ─────────────────────────────────────────────────────────
109
+ cmd_remove() {
110
+ local provider_id="${1:-}"
111
+ [ -z "$provider_id" ] && { echo "Usage: arka providers remove <provider-id>"; exit 1; }
112
+
113
+ if ! jq -e --arg id "$provider_id" '.providers[$id]' "$REGISTRY" &>/dev/null; then
114
+ echo -e "${RED}Provider '$provider_id' not found.${NC}"
115
+ exit 1
116
+ fi
117
+
118
+ local tmp
119
+ tmp=$(mktemp)
120
+ jq --arg id "$provider_id" '
121
+ .providers |= del(.[$id]) |
122
+ .routing |= with_entries(.value |= [.[] | select(startswith($id + "/") | not)])
123
+ ' "$REGISTRY" > "$tmp" && mv "$tmp" "$REGISTRY"
124
+
125
+ echo -e "${GREEN}✓${NC} Provider '$provider_id' removed."
126
+ }
127
+
128
+ # ─── Add model to existing provider ──────────────────────────────────────────
129
+ cmd_add_model() {
130
+ local provider_id="${1:-}"
131
+ local model_id="${2:-}"
132
+ [ -z "$provider_id" ] || [ -z "$model_id" ] && { echo "Usage: arka providers add-model <provider-id> <model-id> [--type image|video|text] [--description \"...\"]"; exit 1; }
133
+
134
+ if ! jq -e --arg id "$provider_id" '.providers[$id]' "$REGISTRY" &>/dev/null; then
135
+ echo -e "${RED}Provider '$provider_id' not found.${NC}"
136
+ exit 1
137
+ fi
138
+
139
+ shift 2
140
+ local model_type="text"
141
+ local description=""
142
+
143
+ while [ $# -gt 0 ]; do
144
+ case "$1" in
145
+ --type) model_type="$2"; shift 2 ;;
146
+ --description) description="$2"; shift 2 ;;
147
+ *) shift ;;
148
+ esac
149
+ done
150
+
151
+ [ -z "$description" ] && description="$model_id"
152
+
153
+ local tmp
154
+ tmp=$(mktemp)
155
+ jq --arg pid "$provider_id" \
156
+ --arg mid "$model_id" \
157
+ --arg type "$model_type" \
158
+ --arg desc "$description" \
159
+ '.providers[$pid].models[$mid] = {type: $type, description: $desc}' \
160
+ "$REGISTRY" > "$tmp" && mv "$tmp" "$REGISTRY"
161
+
162
+ echo -e "${GREEN}✓${NC} Model '$model_id' ($model_type) added to '$provider_id'."
163
+ }
164
+
165
+ # ─── Add API key ─────────────────────────────────────────────────────────────
166
+ cmd_add_key() {
167
+ local var_name="${1:-}"
168
+ [ -z "$var_name" ] && { echo "Usage: arka providers add-key <ENV_VAR_NAME>"; exit 1; }
169
+
170
+ mkdir -p "$(dirname "$ENV_FILE")"
171
+
172
+ if is_key_configured "$var_name"; then
173
+ echo -e "${GREEN}✓${NC} $var_name is already configured."
174
+ read -rp " Overwrite? (y/N): " confirm
175
+ [ "$confirm" != "y" ] && [ "$confirm" != "Y" ] && exit 0
176
+ fi
177
+
178
+ read -rp " Value for $var_name: " value
179
+ [ -z "$value" ] && { echo "Skipped."; exit 0; }
180
+
181
+ if grep -q "^export $var_name=" "$ENV_FILE" 2>/dev/null; then
182
+ local tmp
183
+ tmp=$(mktemp)
184
+ sed "s|^export $var_name=.*|export $var_name=\"$value\"|" "$ENV_FILE" > "$tmp" && mv "$tmp" "$ENV_FILE"
185
+ else
186
+ echo "export $var_name=\"$value\"" >> "$ENV_FILE"
187
+ fi
188
+
189
+ echo -e "${GREEN}✓${NC} $var_name saved to $ENV_FILE"
190
+ echo -e " Run ${CYAN}source ~/.zshrc${NC} to load."
191
+ }
192
+
193
+ # ─── Show routing chains ─────────────────────────────────────────────────────
194
+ cmd_routing() {
195
+ echo ""
196
+ echo -e "${CYAN}═══ AI Provider Routing Chains ═══${NC}"
197
+ echo ""
198
+
199
+ local chains
200
+ chains=$(jq -r '.routing | to_entries[] | .key' "$REGISTRY")
201
+
202
+ for chain in $chains; do
203
+ echo -e " ${BOLD}$chain:${NC}"
204
+ jq -r --arg chain "$chain" '.routing[$chain][]' "$REGISTRY" | while IFS= read -r model_ref; do
205
+ local provider_id="${model_ref%%/*}"
206
+ local model_id="${model_ref#*/}"
207
+ local auth_env
208
+ auth_env=$(jq -r --arg id "$provider_id" '.providers[$id].auth_env // "?"' "$REGISTRY")
209
+ if is_key_configured "$auth_env"; then
210
+ echo -e " ${GREEN}●${NC} $model_ref"
211
+ else
212
+ echo -e " ${RED}○${NC} $model_ref (${YELLOW}no key${NC})"
213
+ fi
214
+ done
215
+ echo ""
216
+ done
217
+ }
218
+
219
+ # ─── JSON output ──────────────────────────────────────────────────────────────
220
+ cmd_json() {
221
+ # Enrich with configured status
222
+ jq --arg env_file "$ENV_FILE" '
223
+ . as $reg |
224
+ .providers | to_entries | map(
225
+ .value.configured = (
226
+ .value.auth_env as $env |
227
+ (env[$env] // "") | length >= 8
228
+ )
229
+ ) | from_entries |
230
+ $reg | .providers = .
231
+ ' "$REGISTRY" 2>/dev/null || cat "$REGISTRY"
232
+ }
233
+
234
+ # ─── Main ─────────────────────────────────────────────────────────────────────
235
+ case "${1:-}" in
236
+ add)
237
+ shift
238
+ cmd_add "$@"
239
+ ;;
240
+ remove)
241
+ shift
242
+ cmd_remove "$@"
243
+ ;;
244
+ add-model)
245
+ shift
246
+ cmd_add_model "$@"
247
+ ;;
248
+ add-key)
249
+ shift
250
+ cmd_add_key "$@"
251
+ ;;
252
+ routing)
253
+ cmd_routing
254
+ ;;
255
+ --json)
256
+ cmd_json
257
+ ;;
258
+ *)
259
+ cmd_list
260
+ ;;
261
+ esac