@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/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
- for mcp_item in "${loaded_mcp_integrations[@]}"; do
1156
- case "$mcp_item" in
1157
- context7)
1158
- if [[ -z "${AGENTIC_ENABLE_CONTEXT7:-}" ]]; then
1159
- AGENTIC_ENABLE_CONTEXT7="y"
1160
- fi
1161
- ;;
1162
- mempalace)
1163
- if [[ -z "${AGENTIC_ENABLE_MEMPALACE:-}" ]]; then
1164
- AGENTIC_ENABLE_MEMPALACE="y"
1165
- fi
1166
- ;;
1167
- esac
1168
- done
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
- out "To add a Context7 API key later, set CONTEXT7_API_KEY before rerunning agentic or edit the generated config:"
1870
+ }
1748
1871
 
1749
- if selected_agent_os_contains "opencode"; then
1750
- out " - $PROJECT_DIR/opencode.json"
1751
- out " - $PROJECT_DIR/.opencode/opencode.json"
1752
- out ' Example: "headers": {"CONTEXT7_API_KEY": "ctx7_your_api_key_here"}'
1753
- fi
1754
- if selected_agent_os_contains "codex"; then
1755
- out " - $PROJECT_DIR/.codex/config.toml"
1756
- out ' Example: http_headers = { "CONTEXT7_API_KEY" = "ctx7_your_api_key_here" }'
1757
- fi
1758
- if selected_agent_os_contains "claude"; then
1759
- out " - $PROJECT_DIR/.mcp.json"
1760
- out ' Example: "headers": {"CONTEXT7_API_KEY": "ctx7_your_api_key_here"}'
1761
- fi
1762
- if selected_agent_os_contains "cursor"; then
1763
- out " - $PROJECT_DIR/.cursor/mcp.json"
1764
- out ' Example: "headers": {"CONTEXT7_API_KEY": "ctx7_your_api_key_here"}'
1765
- fi
1766
- if selected_agent_os_contains "gemini"; then
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
- if selected_agent_os_contains "antigravity"; then
1775
- out " - $HOME/.gemini/antigravity/mcp_config.json"
1776
- out ' Example: "headers": {"CONTEXT7_API_KEY": "ctx7_your_api_key_here"}'
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) Optionally initialize project-local MemPalace cache:"
1828
- out " mempalace init \"$PROJECT_DIR\" --yes --auto-mine"
1829
- out "4) Optionally index existing project memory:"
1830
- out " # optional if --auto-mine was skipped"
1831
- out " mempalace mine \"$PROJECT_DIR\""
1832
- out "5) Verify in your IDE/agent that MemPalace MCP tools are connected."
1833
- out "Note: Ollama at localhost:11434 is optional; MemPalace can run heuristics-only without it."
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
- if "$@" >"$output_file" 2>&1; then
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
- log "$step_prefix [4/4] Initializing project memory at $PROJECT_DIR"
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 --auto-mine; then
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 install mempalace >/dev/null 2>&1; then
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 upgrade/re-install with existing plugin config, keep current settings
2176
- if [[ -f "$OPENCODE_PLUGIN_CONFIG_FILE" && ( -n "${AGENTIC_ENABLE_MEMPALACE:-}" || -n "${AGENTIC_ENABLE_CONTEXT7:-}" ) ]]; then
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-opencode-notifier" "agent-model-mapper")
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
- log "Telegram plugin enabled; credentials are read only from OPENCODE_TELEGRAM_BOT_TOKEN and OPENCODE_TELEGRAM_CHAT_ID"
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
- python3 - "$config_path" <<'PY'
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
- path = Path(sys.argv[1])
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
- models.append(f"{provider_name}/{model_name}")
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
- models.append(item)
2690
+ add_model(item)
2324
2691
  if key == "fallback" and isinstance(item, list):
2325
- models.extend(model for model in item if isinstance(model, str))
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(path.read_text(encoding="utf-8"))
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
- confirm="$(trim "$confirm")"
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
- if [[ "${#CREATED_PATHS[@]}" -eq 0 ]]; then
2879
- out "- (none)"
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' "/develop-feature напиши hello world python"
3469
+ printf '%s\n' "Reply with exactly: AGENTIC_DOCTOR_OK"
3037
3470
  }
3038
3471
 
3039
3472
  doctor_prompt_for_agent() {
3040
- local agent_os="$1"
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
- local agent_os="$1"
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