@zachjxyz/moxie 0.4.3 → 0.4.5
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.
- package/bin/moxie +6 -1
- package/lib/agents.sh +103 -0
- package/lib/gateway-models.mjs +84 -0
- package/lib/phases.sh +134 -6
- package/package.json +1 -1
package/bin/moxie
CHANGED
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
# moxie status
|
|
13
13
|
# moxie cost
|
|
14
14
|
# moxie logs [phase]
|
|
15
|
+
# moxie models [--filter <search>]
|
|
15
16
|
# moxie agents
|
|
16
17
|
|
|
17
18
|
set -euo pipefail
|
|
18
19
|
|
|
19
|
-
MOXIE_VERSION="0.4.
|
|
20
|
+
MOXIE_VERSION="0.4.5"
|
|
20
21
|
# Resolve symlinks (npm installs bin as a symlink)
|
|
21
22
|
_self="$0"
|
|
22
23
|
while [ -L "$_self" ]; do
|
|
@@ -48,6 +49,7 @@ case "$COMMAND" in
|
|
|
48
49
|
status) cmd_status "$@" ;;
|
|
49
50
|
cost) cmd_cost "$@" ;;
|
|
50
51
|
logs) cmd_logs "$@" ;;
|
|
52
|
+
models) cmd_models "$@" ;;
|
|
51
53
|
agents) cmd_agents "$@" ;;
|
|
52
54
|
doctor) cmd_doctor "$@" ;;
|
|
53
55
|
version) echo "moxie $MOXIE_VERSION" ;;
|
|
@@ -63,6 +65,7 @@ Commands:
|
|
|
63
65
|
status Show phase progress, quorum state, and running status
|
|
64
66
|
cost Token usage breakdown by phase and agent
|
|
65
67
|
logs Tail or view logs for a phase
|
|
68
|
+
models List available AI Gateway models
|
|
66
69
|
agents List configured agents
|
|
67
70
|
doctor Check agent CLIs, auth, and project health
|
|
68
71
|
version Print version
|
|
@@ -80,6 +83,8 @@ Usage:
|
|
|
80
83
|
moxie run --dry-run Trace without spawning agents
|
|
81
84
|
moxie status Show progress and quorum
|
|
82
85
|
moxie cost Show token usage summary
|
|
86
|
+
moxie models List all AI Gateway models
|
|
87
|
+
moxie models -f anthropic Filter models by provider/name
|
|
83
88
|
moxie doctor Check agents and dependencies
|
|
84
89
|
|
|
85
90
|
Pipeline: rfc → audit → fix → plan → build
|
package/lib/agents.sh
CHANGED
|
@@ -554,6 +554,109 @@ cmd_agents() {
|
|
|
554
554
|
done
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
+
# ---- List available gateway models ----
|
|
558
|
+
|
|
559
|
+
cmd_models() {
|
|
560
|
+
local filter=""
|
|
561
|
+
while [ $# -gt 0 ]; do
|
|
562
|
+
case "$1" in
|
|
563
|
+
--filter|-f) filter="$2"; shift 2 ;;
|
|
564
|
+
--help|-h)
|
|
565
|
+
cat <<'EOF'
|
|
566
|
+
Usage: moxie models [--filter <search>]
|
|
567
|
+
|
|
568
|
+
List models available through your Vercel AI Gateway.
|
|
569
|
+
Requires a stored gateway API key (run 'moxie init' to set one up).
|
|
570
|
+
|
|
571
|
+
Options:
|
|
572
|
+
--filter, -f <search> Filter models by provider or name (e.g. "anthropic", "gpt")
|
|
573
|
+
|
|
574
|
+
Examples:
|
|
575
|
+
moxie models List all available models
|
|
576
|
+
moxie models -f anthropic Show only Anthropic models
|
|
577
|
+
moxie models -f gemini Search for Gemini models
|
|
578
|
+
EOF
|
|
579
|
+
return 0
|
|
580
|
+
;;
|
|
581
|
+
*) filter="$1"; shift ;;
|
|
582
|
+
esac
|
|
583
|
+
done
|
|
584
|
+
|
|
585
|
+
if ! command -v node &>/dev/null; then
|
|
586
|
+
echo "ERROR: node not found on PATH. Required for gateway API calls." >&2
|
|
587
|
+
return 1
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
if ! gateway_has_key "vercel-ai-gateway"; then
|
|
591
|
+
echo "ERROR: No AI Gateway API key configured." >&2
|
|
592
|
+
echo "Run 'moxie init' to set one up, or store a key with:" >&2
|
|
593
|
+
echo " moxie doctor (will prompt for key setup)" >&2
|
|
594
|
+
return 1
|
|
595
|
+
fi
|
|
596
|
+
|
|
597
|
+
local api_key
|
|
598
|
+
api_key=$(gateway_get_key "vercel-ai-gateway") || {
|
|
599
|
+
echo "ERROR: Failed to retrieve gateway API key." >&2
|
|
600
|
+
return 1
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
local endpoint="https://ai-gateway.vercel.sh"
|
|
604
|
+
if [ -f "${MOXIE_CONFIG:-}" ]; then
|
|
605
|
+
local cfg_endpoint
|
|
606
|
+
cfg_endpoint=$(toml_get "$MOXIE_CONFIG" "gateway.endpoint" "") || true
|
|
607
|
+
[ -n "$cfg_endpoint" ] && endpoint="$cfg_endpoint"
|
|
608
|
+
fi
|
|
609
|
+
|
|
610
|
+
local json
|
|
611
|
+
json=$(GATEWAY_API_KEY="$api_key" node "$MOXIE_LIB/gateway-models.mjs" "$endpoint" "$filter" 2>&1) || {
|
|
612
|
+
echo "ERROR: Failed to fetch models from gateway." >&2
|
|
613
|
+
echo "$json" >&2
|
|
614
|
+
return 1
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
# Parse JSON with python3 for pretty display
|
|
618
|
+
python3 -c "
|
|
619
|
+
import json, sys
|
|
620
|
+
|
|
621
|
+
data = json.loads(sys.stdin.read())
|
|
622
|
+
models = data.get('models', [])
|
|
623
|
+
|
|
624
|
+
if not models:
|
|
625
|
+
print('No models found.' + (' Try a different filter.' if '$filter' else ''))
|
|
626
|
+
sys.exit(0)
|
|
627
|
+
|
|
628
|
+
# Group by provider
|
|
629
|
+
providers = {}
|
|
630
|
+
for m in models:
|
|
631
|
+
p = m['provider']
|
|
632
|
+
providers.setdefault(p, []).append(m)
|
|
633
|
+
|
|
634
|
+
# Hardcoded models for marking
|
|
635
|
+
hardcoded = set('''$(printf '%s\n' "${KNOWN_GATEWAY_MODELS[@]}")'''.strip().split('\n'))
|
|
636
|
+
|
|
637
|
+
total = len(models)
|
|
638
|
+
filter_note = ' matching \"$filter\"' if '$filter' else ''
|
|
639
|
+
print(f'Available gateway models ({total}{filter_note}):')
|
|
640
|
+
print()
|
|
641
|
+
|
|
642
|
+
for provider in sorted(providers.keys()):
|
|
643
|
+
pmodels = providers[provider]
|
|
644
|
+
print(f' \033[1m{provider}\033[0m ({len(pmodels)} models)')
|
|
645
|
+
for m in pmodels:
|
|
646
|
+
marker = ' \033[32m●\033[0m' if m['id'] in hardcoded else ' '
|
|
647
|
+
print(f' {marker} {m[\"id\"]}')
|
|
648
|
+
print()
|
|
649
|
+
|
|
650
|
+
print('\033[32m●\033[0m = currently in moxie defaults')
|
|
651
|
+
print()
|
|
652
|
+
print('To use any model, add it to .moxie/config.toml:')
|
|
653
|
+
print(' [agents.my-model]')
|
|
654
|
+
print(' type = \"gateway\"')
|
|
655
|
+
print(' model = \"provider/model-name\"')
|
|
656
|
+
print(' order = 10')
|
|
657
|
+
" <<< "$json"
|
|
658
|
+
}
|
|
659
|
+
|
|
557
660
|
# ---- Doctor: health checks ----
|
|
558
661
|
|
|
559
662
|
cmd_doctor() {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// moxie/lib/gateway-models.mjs — List available models from Vercel AI Gateway
|
|
3
|
+
// Zero npm dependencies. Node 14+ compatible.
|
|
4
|
+
// Usage: GATEWAY_API_KEY=xxx node gateway-models.mjs [endpoint] [--filter provider]
|
|
5
|
+
|
|
6
|
+
import https from 'https';
|
|
7
|
+
import http from 'http';
|
|
8
|
+
|
|
9
|
+
const API_KEY = process.env.GATEWAY_API_KEY;
|
|
10
|
+
const endpoint = process.argv[2] || 'https://ai-gateway.vercel.sh';
|
|
11
|
+
const filter = process.argv[3] || '';
|
|
12
|
+
|
|
13
|
+
if (!API_KEY) { process.stderr.write('ERROR: GATEWAY_API_KEY not set\n'); process.exit(1); }
|
|
14
|
+
|
|
15
|
+
const url = new URL(`${endpoint}/v1/models`);
|
|
16
|
+
|
|
17
|
+
const isHttps = url.protocol === 'https:';
|
|
18
|
+
const mod = isHttps ? https : http;
|
|
19
|
+
|
|
20
|
+
const options = {
|
|
21
|
+
hostname: url.hostname,
|
|
22
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
23
|
+
path: url.pathname + url.search,
|
|
24
|
+
method: 'GET',
|
|
25
|
+
headers: {
|
|
26
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
27
|
+
'Accept': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const req = mod.request(options, (res) => {
|
|
32
|
+
let body = '';
|
|
33
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
34
|
+
res.on('end', () => {
|
|
35
|
+
if (res.statusCode !== 200) {
|
|
36
|
+
process.stderr.write(`API error ${res.statusCode}: ${body.slice(0, 500)}\n`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const data = JSON.parse(body);
|
|
42
|
+
// OpenAI-compatible /v1/models returns { data: [...] }
|
|
43
|
+
let models = data.data || data.models || data || [];
|
|
44
|
+
if (!Array.isArray(models)) models = [];
|
|
45
|
+
|
|
46
|
+
// Normalize each model to { id, provider, name, created }
|
|
47
|
+
const normalized = models.map((m) => {
|
|
48
|
+
const id = m.id || m.model || '';
|
|
49
|
+
const parts = id.split('/');
|
|
50
|
+
const provider = parts.length > 1 ? parts[0] : (m.owned_by || 'unknown');
|
|
51
|
+
const name = parts.length > 1 ? parts.slice(1).join('/') : id;
|
|
52
|
+
return {
|
|
53
|
+
id,
|
|
54
|
+
provider,
|
|
55
|
+
name,
|
|
56
|
+
created: m.created || 0,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Apply filter if provided
|
|
61
|
+
const filtered = filter
|
|
62
|
+
? normalized.filter((m) => m.id.toLowerCase().includes(filter.toLowerCase()))
|
|
63
|
+
: normalized;
|
|
64
|
+
|
|
65
|
+
// Sort by provider, then name
|
|
66
|
+
filtered.sort((a, b) => {
|
|
67
|
+
if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);
|
|
68
|
+
return a.name.localeCompare(b.name);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
process.stdout.write(JSON.stringify({ models: filtered }) + '\n');
|
|
72
|
+
} catch (err) {
|
|
73
|
+
process.stderr.write(`Failed to parse response: ${err.message}\n`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
req.on('error', (err) => {
|
|
80
|
+
process.stderr.write(`Network error: ${err.message}\n`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
req.end();
|
package/lib/phases.sh
CHANGED
|
@@ -216,6 +216,7 @@ _select_context_docs() {
|
|
|
216
216
|
_select_agents() {
|
|
217
217
|
SELECTED_AGENT_INDICES=()
|
|
218
218
|
SELECTED_GATEWAY_INDICES=()
|
|
219
|
+
SELECTED_CUSTOM_GATEWAY_INDICES=()
|
|
219
220
|
|
|
220
221
|
# Build unified item list: CLI agents, separator, gateway models
|
|
221
222
|
local item_labels=()
|
|
@@ -262,6 +263,18 @@ _select_agents() {
|
|
|
262
263
|
[ "$len" -gt "$max_label_len" ] && max_label_len=$len
|
|
263
264
|
done
|
|
264
265
|
|
|
266
|
+
# Custom model entry point
|
|
267
|
+
item_labels+=("+ Add custom model...")
|
|
268
|
+
item_meta+=("type provider/model-name")
|
|
269
|
+
item_types+=("custom_add")
|
|
270
|
+
item_sources+=("-1")
|
|
271
|
+
local len=${#item_labels[-1]}
|
|
272
|
+
[ "$len" -gt "$max_label_len" ] && max_label_len=$len
|
|
273
|
+
|
|
274
|
+
# Track custom models added during this session
|
|
275
|
+
local custom_gateway_names=()
|
|
276
|
+
local custom_gateway_models=()
|
|
277
|
+
|
|
265
278
|
local count=${#item_labels[@]}
|
|
266
279
|
|
|
267
280
|
# State: pre-select CLI agents, gateway unselected
|
|
@@ -299,6 +312,10 @@ _select_agents() {
|
|
|
299
312
|
fi
|
|
300
313
|
local marker=" "
|
|
301
314
|
[ "$cursor" -eq "$i" ] && marker="> "
|
|
315
|
+
if [ "${item_types[$i]}" = "custom_add" ]; then
|
|
316
|
+
printf "\\r\\033[K %s \\033[36m%s\\033[0m \\033[2m(%s)\\033[0m\\n" "$marker" "${item_labels[$i]}" "${item_meta[$i]}" >&2
|
|
317
|
+
continue
|
|
318
|
+
fi
|
|
302
319
|
local check="[ ]"
|
|
303
320
|
[ "${selected[$i]}" = "1" ] && check="[x]"
|
|
304
321
|
if [ -n "${item_meta[$i]}" ]; then
|
|
@@ -318,7 +335,7 @@ _select_agents() {
|
|
|
318
335
|
printf "\\r\\033[K %s\\033[2m%s\\033[0m\\n" "$submit_marker" "Submit (need at least 2)" >&2
|
|
319
336
|
fi
|
|
320
337
|
|
|
321
|
-
printf "\\r\\033[K\\n\\033[K \\033[2m↑↓ navigate · space
|
|
338
|
+
printf "\\r\\033[K\\n\\033[K \\033[2m↑↓ navigate · space toggle · enter submit/add · 'moxie models' to browse\\033[0m" >&2
|
|
322
339
|
}
|
|
323
340
|
|
|
324
341
|
printf "Select agents (at least 2). CLI agents pre-selected, gateway models available below:\\n\\n" >&2
|
|
@@ -361,16 +378,82 @@ _select_agents() {
|
|
|
361
378
|
esac
|
|
362
379
|
fi
|
|
363
380
|
elif [ "$key" = " " ]; then
|
|
364
|
-
if [ "$cursor" -lt "$count" ] && [ "${item_types[$cursor]}" != "separator" ]; then
|
|
381
|
+
if [ "$cursor" -lt "$count" ] && [ "${item_types[$cursor]}" != "separator" ] && [ "${item_types[$cursor]}" != "custom_add" ]; then
|
|
365
382
|
[ "${selected[$cursor]}" = "0" ] && selected[$cursor]=1 || selected[$cursor]=0
|
|
366
383
|
fi
|
|
367
384
|
elif [ "$key" = "" ]; then
|
|
368
385
|
if [ "$cursor" -eq "$count" ]; then
|
|
386
|
+
# Submit
|
|
369
387
|
local sel_count=0
|
|
370
388
|
for (( i = 0; i < count; i++ )); do
|
|
371
389
|
[ "${selected[$i]}" = "1" ] && sel_count=$(( sel_count + 1 ))
|
|
372
390
|
done
|
|
373
391
|
[ "$sel_count" -ge 2 ] && break
|
|
392
|
+
elif [ "${item_types[$cursor]}" = "custom_add" ]; then
|
|
393
|
+
# Prompt for custom model string
|
|
394
|
+
printf "\\033[?25h" >&2
|
|
395
|
+
stty echo icanon < /dev/tty 2>/dev/null
|
|
396
|
+
printf "\\n\\r\\033[K Model ID (provider/model-name): " >&2
|
|
397
|
+
local custom_model=""
|
|
398
|
+
read -r custom_model < /dev/tty
|
|
399
|
+
stty -echo -icanon min 1 < /dev/tty 2>/dev/null
|
|
400
|
+
printf "\\033[?25l" >&2
|
|
401
|
+
|
|
402
|
+
if [ -n "$custom_model" ] && [[ "$custom_model" == */* ]]; then
|
|
403
|
+
# Derive a short name from the model ID: provider-modelbase
|
|
404
|
+
local custom_provider="${custom_model%%/*}"
|
|
405
|
+
local custom_name_part="${custom_model#*/}"
|
|
406
|
+
local custom_slug="${custom_provider}-${custom_name_part}"
|
|
407
|
+
# Sanitize slug for TOML key
|
|
408
|
+
custom_slug=$(echo "$custom_slug" | tr '[:upper:]' '[:lower:]' | tr ' .' '-' | tr -cd 'a-z0-9-')
|
|
409
|
+
custom_slug="${custom_slug}-gw"
|
|
410
|
+
|
|
411
|
+
# Insert before the custom_add row (which is at current cursor)
|
|
412
|
+
local insert_at=$cursor
|
|
413
|
+
|
|
414
|
+
# Shift arrays to insert new item
|
|
415
|
+
local new_labels=() new_meta=() new_types=() new_sources=() new_selected=()
|
|
416
|
+
for (( i = 0; i < count; i++ )); do
|
|
417
|
+
if [ "$i" -eq "$insert_at" ]; then
|
|
418
|
+
new_labels+=("$custom_name_part")
|
|
419
|
+
new_meta+=("$custom_model")
|
|
420
|
+
new_types+=("custom_gateway")
|
|
421
|
+
new_sources+=("${#custom_gateway_names[@]}")
|
|
422
|
+
new_selected+=(1)
|
|
423
|
+
fi
|
|
424
|
+
new_labels+=("${item_labels[$i]}")
|
|
425
|
+
new_meta+=("${item_meta[$i]}")
|
|
426
|
+
new_types+=("${item_types[$i]}")
|
|
427
|
+
new_sources+=("${item_sources[$i]}")
|
|
428
|
+
new_selected+=("${selected[$i]}")
|
|
429
|
+
done
|
|
430
|
+
|
|
431
|
+
item_labels=("${new_labels[@]}")
|
|
432
|
+
item_meta=("${new_meta[@]}")
|
|
433
|
+
item_types=("${new_types[@]}")
|
|
434
|
+
item_sources=("${new_sources[@]}")
|
|
435
|
+
selected=("${new_selected[@]}")
|
|
436
|
+
|
|
437
|
+
custom_gateway_names+=("$custom_slug")
|
|
438
|
+
custom_gateway_models+=("$custom_model")
|
|
439
|
+
|
|
440
|
+
count=${#item_labels[@]}
|
|
441
|
+
jump_back=$(( count + 3 ))
|
|
442
|
+
|
|
443
|
+
# Update max_label_len if needed
|
|
444
|
+
local len=${#custom_name_part}
|
|
445
|
+
[ "$len" -gt "$max_label_len" ] && max_label_len=$len
|
|
446
|
+
sep=""
|
|
447
|
+
for (( i = 0; i < max_label_len + 40; i++ )); do sep="${sep}-"; done
|
|
448
|
+
else
|
|
449
|
+
# Invalid — erase the prompt line
|
|
450
|
+
if [ -n "$custom_model" ]; then
|
|
451
|
+
printf "\\r\\033[K \\033[31mInvalid format. Use provider/model-name (e.g. anthropic/claude-sonnet-4-6)\\033[0m" >&2
|
|
452
|
+
sleep 1
|
|
453
|
+
fi
|
|
454
|
+
fi
|
|
455
|
+
# Erase the prompt lines and re-render
|
|
456
|
+
printf "\\033[2A" >&2
|
|
374
457
|
elif [ "${item_types[$cursor]}" != "separator" ]; then
|
|
375
458
|
[ "${selected[$cursor]}" = "0" ] && selected[$cursor]=1 || selected[$cursor]=0
|
|
376
459
|
fi
|
|
@@ -387,6 +470,10 @@ _select_agents() {
|
|
|
387
470
|
trap - INT TERM
|
|
388
471
|
printf "\\n\\n" >&2
|
|
389
472
|
|
|
473
|
+
# Export custom models for config writer
|
|
474
|
+
CUSTOM_GATEWAY_NAMES=("${custom_gateway_names[@]}")
|
|
475
|
+
CUSTOM_GATEWAY_MODELS=("${custom_gateway_models[@]}")
|
|
476
|
+
|
|
390
477
|
# Collect selections by type
|
|
391
478
|
local has_gateway=0
|
|
392
479
|
for (( i = 0; i < count; i++ )); do
|
|
@@ -397,6 +484,10 @@ _select_agents() {
|
|
|
397
484
|
SELECTED_GATEWAY_INDICES+=("${item_sources[$i]}")
|
|
398
485
|
has_gateway=1
|
|
399
486
|
;;
|
|
487
|
+
custom_gateway)
|
|
488
|
+
SELECTED_CUSTOM_GATEWAY_INDICES+=("${item_sources[$i]}")
|
|
489
|
+
has_gateway=1
|
|
490
|
+
;;
|
|
400
491
|
esac
|
|
401
492
|
done
|
|
402
493
|
|
|
@@ -406,6 +497,7 @@ _select_agents() {
|
|
|
406
497
|
gateway_store_key "vercel-ai-gateway" || {
|
|
407
498
|
echo "ERROR: Failed to store gateway key. Gateway models will not work." >&2
|
|
408
499
|
SELECTED_GATEWAY_INDICES=()
|
|
500
|
+
SELECTED_CUSTOM_GATEWAY_INDICES=()
|
|
409
501
|
}
|
|
410
502
|
fi
|
|
411
503
|
}
|
|
@@ -424,7 +516,10 @@ path = "spec.md"
|
|
|
424
516
|
HEADER
|
|
425
517
|
|
|
426
518
|
# Gateway section (if any gateway models selected)
|
|
427
|
-
|
|
519
|
+
local has_any_gw=0
|
|
520
|
+
[ ${#SELECTED_GATEWAY_INDICES[@]} -gt 0 ] && has_any_gw=1
|
|
521
|
+
[ ${#SELECTED_CUSTOM_GATEWAY_INDICES[@]} -gt 0 ] && has_any_gw=1
|
|
522
|
+
if [ "$has_any_gw" = "1" ]; then
|
|
428
523
|
local run_id
|
|
429
524
|
run_id="run-$(date +%Y%m%d-%H%M%S)-$$"
|
|
430
525
|
cat >> "$config_file" <<GATEWAY
|
|
@@ -458,7 +553,7 @@ AGENT
|
|
|
458
553
|
done
|
|
459
554
|
fi
|
|
460
555
|
|
|
461
|
-
# Gateway agents
|
|
556
|
+
# Gateway agents (built-in)
|
|
462
557
|
for idx in "${SELECTED_GATEWAY_INDICES[@]}"; do
|
|
463
558
|
local name="${KNOWN_GATEWAY_NAMES[$idx]}"
|
|
464
559
|
local model="${KNOWN_GATEWAY_MODELS[$idx]}"
|
|
@@ -468,6 +563,20 @@ type = "gateway"
|
|
|
468
563
|
model = "${model}"
|
|
469
564
|
order = ${order}
|
|
470
565
|
|
|
566
|
+
GWAGENT
|
|
567
|
+
order=$((order + 1))
|
|
568
|
+
done
|
|
569
|
+
|
|
570
|
+
# Custom gateway agents (added during init)
|
|
571
|
+
for idx in "${SELECTED_CUSTOM_GATEWAY_INDICES[@]}"; do
|
|
572
|
+
local name="${CUSTOM_GATEWAY_NAMES[$idx]}"
|
|
573
|
+
local model="${CUSTOM_GATEWAY_MODELS[$idx]}"
|
|
574
|
+
cat >> "$config_file" <<GWAGENT
|
|
575
|
+
[agents.${name}]
|
|
576
|
+
type = "gateway"
|
|
577
|
+
model = "${model}"
|
|
578
|
+
order = ${order}
|
|
579
|
+
|
|
471
580
|
GWAGENT
|
|
472
581
|
order=$((order + 1))
|
|
473
582
|
done
|
|
@@ -1487,11 +1596,27 @@ cmd_status() {
|
|
|
1487
1596
|
echo "Context: $ctx_count document(s) in .moxie/context/"
|
|
1488
1597
|
fi
|
|
1489
1598
|
|
|
1490
|
-
# Quick agent health check
|
|
1599
|
+
# Quick agent health check (includes persisted degradation)
|
|
1600
|
+
local deg_file="$MOXIE_DIR/degraded.json"
|
|
1491
1601
|
local agent_health=""
|
|
1492
1602
|
local all_healthy=1
|
|
1603
|
+
local any_degraded=0
|
|
1493
1604
|
for name in "${AGENT_NAMES[@]}"; do
|
|
1494
|
-
if
|
|
1605
|
+
# Check if agent is degraded (persisted across phases)
|
|
1606
|
+
local is_deg=0
|
|
1607
|
+
if [ -f "$deg_file" ]; then
|
|
1608
|
+
is_deg=$(python3 -c "
|
|
1609
|
+
import json
|
|
1610
|
+
with open('$deg_file') as f:
|
|
1611
|
+
d = json.load(f)
|
|
1612
|
+
print('1' if d.get('$name', False) else '0')
|
|
1613
|
+
" 2>/dev/null)
|
|
1614
|
+
fi
|
|
1615
|
+
|
|
1616
|
+
if [ "$is_deg" = "1" ]; then
|
|
1617
|
+
agent_health="$agent_health $name[DEGRADED]"
|
|
1618
|
+
any_degraded=1
|
|
1619
|
+
elif _is_gateway_agent "$name"; then
|
|
1495
1620
|
if command -v node &>/dev/null && gateway_has_key "vercel-ai-gateway"; then
|
|
1496
1621
|
agent_health="$agent_health $name[ok]"
|
|
1497
1622
|
else
|
|
@@ -1515,6 +1640,9 @@ cmd_status() {
|
|
|
1515
1640
|
if [ "$all_healthy" = "0" ]; then
|
|
1516
1641
|
echo " (some agents missing — run 'moxie doctor' for details)"
|
|
1517
1642
|
fi
|
|
1643
|
+
if [ "$any_degraded" = "1" ]; then
|
|
1644
|
+
echo " (degraded agents are skipped — rm .moxie/degraded.json to reset)"
|
|
1645
|
+
fi
|
|
1518
1646
|
echo ""
|
|
1519
1647
|
|
|
1520
1648
|
for phase in "${PHASES[@]}"; do
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zachjxyz/moxie",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Run multiple AI coding agents through spec-driven phases with quorum convergence. Supports CLI agents (Claude, Codex, Qwen, Aider, Goose, Amp, Cline, Roo) and Vercel AI Gateway models.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"moxie": "bin/moxie"
|