diff --git a/main.cpp b/main.cpp index 329baef..2fb18b2 100644 --- a/main.cpp +++ b/main.cpp @@ -1,4 +1,8 @@ #include +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +#endif #include "app_base.h" #include "core/panel_menu.h" #include "core/icons_tabler.h" @@ -164,6 +168,56 @@ static double now_seconds() { return duration_cast>(steady_clock::now().time_since_epoch()).count(); } +// Lanza terminal externa con `claude --dangerously-skip-permissions` en el +// repo fn_registry. En Windows usa Windows Terminal + WSL; en Linux usa el +// primer emulador disponible. Fire-and-forget; no captura output. +static bool spawn_claude_terminal(const fs::path& registry_root) { +#ifdef _WIN32 + // wt.exe lanza Windows Terminal. wsl.exe --cd cambia cwd antes del comando. + // Bash -ic para que /etc/profile cargue PATH (claude esta en ~/.local/bin + // o equivalente). Sin comillas el resto del comando. + std::string cmd = "wt.exe new-tab wsl.exe --cd ~/fn_registry -- bash -ic \"claude --dangerously-skip-permissions\""; + STARTUPINFOA si{}; si.cb = sizeof(si); + PROCESS_INFORMATION pi{}; + std::string mutable_cmd = cmd; // CreateProcessA needs non-const + BOOL ok = CreateProcessA(nullptr, mutable_cmd.data(), nullptr, nullptr, FALSE, + CREATE_NEW_CONSOLE, nullptr, nullptr, &si, &pi); + if (!ok) { + // Fallback: cmd.exe + start wt.exe (less reliable but works without explicit path). + std::string fb = "cmd.exe /C start \"\" wt.exe wsl.exe --cd ~/fn_registry -- bash -ic \"claude --dangerously-skip-permissions\""; + mutable_cmd = fb; + ok = CreateProcessA(nullptr, mutable_cmd.data(), nullptr, nullptr, FALSE, + CREATE_NEW_CONSOLE, nullptr, nullptr, &si, &pi); + } + if (ok) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return true; + } + return false; +#else + // Linux: probar varios emuladores comunes. + const std::string cd = "cd '" + registry_root.string() + "' && exec claude --dangerously-skip-permissions"; + const char* candidates[] = { + "x-terminal-emulator", "gnome-terminal", "konsole", "xterm", "alacritty", "kitty", + }; + for (const char* term : candidates) { + std::string probe = std::string("command -v ") + term + " >/dev/null 2>&1"; + int probe_rc = std::system(probe.c_str()); + if (probe_rc != 0) continue; + std::string cmd; + if (std::string(term) == "gnome-terminal") { + cmd = std::string(term) + " -- bash -ic \"" + cd + "\" &"; + } else { + cmd = std::string(term) + " -e bash -ic \"" + cd + "\" &"; + } + (void)!std::system(cmd.c_str()); + return true; + } + return false; +#endif +} + // ---- Scanner ------------------------------------------------------------ static void scan_dir(const fs::path& dir, NodeKind kind, ScanResult& out) { @@ -614,15 +668,20 @@ static void draw_canvas() { dl->AddText(tp, IM_COL32(255, 255, 255, 245), lbl); } - // Tooltip on hover (title). + // Tooltip on hover (title). Fix width up-front so the first frame + // doesnt flash a giant window before reflow. if (over) { + const float kTipW = 360.0f; + ImGui::SetNextWindowSize(ImVec2(kTipW, 0.0f), ImGuiCond_Always); ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(kTipW - 16.0f); ImGui::Text("%s %s", n.kind == NodeKind::Issue ? "[ISSUE]" : "[FLOW]", n.id.c_str()); ImGui::TextWrapped("%s", n.title.c_str()); ImGui::TextDisabled("status: %s · ring: %s · domain: %s", n.status_eff.c_str(), ring_label(n.ring), n.domain.empty() ? "?" : n.domain.front().c_str()); + ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } }; @@ -716,6 +775,42 @@ static void draw_inspector() { for (const auto& r : n.related) ImGui::BulletText("%s", r.c_str()); } + ImGui::Separator(); + + // === Claude fix: lanza terminal externa con claude === + static double s_last_launch_t = -1e9; + static bool s_last_launch_ok = true; + static std::string s_last_launch_id; + + const Bucket b = status_bucket(n.status_eff); + const bool fixable = (b != Bucket::Done); + if (fixable) { + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(168, 85, 247, 200)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(192, 132, 252, 240)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(147, 51, 234, 255)); + if (ImGui::Button(TI_TERMINAL_2 " Claude fix")) { + s_last_launch_ok = spawn_claude_terminal(g_root); + s_last_launch_t = now_seconds(); + s_last_launch_id = n.id; + fn_log::log_info("skill_tree: spawn claude terminal for %s -> %s", + n.id.c_str(), s_last_launch_ok ? "ok" : "fail"); + } + ImGui::PopStyleColor(3); + ImGui::SameLine(); + ImGui::TextDisabled("abre terminal externa con `claude --dangerously-skip-permissions` en fn_registry"); + } else { + ImGui::TextDisabled("Issue done — no fix necesario."); + } + if (s_last_launch_id == n.id && (now_seconds() - s_last_launch_t) < 5.0) { + if (s_last_launch_ok) { + ImGui::TextColored(ImVec4(0.4f, 0.95f, 0.5f, 1.0f), + TI_CHECK " terminal lanzada (revisa Windows Terminal / WSL)"); + } else { + ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), + TI_ALERT_TRIANGLE " no se pudo lanzar terminal"); + } + } + ImGui::Separator(); ImGui::TextDisabled("Botones [Generate ideas] / [Run autonomous-task] llegan en 0109e/f."); ImGui::End();