Repository: abolinsky/Hook
Branch: main
Commit: 4ca916cdec9d
Files: 16
Total size: 34.0 KB
Directory structure:
gitextract_681_8esy/
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── README.md
├── cmake/
│ └── standalone.cmake
├── resources/
│ └── icons/
│ ├── hook.icns
│ └── svg2icns.sh
├── scripts/
│ ├── build.sh
│ └── create_dmg.sh
├── src/
│ ├── backend.cpp
│ ├── backend.h
│ ├── config.h.in
│ └── main.cpp
└── test/
├── hook.h
├── test.cpp
└── test.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# CMake
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
# C++
## Prerequisites
*.d
## Compiled Object files
*.slo
*.lo
*.o
*.obj
## Precompiled Headers
*.gch
*.pch
## Compiled Dynamic libraries
*.so
*.dylib
*.dll
## Fortran module files
*.mod
*.smod
## Compiled Static libraries
*.lai
*.la
*.a
*.lib
## Executables
*.exe
*.out
*.app
# Xcode
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
# macOS
.DS_Store
# build
build
bin
.dmg
.app
# vim
tags
================================================
FILE: .gitmodules
================================================
[submodule "external/imgui"]
path = external/imgui
url = git@github.com:ocornut/imgui.git
[submodule "external/glfw"]
path = external/glfw
url = git@github.com:glfw/glfw.git
[submodule "external/llvm-project"]
path = external/llvm-project
url = git@github.com:llvm/llvm-project.git
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.18)
set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "" FORCE)
project(Hook
VERSION 0.1.1
LANGUAGES CXX OBJCXX
)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wformat")
if(APPLE)
set(CMAKE_INSTALL_PREFIX "/Applications")
endif()
find_package(Git REQUIRED)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
endif()
endif()
execute_process(COMMAND ${PROJECT_SOURCE_DIR}/external/llvm-project/lldb/scripts/macos-setup-codesign.sh)
include(ExternalProject)
ExternalProject_Add(${PROJECT_NAME}-llvm
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/llvm-project/llvm
BINARY_DIR ${PROJECT_BINARY_DIR}/lldb-build
CONFIGURE_COMMAND ${CMAKE_COMMAND}
-G Ninja
-DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/${PROJECT_NAME}.app/Contents/Frameworks
-C ${CMAKE_CURRENT_SOURCE_DIR}/cmake/standalone.cmake
${CMAKE_CURRENT_SOURCE_DIR}/external/llvm-project/llvm
BUILD_COMMAND ninja
-C ${PROJECT_BINARY_DIR}/lldb-build
lldb
debugserver
# darwin-debug
INSTALL_COMMAND ninja
-C ${PROJECT_BINARY_DIR}/lldb-build
#install-lldb
install-debugserver
install-liblldb
#install-darwin-debug
TEST_COMMAND ""
)
add_subdirectory(external/glfw)
set(CPP_SOURCES
src/main.cpp
external/imgui/imgui.cpp
external/imgui/misc/cpp/imgui_stdlib.cpp
external/imgui/imgui_draw.cpp
external/imgui/imgui_tables.cpp
external/imgui/imgui_widgets.cpp
external/imgui/backends/imgui_impl_glfw.cpp
)
set(OBJC_SOURCES
external/imgui/backends/imgui_impl_metal.mm
src/backend.cpp
)
set(APP_ICON_MACOSX resources/icons/hook.icns)
set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES
MACOSX_PACKAGE_LOCATION "Resources"
)
add_executable(${PROJECT_NAME} MACOSX_BUNDLE
${CPP_SOURCES}
${OBJC_SOURCES}
${APP_ICON_MACOSX}
)
set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE_ICON_FILE hook.icns
)
configure_file(src/config.h.in config.h)
target_include_directories(${PROJECT_NAME}
PUBLIC ${PROJECT_BINARY_DIR}
PUBLIC external/glfw/include
PUBLIC external/imgui
PUBLIC external/imgui/backends
PUBLIC external/imgui/misc/cpp
PUBLIC external/llvm-project/lldb/include
PUBLIC src
)
target_link_directories(${PROJECT_NAME}
PUBLIC ${PROJECT_BINARY_DIR}/lldb-build/lib
)
target_link_libraries(${PROJECT_NAME}
"lldb"
"glfw"
"-framework Metal"
"-framework MetalKit"
"-framework Cocoa"
"-framework IOKit"
"-framework CoreVideo"
"-framework QuartzCore"
"-framework Security"
)
set_source_files_properties(${OBJC_SOURCES} PROPERTIES
LANGUAGE OBJCXX
COMPILE_FLAGS "-x objective-c++"
)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change @rpath/liblldb.17.0.4.dylib @executable_path/../Frameworks/lib/liblldb.dylib $<TARGET_FILE:${PROJECT_NAME}>
)
set(GLFW_INSTALL OFF CACHE BOOL "" FORCE)
install(TARGETS ${PROJECT_NAME}
BUNDLE DESTINATION .
)
================================================
FILE: README.md
================================================
<p align="center">
<img width="256" height="256" src="./resources/icons/hook.svg"/>
</p>
# Hook
A **graphical C/C++ runtime editor** for rapid experimentation. It attaches to your running program and allows you to easily change variables live, breaking the time-consuming edit-compile-run-edit cycle.
<img width="1141" alt="hook_in_action" src="https://github.com/abolinsky/hook/assets/5623716/0f699866-4934-4e79-991b-07e6579bed36">
# build and install
> Warning: This will take a long time. (~20 minutes on an M2 MacBook air)
```
./scripts/build.sh
```
# run
```
open -a Hook
```
================================================
FILE: cmake/standalone.cmake
================================================
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "")
set(LLVM_TARGETS_TO_BUILD AArch64;X86 CACHE STRING "")
set(LLVM_ENABLE_PROJECTS clang;lldb CACHE STRING "")
set(LLDB_INCLUDE_TESTS OFF CACHE BOOL "")
set(LLDB_SKIP_STRIP ON CACHE BOOL "")
set(LLDB_NO_INSTALL_DEFAULT_RPATH OFF CACHE BOOL "")
set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0 CACHE STRING "")
set(LLDB_BUILD_FRAMEWORK OFF CACHE BOOL "")
set(LLVM_ENABLE_ZSTD OFF CACHE BOOL "")
set(LLVM_ENABLE_DOXYGEN OFF CACHE BOOL "")
set(LLDB_ENABLE_SWIG OFF CACHE BOOL "")
set(LLDB_ENABLE_PYTHON OFF CACHE BOOL "")
set(LLDB_ENABLE_CURSES OFF CACHE BOOL "")
set(LLDB_ENABLE_TERMIOS OFF CACHE BOOL "")
set(LLDB_ENABLE_LIBEDIT OFF CACHE BOOL "")
set(LLDB_ENABLE_LIBXML2 OFF CACHE BOOL "")
set(LLDB_ENABLE_LZMA OFF CACHE BOOL "")
set(LLDB_ENABLE_LUA OFF CACHE BOOL "")
set(LLDB_USE_OS_LOG ON CACHE BOOL "")
set(LLVM_DISTRIBUTION_COMPONENTS
#lldb
liblldb
#lldb-argdumper
#darwin-debug
debugserver
CACHE STRING "")
================================================
FILE: resources/icons/svg2icns.sh
================================================
set -e
SIZES="
16,16x16
32,16x16@2x
32,32x32
64,32x32@2x
128,128x128
256,128x128@2x
256,256x256
512,256x256@2x
512,512x512
1024,512x512@2x
"
for SVG in "$@"; do
BASE=$(basename "$SVG" | sed 's/\.[^\.]*$//')
echo $BASE
ICONSET="$BASE.iconset"
mkdir -p "$ICONSET"
for PARAMS in $SIZES; do
SIZE=$(echo $PARAMS | cut -d, -f1)
LABEL=$(echo $PARAMS | cut -d, -f2)
svg2png -w $SIZE -h $SIZE "$SVG" "$ICONSET"/icon_$LABEL.png
done
iconutil -c icns "$ICONSET"
rm -rf "$ICONSET"
done
================================================
FILE: scripts/build.sh
================================================
rm -rf build
cmake -B build -S .
cmake --build build
cmake --install build
================================================
FILE: scripts/create_dmg.sh
================================================
create-dmg \
--volname "Hook Installer" \
--window-pos 200 120 \
--window-size 800 400 \
--icon-size 100 \
--icon "hook.app" 200 190 \
--hide-extension "Hook.app" \
--app-drop-link 600 185 \
"build/Hook.dmg" \
"build/Hook.app"
================================================
FILE: src/backend.cpp
================================================
#include "backend.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_metal.h"
#include "config.h"
#define GLFW_INCLUDE_NONE
#define GLFW_EXPOSE_NATIVE_COCOA
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#import <Metal/Metal.h>
#import <QuartzCore/QuartzCore.h>
#include <functional>
#include <iostream>
void glfw_error_callback(int error, const char* description) {
throw std::runtime_error("Glfw Error " + std::to_string(error) + ": " + description);
}
void main_loop(void (*user_function)()) {
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit())
return;
// Create window with graphics context
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
std::string title = project::name + " " + project::version.to_string();
GLFWwindow* window = glfwCreateWindow(440, 720, title.c_str(), nullptr, nullptr);
if (window == nullptr)
return;
id <MTLDevice> device = MTLCreateSystemDefaultDevice();
id <MTLCommandQueue> commandQueue = [device newCommandQueue];
// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplMetal_Init(device);
NSWindow *nswin = glfwGetCocoaWindow(window);
CAMetalLayer *layer = [CAMetalLayer layer];
layer.device = device;
layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
nswin.contentView.layer = layer;
nswin.contentView.wantsLayer = YES;
MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor new];
float clear_color[4] = {0.45f, 0.55f, 0.60f, 1.00f};
while (!glfwWindowShouldClose(window))
{
@autoreleasepool
{
glfwPollEvents();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
layer.drawableSize = CGSizeMake(width, height);
id<CAMetalDrawable> drawable = [layer nextDrawable];
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0] * clear_color[3], clear_color[1] * clear_color[3], clear_color[2] * clear_color[3], clear_color[3]);
renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
// Start the Dear ImGui frame
ImGui_ImplMetal_NewFrame(renderPassDescriptor);
ImGui_ImplGlfw_NewFrame();
user_function();
ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), commandBuffer, renderEncoder);
[renderEncoder endEncoding];
[commandBuffer presentDrawable:drawable];
[commandBuffer commit];
}
}
// Cleanup
ImGui_ImplMetal_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
}
================================================
FILE: src/backend.h
================================================
#pragma once
void glfw_error_callback(int error, const char* description);
#ifdef __cplusplus
extern "C" {
#endif
void main_loop(void (*user_function)());
#ifdef __cplusplus
}
#endif
================================================
FILE: src/config.h.in
================================================
#pragma once
#include <string>
namespace project {
struct Version {
int major = @PROJECT_VERSION_MAJOR@;
int minor = @PROJECT_VERSION_MINOR@;
int patch = @PROJECT_VERSION_PATCH@;
std::string to_string() const {
return "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@";
}
} version;
std::string name { "@PROJECT_NAME@" };
}
================================================
FILE: src/main.cpp
================================================
#include "backend.h"
#include <mach-o/dyld.h>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <lldb/API/LLDB.h>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <limits>
namespace Hook {
std::string GetExecutablePath() {
char path[PATH_MAX];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) != 0) {
throw std::runtime_error("Could not get executable path");
}
return std::string(path);
}
std::string GetDebugServerPath() {
std::string executablePath = GetExecutablePath();
size_t appDirPos = executablePath.find(".app");
if (appDirPos == std::string::npos) {
throw std::runtime_error("Could not get debugserver path");
}
std::string debugServerPath = executablePath.substr(0, appDirPos) + ".app/Contents/Frameworks/bin/debugserver";
return debugServerPath;
}
struct VariableInfo {
VariableInfo(lldb::SBValue& value) {
this->name = value.GetName();
this->function_name = (name.substr(0,2) == "::") ? "" : value.GetFrame().GetFunctionName();
this->id = value.GetID();
this->type = value.GetType().GetCanonicalType();
while (type.GetTypeClass() == lldb::eTypeClassTypedef) {
this->type.GetTypedefedType();
}
this->type_name = this->type.GetName();
this->type_class = this->type.GetTypeClass();
this->basic_type = this->type.GetBasicType();
if (this->basic_type == lldb::eBasicTypeUnsignedChar) {
value.SetFormat(lldb::Format::eFormatDecimal);
}
this->is_nested = this->type.IsAggregateType();
if (!this->is_nested) {
this->value = value.GetValue();
}
}
VariableInfo() = default;
bool IsAggregateType() const {
return is_nested;
}
std::string GetFullyQualifiedValue() const {
if (type_class == lldb::eTypeClassEnumeration) {
return type_name + "::" + value;
} else {
return value;
}
}
const VariableInfo& GetRoot() const {
const VariableInfo* root = this;
while (root->parent) {
root = root->parent;
}
return *root;
}
bool IsRoot() const {
return !parent;
}
bool ParentIsContainer() const {
return name.back() == ']';
}
std::string GetFullyQualifiedName() const {
std::string full_name;
const VariableInfo* current = this;
while (current != nullptr) {
full_name = current->name + full_name;
if (!current->IsRoot() && !current->ParentIsContainer()) {
full_name = "." + full_name;
}
current = current->parent;
}
return full_name;
}
std::string name;
std::string function_name;
std::string value;
lldb::SBType type;
std::string type_name;
lldb::TypeClass type_class;
lldb::BasicType basic_type;
bool is_nested = false;
uint64_t id = std::numeric_limits<uint64_t>::max();
std::vector<VariableInfo*> children;
VariableInfo* parent = nullptr;
};
std::vector<VariableInfo> variables;
const VariableInfo* current_var_info;
bool published_changes = true;
bool open_pid_popup = true;
lldb::SBDebugger debugger;
lldb::SBTarget target;
lldb::SBListener listener;
lldb::SBError error;
lldb::SBProcess process;
lldb::pid_t pid = 0;
void HelpMarker(const char* desc) {
ImGui::TextDisabled("(?)");
if (ImGui::BeginItemTooltip()) {
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
ImGui::TextUnformatted(desc);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
}
void PublishChange(const VariableInfo& varInfo) {
current_var_info = &varInfo;
published_changes = false;
process.Stop();
}
lldb::SBValue FindVariableById(lldb::SBFrame& frame, uint64_t id) {
lldb::SBValueList vars = frame.GetVariables(true, true, true, true);
for (int i = 0; i < vars.GetSize(); ++i) {
lldb::SBValue v = vars.GetValueAtIndex(i);
if (v.GetID() == id) {
return v;
}
}
return lldb::SBValue();
}
std::vector<lldb::SBValue> GetVariablesFromFrame(lldb::SBFrame& frame) {
std::vector<lldb::SBValue> variables;
lldb::SBValueList frameVariables = frame.GetVariables(true, true, true, true); // arguments, locals, statics, in_scope_only
for (int i = 0; i < frameVariables.GetSize(); i++) {
lldb::SBValue var = frameVariables.GetValueAtIndex(i);
if (var) {
variables.push_back(var);
}
}
return variables;
}
lldb::SBThread GetThread(lldb::SBProcess& process) {
lldb::SBThread thread = process.GetSelectedThread();
if (!thread) {
std::cerr << "Failed to get thread" << std::endl;
}
return thread;
}
std::vector<lldb::SBFrame> GetFrames(lldb::SBThread& thread) {
std::vector<lldb::SBFrame> frames;
const auto num_frames = thread.GetNumFrames();
frames.reserve(num_frames);
for (auto i = 0; i < num_frames; ++i) {
lldb::SBFrame frame = thread.GetFrameAtIndex(i);
if (frame) {
frames.push_back(frame);
}
}
return frames;
}
void DisplayVariable(VariableInfo& varInfo) {
std::string prefix = !varInfo.IsRoot() ? "" : varInfo.function_name.empty() ? "" : "(" + varInfo.function_name + ") ";
ImGui::Text("%s%s =", prefix.c_str(), varInfo.name.c_str());
ImGui::SameLine();
if (varInfo.IsAggregateType()) {
std::string treeNodeLabel = "##varname" + std::to_string(varInfo.id);
if (ImGui::TreeNode(treeNodeLabel.c_str())) {
for (auto childVar : varInfo.children) {
DisplayVariable(*childVar);
}
ImGui::TreePop();
}
} else {
std::string inputTextLabel = "##varname" + varInfo.function_name + varInfo.name;
if (varInfo.type_class == lldb::eTypeClassEnumeration) {
auto members = varInfo.type.GetEnumMembers();
auto num_members = members.GetSize();
std::vector<std::string> member_names;
for (unsigned i = 0; i < num_members; ++i) {
auto current_enum_member = members.GetTypeEnumMemberAtIndex(i);
if (current_enum_member.IsValid() && std::string(current_enum_member.GetName()) != "") {
member_names.push_back(current_enum_member.GetName());
}
}
int index = 0;
for (auto& n : member_names) {
if (varInfo.value == n) {
std::string inputTextLabel = "##sliderEnum" + varInfo.name;
ImGui::SliderInt(inputTextLabel.c_str(), &index, 0, member_names.size() - 1, member_names[index].c_str());
varInfo.value = member_names[index];
break;
}
++index;
}
if (ImGui::IsItemDeactivatedAfterEdit()) {
PublishChange(varInfo);
}
} else if (varInfo.basic_type == lldb::eBasicTypeBool) {
bool value = varInfo.value == "true";
ImGui::Checkbox(inputTextLabel.c_str(), &value);
varInfo.value = value ? "true" : "false";
if (ImGui::IsItemDeactivatedAfterEdit()) {
PublishChange(varInfo);
}
} else if (varInfo.basic_type == lldb::eBasicTypeUnsignedChar) {
uint8_t value = std::stoul(varInfo.value);
static auto min = std::numeric_limits<uint8_t>::min();
static auto max = std::numeric_limits<uint8_t>::max();
ImGui::SliderScalar(inputTextLabel.c_str(), ImGuiDataType_U8, &value, &min , &max);
if (ImGui::IsItemDeactivatedAfterEdit()) {
PublishChange(varInfo);
}
ImGui::SameLine(); HelpMarker("CTRL+click to input value");
varInfo.value = std::to_string(value);
} else if (varInfo.basic_type == lldb::eBasicTypeInt) {
int value = std::stoi(varInfo.value);
static auto min = std::numeric_limits<int>::min() / 2;
static auto max = std::numeric_limits<int>::max() / 2;
ImGui::SliderScalar(inputTextLabel.c_str(), ImGuiDataType_S32, &value, &min , &max);
if (ImGui::IsItemDeactivatedAfterEdit()) {
PublishChange(varInfo);
}
ImGui::SameLine(); HelpMarker("CTRL+click to input value");
varInfo.value = std::to_string(value);
} else {
ImGui::InputText(inputTextLabel.c_str(), &varInfo.value, ImGuiInputTextFlags_CharsDecimal);
if (ImGui::IsItemDeactivatedAfterEdit()) {
PublishChange(varInfo);
}
}
}
}
void FetchNestedMembers(lldb::SBValue& aggregateValue, VariableInfo& parent) {
for (int i = 0; i < aggregateValue.GetNumChildren(); ++i) {
lldb::SBValue childValue = aggregateValue.GetChildAtIndex(i);
if (!childValue.IsValid()) continue;
variables.emplace_back(childValue);
variables.back().parent = &parent;
parent.children.push_back(&variables.back());
if (parent.children.back()->IsAggregateType()) {
FetchNestedMembers(childValue, *parent.children.back());
}
}
}
std::vector<lldb::SBValue> GetVariablesFromThread(lldb::SBThread& thread) {
std::vector<lldb::SBValue> variables;
for (auto& frame : GetFrames(thread)) {
auto frame_vars = GetVariablesFromFrame(frame);
variables.insert(variables.end(), frame_vars.begin(), frame_vars.end());
}
return variables;
}
void FetchAllVariables() {
auto thread = GetThread(process);
if (!thread) return;
auto thread_variables = GetVariablesFromThread(thread);
variables.clear();
variables.reserve(thread_variables.size() * 100);
for (auto& var : thread_variables) {
const VariableInfo varInfo(var);
if (std::none_of(variables.begin(), variables.end(), [&varInfo](const VariableInfo& v) {
return v.function_name == varInfo.function_name && v.name == varInfo.name;
})) {
variables.push_back(varInfo);
if (varInfo.IsAggregateType()) {
FetchNestedMembers(var, variables.back());
}
}
}
}
void AttachToProcess(lldb::SBAttachInfo& attachInfo) {
target = debugger.CreateTarget("");
process = target.Attach(attachInfo, error);
if (!process.IsValid() || error.Fail()) {
throw std::runtime_error(std::string("Failed to attach to process: ") + error.GetCString());
}
}
void AttachToProcessWithID(lldb::pid_t pid) {
lldb::SBAttachInfo attachInfo;
attachInfo.SetProcessID(pid);
AttachToProcess(attachInfo);
}
void SetupEventListener() {
listener = debugger.GetListener();
auto event_mask = lldb::SBProcess::eBroadcastBitStateChanged;
auto event_bits = process.GetBroadcaster().AddListener(listener, event_mask);
if (event_bits != event_mask) {
throw std::runtime_error("Could not set up event listener");
}
}
void HandleAttachProcess() {
AttachToProcessWithID(pid);
SetupEventListener();
FetchAllVariables();
process.Continue();
}
void StyleColorsFunky() {
auto yellow = ImVec4{0.996, 0.780, 0.008, 1.0};
auto blue = ImVec4{0.090, 0.729, 0.808, 1.0};
auto green = ImVec4{0.149, 0.918, 0.694, 1.0};
auto white = ImVec4{0.996, 0.996, 0.996, 1.0};
auto middle_gray = ImVec4{0.5f, 0.5f, 0.5f, 1.0};
auto light_gray = ImVec4{0.85f, 0.85f, 0.85f, 1.0};
auto off_white = ImVec4{0.96f, 0.96f, 0.96f, 1.0};
auto black = ImVec4{0.0, 0.0, 0.0, 1.0};
auto &colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_WindowBg] = green;
colors[ImGuiCol_MenuBarBg] = blue;
// Border
colors[ImGuiCol_Border] = white;
colors[ImGuiCol_BorderShadow] = ImVec4{0.0f, 0.0f, 0.0f, 0.24f};
// Text
colors[ImGuiCol_Text] = black;
colors[ImGuiCol_TextDisabled] = middle_gray;
// Headers
colors[ImGuiCol_Header] = yellow;
colors[ImGuiCol_HeaderHovered] = light_gray;
colors[ImGuiCol_HeaderActive] = white;
// Buttons
colors[ImGuiCol_Button] = blue;
colors[ImGuiCol_ButtonHovered] = white;
colors[ImGuiCol_ButtonActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_CheckMark] = blue;
// Popups
colors[ImGuiCol_PopupBg] = white;
// Slider
colors[ImGuiCol_SliderGrab] = yellow;
colors[ImGuiCol_SliderGrabActive] = white;
// Frame BG
colors[ImGuiCol_FrameBg] = off_white;
colors[ImGuiCol_FrameBgHovered] = light_gray;
colors[ImGuiCol_FrameBgActive] = yellow;
// Tabs
colors[ImGuiCol_Tab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_TabHovered] = ImVec4{0.24, 0.24f, 0.32f, 1.0f};
colors[ImGuiCol_TabActive] = ImVec4{0.2f, 0.22f, 0.27f, 1.0f};
colors[ImGuiCol_TabUnfocused] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_TabUnfocusedActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
// Title
colors[ImGuiCol_TitleBg] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_TitleBgActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_TitleBgCollapsed] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
// Scrollbar
colors[ImGuiCol_ScrollbarBg] = ImVec4{0.1f, 0.1f, 0.13f, 1.0f};
colors[ImGuiCol_ScrollbarGrab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4{0.24f, 0.24f, 0.32f, 1.0f};
// Seperator
colors[ImGuiCol_Separator] = ImVec4{0.44f, 0.37f, 0.61f, 1.0f};
colors[ImGuiCol_SeparatorHovered] = ImVec4{0.74f, 0.58f, 0.98f, 1.0f};
colors[ImGuiCol_SeparatorActive] = ImVec4{0.84f, 0.58f, 1.0f, 1.0f};
// Resize Grip
colors[ImGuiCol_ResizeGrip] = ImVec4{0.44f, 0.37f, 0.61f, 0.29f};
colors[ImGuiCol_ResizeGripHovered] = ImVec4{0.74f, 0.58f, 0.98f, 0.29f};
colors[ImGuiCol_ResizeGripActive] = ImVec4{0.84f, 0.58f, 1.0f, 0.29f};
auto &style = ImGui::GetStyle();
style.TabRounding = 4;
style.ScrollbarRounding = 9;
style.WindowBorderSize = 0;
style.GrabRounding = 3;
style.FrameRounding = 3;
style.PopupRounding = 4;
style.ChildRounding = 4;
}
void StyleColorsBlack() {
auto &colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_WindowBg] = ImVec4{0.1f, 0.1f, 0.13f, 1.0};
colors[ImGuiCol_MenuBarBg] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
// Border
colors[ImGuiCol_Border] = ImVec4{0.44f, 0.37f, 0.61f, 0.29f};
colors[ImGuiCol_BorderShadow] = ImVec4{0.0f, 0.0f, 0.0f, 0.24f};
// Text
colors[ImGuiCol_Text] = ImVec4{1.0f, 1.0f, 1.0f, 1.0f};
colors[ImGuiCol_TextDisabled] = ImVec4{0.5f, 0.5f, 0.5f, 1.0f};
// Headers
colors[ImGuiCol_Header] = ImVec4{0.13f, 0.13f, 0.17, 1.0f};
colors[ImGuiCol_HeaderHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};
colors[ImGuiCol_HeaderActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
// Buttons
colors[ImGuiCol_Button] = ImVec4{0.13f, 0.13f, 0.17, 1.0f};
colors[ImGuiCol_ButtonHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};
colors[ImGuiCol_ButtonActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_CheckMark] = ImVec4{0.74f, 0.58f, 0.98f, 1.0f};
// Popups
colors[ImGuiCol_PopupBg] = ImVec4{0.1f, 0.1f, 0.13f, 0.92f};
// Slider
colors[ImGuiCol_SliderGrab] = ImVec4{0.44f, 0.37f, 0.61f, 0.54f};
colors[ImGuiCol_SliderGrabActive] = ImVec4{0.74f, 0.58f, 0.98f, 0.54f};
// Frame BG
colors[ImGuiCol_FrameBg] = ImVec4{0.13f, 0.13, 0.17, 1.0f};
colors[ImGuiCol_FrameBgHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};
colors[ImGuiCol_FrameBgActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
// Tabs
colors[ImGuiCol_Tab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_TabHovered] = ImVec4{0.24, 0.24f, 0.32f, 1.0f};
colors[ImGuiCol_TabActive] = ImVec4{0.2f, 0.22f, 0.27f, 1.0f};
colors[ImGuiCol_TabUnfocused] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_TabUnfocusedActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
// Title
colors[ImGuiCol_TitleBg] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_TitleBgActive] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_TitleBgCollapsed] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
// Scrollbar
colors[ImGuiCol_ScrollbarBg] = ImVec4{0.1f, 0.1f, 0.13f, 1.0f};
colors[ImGuiCol_ScrollbarGrab] = ImVec4{0.16f, 0.16f, 0.21f, 1.0f};
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4{0.19f, 0.2f, 0.25f, 1.0f};
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4{0.24f, 0.24f, 0.32f, 1.0f};
// Seperator
colors[ImGuiCol_Separator] = ImVec4{0.44f, 0.37f, 0.61f, 1.0f};
colors[ImGuiCol_SeparatorHovered] = ImVec4{0.74f, 0.58f, 0.98f, 1.0f};
colors[ImGuiCol_SeparatorActive] = ImVec4{0.84f, 0.58f, 1.0f, 1.0f};
// Resize Grip
colors[ImGuiCol_ResizeGrip] = ImVec4{0.44f, 0.37f, 0.61f, 0.29f};
colors[ImGuiCol_ResizeGripHovered] = ImVec4{0.74f, 0.58f, 0.98f, 0.29f};
colors[ImGuiCol_ResizeGripActive] = ImVec4{0.84f, 0.58f, 1.0f, 0.29f};
auto &style = ImGui::GetStyle();
style.TabRounding = 4;
style.ScrollbarRounding = 9;
style.WindowBorderSize = 0;
style.GrabRounding = 3;
style.FrameRounding = 3;
style.PopupRounding = 4;
style.ChildRounding = 4;
}
void Draw() {
ImGui::NewFrame();
ImGuiIO& io = ImGui::GetIO();
ImGui::SetNextWindowSize(io.DisplaySize);
ImGui::SetNextWindowPos(ImVec2(0, 0));
if (!ImGui::Begin("Variables", nullptr, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
ImGui::End();
return;
}
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Attach with PID", "Ctrl+A")) {
open_pid_popup = true;
}
if (ImGui::BeginMenu("Theme")) {
if (ImGui::MenuItem("Dark", "Ctrl+D")) {
ImGui::StyleColorsDark();
}
if (ImGui::MenuItem("Light", "Ctrl+L")) {
ImGui::StyleColorsLight();
}
if (ImGui::MenuItem("Classic", "Ctrl+C")) {
ImGui::StyleColorsClassic();
}
if (ImGui::MenuItem("Black", "Ctrl+B")) {
StyleColorsBlack();
}
if (ImGui::MenuItem("Funky", "Ctrl+F")) {
StyleColorsFunky();
}
ImGui::EndMenu();
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
if (ImGui::BeginPopup("AttachWithPID", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove)) {
static bool attach_failed = false;
std::string pidInput;
pidInput.reserve(64);
ImGui::Text("PID:");
ImGui::SameLine();
ImGui::SetKeyboardFocusHere();
if (ImGui::InputText("##pid", &pidInput, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CharsDecimal)) {
pid = std::stoull(pidInput);
try {
HandleAttachProcess();
ImGui::CloseCurrentPopup();
attach_failed = false;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
attach_failed = true;
}
}
// TODO: progress bar
if (attach_failed) {
ImGui::BeginDisabled();
ImGui::TextColored(ImVec4{1.000, 0.353, 0.322, 1.0}, "Error: Could not attach to pid %llu", pid);
ImGui::EndDisabled();
}
ImGui::EndPopup();
}
if (open_pid_popup) {
open_pid_popup = false;
ImGui::OpenPopup("AttachWithPID");
}
for (auto& var : variables) {
if (var.IsRoot()) {
DisplayVariable(var);
}
}
ImGui::End();
ImGui::Render();
}
void UpdateVariableValue(const VariableInfo* var_info) {
auto thread = GetThread(process);
if (!thread) return;
std::string fully_qualified_name = var_info->GetFullyQualifiedName();
std::string expression = fully_qualified_name + " = " + var_info->GetFullyQualifiedValue();
for (auto& frame : GetFrames(thread)) {
lldb::SBValue var = FindVariableById(frame, var_info->GetRoot().id);
if (!var) continue;
lldb::SBValue value = frame.EvaluateExpression(expression.c_str());
if (value && value.GetValue()) return;
}
std::cerr << "Failed to evaluate " << expression << std::endl;
}
void HandleLLDBProcessEvents() {
lldb::SBEvent event;
while (listener.PeekAtNextEvent(event)) {
if (lldb::SBProcess::EventIsProcessEvent(event)) {
lldb::StateType state = lldb::SBProcess::GetStateFromEvent(event);
if (state == lldb::eStateStopped) {
if (!published_changes) {
UpdateVariableValue(current_var_info);
published_changes = true;
}
FetchAllVariables();
process.Continue();
}
}
listener.GetNextEvent(event);
}
}
void HandleKeys() {
if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) {
if (ImGui::IsKeyDown(ImGuiKey_A)) {
open_pid_popup = true;
} else if (ImGui::IsKeyDown(ImGuiKey_D)) {
ImGui::StyleColorsDark(nullptr);
} else if (ImGui::IsKeyDown(ImGuiKey_C)) {
ImGui::StyleColorsClassic(nullptr);
} else if (ImGui::IsKeyDown(ImGuiKey_L)) {
ImGui::StyleColorsLight(nullptr);
} else if (ImGui::IsKeyDown(ImGuiKey_B)) {
StyleColorsBlack();
} else if (ImGui::IsKeyDown(ImGuiKey_F)) {
StyleColorsFunky();
}
} else if (ImGui::IsKeyDown(ImGuiKey_S)) {
process.Stop();
}
}
void core() {
HandleKeys();
HandleLLDBProcessEvents();
Draw();
}
void SetupLoop() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
StyleColorsBlack();
}
void TearDownDebugger() {
lldb::SBDebugger::Destroy(debugger);
}
void SetupDebugger() {
setenv("LLDB_DEBUGSERVER_PATH", GetDebugServerPath().c_str(), 1);
lldb::SBDebugger::Initialize();
debugger = lldb::SBDebugger::Create();
}
}
int main() {
using namespace Hook;
try {
SetupDebugger();
SetupLoop();
main_loop(core);
TearDownDebugger();
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
}
}
================================================
FILE: test/hook.h
================================================
#ifdef HOOK
#define const volatile
#endif
================================================
FILE: test/test.cpp
================================================
#include <iostream>
#include <chrono>
#include <thread>
#include "hook.h"
using namespace std::chrono_literals;
float global_float = 3.14f;
struct Foo {
int a = 0;
int b = 0;
};
std::ostream& operator<<(std::ostream& s, const Foo& foo) {
s << "foo: (a: " << foo.a << ", b: " << foo.b << ")";
return s;
}
class Bar {
public:
int c = 0;
private:
Foo foo;
friend std::ostream& operator<<(std::ostream& os, const Bar& obj);
};
std::ostream& operator<<(std::ostream& s, const Bar& bar) {
s << "bar: (" << bar.c << ", " << bar.foo << ")";
return s;
}
void infinite_sleep() {
bool stop = false;
while (!stop) {
std::this_thread::sleep_for(20ms);
}
}
int main() {
Foo foo;
Bar bar;
bool stop = false;
for (const int i = 0; !stop;) {
infinite_sleep();
std::cout << "i: " << i << std::endl;
std::cout << foo << std::endl;
std::cout << bar << std::endl;
std::cout << "global_float: " << global_float << std::endl;
}
std::cout << "exited!" << std::endl;
}
================================================
FILE: test/test.sh
================================================
g++ -std=c++20 -g -O0 -DHOOK -o test test.cpp
./test &
gitextract_681_8esy/
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── README.md
├── cmake/
│ └── standalone.cmake
├── resources/
│ └── icons/
│ ├── hook.icns
│ └── svg2icns.sh
├── scripts/
│ ├── build.sh
│ └── create_dmg.sh
├── src/
│ ├── backend.cpp
│ ├── backend.h
│ ├── config.h.in
│ └── main.cpp
└── test/
├── hook.h
├── test.cpp
└── test.sh
SYMBOL INDEX (43 symbols across 3 files)
FILE: src/backend.cpp
function glfw_error_callback (line 18) | void glfw_error_callback(int error, const char* description) {
function main_loop (line 22) | void main_loop(void (*user_function)()) {
FILE: src/main.cpp
type Hook (line 17) | namespace Hook {
function GetExecutablePath (line 19) | std::string GetExecutablePath() {
function GetDebugServerPath (line 29) | std::string GetDebugServerPath() {
type VariableInfo (line 40) | struct VariableInfo {
method VariableInfo (line 41) | VariableInfo(lldb::SBValue& value) {
method VariableInfo (line 63) | VariableInfo() = default;
method IsAggregateType (line 65) | bool IsAggregateType() const {
method GetFullyQualifiedValue (line 69) | std::string GetFullyQualifiedValue() const {
method VariableInfo (line 77) | const VariableInfo& GetRoot() const {
method IsRoot (line 85) | bool IsRoot() const {
method ParentIsContainer (line 89) | bool ParentIsContainer() const {
method GetFullyQualifiedName (line 93) | std::string GetFullyQualifiedName() const {
function HelpMarker (line 132) | void HelpMarker(const char* desc) {
function PublishChange (line 142) | void PublishChange(const VariableInfo& varInfo) {
function FindVariableById (line 148) | lldb::SBValue FindVariableById(lldb::SBFrame& frame, uint64_t id) {
function GetVariablesFromFrame (line 159) | std::vector<lldb::SBValue> GetVariablesFromFrame(lldb::SBFrame& frame) {
function GetThread (line 172) | lldb::SBThread GetThread(lldb::SBProcess& process) {
function GetFrames (line 180) | std::vector<lldb::SBFrame> GetFrames(lldb::SBThread& thread) {
function DisplayVariable (line 194) | void DisplayVariable(VariableInfo& varInfo) {
function FetchNestedMembers (line 268) | void FetchNestedMembers(lldb::SBValue& aggregateValue, VariableInfo& p...
function GetVariablesFromThread (line 283) | std::vector<lldb::SBValue> GetVariablesFromThread(lldb::SBThread& thre...
function FetchAllVariables (line 292) | void FetchAllVariables() {
function AttachToProcess (line 314) | void AttachToProcess(lldb::SBAttachInfo& attachInfo) {
function AttachToProcessWithID (line 322) | void AttachToProcessWithID(lldb::pid_t pid) {
function SetupEventListener (line 328) | void SetupEventListener() {
function HandleAttachProcess (line 337) | void HandleAttachProcess() {
function StyleColorsFunky (line 344) | void StyleColorsFunky() {
function StyleColorsBlack (line 427) | void StyleColorsBlack() {
function Draw (line 501) | void Draw() {
function UpdateVariableValue (line 588) | void UpdateVariableValue(const VariableInfo* var_info) {
function HandleLLDBProcessEvents (line 606) | void HandleLLDBProcessEvents() {
function HandleKeys (line 625) | void HandleKeys() {
function core (line 645) | void core() {
function SetupLoop (line 651) | void SetupLoop() {
function TearDownDebugger (line 657) | void TearDownDebugger() {
function SetupDebugger (line 661) | void SetupDebugger() {
function main (line 669) | int main() {
FILE: test/test.cpp
type Foo (line 10) | struct Foo {
class Bar (line 19) | class Bar {
function infinite_sleep (line 32) | void infinite_sleep() {
function main (line 39) | int main() {
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (37K chars).
[
{
"path": ".gitignore",
"chars": 568,
"preview": "# CMake\nCMakeLists.txt.user\nCMakeCache.txt\nCMakeFiles\nCMakeScripts\nTesting\nMakefile\ncmake_install.cmake\ninstall_manifest"
},
{
"path": ".gitmodules",
"chars": 288,
"preview": "[submodule \"external/imgui\"]\n\tpath = external/imgui\n\turl = git@github.com:ocornut/imgui.git\n[submodule \"external/glfw\"]\n"
},
{
"path": "CMakeLists.txt",
"chars": 3701,
"preview": "cmake_minimum_required(VERSION 3.18)\n\nset(CMAKE_OSX_ARCHITECTURES \"x86_64;arm64\" CACHE STRING \"\" FORCE)\n\nproject(Hook\n "
},
{
"path": "README.md",
"chars": 587,
"preview": "<p align=\"center\">\n <img width=\"256\" height=\"256\" src=\"./resources/icons/hook.svg\"/>\n</p>\n\n# Hook\nA **graphical C/C++ r"
},
{
"path": "cmake/standalone.cmake",
"chars": 1021,
"preview": "set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING \"\")\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL \"\")\n\nset(LLVM_TARGE"
},
{
"path": "resources/icons/svg2icns.sh",
"chars": 533,
"preview": "set -e\n\nSIZES=\"\n16,16x16\n32,16x16@2x\n32,32x32\n64,32x32@2x\n128,128x128\n256,128x128@2x\n256,256x256\n512,256x256@2x\n512,512x"
},
{
"path": "scripts/build.sh",
"chars": 75,
"preview": "rm -rf build\ncmake -B build -S .\ncmake --build build\ncmake --install build\n"
},
{
"path": "scripts/create_dmg.sh",
"chars": 245,
"preview": "create-dmg \\\n --volname \"Hook Installer\" \\\n --window-pos 200 120 \\\n --window-size 800 400 \\\n --icon-size 100 \\\n --i"
},
{
"path": "src/backend.cpp",
"chars": 3134,
"preview": "#include \"backend.h\"\n#include \"imgui_impl_glfw.h\"\n#include \"imgui_impl_metal.h\"\n\n#include \"config.h\"\n\n#define GLFW_INCLU"
},
{
"path": "src/backend.h",
"chars": 186,
"preview": "#pragma once\n\nvoid glfw_error_callback(int error, const char* description);\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoi"
},
{
"path": "src/config.h.in",
"chars": 380,
"preview": "#pragma once\n\n#include <string>\n\nnamespace project {\n\nstruct Version {\n int major = @PROJECT_VERSION_MAJOR@;\n int "
},
{
"path": "src/main.cpp",
"chars": 22907,
"preview": "#include \"backend.h\"\n\n#include <mach-o/dyld.h>\n\n#include <imgui.h>\n#include <imgui_stdlib.h>\n\n#include <lldb/API/LLDB.h>"
},
{
"path": "test/hook.h",
"chars": 41,
"preview": "#ifdef HOOK\n#define const volatile\n#endif"
},
{
"path": "test/test.cpp",
"chars": 1071,
"preview": "#include <iostream>\n#include <chrono>\n#include <thread>\n#include \"hook.h\"\n\nusing namespace std::chrono_literals;\n\nfloat "
},
{
"path": "test/test.sh",
"chars": 54,
"preview": "g++ -std=c++20 -g -O0 -DHOOK -o test test.cpp\n./test &"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the abolinsky/Hook GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (34.0 KB), approximately 10.6k tokens, and a symbol index with 43 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.