@zachjxyz/moxie 0.4.5 → 0.4.9
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 +3 -2
- package/lib/agents.sh +405 -8
- package/lib/phases.sh +293 -76
- package/package.json +1 -1
package/bin/moxie
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
set -euo pipefail
|
|
19
19
|
|
|
20
|
-
MOXIE_VERSION="0.4.
|
|
20
|
+
MOXIE_VERSION="0.4.9"
|
|
21
21
|
# Resolve symlinks (npm installs bin as a symlink)
|
|
22
22
|
_self="$0"
|
|
23
23
|
while [ -L "$_self" ]; do
|
|
@@ -66,7 +66,7 @@ Commands:
|
|
|
66
66
|
cost Token usage breakdown by phase and agent
|
|
67
67
|
logs Tail or view logs for a phase
|
|
68
68
|
models List available AI Gateway models
|
|
69
|
-
agents List configured agents
|
|
69
|
+
agents List configured agents (agents swap to change models)
|
|
70
70
|
doctor Check agent CLIs, auth, and project health
|
|
71
71
|
version Print version
|
|
72
72
|
help Show this help
|
|
@@ -85,6 +85,7 @@ Usage:
|
|
|
85
85
|
moxie cost Show token usage summary
|
|
86
86
|
moxie models List all AI Gateway models
|
|
87
87
|
moxie models -f anthropic Filter models by provider/name
|
|
88
|
+
moxie agents swap Add/remove/swap gateway models
|
|
88
89
|
moxie doctor Check agents and dependencies
|
|
89
90
|
|
|
90
91
|
Pipeline: rfc → audit → fix → plan → build
|
package/lib/agents.sh
CHANGED
|
@@ -537,9 +537,121 @@ dispatch_logged() {
|
|
|
537
537
|
_record_turn_health "$agent" "$logfile" || return $?
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
+
# ---- Rewrite agents section of config.toml ----
|
|
541
|
+
# Preserves [spec], [gateway], [settings] etc. Replaces all [agents.*] blocks.
|
|
542
|
+
|
|
543
|
+
_rewrite_config_agents() {
|
|
544
|
+
# Reads from caller's arrays: cli_agents, cli_cmds, new_gw_names, new_gw_models
|
|
545
|
+
# Use python3 to surgically rewrite just the agents section
|
|
546
|
+
python3 -c "
|
|
547
|
+
import sys, re
|
|
548
|
+
|
|
549
|
+
config_path = sys.argv[1]
|
|
550
|
+
cli_names = sys.argv[2].split('|') if sys.argv[2] else []
|
|
551
|
+
cli_cmds = sys.argv[3].split('|') if sys.argv[3] else []
|
|
552
|
+
gw_names = sys.argv[4].split('|') if sys.argv[4] else []
|
|
553
|
+
gw_models = sys.argv[5].split('|') if sys.argv[5] else []
|
|
554
|
+
|
|
555
|
+
with open(config_path, 'r') as f:
|
|
556
|
+
content = f.read()
|
|
557
|
+
|
|
558
|
+
# Remove all [agents.*] blocks (including their content up to the next section)
|
|
559
|
+
cleaned = re.sub(r'\[agents\.[^\]]+\]\n(?:[^\[]*\n)*', '', content)
|
|
560
|
+
|
|
561
|
+
# Remove old agent comment headers (may span 1-3 lines)
|
|
562
|
+
cleaned = re.sub(r'# Agent definitions[^\n]*\n(?:#[^\n]*\n)*', '', cleaned)
|
|
563
|
+
|
|
564
|
+
# Collapse excessive blank lines
|
|
565
|
+
cleaned = re.sub(r'\n{3,}', '\n\n', cleaned)
|
|
566
|
+
|
|
567
|
+
# Ensure gateway section exists if we have gateway models
|
|
568
|
+
if gw_names and '[gateway]' not in cleaned:
|
|
569
|
+
import datetime, os
|
|
570
|
+
run_id = f'run-{datetime.datetime.now().strftime(\"%Y%m%d-%H%M%S\")}-{os.getpid()}'
|
|
571
|
+
gw_section = f'''[gateway]
|
|
572
|
+
endpoint = \"https://ai-gateway.vercel.sh\"
|
|
573
|
+
run_id = \"{run_id}\"
|
|
574
|
+
|
|
575
|
+
'''
|
|
576
|
+
# Insert before [settings] or at end
|
|
577
|
+
if '[settings]' in cleaned:
|
|
578
|
+
cleaned = cleaned.replace('[settings]', gw_section + '[settings]')
|
|
579
|
+
else:
|
|
580
|
+
cleaned = cleaned.rstrip() + '\n\n' + gw_section
|
|
581
|
+
|
|
582
|
+
# Build new agents section
|
|
583
|
+
agents_lines = []
|
|
584
|
+
agents_lines.append('# Agent definitions — order is the default rotation sequence.')
|
|
585
|
+
agents_lines.append('')
|
|
586
|
+
|
|
587
|
+
order = 1
|
|
588
|
+
for name, cmd in zip(cli_names, cli_cmds):
|
|
589
|
+
if not name:
|
|
590
|
+
continue
|
|
591
|
+
agents_lines.append(f'[agents.{name}]')
|
|
592
|
+
agents_lines.append(f\"command = '{cmd}'\")
|
|
593
|
+
agents_lines.append(f'order = {order}')
|
|
594
|
+
agents_lines.append('')
|
|
595
|
+
order += 1
|
|
596
|
+
|
|
597
|
+
for name, model in zip(gw_names, gw_models):
|
|
598
|
+
if not name:
|
|
599
|
+
continue
|
|
600
|
+
agents_lines.append(f'[agents.{name}]')
|
|
601
|
+
agents_lines.append('type = \"gateway\"')
|
|
602
|
+
agents_lines.append(f'model = \"{model}\"')
|
|
603
|
+
agents_lines.append(f'order = {order}')
|
|
604
|
+
agents_lines.append('')
|
|
605
|
+
order += 1
|
|
606
|
+
|
|
607
|
+
agents_block = '\n'.join(agents_lines)
|
|
608
|
+
|
|
609
|
+
# Insert agents block before [settings] or at end
|
|
610
|
+
if '[settings]' in cleaned:
|
|
611
|
+
cleaned = cleaned.replace('[settings]', agents_block + '\n[settings]')
|
|
612
|
+
else:
|
|
613
|
+
cleaned = cleaned.rstrip() + '\n\n' + agents_block
|
|
614
|
+
|
|
615
|
+
with open(config_path, 'w') as f:
|
|
616
|
+
f.write(cleaned)
|
|
617
|
+
" "$MOXIE_CONFIG" \
|
|
618
|
+
"$(IFS='|'; echo "${cli_agents[*]}")" \
|
|
619
|
+
"$(IFS='|'; echo "${cli_cmds[*]}")" \
|
|
620
|
+
"$(IFS='|'; echo "${new_gw_names[*]}")" \
|
|
621
|
+
"$(IFS='|'; echo "${new_gw_models[*]}")"
|
|
622
|
+
}
|
|
623
|
+
|
|
540
624
|
# ---- List agents ----
|
|
541
625
|
|
|
542
626
|
cmd_agents() {
|
|
627
|
+
local subcmd="${1:-list}"
|
|
628
|
+
shift 2>/dev/null || true
|
|
629
|
+
|
|
630
|
+
case "$subcmd" in
|
|
631
|
+
list|ls) _cmd_agents_list "$@" ;;
|
|
632
|
+
swap) _cmd_agents_swap "$@" ;;
|
|
633
|
+
help|--help|-h)
|
|
634
|
+
cat <<'EOF'
|
|
635
|
+
Usage: moxie agents [subcommand]
|
|
636
|
+
|
|
637
|
+
Subcommands:
|
|
638
|
+
list List configured agents (default)
|
|
639
|
+
swap Add, remove, or replace gateway models
|
|
640
|
+
|
|
641
|
+
Examples:
|
|
642
|
+
moxie agents Show current agent lineup
|
|
643
|
+
moxie agents swap Interactively swap gateway models
|
|
644
|
+
EOF
|
|
645
|
+
;;
|
|
646
|
+
*)
|
|
647
|
+
echo "Unknown subcommand: $subcmd" >&2
|
|
648
|
+
echo "Run 'moxie agents help' for usage." >&2
|
|
649
|
+
return 1
|
|
650
|
+
;;
|
|
651
|
+
esac
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
_cmd_agents_list() {
|
|
543
655
|
require_moxie_project
|
|
544
656
|
load_agents
|
|
545
657
|
|
|
@@ -552,6 +664,297 @@ cmd_agents() {
|
|
|
552
664
|
echo " $((i + 1)). $name"
|
|
553
665
|
echo " $cmd"
|
|
554
666
|
done
|
|
667
|
+
echo ""
|
|
668
|
+
echo "Swap gateway models anytime: moxie agents swap"
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
# ---- Swap gateway models interactively ----
|
|
672
|
+
|
|
673
|
+
_cmd_agents_swap() {
|
|
674
|
+
require_moxie_project
|
|
675
|
+
load_agents
|
|
676
|
+
|
|
677
|
+
if ! command -v node &>/dev/null; then
|
|
678
|
+
echo "ERROR: node not found on PATH. Required for gateway API calls." >&2
|
|
679
|
+
return 1
|
|
680
|
+
fi
|
|
681
|
+
|
|
682
|
+
# Collect current gateway models from config
|
|
683
|
+
local current_gw_names=()
|
|
684
|
+
local current_gw_models=()
|
|
685
|
+
local cli_agents=()
|
|
686
|
+
local cli_cmds=()
|
|
687
|
+
for i in "${!AGENT_NAMES[@]}"; do
|
|
688
|
+
local name="${AGENT_NAMES[$i]}"
|
|
689
|
+
if _is_gateway_agent "$name"; then
|
|
690
|
+
local model
|
|
691
|
+
model=$(_agent_model "$name")
|
|
692
|
+
current_gw_names+=("$name")
|
|
693
|
+
current_gw_models+=("$model")
|
|
694
|
+
else
|
|
695
|
+
local cmd
|
|
696
|
+
cmd=$(_agent_cmd "$name")
|
|
697
|
+
cli_agents+=("$name")
|
|
698
|
+
cli_cmds+=("$cmd")
|
|
699
|
+
fi
|
|
700
|
+
done
|
|
701
|
+
|
|
702
|
+
echo "Current gateway models:"
|
|
703
|
+
if [ ${#current_gw_names[@]} -eq 0 ]; then
|
|
704
|
+
echo " (none)"
|
|
705
|
+
else
|
|
706
|
+
for i in "${!current_gw_names[@]}"; do
|
|
707
|
+
echo " ${current_gw_names[$i]} — ${current_gw_models[$i]}"
|
|
708
|
+
done
|
|
709
|
+
fi
|
|
710
|
+
echo ""
|
|
711
|
+
|
|
712
|
+
# Ensure we have an API key
|
|
713
|
+
local api_key=""
|
|
714
|
+
if gateway_has_key "vercel-ai-gateway"; then
|
|
715
|
+
api_key=$(gateway_get_key "vercel-ai-gateway" 2>/dev/null) || true
|
|
716
|
+
fi
|
|
717
|
+
if [ -z "$api_key" ]; then
|
|
718
|
+
printf "Gateway API key needed to search models.\\n\\n" >&2
|
|
719
|
+
gateway_store_key "vercel-ai-gateway" || {
|
|
720
|
+
echo "ERROR: Failed to store key." >&2
|
|
721
|
+
return 1
|
|
722
|
+
}
|
|
723
|
+
api_key=$(gateway_get_key "vercel-ai-gateway" 2>/dev/null) || {
|
|
724
|
+
echo "ERROR: Failed to retrieve key." >&2
|
|
725
|
+
return 1
|
|
726
|
+
}
|
|
727
|
+
fi
|
|
728
|
+
|
|
729
|
+
local endpoint="https://ai-gateway.vercel.sh"
|
|
730
|
+
if [ -f "$MOXIE_CONFIG" ]; then
|
|
731
|
+
local cfg_ep
|
|
732
|
+
cfg_ep=$(toml_get "$MOXIE_CONFIG" "gateway.endpoint" "") || true
|
|
733
|
+
[ -n "$cfg_ep" ] && endpoint="$cfg_ep"
|
|
734
|
+
fi
|
|
735
|
+
|
|
736
|
+
# Fetch model catalog
|
|
737
|
+
printf "Fetching models from AI Gateway..." >&2
|
|
738
|
+
local models_json=""
|
|
739
|
+
models_json=$(GATEWAY_API_KEY="$api_key" node "$MOXIE_LIB/gateway-models.mjs" "$endpoint" "" 2>/dev/null) || true
|
|
740
|
+
printf "\\r\\033[K" >&2
|
|
741
|
+
|
|
742
|
+
if [ -z "$models_json" ]; then
|
|
743
|
+
echo "ERROR: Failed to fetch models. Check your API key or network." >&2
|
|
744
|
+
return 1
|
|
745
|
+
fi
|
|
746
|
+
|
|
747
|
+
# Parse into arrays
|
|
748
|
+
local all_model_ids=()
|
|
749
|
+
local all_model_labels=()
|
|
750
|
+
eval "$(python3 -c "
|
|
751
|
+
import json, sys, shlex
|
|
752
|
+
data = json.loads(sys.stdin.read())
|
|
753
|
+
models = data.get('models', [])
|
|
754
|
+
ids = [m['id'] for m in models]
|
|
755
|
+
labels = [m.get('name', m['id']) for m in models]
|
|
756
|
+
print('all_model_ids=(' + ' '.join(shlex.quote(x) for x in ids) + ')')
|
|
757
|
+
print('all_model_labels=(' + ' '.join(shlex.quote(x) for x in labels) + ')')
|
|
758
|
+
" <<< "$models_json" 2>/dev/null)"
|
|
759
|
+
|
|
760
|
+
if [ ${#all_model_ids[@]} -eq 0 ]; then
|
|
761
|
+
echo "ERROR: No models returned from gateway." >&2
|
|
762
|
+
return 1
|
|
763
|
+
fi
|
|
764
|
+
|
|
765
|
+
# Pre-select currently configured models
|
|
766
|
+
local sselected=()
|
|
767
|
+
for (( i = 0; i < ${#all_model_ids[@]}; i++ )); do
|
|
768
|
+
local _matched=0
|
|
769
|
+
for cm in "${current_gw_models[@]}"; do
|
|
770
|
+
if [ "${all_model_ids[$i]}" = "$cm" ]; then
|
|
771
|
+
_matched=1
|
|
772
|
+
break
|
|
773
|
+
fi
|
|
774
|
+
done
|
|
775
|
+
sselected+=("$_matched")
|
|
776
|
+
done
|
|
777
|
+
|
|
778
|
+
# Interactive search TUI (same as init picker)
|
|
779
|
+
local search=""
|
|
780
|
+
local scursor=0
|
|
781
|
+
local max_visible=15
|
|
782
|
+
|
|
783
|
+
_swap_render() {
|
|
784
|
+
local filtered=()
|
|
785
|
+
local lc_search
|
|
786
|
+
lc_search=$(echo "$search" | tr '[:upper:]' '[:lower:]')
|
|
787
|
+
for (( i = 0; i < ${#all_model_ids[@]}; i++ )); do
|
|
788
|
+
if [ -z "$search" ]; then
|
|
789
|
+
filtered+=("$i")
|
|
790
|
+
else
|
|
791
|
+
local lc_id
|
|
792
|
+
lc_id=$(echo "${all_model_ids[$i]}" | tr '[:upper:]' '[:lower:]')
|
|
793
|
+
[[ "$lc_id" == *"$lc_search"* ]] && filtered+=("$i")
|
|
794
|
+
fi
|
|
795
|
+
done
|
|
796
|
+
|
|
797
|
+
local fcount=${#filtered[@]}
|
|
798
|
+
[ "$scursor" -ge "$fcount" ] && scursor=$(( fcount > 0 ? fcount - 1 : 0 ))
|
|
799
|
+
[ "$scursor" -lt 0 ] && scursor=0
|
|
800
|
+
|
|
801
|
+
local vstart=0
|
|
802
|
+
if [ "$scursor" -ge "$max_visible" ]; then
|
|
803
|
+
vstart=$(( scursor - max_visible + 1 ))
|
|
804
|
+
fi
|
|
805
|
+
local vend=$(( vstart + max_visible ))
|
|
806
|
+
[ "$vend" -gt "$fcount" ] && vend=$fcount
|
|
807
|
+
|
|
808
|
+
local sel_count=0
|
|
809
|
+
for (( i = 0; i < ${#sselected[@]}; i++ )); do
|
|
810
|
+
[ "${sselected[$i]}" = "1" ] && sel_count=$(( sel_count + 1 ))
|
|
811
|
+
done
|
|
812
|
+
|
|
813
|
+
printf "\\r\\033[K \\033[1mSwap gateway models\\033[0m (%d available, %d selected)\\n" "$fcount" "$sel_count" >&2
|
|
814
|
+
printf "\\r\\033[K Search: \\033[36m%s\\033[0m\\033[2m|\\033[0m\\n\\n" "$search" >&2
|
|
815
|
+
|
|
816
|
+
local rendered=0
|
|
817
|
+
for (( vi = vstart; vi < vend; vi++ )); do
|
|
818
|
+
local ri=${filtered[$vi]}
|
|
819
|
+
local mid="${all_model_ids[$ri]}"
|
|
820
|
+
local marker=" "
|
|
821
|
+
[ "$vi" -eq "$scursor" ] && marker="> "
|
|
822
|
+
local check="[ ]"
|
|
823
|
+
[ "${sselected[$ri]}" = "1" ] && check="[x]"
|
|
824
|
+
printf "\\r\\033[K %s%s %s\\n" "$marker" "$check" "$mid" >&2
|
|
825
|
+
rendered=$(( rendered + 1 ))
|
|
826
|
+
done
|
|
827
|
+
|
|
828
|
+
while [ "$rendered" -lt "$max_visible" ]; do
|
|
829
|
+
printf "\\r\\033[K\\n" >&2
|
|
830
|
+
rendered=$(( rendered + 1 ))
|
|
831
|
+
done
|
|
832
|
+
|
|
833
|
+
if [ "$vstart" -gt 0 ] || [ "$vend" -lt "$fcount" ]; then
|
|
834
|
+
printf "\\r\\033[K \\033[2m(%d-%d of %d · scroll for more)\\033[0m\\n" "$(( vstart + 1 ))" "$vend" "$fcount" >&2
|
|
835
|
+
else
|
|
836
|
+
printf "\\r\\033[K\\n" >&2
|
|
837
|
+
fi
|
|
838
|
+
|
|
839
|
+
printf "\\r\\033[K \\033[2m↑↓ navigate · space select · enter confirm · esc cancel · type to filter\\033[0m" >&2
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
local swap_lines=$(( max_visible + 5 ))
|
|
843
|
+
|
|
844
|
+
printf "\\033[?25l" >&2
|
|
845
|
+
local old_stty
|
|
846
|
+
old_stty=$(stty -g < /dev/tty 2>/dev/null)
|
|
847
|
+
stty -echo -icanon min 1 < /dev/tty 2>/dev/null
|
|
848
|
+
|
|
849
|
+
_swap_cleanup() {
|
|
850
|
+
printf "\\033[?25h" >&2
|
|
851
|
+
stty "$old_stty" < /dev/tty 2>/dev/null
|
|
852
|
+
}
|
|
853
|
+
trap '_swap_cleanup' INT TERM
|
|
854
|
+
|
|
855
|
+
_swap_render
|
|
856
|
+
|
|
857
|
+
local swap_done=0
|
|
858
|
+
local swap_cancelled=0
|
|
859
|
+
while [ "$swap_done" = "0" ]; do
|
|
860
|
+
local skey
|
|
861
|
+
skey=$(dd bs=1 count=1 2>/dev/null < /dev/tty) || break
|
|
862
|
+
|
|
863
|
+
if [ "$skey" = $'\033' ]; then
|
|
864
|
+
local sbracket sdir
|
|
865
|
+
sbracket=$(dd bs=1 count=1 2>/dev/null < /dev/tty) || true
|
|
866
|
+
sdir=$(dd bs=1 count=1 2>/dev/null < /dev/tty) || true
|
|
867
|
+
if [ "$sbracket" = "[" ]; then
|
|
868
|
+
case "$sdir" in
|
|
869
|
+
A) scursor=$(( scursor - 1 )); [ "$scursor" -lt 0 ] && scursor=0 ;;
|
|
870
|
+
B) scursor=$(( scursor + 1 )) ;;
|
|
871
|
+
esac
|
|
872
|
+
else
|
|
873
|
+
swap_cancelled=1
|
|
874
|
+
swap_done=1
|
|
875
|
+
fi
|
|
876
|
+
elif [ "$skey" = " " ]; then
|
|
877
|
+
local ft=()
|
|
878
|
+
local lcs
|
|
879
|
+
lcs=$(echo "$search" | tr '[:upper:]' '[:lower:]')
|
|
880
|
+
for (( i = 0; i < ${#all_model_ids[@]}; i++ )); do
|
|
881
|
+
if [ -z "$search" ]; then
|
|
882
|
+
ft+=("$i")
|
|
883
|
+
else
|
|
884
|
+
local lci
|
|
885
|
+
lci=$(echo "${all_model_ids[$i]}" | tr '[:upper:]' '[:lower:]')
|
|
886
|
+
[[ "$lci" == *"$lcs"* ]] && ft+=("$i")
|
|
887
|
+
fi
|
|
888
|
+
done
|
|
889
|
+
if [ "$scursor" -lt "${#ft[@]}" ]; then
|
|
890
|
+
local tidx=${ft[$scursor]}
|
|
891
|
+
[ "${sselected[$tidx]}" = "0" ] && sselected[$tidx]=1 || sselected[$tidx]=0
|
|
892
|
+
fi
|
|
893
|
+
elif [ "$skey" = "" ]; then
|
|
894
|
+
swap_done=1
|
|
895
|
+
elif [ "$skey" = $'\177' ] || [ "$skey" = $'\010' ]; then
|
|
896
|
+
if [ -n "$search" ]; then
|
|
897
|
+
search="${search%?}"
|
|
898
|
+
scursor=0
|
|
899
|
+
fi
|
|
900
|
+
else
|
|
901
|
+
if [[ "$skey" =~ [[:print:]] ]]; then
|
|
902
|
+
search="${search}${skey}"
|
|
903
|
+
scursor=0
|
|
904
|
+
fi
|
|
905
|
+
fi
|
|
906
|
+
|
|
907
|
+
printf "\\033[%dA" "$swap_lines" >&2
|
|
908
|
+
_swap_render
|
|
909
|
+
done
|
|
910
|
+
|
|
911
|
+
_swap_cleanup
|
|
912
|
+
trap - INT TERM
|
|
913
|
+
printf "\\n\\n" >&2
|
|
914
|
+
|
|
915
|
+
if [ "$swap_cancelled" = "1" ]; then
|
|
916
|
+
echo "Cancelled — no changes made."
|
|
917
|
+
return 0
|
|
918
|
+
fi
|
|
919
|
+
|
|
920
|
+
# Collect selected models
|
|
921
|
+
local new_gw_names=()
|
|
922
|
+
local new_gw_models=()
|
|
923
|
+
for (( i = 0; i < ${#sselected[@]}; i++ )); do
|
|
924
|
+
[ "${sselected[$i]}" != "1" ] && continue
|
|
925
|
+
local mid="${all_model_ids[$i]}"
|
|
926
|
+
local prov="${mid%%/*}"
|
|
927
|
+
local mname="${mid#*/}"
|
|
928
|
+
local slug
|
|
929
|
+
slug=$(echo "${prov}-${mname}" | tr '[:upper:]' '[:lower:]' | tr ' .' '-' | tr -cd 'a-z0-9-')
|
|
930
|
+
slug="${slug}-gw"
|
|
931
|
+
new_gw_names+=("$slug")
|
|
932
|
+
new_gw_models+=("$mid")
|
|
933
|
+
done
|
|
934
|
+
|
|
935
|
+
local total_agents=$(( ${#cli_agents[@]} + ${#new_gw_names[@]} ))
|
|
936
|
+
if [ "$total_agents" -lt 2 ]; then
|
|
937
|
+
echo "ERROR: Need at least 2 agents total (${#cli_agents[@]} CLI + ${#new_gw_names[@]} gateway = $total_agents)." >&2
|
|
938
|
+
echo "Select more gateway models or install additional CLI agents." >&2
|
|
939
|
+
return 1
|
|
940
|
+
fi
|
|
941
|
+
|
|
942
|
+
# Rewrite config.toml — preserve everything before [agents.*], rewrite agents
|
|
943
|
+
_rewrite_config_agents
|
|
944
|
+
|
|
945
|
+
echo "Updated .moxie/config.toml:"
|
|
946
|
+
echo ""
|
|
947
|
+
local order=1
|
|
948
|
+
for i in "${!cli_agents[@]}"; do
|
|
949
|
+
echo " $order. ${cli_agents[$i]} (CLI)"
|
|
950
|
+
order=$((order + 1))
|
|
951
|
+
done
|
|
952
|
+
for i in "${!new_gw_names[@]}"; do
|
|
953
|
+
echo " $order. ${new_gw_names[$i]} — ${new_gw_models[$i]} (gateway)"
|
|
954
|
+
order=$((order + 1))
|
|
955
|
+
done
|
|
956
|
+
echo ""
|
|
957
|
+
echo "Total: $total_agents agents. Run 'moxie run' or 'moxie start' to go."
|
|
555
958
|
}
|
|
556
959
|
|
|
557
960
|
# ---- List available gateway models ----
|
|
@@ -631,9 +1034,6 @@ for m in models:
|
|
|
631
1034
|
p = m['provider']
|
|
632
1035
|
providers.setdefault(p, []).append(m)
|
|
633
1036
|
|
|
634
|
-
# Hardcoded models for marking
|
|
635
|
-
hardcoded = set('''$(printf '%s\n' "${KNOWN_GATEWAY_MODELS[@]}")'''.strip().split('\n'))
|
|
636
|
-
|
|
637
1037
|
total = len(models)
|
|
638
1038
|
filter_note = ' matching \"$filter\"' if '$filter' else ''
|
|
639
1039
|
print(f'Available gateway models ({total}{filter_note}):')
|
|
@@ -643,13 +1043,10 @@ for provider in sorted(providers.keys()):
|
|
|
643
1043
|
pmodels = providers[provider]
|
|
644
1044
|
print(f' \033[1m{provider}\033[0m ({len(pmodels)} models)')
|
|
645
1045
|
for m in pmodels:
|
|
646
|
-
|
|
647
|
-
print(f' {marker} {m[\"id\"]}')
|
|
1046
|
+
print(f' {m[\"id\"]}')
|
|
648
1047
|
print()
|
|
649
1048
|
|
|
650
|
-
print('
|
|
651
|
-
print()
|
|
652
|
-
print('To use any model, add it to .moxie/config.toml:')
|
|
1049
|
+
print('Use any model with moxie init (search picker) or add to .moxie/config.toml:')
|
|
653
1050
|
print(' [agents.my-model]')
|
|
654
1051
|
print(' type = \"gateway\"')
|
|
655
1052
|
print(' model = \"provider/model-name\"')
|
package/lib/phases.sh
CHANGED
|
@@ -246,29 +246,18 @@ _select_agents() {
|
|
|
246
246
|
[ "$len" -gt "$max_label_len" ] && max_label_len=$len
|
|
247
247
|
done
|
|
248
248
|
|
|
249
|
-
# Separator
|
|
249
|
+
# Separator + gateway search
|
|
250
250
|
item_labels+=("--- Vercel AI Gateway ---")
|
|
251
251
|
item_meta+=("")
|
|
252
252
|
item_types+=("separator")
|
|
253
253
|
item_sources+=("-1")
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
local label="${KNOWN_GATEWAY_LABELS[$idx]}"
|
|
258
|
-
item_labels+=("$label")
|
|
259
|
-
item_meta+=("${KNOWN_GATEWAY_MODELS[$idx]}")
|
|
260
|
-
item_types+=("gateway")
|
|
261
|
-
item_sources+=("$idx")
|
|
262
|
-
local len=${#label}
|
|
263
|
-
[ "$len" -gt "$max_label_len" ] && max_label_len=$len
|
|
264
|
-
done
|
|
265
|
-
|
|
266
|
-
# Custom model entry point
|
|
267
|
-
item_labels+=("+ Add custom model...")
|
|
268
|
-
item_meta+=("type provider/model-name")
|
|
255
|
+
item_labels+=("+ Search gateway models...")
|
|
256
|
+
item_meta+=("browse all available models")
|
|
269
257
|
item_types+=("custom_add")
|
|
270
258
|
item_sources+=("-1")
|
|
271
|
-
local
|
|
259
|
+
local _custom_label="+ Search gateway models..."
|
|
260
|
+
local len=${#_custom_label}
|
|
272
261
|
[ "$len" -gt "$max_label_len" ] && max_label_len=$len
|
|
273
262
|
|
|
274
263
|
# Track custom models added during this session
|
|
@@ -335,10 +324,10 @@ _select_agents() {
|
|
|
335
324
|
printf "\\r\\033[K %s\\033[2m%s\\033[0m\\n" "$submit_marker" "Submit (need at least 2)" >&2
|
|
336
325
|
fi
|
|
337
326
|
|
|
338
|
-
printf "\\r\\033[K\\n\\033[K \\033[2m↑↓ navigate · space toggle · enter submit/
|
|
327
|
+
printf "\\r\\033[K\\n\\033[K \\033[2m↑↓ navigate · space toggle · enter submit/search\\033[0m" >&2
|
|
339
328
|
}
|
|
340
329
|
|
|
341
|
-
printf "Select agents (at least 2).
|
|
330
|
+
printf "Select agents (at least 2). Search gateway models to add more:\\n\\n" >&2
|
|
342
331
|
printf "\\033[?25l" >&2
|
|
343
332
|
_agent_render
|
|
344
333
|
|
|
@@ -390,70 +379,297 @@ _select_agents() {
|
|
|
390
379
|
done
|
|
391
380
|
[ "$sel_count" -ge 2 ] && break
|
|
392
381
|
elif [ "${item_types[$cursor]}" = "custom_add" ]; then
|
|
393
|
-
#
|
|
382
|
+
# Live search picker — fetch models from gateway, filter interactively
|
|
383
|
+
stty "$old_stty" < /dev/tty 2>/dev/null
|
|
394
384
|
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
385
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
386
|
+
# Ensure we have an API key
|
|
387
|
+
local _gw_key=""
|
|
388
|
+
if gateway_has_key "vercel-ai-gateway"; then
|
|
389
|
+
_gw_key=$(gateway_get_key "vercel-ai-gateway" 2>/dev/null) || true
|
|
390
|
+
fi
|
|
391
|
+
if [ -z "$_gw_key" ]; then
|
|
392
|
+
printf "\\n Gateway API key needed to search models.\\n\\n" >&2
|
|
393
|
+
gateway_store_key "vercel-ai-gateway" || {
|
|
394
|
+
printf " \\033[31mFailed to store key. Skipping search.\\033[0m\\n" >&2
|
|
395
|
+
sleep 1
|
|
396
|
+
stty -echo -icanon min 1 < /dev/tty 2>/dev/null
|
|
397
|
+
printf "\\033[?25l" >&2
|
|
398
|
+
printf "\\033[%dA" "$jump_back" >&2
|
|
399
|
+
_agent_render
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
_gw_key=$(gateway_get_key "vercel-ai-gateway" 2>/dev/null) || true
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
# Fetch model list
|
|
406
|
+
printf "\\n Fetching models from AI Gateway..." >&2
|
|
407
|
+
local _models_json=""
|
|
408
|
+
_models_json=$(GATEWAY_API_KEY="$_gw_key" node "$MOXIE_LIB/gateway-models.mjs" "https://ai-gateway.vercel.sh" "" 2>/dev/null) || true
|
|
409
|
+
printf "\\r\\033[K" >&2
|
|
410
|
+
|
|
411
|
+
if [ -z "$_models_json" ]; then
|
|
412
|
+
printf " \\033[31mFailed to fetch models. Check your API key or network.\\033[0m\\n" >&2
|
|
413
|
+
sleep 2
|
|
414
|
+
stty -echo -icanon min 1 < /dev/tty 2>/dev/null
|
|
415
|
+
printf "\\033[?25l" >&2
|
|
416
|
+
printf "\\033[%dA" "$((jump_back + 2))" >&2
|
|
417
|
+
_agent_render
|
|
418
|
+
continue
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
# Parse into arrays using python3
|
|
422
|
+
local _all_model_ids=()
|
|
423
|
+
local _all_model_labels=()
|
|
424
|
+
eval "$(python3 -c "
|
|
425
|
+
import json, sys, shlex
|
|
426
|
+
data = json.loads(sys.stdin.read())
|
|
427
|
+
models = data.get('models', [])
|
|
428
|
+
ids = []
|
|
429
|
+
labels = []
|
|
430
|
+
for m in models:
|
|
431
|
+
mid = m['id']
|
|
432
|
+
ids.append(mid)
|
|
433
|
+
labels.append(m.get('name', mid))
|
|
434
|
+
# Output as bash array assignments
|
|
435
|
+
print('_all_model_ids=(' + ' '.join(shlex.quote(x) for x in ids) + ')')
|
|
436
|
+
print('_all_model_labels=(' + ' '.join(shlex.quote(x) for x in labels) + ')')
|
|
437
|
+
" <<< "$_models_json" 2>/dev/null)"
|
|
438
|
+
|
|
439
|
+
if [ ${#_all_model_ids[@]} -eq 0 ]; then
|
|
440
|
+
printf " \\033[31mNo models returned from gateway.\\033[0m\\n" >&2
|
|
441
|
+
sleep 1
|
|
442
|
+
stty -echo -icanon min 1 < /dev/tty 2>/dev/null
|
|
443
|
+
printf "\\033[?25l" >&2
|
|
444
|
+
printf "\\033[%dA" "$((jump_back + 2))" >&2
|
|
445
|
+
_agent_render
|
|
446
|
+
continue
|
|
447
|
+
fi
|
|
448
|
+
|
|
449
|
+
# Build set of already-selected model IDs
|
|
450
|
+
local _existing_ids=""
|
|
451
|
+
for _eid in "${custom_gateway_models[@]}"; do
|
|
452
|
+
_existing_ids="${_existing_ids}|${_eid}"
|
|
453
|
+
done
|
|
454
|
+
|
|
455
|
+
# Interactive search sub-TUI
|
|
456
|
+
local _search=""
|
|
457
|
+
local _scursor=0
|
|
458
|
+
local _sselected=()
|
|
459
|
+
for (( _si = 0; _si < ${#_all_model_ids[@]}; _si++ )); do
|
|
460
|
+
_sselected+=(0)
|
|
461
|
+
done
|
|
462
|
+
|
|
463
|
+
local _max_visible=15
|
|
464
|
+
|
|
465
|
+
_search_render() {
|
|
466
|
+
# Build filtered index list
|
|
467
|
+
local _filtered=()
|
|
468
|
+
local _lc_search
|
|
469
|
+
_lc_search=$(echo "$_search" | tr '[:upper:]' '[:lower:]')
|
|
470
|
+
for (( _si = 0; _si < ${#_all_model_ids[@]}; _si++ )); do
|
|
471
|
+
if [ -z "$_search" ]; then
|
|
472
|
+
_filtered+=("$_si")
|
|
473
|
+
else
|
|
474
|
+
local _lc_id
|
|
475
|
+
_lc_id=$(echo "${_all_model_ids[$_si]}" | tr '[:upper:]' '[:lower:]')
|
|
476
|
+
if [[ "$_lc_id" == *"$_lc_search"* ]]; then
|
|
477
|
+
_filtered+=("$_si")
|
|
478
|
+
fi
|
|
423
479
|
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
480
|
done
|
|
430
481
|
|
|
431
|
-
|
|
432
|
-
item_meta=("${new_meta[@]}")
|
|
433
|
-
item_types=("${new_types[@]}")
|
|
434
|
-
item_sources=("${new_sources[@]}")
|
|
435
|
-
selected=("${new_selected[@]}")
|
|
482
|
+
local _fcount=${#_filtered[@]}
|
|
436
483
|
|
|
437
|
-
|
|
438
|
-
|
|
484
|
+
# Clamp cursor
|
|
485
|
+
[ "$_scursor" -ge "$_fcount" ] && _scursor=$(( _fcount > 0 ? _fcount - 1 : 0 ))
|
|
486
|
+
[ "$_scursor" -lt 0 ] && _scursor=0
|
|
439
487
|
|
|
440
|
-
|
|
441
|
-
|
|
488
|
+
# Visible window
|
|
489
|
+
local _vstart=0
|
|
490
|
+
if [ "$_scursor" -ge "$_max_visible" ]; then
|
|
491
|
+
_vstart=$(( _scursor - _max_visible + 1 ))
|
|
492
|
+
fi
|
|
493
|
+
local _vend=$(( _vstart + _max_visible ))
|
|
494
|
+
[ "$_vend" -gt "$_fcount" ] && _vend=$_fcount
|
|
442
495
|
|
|
443
|
-
#
|
|
444
|
-
local
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
#
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
496
|
+
# Count selected
|
|
497
|
+
local _sel_count=0
|
|
498
|
+
for (( _si = 0; _si < ${#_sselected[@]}; _si++ )); do
|
|
499
|
+
[ "${_sselected[$_si]}" = "1" ] && _sel_count=$(( _sel_count + 1 ))
|
|
500
|
+
done
|
|
501
|
+
|
|
502
|
+
# Render
|
|
503
|
+
printf "\\r\\033[K \\033[1mSearch gateway models\\033[0m (%d available, %d selected)\\n" "$_fcount" "$_sel_count" >&2
|
|
504
|
+
printf "\\r\\033[K Search: \\033[36m%s\\033[0m\\033[2m|\\033[0m\\n\\n" "$_search" >&2
|
|
505
|
+
|
|
506
|
+
local _rendered=0
|
|
507
|
+
for (( _vi = _vstart; _vi < _vend; _vi++ )); do
|
|
508
|
+
local _ri=${_filtered[$_vi]}
|
|
509
|
+
local _mid="${_all_model_ids[$_ri]}"
|
|
510
|
+
local _marker=" "
|
|
511
|
+
[ "$_vi" -eq "$_scursor" ] && _marker="> "
|
|
512
|
+
local _check="[ ]"
|
|
513
|
+
[ "${_sselected[$_ri]}" = "1" ] && _check="[x]"
|
|
514
|
+
local _exists_marker=""
|
|
515
|
+
if [[ "$_existing_ids" == *"|${_mid}"* ]]; then
|
|
516
|
+
_exists_marker=" \\033[2m(added)\\033[0m"
|
|
517
|
+
fi
|
|
518
|
+
printf "\\r\\033[K %s%s %s%b\\n" "$_marker" "$_check" "$_mid" "$_exists_marker" >&2
|
|
519
|
+
_rendered=$(( _rendered + 1 ))
|
|
520
|
+
done
|
|
521
|
+
|
|
522
|
+
# Pad remaining lines
|
|
523
|
+
while [ "$_rendered" -lt "$_max_visible" ]; do
|
|
524
|
+
printf "\\r\\033[K\\n" >&2
|
|
525
|
+
_rendered=$(( _rendered + 1 ))
|
|
526
|
+
done
|
|
527
|
+
|
|
528
|
+
if [ "$_vstart" -gt 0 ] || [ "$_vend" -lt "$_fcount" ]; then
|
|
529
|
+
printf "\\r\\033[K \\033[2m(%d-%d of %d · scroll for more)\\033[0m\\n" "$(( _vstart + 1 ))" "$_vend" "$_fcount" >&2
|
|
530
|
+
else
|
|
531
|
+
printf "\\r\\033[K\\n" >&2
|
|
453
532
|
fi
|
|
533
|
+
|
|
534
|
+
printf "\\r\\033[K \\033[2m↑↓ navigate · space select · enter confirm · esc cancel · type to filter\\033[0m" >&2
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
# Total lines rendered by _search_render: 3 header + _max_visible + 1 scroll + 1 hint = _max_visible + 5
|
|
538
|
+
local _search_lines=$(( _max_visible + 5 ))
|
|
539
|
+
|
|
540
|
+
printf "\\033[?25l" >&2
|
|
541
|
+
stty -echo -icanon min 1 < /dev/tty 2>/dev/null
|
|
542
|
+
|
|
543
|
+
printf "\\n" >&2
|
|
544
|
+
_search_render
|
|
545
|
+
|
|
546
|
+
local _search_done=0
|
|
547
|
+
local _search_cancelled=0
|
|
548
|
+
while [ "$_search_done" = "0" ]; do
|
|
549
|
+
local _skey
|
|
550
|
+
_skey=$(dd bs=1 count=1 2>/dev/null < /dev/tty) || break
|
|
551
|
+
|
|
552
|
+
if [ "$_skey" = $'\033' ]; then
|
|
553
|
+
local _sbracket _sdir
|
|
554
|
+
_sbracket=$(dd bs=1 count=1 2>/dev/null < /dev/tty) || true
|
|
555
|
+
_sdir=$(dd bs=1 count=1 2>/dev/null < /dev/tty) || true
|
|
556
|
+
if [ "$_sbracket" = "[" ]; then
|
|
557
|
+
case "$_sdir" in
|
|
558
|
+
A) _scursor=$(( _scursor - 1 )); [ "$_scursor" -lt 0 ] && _scursor=0 ;;
|
|
559
|
+
B) _scursor=$(( _scursor + 1 )) ;;
|
|
560
|
+
esac
|
|
561
|
+
else
|
|
562
|
+
# Bare escape — cancel
|
|
563
|
+
_search_cancelled=1
|
|
564
|
+
_search_done=1
|
|
565
|
+
fi
|
|
566
|
+
elif [ "$_skey" = " " ]; then
|
|
567
|
+
# Toggle selection at cursor
|
|
568
|
+
local _filtered_for_toggle=()
|
|
569
|
+
local _lc_s2
|
|
570
|
+
_lc_s2=$(echo "$_search" | tr '[:upper:]' '[:lower:]')
|
|
571
|
+
for (( _si = 0; _si < ${#_all_model_ids[@]}; _si++ )); do
|
|
572
|
+
if [ -z "$_search" ]; then
|
|
573
|
+
_filtered_for_toggle+=("$_si")
|
|
574
|
+
else
|
|
575
|
+
local _lc2
|
|
576
|
+
_lc2=$(echo "${_all_model_ids[$_si]}" | tr '[:upper:]' '[:lower:]')
|
|
577
|
+
[[ "$_lc2" == *"$_lc_s2"* ]] && _filtered_for_toggle+=("$_si")
|
|
578
|
+
fi
|
|
579
|
+
done
|
|
580
|
+
if [ "$_scursor" -lt "${#_filtered_for_toggle[@]}" ]; then
|
|
581
|
+
local _tidx=${_filtered_for_toggle[$_scursor]}
|
|
582
|
+
[ "${_sselected[$_tidx]}" = "0" ] && _sselected[$_tidx]=1 || _sselected[$_tidx]=0
|
|
583
|
+
fi
|
|
584
|
+
elif [ "$_skey" = "" ]; then
|
|
585
|
+
# Confirm
|
|
586
|
+
_search_done=1
|
|
587
|
+
elif [ "$_skey" = $'\177' ] || [ "$_skey" = $'\010' ]; then
|
|
588
|
+
# Backspace
|
|
589
|
+
if [ -n "$_search" ]; then
|
|
590
|
+
_search="${_search%?}"
|
|
591
|
+
_scursor=0
|
|
592
|
+
fi
|
|
593
|
+
else
|
|
594
|
+
# Printable character — append to search
|
|
595
|
+
if [[ "$_skey" =~ [[:print:]] ]]; then
|
|
596
|
+
_search="${_search}${_skey}"
|
|
597
|
+
_scursor=0
|
|
598
|
+
fi
|
|
599
|
+
fi
|
|
600
|
+
|
|
601
|
+
printf "\\033[%dA" "$_search_lines" >&2
|
|
602
|
+
_search_render
|
|
603
|
+
done
|
|
604
|
+
|
|
605
|
+
# Clear the search sub-TUI
|
|
606
|
+
printf "\\033[%dA" "$_search_lines" >&2
|
|
607
|
+
for (( _ci = 0; _ci <= _search_lines; _ci++ )); do
|
|
608
|
+
printf "\\r\\033[K\\n" >&2
|
|
609
|
+
done
|
|
610
|
+
printf "\\033[%dA" "$(( _search_lines + 1 ))" >&2
|
|
611
|
+
|
|
612
|
+
# Insert selected models into the main list
|
|
613
|
+
if [ "$_search_cancelled" = "0" ]; then
|
|
614
|
+
for (( _si = 0; _si < ${#_sselected[@]}; _si++ )); do
|
|
615
|
+
[ "${_sselected[$_si]}" != "1" ] && continue
|
|
616
|
+
local _new_model="${_all_model_ids[$_si]}"
|
|
617
|
+
local _new_label="${_all_model_labels[$_si]}"
|
|
618
|
+
|
|
619
|
+
# Skip if already in list
|
|
620
|
+
if [[ "$_existing_ids" == *"|${_new_model}"* ]]; then
|
|
621
|
+
continue
|
|
622
|
+
fi
|
|
623
|
+
_existing_ids="${_existing_ids}|${_new_model}"
|
|
624
|
+
|
|
625
|
+
# Derive slug for TOML key
|
|
626
|
+
local _cprov="${_new_model%%/*}"
|
|
627
|
+
local _cname="${_new_model#*/}"
|
|
628
|
+
local _cslug
|
|
629
|
+
_cslug=$(echo "${_cprov}-${_cname}" | tr '[:upper:]' '[:lower:]' | tr ' .' '-' | tr -cd 'a-z0-9-')
|
|
630
|
+
_cslug="${_cslug}-gw"
|
|
631
|
+
|
|
632
|
+
# Insert before the custom_add row
|
|
633
|
+
local _ins=$cursor
|
|
634
|
+
local new_labels=() new_meta=() new_types=() new_sources=() new_selected=()
|
|
635
|
+
for (( _ii = 0; _ii < count; _ii++ )); do
|
|
636
|
+
if [ "$_ii" -eq "$_ins" ]; then
|
|
637
|
+
new_labels+=("$_new_label")
|
|
638
|
+
new_meta+=("$_new_model")
|
|
639
|
+
new_types+=("custom_gateway")
|
|
640
|
+
new_sources+=("${#custom_gateway_names[@]}")
|
|
641
|
+
new_selected+=(1)
|
|
642
|
+
fi
|
|
643
|
+
new_labels+=("${item_labels[$_ii]}")
|
|
644
|
+
new_meta+=("${item_meta[$_ii]}")
|
|
645
|
+
new_types+=("${item_types[$_ii]}")
|
|
646
|
+
new_sources+=("${item_sources[$_ii]}")
|
|
647
|
+
new_selected+=("${selected[$_ii]}")
|
|
648
|
+
done
|
|
649
|
+
|
|
650
|
+
item_labels=("${new_labels[@]}")
|
|
651
|
+
item_meta=("${new_meta[@]}")
|
|
652
|
+
item_types=("${new_types[@]}")
|
|
653
|
+
item_sources=("${new_sources[@]}")
|
|
654
|
+
selected=("${new_selected[@]}")
|
|
655
|
+
|
|
656
|
+
custom_gateway_names+=("$_cslug")
|
|
657
|
+
custom_gateway_models+=("$_new_model")
|
|
658
|
+
|
|
659
|
+
count=${#item_labels[@]}
|
|
660
|
+
cursor=$(( cursor + 1 ))
|
|
661
|
+
|
|
662
|
+
local _ll=${#_new_label}
|
|
663
|
+
[ "$_ll" -gt "$max_label_len" ] && max_label_len=$_ll
|
|
664
|
+
done
|
|
665
|
+
|
|
666
|
+
jump_back=$(( count + 3 ))
|
|
667
|
+
sep=""
|
|
668
|
+
for (( _si = 0; _si < max_label_len + 40; _si++ )); do sep="${sep}-"; done
|
|
454
669
|
fi
|
|
455
|
-
|
|
456
|
-
|
|
670
|
+
|
|
671
|
+
stty -echo -icanon min 1 < /dev/tty 2>/dev/null
|
|
672
|
+
printf "\\033[?25l" >&2
|
|
457
673
|
elif [ "${item_types[$cursor]}" != "separator" ]; then
|
|
458
674
|
[ "${selected[$cursor]}" = "0" ] && selected[$cursor]=1 || selected[$cursor]=0
|
|
459
675
|
fi
|
|
@@ -781,13 +997,13 @@ SEED
|
|
|
781
997
|
|
|
782
998
|
echo ""
|
|
783
999
|
echo "Next steps:"
|
|
784
|
-
echo " 1. Run: moxie doctor
|
|
785
|
-
echo " 2.
|
|
1000
|
+
echo " 1. Run: moxie doctor (verify dependencies and environment)"
|
|
1001
|
+
echo " 2. Run: moxie agents swap (add or change gateway models anytime)"
|
|
786
1002
|
if [ ! -d "$MOXIE_DIR/context" ] || [ -z "$(ls -A "$MOXIE_DIR/context" 2>/dev/null)" ]; then
|
|
787
1003
|
echo " Tip: add context docs to .moxie/context/ (roadmaps, PRDs, etc.)"
|
|
788
1004
|
fi
|
|
789
|
-
echo " 3. Run: moxie start
|
|
790
|
-
echo " Or: moxie run
|
|
1005
|
+
echo " 3. Run: moxie start (background)"
|
|
1006
|
+
echo " Or: moxie run (foreground)"
|
|
791
1007
|
}
|
|
792
1008
|
|
|
793
1009
|
_init_ledger() {
|
|
@@ -1124,6 +1340,7 @@ _print_run_banner() {
|
|
|
1124
1340
|
printf "║ Agents: %-51s║\n" "$(IFS=', '; echo "${AGENT_NAMES[*]}")"
|
|
1125
1341
|
echo "║ Quorum: unanimous (all agents must sign off) ║"
|
|
1126
1342
|
echo "║ Rotation: randomized per phase ║"
|
|
1343
|
+
echo "║ Swap models: moxie agents swap ║"
|
|
1127
1344
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
1128
1345
|
echo ""
|
|
1129
1346
|
echo "Started: $(date)"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zachjxyz/moxie",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
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"
|