@jetrabbits/agentic 0.3.3 → 0.5.0
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/AGENTS.md +8 -0
- package/CHANGELOG.md +18 -0
- package/Makefile +26 -5
- package/README.md +25 -6
- package/agentic +801 -66
- package/areas/devops/ci-cd/workflows/onboard-repo.md +29 -0
- package/areas/devops/ci-cd/workflows/pipeline-debug.md +26 -0
- package/areas/devops/ci-cd/workflows/release-pipeline.md +53 -0
- package/areas/devops/database-ops/workflows/backup-verify.md +27 -0
- package/areas/devops/database-ops/workflows/db-incident.md +30 -0
- package/areas/devops/devsecops/workflows/policy-onboard.md +34 -0
- package/areas/devops/devsecops/workflows/security-scan-pipeline.md +33 -0
- package/areas/devops/infrastructure/workflows/destroy-environment.md +31 -0
- package/areas/devops/infrastructure/workflows/drift-remediation.md +29 -0
- package/areas/devops/infrastructure/workflows/module-development.md +32 -0
- package/areas/devops/infrastructure/workflows/provision-environment.md +29 -0
- package/areas/devops/kubernetes/workflows/cluster-bootstrap.md +36 -0
- package/areas/devops/kubernetes/workflows/debug-workload.md +29 -0
- package/areas/devops/kubernetes/workflows/onboard-service.md +35 -0
- package/areas/devops/kubernetes/workflows/upgrade-cluster.md +30 -0
- package/areas/devops/networking/workflows/onboard-ingress.md +27 -0
- package/areas/devops/networking/workflows/service-mesh-onboard.md +27 -0
- package/areas/devops/observability/workflows/alert-investigation.md +29 -0
- package/areas/devops/observability/workflows/observability-stack-setup.md +33 -0
- package/areas/devops/observability/workflows/onboard-service-monitoring.md +31 -0
- package/areas/devops/sre/workflows/incident-response.md +48 -0
- package/areas/devops/sre/workflows/postmortem.md +32 -0
- package/areas/devops/sre/workflows/slo-review.md +35 -1
- package/areas/software/backend/workflows/add-migration.md +33 -0
- package/areas/software/backend/workflows/create-endpoint.md +40 -0
- package/areas/software/backend/workflows/debug-issue.md +31 -0
- package/areas/software/backend/workflows/develop-epic.md +37 -0
- package/areas/software/backend/workflows/develop-feature.md +44 -0
- package/areas/software/backend/workflows/refactor-module.md +35 -0
- package/areas/software/backend/workflows/test-feature.md +30 -0
- package/areas/software/data-engineering/workflows/backfill-data.md +25 -0
- package/areas/software/data-engineering/workflows/data-quality-incident.md +31 -0
- package/areas/software/data-engineering/workflows/lineage-trace.md +25 -0
- package/areas/software/data-engineering/workflows/new-model.md +30 -0
- package/areas/software/data-engineering/workflows/schema-migration.md +29 -0
- package/areas/software/frontend/workflows/a11y-fix.md +30 -0
- package/areas/software/frontend/workflows/bundle-analyze.md +28 -0
- package/areas/software/frontend/workflows/release-prep.md +33 -0
- package/areas/software/frontend/workflows/scaffold-component.md +32 -0
- package/areas/software/frontend/workflows/visual-regression.md +32 -0
- package/areas/software/full-stack/workflows/backend-project-full-cycle.md +47 -2
- package/areas/software/full-stack/workflows/debug-issue.md +29 -0
- package/areas/software/full-stack/workflows/develop-feature.md +38 -0
- package/areas/software/full-stack/workflows/feature-implementation-flow.md +38 -0
- package/areas/software/full-stack/workflows/testing-ci-pipeline.md +30 -0
- package/areas/software/general/workflows/code-review-workflow.md +31 -0
- package/areas/software/general/workflows/development-cycle-workflow.md +38 -0
- package/areas/software/general/workflows/project-setup-workflow.md +38 -0
- package/areas/software/mlops/workflows/champion-challenger.md +29 -0
- package/areas/software/mlops/workflows/deploy-endpoint.md +30 -0
- package/areas/software/mlops/workflows/evaluate-model.md +28 -0
- package/areas/software/mlops/workflows/model-incident.md +29 -0
- package/areas/software/mlops/workflows/train-experiment.md +25 -0
- package/areas/software/mobile/workflows/crash-triage.md +28 -0
- package/areas/software/mobile/workflows/device-testing.md +27 -0
- package/areas/software/mobile/workflows/ota-update.md +25 -0
- package/areas/software/mobile/workflows/release-build.md +30 -0
- package/areas/software/mobile/workflows/store-submission.md +29 -0
- package/areas/software/platform/workflows/cost-audit.md +28 -0
- package/areas/software/platform/workflows/deploy-production.md +30 -0
- package/areas/software/platform/workflows/drift-check.md +29 -0
- package/areas/software/platform/workflows/incident-response.md +33 -0
- package/areas/software/platform/workflows/provision-env.md +36 -0
- package/areas/software/qa/workflows/flakiness-investigation.md +30 -0
- package/areas/software/qa/workflows/performance-audit.md +29 -0
- package/areas/software/qa/workflows/regression-suite.md +28 -0
- package/areas/software/qa/workflows/smoke-test.md +31 -0
- package/areas/software/qa/workflows/test-coverage-report.md +28 -0
- package/areas/software/security/workflows/compliance-report.md +27 -0
- package/areas/software/security/workflows/pen-test-sim.md +28 -0
- package/areas/software/security/workflows/secret-rotation.md +33 -2
- package/areas/software/security/workflows/security-scan.md +29 -0
- package/areas/software/security/workflows/threat-model-review.md +30 -0
- package/docs/agentic-usage.md +19 -2
- package/docs/catalog.schema.json +5 -1
- package/docs/mcp/README.md +28 -0
- package/docs/opencode_setup.md +21 -1
- package/docs/site/README.md +15 -1
- package/docs/site/app.js +68 -0
- package/docs/site/catalog.json +74 -1
- package/docs/site/index.html +5 -1
- package/docs/site/styles.css +52 -4
- package/extensions/opencode/opencode.json +0 -1
- package/extensions/opencode/profiles/githubcopilot/opencode.json +87 -0
- package/extensions/opencode/profiles/openai/opencode.json +100 -0
- package/package.json +1 -1
- package/scripts/build_docs_catalog.py +13 -1
- package/scripts/sync_workflow_diagrams.py +199 -0
- package/extensions/opencode/plugins/sound-notification.ts +0 -13
package/agentic
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
shopt -s inherit_errexit 2>/dev/null || true
|
|
5
5
|
|
|
6
|
+
# macOS ships bash 3.2, where empty arrays and set -u interact poorly.
|
|
7
|
+
# Keep the CLI quiet and compatible there while retaining nounset on newer bash.
|
|
8
|
+
AGENTIC_BASH_MAJOR="${AGENTIC_TEST_BASH_MAJOR:-${BASH_VERSINFO[0]}}"
|
|
9
|
+
if [[ "$AGENTIC_BASH_MAJOR" =~ ^[0-9]+$ ]] && (( AGENTIC_BASH_MAJOR < 4 )); then
|
|
10
|
+
set +u
|
|
11
|
+
fi
|
|
12
|
+
|
|
6
13
|
SCRIPT_SOURCE="${BASH_SOURCE[0]}"
|
|
7
14
|
SCRIPT_DIR="$(cd -- "$(dirname -- "$SCRIPT_SOURCE")" && pwd -P)"
|
|
8
15
|
SCRIPT_NAME="$(basename -- "$SCRIPT_SOURCE")"
|
|
@@ -24,6 +31,7 @@ XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
|
24
31
|
APP_CONFIG_DIR="$XDG_CONFIG_HOME/$APP_NAME"
|
|
25
32
|
APP_CONFIG_FILE="$APP_CONFIG_DIR/config"
|
|
26
33
|
OPENCODE_PLUGIN_CONFIG_FILE="$APP_CONFIG_DIR/opencode-plugins.json"
|
|
34
|
+
OPENCODE_USER_PROFILES_DIR="$HOME/.config/$APP_NAME/opencode/profiles"
|
|
27
35
|
APP_DATA_DIR="$XDG_DATA_HOME/$APP_NAME"
|
|
28
36
|
APP_REPO_DIR="$APP_DATA_DIR/repo"
|
|
29
37
|
|
|
@@ -42,6 +50,8 @@ ACTIVE_THEME="dark"
|
|
|
42
50
|
SELECTED_AGENT_OS=("$DEFAULT_AGENT_OS")
|
|
43
51
|
SELECTED_AREAS=()
|
|
44
52
|
SELECTED_SPECS=()
|
|
53
|
+
SELECTED_MCPS=()
|
|
54
|
+
SELECTED_OPENCODE_PROFILE=""
|
|
45
55
|
INSTALL_SETTINGS_REPLAY=false
|
|
46
56
|
|
|
47
57
|
SELF_INSTALL_FORCE=false
|
|
@@ -749,6 +759,245 @@ ensure_agentic_runtime_requirements() {
|
|
|
749
759
|
ensure_hash_available
|
|
750
760
|
}
|
|
751
761
|
|
|
762
|
+
|
|
763
|
+
MCP_REGISTRY_IDS=(opencode-docs playwright kubernetes youtube-transcript docker-mcp context7 mempalace anydb)
|
|
764
|
+
MCP_NONE_OPTION="None / skip"
|
|
765
|
+
OPENCODE_PROFILE_IDS=(openai githubcopilot)
|
|
766
|
+
OPENCODE_PROFILE_NONE_OPTION="No OpenCode model profile"
|
|
767
|
+
|
|
768
|
+
mcp_registry_contains() {
|
|
769
|
+
local expected="$1"
|
|
770
|
+
local mcp_id
|
|
771
|
+
for mcp_id in "${MCP_REGISTRY_IDS[@]}"; do
|
|
772
|
+
[[ "$mcp_id" == "$expected" ]] && return 0
|
|
773
|
+
done
|
|
774
|
+
return 1
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
mcp_title() {
|
|
778
|
+
case "$1" in
|
|
779
|
+
opencode-docs) echo "OpenCode Docs" ;;
|
|
780
|
+
playwright) echo "Playwright" ;;
|
|
781
|
+
kubernetes) echo "Kubernetes" ;;
|
|
782
|
+
youtube-transcript) echo "YouTube Transcript" ;;
|
|
783
|
+
docker-mcp) echo "Docker MCP" ;;
|
|
784
|
+
context7) echo "Context7" ;;
|
|
785
|
+
mempalace) echo "MemPalace" ;;
|
|
786
|
+
anydb) echo "AnyDB" ;;
|
|
787
|
+
*) echo "$1" ;;
|
|
788
|
+
esac
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
mcp_description() {
|
|
792
|
+
case "$1" in
|
|
793
|
+
opencode-docs) echo "OpenCode docs MCP" ;;
|
|
794
|
+
playwright) echo "Browser automation via Playwright MCP" ;;
|
|
795
|
+
kubernetes) echo "Kubernetes pods/logs/exec management" ;;
|
|
796
|
+
youtube-transcript) echo "YouTube transcript extraction" ;;
|
|
797
|
+
docker-mcp) echo "Docker MCP Gateway" ;;
|
|
798
|
+
context7) echo "Fresh library documentation" ;;
|
|
799
|
+
mempalace) echo "Persistent project memory" ;;
|
|
800
|
+
anydb) echo "Database access MCP" ;;
|
|
801
|
+
*) echo "MCP server" ;;
|
|
802
|
+
esac
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
mcp_security() {
|
|
806
|
+
case "$1" in
|
|
807
|
+
kubernetes|docker-mcp|anydb) echo "dangerous" ;;
|
|
808
|
+
playwright|mempalace) echo "sensitive" ;;
|
|
809
|
+
*) echo "safe" ;;
|
|
810
|
+
esac
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
mcp_verified() {
|
|
814
|
+
case "$1" in
|
|
815
|
+
opencode-docs|playwright|kubernetes|youtube-transcript|docker-mcp|context7|mempalace|anydb) return 0 ;;
|
|
816
|
+
*) return 1 ;;
|
|
817
|
+
esac
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
mcp_display_row() {
|
|
821
|
+
local mcp_id="$1"
|
|
822
|
+
local checked="${2:-false}"
|
|
823
|
+
local mark="[ ]"
|
|
824
|
+
[[ "$checked" == true ]] && mark="[x]"
|
|
825
|
+
printf '%s %-20s %s\n' "$mark" "$mcp_id" "$(mcp_description "$mcp_id")"
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
mcp_id_from_display_row() {
|
|
829
|
+
local row="$1"
|
|
830
|
+
row="${row#\[ \] }"
|
|
831
|
+
row="${row#\[x\] }"
|
|
832
|
+
row="${row#\[X\] }"
|
|
833
|
+
row="${row%%[[:space:]]*}"
|
|
834
|
+
printf '%s\n' "$row"
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
selected_mcp_contains() {
|
|
838
|
+
local expected="$1"
|
|
839
|
+
local mcp_id
|
|
840
|
+
for mcp_id in "${SELECTED_MCPS[@]}"; do
|
|
841
|
+
[[ "$mcp_id" == "$expected" ]] && return 0
|
|
842
|
+
done
|
|
843
|
+
return 1
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
add_selected_mcp() {
|
|
847
|
+
local mcp_id="$1"
|
|
848
|
+
mcp_registry_contains "$mcp_id" || {
|
|
849
|
+
warn "Ignoring unknown MCP integration '$mcp_id'"
|
|
850
|
+
return 0
|
|
851
|
+
}
|
|
852
|
+
mcp_verified "$mcp_id" || {
|
|
853
|
+
warn "Skipping MCP integration '$mcp_id' because its package/config is not verified"
|
|
854
|
+
return 0
|
|
855
|
+
}
|
|
856
|
+
unique_append "$mcp_id" SELECTED_MCPS
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
sync_selected_mcps_from_env() {
|
|
860
|
+
local raw="${AGENTIC_ENABLE_MCPS:-}"
|
|
861
|
+
local item
|
|
862
|
+
if [[ -n "$raw" ]]; then
|
|
863
|
+
local old_ifs="$IFS"
|
|
864
|
+
IFS=,
|
|
865
|
+
for item in $raw; do
|
|
866
|
+
item="$(trim "$item")"
|
|
867
|
+
[[ -z "$item" ]] && continue
|
|
868
|
+
add_selected_mcp "$item"
|
|
869
|
+
done
|
|
870
|
+
IFS="$old_ifs"
|
|
871
|
+
fi
|
|
872
|
+
if [[ "${AGENTIC_ENABLE_CONTEXT7:-}" =~ ^[Yy](es)?$ ]]; then
|
|
873
|
+
add_selected_mcp "context7"
|
|
874
|
+
fi
|
|
875
|
+
if [[ "${AGENTIC_ENABLE_MEMPALACE:-}" =~ ^[Yy](es)?$ ]]; then
|
|
876
|
+
add_selected_mcp "mempalace"
|
|
877
|
+
fi
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
sync_legacy_mcp_env_from_selected() {
|
|
881
|
+
if selected_mcp_contains "context7"; then
|
|
882
|
+
AGENTIC_ENABLE_CONTEXT7="y"
|
|
883
|
+
elif [[ -z "${AGENTIC_ENABLE_CONTEXT7:-}" ]]; then
|
|
884
|
+
AGENTIC_ENABLE_CONTEXT7="n"
|
|
885
|
+
fi
|
|
886
|
+
if selected_mcp_contains "mempalace"; then
|
|
887
|
+
AGENTIC_ENABLE_MEMPALACE="y"
|
|
888
|
+
elif [[ -z "${AGENTIC_ENABLE_MEMPALACE:-}" ]]; then
|
|
889
|
+
AGENTIC_ENABLE_MEMPALACE="n"
|
|
890
|
+
fi
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
selected_mcps_csv() {
|
|
894
|
+
local old_ifs="$IFS"
|
|
895
|
+
IFS=,
|
|
896
|
+
printf '%s\n' "${SELECTED_MCPS[*]:-}"
|
|
897
|
+
IFS="$old_ifs"
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
mcp_registry_json() {
|
|
901
|
+
cat <<'JSON'
|
|
902
|
+
{
|
|
903
|
+
"opencode-docs": {"security":"safe", "default_enabled":false, "target":"mcpServers", "server":"opencode", "command":"npx", "args":["-y", "opencode-docs-mcp"]},
|
|
904
|
+
"playwright": {"security":"sensitive", "default_enabled":false, "target":"mcpServers", "server":"playwright", "command":"npx", "args":["@playwright/mcp@latest"]},
|
|
905
|
+
"kubernetes": {"security":"dangerous", "default_enabled":false, "target":"mcpServers", "server":"kubernetes", "command":"npx", "args":["-y", "kubernetes-mcp-server"]},
|
|
906
|
+
"youtube-transcript": {"security":"safe", "default_enabled":false, "target":"mcpServers", "server":"youtube-transcript", "command":"npx", "args":["-y", "@kimtaeyoon83/mcp-server-youtube-transcript"]},
|
|
907
|
+
"docker-mcp": {"security":"dangerous", "default_enabled":false, "target":"mcp", "server":"docker", "opencode":{"type":"local", "command":["docker", "mcp", "gateway", "run"], "enabled":true}, "generic":{"command":"docker", "args":["mcp", "gateway", "run"]}},
|
|
908
|
+
"context7": {"security":"safe", "default_enabled":false, "target":"mcpServers", "server":"context7", "command":"npx", "args":["-y", "@upstash/context7-mcp"], "remote":"https://mcp.context7.com/mcp"},
|
|
909
|
+
"mempalace": {"security":"sensitive", "default_enabled":false, "target":"mcpServers", "server":"mempalace", "command":"mempalace-mcp", "args":[]},
|
|
910
|
+
"anydb": {"security":"dangerous", "default_enabled":false, "target":"mcpServers", "server":"anydb", "command":"npx", "args":["-y", "anydb-mcp"]}
|
|
911
|
+
}
|
|
912
|
+
JSON
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
opencode_builtin_profile_contains() {
|
|
916
|
+
local expected="$1"
|
|
917
|
+
local profile_id
|
|
918
|
+
for profile_id in "${OPENCODE_PROFILE_IDS[@]}"; do
|
|
919
|
+
[[ "$profile_id" == "$expected" ]] && return 0
|
|
920
|
+
done
|
|
921
|
+
return 1
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
opencode_profile_contains() {
|
|
925
|
+
local expected="$1"
|
|
926
|
+
opencode_builtin_profile_contains "$expected" && return 0
|
|
927
|
+
[[ -f "$OPENCODE_USER_PROFILES_DIR/$expected/opencode.json" ]] && return 0
|
|
928
|
+
return 1
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
opencode_user_profile_ids() {
|
|
932
|
+
[[ -d "$OPENCODE_USER_PROFILES_DIR" ]] || return 0
|
|
933
|
+
find "$OPENCODE_USER_PROFILES_DIR" -mindepth 2 -maxdepth 2 -type f -name opencode.json -print 2>/dev/null | \
|
|
934
|
+
while IFS= read -r profile_config; do
|
|
935
|
+
basename -- "$(dirname -- "$profile_config")"
|
|
936
|
+
done | sort
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
opencode_profile_source_path() {
|
|
940
|
+
local profile_id="$1"
|
|
941
|
+
local bundled_src="$EXTENSIONS_ROOT/opencode/profiles/$profile_id/opencode.json"
|
|
942
|
+
local user_src="$OPENCODE_USER_PROFILES_DIR/$profile_id/opencode.json"
|
|
943
|
+
if [[ -f "$bundled_src" ]]; then
|
|
944
|
+
printf '%s\n' "$bundled_src"
|
|
945
|
+
return
|
|
946
|
+
fi
|
|
947
|
+
printf '%s\n' "$user_src"
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
opencode_profile_label() {
|
|
951
|
+
case "$1" in
|
|
952
|
+
openai) echo "OpenAI Model Profile" ;;
|
|
953
|
+
githubcopilot) echo "GitHub Copilot Model Profile" ;;
|
|
954
|
+
*) echo "$1 profile" ;;
|
|
955
|
+
esac
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
opencode_profile_id_from_label() {
|
|
959
|
+
local label="$1"
|
|
960
|
+
local profile_id
|
|
961
|
+
for profile_id in "${OPENCODE_PROFILE_IDS[@]}"; do
|
|
962
|
+
if [[ "$label" == "$(opencode_profile_label "$profile_id")" ]]; then
|
|
963
|
+
printf '%s\n' "$profile_id"
|
|
964
|
+
return
|
|
965
|
+
fi
|
|
966
|
+
done
|
|
967
|
+
case "$label" in
|
|
968
|
+
*" profile")
|
|
969
|
+
printf '%s\n' "${label%" profile"}"
|
|
970
|
+
return
|
|
971
|
+
;;
|
|
972
|
+
esac
|
|
973
|
+
printf '%s\n' "$label"
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
opencode_plugin_label() {
|
|
977
|
+
case "$1" in
|
|
978
|
+
telegram-notification) echo "Telegram Notifications" ;;
|
|
979
|
+
agent-model-mapper) echo "Agent Model Mapping" ;;
|
|
980
|
+
*) echo "$1" ;;
|
|
981
|
+
esac
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
opencode_plugin_id_from_label() {
|
|
985
|
+
case "$1" in
|
|
986
|
+
"Telegram Notifications") echo "telegram-notification" ;;
|
|
987
|
+
"Agent Model Mapping") echo "agent-model-mapper" ;;
|
|
988
|
+
*) echo "$1" ;;
|
|
989
|
+
esac
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
opencode_profile_is_none() {
|
|
993
|
+
local profile_id
|
|
994
|
+
profile_id="$(trim "${1:-}")"
|
|
995
|
+
case "$profile_id" in
|
|
996
|
+
""|none|None|skip|Skip|no|No) return 0 ;;
|
|
997
|
+
*) return 1 ;;
|
|
998
|
+
esac
|
|
999
|
+
}
|
|
1000
|
+
|
|
752
1001
|
selected_agent_os_contains() {
|
|
753
1002
|
local expected="$1"
|
|
754
1003
|
local agent
|
|
@@ -764,6 +1013,34 @@ project_manifest_path() {
|
|
|
764
1013
|
printf '%s\n' "$PROJECT_DIR/$PROJECT_MANIFEST_NAME"
|
|
765
1014
|
}
|
|
766
1015
|
|
|
1016
|
+
normalize_project_dir_path() {
|
|
1017
|
+
local raw="$1"
|
|
1018
|
+
local parent base parent_real
|
|
1019
|
+
|
|
1020
|
+
if [[ -z "$raw" ]]; then
|
|
1021
|
+
printf '%s\n' "$raw"
|
|
1022
|
+
return
|
|
1023
|
+
fi
|
|
1024
|
+
|
|
1025
|
+
if [[ -d "$raw" ]]; then
|
|
1026
|
+
(cd -- "$raw" && pwd -P)
|
|
1027
|
+
return
|
|
1028
|
+
fi
|
|
1029
|
+
|
|
1030
|
+
parent="$(dirname -- "$raw")"
|
|
1031
|
+
base="$(basename -- "$raw")"
|
|
1032
|
+
if [[ -d "$parent" ]]; then
|
|
1033
|
+
parent_real="$(cd -- "$parent" && pwd -P)" || {
|
|
1034
|
+
printf '%s\n' "$raw"
|
|
1035
|
+
return
|
|
1036
|
+
}
|
|
1037
|
+
printf '%s/%s\n' "$parent_real" "$base"
|
|
1038
|
+
return
|
|
1039
|
+
fi
|
|
1040
|
+
|
|
1041
|
+
printf '%s\n' "$raw"
|
|
1042
|
+
}
|
|
1043
|
+
|
|
767
1044
|
project_rel_path() {
|
|
768
1045
|
local path="$1"
|
|
769
1046
|
local rel="${path#"$PROJECT_DIR"/}"
|
|
@@ -1040,21 +1317,11 @@ write_agentic_manifest() {
|
|
|
1040
1317
|
specs_csv="${SELECTED_SPECS[*]}"
|
|
1041
1318
|
IFS="$old_ifs"
|
|
1042
1319
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
if [[ "${AGENTIC_ENABLE_CONTEXT7:-}" =~ ^[Yy](es)?$ ]]; then
|
|
1046
|
-
mcp_integrations+=("context7")
|
|
1047
|
-
fi
|
|
1048
|
-
if [[ "${AGENTIC_ENABLE_MEMPALACE:-}" =~ ^[Yy](es)?$ ]]; then
|
|
1049
|
-
mcp_integrations+=("mempalace")
|
|
1050
|
-
fi
|
|
1051
|
-
old_ifs="$IFS"
|
|
1052
|
-
IFS=,
|
|
1053
|
-
mcp_integrations_csv="${mcp_integrations[*]:-}"
|
|
1054
|
-
IFS="$old_ifs"
|
|
1320
|
+
sync_legacy_mcp_env_from_selected
|
|
1321
|
+
mcp_integrations_csv="$(selected_mcps_csv)"
|
|
1055
1322
|
|
|
1056
1323
|
local manifest_status
|
|
1057
|
-
manifest_status="$(python3 - "$manifest" "$records_file" "$skipped_file" "$APP_REPO_LINK" "$REPO_ROOT" "$agent_os_csv" "$areas_csv" "$specs_csv" "$(app_version_label)" "$mcp_integrations_csv" "$OPENCODE_TELEGRAM_ENABLED" "$OPENCODE_TELEGRAM_BOT_TOKEN" "$OPENCODE_TELEGRAM_CHAT_ID" "$OPENCODE_AGENT_MODEL_MAPPER_ENABLED" <<'PY'
|
|
1324
|
+
manifest_status="$(python3 - "$manifest" "$records_file" "$skipped_file" "$APP_REPO_LINK" "$REPO_ROOT" "$agent_os_csv" "$areas_csv" "$specs_csv" "$(app_version_label)" "$mcp_integrations_csv" "$OPENCODE_TELEGRAM_ENABLED" "$OPENCODE_TELEGRAM_BOT_TOKEN" "$OPENCODE_TELEGRAM_CHAT_ID" "$OPENCODE_AGENT_MODEL_MAPPER_ENABLED" "$SELECTED_OPENCODE_PROFILE" <<'PY'
|
|
1058
1325
|
import json
|
|
1059
1326
|
import sys
|
|
1060
1327
|
from datetime import datetime, timezone
|
|
@@ -1074,6 +1341,7 @@ telegram_enabled = sys.argv[11].lower() == "true" if len(sys.argv) > 11 and sys.
|
|
|
1074
1341
|
telegram_bot_token = sys.argv[12] if len(sys.argv) > 12 else ""
|
|
1075
1342
|
telegram_chat_id = sys.argv[13] if len(sys.argv) > 13 else ""
|
|
1076
1343
|
mapper_enabled = sys.argv[14].lower() == "true" if len(sys.argv) > 14 and sys.argv[14] else None
|
|
1344
|
+
opencode_profile = sys.argv[15] if len(sys.argv) > 15 else ""
|
|
1077
1345
|
now = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
1078
1346
|
|
|
1079
1347
|
existing = {}
|
|
@@ -1150,6 +1418,7 @@ data = {
|
|
|
1150
1418
|
"areas": areas,
|
|
1151
1419
|
"specializations": specs,
|
|
1152
1420
|
"mcp_integrations": mcp_integrations,
|
|
1421
|
+
"opencode_profile": opencode_profile,
|
|
1153
1422
|
"opencode_plugins": opencode_plugins,
|
|
1154
1423
|
"source_repo": repo_link,
|
|
1155
1424
|
"source_checkout": repo_root,
|
|
@@ -1198,6 +1467,9 @@ for key in ("agent_os", "areas", "specializations", "mcp_integrations"):
|
|
|
1198
1467
|
print("::" + key)
|
|
1199
1468
|
for value in settings.get(key, []):
|
|
1200
1469
|
print(value)
|
|
1470
|
+
print("::opencode_profile")
|
|
1471
|
+
if settings.get("opencode_profile"):
|
|
1472
|
+
print(settings.get("opencode_profile"))
|
|
1201
1473
|
plugins = settings.get("opencode_plugins", {})
|
|
1202
1474
|
if isinstance(plugins, dict):
|
|
1203
1475
|
telegram = plugins.get("telegram", {})
|
|
@@ -1228,6 +1500,7 @@ PY
|
|
|
1228
1500
|
local loaded_telegram_bot_token=""
|
|
1229
1501
|
local loaded_telegram_chat_id=""
|
|
1230
1502
|
local loaded_mapper_enabled=""
|
|
1503
|
+
local loaded_opencode_profile=""
|
|
1231
1504
|
local value
|
|
1232
1505
|
for value in "${values[@]}"; do
|
|
1233
1506
|
case "$value" in
|
|
@@ -1235,12 +1508,18 @@ PY
|
|
|
1235
1508
|
"::areas") section="areas" ;;
|
|
1236
1509
|
"::specializations") section="specializations" ;;
|
|
1237
1510
|
"::mcp_integrations") section="mcp_integrations" ;;
|
|
1511
|
+
"::opencode_profile") section="opencode_profile" ;;
|
|
1512
|
+
"::opencode_telegram_enabled") section="opencode_telegram_enabled" ;;
|
|
1513
|
+
"::opencode_telegram_bot_token") section="opencode_telegram_bot_token" ;;
|
|
1514
|
+
"::opencode_telegram_chat_id") section="opencode_telegram_chat_id" ;;
|
|
1515
|
+
"::opencode_agent_model_mapper_enabled") section="opencode_agent_model_mapper_enabled" ;;
|
|
1238
1516
|
*)
|
|
1239
1517
|
case "$section" in
|
|
1240
1518
|
agent_os) loaded_agent_os+=("$value") ;;
|
|
1241
1519
|
areas) loaded_areas+=("$value") ;;
|
|
1242
1520
|
specializations) loaded_specs+=("$value") ;;
|
|
1243
1521
|
mcp_integrations) loaded_mcp_integrations+=("$value") ;;
|
|
1522
|
+
opencode_profile) loaded_opencode_profile="$value" ;;
|
|
1244
1523
|
opencode_telegram_enabled) loaded_telegram_enabled="$value" ;;
|
|
1245
1524
|
opencode_telegram_bot_token) loaded_telegram_bot_token="$value" ;;
|
|
1246
1525
|
opencode_telegram_chat_id) loaded_telegram_chat_id="$value" ;;
|
|
@@ -1260,23 +1539,13 @@ PY
|
|
|
1260
1539
|
SELECTED_SPECS=("${loaded_specs[@]}")
|
|
1261
1540
|
fi
|
|
1262
1541
|
|
|
1263
|
-
# Restore MCP integration selections so
|
|
1542
|
+
# Restore MCP integration selections so MCP config replay skips interactive prompts.
|
|
1264
1543
|
local mcp_item
|
|
1265
1544
|
if [[ "${#loaded_mcp_integrations[@]}" -gt 0 ]]; then
|
|
1266
1545
|
for mcp_item in "${loaded_mcp_integrations[@]}"; do
|
|
1267
|
-
|
|
1268
|
-
context7)
|
|
1269
|
-
if [[ -z "${AGENTIC_ENABLE_CONTEXT7:-}" ]]; then
|
|
1270
|
-
AGENTIC_ENABLE_CONTEXT7="y"
|
|
1271
|
-
fi
|
|
1272
|
-
;;
|
|
1273
|
-
mempalace)
|
|
1274
|
-
if [[ -z "${AGENTIC_ENABLE_MEMPALACE:-}" ]]; then
|
|
1275
|
-
AGENTIC_ENABLE_MEMPALACE="y"
|
|
1276
|
-
fi
|
|
1277
|
-
;;
|
|
1278
|
-
esac
|
|
1546
|
+
add_selected_mcp "$mcp_item"
|
|
1279
1547
|
done
|
|
1548
|
+
sync_legacy_mcp_env_from_selected
|
|
1280
1549
|
fi
|
|
1281
1550
|
|
|
1282
1551
|
if [[ -n "$loaded_telegram_enabled" ]]; then
|
|
@@ -1289,6 +1558,9 @@ PY
|
|
|
1289
1558
|
OPENCODE_AGENT_MODEL_MAPPER_ENABLED="$loaded_mapper_enabled"
|
|
1290
1559
|
OPENCODE_PLUGINS_CONFIGURED=true
|
|
1291
1560
|
fi
|
|
1561
|
+
if [[ -n "$loaded_opencode_profile" ]]; then
|
|
1562
|
+
SELECTED_OPENCODE_PROFILE="$loaded_opencode_profile"
|
|
1563
|
+
fi
|
|
1292
1564
|
}
|
|
1293
1565
|
|
|
1294
1566
|
path_ref_for_shell_export() {
|
|
@@ -1367,6 +1639,7 @@ ensure_bin_dir_in_shell_path() {
|
|
|
1367
1639
|
copy_dir_contents() {
|
|
1368
1640
|
local src="$1"
|
|
1369
1641
|
local dest="$2"
|
|
1642
|
+
local skip_opencode_base_config="${3:-false}"
|
|
1370
1643
|
ensure_dir "$dest"
|
|
1371
1644
|
if [[ "$DRY_RUN" == true ]]; then
|
|
1372
1645
|
log "DRY-RUN copy managed contents $src -> $dest"
|
|
@@ -1376,7 +1649,7 @@ copy_dir_contents() {
|
|
|
1376
1649
|
|
|
1377
1650
|
local event kind value events_file
|
|
1378
1651
|
events_file="$(mktemp "${TMPDIR:-/tmp}/agentic-copy-events.XXXXXX")"
|
|
1379
|
-
python3 - "$src" "$dest" "$REPO_ROOT" "$PROJECT_DIR" "$(project_manifest_path)" "$APP_REPO_LINK" "$(app_version_label)" > "$events_file" <<'PY'
|
|
1652
|
+
python3 - "$src" "$dest" "$REPO_ROOT" "$PROJECT_DIR" "$(project_manifest_path)" "$APP_REPO_LINK" "$(app_version_label)" "$skip_opencode_base_config" > "$events_file" <<'PY'
|
|
1380
1653
|
import hashlib
|
|
1381
1654
|
import json
|
|
1382
1655
|
import re
|
|
@@ -1390,6 +1663,7 @@ project_dir = Path(sys.argv[4])
|
|
|
1390
1663
|
manifest = Path(sys.argv[5])
|
|
1391
1664
|
repo = sys.argv[6]
|
|
1392
1665
|
version = sys.argv[7]
|
|
1666
|
+
skip_opencode_base_config = sys.argv[8].lower() == "true"
|
|
1393
1667
|
|
|
1394
1668
|
|
|
1395
1669
|
def emit(kind: str, value: str) -> None:
|
|
@@ -1495,6 +1769,12 @@ def add_marker(file_path: Path, target: Path, source_ref: str) -> str:
|
|
|
1495
1769
|
emit("DIR", str(dest_root))
|
|
1496
1770
|
for file_path in sorted(p for p in src.rglob("*") if p.is_file()):
|
|
1497
1771
|
rel = file_path.relative_to(src)
|
|
1772
|
+
if str(src).endswith("/extensions/opencode") and rel.parts and rel.parts[0] == "profiles":
|
|
1773
|
+
continue
|
|
1774
|
+
if str(src).endswith("/extensions/opencode") and skip_opencode_base_config and rel == Path("opencode.json"):
|
|
1775
|
+
continue
|
|
1776
|
+
if str(src).endswith("/extensions/opencode") and skip_opencode_base_config and rel == Path("plugins/telegram-notification.ts"):
|
|
1777
|
+
continue
|
|
1498
1778
|
target = dest_root / rel
|
|
1499
1779
|
project_rel = rel_to_project(target)
|
|
1500
1780
|
source_ref = rel_to_repo(file_path)
|
|
@@ -1727,11 +2007,192 @@ PY
|
|
|
1727
2007
|
fi
|
|
1728
2008
|
}
|
|
1729
2009
|
|
|
2010
|
+
|
|
2011
|
+
detect_configured_mcps() {
|
|
2012
|
+
local project_dir="$1"
|
|
2013
|
+
[[ -n "$project_dir" ]] || return 0
|
|
2014
|
+
python3 - "$project_dir" "$HOME" <<'PY'
|
|
2015
|
+
import json
|
|
2016
|
+
import re
|
|
2017
|
+
import sys
|
|
2018
|
+
from pathlib import Path
|
|
2019
|
+
project = Path(sys.argv[1]); home = Path(sys.argv[2]); found = set()
|
|
2020
|
+
server_to_id = {"opencode":"opencode-docs","playwright":"playwright","kubernetes":"kubernetes","youtube-transcript":"youtube-transcript","docker":"docker-mcp","MCP_DOCKER":"docker-mcp","context7":"context7","mempalace":"mempalace","anydb":"anydb"}
|
|
2021
|
+
json_paths = [project/"opencode.json", project/".opencode"/"opencode.json", project/".mcp.json", project/".cursor"/"mcp.json", project/".gemini"/"settings.json", project/".kilocode"/"mcp.json", home/".gemini"/"antigravity"/"mcp_config.json"]
|
|
2022
|
+
for path in json_paths:
|
|
2023
|
+
if not path.exists(): continue
|
|
2024
|
+
try: data = json.loads(path.read_text(encoding="utf-8"))
|
|
2025
|
+
except Exception: continue
|
|
2026
|
+
if not isinstance(data, dict): continue
|
|
2027
|
+
for section in ("mcpServers", "mcp"):
|
|
2028
|
+
value = data.get(section, {})
|
|
2029
|
+
if isinstance(value, dict):
|
|
2030
|
+
for server in value:
|
|
2031
|
+
if server in server_to_id: found.add(server_to_id[server])
|
|
2032
|
+
config = project/".codex"/"config.toml"
|
|
2033
|
+
if config.exists():
|
|
2034
|
+
try: text = config.read_text(encoding="utf-8")
|
|
2035
|
+
except Exception: text = ""
|
|
2036
|
+
for server, mcp_id in server_to_id.items():
|
|
2037
|
+
if re.search(rf"(?m)^\[mcp_servers\.{re.escape(server)}\]", text): found.add(mcp_id)
|
|
2038
|
+
for mcp_id in sorted(found): print(mcp_id)
|
|
2039
|
+
PY
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
write_selected_mcp_json_config() {
|
|
2043
|
+
local dest="$1"
|
|
2044
|
+
local source_ref="$2"
|
|
2045
|
+
local platform="$3"
|
|
2046
|
+
shift 3
|
|
2047
|
+
local selected_ids=("$@")
|
|
2048
|
+
[[ "${#selected_ids[@]}" -gt 0 ]] || return 0
|
|
2049
|
+
local selected_csv old_ifs registry_json
|
|
2050
|
+
old_ifs="$IFS"; IFS=,; selected_csv="${selected_ids[*]}"; IFS="$old_ifs"
|
|
2051
|
+
registry_json="$(mcp_registry_json)"
|
|
2052
|
+
local body
|
|
2053
|
+
body="$(python3 - "$platform" "$selected_csv" "$registry_json" <<'PY'
|
|
2054
|
+
import json, sys
|
|
2055
|
+
platform = sys.argv[1]
|
|
2056
|
+
selected = [x for x in sys.argv[2].split(",") if x]
|
|
2057
|
+
registry = json.loads(sys.argv[3])
|
|
2058
|
+
print('registry = ' + repr(registry))
|
|
2059
|
+
print('selected = ' + repr(selected))
|
|
2060
|
+
print('platform = ' + repr(platform))
|
|
2061
|
+
print(r'''
|
|
2062
|
+
def opencode_local(command, args=None):
|
|
2063
|
+
values = [command] + list(args or [])
|
|
2064
|
+
return {"type": "local", "command": values, "enabled": True}
|
|
2065
|
+
|
|
2066
|
+
def opencode_remote(url, headers=None):
|
|
2067
|
+
cfg = {"type": "remote", "url": url, "enabled": True}
|
|
2068
|
+
if headers:
|
|
2069
|
+
cfg["headers"] = headers
|
|
2070
|
+
return cfg
|
|
2071
|
+
|
|
2072
|
+
if platform == "opencode":
|
|
2073
|
+
data.setdefault("$schema", "https://opencode.ai/config.json")
|
|
2074
|
+
legacy_servers = data.pop("mcpServers", {})
|
|
2075
|
+
mcp = data.setdefault("mcp", {})
|
|
2076
|
+
if isinstance(legacy_servers, dict):
|
|
2077
|
+
for legacy_server, legacy_cfg in legacy_servers.items():
|
|
2078
|
+
if legacy_server in mcp:
|
|
2079
|
+
continue
|
|
2080
|
+
if not isinstance(legacy_cfg, dict):
|
|
2081
|
+
continue
|
|
2082
|
+
command = legacy_cfg.get("command")
|
|
2083
|
+
args = legacy_cfg.get("args", [])
|
|
2084
|
+
if isinstance(command, str):
|
|
2085
|
+
mcp[legacy_server] = opencode_local(command, args if isinstance(args, list) else [])
|
|
2086
|
+
for mcp_id in selected:
|
|
2087
|
+
entry = registry.get(mcp_id)
|
|
2088
|
+
if not entry:
|
|
2089
|
+
continue
|
|
2090
|
+
server = entry["server"]
|
|
2091
|
+
if platform == "opencode":
|
|
2092
|
+
if mcp_id == "docker-mcp":
|
|
2093
|
+
data.setdefault("mcp", {})[server] = dict(entry.get("opencode", {}))
|
|
2094
|
+
elif mcp_id == "context7":
|
|
2095
|
+
headers = {"CONTEXT7_API_KEY": context7_api_key} if context7_api_key else None
|
|
2096
|
+
data.setdefault("mcp", {})[server] = opencode_remote(entry.get("remote", ""), headers)
|
|
2097
|
+
else:
|
|
2098
|
+
data.setdefault("mcp", {})[server] = opencode_local(entry["command"], entry.get("args", []))
|
|
2099
|
+
continue
|
|
2100
|
+
if mcp_id == "docker-mcp":
|
|
2101
|
+
cfg = dict(entry.get("generic", {}))
|
|
2102
|
+
else:
|
|
2103
|
+
cfg = {"command": entry["command"]}
|
|
2104
|
+
args = list(entry.get("args", []))
|
|
2105
|
+
if args:
|
|
2106
|
+
cfg["args"] = args
|
|
2107
|
+
if mcp_id == "context7" and context7_api_key:
|
|
2108
|
+
cfg["env"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
2109
|
+
data.setdefault("mcpServers", {})[server] = cfg
|
|
2110
|
+
''')
|
|
2111
|
+
PY
|
|
2112
|
+
)"
|
|
2113
|
+
write_json_config_file "$dest" "$source_ref" "$body"
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
write_selected_mcp_codex_config() {
|
|
2117
|
+
local selected_ids=("$@")
|
|
2118
|
+
[[ "${#selected_ids[@]}" -gt 0 ]] || return 0
|
|
2119
|
+
local dest="$PROJECT_DIR/.codex/config.toml"
|
|
2120
|
+
local selected_csv old_ifs registry_json
|
|
2121
|
+
old_ifs="$IFS"; IFS=,; selected_csv="${selected_ids[*]}"; IFS="$old_ifs"
|
|
2122
|
+
registry_json="$(mcp_registry_json)"
|
|
2123
|
+
local body
|
|
2124
|
+
body="$(python3 - "$dest" "$selected_csv" "$registry_json" "$CONTEXT7_API_KEY" <<'PY'
|
|
2125
|
+
import json, re, sys
|
|
2126
|
+
from pathlib import Path
|
|
2127
|
+
path = Path(sys.argv[1]); selected = [x for x in sys.argv[2].split(",") if x]; registry = json.loads(sys.argv[3]); context7_api_key = sys.argv[4]
|
|
2128
|
+
text = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
2129
|
+
for mcp_id in selected:
|
|
2130
|
+
entry = registry.get(mcp_id)
|
|
2131
|
+
if not entry: continue
|
|
2132
|
+
server = entry["server"]
|
|
2133
|
+
text = re.sub(rf"(?ms)^\[mcp_servers\.{re.escape(server)}\]\n.*?(?=^\[|\Z)", "", text).strip()
|
|
2134
|
+
if mcp_id == "docker-mcp": command, args = "docker", ["mcp", "gateway", "run"]
|
|
2135
|
+
else: command, args = entry["command"], entry.get("args", [])
|
|
2136
|
+
block = f"[mcp_servers.{server}]\ncommand = {json.dumps(command)}\n"
|
|
2137
|
+
if args: block += "args = [" + ", ".join(json.dumps(a) for a in args) + "]\n"
|
|
2138
|
+
if mcp_id == "context7" and context7_api_key: block += 'env = { "CONTEXT7_API_KEY" = ' + json.dumps(context7_api_key) + ' }\n'
|
|
2139
|
+
text = (text.rstrip() + "\n\n" + block).strip() if text else block.strip()
|
|
2140
|
+
print(text.rstrip() + "\n", end="")
|
|
2141
|
+
PY
|
|
2142
|
+
)"
|
|
2143
|
+
write_text_config_file "$dest" "generated:mcp-codex-config" "$body"
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
configure_selected_mcps_if_needed() {
|
|
2147
|
+
sync_selected_mcps_from_env
|
|
2148
|
+
sync_legacy_mcp_env_from_selected
|
|
2149
|
+
[[ "${#SELECTED_MCPS[@]}" -gt 0 ]] || return 0
|
|
2150
|
+
local generic_mcps=()
|
|
2151
|
+
local mcp_id
|
|
2152
|
+
for mcp_id in "${SELECTED_MCPS[@]}"; do
|
|
2153
|
+
case "$mcp_id" in context7|mempalace) ;; *) generic_mcps+=("$mcp_id") ;; esac
|
|
2154
|
+
done
|
|
2155
|
+
[[ "${#generic_mcps[@]}" -gt 0 ]] || return 0
|
|
2156
|
+
if selected_agent_os_contains "opencode"; then
|
|
2157
|
+
write_selected_mcp_json_config "$PROJECT_DIR/opencode.json" "generated:mcp-opencode-config" "opencode" "${generic_mcps[@]}"
|
|
2158
|
+
write_selected_mcp_json_config "$PROJECT_DIR/.opencode/opencode.json" "generated:mcp-opencode-legacy-config" "opencode" "${generic_mcps[@]}"
|
|
2159
|
+
fi
|
|
2160
|
+
if selected_agent_os_contains "codex"; then write_selected_mcp_codex_config "${generic_mcps[@]}"; fi
|
|
2161
|
+
if selected_agent_os_contains "claude"; then write_selected_mcp_json_config "$PROJECT_DIR/.mcp.json" "generated:mcp-claude-config" "generic" "${generic_mcps[@]}"; fi
|
|
2162
|
+
if selected_agent_os_contains "cursor"; then write_selected_mcp_json_config "$PROJECT_DIR/.cursor/mcp.json" "generated:mcp-cursor-config" "generic" "${generic_mcps[@]}"; fi
|
|
2163
|
+
if selected_agent_os_contains "gemini"; then write_selected_mcp_json_config "$PROJECT_DIR/.gemini/settings.json" "generated:mcp-gemini-config" "gemini" "${generic_mcps[@]}"; fi
|
|
2164
|
+
if selected_agent_os_contains "kilocode"; then write_selected_mcp_json_config "$PROJECT_DIR/.kilocode/mcp.json" "generated:mcp-kilocode-config" "generic" "${generic_mcps[@]}"; fi
|
|
2165
|
+
if selected_agent_os_contains "antigravity"; then write_selected_mcp_json_config "$HOME/.gemini/antigravity/mcp_config.json" "generated:mcp-antigravity-config" "generic" "${generic_mcps[@]}"; fi
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
check_selected_mcp_runtime_prerequisites() {
|
|
2169
|
+
if selected_mcp_contains "kubernetes"; then
|
|
2170
|
+
if ! kubectl version >/dev/null 2>&1; then
|
|
2171
|
+
warn "Kubernetes MCP selected, but 'kubectl version' did not complete successfully. Install or configure kubectl: https://kubernetes.io/docs/tasks/tools/"
|
|
2172
|
+
fi
|
|
2173
|
+
fi
|
|
2174
|
+
|
|
2175
|
+
if selected_mcp_contains "docker-mcp"; then
|
|
2176
|
+
if ! docker mcp --version >/dev/null 2>&1; then
|
|
2177
|
+
warn "Docker MCP selected, but 'docker mcp --version' did not complete successfully. Install Docker and Docker MCP support: https://docs.docker.com/get-started/get-docker/ and https://docs.docker.com/ai/mcp-catalog-and-toolkit/"
|
|
2178
|
+
fi
|
|
2179
|
+
fi
|
|
2180
|
+
}
|
|
2181
|
+
|
|
1730
2182
|
write_context7_opencode_config() {
|
|
1731
2183
|
local dest="$PROJECT_DIR/opencode.json"
|
|
1732
2184
|
local body
|
|
1733
2185
|
body='
|
|
2186
|
+
legacy_servers = data.pop("mcpServers", {})
|
|
1734
2187
|
mcp = data.setdefault("mcp", {})
|
|
2188
|
+
if isinstance(legacy_servers, dict):
|
|
2189
|
+
for legacy_server, legacy_cfg in legacy_servers.items():
|
|
2190
|
+
if legacy_server in mcp or not isinstance(legacy_cfg, dict):
|
|
2191
|
+
continue
|
|
2192
|
+
command = legacy_cfg.get("command")
|
|
2193
|
+
args = legacy_cfg.get("args", [])
|
|
2194
|
+
if isinstance(command, str):
|
|
2195
|
+
mcp[legacy_server] = {"type": "local", "command": [command] + (args if isinstance(args, list) else []), "enabled": True}
|
|
1735
2196
|
context7 = {
|
|
1736
2197
|
"type": "remote",
|
|
1737
2198
|
"url": "https://mcp.context7.com/mcp",
|
|
@@ -1748,7 +2209,16 @@ write_context7_opencode_legacy_config() {
|
|
|
1748
2209
|
local dest="$PROJECT_DIR/.opencode/opencode.json"
|
|
1749
2210
|
local body
|
|
1750
2211
|
body='
|
|
2212
|
+
legacy_servers = data.pop("mcpServers", {})
|
|
1751
2213
|
mcp = data.setdefault("mcp", {})
|
|
2214
|
+
if isinstance(legacy_servers, dict):
|
|
2215
|
+
for legacy_server, legacy_cfg in legacy_servers.items():
|
|
2216
|
+
if legacy_server in mcp or not isinstance(legacy_cfg, dict):
|
|
2217
|
+
continue
|
|
2218
|
+
command = legacy_cfg.get("command")
|
|
2219
|
+
args = legacy_cfg.get("args", [])
|
|
2220
|
+
if isinstance(command, str):
|
|
2221
|
+
mcp[legacy_server] = {"type": "local", "command": [command] + (args if isinstance(args, list) else []), "enabled": True}
|
|
1752
2222
|
context7 = {
|
|
1753
2223
|
"type": "remote",
|
|
1754
2224
|
"url": "https://mcp.context7.com/mcp",
|
|
@@ -1761,6 +2231,51 @@ mcp["context7"] = context7
|
|
|
1761
2231
|
write_json_config_file "$dest" "generated:context7-opencode-legacy-config" "$body"
|
|
1762
2232
|
}
|
|
1763
2233
|
|
|
2234
|
+
write_codex_features_config() {
|
|
2235
|
+
local dest="$PROJECT_DIR/.codex/config.toml"
|
|
2236
|
+
local body
|
|
2237
|
+
body="$(python3 - "$dest" <<'PY'
|
|
2238
|
+
import re
|
|
2239
|
+
import sys
|
|
2240
|
+
from pathlib import Path
|
|
2241
|
+
|
|
2242
|
+
path = Path(sys.argv[1])
|
|
2243
|
+
text = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
2244
|
+
features_re = re.compile(r"(?ms)^(\[features\]\n)(.*?)(?=^\[|\Z)")
|
|
2245
|
+
|
|
2246
|
+
|
|
2247
|
+
def enable_memories(match):
|
|
2248
|
+
header = match.group(1)
|
|
2249
|
+
body = match.group(2)
|
|
2250
|
+
lines = body.splitlines(keepends=True)
|
|
2251
|
+
output = []
|
|
2252
|
+
found = False
|
|
2253
|
+
for line in lines:
|
|
2254
|
+
if re.match(r"\s*memories\s*=", line):
|
|
2255
|
+
output.append("memories = true" + ("\n" if line.endswith("\n") else ""))
|
|
2256
|
+
found = True
|
|
2257
|
+
else:
|
|
2258
|
+
output.append(line)
|
|
2259
|
+
if not found:
|
|
2260
|
+
trailing = re.search(r"\n*\Z", body).group(0)
|
|
2261
|
+
main = body[: len(body) - len(trailing)] if trailing else body
|
|
2262
|
+
if main and not main.endswith("\n"):
|
|
2263
|
+
main += "\n"
|
|
2264
|
+
return header + main + "memories = true\n" + trailing
|
|
2265
|
+
return header + "".join(output)
|
|
2266
|
+
|
|
2267
|
+
|
|
2268
|
+
if features_re.search(text):
|
|
2269
|
+
print(features_re.sub(enable_memories, text, count=1), end="")
|
|
2270
|
+
elif text.strip():
|
|
2271
|
+
print(text.rstrip() + "\n\n[features]\nmemories = true\n", end="")
|
|
2272
|
+
else:
|
|
2273
|
+
print("[features]\nmemories = true\n", end="")
|
|
2274
|
+
PY
|
|
2275
|
+
)"
|
|
2276
|
+
write_text_config_file "$dest" "generated:codex-features-config" "$body"
|
|
2277
|
+
}
|
|
2278
|
+
|
|
1764
2279
|
write_context7_codex_config() {
|
|
1765
2280
|
local dest="$PROJECT_DIR/.codex/config.toml"
|
|
1766
2281
|
local body
|
|
@@ -1900,12 +2415,98 @@ write_mempalace_opencode_config() {
|
|
|
1900
2415
|
local dest="$1"
|
|
1901
2416
|
local body
|
|
1902
2417
|
body='
|
|
2418
|
+
legacy_servers = data.pop("mcpServers", {})
|
|
1903
2419
|
mcp = data.setdefault("mcp", {})
|
|
2420
|
+
if isinstance(legacy_servers, dict):
|
|
2421
|
+
for legacy_server, legacy_cfg in legacy_servers.items():
|
|
2422
|
+
if legacy_server in mcp or not isinstance(legacy_cfg, dict):
|
|
2423
|
+
continue
|
|
2424
|
+
command = legacy_cfg.get("command")
|
|
2425
|
+
args = legacy_cfg.get("args", [])
|
|
2426
|
+
if isinstance(command, str):
|
|
2427
|
+
mcp[legacy_server] = {"type": "local", "command": [command] + (args if isinstance(args, list) else []), "enabled": True}
|
|
1904
2428
|
mcp["mempalace"] = {"type": "local", "command": ["mempalace-mcp"]}
|
|
1905
2429
|
'
|
|
1906
2430
|
write_json_config_file "$dest" "generated:mempalace-opencode-config" "$body"
|
|
1907
2431
|
}
|
|
1908
2432
|
|
|
2433
|
+
configure_opencode_profile_if_needed() {
|
|
2434
|
+
selected_agent_os_contains "opencode" || return 0
|
|
2435
|
+
|
|
2436
|
+
local profile_id="${AGENTIC_OPENCODE_PROFILE:-$SELECTED_OPENCODE_PROFILE}"
|
|
2437
|
+
profile_id="$(trim "$profile_id")"
|
|
2438
|
+
case "$profile_id" in
|
|
2439
|
+
none|None|skip|Skip|no|No) return 0 ;;
|
|
2440
|
+
esac
|
|
2441
|
+
[[ -n "$profile_id" ]] || return 0
|
|
2442
|
+
if ! opencode_profile_contains "$profile_id"; then
|
|
2443
|
+
warn "Ignoring unknown OpenCode profile '$profile_id'"
|
|
2444
|
+
return 0
|
|
2445
|
+
fi
|
|
2446
|
+
SELECTED_OPENCODE_PROFILE="$profile_id"
|
|
2447
|
+
|
|
2448
|
+
local src
|
|
2449
|
+
src="$(opencode_profile_source_path "$profile_id")"
|
|
2450
|
+
local dest="$PROJECT_DIR/.opencode/opencode.json"
|
|
2451
|
+
if [[ ! -f "$src" ]]; then
|
|
2452
|
+
warn "OpenCode profile not found: $src"
|
|
2453
|
+
return 0
|
|
2454
|
+
fi
|
|
2455
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
2456
|
+
log "DRY-RUN apply OpenCode profile $(opencode_profile_label "$profile_id") to $dest"
|
|
2457
|
+
unique_append "$dest" COPIED_PATHS
|
|
2458
|
+
return 0
|
|
2459
|
+
fi
|
|
2460
|
+
can_write_managed_file "$dest" || return 0
|
|
2461
|
+
ensure_dir "$(dirname -- "$dest")"
|
|
2462
|
+
|
|
2463
|
+
local write_status
|
|
2464
|
+
write_status="$(python3 - "$src" "$dest" <<'PY'
|
|
2465
|
+
import json
|
|
2466
|
+
import sys
|
|
2467
|
+
from pathlib import Path
|
|
2468
|
+
|
|
2469
|
+
src = Path(sys.argv[1])
|
|
2470
|
+
dest = Path(sys.argv[2])
|
|
2471
|
+
profile = json.loads(src.read_text(encoding="utf-8"))
|
|
2472
|
+
if not isinstance(profile, dict):
|
|
2473
|
+
raise SystemExit("OpenCode profile must be a JSON object")
|
|
2474
|
+
try:
|
|
2475
|
+
data = json.loads(dest.read_text(encoding="utf-8")) if dest.exists() else {}
|
|
2476
|
+
except Exception:
|
|
2477
|
+
data = {}
|
|
2478
|
+
if not isinstance(data, dict):
|
|
2479
|
+
data = {}
|
|
2480
|
+
|
|
2481
|
+
def merge(base, incoming):
|
|
2482
|
+
for key, value in incoming.items():
|
|
2483
|
+
if isinstance(value, dict) and isinstance(base.get(key), dict):
|
|
2484
|
+
merge(base[key], value)
|
|
2485
|
+
else:
|
|
2486
|
+
base[key] = value
|
|
2487
|
+
return base
|
|
2488
|
+
|
|
2489
|
+
merge(data, profile)
|
|
2490
|
+
output = json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
|
2491
|
+
if dest.exists():
|
|
2492
|
+
try:
|
|
2493
|
+
if dest.read_text(encoding="utf-8") == output:
|
|
2494
|
+
print("unchanged")
|
|
2495
|
+
raise SystemExit(0)
|
|
2496
|
+
except UnicodeDecodeError:
|
|
2497
|
+
pass
|
|
2498
|
+
dest.write_text(output, encoding="utf-8")
|
|
2499
|
+
print("written")
|
|
2500
|
+
PY
|
|
2501
|
+
)"
|
|
2502
|
+
if [[ "$write_status" == "unchanged" ]]; then
|
|
2503
|
+
register_managed_file "$dest" "generated:opencode-profile-$profile_id" "config" false
|
|
2504
|
+
else
|
|
2505
|
+
register_managed_file "$dest" "generated:opencode-profile-$profile_id" "config"
|
|
2506
|
+
fi
|
|
2507
|
+
log "Applied OpenCode profile: $(opencode_profile_label "$profile_id")"
|
|
2508
|
+
}
|
|
2509
|
+
|
|
1909
2510
|
write_mempalace_codex_config() {
|
|
1910
2511
|
local dest="$PROJECT_DIR/.codex/config.toml"
|
|
1911
2512
|
local body
|
|
@@ -2526,7 +3127,19 @@ configure_opencode_plugins_if_needed() {
|
|
|
2526
3127
|
return
|
|
2527
3128
|
fi
|
|
2528
3129
|
|
|
2529
|
-
local plugin_options=(
|
|
3130
|
+
local plugin_options=(
|
|
3131
|
+
"$(opencode_plugin_label "telegram-notification")"
|
|
3132
|
+
"$(opencode_plugin_label "agent-model-mapper")"
|
|
3133
|
+
"$(opencode_profile_label "openai")"
|
|
3134
|
+
"$(opencode_profile_label "githubcopilot")"
|
|
3135
|
+
)
|
|
3136
|
+
local user_profile_id
|
|
3137
|
+
while IFS= read -r user_profile_id; do
|
|
3138
|
+
[[ -z "$user_profile_id" ]] && continue
|
|
3139
|
+
opencode_builtin_profile_contains "$user_profile_id" && continue
|
|
3140
|
+
opencode_profile_contains "$user_profile_id" || continue
|
|
3141
|
+
plugin_options+=("$(opencode_profile_label "$user_profile_id")")
|
|
3142
|
+
done < <(opencode_user_profile_ids)
|
|
2530
3143
|
local selected_plugins=()
|
|
2531
3144
|
local use_fzf_plugins=false
|
|
2532
3145
|
if fzf_available; then
|
|
@@ -2545,12 +3158,20 @@ configure_opencode_plugins_if_needed() {
|
|
|
2545
3158
|
|
|
2546
3159
|
local enable_telegram="n" enable_agent_model_mapper="n"
|
|
2547
3160
|
local selected_plugin
|
|
2548
|
-
for selected_plugin in "${selected_plugins[@]}"; do
|
|
3161
|
+
for selected_plugin in ${selected_plugins[@]+"${selected_plugins[@]}"}; do
|
|
2549
3162
|
selected_plugin="$(trim "$selected_plugin")"
|
|
2550
3163
|
[[ -z "$selected_plugin" ]] && continue
|
|
3164
|
+
selected_plugin="$(opencode_plugin_id_from_label "$selected_plugin")"
|
|
2551
3165
|
case "$selected_plugin" in
|
|
2552
3166
|
telegram-notification|telegram-opencode-notifier) enable_telegram="y" ;;
|
|
2553
3167
|
agent-model-mapper) enable_agent_model_mapper="y" ;;
|
|
3168
|
+
*)
|
|
3169
|
+
local selected_profile_id
|
|
3170
|
+
selected_profile_id="$(opencode_profile_id_from_label "$selected_plugin")"
|
|
3171
|
+
if opencode_profile_contains "$selected_profile_id"; then
|
|
3172
|
+
SELECTED_OPENCODE_PROFILE="$selected_profile_id"
|
|
3173
|
+
fi
|
|
3174
|
+
;;
|
|
2554
3175
|
esac
|
|
2555
3176
|
done
|
|
2556
3177
|
|
|
@@ -3045,7 +3666,17 @@ copy_extension_for_agent() {
|
|
|
3045
3666
|
return
|
|
3046
3667
|
fi
|
|
3047
3668
|
|
|
3048
|
-
|
|
3669
|
+
local skip_opencode_base_config=false
|
|
3670
|
+
if [[ "$agent_os" == "opencode" ]]; then
|
|
3671
|
+
local profile_id="${AGENTIC_OPENCODE_PROFILE:-$SELECTED_OPENCODE_PROFILE}"
|
|
3672
|
+
if opencode_profile_is_none "$profile_id" \
|
|
3673
|
+
&& [[ "$OPENCODE_TELEGRAM_ENABLED" != "true" ]] \
|
|
3674
|
+
&& [[ "$OPENCODE_AGENT_MODEL_MAPPER_ENABLED" != "true" ]]; then
|
|
3675
|
+
skip_opencode_base_config=true
|
|
3676
|
+
fi
|
|
3677
|
+
fi
|
|
3678
|
+
|
|
3679
|
+
copy_dir_contents "$src" "$dest" "$skip_opencode_base_config"
|
|
3049
3680
|
}
|
|
3050
3681
|
|
|
3051
3682
|
copy_extensions() {
|
|
@@ -3165,23 +3796,12 @@ append_root_agents_template() {
|
|
|
3165
3796
|
generate_agents_md() {
|
|
3166
3797
|
local project_dir="$1"
|
|
3167
3798
|
local outputs=()
|
|
3168
|
-
local needs_root=false
|
|
3169
|
-
local agent_os
|
|
3170
3799
|
|
|
3171
3800
|
if selected_agent_os_contains "opencode"; then
|
|
3172
3801
|
unique_append "$project_dir/.opencode/AGENTS.md" outputs
|
|
3173
3802
|
fi
|
|
3174
3803
|
|
|
3175
|
-
|
|
3176
|
-
if [[ "$agent_os" != "opencode" ]]; then
|
|
3177
|
-
needs_root=true
|
|
3178
|
-
break
|
|
3179
|
-
fi
|
|
3180
|
-
done
|
|
3181
|
-
|
|
3182
|
-
if [[ "$needs_root" == true ]] || ! selected_agent_os_contains "opencode"; then
|
|
3183
|
-
unique_append "$project_dir/AGENTS.md" outputs
|
|
3184
|
-
fi
|
|
3804
|
+
unique_append "$project_dir/AGENTS.md" outputs
|
|
3185
3805
|
|
|
3186
3806
|
if [[ "$DRY_RUN" == true ]]; then
|
|
3187
3807
|
local dry_run_out
|
|
@@ -3651,15 +4271,24 @@ run_install() {
|
|
|
3651
4271
|
normalize_selected_agent_os
|
|
3652
4272
|
validate_inputs
|
|
3653
4273
|
|
|
4274
|
+
PROJECT_DIR="$(normalize_project_dir_path "$PROJECT_DIR")"
|
|
3654
4275
|
ensure_dir "$PROJECT_DIR"
|
|
3655
4276
|
configure_opencode_plugins_if_needed
|
|
3656
4277
|
copy_extensions "$PROJECT_DIR"
|
|
4278
|
+
configure_opencode_profile_if_needed
|
|
3657
4279
|
configure_opencode_agent_model_mapper_if_needed
|
|
3658
4280
|
copy_specialization_assets "$PROJECT_DIR"
|
|
3659
4281
|
generate_agents_md "$PROJECT_DIR"
|
|
3660
4282
|
copy_memory_md "$PROJECT_DIR"
|
|
4283
|
+
if selected_agent_os_contains "codex"; then
|
|
4284
|
+
write_codex_features_config
|
|
4285
|
+
fi
|
|
4286
|
+
sync_selected_mcps_from_env
|
|
4287
|
+
sync_legacy_mcp_env_from_selected
|
|
3661
4288
|
configure_context7_if_needed
|
|
3662
4289
|
configure_mempalace_if_needed
|
|
4290
|
+
configure_selected_mcps_if_needed
|
|
4291
|
+
check_selected_mcp_runtime_prerequisites
|
|
3663
4292
|
write_agentic_manifest "$PROJECT_DIR"
|
|
3664
4293
|
print_report
|
|
3665
4294
|
print_missing_agent_binary_guides
|
|
@@ -3865,10 +4494,12 @@ choose_multi_by_index() {
|
|
|
3865
4494
|
unique_append "${options[$((idx - 1))]}" out
|
|
3866
4495
|
done
|
|
3867
4496
|
|
|
3868
|
-
printf '%s\n' "${out[@]}"
|
|
4497
|
+
printf '%s\n' ${out[@]+"${out[@]}"}
|
|
3869
4498
|
}
|
|
3870
4499
|
|
|
3871
4500
|
fzf_available() {
|
|
4501
|
+
[[ "${AGENTIC_DISABLE_FZF:-}" != "1" ]] || return 1
|
|
4502
|
+
[[ "${AGENTIC_AGENT_MODEL_MAPPER_NO_FZF:-}" != "1" ]] || return 1
|
|
3872
4503
|
command -v fzf >/dev/null 2>&1
|
|
3873
4504
|
}
|
|
3874
4505
|
|
|
@@ -3931,11 +4562,88 @@ auto_install_fzf_windows() {
|
|
|
3931
4562
|
return 1
|
|
3932
4563
|
}
|
|
3933
4564
|
|
|
4565
|
+
add_fzf_to_path_if_installed() {
|
|
4566
|
+
local candidate candidate_dir
|
|
4567
|
+
for candidate in "/opt/homebrew/bin/fzf" "/usr/local/bin/fzf"; do
|
|
4568
|
+
if [[ -x "$candidate" ]]; then
|
|
4569
|
+
candidate_dir="$(dirname -- "$candidate")"
|
|
4570
|
+
case ":$PATH:" in
|
|
4571
|
+
*:"$candidate_dir":*) ;;
|
|
4572
|
+
*) PATH="$candidate_dir:$PATH" ;;
|
|
4573
|
+
esac
|
|
4574
|
+
return 0
|
|
4575
|
+
fi
|
|
4576
|
+
done
|
|
4577
|
+
return 1
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
fzf_install_hint() {
|
|
4581
|
+
local platform
|
|
4582
|
+
platform="$(detect_platform)"
|
|
4583
|
+
case "$platform" in
|
|
4584
|
+
macos)
|
|
4585
|
+
cat <<'HINT'
|
|
4586
|
+
Install fzf on macOS with one of:
|
|
4587
|
+
brew install fzf
|
|
4588
|
+
/opt/homebrew/opt/fzf/install
|
|
4589
|
+
If Homebrew is not installed, install it from https://brew.sh/ first.
|
|
4590
|
+
HINT
|
|
4591
|
+
;;
|
|
4592
|
+
linux)
|
|
4593
|
+
cat <<'HINT'
|
|
4594
|
+
Install fzf with your package manager, for example:
|
|
4595
|
+
sudo apt-get install -y fzf
|
|
4596
|
+
sudo dnf install -y fzf
|
|
4597
|
+
HINT
|
|
4598
|
+
;;
|
|
4599
|
+
windows)
|
|
4600
|
+
cat <<'HINT'
|
|
4601
|
+
Install fzf with one of:
|
|
4602
|
+
winget install --id junegunn.fzf -e
|
|
4603
|
+
choco install fzf -y
|
|
4604
|
+
scoop install fzf
|
|
4605
|
+
HINT
|
|
4606
|
+
;;
|
|
4607
|
+
*)
|
|
4608
|
+
echo "Install fzf from https://github.com/junegunn/fzf and re-run agentic tui."
|
|
4609
|
+
;;
|
|
4610
|
+
esac
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
print_fzf_install_hint() {
|
|
4614
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
4615
|
+
[[ -n "$line" ]] && warn "$line"
|
|
4616
|
+
done < <(fzf_install_hint)
|
|
4617
|
+
}
|
|
4618
|
+
|
|
3934
4619
|
auto_install_fzf_macos() {
|
|
3935
|
-
if
|
|
3936
|
-
|
|
4620
|
+
if add_fzf_to_path_if_installed && fzf_available; then
|
|
4621
|
+
return 0
|
|
4622
|
+
fi
|
|
4623
|
+
|
|
4624
|
+
if ! command -v brew >/dev/null 2>&1; then
|
|
4625
|
+
warn "Homebrew was not found; cannot auto-install fzf on macOS."
|
|
4626
|
+
print_fzf_install_hint
|
|
4627
|
+
return 1
|
|
4628
|
+
fi
|
|
4629
|
+
|
|
4630
|
+
if ! brew install fzf; then
|
|
4631
|
+
warn "Homebrew failed to install fzf."
|
|
4632
|
+
print_fzf_install_hint
|
|
4633
|
+
return 1
|
|
4634
|
+
fi
|
|
4635
|
+
|
|
4636
|
+
if fzf_available; then
|
|
4637
|
+
return 0
|
|
4638
|
+
fi
|
|
4639
|
+
|
|
4640
|
+
if add_fzf_to_path_if_installed && fzf_available; then
|
|
4641
|
+
log "Added installed fzf to PATH for this session"
|
|
3937
4642
|
return 0
|
|
3938
4643
|
fi
|
|
4644
|
+
|
|
4645
|
+
warn "fzf was installed but is not available in PATH for this shell."
|
|
4646
|
+
print_fzf_install_hint
|
|
3939
4647
|
return 1
|
|
3940
4648
|
}
|
|
3941
4649
|
|
|
@@ -4052,7 +4760,7 @@ choose_multi_fzf_strict() {
|
|
|
4052
4760
|
readlines picked < <(choose_multi_fzf "$prompt" "$sentinel" "${options[@]}")
|
|
4053
4761
|
|
|
4054
4762
|
local item
|
|
4055
|
-
for item in "${picked[@]}"; do
|
|
4763
|
+
for item in ${picked[@]+"${picked[@]}"}; do
|
|
4056
4764
|
item="$(trim "$item")"
|
|
4057
4765
|
[[ -z "$item" || "$item" == "$sentinel" ]] && continue
|
|
4058
4766
|
printf '%s\n' "$item"
|
|
@@ -4144,54 +4852,81 @@ run_tui() {
|
|
|
4144
4852
|
|
|
4145
4853
|
local picked_agent_os=()
|
|
4146
4854
|
if [[ "$use_fzf" == true ]]; then
|
|
4147
|
-
readlines picked_agent_os < <(choose_multi_fzf "Select Agent OS target(s):" "${agentos_choices[@]}")
|
|
4855
|
+
readlines picked_agent_os < <(choose_multi_fzf "Select Agent OS target(s):" ${agentos_choices[@]+"${agentos_choices[@]}"})
|
|
4148
4856
|
else
|
|
4149
4857
|
local picked_agent_os_output
|
|
4150
|
-
picked_agent_os_output="$(choose_multi_by_index "Select Agent OS target(s):" "${agentos_choices[@]}")"
|
|
4858
|
+
picked_agent_os_output="$(choose_multi_by_index "Select Agent OS target(s):" ${agentos_choices[@]+"${agentos_choices[@]}"})"
|
|
4151
4859
|
readlines picked_agent_os <<< "$picked_agent_os_output"
|
|
4152
4860
|
fi
|
|
4153
4861
|
if [[ "${#picked_agent_os[@]}" -eq 0 ]]; then
|
|
4154
4862
|
SELECTED_AGENT_OS=("$DEFAULT_AGENT_OS")
|
|
4155
4863
|
else
|
|
4156
|
-
SELECTED_AGENT_OS=("${picked_agent_os[@]}")
|
|
4157
|
-
fi
|
|
4864
|
+
SELECTED_AGENT_OS=(${picked_agent_os[@]+"${picked_agent_os[@]}"})
|
|
4865
|
+
fi
|
|
4866
|
+
|
|
4867
|
+
local detected_mcps=()
|
|
4868
|
+
readlines detected_mcps < <(detect_configured_mcps "$PROJECT_DIR" || true)
|
|
4869
|
+
local mcp_options=("$MCP_NONE_OPTION")
|
|
4870
|
+
local registry_mcp
|
|
4871
|
+
for registry_mcp in "${MCP_REGISTRY_IDS[@]}"; do
|
|
4872
|
+
local checked=false
|
|
4873
|
+
local detected_mcp
|
|
4874
|
+
for detected_mcp in ${detected_mcps[@]+"${detected_mcps[@]}"}; do
|
|
4875
|
+
if [[ "$detected_mcp" == "$registry_mcp" ]]; then
|
|
4876
|
+
checked=true
|
|
4877
|
+
break
|
|
4878
|
+
fi
|
|
4879
|
+
done
|
|
4880
|
+
mcp_options+=("$(mcp_display_row "$registry_mcp" "$checked")")
|
|
4881
|
+
done
|
|
4158
4882
|
|
|
4159
|
-
local mcp_options=("context7" "mempalace")
|
|
4160
4883
|
local picked_mcps=()
|
|
4161
4884
|
if [[ "$use_fzf" == true ]]; then
|
|
4162
|
-
readlines picked_mcps < <(
|
|
4885
|
+
readlines picked_mcps < <(choose_multi_fzf "Select MCP servers to enable:" ${mcp_options[@]+"${mcp_options[@]}"})
|
|
4163
4886
|
else
|
|
4164
4887
|
local picked_mcps_output
|
|
4165
|
-
picked_mcps_output="$(choose_multi_by_index "Select
|
|
4888
|
+
picked_mcps_output="$(choose_multi_by_index "Select MCP servers to enable:" ${mcp_options[@]+"${mcp_options[@]}"})"
|
|
4166
4889
|
readlines picked_mcps <<< "$picked_mcps_output"
|
|
4167
4890
|
fi
|
|
4168
4891
|
|
|
4892
|
+
SELECTED_MCPS=()
|
|
4169
4893
|
AGENTIC_ENABLE_CONTEXT7="n"
|
|
4170
4894
|
AGENTIC_ENABLE_MEMPALACE="n"
|
|
4171
|
-
local picked_mcp
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4895
|
+
local picked_mcp picked_mcp_id
|
|
4896
|
+
if [[ "${#picked_mcps[@]}" -eq 0 && "${#detected_mcps[@]}" -gt 0 ]]; then
|
|
4897
|
+
for picked_mcp_id in ${detected_mcps[@]+"${detected_mcps[@]}"}; do
|
|
4898
|
+
add_selected_mcp "$picked_mcp_id"
|
|
4899
|
+
done
|
|
4900
|
+
else
|
|
4901
|
+
for picked_mcp in ${picked_mcps[@]+"${picked_mcps[@]}"}; do
|
|
4902
|
+
picked_mcp="$(trim "$picked_mcp")"
|
|
4903
|
+
[[ -z "$picked_mcp" ]] && continue
|
|
4904
|
+
if [[ "$picked_mcp" == "$MCP_NONE_OPTION" ]]; then
|
|
4905
|
+
SELECTED_MCPS=()
|
|
4906
|
+
break
|
|
4907
|
+
fi
|
|
4908
|
+
picked_mcp_id="$(mcp_id_from_display_row "$picked_mcp")"
|
|
4909
|
+
add_selected_mcp "$picked_mcp_id"
|
|
4910
|
+
done
|
|
4911
|
+
fi
|
|
4912
|
+
sync_legacy_mcp_env_from_selected
|
|
4178
4913
|
|
|
4179
4914
|
local areas=()
|
|
4180
4915
|
readlines areas < <(list_areas)
|
|
4181
4916
|
|
|
4182
4917
|
local picked_areas=()
|
|
4183
4918
|
if [[ "$use_fzf" == true ]]; then
|
|
4184
|
-
readlines picked_areas < <(choose_multi_fzf "Select area(s):" "${areas[@]}")
|
|
4919
|
+
readlines picked_areas < <(choose_multi_fzf "Select area(s):" ${areas[@]+"${areas[@]}"})
|
|
4185
4920
|
else
|
|
4186
4921
|
local picked_areas_output
|
|
4187
|
-
picked_areas_output="$(choose_multi_by_index "Select area(s):" "${areas[@]}")"
|
|
4922
|
+
picked_areas_output="$(choose_multi_by_index "Select area(s):" ${areas[@]+"${areas[@]}"})"
|
|
4188
4923
|
readlines picked_areas <<< "$picked_areas_output"
|
|
4189
4924
|
fi
|
|
4190
4925
|
|
|
4191
4926
|
if [[ "${#picked_areas[@]}" -eq 0 ]]; then
|
|
4192
4927
|
SELECTED_AREAS=(software)
|
|
4193
4928
|
else
|
|
4194
|
-
SELECTED_AREAS=("${picked_areas[@]}")
|
|
4929
|
+
SELECTED_AREAS=(${picked_areas[@]+"${picked_areas[@]}"})
|
|
4195
4930
|
fi
|
|
4196
4931
|
|
|
4197
4932
|
SELECTED_SPECS=()
|
|
@@ -4202,10 +4937,10 @@ run_tui() {
|
|
|
4202
4937
|
|
|
4203
4938
|
local chosen_specs=()
|
|
4204
4939
|
if [[ "$use_fzf" == true ]]; then
|
|
4205
|
-
readlines chosen_specs < <(choose_multi_fzf "Select specialization(s) for '$area':" "${specs[@]}")
|
|
4940
|
+
readlines chosen_specs < <(choose_multi_fzf "Select specialization(s) for '$area':" ${specs[@]+"${specs[@]}"})
|
|
4206
4941
|
else
|
|
4207
4942
|
local chosen_specs_output
|
|
4208
|
-
chosen_specs_output="$(choose_multi_by_index "Select specialization(s) for '$area':" "${specs[@]}")"
|
|
4943
|
+
chosen_specs_output="$(choose_multi_by_index "Select specialization(s) for '$area':" ${specs[@]+"${specs[@]}"})"
|
|
4209
4944
|
readlines chosen_specs <<< "$chosen_specs_output"
|
|
4210
4945
|
fi
|
|
4211
4946
|
|
|
@@ -4215,7 +4950,7 @@ run_tui() {
|
|
|
4215
4950
|
fi
|
|
4216
4951
|
|
|
4217
4952
|
local spec
|
|
4218
|
-
for spec in "${chosen_specs[@]}"; do
|
|
4953
|
+
for spec in ${chosen_specs[@]+"${chosen_specs[@]}"}; do
|
|
4219
4954
|
SELECTED_SPECS+=("$area.$spec")
|
|
4220
4955
|
done
|
|
4221
4956
|
done
|