@ornexus/neocortex 4.0.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.

Potentially problematic release.


This version of @ornexus/neocortex might be problematic. Click here for more details.

Files changed (121) hide show
  1. package/LICENSE +56 -0
  2. package/README.md +32 -0
  3. package/install.js +486 -0
  4. package/install.ps1 +1790 -0
  5. package/install.sh +1587 -0
  6. package/package.json +104 -0
  7. package/packages/client/dist/adapters/adapter-registry.d.ts +61 -0
  8. package/packages/client/dist/adapters/adapter-registry.js +106 -0
  9. package/packages/client/dist/adapters/antigravity-adapter.d.ts +18 -0
  10. package/packages/client/dist/adapters/antigravity-adapter.js +77 -0
  11. package/packages/client/dist/adapters/claude-code-adapter.d.ts +19 -0
  12. package/packages/client/dist/adapters/claude-code-adapter.js +79 -0
  13. package/packages/client/dist/adapters/codex-adapter.d.ts +19 -0
  14. package/packages/client/dist/adapters/codex-adapter.js +80 -0
  15. package/packages/client/dist/adapters/cursor-adapter.d.ts +19 -0
  16. package/packages/client/dist/adapters/cursor-adapter.js +115 -0
  17. package/packages/client/dist/adapters/gemini-adapter.d.ts +18 -0
  18. package/packages/client/dist/adapters/gemini-adapter.js +71 -0
  19. package/packages/client/dist/adapters/index.d.ts +19 -0
  20. package/packages/client/dist/adapters/index.js +21 -0
  21. package/packages/client/dist/adapters/platform-detector.d.ts +46 -0
  22. package/packages/client/dist/adapters/platform-detector.js +106 -0
  23. package/packages/client/dist/adapters/target-adapter.d.ts +70 -0
  24. package/packages/client/dist/adapters/target-adapter.js +12 -0
  25. package/packages/client/dist/adapters/vscode-adapter.d.ts +19 -0
  26. package/packages/client/dist/adapters/vscode-adapter.js +72 -0
  27. package/packages/client/dist/agent/refresh-stubs.d.ts +65 -0
  28. package/packages/client/dist/agent/refresh-stubs.js +234 -0
  29. package/packages/client/dist/agent/update-agent-yaml.d.ts +26 -0
  30. package/packages/client/dist/agent/update-agent-yaml.js +102 -0
  31. package/packages/client/dist/agent/update-description.d.ts +45 -0
  32. package/packages/client/dist/agent/update-description.js +251 -0
  33. package/packages/client/dist/cache/crypto-utils.d.ts +30 -0
  34. package/packages/client/dist/cache/crypto-utils.js +76 -0
  35. package/packages/client/dist/cache/encrypted-cache.d.ts +30 -0
  36. package/packages/client/dist/cache/encrypted-cache.js +94 -0
  37. package/packages/client/dist/cache/in-memory-asset-cache.d.ts +59 -0
  38. package/packages/client/dist/cache/in-memory-asset-cache.js +70 -0
  39. package/packages/client/dist/cache/index.d.ts +13 -0
  40. package/packages/client/dist/cache/index.js +13 -0
  41. package/packages/client/dist/cli.d.ts +14 -0
  42. package/packages/client/dist/cli.js +194 -0
  43. package/packages/client/dist/commands/activate.d.ts +55 -0
  44. package/packages/client/dist/commands/activate.js +390 -0
  45. package/packages/client/dist/commands/cache-status.d.ts +39 -0
  46. package/packages/client/dist/commands/cache-status.js +112 -0
  47. package/packages/client/dist/commands/invoke.d.ts +70 -0
  48. package/packages/client/dist/commands/invoke.js +490 -0
  49. package/packages/client/dist/config/resolver-selection.d.ts +40 -0
  50. package/packages/client/dist/config/resolver-selection.js +278 -0
  51. package/packages/client/dist/config/secure-config.d.ts +78 -0
  52. package/packages/client/dist/config/secure-config.js +269 -0
  53. package/packages/client/dist/constants.d.ts +25 -0
  54. package/packages/client/dist/constants.js +25 -0
  55. package/packages/client/dist/context/context-collector.d.ts +28 -0
  56. package/packages/client/dist/context/context-collector.js +222 -0
  57. package/packages/client/dist/context/context-sanitizer.d.ts +28 -0
  58. package/packages/client/dist/context/context-sanitizer.js +145 -0
  59. package/packages/client/dist/index.d.ts +55 -0
  60. package/packages/client/dist/index.js +38 -0
  61. package/packages/client/dist/license/index.d.ts +5 -0
  62. package/packages/client/dist/license/index.js +5 -0
  63. package/packages/client/dist/license/license-client.d.ts +79 -0
  64. package/packages/client/dist/license/license-client.js +257 -0
  65. package/packages/client/dist/machine/fingerprint.d.ts +34 -0
  66. package/packages/client/dist/machine/fingerprint.js +160 -0
  67. package/packages/client/dist/machine/index.d.ts +5 -0
  68. package/packages/client/dist/machine/index.js +5 -0
  69. package/packages/client/dist/resilience/circuit-breaker.d.ts +70 -0
  70. package/packages/client/dist/resilience/circuit-breaker.js +170 -0
  71. package/packages/client/dist/resilience/degradation-manager.d.ts +67 -0
  72. package/packages/client/dist/resilience/degradation-manager.js +164 -0
  73. package/packages/client/dist/resilience/freshness-indicator.d.ts +59 -0
  74. package/packages/client/dist/resilience/freshness-indicator.js +100 -0
  75. package/packages/client/dist/resilience/index.d.ts +8 -0
  76. package/packages/client/dist/resilience/index.js +8 -0
  77. package/packages/client/dist/resilience/recovery-detector.d.ts +59 -0
  78. package/packages/client/dist/resilience/recovery-detector.js +74 -0
  79. package/packages/client/dist/resolvers/asset-resolver.d.ts +79 -0
  80. package/packages/client/dist/resolvers/asset-resolver.js +13 -0
  81. package/packages/client/dist/resolvers/local-resolver.d.ts +26 -0
  82. package/packages/client/dist/resolvers/local-resolver.js +218 -0
  83. package/packages/client/dist/resolvers/remote-resolver.d.ts +91 -0
  84. package/packages/client/dist/resolvers/remote-resolver.js +282 -0
  85. package/packages/client/dist/telemetry/index.d.ts +5 -0
  86. package/packages/client/dist/telemetry/index.js +5 -0
  87. package/packages/client/dist/telemetry/offline-queue.d.ts +57 -0
  88. package/packages/client/dist/telemetry/offline-queue.js +131 -0
  89. package/packages/client/dist/tier/index.d.ts +5 -0
  90. package/packages/client/dist/tier/index.js +5 -0
  91. package/packages/client/dist/tier/tier-aware-client.d.ts +97 -0
  92. package/packages/client/dist/tier/tier-aware-client.js +260 -0
  93. package/packages/client/dist/types/index.d.ts +140 -0
  94. package/packages/client/dist/types/index.js +38 -0
  95. package/postinstall.js +272 -0
  96. package/targets-stubs/antigravity/README.md +36 -0
  97. package/targets-stubs/antigravity/gemini.md +22 -0
  98. package/targets-stubs/antigravity/install-antigravity.sh +44 -0
  99. package/targets-stubs/antigravity/mcp-config.json +9 -0
  100. package/targets-stubs/antigravity/skill/SKILL.md +67 -0
  101. package/targets-stubs/claude-code/README.md +20 -0
  102. package/targets-stubs/claude-code/neocortex.agent.yaml +24 -0
  103. package/targets-stubs/claude-code/neocortex.md +125 -0
  104. package/targets-stubs/codex/README.md +32 -0
  105. package/targets-stubs/codex/agents.md +61 -0
  106. package/targets-stubs/codex/config-mcp.toml +6 -0
  107. package/targets-stubs/codex/install-codex.sh +61 -0
  108. package/targets-stubs/cursor/README.md +33 -0
  109. package/targets-stubs/cursor/agent.md +94 -0
  110. package/targets-stubs/cursor/install-cursor.sh +35 -0
  111. package/targets-stubs/cursor/mcp.json +11 -0
  112. package/targets-stubs/gemini-cli/README.md +34 -0
  113. package/targets-stubs/gemini-cli/agent.md +101 -0
  114. package/targets-stubs/gemini-cli/gemini.md +16 -0
  115. package/targets-stubs/gemini-cli/install-gemini.sh +56 -0
  116. package/targets-stubs/gemini-cli/settings-mcp.json +11 -0
  117. package/targets-stubs/vscode/README.md +34 -0
  118. package/targets-stubs/vscode/agent.md +102 -0
  119. package/targets-stubs/vscode/copilot-instructions.md +16 -0
  120. package/targets-stubs/vscode/install-vscode.sh +42 -0
  121. package/targets-stubs/vscode/mcp.json +13 -0
