[eric] production push 1.0.23

This commit is contained in:
ciregenz
2026-04-17 02:58:12 -07:00
parent bb57a2aa51
commit b469648e05
21 changed files with 787 additions and 29 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ from backend.apps.analytics.collector import init as init_collector, shutdown as
logger = logging.getLogger(__name__)
APP_VERSION = "1.0.22"
APP_VERSION = "1.0.23"
_heartbeat_task: asyncio.Task | None = None
+22
View File
@@ -19352,6 +19352,17 @@ var requestPrototype = {
}
});
});
Object.defineProperty(requestPrototype, /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom"), {
value: function(depth, options, inspectFn) {
const props = {
method: this.method,
url: this.url,
headers: this.headers,
nativeRequest: this[requestCache]
};
return `Request (lightweight) ${inspectFn(props, { ...options, depth: depth == null ? null : depth - 1 })}`;
}
});
Object.setPrototypeOf(requestPrototype, Request.prototype);
var newRequest = (incoming, defaultHostname) => {
const req = Object.create(requestPrototype);
@@ -19456,6 +19467,17 @@ var Response2 = class _Response {
}
});
});
Object.defineProperty(Response2.prototype, /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom"), {
value: function(depth, options, inspectFn) {
const props = {
status: this.status,
headers: this.headers,
ok: this.ok,
nativeResponse: this[responseCache]
};
return `Response (lightweight) ${inspectFn(props, { ...options, depth: depth == null ? null : depth - 1 })}`;
}
});
Object.setPrototypeOf(Response2, GlobalResponse);
Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
async function readWithoutBlocking(readPromise) {
@@ -13,9 +13,9 @@
}
},
"node_modules/@hono/node-server": {
"version": "1.19.13",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz",
"integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==",
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
"integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
@@ -1010,9 +1010,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
@@ -1182,9 +1182,9 @@
}
},
"node_modules/hono": {
"version": "4.12.12",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz",
"integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==",
"version": "4.12.14",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
"integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@softeria/ms-365-mcp-server": "^0.75.0"
"@softeria/ms-365-mcp-server": "^0.79.6"
}
},
"node_modules/@azure-rest/core-client": {
@@ -350,9 +350,9 @@
}
},
"node_modules/@hono/node-server": {
"version": "1.19.13",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz",
"integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==",
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
"integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
@@ -412,13 +412,13 @@
}
},
"node_modules/@softeria/ms-365-mcp-server": {
"version": "0.75.0",
"resolved": "https://registry.npmjs.org/@softeria/ms-365-mcp-server/-/ms-365-mcp-server-0.75.0.tgz",
"integrity": "sha512-UkrVjbg20Gbaxs4gqhsdSfuAUVHnX8kFKARDhnrEI/KIZicI23CZiCdmK75DkS8ae+n2au+BumK9gI2Utk3Xww==",
"version": "0.79.6",
"resolved": "https://registry.npmjs.org/@softeria/ms-365-mcp-server/-/ms-365-mcp-server-0.79.6.tgz",
"integrity": "sha512-vr2ea+YRJuinFIqRoqh0p5S4HEwN0F9GpKC8iOHtRM6FhmCx9fth1sW1zJnCbTJjJ/Jrit1wTXYUFuAE2QmcEw==",
"license": "MIT",
"dependencies": {
"@azure/msal-node": "^3.8.0",
"@modelcontextprotocol/sdk": "^1.28.0",
"@modelcontextprotocol/sdk": "^1.29.0",
"@toon-format/toon": "^0.8.0",
"commander": "^11.1.0",
"dotenv": "^17.0.1",
@@ -426,7 +426,8 @@
"js-yaml": "^4.1.0",
"open": "^11.0.0",
"winston": "^3.17.0",
"zod": "^3.24.2"
"zod": "^3.24.2",
"zod-to-json-schema": "^3.25.1"
},
"bin": {
"ms-365-mcp-server": "dist/index.js"
@@ -912,9 +913,9 @@
}
},
"node_modules/dotenv": {
"version": "17.4.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
"integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==",
"version": "17.4.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -1284,9 +1285,9 @@
}
},
"node_modules/hono": {
"version": "4.12.12",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz",
"integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==",
"version": "4.12.14",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
"integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
@@ -11,6 +11,6 @@
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@softeria/ms-365-mcp-server": "^0.75.0"
"@softeria/ms-365-mcp-server": "^0.79.6"
}
}
+57
View File
@@ -0,0 +1,57 @@
import re
import inspect
import os
from debugger_backend.log_config import log_config
from debugger_backend.Debugleton import Debugleton
from debugger_backend.color_adjuster import rgb_to_ansi, bold_and_italicize_text, hex_to_rgb
from debugger_backend.debug_arg_parser import is_text, is_error
def debug(*args, mode:str='debug', override_max_chars:bool=False):
frame = inspect.currentframe().f_back
code = frame.f_code
line_no = frame.f_lineno
calling_function_name = frame.f_code.co_name
calling_file_name = os.path.basename(code.co_filename)
if calling_function_name == "<module>":
calling_function_name = calling_file_name
# Retrieve the file path of the calling function
file_path = os.path.abspath(code.co_filename)
# print(f"FILE PATH: {file_path}")
t_color, t_is_on, t_emoji = Debugleton().find_file_info(file_path)
# print(f"DEBUGGING: {t_color}, {t_is_on}")
max_chars = 3000
with open(code.co_filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
line = lines[line_no - 1]
leading_spaces = len(line) - len(line.lstrip(' '))
indent = leading_spaces // 4
arg_names = re.findall(r'debug\((.*?)\)', line)[0].split(', ')
for arg_name, arg_value in zip(arg_names, args):
indent_str = ' |\t' * indent
if indent > 0:
indent_str = indent_str[:-3] + ' |-- '
arg_is_error = is_error(arg_value, arg_name)
arg_is_text = is_text(arg_value, arg_name)
if arg_is_error:
t_color = "#FE3F3F"
t_emoji = ""
t_is_on = True
arg_len = len(str(arg_value))
if arg_len > max_chars and not override_max_chars:
if not arg_is_text: arg_value = str(arg_value)
arg_value = arg_value[:int(max_chars/2)] + "...\n..." + arg_value[arg_len-int(max_chars/2):]
function_print_str = calling_function_name if 'self' not in frame.f_locals else f'{frame.f_locals["self"].__class__.__name__}.{calling_function_name}'
# color = COLORS.get(function_print_str, white)
color = hex_to_rgb(t_color)
if arg_is_text:
print_str = f"{t_emoji}{rgb_to_ansi(color)}{indent_str}[{function_print_str}] : {bold_and_italicize_text(arg_value)}\033[0m"
else:
print_str = f"{t_emoji}{rgb_to_ansi(color)}{indent_str}[{function_print_str}] : {arg_name} = {arg_value}\033[0m"
if t_is_on: log_config.debug_custom(print_str, mode)
# Assign the function to the module's __call__ attribute
import sys
sys.modules[__name__] = debug
@@ -0,0 +1,7 @@
import os
TOGGLE_FILE = os.path.join(os.path.dirname(__file__), 'debug_toggles.json')
DEFAULT_COLOR = '#ffffff'
DEFAULT_TOGGLED = False
DEFAULT_SET_MANUALLY = False
DEFAULT_EMOJI = ''
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -0,0 +1,39 @@
import os
from debugger_backend.File import File
from debugger_backend.DEFAULTS import DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_SET_MANUALLY, DEFAULT_EMOJI
class DebugFile(File):
def __init__(self, filename, path, color=DEFAULT_COLOR, is_toggled=DEFAULT_TOGGLED,
set_manually=DEFAULT_SET_MANUALLY, emoji=DEFAULT_EMOJI, directory=None):
super().__init__(filename, path)
self.color = color
self.is_toggled = is_toggled
self.set_manually = set_manually
self.emoji = emoji
self.directory = directory # Reference to parent directory
def to_dict(self):
"""
Converts the DebugFile object to a dictionary format.
"""
return {
"name": os.path.basename(self.filename),
"color": self.color,
"is_toggled": self.is_toggled,
"set_manually": self.set_manually,
"emoji": self.emoji
}
@classmethod
def from_dict(cls, file_dict, directory):
"""
Creates a DebugFile object from a dictionary loaded from JSON.
"""
filename = os.path.join(directory.path, file_dict["name"])
return cls(
filename=filename,
color=file_dict.get("color", DEFAULT_COLOR),
is_toggled=file_dict.get("is_toggled", DEFAULT_TOGGLED),
set_manually=file_dict.get("set_manually", DEFAULT_SET_MANUALLY),
directory=directory
)
@@ -0,0 +1,88 @@
# Haik: sorry bout the filename
import threading
from debugger_backend.project_scanner import update_debug_toggles
from debugger_backend.Directory import Directory
from debugger_backend.DebugFile import DebugFile
from debugger_backend.DEFAULTS import DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_EMOJI
import os
import time
NEEDS_RESYNC_FILE = os.path.join(os.path.dirname(__file__), 'needs_resync.txt')
class Debugleton:
_instance = None
_lock = threading.Lock() # Lock for thread-safe singleton creation
sync_lock: threading.Lock
def __new__(cls, *args, **kwargs):
# Double-checked locking for thread-safe singleton creation
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super(Debugleton, cls).__new__(cls)
print("\033[38;5;120m\n---------------------------------\033[0m")
print("\033[38;5;120m|\tDEBUGLETON INIT \t|\033[0m")
cls._instance.dir = None
print("\033[38;5;120m|\tScanning Project...\t|\033[0m")
cls._instance.sync_lock = threading.Lock()
cls._instance.sync_lock.acquire(blocking=False)
cls._instance.sync_to_saved(is_first_sync=True)
cls._instance.sync_lock.release()
print("\033[38;5;120m|\t...Project Scanned\t|\033[0m")
print("\033[38;5;120m|\tDEBUGLETON INIT DONE\t|\033[0m")
print("\033[38;5;120m---------------------------------\n\033[0m")
# else: print("DEBUGLETON Already initialized INNER")
# else: print("DEBUGLETON Already initialized OUTER")
return cls._instance
def sync_to_saved(self, is_first_sync=False):
# print(f"[sync_to_saved]: START")
if not is_first_sync: self.sync_lock.acquire()
# print(f"[sync_to_saved]: Acquired sync lock")
self.dir = update_debug_toggles(save_to_file=False)
# print(f"Synced to saved dir: {self.dir}")
self.abspaths, self.instances = self.dir.get_ordered_abspaths_and_instances()
# print(f"Synced to abspaths: {self.abspaths}")
with open(NEEDS_RESYNC_FILE, 'w') as f:
f.write('0')
if not is_first_sync: self.sync_lock.release()
# print(f"[sync_to_saved]: Released sync lock")
# print(f"[sync_to_saved]: END")
def needs_resync(self):
# print(f"[needs_resync]: START")
num_tries = 0
while self.is_syncing():
print(f"Waiting for Debugleton to sync... ({num_tries})")
time.sleep(5)
num_tries += 1
if num_tries > 10:
print(f"""
NOTE: Debugleton is taking a long time, there's one scenario where it breaks:
\n\t- If running in docker, and you deleted one of the root dirs in the volumes of docker compose,
\n\t then the debugger will not be able to find the project and will get stuck in an infinite loop.
\n\t- In this case, you can restart the docker container and delete the volume in the docker compose file and it will resync.
""")
with open(NEEDS_RESYNC_FILE, 'r') as f:
does_need_resync = True if f.read().strip() == '1' else False
# if does_need_resync: print("Resyncing Debugleton...")
# print(f"[needs_resync]: END")
return does_need_resync
def is_syncing(self):
return self.sync_lock.locked()
def find_file_info(self, filepath: str):
filepath = filepath.lower()
# print(f"Finding file info for {filepath}")
if self.needs_resync():
self.sync_to_saved()
try:
filepath_id = self.abspaths.index(filepath)
match = self.instances[filepath_id]
return match.color, match.is_toggled, match.emoji
except ValueError:
print(f"Filepath not found: {filepath}")
return DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_EMOJI
@@ -0,0 +1,200 @@
import os
import json
import colorsys
from pathlib import Path
from debugger_backend.DebugFile import DebugFile
from debugger_backend.DEFAULTS import DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_SET_MANUALLY, DEFAULT_EMOJI
from debugger_backend.path_mngr import get_abspath, get_root_rel_path
class Directory:
def __init__(self, path, color=DEFAULT_COLOR, is_toggled=DEFAULT_TOGGLED,
set_manually=DEFAULT_SET_MANUALLY, emoji=DEFAULT_EMOJI):
self.path = path
# print(f"Directory init: {self.path}")
self.children = [] # Can contain DebugFile or other Directory objects
self.color = color
self.is_toggled = is_toggled
self.set_manually = set_manually
self.emoji = emoji
def __str__(self):
return f"Directory: {self.path}\nNum Children: {len(self.children)}\nColor: {self.color}\nToggled: {self.is_toggled}\nSet Manually: {self.set_manually}"
def get_abspath(self):
return get_abspath(self.path)
def add_child(self, child):
"""
Adds a child to the directory (either a DebugFile or another Directory).
"""
self.children.append(child)
def get_ordered_abspaths_and_instances(self):
# print("[get_ordered_abspaths]: START")
curr_file_path = os.path.abspath(__file__)
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(curr_file_path)))
# print(f"[get_ordered_abspaths]: Curr path: {curr_file_path}")
# print(f"[get_ordered_abspaths]: Dir path: {root_dir}")
def construct_ordered_abspaths(dir: Directory, ordered_abspaths: list):
dir_path = dir.path
full_path = os.path.join(root_dir, dir_path)
ordered_abspaths.append({"abspath": full_path, "instance": dir})
# print(f"\t[construct_ordered_abspaths]: Full path: {full_path}")
for child in dir.children:
child_abspath = os.path.join(root_dir, child.path).lower()
if os.path.isdir(child_abspath):
construct_ordered_abspaths(child, ordered_abspaths)
elif os.path.isfile(child_abspath):
# print(f"\t[construct_ordered_abspaths]: Child is file: {child_abspath}")
ordered_abspaths.append({"abspath": child_abspath, "instance": child})
else:
print(f"\033[38;5;120mEntry is non existent: {child_abspath}\033[0m")
# print(f"\t[construct_ordered_abspaths]: Finished for dir: {full_path}")
# print(f"\t[construct_ordered_abspaths]: RETURNING FROM DIR: {full_path}")
return ordered_abspaths
ordered_abspaths_and_instances = construct_ordered_abspaths(self, [])
# print("[get_ordered_abspaths]: Finished getting ordered abspaths and instances")
# for abspath_and_instance in ordered_abspaths_and_instances:
# abspath = abspath_and_instance["abspath"]
# print(f"\t[get_ordered_abspaths]: Abspath: {abspath}")
ordered_abspaths = [abspath_and_instance["abspath"] for abspath_and_instance in ordered_abspaths_and_instances]
ordered_instances = [abspath_and_instance["instance"] for abspath_and_instance in ordered_abspaths_and_instances]
return ordered_abspaths, ordered_instances
def build_structure(self):
print("[build_structure]: START")
root_dir = self.get_abspath()
# print(f"[build_structure]: Root dir: {root_dir}")
excluded_dirs = [".venv", "debugger", "node_modules", ".git", "__pycache__"]
project_structure = []
def construct_project_structure(dir_path: str, parent_dir: Directory):
# print(f"[build_structure]: Scanning dir: {dir_path}")
with os.scandir(dir_path) as it:
for entry in it:
# print(f"[build_structure]: Entry: {entry.path}")
if any(excluded_dir in entry.path for excluded_dir in excluded_dirs):
# print(f"[build_structure]: Excluding {entry.path}")
continue
root_rel_path = get_root_rel_path(entry.path)
if entry.is_dir():
subdir = Directory(root_rel_path)
construct_project_structure(entry.path, subdir)
parent_dir.add_child(subdir)
elif entry.is_file():
debug_file = DebugFile(filename=entry.name, path=root_rel_path)
if debug_file.calls_debug_function():
parent_dir.add_child(debug_file)
else:
continue
construct_project_structure(root_dir, self)
# [print(f"[build_structure]: {file}") for file in project_structure]
# print(f"[build_structure]: END")
return
def to_dict(self):
"""
Converts the Directory object to a dictionary format, recursively.
"""
return {
"name": os.path.basename(self.path),
"color": self.color,
"is_toggled": self.is_toggled,
"set_manually": self.set_manually,
"emoji": self.emoji,
"children": [child.to_dict() if isinstance(child, DebugFile) else child.to_dict() for child in self.children]
}
def prune_empty(self):
# Recursively prune empty directories
# Base case) if the current directory has no children, return
# Recursive case) for each of the directories in the current directory, call prune_empty
# then remove the directory from the children of the current directory if it has no children
for child in self.children[:]:
if isinstance(child, Directory):
# Recursively prune empty subdirectories
child.prune_empty()
# If the subdirectory is empty after pruning, remove it
if len(child.children) == 0:
self.children.remove(child)
def propagate_toggled_state(self):
"""
Propagates the toggled state down the hierarchy.
"""
for child in self.children:
if isinstance(child, DebugFile) and not child.set_manually:
child.is_toggled = self.is_toggled
elif isinstance(child, Directory) and not child.set_manually:
child.is_toggled = self.is_toggled
child.propagate_toggled_state()
def propagate_color(self, parent_color=DEFAULT_COLOR):
"""
Propagates the color from parent to children.
"""
if self.color == DEFAULT_COLOR:
self.color = lighten_color(parent_color)
for child in self.children:
if isinstance(child, DebugFile) and child.color == DEFAULT_COLOR:
child.color = lighten_color(self.color)
elif isinstance(child, Directory):
child.propagate_color(self.color)
def load_from_json(self, json_data):
"""
Loads a directory structure from a JSON file into this Directory instance.
"""
for item in json_data:
if 'children' in item:
subdir = Directory(
path=os.path.join(self.path, item['name']),
color=item.get('color', DEFAULT_COLOR),
is_toggled=item.get('is_toggled', DEFAULT_TOGGLED),
set_manually=item.get('set_manually', DEFAULT_SET_MANUALLY),
emoji=item.get('emoji', DEFAULT_EMOJI)
)
subdir.load_from_json(item['children'])
self.add_child(subdir)
else:
# debug_file = DebugFile.from_dict(item, self)
debug_file = DebugFile(
filename=item['name'],
path=os.path.join(self.path, item['name']),
color=item.get('color', DEFAULT_COLOR),
is_toggled=item.get('is_toggled', DEFAULT_TOGGLED),
set_manually=item.get('set_manually', DEFAULT_SET_MANUALLY),
emoji=item.get('emoji', DEFAULT_EMOJI),
directory=self
)
self.add_child(debug_file)
def reset_colors(self):
"""
Resets the color of all DebugFile and Directory objects in this directory structure to the default color.
"""
self.color = DEFAULT_COLOR
for child in self.children:
if isinstance(child, DebugFile):
child.color = DEFAULT_COLOR
elif isinstance(child, Directory):
child.reset_colors()
def lighten_color(color, amount=0.1):
"""
Lightens the given color by the specified amount.
"""
try:
color = color.lstrip('#')
r, g, b = int(color[:2], 16), int(color[2:4], 16), int(color[4:6], 16)
h, l, s = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0)
l = min(1, l + amount)
r, g, b = colorsys.hls_to_rgb(h, l, s)
return '#{:02x}{:02x}{:02x}'.format(int(r * 255), int(g * 255), int(b * 255))
except Exception as e:
print(f"Error lightening color {color}: {e}")
return color
@@ -0,0 +1,29 @@
import os
from debugger_backend.path_mngr import get_abspath
class File:
def __init__(self, filename, path):
self.filename = filename
self.path = path
def get_abspath(self):
return get_abspath(self.path)
def calls_debug_function(self):
"""
Checks if the file calls the debug function.
"""
full_path = self.get_abspath()
if not full_path.endswith('.py') or full_path.endswith('.pyc'):
result = False
else:
try:
with open(full_path, 'r', encoding='utf-8') as file:
content = file.read()
result = 'debug(' in content
except (UnicodeDecodeError, FileNotFoundError) as e:
print(f"Error reading file {full_path}")
result = False
# print(f"??calls_debug_function?? {result}")
return result
@@ -0,0 +1,21 @@
import colorsys
def adjust_brightness(color, brightness_factor):
hls = colorsys.rgb_to_hls(*[x/255.0 for x in color]) # Convert RGB to HLS
hls = (hls[0], max(0, min(1, hls[1] + brightness_factor)), hls[2]) # Adjust lightness
rgb = [int(x*255.0) for x in colorsys.hls_to_rgb(*hls)] # Convert back to RGB
return rgb
def rgb_to_ansi(rgb):
return '\033[38;2;{};{};{}m'.format(*rgb)
def bold_and_italicize_text(text):
return f"\033[1m\033[3m{text}\033[0m"
def hex_to_rgb(hex_code):
# Remove the '#' symbol if it exists
hex_code = hex_code.lstrip('#')
# Convert the hex code to RGB
return tuple(int(hex_code[i:i+2], 16) for i in (0, 2, 4))
@@ -0,0 +1,21 @@
def is_fstring(arg_name):
if not isinstance(arg_name, str):
return False
# print(f"arg_name: {arg_name}")
fstring_start_values = ["f'", "f\""]
num_start_matches = sum(arg_name.startswith(start_value) for start_value in fstring_start_values)
conditions = [num_start_matches == 1]
return all(conditions)
def is_text(arg_value, arg_name):
arg_is_text = isinstance(arg_value, str) and len(arg_name) > 2 and arg_name[1:len(arg_name)-1] == arg_value and not arg_name.endswith(")")
if not arg_is_text:
arg_is_text = is_fstring(arg_name)
# print(f"is_text: {arg_is_text}")
return arg_is_text
def is_error(arg_value, arg_name):
arg_is_error = isinstance(arg_value, Exception) or "error" in str(arg_value).lower() or "error" in str(arg_name).lower()
return arg_is_error
@@ -0,0 +1,49 @@
from flask import Flask, request, jsonify, Response
from flask_cors import CORS
from debugger_backend.project_scanner import update_debug_toggles, dir_to_output_format
import json
import os
NEEDS_RESYNC_FILE = os.path.join(os.path.dirname(__file__), 'needs_resync.txt')
DEBUG_TOGGLE_FILE = os.path.join(os.path.dirname(__file__), 'debug_toggles.json')
app = Flask(__name__)
CORS(app)
@app.route('/pull_structure', methods=['GET'])
def api_get_structure():
print("GET /get_structure")
scanned_dir=update_debug_toggles(save_to_file=True)
# print("\n\nPS scanned_dir: ", scanned_dir)
output = dir_to_output_format(scanned_dir)
output = json.dumps(output, ensure_ascii=False, indent=4)
# print("output: ", output)
return Response(output, mimetype='application/json')
@app.route('/push_structure', methods=['POST'])
def api_push_structure():
print("POST /push_structure")
data = request.get_json()
data = data['projectStructure']
# print(data)
with open(DEBUG_TOGGLE_FILE, 'w', encoding='utf-8') as file:
json.dump(data, file, indent=4)
with open(NEEDS_RESYNC_FILE, 'w') as f:
f.write('1')
return jsonify({"status": "success"})
@app.route('/reset_color', methods=['POST'])
def api_reset_color():
print("POST /reset_color")
scanned_dir=update_debug_toggles(save_to_file=False)
scanned_dir.reset_colors()
# print("RS: scanned_dir: ", scanned_dir)
output = dir_to_output_format(scanned_dir)
output = json.dumps(output, ensure_ascii=False, indent=4)
# print("RS: output: ", output)
return Response(output, mimetype='application/json')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=6969, debug=False)
@@ -0,0 +1,50 @@
import logging
from debugger_backend.log_mode import get_log_mode, set_log_mode
class LogConfig:
_instance = None
MODES = {
"all": 1,
"debug": 10,
"test": 20,
}
def __new__(cls):
if cls._instance is None:
cls._instance = super(LogConfig, cls).__new__(cls)
cls._instance._initialize_logger()
return cls._instance
def _initialize_logger(self):
for name, level in self.MODES.items():
logging.addLevelName(level, name.upper())
self.logger = logging.getLogger('custom_logger')
self.logger.propagate = False # Prevent log propagation
handler = logging.StreamHandler()
formatter = logging.Formatter('%(message)s')
handler.setFormatter(formatter)
# Remove existing handlers to prevent duplicate logging
if self.logger.hasHandlers():
self.logger.handlers.clear()
self.logger.addHandler(handler)
self.set_debug_mode(get_log_mode())
def debug_custom(self, message, mode = None, *args, **kwargs):
if mode is None:
mode = get_log_mode()
if self.logger.isEnabledFor(self.MODES[mode]):
self.logger._log(self.MODES[mode], message, args, **kwargs)
def set_debug_mode(self, mode):
current_mode = get_log_mode()
# print(f"Setting debug mode from {current_mode} -> to {mode}")
if mode not in self.MODES: raise ValueError(f"Invalid mode: {mode}")
set_log_mode(mode)
self.logger.setLevel(self.MODES[mode])
def get_debug_mode(self):
return get_log_mode()
log_config = LogConfig()
@@ -0,0 +1,13 @@
import os
# LOG_MODE_FILE = 'debugger/log_mode.txt'
LOG_MODE_FILE = os.path.join(os.path.dirname(__file__), 'log_mode.txt')
def set_log_mode(mode):
with open(LOG_MODE_FILE, 'w') as f:
f.write(mode)
def get_log_mode():
if os.path.exists(LOG_MODE_FILE):
with open(LOG_MODE_FILE, 'r') as f:
return f.read().strip()
return 'all' # Default to 'all' if the file doesn't exist
@@ -0,0 +1,12 @@
import os
from debugger_backend.DEFAULTS import ROOT_DIR
def get_abspath(path: str):
return os.path.join(ROOT_DIR, path)
def get_root_rel_path(path: str):
assert path.startswith(ROOT_DIR)
path = path[len(ROOT_DIR):]
while path.startswith(os.sep):
path = path[1:]
return path
@@ -0,0 +1,149 @@
import os
import json
import colorsys
from typing import Union
from debugger_backend.Directory import Directory
from debugger_backend.DEFAULTS import DEFAULT_COLOR, DEFAULT_TOGGLED, DEFAULT_SET_MANUALLY, TOGGLE_FILE, DEFAULT_EMOJI, ROOT_DIR
from debugger_backend.DebugFile import DebugFile
from collections import OrderedDict
def merge_directories(json_dir: Directory, scanned_dir: Directory):
"""
Merges two Directory instances: one loaded from JSON (json_dir) and one built from scanning (scanned_dir).
The values from json_dir take precedence where attributes overlap.
It matches based on full directory and file structure, not just file names.
"""
# print(f"Merging JSON_DIR: {json_dir.path}\n with SCAN_DIR: {scanned_dir.path}")
json_abspaths, json_instances = json_dir.get_ordered_abspaths_and_instances()
# print(f"json_abspaths: {json_abspaths}")
scanned_abspaths, scanned_instances = scanned_dir.get_ordered_abspaths_and_instances()
# print(f"scanned_abspaths: {scanned_abspaths}")
def find_matching_in_structure(scanned_child: Union[DebugFile, Directory], json_dir: Directory):
assert json_dir in json_instances, f"JSON_DIR: {json_dir.path} not in json_instances"
assert scanned_child in scanned_instances, f"SCANNED_CHILD: {scanned_child.path} not in scanned_instances"
scanned_id = scanned_instances.index(scanned_child)
scanned_abspath = scanned_abspaths[scanned_id]
json_instance = None
try:
json_id = json_abspaths.index(scanned_abspath)
json_instance = json_instances[json_id]
# print(f"Match found: {scanned_child.path} == {json_instance.path}")
except ValueError:
# print(f"SCANNED_ABSPATH: {scanned_abspath} not in JSON_ABSPATHS")
pass
return json_instance
def construct_merged_dir(json_dir: Directory, scanned_dir: Directory):
for scanned_child in scanned_dir.children:
# Use the new recursive function to find the corresponding child in the JSON directory structure
matching_json_child = find_matching_in_structure(scanned_child, json_dir)
if isinstance(scanned_child, DebugFile) and matching_json_child:
# Merge attributes from the JSON-loaded structure
scanned_child.color = matching_json_child.color
scanned_child.is_toggled = matching_json_child.is_toggled
scanned_child.set_manually = matching_json_child.set_manually
scanned_child.emoji = matching_json_child.emoji
elif isinstance(scanned_child, Directory) and matching_json_child:
# Merge directory attributes
scanned_child.color = matching_json_child.color
scanned_child.is_toggled = matching_json_child.is_toggled
scanned_child.set_manually = matching_json_child.set_manually
scanned_child.emoji = matching_json_child.emoji
# Recursively merge the subdirectories
construct_merged_dir(matching_json_child, scanned_child)
else:
scanned_child.color = DEFAULT_COLOR
scanned_child.is_toggled = DEFAULT_TOGGLED
scanned_child.set_manually = DEFAULT_SET_MANUALLY
scanned_child.emoji = DEFAULT_EMOJI
construct_merged_dir(json_dir, scanned_dir)
def update_debug_toggles(save_to_file=True) -> Directory:
# print(f"[update_debug_toggles]: START")
json_loaded_dir = None
if os.path.exists(TOGGLE_FILE):
with open(TOGGLE_FILE, 'r', encoding='utf-8') as file:
try:
json_data = json.load(file)
json_loaded_dir = Directory("")
json_loaded_dir = Directory(path="",
color=json_data[0].get('color', DEFAULT_COLOR),
is_toggled=json_data[0].get('is_toggled', DEFAULT_TOGGLED),
set_manually=json_data[0].get('set_manually', DEFAULT_SET_MANUALLY),
emoji=json_data[0].get('emoji', DEFAULT_EMOJI)
)
# print(f"Root: {json_loaded_dir}")
# print("Json Children 1:")
# [print(child.path) for child in json_loaded_dir.children]
json_loaded_dir.load_from_json(json_data[0]['children']) # Assuming the root is in json_data[0]
# print("Json Children 2:")
# [print(child.path) for child in json_loaded_dir.children]
except json.JSONDecodeError:
ValueError("Error: JSON file could not be decoded.")
else:
print("No JSON file found")
# 1. Create a directory structure from the filesystem scan
# print("Scanning directory...")
scanned_dir = Directory(path="",
color=json_loaded_dir.color if json_loaded_dir else DEFAULT_COLOR,
is_toggled=json_loaded_dir.is_toggled if json_loaded_dir else DEFAULT_TOGGLED,
set_manually=json_loaded_dir.set_manually if json_loaded_dir else DEFAULT_SET_MANUALLY,
emoji=json_loaded_dir.emoji if json_loaded_dir else DEFAULT_EMOJI
)
# print(f"\n\nNum Children 1: {len(scanned_dir.children)}")
# [print(child.path) for child in scanned_dir.children]
scanned_dir.build_structure()
# print(f"\n\nNum Children 2: {len(scanned_dir.children)}")
# [print(child.path) for child in scanned_dir.children]
scanned_dir.prune_empty()
# print(f"\n\nNum Children 3: {len(scanned_dir.children)}")
# [print(child.path) for child in scanned_dir.children]
# print("1.1 Merged Dir First Child: ", scanned_dir.children[0])
# 4. Propagate the toggled state and color through the merged structure
scanned_dir.propagate_toggled_state()
# print(f"\n\nNum Children 4: {len(scanned_dir.children)}")
# [print(child.path) for child in scanned_dir.children]
# 3. Merge the two directory structures
if json_loaded_dir:
merge_directories(json_loaded_dir, scanned_dir)
# print(f"\n\nNum Children 5: {len(scanned_dir.children)}")
scanned_dir.propagate_color()
output = dir_to_output_format(scanned_dir)
# print(f"\n\nNum Children 6: {len(scanned_dir.children)}")
# 5. Write the updated structure back to the JSON file
if save_to_file:
with open(TOGGLE_FILE, 'w', encoding='utf-8') as file:
json.dump(output, file, ensure_ascii=False, indent=4)
# print(f"[update_debug_toggles]: END")
return scanned_dir
def dir_to_output_format(input_dir):
root_node = {
"name": "root",
"color": input_dir.color, # Use input_dir's color
"is_toggled": input_dir.is_toggled, # Use input_dir's toggled state
"set_manually": input_dir.set_manually, # Use input_dir's set_manually
"emoji": input_dir.emoji, # Use input_dir's emoji
"children": input_dir.to_dict()["children"]
}
return [ordered(root_node)]
def ordered(obj):
if isinstance(obj, dict):
return OrderedDict((k, ordered(v)) for k, v in obj.items())
if isinstance(obj, list):
return [ordered(x) for x in obj]
return obj
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "openswarm",
"version": "1.0.22",
"version": "1.0.23",
"description": "OpenSwarm — AI Agent Orchestrator",
"main": "main.js",
"scripts": {
@@ -44,7 +44,7 @@
},
"dmg": {
"artifactName": "OpenSwarm-${arch}.${ext}",
"title": "OpenSwarm",
"title": "OpenSwarm ${version}",
"contents": [
{
"x": 130,
+1 -1
View File
@@ -86,7 +86,7 @@ echo "Installing backend dependencies..."
# Install the debugger module
echo "Installing debugger module..."
"$PYTHON_BIN" -m pip install -e "$PROJECT_ROOT/debugger"
"$PYTHON_BIN" -m pip install "$PROJECT_ROOT/debugger"
# Verify claude-agent-sdk and its bundled binary
echo "Verifying claude-agent-sdk..."