@jetrabbits/agentic 0.3.0 → 0.3.1
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 +11 -4
- package/CHANGELOG.md +13 -0
- package/MEMORY.md +41 -87
- package/Makefile +80 -22
- package/agentic +634 -124
- package/areas/devops/ci-cd/AGENTS.md +1 -15
- package/areas/devops/database-ops/AGENTS.md +1 -15
- package/areas/devops/devsecops/AGENTS.md +1 -15
- package/areas/devops/infrastructure/AGENTS.md +1 -15
- package/areas/devops/kubernetes/AGENTS.md +1 -15
- package/areas/devops/networking/AGENTS.md +1 -15
- package/areas/devops/observability/AGENTS.md +1 -15
- package/areas/devops/sre/AGENTS.md +1 -15
- package/areas/software/backend/AGENTS.md +1 -16
- package/areas/software/data-engineering/AGENTS.md +1 -16
- package/areas/software/frontend/AGENTS.md +1 -16
- package/areas/software/full-stack/AGENTS.md +1 -16
- package/areas/software/general/AGENTS.md +1 -7
- package/areas/software/mlops/AGENTS.md +1 -16
- package/areas/software/mobile/AGENTS.md +1 -16
- package/areas/software/platform/AGENTS.md +1 -16
- package/areas/software/qa/AGENTS.md +1 -16
- package/areas/software/security/AGENTS.md +1 -16
- package/areas/template/AGENTS.tmpl.md +1 -17
- package/docs/agentic-lifecycle.md +7 -3
- package/docs/agentic-stabilization/README.md +11 -7
- package/docs/agentic-token-minimization/README.md +7 -5
- package/docs/agentic-usage.md +12 -5
- package/docs/opencode_setup.md +7 -5
- package/extensions/opencode/plugins/agent-model-mapper.ts +13 -2
- package/extensions/opencode/plugins/telegram-notification.ts +14 -14
- package/package.json +1 -1
package/agentic
CHANGED
|
@@ -42,6 +42,7 @@ ACTIVE_THEME="dark"
|
|
|
42
42
|
SELECTED_AGENT_OS=("$DEFAULT_AGENT_OS")
|
|
43
43
|
SELECTED_AREAS=()
|
|
44
44
|
SELECTED_SPECS=()
|
|
45
|
+
INSTALL_SETTINGS_REPLAY=false
|
|
45
46
|
|
|
46
47
|
SELF_INSTALL_FORCE=false
|
|
47
48
|
SELF_INSTALL_BIN_DIR="${HOME}/.local/bin"
|
|
@@ -59,9 +60,17 @@ AGENTIC_ENABLE_CONTEXT7="${AGENTIC_ENABLE_CONTEXT7:-}"
|
|
|
59
60
|
AGENTIC_DOCTOR="${AGENTIC_DOCTOR:-1}"
|
|
60
61
|
AGENTIC_DOCTOR_KEEP_TMP="${AGENTIC_DOCTOR_KEEP_TMP:-0}"
|
|
61
62
|
AGENTIC_DOCTOR_TIMEOUT_SECONDS="${AGENTIC_DOCTOR_TIMEOUT_SECONDS:-10}"
|
|
63
|
+
AGENTIC_MEMPALACE_TIMEOUT_SECONDS="${AGENTIC_MEMPALACE_TIMEOUT_SECONDS:-60}"
|
|
64
|
+
|
|
65
|
+
OPENCODE_TELEGRAM_ENABLED=""
|
|
66
|
+
OPENCODE_TELEGRAM_BOT_TOKEN=""
|
|
67
|
+
OPENCODE_TELEGRAM_CHAT_ID=""
|
|
68
|
+
OPENCODE_AGENT_MODEL_MAPPER_ENABLED=""
|
|
69
|
+
OPENCODE_PLUGINS_CONFIGURED=false
|
|
62
70
|
|
|
63
71
|
RUN_LOG_ACTIVE=false
|
|
64
72
|
RUN_LOG_FILE=""
|
|
73
|
+
CHANGED_PATHS_REPORT_FILE=""
|
|
65
74
|
|
|
66
75
|
COLOR_RESET=""
|
|
67
76
|
COLOR_HEADER=""
|
|
@@ -277,6 +286,7 @@ init_run_logging() {
|
|
|
277
286
|
local stamp
|
|
278
287
|
stamp="$(date '+%Y%m%d-%H%M%S')"
|
|
279
288
|
RUN_LOG_FILE="$(mktemp "$base_dir/agentic-$stamp.XXXXXX")"
|
|
289
|
+
CHANGED_PATHS_REPORT_FILE="$RUN_LOG_FILE.changes"
|
|
280
290
|
RUN_LOG_ACTIVE=true
|
|
281
291
|
log "Run log initialized: $RUN_LOG_FILE"
|
|
282
292
|
}
|
|
@@ -364,6 +374,55 @@ log_file_block() {
|
|
|
364
374
|
write_run_log_line "$(timestamp_now) --- $label output end ---"
|
|
365
375
|
}
|
|
366
376
|
|
|
377
|
+
write_changed_paths_report() {
|
|
378
|
+
if [[ -z "$CHANGED_PATHS_REPORT_FILE" ]]; then
|
|
379
|
+
local base_dir="${TMPDIR:-/tmp}"
|
|
380
|
+
local stamp
|
|
381
|
+
stamp="$(date '+%Y%m%d-%H%M%S')"
|
|
382
|
+
CHANGED_PATHS_REPORT_FILE="$(mktemp "$base_dir/agentic-changed-paths-$stamp.XXXXXX")"
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
{
|
|
386
|
+
printf 'Agentic changed paths report\n'
|
|
387
|
+
printf 'Generated at: %s\n' "$(timestamp_now)"
|
|
388
|
+
printf 'Project dir: %s\n' "$PROJECT_DIR"
|
|
389
|
+
printf 'Knowledge base repo: %s\n' "$REPO_ROOT"
|
|
390
|
+
printf '\n'
|
|
391
|
+
|
|
392
|
+
printf 'Created directories (%s)\n' "${#CREATED_PATHS[@]}"
|
|
393
|
+
if [[ "${#CREATED_PATHS[@]}" -eq 0 ]]; then
|
|
394
|
+
printf -- '- (none)\n'
|
|
395
|
+
else
|
|
396
|
+
local created_path
|
|
397
|
+
for created_path in "${CREATED_PATHS[@]}"; do
|
|
398
|
+
printf -- '- %s\n' "$created_path"
|
|
399
|
+
done
|
|
400
|
+
fi
|
|
401
|
+
printf '\n'
|
|
402
|
+
|
|
403
|
+
printf 'Copied/generated paths (%s)\n' "${#COPIED_PATHS[@]}"
|
|
404
|
+
if [[ "${#COPIED_PATHS[@]}" -eq 0 ]]; then
|
|
405
|
+
printf -- '- (none)\n'
|
|
406
|
+
else
|
|
407
|
+
local copied_path
|
|
408
|
+
for copied_path in "${COPIED_PATHS[@]}"; do
|
|
409
|
+
printf -- '- %s\n' "$copied_path"
|
|
410
|
+
done
|
|
411
|
+
fi
|
|
412
|
+
printf '\n'
|
|
413
|
+
|
|
414
|
+
printf 'Warnings (%s)\n' "${#WARNINGS[@]}"
|
|
415
|
+
if [[ "${#WARNINGS[@]}" -eq 0 ]]; then
|
|
416
|
+
printf -- '- (none)\n'
|
|
417
|
+
else
|
|
418
|
+
local warning
|
|
419
|
+
for warning in "${WARNINGS[@]}"; do
|
|
420
|
+
printf -- '- %s\n' "$warning"
|
|
421
|
+
done
|
|
422
|
+
fi
|
|
423
|
+
} > "$CHANGED_PATHS_REPORT_FILE"
|
|
424
|
+
}
|
|
425
|
+
|
|
367
426
|
unique_append() {
|
|
368
427
|
local value="$1"
|
|
369
428
|
local arr_name="$2"
|
|
@@ -995,7 +1054,7 @@ write_agentic_manifest() {
|
|
|
995
1054
|
IFS="$old_ifs"
|
|
996
1055
|
|
|
997
1056
|
local manifest_status
|
|
998
|
-
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" <<'PY'
|
|
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'
|
|
999
1058
|
import json
|
|
1000
1059
|
import sys
|
|
1001
1060
|
from datetime import datetime, timezone
|
|
@@ -1011,6 +1070,10 @@ areas = [x for x in sys.argv[7].split(",") if x]
|
|
|
1011
1070
|
specs = [x for x in sys.argv[8].split(",") if x]
|
|
1012
1071
|
app_version = sys.argv[9]
|
|
1013
1072
|
mcp_integrations = [x for x in sys.argv[10].split(",") if x] if len(sys.argv) > 10 else []
|
|
1073
|
+
telegram_enabled = sys.argv[11].lower() == "true" if len(sys.argv) > 11 and sys.argv[11] else None
|
|
1074
|
+
telegram_bot_token = sys.argv[12] if len(sys.argv) > 12 else ""
|
|
1075
|
+
telegram_chat_id = sys.argv[13] if len(sys.argv) > 13 else ""
|
|
1076
|
+
mapper_enabled = sys.argv[14].lower() == "true" if len(sys.argv) > 14 and sys.argv[14] else None
|
|
1014
1077
|
now = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
1015
1078
|
|
|
1016
1079
|
existing = {}
|
|
@@ -1052,7 +1115,26 @@ for line in records_file.read_text(encoding="utf-8").splitlines():
|
|
|
1052
1115
|
|
|
1053
1116
|
skipped = [x for x in skipped_file.read_text(encoding="utf-8").splitlines() if x]
|
|
1054
1117
|
old_agentic = old_data.get("_agentic", {}) if isinstance(old_data, dict) else {}
|
|
1118
|
+
old_settings = old_data.get("settings", {}) if isinstance(old_data, dict) else {}
|
|
1055
1119
|
created_by = old_agentic.get("created_by", app_version)
|
|
1120
|
+
opencode_plugins = old_settings.get("opencode_plugins", {}) if isinstance(old_settings, dict) else {}
|
|
1121
|
+
if not isinstance(opencode_plugins, dict):
|
|
1122
|
+
opencode_plugins = {}
|
|
1123
|
+
telegram = opencode_plugins.get("telegram", {}) if isinstance(opencode_plugins.get("telegram"), dict) else {}
|
|
1124
|
+
agent_model_mapper = opencode_plugins.get("agentModelMapper", {}) if isinstance(opencode_plugins.get("agentModelMapper"), dict) else {}
|
|
1125
|
+
if telegram_enabled is not None:
|
|
1126
|
+
telegram = {
|
|
1127
|
+
"enabled": telegram_enabled,
|
|
1128
|
+
"botToken": telegram_bot_token,
|
|
1129
|
+
"chatId": telegram_chat_id,
|
|
1130
|
+
}
|
|
1131
|
+
if mapper_enabled is not None:
|
|
1132
|
+
agent_model_mapper = {"enabled": mapper_enabled}
|
|
1133
|
+
if telegram or agent_model_mapper:
|
|
1134
|
+
opencode_plugins = {
|
|
1135
|
+
"telegram": telegram or {"enabled": False},
|
|
1136
|
+
"agentModelMapper": agent_model_mapper or {"enabled": False},
|
|
1137
|
+
}
|
|
1056
1138
|
data = {
|
|
1057
1139
|
"_agentic": {
|
|
1058
1140
|
"generated_by": "agentic",
|
|
@@ -1068,6 +1150,7 @@ data = {
|
|
|
1068
1150
|
"areas": areas,
|
|
1069
1151
|
"specializations": specs,
|
|
1070
1152
|
"mcp_integrations": mcp_integrations,
|
|
1153
|
+
"opencode_plugins": opencode_plugins,
|
|
1071
1154
|
"source_repo": repo_link,
|
|
1072
1155
|
"source_checkout": repo_root,
|
|
1073
1156
|
},
|
|
@@ -1101,6 +1184,7 @@ load_install_settings_from_manifest() {
|
|
|
1101
1184
|
local manifest="$1"
|
|
1102
1185
|
[[ -f "$manifest" ]] || return 1
|
|
1103
1186
|
ensure_python_available
|
|
1187
|
+
INSTALL_SETTINGS_REPLAY=true
|
|
1104
1188
|
|
|
1105
1189
|
local values=()
|
|
1106
1190
|
readlines values < <(python3 - "$manifest" <<'PY'
|
|
@@ -1114,6 +1198,24 @@ for key in ("agent_os", "areas", "specializations", "mcp_integrations"):
|
|
|
1114
1198
|
print("::" + key)
|
|
1115
1199
|
for value in settings.get(key, []):
|
|
1116
1200
|
print(value)
|
|
1201
|
+
plugins = settings.get("opencode_plugins", {})
|
|
1202
|
+
if isinstance(plugins, dict):
|
|
1203
|
+
telegram = plugins.get("telegram", {})
|
|
1204
|
+
if isinstance(telegram, dict):
|
|
1205
|
+
print("::opencode_telegram_enabled")
|
|
1206
|
+
if telegram.get("enabled") is not None:
|
|
1207
|
+
print("true" if telegram.get("enabled") is True else "false")
|
|
1208
|
+
print("::opencode_telegram_bot_token")
|
|
1209
|
+
if telegram.get("botToken"):
|
|
1210
|
+
print(telegram.get("botToken"))
|
|
1211
|
+
print("::opencode_telegram_chat_id")
|
|
1212
|
+
if telegram.get("chatId"):
|
|
1213
|
+
print(telegram.get("chatId"))
|
|
1214
|
+
mapper = plugins.get("agentModelMapper", {})
|
|
1215
|
+
if isinstance(mapper, dict):
|
|
1216
|
+
print("::opencode_agent_model_mapper_enabled")
|
|
1217
|
+
if mapper.get("enabled") is not None:
|
|
1218
|
+
print("true" if mapper.get("enabled") is True else "false")
|
|
1117
1219
|
PY
|
|
1118
1220
|
)
|
|
1119
1221
|
|
|
@@ -1122,6 +1224,10 @@ PY
|
|
|
1122
1224
|
local loaded_areas=()
|
|
1123
1225
|
local loaded_specs=()
|
|
1124
1226
|
local loaded_mcp_integrations=()
|
|
1227
|
+
local loaded_telegram_enabled=""
|
|
1228
|
+
local loaded_telegram_bot_token=""
|
|
1229
|
+
local loaded_telegram_chat_id=""
|
|
1230
|
+
local loaded_mapper_enabled=""
|
|
1125
1231
|
local value
|
|
1126
1232
|
for value in "${values[@]}"; do
|
|
1127
1233
|
case "$value" in
|
|
@@ -1135,6 +1241,10 @@ PY
|
|
|
1135
1241
|
areas) loaded_areas+=("$value") ;;
|
|
1136
1242
|
specializations) loaded_specs+=("$value") ;;
|
|
1137
1243
|
mcp_integrations) loaded_mcp_integrations+=("$value") ;;
|
|
1244
|
+
opencode_telegram_enabled) loaded_telegram_enabled="$value" ;;
|
|
1245
|
+
opencode_telegram_bot_token) loaded_telegram_bot_token="$value" ;;
|
|
1246
|
+
opencode_telegram_chat_id) loaded_telegram_chat_id="$value" ;;
|
|
1247
|
+
opencode_agent_model_mapper_enabled) loaded_mapper_enabled="$value" ;;
|
|
1138
1248
|
esac
|
|
1139
1249
|
;;
|
|
1140
1250
|
esac
|
|
@@ -1152,20 +1262,33 @@ PY
|
|
|
1152
1262
|
|
|
1153
1263
|
# Restore MCP integration selections so configure_*_if_needed skip interactive prompts
|
|
1154
1264
|
local mcp_item
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
AGENTIC_ENABLE_CONTEXT7
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
AGENTIC_ENABLE_MEMPALACE
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1265
|
+
if [[ "${#loaded_mcp_integrations[@]}" -gt 0 ]]; then
|
|
1266
|
+
for mcp_item in "${loaded_mcp_integrations[@]}"; do
|
|
1267
|
+
case "$mcp_item" in
|
|
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
|
|
1279
|
+
done
|
|
1280
|
+
fi
|
|
1281
|
+
|
|
1282
|
+
if [[ -n "$loaded_telegram_enabled" ]]; then
|
|
1283
|
+
OPENCODE_TELEGRAM_ENABLED="$loaded_telegram_enabled"
|
|
1284
|
+
OPENCODE_TELEGRAM_BOT_TOKEN="$loaded_telegram_bot_token"
|
|
1285
|
+
OPENCODE_TELEGRAM_CHAT_ID="$loaded_telegram_chat_id"
|
|
1286
|
+
OPENCODE_PLUGINS_CONFIGURED=true
|
|
1287
|
+
fi
|
|
1288
|
+
if [[ -n "$loaded_mapper_enabled" ]]; then
|
|
1289
|
+
OPENCODE_AGENT_MODEL_MAPPER_ENABLED="$loaded_mapper_enabled"
|
|
1290
|
+
OPENCODE_PLUGINS_CONFIGURED=true
|
|
1291
|
+
fi
|
|
1169
1292
|
}
|
|
1170
1293
|
|
|
1171
1294
|
path_ref_for_shell_export() {
|
|
@@ -1744,36 +1867,32 @@ print_context7_key_recommendation() {
|
|
|
1744
1867
|
[[ -z "$CONTEXT7_API_KEY" ]] || return 0
|
|
1745
1868
|
|
|
1746
1869
|
out "Context7 MCP configured without an API key."
|
|
1747
|
-
|
|
1870
|
+
}
|
|
1748
1871
|
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
out " - $PROJECT_DIR/.gemini/settings.json"
|
|
1768
|
-
out ' Example: "headers": {"CONTEXT7_API_KEY": "ctx7_your_api_key_here"}'
|
|
1769
|
-
fi
|
|
1770
|
-
if selected_agent_os_contains "kilocode"; then
|
|
1771
|
-
out " - $PROJECT_DIR/.kilocode/mcp.json"
|
|
1772
|
-
out ' Example: "headers": {"CONTEXT7_API_KEY": "ctx7_your_api_key_here"}'
|
|
1872
|
+
configure_context7_key_interactive() {
|
|
1873
|
+
is_interactive_terminal || return 0
|
|
1874
|
+
|
|
1875
|
+
local choice
|
|
1876
|
+
if fzf_available; then
|
|
1877
|
+
choice="$(choose_single_fzf "Context7 API key mode:" "Use without API key" "Enter CONTEXT7_API_KEY" || true)"
|
|
1878
|
+
else
|
|
1879
|
+
echo "Context7 API key mode:" >&2
|
|
1880
|
+
echo " 1) Use without API key" >&2
|
|
1881
|
+
echo " 2) Enter CONTEXT7_API_KEY" >&2
|
|
1882
|
+
local answer
|
|
1883
|
+
read -r -p "Select one (empty=1): " answer
|
|
1884
|
+
answer="$(trim "$answer")"
|
|
1885
|
+
case "$answer" in
|
|
1886
|
+
""|1) choice="Use without API key" ;;
|
|
1887
|
+
2) choice="Enter CONTEXT7_API_KEY" ;;
|
|
1888
|
+
*) error "Invalid choice"; exit 1 ;;
|
|
1889
|
+
esac
|
|
1773
1890
|
fi
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1891
|
+
|
|
1892
|
+
if [[ "$choice" == "Enter CONTEXT7_API_KEY" ]]; then
|
|
1893
|
+
CONTEXT7_API_KEY="$(prompt_text_interactive "CONTEXT7_API_KEY" "$CONTEXT7_API_KEY")"
|
|
1894
|
+
else
|
|
1895
|
+
CONTEXT7_API_KEY=""
|
|
1777
1896
|
fi
|
|
1778
1897
|
}
|
|
1779
1898
|
|
|
@@ -1820,17 +1939,42 @@ servers["mempalace"] = {"command": "mempalace-mcp"}
|
|
|
1820
1939
|
}
|
|
1821
1940
|
|
|
1822
1941
|
print_mempalace_project_setup_instructions() {
|
|
1942
|
+
local project_wing
|
|
1943
|
+
project_wing="$(mempalace_project_wing)"
|
|
1823
1944
|
log "Optional MemPalace project indexing instructions for target project: $PROJECT_DIR"
|
|
1824
1945
|
out "1) Ensure Python is installed and available in PATH."
|
|
1825
1946
|
out "2) Install MemPalace:"
|
|
1826
1947
|
out " pip install mempalace"
|
|
1827
|
-
out "3)
|
|
1828
|
-
out " mempalace init \"$PROJECT_DIR\" --yes --
|
|
1829
|
-
out "4)
|
|
1830
|
-
out "
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1948
|
+
out "3) Initialize the project memory taxonomy without LLM calls:"
|
|
1949
|
+
out " mempalace init \"$PROJECT_DIR\" --yes --no-llm"
|
|
1950
|
+
out "4) Mine project knowledge into its isolated wing:"
|
|
1951
|
+
out " mempalace mine \"$PROJECT_DIR\" --wing \"$project_wing\""
|
|
1952
|
+
if [[ -d "$PROJECT_DIR/docs" ]]; then
|
|
1953
|
+
out "5) Mine shared project docs into the cross-project docs wing:"
|
|
1954
|
+
out " mempalace mine \"$PROJECT_DIR/docs\" --wing shared_docs"
|
|
1955
|
+
out "6) Verify in your IDE/agent that MemPalace MCP tools are connected."
|
|
1956
|
+
else
|
|
1957
|
+
out "5) Verify in your IDE/agent that MemPalace MCP tools are connected."
|
|
1958
|
+
fi
|
|
1959
|
+
out "Note: agentic uses --no-llm by default to keep MemPalace setup low-cost."
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
mempalace_sanitize_wing_name() {
|
|
1963
|
+
local raw="$1"
|
|
1964
|
+
local sanitized
|
|
1965
|
+
sanitized="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/_/g; s/^_+//; s/_+$//; s/_+/_/g')"
|
|
1966
|
+
if [[ -z "$sanitized" ]]; then
|
|
1967
|
+
sanitized="project"
|
|
1968
|
+
fi
|
|
1969
|
+
printf '%s\n' "$sanitized"
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
mempalace_project_wing() {
|
|
1973
|
+
mempalace_sanitize_wing_name "$(basename "$PROJECT_DIR")"
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
mempalace_shared_docs_wing() {
|
|
1977
|
+
printf '%s\n' "shared_docs"
|
|
1834
1978
|
}
|
|
1835
1979
|
|
|
1836
1980
|
write_mempalace_ignore_file() {
|
|
@@ -1840,17 +1984,27 @@ write_mempalace_ignore_file() {
|
|
|
1840
1984
|
.venv/
|
|
1841
1985
|
venv/
|
|
1842
1986
|
dist/
|
|
1987
|
+
logs/
|
|
1843
1988
|
build/
|
|
1844
1989
|
target/
|
|
1845
1990
|
coverage/
|
|
1991
|
+
.ai/
|
|
1846
1992
|
.git/
|
|
1847
|
-
|
|
1993
|
+
.github/
|
|
1994
|
+
.cursor/
|
|
1995
|
+
.agent/
|
|
1996
|
+
.opencode/
|
|
1997
|
+
.claude/
|
|
1998
|
+
.gemini/
|
|
1999
|
+
.codex/
|
|
2000
|
+
.idea/
|
|
1848
2001
|
*.csv
|
|
1849
2002
|
*.parquet
|
|
1850
2003
|
*.log
|
|
1851
2004
|
*.jsonl
|
|
1852
2005
|
|
|
1853
2006
|
data/
|
|
2007
|
+
dumps/
|
|
1854
2008
|
tmp/
|
|
1855
2009
|
'
|
|
1856
2010
|
|
|
@@ -1877,24 +2031,153 @@ warn_mempalace_failure_reason() {
|
|
|
1877
2031
|
fi
|
|
1878
2032
|
}
|
|
1879
2033
|
|
|
2034
|
+
warn_mempalace_pip_failure_reason() {
|
|
2035
|
+
local output_file="$1"
|
|
2036
|
+
[[ -f "$output_file" ]] || return 0
|
|
2037
|
+
|
|
2038
|
+
local reason
|
|
2039
|
+
reason="$(sed -n '/[^[:space:]]/{p;q;}' "$output_file" 2>/dev/null || true)"
|
|
2040
|
+
if [[ -n "$reason" ]]; then
|
|
2041
|
+
warn "pip failure reason: $reason"
|
|
2042
|
+
else
|
|
2043
|
+
warn "pip failure output was empty; inspect the MemPalace pip install log for details."
|
|
2044
|
+
fi
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
mempalace_timeout_seconds() {
|
|
2048
|
+
local value="${AGENTIC_MEMPALACE_TIMEOUT_SECONDS:-60}"
|
|
2049
|
+
if [[ "$value" =~ ^[0-9]+$ ]] && (( value > 0 )); then
|
|
2050
|
+
printf '%s\n' "$value"
|
|
2051
|
+
return
|
|
2052
|
+
fi
|
|
2053
|
+
printf '%s\n' "60"
|
|
2054
|
+
}
|
|
2055
|
+
|
|
1880
2056
|
run_mempalace_command() {
|
|
1881
2057
|
local label="$1"
|
|
1882
2058
|
shift
|
|
1883
|
-
local output_file
|
|
2059
|
+
local output_file timeout_seconds child_pid elapsed status
|
|
1884
2060
|
output_file="$(mktemp "${TMPDIR:-/tmp}/agentic-mempalace.XXXXXX")"
|
|
1885
|
-
|
|
2061
|
+
timeout_seconds="$(mempalace_timeout_seconds)"
|
|
2062
|
+
|
|
2063
|
+
"$@" >"$output_file" 2>&1 &
|
|
2064
|
+
child_pid=$!
|
|
2065
|
+
elapsed=0
|
|
2066
|
+
status=0
|
|
2067
|
+
while kill -0 "$child_pid" 2>/dev/null; do
|
|
2068
|
+
if (( elapsed >= timeout_seconds )); then
|
|
2069
|
+
pkill -TERM -P "$child_pid" 2>/dev/null || true
|
|
2070
|
+
kill "$child_pid" 2>/dev/null || true
|
|
2071
|
+
sleep 1
|
|
2072
|
+
pkill -KILL -P "$child_pid" 2>/dev/null || true
|
|
2073
|
+
kill -9 "$child_pid" 2>/dev/null || true
|
|
2074
|
+
wait "$child_pid" 2>/dev/null || true
|
|
2075
|
+
warn "Timed out after ${timeout_seconds}s: $* (log: $output_file)"
|
|
2076
|
+
log_file_block "$label" "$output_file"
|
|
2077
|
+
return 1
|
|
2078
|
+
fi
|
|
2079
|
+
sleep 1
|
|
2080
|
+
elapsed=$((elapsed + 1))
|
|
2081
|
+
done
|
|
2082
|
+
|
|
2083
|
+
if wait "$child_pid"; then
|
|
1886
2084
|
log "$label completed"
|
|
1887
2085
|
log_file_block "$label" "$output_file"
|
|
1888
2086
|
rm -f "$output_file"
|
|
1889
2087
|
return 0
|
|
1890
2088
|
fi
|
|
2089
|
+
status=$?
|
|
1891
2090
|
|
|
1892
|
-
warn "Failed: $* (log: $output_file)"
|
|
2091
|
+
warn "Failed: $* (exit $status, log: $output_file)"
|
|
1893
2092
|
log_file_block "$label" "$output_file"
|
|
1894
2093
|
warn_mempalace_failure_reason "$output_file"
|
|
1895
2094
|
return 1
|
|
1896
2095
|
}
|
|
1897
2096
|
|
|
2097
|
+
install_mempalace_with_pip() {
|
|
2098
|
+
local pip_bin="$1"
|
|
2099
|
+
local output_file status
|
|
2100
|
+
local py_bin venv_dir venv_python
|
|
2101
|
+
|
|
2102
|
+
output_file="$(mktemp "${TMPDIR:-/tmp}/agentic-mempalace-pip.XXXXXX")"
|
|
2103
|
+
|
|
2104
|
+
if $pip_bin install mempalace >"$output_file" 2>&1; then
|
|
2105
|
+
log "MemPalace package installed via '$pip_bin install mempalace'"
|
|
2106
|
+
log_file_block "MemPalace pip install" "$output_file"
|
|
2107
|
+
rm -f "$output_file"
|
|
2108
|
+
return 0
|
|
2109
|
+
else
|
|
2110
|
+
status=$?
|
|
2111
|
+
fi
|
|
2112
|
+
|
|
2113
|
+
if grep -qi "externally-managed-environment" "$output_file"; then
|
|
2114
|
+
log "Detected PEP 668 externally-managed Python environment; retrying inside isolated venv"
|
|
2115
|
+
|
|
2116
|
+
py_bin="$(command -v python3 || command -v python)"
|
|
2117
|
+
if [[ -z "$py_bin" ]]; then
|
|
2118
|
+
warn "python3/python executable not found"
|
|
2119
|
+
return 1
|
|
2120
|
+
fi
|
|
2121
|
+
|
|
2122
|
+
venv_dir="${HOME}/.agentic/mempalace-venv"
|
|
2123
|
+
|
|
2124
|
+
if [[ ! -d "$venv_dir" ]]; then
|
|
2125
|
+
mkdir -p "$(dirname "$venv_dir")"
|
|
2126
|
+
|
|
2127
|
+
if ! "$py_bin" -m venv "$venv_dir" >>"$output_file" 2>&1; then
|
|
2128
|
+
warn "Unable to create virtual environment at $venv_dir"
|
|
2129
|
+
log_file_block "MemPalace pip install" "$output_file"
|
|
2130
|
+
return 1
|
|
2131
|
+
fi
|
|
2132
|
+
fi
|
|
2133
|
+
|
|
2134
|
+
venv_python="$venv_dir/bin/python"
|
|
2135
|
+
|
|
2136
|
+
if ! "$venv_python" -m pip install --upgrade pip setuptools wheel >>"$output_file" 2>&1; then
|
|
2137
|
+
warn "Unable to upgrade pip inside virtual environment"
|
|
2138
|
+
log_file_block "MemPalace pip install" "$output_file"
|
|
2139
|
+
return 1
|
|
2140
|
+
fi
|
|
2141
|
+
|
|
2142
|
+
if ! "$venv_python" -m pip install --no-cache-dir --upgrade mempalace >>"$output_file" 2>&1; then
|
|
2143
|
+
warn "Unable to install mempalace inside virtual environment"
|
|
2144
|
+
log_file_block "MemPalace pip install" "$output_file"
|
|
2145
|
+
return 1
|
|
2146
|
+
fi
|
|
2147
|
+
|
|
2148
|
+
local_bin_dir="$HOME/.local/bin"
|
|
2149
|
+
|
|
2150
|
+
mkdir -p "$local_bin_dir"
|
|
2151
|
+
|
|
2152
|
+
ln -sf "$venv_dir/bin/mempalace" "$local_bin_dir/mempalace"
|
|
2153
|
+
|
|
2154
|
+
export PATH="$local_bin_dir:$venv_dir/bin:$PATH"
|
|
2155
|
+
|
|
2156
|
+
shell_name="$(basename "${SHELL:-}")"
|
|
2157
|
+
|
|
2158
|
+
if [[ "$shell_name" == "zsh" ]]; then
|
|
2159
|
+
grep -qxF 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.zshrc" 2>/dev/null || \
|
|
2160
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.zshrc"
|
|
2161
|
+
else
|
|
2162
|
+
grep -qxF 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.bashrc" 2>/dev/null || \
|
|
2163
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc"
|
|
2164
|
+
fi
|
|
2165
|
+
|
|
2166
|
+
log "MemPalace installed successfully inside virtual environment: $venv_dir"
|
|
2167
|
+
log "MemPalace binary linked to: $local_bin_dir/mempalace"
|
|
2168
|
+
log_file_block "MemPalace pip install" "$output_file"
|
|
2169
|
+
|
|
2170
|
+
rm -f "$output_file"
|
|
2171
|
+
return 0
|
|
2172
|
+
fi
|
|
2173
|
+
|
|
2174
|
+
warn "Unable to auto-install mempalace via pip; continuing with manual setup instructions (exit $status, log: $output_file)"
|
|
2175
|
+
log_file_block "MemPalace pip install" "$output_file"
|
|
2176
|
+
warn_mempalace_pip_failure_reason "$output_file"
|
|
2177
|
+
|
|
2178
|
+
return 1
|
|
2179
|
+
}
|
|
2180
|
+
|
|
1898
2181
|
mempalace_venv_dir() {
|
|
1899
2182
|
printf '%s\n' "${AGENTIC_MEMPALACE_VENV:-$HOME/.venvs/mempalace}"
|
|
1900
2183
|
}
|
|
@@ -1949,17 +2232,30 @@ install_mempalace_managed() {
|
|
|
1949
2232
|
|
|
1950
2233
|
initialize_mempalace_project() {
|
|
1951
2234
|
local step_prefix="$1"
|
|
1952
|
-
|
|
2235
|
+
local project_wing shared_docs_wing
|
|
2236
|
+
project_wing="$(mempalace_project_wing)"
|
|
2237
|
+
shared_docs_wing="$(mempalace_shared_docs_wing)"
|
|
2238
|
+
log "$step_prefix [4/4] Initializing project memory at $PROJECT_DIR (wing: $project_wing)"
|
|
1953
2239
|
if ! command -v mempalace >/dev/null 2>&1; then
|
|
1954
2240
|
warn "mempalace command is unavailable after install; please run setup manually"
|
|
1955
2241
|
print_mempalace_project_setup_instructions
|
|
1956
2242
|
return 1
|
|
1957
2243
|
fi
|
|
1958
2244
|
|
|
1959
|
-
if ! run_mempalace_command "MemPalace init" mempalace init "$PROJECT_DIR" --yes --
|
|
2245
|
+
if ! run_mempalace_command "MemPalace init" mempalace init "$PROJECT_DIR" --yes --no-llm; then
|
|
1960
2246
|
print_mempalace_project_setup_instructions
|
|
1961
2247
|
return 1
|
|
1962
2248
|
fi
|
|
2249
|
+
if ! run_mempalace_command "MemPalace mine project wing" mempalace mine "$PROJECT_DIR" --wing "$project_wing"; then
|
|
2250
|
+
print_mempalace_project_setup_instructions
|
|
2251
|
+
return 1
|
|
2252
|
+
fi
|
|
2253
|
+
if [[ -d "$PROJECT_DIR/docs" ]]; then
|
|
2254
|
+
if ! run_mempalace_command "MemPalace mine shared docs wing" mempalace mine "$PROJECT_DIR/docs" --wing "$shared_docs_wing"; then
|
|
2255
|
+
print_mempalace_project_setup_instructions
|
|
2256
|
+
return 1
|
|
2257
|
+
fi
|
|
2258
|
+
fi
|
|
1963
2259
|
log "$step_prefix [4/4] Initialization step finished"
|
|
1964
2260
|
}
|
|
1965
2261
|
|
|
@@ -1967,6 +2263,14 @@ setup_mempalace_for_agentic() {
|
|
|
1967
2263
|
local initialize_project="${1:-false}"
|
|
1968
2264
|
local step_prefix="MemPalace setup"
|
|
1969
2265
|
|
|
2266
|
+
if [[ "${AGENTIC_MEMPALACE_SETUP:-}" == "skip" ]]; then
|
|
2267
|
+
log "$step_prefix skipped by AGENTIC_MEMPALACE_SETUP=skip"
|
|
2268
|
+
if command -v mempalace-mcp >/dev/null 2>&1; then
|
|
2269
|
+
return 0
|
|
2270
|
+
fi
|
|
2271
|
+
return 1
|
|
2272
|
+
fi
|
|
2273
|
+
|
|
1970
2274
|
log "$step_prefix [1/4] Checking Python availability"
|
|
1971
2275
|
if ! command -v python3 >/dev/null 2>&1 && ! command -v python >/dev/null 2>&1; then
|
|
1972
2276
|
warn "Python is not installed. Install Python 3 first, then run: pip install mempalace"
|
|
@@ -1998,10 +2302,7 @@ setup_mempalace_for_agentic() {
|
|
|
1998
2302
|
log "$step_prefix [2/4] pip check passed"
|
|
1999
2303
|
|
|
2000
2304
|
log "$step_prefix [3/4] Installing mempalace package"
|
|
2001
|
-
if $pip_bin
|
|
2002
|
-
log "MemPalace package installed via '$pip_bin install mempalace'"
|
|
2003
|
-
else
|
|
2004
|
-
warn "Unable to auto-install mempalace via pip; continuing with manual setup instructions"
|
|
2305
|
+
if ! install_mempalace_with_pip "$pip_bin"; then
|
|
2005
2306
|
print_mempalace_project_setup_instructions
|
|
2006
2307
|
return 1
|
|
2007
2308
|
fi
|
|
@@ -2099,6 +2400,7 @@ configure_context7_if_needed() {
|
|
|
2099
2400
|
log "Context7 MCP configuration disabled"
|
|
2100
2401
|
return
|
|
2101
2402
|
fi
|
|
2403
|
+
configure_context7_key_interactive
|
|
2102
2404
|
|
|
2103
2405
|
elif [[ -n "$enable_context7" ]]; then
|
|
2104
2406
|
if [[ ! "$enable_context7" =~ ^[Yy]$ ]]; then
|
|
@@ -2144,6 +2446,11 @@ configure_context7_if_needed() {
|
|
|
2144
2446
|
|
|
2145
2447
|
write_default_opencode_plugin_config() {
|
|
2146
2448
|
ensure_dir "$APP_CONFIG_DIR"
|
|
2449
|
+
OPENCODE_TELEGRAM_ENABLED="false"
|
|
2450
|
+
OPENCODE_TELEGRAM_BOT_TOKEN=""
|
|
2451
|
+
OPENCODE_TELEGRAM_CHAT_ID=""
|
|
2452
|
+
OPENCODE_AGENT_MODEL_MAPPER_ENABLED="false"
|
|
2453
|
+
OPENCODE_PLUGINS_CONFIGURED=true
|
|
2147
2454
|
if [[ "$DRY_RUN" == true ]]; then
|
|
2148
2455
|
log "DRY-RUN write disabled opencode plugin config to $OPENCODE_PLUGIN_CONFIG_FILE"
|
|
2149
2456
|
else
|
|
@@ -2161,6 +2468,33 @@ PY
|
|
|
2161
2468
|
fi
|
|
2162
2469
|
}
|
|
2163
2470
|
|
|
2471
|
+
load_opencode_plugin_config_globals() {
|
|
2472
|
+
[[ -f "$OPENCODE_PLUGIN_CONFIG_FILE" ]] || return 1
|
|
2473
|
+
local values=()
|
|
2474
|
+
readlines values < <(python3 - "$OPENCODE_PLUGIN_CONFIG_FILE" <<'PY'
|
|
2475
|
+
import json
|
|
2476
|
+
import sys
|
|
2477
|
+
from pathlib import Path
|
|
2478
|
+
|
|
2479
|
+
try:
|
|
2480
|
+
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
2481
|
+
except Exception:
|
|
2482
|
+
data = {}
|
|
2483
|
+
telegram = data.get("telegram", {}) if isinstance(data, dict) else {}
|
|
2484
|
+
mapper = data.get("agentModelMapper", {}) if isinstance(data, dict) else {}
|
|
2485
|
+
print("true" if telegram.get("enabled") is True else "false")
|
|
2486
|
+
print(telegram.get("botToken") or "")
|
|
2487
|
+
print(telegram.get("chatId") or "")
|
|
2488
|
+
print("true" if mapper.get("enabled") is True else "false")
|
|
2489
|
+
PY
|
|
2490
|
+
)
|
|
2491
|
+
OPENCODE_TELEGRAM_ENABLED="${values[0]:-false}"
|
|
2492
|
+
OPENCODE_TELEGRAM_BOT_TOKEN="${values[1]:-}"
|
|
2493
|
+
OPENCODE_TELEGRAM_CHAT_ID="${values[2]:-}"
|
|
2494
|
+
OPENCODE_AGENT_MODEL_MAPPER_ENABLED="${values[3]:-false}"
|
|
2495
|
+
OPENCODE_PLUGINS_CONFIGURED=true
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2164
2498
|
configure_opencode_plugins_if_needed() {
|
|
2165
2499
|
selected_agent_os_contains "opencode" || return 0
|
|
2166
2500
|
|
|
@@ -2172,8 +2506,13 @@ configure_opencode_plugins_if_needed() {
|
|
|
2172
2506
|
ensure_python_available
|
|
2173
2507
|
ensure_dir "$APP_CONFIG_DIR"
|
|
2174
2508
|
|
|
2175
|
-
# During
|
|
2176
|
-
if [[
|
|
2509
|
+
# During manifest replay/re-install, keep current global plugin settings and avoid prompts.
|
|
2510
|
+
if [[ "$INSTALL_SETTINGS_REPLAY" == true && "$OPENCODE_PLUGINS_CONFIGURED" == true ]]; then
|
|
2511
|
+
log "OpenCode plugin settings loaded from .agentic.json"
|
|
2512
|
+
return
|
|
2513
|
+
fi
|
|
2514
|
+
if [[ "$INSTALL_SETTINGS_REPLAY" == true && -f "$OPENCODE_PLUGIN_CONFIG_FILE" ]]; then
|
|
2515
|
+
load_opencode_plugin_config_globals || true
|
|
2177
2516
|
log "OpenCode plugin config already exists; keeping current settings"
|
|
2178
2517
|
return
|
|
2179
2518
|
fi
|
|
@@ -2181,11 +2520,13 @@ configure_opencode_plugins_if_needed() {
|
|
|
2181
2520
|
if ! is_interactive_terminal; then
|
|
2182
2521
|
if [[ ! -f "$OPENCODE_PLUGIN_CONFIG_FILE" ]]; then
|
|
2183
2522
|
write_default_opencode_plugin_config
|
|
2523
|
+
else
|
|
2524
|
+
load_opencode_plugin_config_globals || true
|
|
2184
2525
|
fi
|
|
2185
2526
|
return
|
|
2186
2527
|
fi
|
|
2187
2528
|
|
|
2188
|
-
local plugin_options=("telegram-
|
|
2529
|
+
local plugin_options=("telegram-notification" "agent-model-mapper")
|
|
2189
2530
|
local selected_plugins=()
|
|
2190
2531
|
local use_fzf_plugins=false
|
|
2191
2532
|
if fzf_available; then
|
|
@@ -2208,14 +2549,26 @@ configure_opencode_plugins_if_needed() {
|
|
|
2208
2549
|
selected_plugin="$(trim "$selected_plugin")"
|
|
2209
2550
|
[[ -z "$selected_plugin" ]] && continue
|
|
2210
2551
|
case "$selected_plugin" in
|
|
2211
|
-
telegram-opencode-notifier) enable_telegram="y" ;;
|
|
2552
|
+
telegram-notification|telegram-opencode-notifier) enable_telegram="y" ;;
|
|
2212
2553
|
agent-model-mapper) enable_agent_model_mapper="y" ;;
|
|
2213
2554
|
esac
|
|
2214
2555
|
done
|
|
2215
2556
|
|
|
2216
2557
|
if [[ "$enable_telegram" =~ ^[Yy]$ ]]; then
|
|
2217
|
-
|
|
2558
|
+
OPENCODE_TELEGRAM_BOT_TOKEN="$(prompt_text_interactive "Telegram botToken" "$OPENCODE_TELEGRAM_BOT_TOKEN")"
|
|
2559
|
+
OPENCODE_TELEGRAM_CHAT_ID="$(prompt_text_interactive "Telegram chatId" "$OPENCODE_TELEGRAM_CHAT_ID")"
|
|
2560
|
+
if [[ -z "$OPENCODE_TELEGRAM_BOT_TOKEN" || -z "$OPENCODE_TELEGRAM_CHAT_ID" ]]; then
|
|
2561
|
+
warn "Telegram plugin credentials are incomplete; disabling telegram-notification"
|
|
2562
|
+
enable_telegram="n"
|
|
2563
|
+
OPENCODE_TELEGRAM_BOT_TOKEN=""
|
|
2564
|
+
OPENCODE_TELEGRAM_CHAT_ID=""
|
|
2565
|
+
else
|
|
2566
|
+
log "Telegram plugin enabled; credentials will be stored in project .agentic.json"
|
|
2567
|
+
fi
|
|
2218
2568
|
fi
|
|
2569
|
+
OPENCODE_TELEGRAM_ENABLED=$([[ "$enable_telegram" =~ ^[Yy]$ ]] && echo "true" || echo "false")
|
|
2570
|
+
OPENCODE_AGENT_MODEL_MAPPER_ENABLED=$([[ "$enable_agent_model_mapper" =~ ^[Yy]$ ]] && echo "true" || echo "false")
|
|
2571
|
+
OPENCODE_PLUGINS_CONFIGURED=true
|
|
2219
2572
|
|
|
2220
2573
|
python3 - "$OPENCODE_PLUGIN_CONFIG_FILE" "$enable_telegram" "$enable_agent_model_mapper" <<'PY'
|
|
2221
2574
|
import json
|
|
@@ -2239,6 +2592,12 @@ PY
|
|
|
2239
2592
|
}
|
|
2240
2593
|
|
|
2241
2594
|
opencode_agent_model_mapper_config_enabled() {
|
|
2595
|
+
if [[ "$OPENCODE_AGENT_MODEL_MAPPER_ENABLED" == "true" ]]; then
|
|
2596
|
+
return 0
|
|
2597
|
+
fi
|
|
2598
|
+
if [[ "$OPENCODE_AGENT_MODEL_MAPPER_ENABLED" == "false" ]]; then
|
|
2599
|
+
return 1
|
|
2600
|
+
fi
|
|
2242
2601
|
[[ -f "$OPENCODE_PLUGIN_CONFIG_FILE" ]] || return 1
|
|
2243
2602
|
python3 - "$OPENCODE_PLUGIN_CONFIG_FILE" <<'PY'
|
|
2244
2603
|
import json
|
|
@@ -2287,15 +2646,23 @@ PY
|
|
|
2287
2646
|
|
|
2288
2647
|
opencode_mapper_discover_models() {
|
|
2289
2648
|
local config_path="$HOME/.config/opencode/opencode.json"
|
|
2290
|
-
|
|
2649
|
+
local auth_path="$HOME/.local/share/opencode/auth.json"
|
|
2650
|
+
local models_cache_path="$HOME/.cache/opencode/models.json"
|
|
2651
|
+
python3 - "$config_path" "$auth_path" "$models_cache_path" <<'PY'
|
|
2291
2652
|
import json
|
|
2292
2653
|
import sys
|
|
2293
2654
|
from pathlib import Path
|
|
2294
2655
|
|
|
2295
2656
|
fallback = ["opencode/minimax-m2.5-free"]
|
|
2296
|
-
|
|
2657
|
+
config_path = Path(sys.argv[1])
|
|
2658
|
+
auth_path = Path(sys.argv[2])
|
|
2659
|
+
models_cache_path = Path(sys.argv[3])
|
|
2297
2660
|
models = []
|
|
2298
2661
|
|
|
2662
|
+
def add_model(model):
|
|
2663
|
+
if isinstance(model, str) and model.strip() and "/" in model:
|
|
2664
|
+
models.append(model.strip())
|
|
2665
|
+
|
|
2299
2666
|
def collect_provider_models(data):
|
|
2300
2667
|
"""Extract models from provider.<name>.models dict keys."""
|
|
2301
2668
|
providers = data.get("provider")
|
|
@@ -2309,7 +2676,7 @@ def collect_provider_models(data):
|
|
|
2309
2676
|
continue
|
|
2310
2677
|
for model_name in provider_models:
|
|
2311
2678
|
if isinstance(model_name, str) and model_name.strip():
|
|
2312
|
-
|
|
2679
|
+
add_model(f"{provider_name}/{model_name}")
|
|
2313
2680
|
|
|
2314
2681
|
def collect(value):
|
|
2315
2682
|
if isinstance(value, list):
|
|
@@ -2320,18 +2687,63 @@ def collect(value):
|
|
|
2320
2687
|
return
|
|
2321
2688
|
for key, item in value.items():
|
|
2322
2689
|
if key in {"model", "id"} and isinstance(item, str) and "/" in item:
|
|
2323
|
-
|
|
2690
|
+
add_model(item)
|
|
2324
2691
|
if key == "fallback" and isinstance(item, list):
|
|
2325
|
-
|
|
2692
|
+
for model in item:
|
|
2693
|
+
add_model(model)
|
|
2326
2694
|
collect(item)
|
|
2327
2695
|
|
|
2696
|
+
def read_json(path):
|
|
2697
|
+
try:
|
|
2698
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
2699
|
+
except Exception:
|
|
2700
|
+
return None
|
|
2701
|
+
|
|
2702
|
+
def is_deprecated(model_data):
|
|
2703
|
+
if not isinstance(model_data, dict):
|
|
2704
|
+
return False
|
|
2705
|
+
status = str(model_data.get("status", "")).lower()
|
|
2706
|
+
lifecycle = str(model_data.get("lifecycle", "")).lower()
|
|
2707
|
+
return (
|
|
2708
|
+
model_data.get("deprecated") is True
|
|
2709
|
+
or status in {"deprecated", "retired", "removed"}
|
|
2710
|
+
or lifecycle in {"deprecated", "retired", "removed"}
|
|
2711
|
+
)
|
|
2712
|
+
|
|
2713
|
+
def collect_authenticated_provider_models(auth_data, cache_data):
|
|
2714
|
+
if not isinstance(auth_data, dict) or not isinstance(cache_data, dict):
|
|
2715
|
+
return
|
|
2716
|
+
for provider_name, auth in auth_data.items():
|
|
2717
|
+
if not isinstance(provider_name, str) or not provider_name.strip():
|
|
2718
|
+
continue
|
|
2719
|
+
if auth in (None, False):
|
|
2720
|
+
continue
|
|
2721
|
+
provider_data = cache_data.get(provider_name)
|
|
2722
|
+
if not isinstance(provider_data, dict):
|
|
2723
|
+
continue
|
|
2724
|
+
provider_models = provider_data.get("models")
|
|
2725
|
+
if isinstance(provider_models, dict):
|
|
2726
|
+
for model_name, model_data in provider_models.items():
|
|
2727
|
+
if isinstance(model_name, str) and model_name.strip() and not is_deprecated(model_data):
|
|
2728
|
+
add_model(f"{provider_name}/{model_name}")
|
|
2729
|
+
elif isinstance(provider_models, list):
|
|
2730
|
+
for item in provider_models:
|
|
2731
|
+
if isinstance(item, str):
|
|
2732
|
+
add_model(f"{provider_name}/{item}")
|
|
2733
|
+
elif isinstance(item, dict) and not is_deprecated(item):
|
|
2734
|
+
model_name = item.get("id") or item.get("name")
|
|
2735
|
+
if isinstance(model_name, str) and model_name.strip():
|
|
2736
|
+
add_model(f"{provider_name}/{model_name}")
|
|
2737
|
+
|
|
2328
2738
|
try:
|
|
2329
|
-
data = json.loads(
|
|
2739
|
+
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
2330
2740
|
collect_provider_models(data)
|
|
2331
2741
|
collect(data)
|
|
2332
2742
|
except Exception:
|
|
2333
2743
|
pass
|
|
2334
2744
|
|
|
2745
|
+
collect_authenticated_provider_models(read_json(auth_path), read_json(models_cache_path))
|
|
2746
|
+
|
|
2335
2747
|
seen = set()
|
|
2336
2748
|
for model in models or fallback:
|
|
2337
2749
|
model = model.strip()
|
|
@@ -2491,6 +2903,51 @@ PY
|
|
|
2491
2903
|
register_managed_file "$state_path" "generated:opencode-agent-model-mapper-state" "config"
|
|
2492
2904
|
}
|
|
2493
2905
|
|
|
2906
|
+
confirm_opencode_agent_model_mapping() {
|
|
2907
|
+
local mapping_file="$1"
|
|
2908
|
+
if fzf_available; then
|
|
2909
|
+
local summary selected
|
|
2910
|
+
summary="$(python3 - "$mapping_file" <<'PY'
|
|
2911
|
+
import sys
|
|
2912
|
+
from pathlib import Path
|
|
2913
|
+
|
|
2914
|
+
for line in Path(sys.argv[1]).read_text(encoding="utf-8").splitlines():
|
|
2915
|
+
if not line:
|
|
2916
|
+
continue
|
|
2917
|
+
name, model, fallback = (line.split("\t") + ["", "", ""])[:3]
|
|
2918
|
+
print(f"{name}: main={model} fallback={fallback}")
|
|
2919
|
+
PY
|
|
2920
|
+
)"
|
|
2921
|
+
set +e
|
|
2922
|
+
selected="$(printf '%s\n' "Confirm" "Cancel" | fzf \
|
|
2923
|
+
--ansi \
|
|
2924
|
+
--border \
|
|
2925
|
+
--height=70% \
|
|
2926
|
+
--layout=reverse \
|
|
2927
|
+
--no-sort \
|
|
2928
|
+
--prompt "Save OpenCode model mapping? " \
|
|
2929
|
+
--header "$summary")"
|
|
2930
|
+
local fzf_status=$?
|
|
2931
|
+
set -e
|
|
2932
|
+
[[ "$fzf_status" -eq 0 ]] || return 1
|
|
2933
|
+
[[ "$selected" == "Confirm" ]]
|
|
2934
|
+
return
|
|
2935
|
+
fi
|
|
2936
|
+
|
|
2937
|
+
out "agent-model-mapper selected mapping:"
|
|
2938
|
+
python3 - "$mapping_file" <<'PY'
|
|
2939
|
+
import sys
|
|
2940
|
+
from pathlib import Path
|
|
2941
|
+
|
|
2942
|
+
for line in Path(sys.argv[1]).read_text(encoding="utf-8").splitlines():
|
|
2943
|
+
if not line:
|
|
2944
|
+
continue
|
|
2945
|
+
name, model, fallback = (line.split("\t") + ["", "", ""])[:3]
|
|
2946
|
+
print(f" - {name}: main={model} fallback={fallback}")
|
|
2947
|
+
PY
|
|
2948
|
+
confirm_action_interactive "Write .opencode/opencode.json agent model mapping?"
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2494
2951
|
configure_opencode_agent_model_mapper_if_needed() {
|
|
2495
2952
|
selected_agent_os_contains "opencode" || return 0
|
|
2496
2953
|
opencode_agent_model_mapper_config_enabled || return 0
|
|
@@ -2542,13 +2999,8 @@ configure_opencode_agent_model_mapper_if_needed() {
|
|
|
2542
2999
|
printf '%s\t%s\t%s\n' "$role_name" "$model" "$fallback" >> "$mapping_file"
|
|
2543
3000
|
done < "$roles_file"
|
|
2544
3001
|
|
|
2545
|
-
local confirm
|
|
2546
|
-
if ! read -r -p "Write .opencode/opencode.json agent model mapping? [y/N]: " confirm <&3; then
|
|
2547
|
-
confirm=""
|
|
2548
|
-
fi
|
|
2549
3002
|
exec 3<&-
|
|
2550
|
-
|
|
2551
|
-
if [[ ! "$confirm" =~ ^[Yy]([Ee][Ss])?$ ]]; then
|
|
3003
|
+
if ! confirm_opencode_agent_model_mapping "$mapping_file"; then
|
|
2552
3004
|
log "agent-model-mapper: skipped by user; no files changed"
|
|
2553
3005
|
rm -f "$roles_file" "$models_file" "$mapping_file"
|
|
2554
3006
|
return 0
|
|
@@ -2582,7 +3034,6 @@ copy_extension_for_agent() {
|
|
|
2582
3034
|
local project_dir="$2"
|
|
2583
3035
|
|
|
2584
3036
|
if [[ "$agent_os" == "$DEFAULT_AGENT_OS" ]] || [[ "$agent_os" == "agents" ]]; then
|
|
2585
|
-
log "Agent OS '$agent_os': skipping extension copy"
|
|
2586
3037
|
return
|
|
2587
3038
|
fi
|
|
2588
3039
|
|
|
@@ -2594,7 +3045,6 @@ copy_extension_for_agent() {
|
|
|
2594
3045
|
return
|
|
2595
3046
|
fi
|
|
2596
3047
|
|
|
2597
|
-
log "Copy extension: $src -> $dest"
|
|
2598
3048
|
copy_dir_contents "$src" "$dest"
|
|
2599
3049
|
}
|
|
2600
3050
|
|
|
@@ -2639,7 +3089,6 @@ copy_specialization_assets() {
|
|
|
2639
3089
|
local dest_dir
|
|
2640
3090
|
dest_dir="$(get_dest_dir "$target" "$bucket")"
|
|
2641
3091
|
if [[ "$dest_dir" == "-" ]]; then
|
|
2642
|
-
log "Skip $spec_key/$bucket (not supported by '$target')"
|
|
2643
3092
|
continue
|
|
2644
3093
|
fi
|
|
2645
3094
|
unique_append "$dest_dir" dest_dirs
|
|
@@ -2648,7 +3097,6 @@ copy_specialization_assets() {
|
|
|
2648
3097
|
local resolved_dir
|
|
2649
3098
|
for resolved_dir in "${dest_dirs[@]}"; do
|
|
2650
3099
|
local dest="$project_dir/$resolved_dir"
|
|
2651
|
-
log "Copy $spec_key/$bucket -> $dest"
|
|
2652
3100
|
copy_dir_contents "$src" "$dest"
|
|
2653
3101
|
done
|
|
2654
3102
|
done
|
|
@@ -2863,6 +3311,8 @@ validate_inputs() {
|
|
|
2863
3311
|
}
|
|
2864
3312
|
|
|
2865
3313
|
print_report() {
|
|
3314
|
+
write_changed_paths_report
|
|
3315
|
+
|
|
2866
3316
|
out
|
|
2867
3317
|
out "=== Installation report ===" "$COLOR_HEADER"
|
|
2868
3318
|
out "Agentic version: $(app_version_label)"
|
|
@@ -2874,26 +3324,9 @@ print_report() {
|
|
|
2874
3324
|
out "Specializations: ${SELECTED_SPECS[*]}"
|
|
2875
3325
|
|
|
2876
3326
|
out
|
|
2877
|
-
out "Created directories:"
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
else
|
|
2881
|
-
local created_path
|
|
2882
|
-
for created_path in "${CREATED_PATHS[@]}"; do
|
|
2883
|
-
out "- $created_path"
|
|
2884
|
-
done
|
|
2885
|
-
fi
|
|
2886
|
-
|
|
2887
|
-
out
|
|
2888
|
-
out "Copied/generated paths:"
|
|
2889
|
-
if [[ "${#COPIED_PATHS[@]}" -eq 0 ]]; then
|
|
2890
|
-
out "- (none)"
|
|
2891
|
-
else
|
|
2892
|
-
local copied_path
|
|
2893
|
-
for copied_path in "${COPIED_PATHS[@]}"; do
|
|
2894
|
-
out "- $copied_path"
|
|
2895
|
-
done
|
|
2896
|
-
fi
|
|
3327
|
+
out "Created directories: ${#CREATED_PATHS[@]}"
|
|
3328
|
+
out "Copied/generated paths: ${#COPIED_PATHS[@]}"
|
|
3329
|
+
out "Changed paths report: $CHANGED_PATHS_REPORT_FILE"
|
|
2897
3330
|
|
|
2898
3331
|
out
|
|
2899
3332
|
out "Warnings:"
|
|
@@ -3033,31 +3466,15 @@ doctor_enabled() {
|
|
|
3033
3466
|
}
|
|
3034
3467
|
|
|
3035
3468
|
doctor_prompt() {
|
|
3036
|
-
printf '%s\n' "
|
|
3469
|
+
printf '%s\n' "Reply with exactly: AGENTIC_DOCTOR_OK"
|
|
3037
3470
|
}
|
|
3038
3471
|
|
|
3039
3472
|
doctor_prompt_for_agent() {
|
|
3040
|
-
|
|
3041
|
-
case "$agent_os" in
|
|
3042
|
-
opencode)
|
|
3043
|
-
printf '%s\n' "Reply with exactly: AGENTIC_DOCTOR_OK"
|
|
3044
|
-
;;
|
|
3045
|
-
*)
|
|
3046
|
-
doctor_prompt
|
|
3047
|
-
;;
|
|
3048
|
-
esac
|
|
3473
|
+
doctor_prompt
|
|
3049
3474
|
}
|
|
3050
3475
|
|
|
3051
3476
|
doctor_smoke_label() {
|
|
3052
|
-
|
|
3053
|
-
case "$agent_os" in
|
|
3054
|
-
opencode)
|
|
3055
|
-
printf '%s\n' "lightweight smoke"
|
|
3056
|
-
;;
|
|
3057
|
-
*)
|
|
3058
|
-
printf '%s\n' "/develop-feature smoke"
|
|
3059
|
-
;;
|
|
3060
|
-
esac
|
|
3477
|
+
printf '%s\n' "lightweight smoke"
|
|
3061
3478
|
}
|
|
3062
3479
|
|
|
3063
3480
|
doctor_output_has_fatal_patterns() {
|
|
@@ -3317,6 +3734,84 @@ prompt_with_default_fzf() {
|
|
|
3317
3734
|
printf '%s\n' "$default"
|
|
3318
3735
|
}
|
|
3319
3736
|
|
|
3737
|
+
prompt_text_fzf() {
|
|
3738
|
+
local prompt="$1"
|
|
3739
|
+
local default="${2:-}"
|
|
3740
|
+
local header="Type value and press Enter to confirm"
|
|
3741
|
+
if [[ -n "$default" ]]; then
|
|
3742
|
+
header="$header (empty = current value)"
|
|
3743
|
+
fi
|
|
3744
|
+
local fzf_args=(
|
|
3745
|
+
--ansi
|
|
3746
|
+
--border
|
|
3747
|
+
--height=70%
|
|
3748
|
+
--layout=reverse
|
|
3749
|
+
--cycle
|
|
3750
|
+
--no-sort
|
|
3751
|
+
--phony
|
|
3752
|
+
--print-query
|
|
3753
|
+
--bind "enter:accept"
|
|
3754
|
+
--prompt "$prompt "
|
|
3755
|
+
--header "$header"
|
|
3756
|
+
)
|
|
3757
|
+
if [[ "${#FZF_COLOR_ARGS[@]}" -gt 0 ]]; then
|
|
3758
|
+
fzf_args+=("${FZF_COLOR_ARGS[@]}")
|
|
3759
|
+
fi
|
|
3760
|
+
|
|
3761
|
+
local output selected
|
|
3762
|
+
if ! output="$(printf '%s\n' "<press Enter to confirm>" | fzf "${fzf_args[@]}")"; then
|
|
3763
|
+
return 1
|
|
3764
|
+
fi
|
|
3765
|
+
selected="$(printf '%s\n' "$output" | sed -n '1p')"
|
|
3766
|
+
selected="$(trim "$selected")"
|
|
3767
|
+
if [[ -n "$selected" ]]; then
|
|
3768
|
+
printf '%s\n' "$selected"
|
|
3769
|
+
else
|
|
3770
|
+
printf '%s\n' "$default"
|
|
3771
|
+
fi
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
prompt_text_tui() {
|
|
3775
|
+
local prompt="$1"
|
|
3776
|
+
local default="${2:-}"
|
|
3777
|
+
local answer
|
|
3778
|
+
if [[ -n "$default" ]]; then
|
|
3779
|
+
read -r -p "$prompt [keep current]: " answer
|
|
3780
|
+
else
|
|
3781
|
+
read -r -p "$prompt: " answer
|
|
3782
|
+
fi
|
|
3783
|
+
answer="$(trim "$answer")"
|
|
3784
|
+
if [[ -n "$answer" ]]; then
|
|
3785
|
+
printf '%s\n' "$answer"
|
|
3786
|
+
else
|
|
3787
|
+
printf '%s\n' "$default"
|
|
3788
|
+
fi
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
prompt_text_interactive() {
|
|
3792
|
+
local prompt="$1"
|
|
3793
|
+
local default="${2:-}"
|
|
3794
|
+
if fzf_available; then
|
|
3795
|
+
prompt_text_fzf "$prompt" "$default"
|
|
3796
|
+
else
|
|
3797
|
+
prompt_text_tui "$prompt" "$default"
|
|
3798
|
+
fi
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3801
|
+
confirm_action_interactive() {
|
|
3802
|
+
local prompt="$1"
|
|
3803
|
+
local selected
|
|
3804
|
+
if fzf_available; then
|
|
3805
|
+
selected="$(choose_single_fzf "$prompt" "Confirm" "Cancel" || true)"
|
|
3806
|
+
[[ "$selected" == "Confirm" ]]
|
|
3807
|
+
return
|
|
3808
|
+
fi
|
|
3809
|
+
local answer
|
|
3810
|
+
read -r -p "$prompt [y/N]: " answer
|
|
3811
|
+
answer="$(trim "$answer")"
|
|
3812
|
+
[[ "$answer" =~ ^[Yy]([Ee][Ss])?$ ]]
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3320
3815
|
choose_single_by_index() {
|
|
3321
3816
|
local prompt="$1"
|
|
3322
3817
|
shift
|
|
@@ -3877,6 +4372,7 @@ sync_current_project_after_upgrade() {
|
|
|
3877
4372
|
}
|
|
3878
4373
|
|
|
3879
4374
|
upgrade_mempalace_graph() {
|
|
4375
|
+
local project_wing shared_docs_wing
|
|
3880
4376
|
# Only run if mempalace was enabled for this project
|
|
3881
4377
|
if [[ ! "${AGENTIC_ENABLE_MEMPALACE:-}" =~ ^[Yy] ]]; then
|
|
3882
4378
|
return
|
|
@@ -3886,16 +4382,30 @@ upgrade_mempalace_graph() {
|
|
|
3886
4382
|
return
|
|
3887
4383
|
fi
|
|
3888
4384
|
|
|
4385
|
+
project_wing="$(mempalace_project_wing)"
|
|
4386
|
+
shared_docs_wing="$(mempalace_shared_docs_wing)"
|
|
3889
4387
|
if [[ "$DRY_RUN" == true ]]; then
|
|
3890
|
-
log "DRY-RUN mempalace mine \"$PROJECT_DIR\""
|
|
4388
|
+
log "DRY-RUN mempalace mine \"$PROJECT_DIR\" --wing \"$project_wing\""
|
|
4389
|
+
if [[ -d "$PROJECT_DIR/docs" ]]; then
|
|
4390
|
+
log "DRY-RUN mempalace mine \"$PROJECT_DIR/docs\" --wing \"$shared_docs_wing\""
|
|
4391
|
+
fi
|
|
3891
4392
|
return
|
|
3892
4393
|
fi
|
|
3893
4394
|
|
|
3894
|
-
log "Refreshing MemPalace knowledge graph for $PROJECT_DIR"
|
|
3895
|
-
if mempalace mine "$PROJECT_DIR" >/dev/null 2>&1; then
|
|
4395
|
+
log "Refreshing MemPalace knowledge graph for $PROJECT_DIR (wing: $project_wing)"
|
|
4396
|
+
if mempalace mine "$PROJECT_DIR" --wing "$project_wing" >/dev/null 2>&1; then
|
|
3896
4397
|
log "MemPalace graph updated"
|
|
3897
4398
|
else
|
|
3898
|
-
warn "mempalace mine failed; graph may be stale — run manually: mempalace mine \"$PROJECT_DIR\""
|
|
4399
|
+
warn "mempalace mine failed; graph may be stale — run manually: mempalace mine \"$PROJECT_DIR\" --wing \"$project_wing\""
|
|
4400
|
+
fi
|
|
4401
|
+
|
|
4402
|
+
if [[ -d "$PROJECT_DIR/docs" ]]; then
|
|
4403
|
+
log "Refreshing shared MemPalace docs wing from $PROJECT_DIR/docs"
|
|
4404
|
+
if mempalace mine "$PROJECT_DIR/docs" --wing "$shared_docs_wing" >/dev/null 2>&1; then
|
|
4405
|
+
log "MemPalace shared docs wing updated"
|
|
4406
|
+
else
|
|
4407
|
+
warn "mempalace docs mine failed; shared docs may be stale — run manually: mempalace mine \"$PROJECT_DIR/docs\" --wing \"$shared_docs_wing\""
|
|
4408
|
+
fi
|
|
3899
4409
|
fi
|
|
3900
4410
|
}
|
|
3901
4411
|
|