package/install.sh ADDED
@@ -0,0 +1,1587 @@
1
+ #!/bin/bash
2
+
3
+ # Neocortex - Instalador Unix
4
+ # Development Orchestrator
5
+
6
+ # Versao do instalador
7
+ VERSION="4.0.1"
8
+
9
+ # Flags
10
+ MIGRATION_DETECTED=false
11
+ MIGRATION_SOURCES=""
12
+ CODERABBIT_INSTALLED=false
13
+ LEGACY_ITEMS=""
14
+ LEGACY_WARNINGS=0
15
+ SELECTED_TARGETS=""
16
+ VALID_TARGETS="claude-code cursor vscode gemini-cli codex antigravity"
17
+ # LOCAL_MODE removed (Epic 50): thin-client ALWAYS, zero IP on client
18
+ # SSoT: packages/client/src/constants.ts DEFAULT_SERVER_URL
19
+ NEOCORTEX_SERVER_URL="https://api.neocortex.sh"
20
+
21
+ # =============================================================================
22
+ # DETECCAO DE AMBIENTE
23
+ # =============================================================================
24
+
25
+ is_interactive() { [ -t 0 ] && [ -t 1 ]; }
26
+
27
+ supports_colors() { [ -t 1 ] && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; }
28
+
29
+ supports_unicode() {
30
+ case "${LANG:-}${LC_ALL:-}${LC_CTYPE:-}" in
31
+ *UTF-8*|*utf-8*|*utf8*) return 0 ;;
32
+ *) return 1 ;;
33
+ esac
34
+ }
35
+
36
+ if [ -z "$HOME" ]; then
37
+ HOME=$(getent passwd "$(whoami)" | cut -d: -f6 2>/dev/null || echo "/root")
38
+ export HOME
39
+ fi
40
+
41
+ # =============================================================================
42
+ # PARSING DE ARGUMENTOS
43
+ # =============================================================================
44
+
45
+ AUTO_YES=false
46
+ DEBUG_MODE=false
47
+ SKIP_PROJECT_DIRS=false
48
+ CREATE_PROJECT=false
49
+ QUIET_MODE=false
50
+ NO_BANNER=false
51
+ CLEANUP_LEGACY=false
52
+
53
+ while [[ $# -gt 0 ]]; do
54
+ case $1 in
55
+ -y|--yes) AUTO_YES=true; shift ;;
56
+ -d|--debug) DEBUG_MODE=true; shift ;;
57
+ -s|--skip-project) SKIP_PROJECT_DIRS=true; shift ;;
58
+ -q|--quiet) QUIET_MODE=true; shift ;;
59
+ --no-banner) NO_BANNER=true; shift ;;
60
+ --cleanup-legacy) CLEANUP_LEGACY=true; shift ;;
61
+ --targets=*) SELECTED_TARGETS="${1#--targets=}"; shift ;;
62
+ --create-project) CREATE_PROJECT=true; shift ;;
63
+ --local) warn "--local flag removed: thin-client mode is now mandatory (zero IP on client)"; shift ;;
64
+ --server-url=*) NEOCORTEX_SERVER_URL="${1#--server-url=}"; shift ;;
65
+ -h|--help)
66
+ echo ""
67
+ echo " Neocortex Installer v${VERSION}"
68
+ echo " Development Orchestrator"
69
+ echo ""
70
+ echo " Uso: npx @ornexus/neocortex [opcoes]"
71
+ echo ""
72
+ echo " Opcoes:"
73
+ echo " -y, --yes Modo automatico (Claude Code only)"
74
+ echo " --targets=<lista> Plataformas separadas por virgula"
75
+ echo " --create-project Instalar estrutura no projeto"
76
+ echo " -s, --skip-project Nao perguntar sobre projeto"
77
+ echo " -q, --quiet Modo silencioso"
78
+ echo " --cleanup-legacy (Redundante) Limpeza agora e automatica"
79
+ echo " --local (Removido) Thin-client obrigatorio, zero IP no client"
80
+ echo " --server-url=<url> URL do server Neocortex (padrao: api.neocortex.sh)"
81
+ echo " -d, --debug Modo debug"
82
+ echo " -h, --help Mostra esta ajuda"
83
+ echo ""
84
+ echo " Plataformas: claude-code, cursor, vscode, gemini-cli, codex, antigravity"
85
+ exit 0
86
+ ;;
87
+ *) shift ;;
88
+ esac
89
+ done
90
+
91
+ if ! is_interactive; then
92
+ AUTO_YES=true
93
+ fi
94
+
95
+ # =============================================================================
96
+ # CORES E SIMBOLOS
97
+ # =============================================================================
98
+
99
+ if supports_colors && [ "$QUIET_MODE" = false ]; then
100
+ RED='\033[0;31m'
101
+ GREEN='\033[0;32m'
102
+ YELLOW='\033[1;33m'
103
+ BLUE='\033[0;34m'
104
+ MAGENTA='\033[0;35m'
105
+ CYAN='\033[0;36m'
106
+ WHITE='\033[1;37m'
107
+ GRAY='\033[0;90m'
108
+ BOLD='\033[1m'
109
+ DIM='\033[2m'
110
+ NC='\033[0m'
111
+ else
112
+ RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN=''
113
+ WHITE='' GRAY='' BOLD='' DIM='' NC=''
114
+ fi
115
+
116
+ if supports_unicode; then
117
+ SYM_OK="✓"
118
+ SYM_FAIL="✗"
119
+ SYM_WARN="!"
120
+ SYM_ARROW="→"
121
+ SYM_DOT="·"
122
+ SYM_SPINNER_FRAMES=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
123
+ else
124
+ SYM_OK="ok"
125
+ SYM_FAIL="x"
126
+ SYM_WARN="!"
127
+ SYM_ARROW="->"
128
+ SYM_DOT="."
129
+ SYM_SPINNER_FRAMES=('|' '/' '-' '\')
130
+ fi
131
+
132
+ # =============================================================================
133
+ # FUNCOES DE LOG
134
+ # =============================================================================
135
+
136
+ # Step header: [1/5] Installing core...
137
+ step() {
138
+ local num=$1 total=$2 msg=$3
139
+ echo ""
140
+ echo -e " ${BOLD}${CYAN}[$num/$total]${NC} ${BOLD}$msg${NC}"
141
+ }
142
+
143
+ # Success line
144
+ ok() { echo -e " ${GREEN}${SYM_OK}${NC} $1"; }
145
+
146
+ # Warning line
147
+ warn() { echo -e " ${YELLOW}${SYM_WARN}${NC} $1"; }
148
+
149
+ # Error line
150
+ fail() { echo -e " ${RED}${SYM_FAIL}${NC} $1"; }
151
+
152
+ # Info line
153
+ info() { echo -e " ${DIM}${SYM_ARROW} $1${NC}"; }
154
+
155
+ # Debug line
156
+ debug() { [ "$DEBUG_MODE" = true ] && echo -e " ${GRAY}[debug] $1${NC}"; }
157
+
158
+ # Spinner for background tasks
159
+ run_with_spinner() {
160
+ local msg="$1"
161
+ shift
162
+ "$@" &
163
+ local pid=$!
164
+ local i=0
165
+ local len=${#SYM_SPINNER_FRAMES[@]}
166
+
167
+ while kill -0 "$pid" 2>/dev/null; do
168
+ printf "\r ${CYAN}%s${NC} %s" "${SYM_SPINNER_FRAMES[$((i % len))]}" "$msg"
169
+ i=$((i + 1))
170
+ sleep 0.08
171
+ done
172
+
173
+ wait "$pid"
174
+ local exit_code=$?
175
+
176
+ if [ $exit_code -eq 0 ]; then
177
+ printf "\r ${GREEN}${SYM_OK}${NC} %s\n" "$msg"
178
+ else
179
+ printf "\r ${RED}${SYM_FAIL}${NC} %s\n" "$msg"
180
+ fi
181
+
182
+ return $exit_code
183
+ }
184
+
185
+ # =============================================================================
186
+ # FUNCOES DE COPIA (silenciosas - sem log individual)
187
+ # =============================================================================
188
+
189
+ copy_file() {
190
+ local src="$1" dest="$2"
191
+ debug "Copiando: $src -> $dest"
192
+ if [ -f "$src" ]; then
193
+ cp "$src" "$dest" 2>/dev/null
194
+ else
195
+ debug "Arquivo nao encontrado: $src"
196
+ return 1
197
+ fi
198
+ }
199
+
200
+ copy_dir() {
201
+ local src="$1" dest="$2"
202
+ debug "Copiando dir: $src -> $dest"
203
+ [ -d "$src" ] && cp -r "$src" "$dest" 2>/dev/null
204
+ }
205
+
206
+ # Patch description tier in any agent file (YAML frontmatter or Markdown H1)
207
+ # Usage: patch_description_tier <file>
208
+ # Reads tier from ~/.neocortex/config.json and replaces ANY tier label with actual tier.
209
+ # Supports: (Free) <-> (Pro) <-> (Enterprise) in any direction.
210
+ # Works on both frontmatter description and banner body lines.
211
+ patch_description_tier() {
212
+ local target_file="$1"
213
+ local config_file="${HOME}/.neocortex/config.json"
214
+
215
+ [ -f "$target_file" ] || return 0
216
+ [ -f "$config_file" ] || return 0
217
+
218
+ local tier
219
+ tier=$(cat "$config_file" 2>/dev/null | grep -o '"tier"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
220
+ [ -n "$tier" ] || return 0
221
+
222
+ local tier_label
223
+ case "$tier" in
224
+ free) tier_label="Free" ;;
225
+ pro) tier_label="Pro" ;;
226
+ enterprise) tier_label="Enterprise" ;;
227
+ *) return 0 ;;
228
+ esac
229
+
230
+ # Replace ANY tier label with the correct one in non-banner lines
231
+ # (frontmatter description, body text, H1 headers, etc.)
232
+ # Each sed expression replaces one possible source tier with the target tier.
233
+ # If source == target, sed is a no-op (replaces X with X). This is correct.
234
+ sed -i.bak \
235
+ -e "s/(Free)/(${tier_label})/g" \
236
+ -e "s/(Pro)/(${tier_label})/g" \
237
+ -e "s/(Enterprise)/(${tier_label})/g" \
238
+ "$target_file" 2>/dev/null
239
+ rm -f "${target_file}.bak" 2>/dev/null
240
+
241
+ # Fix banner alignment: the OrNexus Team line has a fixed 62-char frame.
242
+ # After replacing tier labels of different lengths, re-pad to maintain alignment.
243
+ # Pattern: "│ ## ### ###### ## OrNexus Team (Tier)<spaces>│"
244
+ # Inner width must be exactly 60 chars (│ + 60 + │ = 62).
245
+ if grep -q "OrNexus Team (${tier_label})" "$target_file" 2>/dev/null; then
246
+ local prefix="│ ## ### ###### ## " # 25 chars inner prefix
247
+ local content="OrNexus Team (${tier_label})"
248
+ local content_len=${#content}
249
+ local pad_len=$((60 - 25 - content_len))
250
+ local padding=""
251
+ local i
252
+ for ((i=0; i<pad_len; i++)); do padding="${padding} "; done
253
+ local new_line="${prefix}${content}${padding}│"
254
+ # Replace the (possibly misaligned) banner line with the correctly padded one
255
+ sed -i.bak -e "s|│ ## ### ###### ## OrNexus Team ([^)]*).*│|${new_line}|" "$target_file" 2>/dev/null
256
+ rm -f "${target_file}.bak" 2>/dev/null
257
+ fi
258
+
259
+ debug "Tier patched to (${tier_label}) in $(basename "$target_file")"
260
+ }
261
+
262
+ # =============================================================================
263
+ # DETECCAO DE VERSAO ANTIGA
264
+ # =============================================================================
265
+
266
+ detect_old_installation() {
267
+ debug "Verificando instalacoes antigas..."
268
+
269
+ local dest_dir="${HOME}/.claude/agents/neocortex"
270
+
271
+ if [ -f "$dest_dir/.version" ]; then
272
+ INSTALLED_VERSION=$(cat "$dest_dir/.version" 2>/dev/null)
273
+ local new_version=""
274
+ if [ -f "$SOURCE_DIR/package.json" ]; then
275
+ new_version=$(grep '"version"' "$SOURCE_DIR/package.json" 2>/dev/null | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
276
+ fi
277
+ if [ -n "$INSTALLED_VERSION" ] && [ -n "$new_version" ]; then
278
+ if [ "$INSTALLED_VERSION" = "$new_version" ]; then
279
+ info "Reinstalando v$new_version"
280
+ else
281
+ info "Atualizando v$INSTALLED_VERSION ${SYM_ARROW} v$new_version"
282
+ fi
283
+ fi
284
+ fi
285
+
286
+ # Check for old files
287
+ local old_files=()
288
+ if [ -f "$dest_dir/step-registry.json" ]; then
289
+ if ! grep -q "state.json" "$dest_dir/workflow.md" 2>/dev/null; then
290
+ old_files+=("workflow antigo")
291
+ fi
292
+ fi
293
+
294
+ if grep -r "orchestrator.db" "$dest_dir" 2>/dev/null | grep -v "backup" | grep -v "migrated" > /dev/null; then
295
+ old_files+=("referencias SQLite")
296
+ fi
297
+
298
+ if [ ${#old_files[@]} -gt 0 ]; then
299
+ MIGRATION_DETECTED=true
300
+ MIGRATION_SOURCES="${old_files[*]}"
301
+ debug "Detectado: $MIGRATION_SOURCES"
302
+ fi
303
+ }
304
+
305
+ # =============================================================================
306
+ # LIMPEZA AUTOMATICA DE ARTEFATOS LEGADOS (auto_cleanup_legacy)
307
+ # Unifica: detect_legacy_artifacts + cleanup_legacy_artifacts + cleanup_legacy_ip
308
+ # + cleanup_legacy_ip_project + npm globals + cross-platform cleanup
309
+ # Roda AUTOMATICAMENTE a cada instalacao - sem necessidade de --cleanup-legacy
310
+ # Idempotente: rodar multiplas vezes sem efeitos colaterais
311
+ # Seguro: NAO remove dados do usuario (stories, epics, state.json, config.json)
312
+ # =============================================================================
313
+
314
+ auto_cleanup_legacy() {
315
+ local removed=0
316
+ local claude_dir="$HOME/.claude"
317
+ local neocortex_dir="$claude_dir/agents/neocortex"
318
+ local skills_dir="$claude_dir/skills/neocortex"
319
+
320
+ debug "Executando limpeza automatica de artefatos legados..."
321
+
322
+ # ─── Helper: remove e loga ───────────────────────────────────────────
323
+ _remove_legacy() {
324
+ local target="$1"
325
+ local label="$2"
326
+ if [ -e "$target" ]; then
327
+ local display="${target/#$HOME/~}"
328
+ if rm -rf "$target" 2>/dev/null; then
329
+ info "Removido: $display ($label)"
330
+ removed=$((removed + 1))
331
+ else
332
+ debug "Falha ao remover: $display"
333
+ fi
334
+ fi
335
+ }
336
+
337
+ # ─── Categoria 1: Pacotes NPM globais legados ────────────────────────
338
+ if command -v npm >/dev/null 2>&1; then
339
+ # Verificar e remover pacotes npm globais legados
340
+ for pkg in "@ornexus/neocortex-cli" "@ornexus-ai/neocortex"; do
341
+ if npm list -g "$pkg" --depth=0 2>/dev/null | grep -q "$pkg"; then
342
+ if npm uninstall -g "$pkg" 2>/dev/null; then
343
+ info "Removido: $pkg (pacote npm global legado)"
344
+ removed=$((removed + 1))
345
+ else
346
+ debug "Falha ao remover pacote npm: $pkg"
347
+ fi
348
+ fi
349
+ done
350
+
351
+ # Verificar binario neocortex-cli no PATH
352
+ local ncli_path
353
+ ncli_path=$(command -v neocortex-cli 2>/dev/null || true)
354
+ if [ -n "$ncli_path" ]; then
355
+ # Verificar se e um link/binario npm (nao remover se for outro tool)
356
+ if [[ "$ncli_path" == *"node_modules"* ]] || [[ "$ncli_path" == *"npm"* ]]; then
357
+ rm -f "$ncli_path" 2>/dev/null && {
358
+ info "Removido: $ncli_path (binario neocortex-cli legado)"
359
+ removed=$((removed + 1))
360
+ }
361
+ fi
362
+ fi
363
+ fi
364
+
365
+ # ─── Categoria 2: Claude Code (~/.claude/) ────────────────────────────
366
+ # IP proprietaria de versoes anteriores
367
+ _remove_legacy "$neocortex_dir/core" "IP legada"
368
+ _remove_legacy "$skills_dir" "skills legadas"
369
+ _remove_legacy "$neocortex_dir/workflow.md" "workflow removido no Tier 3"
370
+ _remove_legacy "$neocortex_dir/package.json" "arquivo desnecessario"
371
+ _remove_legacy "$neocortex_dir/README.md" "arquivo desnecessario"
372
+
373
+ # Step folders legados
374
+ for folder in steps-c steps-e steps-p steps-r steps-u; do
375
+ _remove_legacy "$neocortex_dir/$folder" "steps legados"
376
+ done
377
+
378
+ # Artefatos de instalacoes anteriores
379
+ _remove_legacy "$claude_dir/agents/.git" "repo git antigo"
380
+ _remove_legacy "$claude_dir/.claude" "diretorio aninhado (erro BMAD)"
381
+ _remove_legacy "$claude_dir/agents-ldtn" "diretorio legado"
382
+ _remove_legacy "$claude_dir/.superclaude-metadata.json" "metadata SuperClaude"
383
+
384
+ # Backups SuperClaude antigos
385
+ if [ -d "$claude_dir/backups" ]; then
386
+ for f in "$claude_dir/backups"/superclaude_backup_*.tar.gz; do
387
+ [ -f "$f" ] || continue
388
+ _remove_legacy "$f" "backup SuperClaude antigo"
389
+ done
390
+ fi
391
+
392
+ # ─── Categoria 3: Cursor ──────────────────────────────────────────────
393
+ _remove_legacy "$HOME/.cursor/neocortex" "configs Cursor legadas"
394
+ # .cursorrules com referencias neocortex (verificar antes de remover)
395
+ if [ -f "$HOME/.cursorrules" ]; then
396
+ if grep -qi "neocortex\|ornexus\|synapse" "$HOME/.cursorrules" 2>/dev/null; then
397
+ _remove_legacy "$HOME/.cursorrules" "cursorrules com refs legadas"
398
+ fi
399
+ fi
400
+
401
+ # ─── Categoria 4: VS Code ─────────────────────────────────────────────
402
+ _remove_legacy "$HOME/.vscode/neocortex" "configs VS Code legadas"
403
+ # .instructions.md com referencias neocortex antigo
404
+ if [ -f "$HOME/.instructions.md" ]; then
405
+ if grep -qi "neocortex\|ornexus\|synapse" "$HOME/.instructions.md" 2>/dev/null; then
406
+ _remove_legacy "$HOME/.instructions.md" "instructions.md com refs legadas"
407
+ fi
408
+ fi
409
+
410
+ # ─── Categoria 5: Gemini CLI ──────────────────────────────────────────
411
+ _remove_legacy "$HOME/.gemini/neocortex" "configs Gemini legadas"
412
+
413
+ # ─── Categoria 6: Codex ───────────────────────────────────────────────
414
+ _remove_legacy "$HOME/.codex/neocortex" "configs Codex legadas"
415
+
416
+ # ─── Categoria 7: Antigravity ─────────────────────────────────────────
417
+ # Configs legadas de Antigravity sao gerenciadas pelo adapter, sem path fixo global
418
+
419
+ # ─── Categoria 8: Plaintext cache cleanup (Epic 62 - GAP 1) ─────────
420
+ local cache_dir="$HOME/.neocortex/cache"
421
+ if [ -d "$cache_dir" ]; then
422
+ # Remove plaintext menu-cache.json
423
+ _remove_legacy "$cache_dir/menu-cache.json" "cache plaintext (menu)"
424
+
425
+ # Remove any non-.enc files in cache dir (excluding directories)
426
+ for cache_file in "$cache_dir"/*; do
427
+ [ -f "$cache_file" ] || continue
428
+ case "$cache_file" in
429
+ *.enc) continue ;; # Keep encrypted cache files
430
+ *) _remove_legacy "$cache_file" "cache plaintext" ;;
431
+ esac
432
+ done
433
+ fi
434
+
435
+ # ─── Resultado ────────────────────────────────────────────────────────
436
+ if [ $removed -gt 0 ]; then
437
+ ok "$removed artefato(s) legado(s) removido(s) automaticamente"
438
+ else
439
+ debug "Nenhum artefato legado encontrado"
440
+ fi
441
+
442
+ # Resetar contadores legados (compatibilidade)
443
+ LEGACY_ITEMS=""
444
+ LEGACY_WARNINGS=0
445
+
446
+ return 0
447
+ }
448
+
449
+ # Cleanup de IP legada em projetos individuais (project-level)
450
+ auto_cleanup_legacy_project() {
451
+ local project_dir="$1"
452
+ local neocortex_dir="$project_dir/.claude/agents/neocortex"
453
+ local skills_dir="$project_dir/.claude/skills/neocortex"
454
+ local cleaned=false
455
+
456
+ # Remover core/ e seus subdiretorios
457
+ if [ -d "$neocortex_dir/core" ]; then
458
+ rm -rf "$neocortex_dir/core"
459
+ cleaned=true
460
+ fi
461
+
462
+ # Remover step folders
463
+ for folder in steps-c steps-e steps-p steps-r steps-u; do
464
+ if [ -d "$neocortex_dir/$folder" ]; then
465
+ rm -rf "$neocortex_dir/$folder"
466
+ cleaned=true
467
+ fi
468
+ done
469
+
470
+ # Remover skills
471
+ if [ -d "$skills_dir" ]; then
472
+ rm -rf "$skills_dir"
473
+ cleaned=true
474
+ fi
475
+
476
+ # Remover arquivos desnecessarios
477
+ for file in package.json README.md workflow.md; do
478
+ if [ -f "$neocortex_dir/$file" ]; then
479
+ rm -f "$neocortex_dir/$file"
480
+ cleaned=true
481
+ fi
482
+ done
483
+
484
+ if [ "$cleaned" = true ]; then
485
+ info "IP proprietaria removida do projeto (agora servida via server remoto)"
486
+ fi
487
+ }
488
+
489
+ detect_project_migration_needs() {
490
+ local migration_needed=false
491
+ local sources=()
492
+
493
+ [ -f ".neocortex/orchestrator.db" ] && { migration_needed=true; sources+=("orchestrator.db"); }
494
+ for yaml_path in "bmad-output/sprint-status.yaml" ".neocortex/sprint-status.yaml" "docs/sprint-status.yaml"; do
495
+ [ -f "$yaml_path" ] && { migration_needed=true; sources+=("$yaml_path"); }
496
+ done
497
+
498
+ if [ "$migration_needed" = true ]; then
499
+ echo "${sources[*]}"
500
+ return 0
501
+ fi
502
+ return 1
503
+ }
504
+
505
+ # =============================================================================
506
+ # BANNER
507
+ # =============================================================================
508
+
509
+ show_banner() {
510
+ [ "$QUIET_MODE" = true ] && return
511
+ [ "$NO_BANNER" = true ] && return
512
+
513
+ echo ""
514
+ echo -e "${CYAN} #######${NC}"
515
+ echo -e "${CYAN} ### ########${NC}"
516
+ echo -e "${CYAN} ######### #####${NC}"
517
+ echo -e "${CYAN} ## ############## ${BOLD}N E O C O R T E X${NC}"
518
+ echo -e "${CYAN} ## ### ###### ## ${BOLD}v${VERSION}${NC}"
519
+ echo -e "${CYAN} ## ### ### ##${NC}"
520
+ echo -e "${CYAN} ## ###### ### ## ${DIM}OrNexus Team${NC}"
521
+ echo -e "${CYAN} ############### ##${NC}"
522
+ echo -e "${CYAN} ##### ########${NC}"
523
+ echo -e "${CYAN} ######## ##${NC}"
524
+ echo -e "${CYAN} #######${NC}"
525
+ echo ""
526
+ }
527
+
528
+ # =============================================================================
529
+ # DETECCAO DO DIRETORIO FONTE
530
+ # =============================================================================
531
+
532
+ detect_source_dir() {
533
+ if [ -n "${BASH_SOURCE[0]}" ]; then
534
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
535
+ elif [ -n "$0" ]; then
536
+ SCRIPT_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
537
+ else
538
+ SCRIPT_DIR="$PWD"
539
+ fi
540
+
541
+ local source_found=false
542
+ if [ -f "$SCRIPT_DIR/targets-stubs/claude-code/neocortex.md" ] || \
543
+ [ -f "$SCRIPT_DIR/targets/claude-code/neocortex.md" ] || \
544
+ [ -f "$SCRIPT_DIR/neocortex.md" ]; then
545
+ source_found=true
546
+ else
547
+ for possible_dir in \
548
+ "$SCRIPT_DIR" \
549
+ "$(npm root -g 2>/dev/null)/@ornexus/neocortex" \
550
+ "$(npm root -g 2>/dev/null)/neocortex" \
551
+ "$(dirname "$0")" \
552
+ "$PWD/node_modules/@ornexus/neocortex" \
553
+ "$PWD/node_modules/neocortex" \
554
+ "$HOME/.npm/_npx/"*"/node_modules/@ornexus/neocortex" \
555
+ "$HOME/.npm/_npx/"*"/node_modules/neocortex"; do
556
+ if [ -f "$possible_dir/targets-stubs/claude-code/neocortex.md" ] 2>/dev/null || \
557
+ [ -f "$possible_dir/targets/claude-code/neocortex.md" ] 2>/dev/null || \
558
+ [ -f "$possible_dir/neocortex.md" ] 2>/dev/null; then
559
+ SCRIPT_DIR="$possible_dir"
560
+ source_found=true
561
+ break
562
+ fi
563
+ done
564
+ fi
565
+
566
+ SOURCE_DIR="$SCRIPT_DIR"
567
+ debug "Source: $SOURCE_DIR"
568
+
569
+ # Epic 65: Emit visible warning when stubs cannot be found
570
+ if [ "$source_found" = false ]; then
571
+ warn "Arquivos de instalacao (stubs) nao encontrados em nenhum path conhecido"
572
+ warn "SCRIPT_DIR: $SCRIPT_DIR"
573
+ warn "Tente reinstalar: npm install -g @ornexus/neocortex"
574
+ fi
575
+ }
576
+
577
+ # =============================================================================
578
+ # TIER 3: CONFIG DO THIN CLIENT
579
+ # =============================================================================
580
+
581
+ setup_thin_client_config() {
582
+ local config_dir="${HOME}/.neocortex"
583
+ local config_file="${config_dir}/config.json"
584
+
585
+ mkdir -p "$config_dir" 2>/dev/null
586
+ mkdir -p "$config_dir/cache" 2>/dev/null
587
+
588
+ # Story 61.4 - F4 remediation: restrictive permissions
589
+ chmod 700 "$config_dir" 2>/dev/null
590
+ chmod 700 "$config_dir/cache" 2>/dev/null
591
+
592
+ if [ -f "$config_file" ]; then
593
+ # Preservar config existente, atualizar apenas serverUrl se necessario
594
+ local existing_mode
595
+ existing_mode=$(grep '"mode"' "$config_file" 2>/dev/null | head -1 | sed 's/.*"mode"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
596
+
597
+ if [ "$existing_mode" = "active" ] || [ "$existing_mode" = "local" ] || [ "$existing_mode" = "remote" ]; then
598
+ # ─── Config schema migration (Epic 62 - GAP 4) ────────────────
599
+ # Check if config needs schema migration
600
+ local has_config_version
601
+ has_config_version=$(grep '"configVersion"' "$config_file" 2>/dev/null)
602
+ if [ -z "$has_config_version" ]; then
603
+ debug "Migrando schema do config.json (adicionando configVersion)"
604
+ if command -v node >/dev/null 2>&1; then
605
+ node -e "
606
+ const fs = require('fs');
607
+ const path = '$config_file';
608
+ try {
609
+ const cfg = JSON.parse(fs.readFileSync(path, 'utf-8'));
610
+ // Add configVersion
611
+ cfg.configVersion = 1;
612
+ // Remove known obsolete fields from old base template
613
+ delete cfg.version;
614
+ delete cfg.cache;
615
+ // Clean obsolete tier:3 from old base template (preserve real tier values)
616
+ if (cfg.tier === 3 && cfg.mode === 'pending-activation') {
617
+ delete cfg.tier;
618
+ }
619
+ fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n');
620
+ }" 2>/dev/null
621
+ chmod 600 "$config_file" 2>/dev/null
622
+ debug "Config migrada para configVersion 1"
623
+ fi
624
+ fi
625
+ # ─── Fix stale localhost serverUrl (Epic 70 - Story 70.02) ────
626
+ # If config has localhost serverUrl and installer has production URL,
627
+ # update serverUrl while preserving all other config fields.
628
+ local existing_server_url
629
+ existing_server_url=$(grep '"serverUrl"' "$config_file" 2>/dev/null | head -1 | sed 's/.*"serverUrl"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
630
+
631
+ if echo "$existing_server_url" | grep -qE '^https?://(localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?'; then
632
+ if [ "$NEOCORTEX_SERVER_URL" != "$existing_server_url" ]; then
633
+ debug "Fixing stale localhost serverUrl: $existing_server_url -> $NEOCORTEX_SERVER_URL"
634
+ if command -v node >/dev/null 2>&1; then
635
+ node -e "
636
+ const fs = require('fs');
637
+ const p = '$config_file';
638
+ try {
639
+ const raw = fs.readFileSync(p, 'utf-8').replace(/^\uFEFF/, '');
640
+ const cfg = JSON.parse(raw);
641
+ cfg.serverUrl = '${NEOCORTEX_SERVER_URL}';
642
+ fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n');
643
+ }" 2>/dev/null
644
+ chmod 600 "$config_file" 2>/dev/null
645
+ debug "serverUrl updated to $NEOCORTEX_SERVER_URL"
646
+ fi
647
+ fi
648
+ fi
649
+
650
+ # ─── Fix stale ornexus.com serverUrl (Epic P46 - Story P46.03) ────
651
+ # If config has old api.neocortex.ornexus.com URL, update to api.neocortex.sh
652
+ if echo "$existing_server_url" | grep -qE 'api\.neocortex\.ornexus\.com'; then
653
+ debug "Fixing old ornexus.com serverUrl: $existing_server_url -> $NEOCORTEX_SERVER_URL"
654
+ if command -v node >/dev/null 2>&1; then
655
+ node -e "
656
+ const fs = require('fs');
657
+ const p = '$config_file';
658
+ try {
659
+ const raw = fs.readFileSync(p, 'utf-8').replace(/^\uFEFF/, '');
660
+ const cfg = JSON.parse(raw);
661
+ cfg.serverUrl = '${NEOCORTEX_SERVER_URL}';
662
+ fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n');
663
+ }" 2>/dev/null
664
+ chmod 600 "$config_file" 2>/dev/null
665
+ debug "serverUrl updated from ornexus.com to $NEOCORTEX_SERVER_URL"
666
+ fi
667
+ fi
668
+
669
+ # Story 61.4 - ensure permissions are always enforced even on existing configs
670
+ chmod 600 "$config_file" 2>/dev/null
671
+ debug "Config existente preservada (mode=$existing_mode)"
672
+ return 0
673
+ fi
674
+ fi
675
+
676
+ # Criar config base para thin client
677
+ cat > "$config_file" << EOFCONFIG
678
+ {
679
+ "configVersion": 1,
680
+ "mode": "pending-activation",
681
+ "serverUrl": "${NEOCORTEX_SERVER_URL}",
682
+ "resilience": {
683
+ "circuitBreaker": true,
684
+ "maxRetries": 3,
685
+ "timeoutMs": 5000
686
+ },
687
+ "installedAt": "$(date -Iseconds 2>/dev/null || date '+%Y-%m-%dT%H:%M:%S')",
688
+ "installerVersion": "${VERSION}"
689
+ }
690
+ EOFCONFIG
691
+
692
+ # Story 61.4 - F4 remediation: config file readable only by owner
693
+ chmod 600 "$config_file" 2>/dev/null
694
+
695
+ debug "Thin client config criada: $config_file"
696
+ }
697
+
698
+ # cleanup_legacy_ip_project() substituida por auto_cleanup_legacy_project()
699
+ # (definida na secao de limpeza automatica acima)
700
+
701
+ # =============================================================================
702
+ # INSTALACAO CORE
703
+ # =============================================================================
704
+
705
+ install_core() {
706
+ local errors=0
707
+
708
+ CLAUDE_DIR="${HOME}/.claude"
709
+ AGENTS_DIR="${CLAUDE_DIR}/agents"
710
+ DEST_DIR="${AGENTS_DIR}/neocortex"
711
+
712
+ debug "HOME=$HOME | DEST=$DEST_DIR | MODE=thin-client"
713
+
714
+ # Create directories
715
+ if [ ! -d "$CLAUDE_DIR" ]; then
716
+ mkdir -p "$CLAUDE_DIR" 2>/dev/null || { fail "Falha ao criar $CLAUDE_DIR"; return 1; }
717
+ fi
718
+ mkdir -p "$AGENTS_DIR" 2>/dev/null || { fail "Falha ao criar $AGENTS_DIR"; return 1; }
719
+
720
+ # Clean previous installation
721
+ if [ -d "$DEST_DIR" ]; then
722
+ rm -rf "$DEST_DIR" 2>/dev/null
723
+ debug "Instalacao anterior removida"
724
+ fi
725
+ mkdir -p "$DEST_DIR" 2>/dev/null || { fail "Falha ao criar $DEST_DIR"; return 1; }
726
+
727
+ # Thin-client ONLY: zero IP on client, all content from server
728
+ setup_thin_client_config
729
+
730
+ # ─── Version-aware cache purge on upgrade (Epic 62 - GAP 2+3) ───────
731
+ local pkg_version=""
732
+ if [ -f "$SOURCE_DIR/package.json" ]; then
733
+ pkg_version=$(grep '"version"' "$SOURCE_DIR/package.json" 2>/dev/null | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
734
+ fi
735
+
736
+ if [ -n "$pkg_version" ]; then
737
+ local old_version=""
738
+ # Read existing .version from either location
739
+ if [ -f "$DEST_DIR/.version" ]; then
740
+ old_version=$(cat "$DEST_DIR/.version" 2>/dev/null | tr -d '[:space:]')
741
+ elif [ -f "$HOME/.neocortex/.version" ]; then
742
+ old_version=$(cat "$HOME/.neocortex/.version" 2>/dev/null | tr -d '[:space:]')
743
+ fi
744
+
745
+ # If version changed, purge all cache files
746
+ if [ -n "$old_version" ] && [ "$old_version" != "$pkg_version" ]; then
747
+ local cache_dir="$HOME/.neocortex/cache"
748
+ if [ -d "$cache_dir" ]; then
749
+ local purged=0
750
+ # Remove all .enc files
751
+ for enc_file in "$cache_dir"/*.enc; do
752
+ [ -f "$enc_file" ] || continue
753
+ rm -f "$enc_file" 2>/dev/null && purged=$((purged + 1))
754
+ done
755
+ # Remove menu-cache.json (redundancy with 62.1)
756
+ [ -f "$cache_dir/menu-cache.json" ] && rm -f "$cache_dir/menu-cache.json" 2>/dev/null && purged=$((purged + 1))
757
+ # Remove any other non-directory files
758
+ for cache_file in "$cache_dir"/*; do
759
+ [ -f "$cache_file" ] || continue
760
+ rm -f "$cache_file" 2>/dev/null && purged=$((purged + 1))
761
+ done
762
+ [ $purged -gt 0 ] && info "Cache purgado: versao alterada de $old_version para $pkg_version ($purged arquivo(s))"
763
+ fi
764
+ fi
765
+
766
+ # Write version file
767
+ echo "$pkg_version" > "$DEST_DIR/.version"
768
+ fi
769
+
770
+ if [ $errors -eq 0 ]; then
771
+ ok "Remote mode configured ${DIM}(thin client ready)${NC}"
772
+ else
773
+ fail "Core instalado com erros"
774
+ fi
775
+
776
+ return $errors
777
+ }
778
+
779
+ # =============================================================================
780
+ # INSTALACAO DE SKILLS
781
+ # =============================================================================
782
+
783
+ install_skills() {
784
+ local errors=0
785
+
786
+ SKILLS_DIR="${CLAUDE_DIR}/skills"
787
+ SKILLS_DEST="${SKILLS_DIR}/neocortex"
788
+ SKILLS_SOURCE="${SOURCE_DIR}/core/skills"
789
+
790
+ # Thin-client: skills delivered by remote server, never copied
791
+ ok "Skills: delivered by remote server"
792
+ return 0
793
+
794
+ mkdir -p "$SKILLS_DIR" 2>/dev/null || { fail "Falha ao criar $SKILLS_DIR"; return 1; }
795
+
796
+ # Clean previous
797
+ [ -d "$SKILLS_DEST" ] && rm -rf "$SKILLS_DEST" 2>/dev/null
798
+
799
+ if cp -r "$SKILLS_SOURCE" "$SKILLS_DEST" 2>/dev/null; then
800
+ # Count skills
801
+ local skill_count=0
802
+ for skill_file in "$SKILLS_DEST"/step-skills/*/*.md "$SKILLS_DEST"/domain-skills/*/*.md; do
803
+ if [ -f "$skill_file" ] && [[ "$(basename "$skill_file")" != "_template.md" ]]; then
804
+ ((skill_count++))
805
+ fi
806
+ done
807
+ ok "Skills instaladas ${DIM}($skill_count skills) [modo local]${NC}"
808
+ else
809
+ fail "Falha ao copiar skills"
810
+ ((errors++))
811
+ fi
812
+
813
+ return $errors
814
+ }
815
+
816
+ # =============================================================================
817
+ # INSTALACAO DE AGENT (Claude Code)
818
+ # =============================================================================
819
+
820
+ install_agent() {
821
+ local errors=0
822
+
823
+ CLAUDE_TARGET_DIR="$SOURCE_DIR/targets/claude-code"
824
+ if [ ! -d "$CLAUDE_TARGET_DIR" ]; then
825
+ CLAUDE_TARGET_DIR="$SOURCE_DIR/targets-stubs/claude-code"
826
+ fi
827
+ if [ ! -d "$CLAUDE_TARGET_DIR" ]; then
828
+ CLAUDE_TARGET_DIR="$SOURCE_DIR"
829
+ fi
830
+
831
+ # Epic 65: Early validation — if neither source file exists, emit clear error
832
+ if [ ! -f "$CLAUDE_TARGET_DIR/neocortex.md" ] && [ ! -f "$CLAUDE_TARGET_DIR/neocortex.agent.yaml" ]; then
833
+ fail "Arquivos fonte nao encontrados em: $CLAUDE_TARGET_DIR"
834
+ fail "Diretorio de origem ($SOURCE_DIR) pode estar incompleto"
835
+ fail "Tente reinstalar: npm install -g @ornexus/neocortex"
836
+ return 2
837
+ fi
838
+
839
+ # Tier 3 Stub-Only: copiar apenas 2 arquivos de interface (stubs minimos)
840
+ copy_file "$CLAUDE_TARGET_DIR/neocortex.md" "$DEST_DIR/" || ((errors++))
841
+ copy_file "$CLAUDE_TARGET_DIR/neocortex.agent.yaml" "$DEST_DIR/" || ((errors++))
842
+
843
+ # Epic 65: Post-copy verification
844
+ if [ ! -f "$DEST_DIR/neocortex.md" ]; then
845
+ fail "neocortex.md nao encontrado no destino apos copia: $DEST_DIR/"
846
+ ((errors++))
847
+ fi
848
+ if [ ! -f "$DEST_DIR/neocortex.agent.yaml" ]; then
849
+ fail "neocortex.agent.yaml nao encontrado no destino apos copia: $DEST_DIR/"
850
+ ((errors++))
851
+ fi
852
+
853
+ # Dynamic description: patch tier from existing config (if activated)
854
+ patch_description_tier "$DEST_DIR/neocortex.md"
855
+ patch_description_tier "$DEST_DIR/neocortex.agent.yaml"
856
+
857
+ # Cleanup: remover workflow.md de instalacoes anteriores (v3.8 -> v3.9)
858
+ if [ -f "$DEST_DIR/workflow.md" ]; then
859
+ rm -f "$DEST_DIR/workflow.md"
860
+ [ "$QUIET_MODE" = false ] && echo " Removed workflow.md (content now server-side)"
861
+ fi
862
+
863
+ # Thin-client ONLY: 2 stub files + server-side orchestration
864
+
865
+ return $errors
866
+ }
867
+
868
+ # =============================================================================
869
+ # INSTALACAO DE TARGETS
870
+ # =============================================================================
871
+
872
+ install_targets() {
873
+ local targets="$1"
874
+ local target_errors=0
875
+ local target_count=0
876
+ local target_results=()
877
+
878
+ targets=$(echo "$targets" | tr ',' ' ')
879
+
880
+ for target in $targets; do
881
+ local adapter_script="$SOURCE_DIR/targets/$target/install-${target}.sh"
882
+ local func_name="install_$(echo "$target" | tr '-' '_')"
883
+
884
+ if [ ! -f "$adapter_script" ]; then
885
+ if [ "$target" = "claude-code" ]; then
886
+ install_agent
887
+ local result=$?
888
+ if [ $result -eq 0 ]; then
889
+ target_results+=("$target:OK")
890
+ ((target_count++))
891
+ ok "${BOLD}claude-code${NC} ${DIM}(thin client)${NC}"
892
+ else
893
+ target_results+=("$target:FAIL")
894
+ ((target_errors++))
895
+ fail "claude-code"
896
+ fi
897
+ continue
898
+ fi
899
+
900
+ warn "$target ${DIM}(adapter nao encontrado)${NC}"
901
+ target_results+=("$target:SKIP")
902
+ ((target_errors++))
903
+ continue
904
+ fi
905
+
906
+ . "$adapter_script"
907
+
908
+ if type "$func_name" >/dev/null 2>&1; then
909
+ "$func_name" "$SOURCE_DIR" "$HOME"
910
+ local result=$?
911
+ if [ $result -eq 0 ]; then
912
+ target_results+=("$target:OK")
913
+ ((target_count++))
914
+ ok "${BOLD}$target${NC}"
915
+ else
916
+ target_results+=("$target:WARN")
917
+ warn "$target ${DIM}(instalado com avisos)${NC}"
918
+ ((target_count++))
919
+ fi
920
+ else
921
+ fail "$target ${DIM}(funcao $func_name nao encontrada)${NC}"
922
+ target_results+=("$target:FAIL")
923
+ ((target_errors++))
924
+ fi
925
+ done
926
+
927
+ TARGET_RESULTS=("${target_results[@]}")
928
+ TARGET_COUNT=$target_count
929
+
930
+ return $target_errors
931
+ }
932
+
933
+ # =============================================================================
934
+ # CARREGAMENTO DE .ENV
935
+ # =============================================================================
936
+
937
+ load_env_file() {
938
+ local env_file=""
939
+ for possible_env in "./.env" "$SOURCE_DIR/.env"; do
940
+ [ -f "$possible_env" ] && { env_file="$possible_env"; break; }
941
+ done
942
+
943
+ if [ -n "$env_file" ] && [ -f "$env_file" ]; then
944
+ local loaded=0
945
+ while IFS='=' read -r key value || [ -n "$key" ]; do
946
+ [[ -z "$key" || "$key" =~ ^[[:space:]]*# ]] && continue
947
+ key=$(echo "$key" | xargs)
948
+ value=$(echo "$value" | xargs)
949
+ value="${value%\"}"; value="${value#\"}"
950
+ value="${value%\'}"; value="${value#\'}"
951
+ if [ -n "$value" ] && [ -z "${!key}" ]; then
952
+ export "$key=$value"
953
+ ((loaded++))
954
+ fi
955
+ done < "$env_file"
956
+ [ $loaded -gt 0 ] && info "$loaded variavel(eis) carregada(s) do .env"
957
+ return 0
958
+ fi
959
+ return 1
960
+ }
961
+
962
+ # =============================================================================
963
+ # CONFIGURACAO DE TOKENS
964
+ # =============================================================================
965
+
966
+ prompt_tokens() {
967
+ [ "$QUIET_MODE" = true ] && return
968
+ [ -n "$CONTEXT7_API_KEY" ] && return
969
+
970
+ if [ "$AUTO_YES" = true ]; then
971
+ return
972
+ fi
973
+
974
+ echo ""
975
+ info "MCP Context7 nao configurado ${DIM}(opcional)${NC}"
976
+ echo -ne " Configurar agora? ${BOLD}[s/N]:${NC} "
977
+
978
+ local response=""
979
+ if read -r -t 15 response 2>/dev/null; then
980
+ : # resposta recebida
981
+ else
982
+ echo ""
983
+ return
984
+ fi
985
+
986
+ if [[ "$response" =~ ^([sS][iI]?[mM]?|[yY][eE]?[sS]?)$ ]]; then
987
+ echo ""
988
+ info "Obtenha sua API Key em: ${WHITE}https://context7.com${NC}"
989
+ echo -ne " Cole sua ${BOLD}CONTEXT7_API_KEY${NC}: "
990
+
991
+ local api_key=""
992
+ if read -r -t 60 api_key 2>/dev/null; then
993
+ if [[ "$api_key" =~ ^ctx7sk- ]]; then
994
+ export CONTEXT7_API_KEY="$api_key"
995
+ ok "CONTEXT7_API_KEY configurada"
996
+ echo ""
997
+ info "Para persistir: ${DIM}echo 'export CONTEXT7_API_KEY=\"$api_key\"' >> ~/.bashrc${NC}"
998
+ elif [ -n "$api_key" ]; then
999
+ warn "Formato invalido (esperado: ctx7sk-...)"
1000
+ fi
1001
+ fi
1002
+ fi
1003
+ }
1004
+
1005
+ # =============================================================================
1006
+ # INSTALACAO DE MCP SERVERS
1007
+ # =============================================================================
1008
+
1009
+ install_mcps() {
1010
+ if ! command -v claude >/dev/null 2>&1; then
1011
+ info "Claude CLI nao encontrado ${DIM}(MCPs serao instalados depois)${NC}"
1012
+ return 0
1013
+ fi
1014
+
1015
+ local mcp_results=""
1016
+
1017
+ if claude mcp list 2>/dev/null | grep -q "^playwright"; then
1018
+ mcp_results="${mcp_results}playwright:OK "
1019
+ else
1020
+ if claude mcp add playwright npx @playwright/mcp@latest 2>/dev/null; then
1021
+ mcp_results="${mcp_results}playwright:OK "
1022
+ else
1023
+ mcp_results="${mcp_results}playwright:FAIL "
1024
+ fi
1025
+ fi
1026
+
1027
+ if claude mcp list 2>/dev/null | grep -q "^context7"; then
1028
+ mcp_results="${mcp_results}context7:OK "
1029
+ elif [ -n "$CONTEXT7_API_KEY" ]; then
1030
+ if claude mcp add --transport http context7 https://mcp.context7.com/mcp --header "CONTEXT7_API_KEY: $CONTEXT7_API_KEY" 2>/dev/null; then
1031
+ mcp_results="${mcp_results}context7:OK "
1032
+ else
1033
+ mcp_results="${mcp_results}context7:FAIL "
1034
+ fi
1035
+ else
1036
+ mcp_results="${mcp_results}context7:SKIP "
1037
+ fi
1038
+
1039
+ # Show compact MCP results
1040
+ local mcp_ok="" mcp_skip=""
1041
+ for entry in $mcp_results; do
1042
+ local name="${entry%%:*}"
1043
+ local status="${entry##*:}"
1044
+ case "$status" in
1045
+ OK) mcp_ok="${mcp_ok}${name}, " ;;
1046
+ SKIP) mcp_skip="${mcp_skip}${name}, " ;;
1047
+ esac
1048
+ done
1049
+
1050
+ [ -n "$mcp_ok" ] && ok "MCPs: ${DIM}${mcp_ok%, }${NC}"
1051
+ [ -n "$mcp_skip" ] && info "MCPs pendentes: ${DIM}${mcp_skip%, }${NC}"
1052
+
1053
+ return 0
1054
+ }
1055
+
1056
+ # =============================================================================
1057
+ # INSTALACAO DO CODERABBIT CLI
1058
+ # =============================================================================
1059
+
1060
+ install_coderabbit() {
1061
+ if command -v coderabbit >/dev/null 2>&1; then
1062
+ ok "CodeRabbit CLI"
1063
+ CODERABBIT_INSTALLED=true
1064
+ return 0
1065
+ fi
1066
+
1067
+ if command -v curl >/dev/null 2>&1 && curl -fsSL https://cli.coderabbit.ai/install.sh | sh 2>/dev/null; then
1068
+ ok "CodeRabbit CLI ${DIM}(instalado)${NC}"
1069
+ CODERABBIT_INSTALLED=true
1070
+ elif command -v npm >/dev/null 2>&1 && npm install -g coderabbit 2>/dev/null; then
1071
+ ok "CodeRabbit CLI ${DIM}(via npm)${NC}"
1072
+ CODERABBIT_INSTALLED=true
1073
+ else
1074
+ info "CodeRabbit CLI ${DIM}(instale depois: curl -fsSL https://cli.coderabbit.ai/install.sh | sh)${NC}"
1075
+ CODERABBIT_INSTALLED=false
1076
+ fi
1077
+
1078
+ return 0
1079
+ }
1080
+
1081
+ # =============================================================================
1082
+ # VERIFICACAO POS-INSTALACAO
1083
+ # =============================================================================
1084
+
1085
+ verify_installation() {
1086
+ local fails=0
1087
+ local warns=0
1088
+ local report=""
1089
+
1090
+ if ! echo "$SELECTED_TARGETS" | grep -q "claude-code"; then
1091
+ return 0
1092
+ fi
1093
+
1094
+ # ─── Layer 1: File existence + minimum size ─────────────────────────
1095
+ local check_fname check_min check_fpath check_size check_ext check_first_line check_display
1096
+ for check_fname in neocortex.md neocortex.agent.yaml; do
1097
+ case "$check_fname" in
1098
+ neocortex.md) check_min=512 ;;
1099
+ neocortex.agent.yaml) check_min=128 ;;
1100
+ esac
1101
+ check_fpath="${DEST_DIR}/${check_fname}"
1102
+
1103
+ if [ ! -f "$check_fpath" ]; then
1104
+ report="${report}FAIL ${check_fname} (nao encontrado)\n"
1105
+ fails=$((fails + 1))
1106
+ else
1107
+ check_size=$(wc -c < "$check_fpath" 2>/dev/null | tr -d ' ')
1108
+ if [ "$check_size" -lt "$check_min" ] 2>/dev/null; then
1109
+ report="${report}FAIL ${check_fname} (${check_size} bytes - possivelmente corrompido, minimo ${check_min})\n"
1110
+ fails=$((fails + 1))
1111
+ else
1112
+ # ─── Layer 2: Content marker (frontmatter) ──────────────
1113
+ check_ext="${check_fname##*.}"
1114
+ if [ "$check_ext" = "md" ]; then
1115
+ check_first_line=$(head -1 "$check_fpath" 2>/dev/null)
1116
+ if [ "$check_first_line" != "---" ]; then
1117
+ report="${report}WARN ${check_fname} (formato invalido - sem frontmatter)\n"
1118
+ warns=$((warns + 1))
1119
+ else
1120
+ if [ "$check_size" -ge 1024 ]; then check_display="$((check_size / 1024))KB"; else check_display="${check_size}B"; fi
1121
+ report="${report}OK ${check_fname} (${check_display})\n"
1122
+ fi
1123
+ else
1124
+ if [ "$check_size" -ge 1024 ]; then check_display="$((check_size / 1024))KB"; else check_display="${check_size}B"; fi
1125
+ report="${report}OK ${check_fname} (${check_display})\n"
1126
+ fi
1127
+ fi
1128
+ fi
1129
+ done
1130
+
1131
+ if false; then
1132
+ # ─── Layer 3: Step directories (removed - thin-client only) ──────
1133
+ for dir in steps-c steps-e steps-p steps-r steps-u; do
1134
+ local dir_path="$DEST_DIR/$dir"
1135
+ if [ ! -d "$dir_path" ]; then
1136
+ report="${report}FAIL ${dir}/ (diretorio nao encontrado)\n"
1137
+ fails=$((fails + 1))
1138
+ else
1139
+ local md_count=0
1140
+ for f in "$dir_path"/*.md; do
1141
+ [ -f "$f" ] && md_count=$((md_count + 1))
1142
+ done
1143
+ if [ $md_count -eq 0 ]; then
1144
+ report="${report}WARN ${dir}/ (vazio - nenhum arquivo .md)\n"
1145
+ warns=$((warns + 1))
1146
+ else
1147
+ report="${report}OK ${dir}/ (${md_count} arquivos)\n"
1148
+ fi
1149
+ fi
1150
+ done
1151
+
1152
+ # ─── Layer 3b: Core directory (local mode only) ──────────────────
1153
+ if [ ! -d "$DEST_DIR/core" ]; then
1154
+ report="${report}FAIL core/ (diretorio nao encontrado)\n"
1155
+ fails=$((fails + 1))
1156
+ else
1157
+ report="${report}OK core/\n"
1158
+ fi
1159
+ else
1160
+ # ─── Layer 3: Thin client config (remote mode) ───────────────────
1161
+ local config_file="${HOME}/.neocortex/config.json"
1162
+ if [ -f "$config_file" ]; then
1163
+ report="${report}OK ~/.neocortex/config.json (thin client configured)\n"
1164
+ else
1165
+ report="${report}WARN ~/.neocortex/config.json (nao encontrado)\n"
1166
+ warns=$((warns + 1))
1167
+ fi
1168
+
1169
+ # Verify NO IP directories exist
1170
+ local ip_found=false
1171
+ for dir in core steps-c steps-e steps-p steps-r steps-u; do
1172
+ if [ -d "$DEST_DIR/$dir" ]; then
1173
+ report="${report}WARN ${dir}/ ainda existe (deveria ter sido removido)\n"
1174
+ warns=$((warns + 1))
1175
+ ip_found=true
1176
+ fi
1177
+ done
1178
+ if [ "$ip_found" = false ]; then
1179
+ report="${report}OK Zero IP no filesystem (modo remoto)\n"
1180
+ fi
1181
+ fi
1182
+
1183
+ # ─── Display report ─────────────────────────────────────────────────
1184
+ if [ $fails -eq 0 ] && [ $warns -eq 0 ]; then
1185
+ # All good - compact output
1186
+ if [ "$QUIET_MODE" != true ]; then
1187
+ ok "Instalacao verificada"
1188
+ fi
1189
+ return 0
1190
+ fi
1191
+
1192
+ # Show detailed report when issues found
1193
+ if [ "$QUIET_MODE" = true ] && [ $fails -eq 0 ]; then
1194
+ return 0 # In quiet mode, skip warnings-only report
1195
+ fi
1196
+
1197
+ echo ""
1198
+ info "Verificacao pos-instalacao:"
1199
+ echo -e "$report" | while IFS= read -r line; do
1200
+ [ -z "$line" ] && continue
1201
+ local status="${line%% *}"
1202
+ local detail="${line#* }"
1203
+ case "$status" in
1204
+ OK) echo -e " ${GREEN}${SYM_OK}${NC} $detail" ;;
1205
+ WARN) echo -e " ${YELLOW}${SYM_WARN}${NC} $detail" ;;
1206
+ FAIL) echo -e " ${RED}${SYM_FAIL}${NC} $detail" ;;
1207
+ esac
1208
+ done
1209
+
1210
+ [ $fails -gt 0 ] && return 1
1211
+ return 0
1212
+ }
1213
+
1214
+ # =============================================================================
1215
+ # RESULTADO
1216
+ # =============================================================================
1217
+
1218
+ show_result() {
1219
+ local install_status=$1
1220
+ [ "$QUIET_MODE" = true ] && return
1221
+
1222
+ echo ""
1223
+ echo -e " ${DIM}────────────────────────────────────────${NC}"
1224
+
1225
+ if [ $install_status -eq 0 ] && verify_installation; then
1226
+ echo ""
1227
+
1228
+ # Success logo (brain/cortex shape, text centered vertically)
1229
+ echo -e "${CYAN} #######${NC}"
1230
+ echo -e "${CYAN} ### ########${NC}"
1231
+ echo -e "${CYAN} ######### #####${NC}"
1232
+ echo -e "${CYAN} ## ############## ${BOLD}N E O C O R T E X${NC}"
1233
+ echo -e "${CYAN} ## ### ###### ## ${BOLD}v${VERSION}${NC}"
1234
+ echo -e "${CYAN} ## ### ### ##${NC}"
1235
+ echo -e "${CYAN} ## ###### ### ## ${GREEN}${BOLD}Installation complete!${NC}"
1236
+ echo -e "${CYAN} ############### ## ${DIM}OrNexus Team${NC}"
1237
+ echo -e "${CYAN} ##### ########${NC}"
1238
+ echo -e "${CYAN} ######## ##${NC}"
1239
+ echo -e "${CYAN} #######${NC}"
1240
+ echo ""
1241
+ echo -e " ${DIM}Mode:${NC} Remote (thin client)"
1242
+ echo -e " ${DIM}Status:${NC} Ready to activate"
1243
+ echo ""
1244
+ echo -e " ${DIM}Activate your license:${NC}"
1245
+ echo -e " ${CYAN}neocortex activate YOUR-LICENSE-KEY${NC}"
1246
+ echo ""
1247
+ echo -e " ${DIM}Get your key at:${NC} ${CYAN}https://neocortex.ornexus.com/login${NC}"
1248
+ echo ""
1249
+ echo -e " ${DIM}After activation:${NC}"
1250
+ echo -e " ${CYAN}@neocortex *menu${NC}"
1251
+ echo ""
1252
+ else
1253
+ echo ""
1254
+ echo -e " ${RED}${BOLD}Instalacao com problemas${NC}"
1255
+ echo ""
1256
+ echo -e " Execute novamente com ${BOLD}--debug${NC} para detalhes:"
1257
+ echo -e " ${YELLOW}npx @ornexus/neocortex --debug${NC}"
1258
+ echo ""
1259
+ return 1
1260
+ fi
1261
+ }
1262
+
1263
+ # =============================================================================
1264
+ # CRIACAO DE DIRETORIOS DO PROJETO
1265
+ # =============================================================================
1266
+
1267
+ create_project_dirs() {
1268
+ [ "$QUIET_MODE" = true ] && return
1269
+ [ "$SKIP_PROJECT_DIRS" = true ] && return
1270
+
1271
+ local should_create=false
1272
+
1273
+ if [ "$CREATE_PROJECT" = true ] || [ "$AUTO_YES" = true ]; then
1274
+ should_create=true
1275
+ else
1276
+ echo -ne " Instalar estrutura no projeto atual? ${BOLD}[s/N]:${NC} "
1277
+
1278
+ local response="n"
1279
+ if read -r -t 30 response </dev/tty 2>/dev/null; then
1280
+ :
1281
+ else
1282
+ echo ""
1283
+ response="n"
1284
+ fi
1285
+
1286
+ [[ "$response" =~ ^([sS][iI]?[mM]?|[yY][eE]?[sS]?)$ ]] && should_create=true
1287
+ fi
1288
+
1289
+ if [ "$should_create" = true ]; then
1290
+ local project_dir="$PWD"
1291
+
1292
+ # Clean previous project files
1293
+ rm -rf "$project_dir/targets/claude-code" 2>/dev/null
1294
+ rm -f "$project_dir/.cursor/agents/neocortex.md" 2>/dev/null
1295
+ rm -rf "$project_dir/.cursor/skills" 2>/dev/null
1296
+ rm -f "$project_dir/.github/agents/neocortex.md" 2>/dev/null
1297
+ rm -rf "$project_dir/.github/skills" 2>/dev/null
1298
+ rm -f "$project_dir/.github/copilot-instructions.md" 2>/dev/null
1299
+ rm -rf "$project_dir/.agent/skills/neocortex" 2>/dev/null
1300
+ rm -f "$project_dir/AGENTS.md" 2>/dev/null
1301
+ rm -rf "$project_dir/.agents/skills" 2>/dev/null
1302
+ rm -f "$project_dir/GEMINI.md" 2>/dev/null
1303
+ rm -rf "$project_dir/.claude/agents/neocortex" 2>/dev/null
1304
+ rm -rf "$project_dir/.claude/skills/neocortex" 2>/dev/null
1305
+
1306
+ # Thin-client: cleanup any legacy core/ from previous local installs
1307
+ rm -rf "$project_dir/core" 2>/dev/null
1308
+
1309
+ # Create base directories
1310
+ mkdir -p "$project_dir/.neocortex/specs" \
1311
+ "$project_dir/.neocortex/planning" \
1312
+ "$project_dir/docs/stories" \
1313
+ "$project_dir/docs/epics" \
1314
+ "$project_dir/docs/proposals" 2>/dev/null
1315
+
1316
+ # Copy state template if needed
1317
+ if [ ! -f "$project_dir/.neocortex/state.json" ]; then
1318
+ [ -f "$SOURCE_DIR/core/data/state-template.json" ] && \
1319
+ cp "$SOURCE_DIR/core/data/state-template.json" "$project_dir/.neocortex/state.json"
1320
+ fi
1321
+
1322
+ # Thin-client: never copy core/ to project
1323
+
1324
+ # Install target-specific files
1325
+ local targets_list
1326
+ targets_list=$(echo "$SELECTED_TARGETS" | tr ',' ' ')
1327
+ local target_summary=""
1328
+
1329
+ for target in $targets_list; do
1330
+ case "$target" in
1331
+ claude-code)
1332
+ local claude_target_dir="$SOURCE_DIR/targets/claude-code"
1333
+ if [ -d "$claude_target_dir" ]; then
1334
+ mkdir -p "$project_dir/.claude/agents/neocortex"
1335
+
1336
+ # Tier 3: copiar apenas 2 stub files
1337
+ cp "$claude_target_dir/neocortex.md" "$project_dir/.claude/agents/neocortex/" 2>/dev/null
1338
+ cp "$claude_target_dir/neocortex.agent.yaml" "$project_dir/.claude/agents/neocortex/" 2>/dev/null
1339
+ # Cleanup workflow.md from previous versions
1340
+ rm -f "$project_dir/.claude/agents/neocortex/workflow.md" 2>/dev/null
1341
+
1342
+ # Dynamic description: patch tier
1343
+ patch_description_tier "$project_dir/.claude/agents/neocortex/neocortex.md"
1344
+ patch_description_tier "$project_dir/.claude/agents/neocortex/neocortex.agent.yaml"
1345
+
1346
+ # Thin-client: cleanup legacy IP from previous installs
1347
+ auto_cleanup_legacy_project "$project_dir"
1348
+ target_summary="${target_summary}claude-code, "
1349
+ fi
1350
+ ;;
1351
+ cursor)
1352
+ # IP Protection: ALWAYS thin-client, zero IP on client
1353
+ local stub_adapter="$SOURCE_DIR/targets-stubs/cursor/install-cursor.sh"
1354
+ if [ -f "$stub_adapter" ]; then
1355
+ . "$stub_adapter"
1356
+ install_cursor "$SOURCE_DIR" "$project_dir"
1357
+ patch_description_tier "$project_dir/.cursor/agents/neocortex.md"
1358
+ target_summary="${target_summary}cursor, "
1359
+ fi
1360
+ ;;
1361
+ vscode)
1362
+ local stub_adapter="$SOURCE_DIR/targets-stubs/vscode/install-vscode.sh"
1363
+ if [ -f "$stub_adapter" ]; then
1364
+ . "$stub_adapter"
1365
+ install_vscode "$SOURCE_DIR" "$project_dir"
1366
+ patch_description_tier "$project_dir/.github/agents/neocortex.md"
1367
+ target_summary="${target_summary}vscode, "
1368
+ fi
1369
+ ;;
1370
+ gemini-cli)
1371
+ local stub_adapter="$SOURCE_DIR/targets-stubs/gemini-cli/install-gemini.sh"
1372
+ if [ -f "$stub_adapter" ]; then
1373
+ . "$stub_adapter"
1374
+ install_gemini "$SOURCE_DIR" "$project_dir"
1375
+ local gemini_home="${GEMINI_HOME:-$HOME/.gemini}"
1376
+ patch_description_tier "$gemini_home/agents/neocortex.md"
1377
+ target_summary="${target_summary}gemini-cli, "
1378
+ fi
1379
+ ;;
1380
+ codex)
1381
+ local stub_adapter="$SOURCE_DIR/targets-stubs/codex/install-codex.sh"
1382
+ if [ -f "$stub_adapter" ]; then
1383
+ . "$stub_adapter"
1384
+ install_codex "$SOURCE_DIR" "$project_dir"
1385
+ patch_description_tier "$project_dir/AGENTS.md"
1386
+ local codex_home="${CODEX_HOME:-$HOME/.codex}"
1387
+ patch_description_tier "$codex_home/AGENTS.md"
1388
+ target_summary="${target_summary}codex, "
1389
+ fi
1390
+ ;;
1391
+ antigravity)
1392
+ local stub_adapter="$SOURCE_DIR/targets-stubs/antigravity/install-antigravity.sh"
1393
+ if [ -f "$stub_adapter" ]; then
1394
+ . "$stub_adapter"
1395
+ install_antigravity "$SOURCE_DIR" "$project_dir"
1396
+ patch_description_tier "$project_dir/.agent/skills/neocortex/SKILL.md"
1397
+ patch_description_tier "$project_dir/GEMINI.md"
1398
+ target_summary="${target_summary}antigravity, "
1399
+ fi
1400
+ ;;
1401
+ esac
1402
+ done
1403
+
1404
+ echo ""
1405
+ ok "Estrutura do projeto instalada ${DIM}(${target_summary%, })${NC}"
1406
+ echo ""
1407
+ info "Proximo passo: ${CYAN}@neocortex *init @docs/epics.md${NC}"
1408
+ fi
1409
+ }
1410
+
1411
+ # =============================================================================
1412
+ # MIGRACAO
1413
+ # =============================================================================
1414
+
1415
+ show_migration_info() {
1416
+ [ "$QUIET_MODE" = true ] && return
1417
+
1418
+ local project_sources
1419
+ if project_sources=$(detect_project_migration_needs); then
1420
+ echo ""
1421
+ warn "${BOLD}Migracao detectada${NC}"
1422
+ for src in $project_sources; do
1423
+ info "Arquivo antigo: $src"
1424
+ done
1425
+ info "Execute: ${CYAN}@neocortex *init @docs/epics.md${NC} para migrar"
1426
+ fi
1427
+ }
1428
+
1429
+ # =============================================================================
1430
+ # TIER-BASED PLATFORM GATING (Story 57.4)
1431
+ # =============================================================================
1432
+
1433
+ # Read tier from config.json (defaults to "free" if not found)
1434
+ get_tier_from_config() {
1435
+ local config_file="$HOME/.neocortex/config.json"
1436
+ if [ -f "$config_file" ]; then
1437
+ local tier
1438
+ tier=$(cat "$config_file" 2>/dev/null | grep -o '"tier"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
1439
+ [ -n "$tier" ] && echo "$tier" || echo "free"
1440
+ else
1441
+ echo "free"
1442
+ fi
1443
+ }
1444
+
1445
+ # Check if a platform is allowed for the current tier
1446
+ # Returns 0 if allowed, 1 if blocked
1447
+ check_platform_tier() {
1448
+ local platform=$1
1449
+ local tier=$2
1450
+ case "$platform" in
1451
+ claude-code) return 0 ;; # Always allowed
1452
+ cursor|gemini-cli|codex)
1453
+ if [ "$tier" = "free" ]; then
1454
+ warn "$platform requer plano Pro. Plataforma disponivel: Claude Code."
1455
+ info "Upgrade: ${CYAN}https://neocortex.dev/pricing${NC}"
1456
+ return 1
1457
+ fi
1458
+ return 0
1459
+ ;;
1460
+ vscode|antigravity)
1461
+ if [ "$tier" != "enterprise" ]; then
1462
+ warn "$platform requer plano Enterprise."
1463
+ info "Upgrade: ${CYAN}https://neocortex.dev/pricing${NC}"
1464
+ return 1
1465
+ fi
1466
+ return 0
1467
+ ;;
1468
+ esac
1469
+ return 0 # Unknown platforms pass through
1470
+ }
1471
+
1472
+ # =============================================================================
1473
+ # MAIN
1474
+ # =============================================================================
1475
+
1476
+ TOTAL_STEPS=4
1477
+
1478
+ main() {
1479
+ show_banner
1480
+ detect_source_dir
1481
+ detect_old_installation
1482
+
1483
+ # Determine targets
1484
+ if [ -z "$SELECTED_TARGETS" ]; then
1485
+ SELECTED_TARGETS="claude-code"
1486
+ fi
1487
+
1488
+ debug "Targets: $SELECTED_TARGETS"
1489
+
1490
+ # Read user tier for platform gating
1491
+ local user_tier
1492
+ user_tier=$(get_tier_from_config)
1493
+ debug "User tier: $user_tier"
1494
+
1495
+ # Validate targets (name check + tier check)
1496
+ local invalid_targets=""
1497
+ local blocked_targets=""
1498
+ for target in $(echo "$SELECTED_TARGETS" | tr ',' ' '); do
1499
+ echo "$VALID_TARGETS" | grep -wq "$target" || invalid_targets="$invalid_targets $target"
1500
+ done
1501
+
1502
+ if [ -n "$invalid_targets" ]; then
1503
+ fail "Plataformas invalidas:$invalid_targets"
1504
+ fail "Validas: $VALID_TARGETS"
1505
+ exit 1
1506
+ fi
1507
+
1508
+ # Filter out tier-blocked platforms
1509
+ local allowed_targets=""
1510
+ for target in $(echo "$SELECTED_TARGETS" | tr ',' ' '); do
1511
+ if check_platform_tier "$target" "$user_tier"; then
1512
+ if [ -z "$allowed_targets" ]; then
1513
+ allowed_targets="$target"
1514
+ else
1515
+ allowed_targets="$allowed_targets,$target"
1516
+ fi
1517
+ else
1518
+ blocked_targets="$blocked_targets $target"
1519
+ fi
1520
+ done
1521
+
1522
+ if [ -n "$blocked_targets" ]; then
1523
+ info "Plataforma(s) bloqueada(s) por tier:$blocked_targets"
1524
+ fi
1525
+
1526
+ # Use only allowed targets
1527
+ if [ -n "$allowed_targets" ]; then
1528
+ SELECTED_TARGETS="$allowed_targets"
1529
+ else
1530
+ SELECTED_TARGETS="claude-code"
1531
+ info "Usando plataforma padrao: claude-code"
1532
+ fi
1533
+
1534
+ # Count targets for step total
1535
+ local target_count=$(echo "$SELECTED_TARGETS" | tr ',' ' ' | wc -w | tr -d ' ')
1536
+ if echo "$SELECTED_TARGETS" | grep -q "claude-code"; then
1537
+ TOTAL_STEPS=6 # cleanup + core + skills + targets + mcps + tools
1538
+ fi
1539
+
1540
+ # Step 1: Limpeza automatica de versoes anteriores
1541
+ step 1 $TOTAL_STEPS "Limpeza de versoes anteriores"
1542
+ auto_cleanup_legacy
1543
+
1544
+ # Step 2: Core
1545
+ step 2 $TOTAL_STEPS "Instalando core"
1546
+ install_core
1547
+ local core_result=$?
1548
+ if [ $core_result -ne 0 ]; then
1549
+ fail "Falha na instalacao do core"
1550
+ exit 1
1551
+ fi
1552
+
1553
+ # Step 3: Skills
1554
+ step 3 $TOTAL_STEPS "Instalando skills"
1555
+ install_skills
1556
+
1557
+ # Step 4: Targets
1558
+ step 4 $TOTAL_STEPS "Instalando ${BOLD}$target_count${NC} plataforma(s)"
1559
+ install_targets "$SELECTED_TARGETS"
1560
+
1561
+ # Step 5-6: Claude Code extras
1562
+ if echo "$SELECTED_TARGETS" | grep -q "claude-code"; then
1563
+ load_env_file
1564
+ prompt_tokens
1565
+
1566
+ step 5 $TOTAL_STEPS "Configurando MCPs"
1567
+ install_mcps
1568
+
1569
+ step 6 $TOTAL_STEPS "Verificando ferramentas"
1570
+ install_coderabbit
1571
+ fi
1572
+
1573
+ # Result
1574
+ show_result $core_result
1575
+ local result_code=$?
1576
+
1577
+ if [ $result_code -eq 0 ]; then
1578
+ show_migration_info
1579
+ create_project_dirs
1580
+ echo -e " ${DIM}Desenvolvido por OrNexus Team${NC}"
1581
+ echo ""
1582
+ fi
1583
+
1584
+ exit $result_code
1585
+ }
1586
+
1587
+ main