diff --git a/.gitignore b/.gitignore index 66b76c1..28d9bef 100644 --- a/.gitignore +++ b/.gitignore @@ -161,4 +161,4 @@ myvenv/ #parlant parlant-data -test.md \ No newline at end of file +test.md diff --git a/examples/Stockagent/README.md b/examples/Stockagent/README.md new file mode 100644 index 0000000..0839233 --- /dev/null +++ b/examples/Stockagent/README.md @@ -0,0 +1,68 @@ +# When AI Meets Finance (StockAgent): Large Language Model-based Stock Trading in Simulated Real-world Environments + +![workflow](fig/workflow.png) +![schematic](fig/schematic.png) + +Can AI Agents simulate real-world trading environments to investigate the impact of external factors on stock trading activities (e.g., macroeconomics, policy changes, company fundamentals, and global events)? These factors, which frequently influence trading behaviors, are critical elements in the quest for maximizing investors' profits. Our work attempts to solve this problem through large language model-based agents. We have developed a multi-agent AI system called StockAgent, driven by LLMs, designed to simulate investors' trading behaviors in response to the real stock market. The StockAgent allows users to evaluate the impact of different external factors on investor trading and to analyze trading behavior and profitability effects. Additionally, StockAgent avoids the test set leakage issue present in existing trading simulation systems based on AI Agents. Specifically, it prevents the model from leveraging prior knowledge it may have acquired related to the test data. We evaluate different LLMs under the framework of StockAgent in a stock trading environment that closely resembles real-world conditions. The experimental results demonstrate the impact of key external factors on stock market trading, including trading behavior and stock price fluctuation rules. This research explores the study of agents' free trading gaps in the context of no prior knowledge related to market data. The patterns identified through StockAgent simulations provide valuable insights for LLM-based investment advice and stock recommendation. + +## Link +ARXIV LINK: https://arxiv.org/pdf/2407.18957 +## Architecture +![architect](fig/workflow2.png) + +The Workflow of Trading Simulation Flow. There are four Phases, namely **Initial Phase**, **Trading Phase**, **Post-Trading Phase** and **Special Events Phase**. In the Post-Trading Phase, Daily events and Quarterly events occur with daily and quarterly frequency respectively. A Specific Events Phase is an event that occurs randomly and acts on a random trading day. + +## Quick Start + +#### Environment + +``` +conda create --name stockagent python=3.9 +conda activate stockagent + +git clone https://github.com/dhh1995/PromptCoder +cd PromptCoder +pip install -e . +cd .. + +git clone +cd Stockagent +pip install -r requirements.txt +``` + +#### API keys + +Use GPTs as agent LLM: + +``` +export OPENAI_API_KEY=YOUR_OPENAI_API_KEY +``` + +Use Gemini as agent LLM: + +``` +export GOOGLE_API_KEY=YOUR_GEMINI_API_KEY +``` + +#### Start simulation + +You can choose a basic LLM and start simulation in one line: + +``` +python main.py --model MODEL_NAME +``` + +We set gemini-pro for default LLM. + +#### Citation +If you find the code is valuable, please use this citation. +``` +@article{zhang2024ai, + title={When AI Meets Finance (StockAgent): Large Language Model-based Stock Trading in Simulated Real-world Environments}, + author={Zhang, Chong and Liu, Xinyi and Jin, Mingyu and Zhang, Zhongmou and Li, Lingyao and Wang, Zhengting and Hua, Wenyue and Shu, Dong and Zhu, Suiyuan and Jin, Xiaobo and others}, + journal={arXiv preprint arXiv:2407.18957}, + year={2024} +} +``` + + diff --git a/examples/Stockagent/agent.py b/examples/Stockagent/agent.py new file mode 100644 index 0000000..b3707e7 --- /dev/null +++ b/examples/Stockagent/agent.py @@ -0,0 +1,427 @@ +import math +import time +import openai +import tiktoken +import random +import requests +import google.generativeai as genai + +import util +from log.custom_logger import log + +from prompt.agent_prompt import * +from procoder.functional import format_prompt +from procoder.prompt import * +from secretary import Secretary +from stock import Stock + + +def random_init(stock_a_initial, stock_b_initial): + stock_a, stock_b, cash, debt_amount = 0.0, 0.0, 0.0, 0.0 + while stock_a * stock_a_initial + stock_b * stock_b_initial + cash < util.MIN_INITIAL_PROPERTY \ + or stock_a * stock_a_initial + stock_b * stock_b_initial + cash > util.MAX_INITIAL_PROPERTY \ + or debt_amount > stock_a * stock_a_initial + stock_b * stock_b_initial + cash: + stock_a = int(random.uniform(0, util.MAX_INITIAL_PROPERTY / stock_a_initial)) + stock_b = int(random.uniform(0, util.MAX_INITIAL_PROPERTY / stock_b_initial)) + cash = random.uniform(0, util.MAX_INITIAL_PROPERTY) + debt_amount = random.uniform(0, util.MAX_INITIAL_PROPERTY) + debt = { + "loan": "yes", + "amount": debt_amount, + "loan_type": random.randint(0, len(util.LOAN_TYPE) - 1), + "repayment_date": random.choice(util.REPAYMENT_DAYS) + } + return stock_a, stock_b, cash, debt +# def random_init(stock_initial_price): +# stock, cash, debt_amount = 0.0, 0.0, 0.0 +# while stock * stock_initial_price + cash < util.MIN_INITIAL_PROPERTY \ +# or stock * stock_initial_price + cash > util.MAX_INITIAL_PROPERTY \ +# or debt_amount > stock * stock_initial_price + cash: +# stock = int(random.uniform(0, util.MAX_INITIAL_PROPERTY / stock_initial_price)) +# cash = random.uniform(0, util.MAX_INITIAL_PROPERTY) +# debt_amount = random.uniform(0, util.MAX_INITIAL_PROPERTY) +# debt = { +# "loan": "yes", +# "amount": debt_amount, +# "loan_type": random.randint(0, len(util.LOAN_TYPE)), +# "repayment_date": random.choice(util.REPAYMENT_DAYS) +# } +# return stock, cash, debt + + +class Agent: + def __init__(self, i, stock_a_price, stock_b_price, secretary, model): + self.order = i + self.secretary = secretary + self.model = model + self.character = random.choice(["Conservative", "Aggressive", "Balanced", "Growth-Oriented"]) + + self.stock_a_amount, self.stock_b_amount, self.cash, init_debt = random_init(stock_a_price, stock_b_price) + #self.stock_b_amount = 0 # stock 以手为单位存储,一手=10股,股价其实是一手的价格 + self.init_proper = self.get_total_proper(stock_a_price, stock_b_price) # 初始资产 后续借贷不超过初始资产 + + self.action_history = [[] for _ in range(util.TOTAL_DATE)] + self.chat_history = [] + self.loans = [init_debt] + self.is_bankrupt = False + self.quit = False + + def run_api(self, prompt, temperature: float = 1): + if 'gpt' in self.model: + return self.run_api_gpt(prompt, temperature) + elif 'gemini' in self.model: + return self.run_api_gemini(prompt, temperature) + + def run_api_gemini(self, prompt, temperature: float = 1): + genai.configure(api_key=util.GOOGLE_API_KEY, transport='rest') + generation_config = genai.types.GenerationConfig( + candidate_count=1, + temperature=temperature) + model = genai.GenerativeModel(self.model) + self.chat_history.append({"role": "user", "parts": [prompt]}) + max_retry = 2 + retry = 0 + while retry < max_retry: + try: + response = model.generate_content(contents=self.chat_history, generation_config=generation_config) + new_message_dict = {"role": 'model', "parts": [response.text]} + self.chat_history.append(new_message_dict) + return response.text + except Exception as e: + log.logger.warning("Gemini api retry...{}".format(e)) + retry += 1 + time.sleep(1) + log.logger.error("ERROR: GEMINI API FAILED. SKIP THIS INTERACTION.") + return "" + + + def run_api_gpt(self, prompt, temperature: float = 1): + openai.api_key = util.OPENAI_API_KEY + client = openai.OpenAI(api_key=openai.api_key) + self.chat_history.append({"role": "user", "content": prompt}) + max_retry = 2 + retry = 0 + + # just cut off the overflow tokens + # tokens = encoding.encode(self.chat_history) + + while retry < max_retry: + try: + response = client.chat.completions.create( + model=self.model, + messages=self.chat_history, + temperature=temperature, + ) + new_message_dict = {"role": response.choices[0].message.role, + "content": response.choices[0].message.content} + self.chat_history.append(new_message_dict) + resp = response.choices[0].message.content + return resp + except openai.OpenAIError as e: + log.logger.warning("OpenAI api retry...{}".format(e)) + retry += 1 + time.sleep(1) + log.logger.error("ERROR: OPENAI API FAILED. SKIP THIS INTERACTION.") + return "" + + def get_total_proper(self, stock_a_price, stock_b_price): + return self.stock_a_amount * stock_a_price + self.stock_b_amount * stock_b_price + self.cash + + def get_proper_cash_value(self, stock_a_price, stock_b_price): + proper = self.stock_a_amount * stock_a_price + self.stock_b_amount * stock_b_price + self.cash + a_value = self.stock_a_amount * stock_a_price + b_value = self.stock_b_amount * stock_b_price + return proper, self.cash, a_value, b_value + + def get_total_loan(self): + debt = 0 + for loan in self.loans: + debt += loan["amount"] + return debt + + def plan_loan(self, date, stock_a_price, stock_b_price, lastday_forum_message): + if self.quit: + return {"loan": "no"} + # first day action : prompt with background + if date == 1: + prompt = Collection(BACKGROUND_PROMPT, + LOAN_TYPE_PROMPT, + DECIDE_IF_LOAN_PROMPT).set_indexing_method(sharp2_indexing).set_sep("\n") + max_loan = self.init_proper - self.get_total_loan() + inputs = { + 'date': date, + 'character': self.character, + 'stock_a': self.stock_a_amount, + 'stock_b': self.stock_b_amount, + 'cash': self.cash, + 'debt': self.loans, + 'max_loan': max_loan, + 'loan_rate1': util.LOAN_RATE[0], + 'loan_rate2': util.LOAN_RATE[1], + 'loan_rate3': util.LOAN_RATE[2], + } + + # other days action : prompt with last day forum message & stock price + else: + prompt = Collection(BACKGROUND_PROMPT, + LASTDAY_FORUM_AND_STOCK_PROMPT, + LOAN_TYPE_PROMPT, + DECIDE_IF_LOAN_PROMPT).set_indexing_method(sharp2_indexing).set_sep("\n") + max_loan = self.init_proper - self.get_total_loan() + inputs = { + "date": date, + "character": self.character, + "stock_a": self.stock_a_amount, + "stock_b": self.stock_b_amount, + "cash": self.cash, + "debt": self.loans, + "max_loan": max_loan, + "stock_a_price": stock_a_price, + "stock_b_price": stock_b_price, + "lastday_forum_message": lastday_forum_message, + 'loan_rate1': util.LOAN_RATE[0], + 'loan_rate2': util.LOAN_RATE[1], + 'loan_rate3': util.LOAN_RATE[2], + } + if max_loan <= 0: + return {"loan": "no"} + try_times = 0 + MAX_TRY_TIMES = 3 + resp = self.run_api(format_prompt(prompt, inputs)) + # print(resp) + if resp == "": + return {"loan": "no"} + + loan_format_check, fail_response, loan = self.secretary.check_loan(resp, + max_loan) # secretary check loan format + while not loan_format_check: + # log.logger.debug("WARNING: Loan format check failed because of these issues: {}".format(fail_response)) + try_times += 1 + if try_times > MAX_TRY_TIMES: + log.logger.warning("WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today.") + loan = {"loan": "no"} + break + + resp = self.run_api(format_prompt(LOAN_RETRY_PROMPT, {"fail_response": fail_response})) + if resp == "": + return {"loan": "no"} + loan_format_check, fail_response, loan = self.secretary.check_loan(date, resp) + + if loan["loan"] == "yes": + loan["repayment_date"] = date + util.LOAN_TYPE_DATE[loan["loan_type"]] # add loan repayment_date + self.loans.append(loan) + #self.action_history[date].append(loan) + self.cash += loan["amount"] + log.logger.info("INFO: Agent {} decide to loan: {}".format(self.order, loan)) + else: + log.logger.info("INFO: Agent {} decide not to loan".format(self.order)) + return loan + + # date=交易日, time=当前交易时段 + # 设置 + def plan_stock(self, date, time, stock_a, stock_b, stock_a_deals, stock_b_deals): + if self.quit: + return {"action_type": "no"} + if date in util.SEASON_REPORT_DAYS and time == 1: + index = util.SEASON_REPORT_DAYS.index(date) + prompt = Collection(FIRST_DAY_FINANCIAL_REPORT, FIRST_DAY_BACKGROUND_KNOWLEDGE, SEASONAL_FINANCIAL_REPORT, + DECIDE_BUY_STOCK_PROMPT).set_indexing_method(sharp2_indexing).set_sep("\n") + inputs = { + "date": date, + "time": time, + "stock_a": self.stock_a_amount, + "stock_b": self.stock_b_amount, + "stock_a_price": stock_a.get_price(), + "stock_b_price": stock_b.get_price(), + "stock_a_deals": stock_a_deals, + "stock_b_deals": stock_b_deals, + "cash": self.cash, + "stock_a_report": stock_a.gen_financial_report(index), + "stock_b_report": stock_b.gen_financial_report(index) + } + elif time == 1: + prompt = Collection(FIRST_DAY_FINANCIAL_REPORT, FIRST_DAY_BACKGROUND_KNOWLEDGE, + DECIDE_BUY_STOCK_PROMPT).set_indexing_method(sharp2_indexing).set_sep("\n") + inputs = { + "date": date, + "time": time, + "stock_a": self.stock_a_amount, + "stock_b": self.stock_b_amount, + "stock_a_price": stock_a.get_price(), + "stock_b_price": stock_b.get_price(), + "stock_a_deals": stock_a_deals, + "stock_b_deals": stock_b_deals, + "cash": self.cash + } + else: + prompt = DECIDE_BUY_STOCK_PROMPT + inputs = { + "date": date, + "time": time, + "stock_a": self.stock_a_amount, + "stock_b": self.stock_b_amount, + "stock_a_price": stock_a.get_price(), + "stock_b_price": stock_b.get_price(), + "stock_a_deals": stock_a_deals, + "stock_b_deals": stock_b_deals, + "cash": self.cash + } + + + try_times = 0 + MAX_TRY_TIMES = 3 + resp = self.run_api(format_prompt(prompt, inputs)) + # print(resp) + if resp == "": + return {"action_type": "no"} + + action_format_check, fail_response, action = self.secretary.check_action( + resp, self.cash, self.stock_a_amount, self.stock_b_amount, stock_a.get_price(), stock_b.get_price()) + while not action_format_check: + # log.logger.debug("Action format check failed because of these issues: {}".format(fail_response)) + try_times += 1 + if try_times > MAX_TRY_TIMES: + log.logger.warning("WARNING: Action format try times > MAX_TRY_TIMES. Skip as no loan today.") + action = {"action_type": "no"} + break + + resp = self.run_api(format_prompt(BUY_STOCK_RETRY_PROMPT, {"fail_response": fail_response})) + if resp == "": + return {"action_type": "no"} + action_format_check, fail_response, action = self.secretary.check_action( + resp, self.cash, self.stock_a_amount, self.stock_b_amount, stock_a.get_price(), stock_b.get_price()) + + if action["action_type"] == "buy": + #self.action_history[date].append(action) + log.logger.info("INFO: Agent {} decide to action: {}".format(self.order, action)) + # if action["stock"] == "stock_a": + # self.stock_a_amount += action["amount"] + # self.cash -= action["amount"] * stock_a.get_price() + # else: + # self.stock_b_amount += action["amount"] + # self.cash -= action["amount"] * stock_b.get_price() + return action + elif action["action_type"] == "sell": + #self.action_history[date].append(action) + log.logger.info("INFO: Agent {} decide to action: {}".format(self.order, action)) + # if action["stock"] == "stock_a": + # self.stock_a_amount -= action["amount"] + # self.cash += action["amount"] * stock_a.get_price() + # else: + # self.stock_b_amount -= action["amount"] + # self.cash += action["amount"] * stock_b.get_price() + return action + elif action["action_type"] == "no": + log.logger.info("INFO: Agent {} decide not to action".format(self.order)) + return action + + log.logger.error("ERROR: WRONG ACTION: {}".format(action)) + return {"action_type": "no"} + + def buy_stock(self, stock_name, price, amount): + if self.quit: + return False + if self.cash < price * amount or stock_name not in ['A', 'B']: + log.logger.warning("ILLEGAL STOCK BUY BEHAVIOR: remain cash {}".format(self.cash)) + return False + self.cash -= price * amount + if stock_name == 'A': + self.stock_a_amount += amount + elif stock_name == 'B': + self.stock_b_amount += amount + + return True + + def sell_stock(self, stock_name, price, amount): + if self.quit: + return False + if stock_name == 'B' and self.stock_b_amount < amount: + log.logger.warning("ILLEGAL STOCK SELL BEHAVIOR: remain stock_b {}, amount {}".format(self.stock_b_amount, + amount)) + return False + elif stock_name == 'A' and self.stock_a_amount < amount: + log.logger.warning("ILLEGAL STOCK SELL BEHAVIOR: remain stock_a {}, amount {}".format(self.stock_a_amount, + amount)) + return False + if stock_name == 'A': + self.stock_a_amount -= amount + elif stock_name == 'B': + self.stock_b_amount -= amount + self.cash += price * amount + return True + + def loan_repayment(self, date): + if self.quit: + return + # check是否贷款还款日,还款,破产检查 + for loan in self.loans[:]: + if loan["repayment_date"] == date: + self.cash -= loan["amount"] * (1 + util.LOAN_RATE[loan["loan_type"]]) + self.loans.remove(loan) + if self.cash < 0: + self.is_bankrupt = True + + + def interest_payment(self): + if self.quit: + return + # 贷款付息日付息 + for loan in self.loans: + self.cash -= loan["amount"] * util.LOAN_RATE[loan["loan_type"]] / 12 + if self.cash < 0: + self.is_bankrupt = True + + def bankrupt_process(self, stock_a_price, stock_b_price): + if self.quit: + return False + total_value_of_stock = self.stock_a_amount * stock_a_price + self.stock_b_amount * stock_b_price + if total_value_of_stock + self.cash < 0: + log.logger.warning(f"Agent {self.order} bankrupt. ") + #f"Action history: " + str(self.action_history)) + return True + if stock_a_price * self.stock_a_amount >= -self.cash: + sell_a = math.ceil(-self.cash / stock_a_price) + self.stock_a_amount -= sell_a + self.cash += sell_a * stock_a_price + else: + self.cash += stock_a_price * self.stock_a_amount + self.stock_a_amount = 0 + sell_b = math.ceil(-self.cash / stock_b_price) + self.stock_b_amount -= sell_b + self.cash += sell_b * stock_b_price + + if self.stock_a_amount < 0 or self.stock_b_amount < 0 or self.cash < 0: + raise RuntimeError("ERROR: WRONG BANKRUPT PROCESS") + self.is_bankrupt = False + return False + + def post_message(self): + if self.quit: + return "" + prompt = format_prompt(POST_MESSAGE_PROMPT, inputs={}) + resp = self.run_api(prompt) + return resp + + def next_day_estimate(self): + if self.quit: + return {"buy_A": "no", "buy_B": "no", "sell_A": "no", "sell_B": "no", "loan": "no"} + prompt = format_prompt(NEXT_DAY_ESTIMATE_PROMPT, inputs={}) + resp = self.run_api(prompt) + if resp == "": + return {"buy_A": "no", "buy_B": "no", "sell_A": "no", "sell_B": "no", "loan": "no"} + format_check, fail_response, estimate = self.secretary.check_estimate(resp) + try_times = 0 + MAX_TRY_TIMES = 3 + while not format_check: + try_times += 1 + if try_times > MAX_TRY_TIMES: + log.logger.warning("WARNING: Estimation format try times > MAX_TRY_TIMES. Skip as all 'no' today.") + estimate = {"buy_A": "no", "buy_B": "no", "sell_A": "no", "sell_B": "no", "loan": "no"} + break + resp = self.run_api(format_prompt(NEXT_DAY_ESTIMATE_RETRY, {"fail_response": fail_response})) + if resp == "": + return {"buy_A": "no", "buy_B": "no", "sell_A": "no", "sell_B": "no", "loan": "no"} + format_check, fail_response, estimate = self.secretary.check_estimate(resp) + return estimate + + diff --git a/examples/Stockagent/log/custom_logger.py b/examples/Stockagent/log/custom_logger.py new file mode 100644 index 0000000..c423d91 --- /dev/null +++ b/examples/Stockagent/log/custom_logger.py @@ -0,0 +1,41 @@ +import logging +from colorama import Fore, Style, Back + +class ColoredFormatter(logging.Formatter): + def format(self, record): + levelname_color = { + 'DEBUG': Fore.CYAN + Style.BRIGHT, + 'INFO': Fore.GREEN + Style.BRIGHT, + 'WARNING': Fore.YELLOW + Style.BRIGHT, + 'ERROR': Fore.RED + Style.BRIGHT, + 'CRITICAL': Fore.RED + Style.BRIGHT, + } + message = super().format(record) + if record.levelname in levelname_color: + message = levelname_color[record.levelname] + message + Style.RESET_ALL + return message + + +class CustomLogger: + def __init__(self): + self.log_file = 'log/test.txt' + self.logger = logging.getLogger('Stocklogger') + self.logger.setLevel(logging.DEBUG) + + # 创建一个handler用于写入日志文件 + file_handler = logging.FileHandler(self.log_file) + file_handler.setLevel(logging.DEBUG) + plain_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(plain_formatter) + + # 创建一个handler用于输出到控制台(带有颜色) + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.DEBUG) + colored_formatter = ColoredFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + console_handler.setFormatter(colored_formatter) + + self.logger.addHandler(file_handler) + self.logger.addHandler(console_handler) + + +log = CustomLogger() diff --git a/examples/Stockagent/log/test.txt b/examples/Stockagent/log/test.txt new file mode 100644 index 0000000..3d36cd1 --- /dev/null +++ b/examples/Stockagent/log/test.txt @@ -0,0 +1,2306 @@ +2025-10-24 16:01:06,975 - Stocklogger - DEBUG - Agents initial... +2025-10-24 16:01:06,975 - Stocklogger - DEBUG - cash: 2000912.9010808768, stock a: 16319, stock b:37960, debt: [{'loan': 'yes', 'amount': 2457245.72356737, 'loan_type': 0, 'repayment_date': 88}] +2025-10-24 16:01:06,975 - Stocklogger - DEBUG - cash: 3406051.8712514797, stock a: 30502, stock b:12714, debt: [{'loan': 'yes', 'amount': 3126925.2067254465, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:01:06,975 - Stocklogger - DEBUG - cash: 2493616.4008787647, stock a: 71183, stock b:2228, debt: [{'loan': 'yes', 'amount': 1126548.1084998674, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:01:06,975 - Stocklogger - DEBUG - cash: 685371.9156391885, stock a: 10002, stock b:56018, debt: [{'loan': 'yes', 'amount': 1447427.6769661803, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:01:06,975 - Stocklogger - DEBUG - cash: 1038215.9319146388, stock a: 113103, stock b:3138, debt: [{'loan': 'yes', 'amount': 1661296.989551057, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:01:06,975 - Stocklogger - DEBUG - cash: 351762.02783490426, stock a: 16091, stock b:79435, debt: [{'loan': 'yes', 'amount': 3046867.113718262, 'loan_type': 0, 'repayment_date': 88}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 1888245.8949236819, stock a: 10327, stock b:38953, debt: [{'loan': 'yes', 'amount': 2539684.1551537025, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 738758.3395750547, stock a: 37442, stock b:16356, debt: [{'loan': 'yes', 'amount': 588877.0268161764, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 903846.8053006931, stock a: 9840, stock b:62216, debt: [{'loan': 'yes', 'amount': 272247.0800900984, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 1200063.1459595796, stock a: 49322, stock b:52464, debt: [{'loan': 'yes', 'amount': 2248690.696698797, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 1691016.5817594025, stock a: 23382, stock b:55800, debt: [{'loan': 'yes', 'amount': 3720252.8617382795, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 381567.61739724263, stock a: 60473, stock b:11775, debt: [{'loan': 'yes', 'amount': 706890.974655242, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 1591897.2080128556, stock a: 39833, stock b:53770, debt: [{'loan': 'yes', 'amount': 2592453.6314102993, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 512317.6743849733, stock a: 120357, stock b:17245, debt: [{'loan': 'yes', 'amount': 3298978.1981310975, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 2079073.338761691, stock a: 53506, stock b:4455, debt: [{'loan': 'yes', 'amount': 1985694.0946037376, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:01:06,976 - Stocklogger - DEBUG - cash: 164174.82192236042, stock a: 80068, stock b:12326, debt: [{'loan': 'yes', 'amount': 1465484.5476633466, 'loan_type': 0, 'repayment_date': 66}] +2025-10-24 16:01:06,977 - Stocklogger - DEBUG - cash: 1258500.8148744646, stock a: 88904, stock b:82, debt: [{'loan': 'yes', 'amount': 251836.866664526, 'loan_type': 1, 'repayment_date': 110}] +2025-10-24 16:01:06,977 - Stocklogger - DEBUG - cash: 554365.9271883988, stock a: 80707, stock b:37935, debt: [{'loan': 'yes', 'amount': 68885.58630959607, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:01:06,977 - Stocklogger - DEBUG - cash: 581626.178314395, stock a: 53381, stock b:66540, debt: [{'loan': 'yes', 'amount': 225831.438117875, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:01:06,977 - Stocklogger - DEBUG - cash: 2795238.0381167964, stock a: 6396, stock b:25236, debt: [{'loan': 'yes', 'amount': 1018707.5825887376, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 16:01:06,977 - Stocklogger - DEBUG - cash: 1501163.0277241573, stock a: 12499, stock b:59083, debt: [{'loan': 'yes', 'amount': 4117594.054122464, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:01:06,977 - Stocklogger - DEBUG - cash: 1131709.6212854034, stock a: 24364, stock b:38388, debt: [{'loan': 'yes', 'amount': 1028053.0988321429, 'loan_type': 0, 'repayment_date': 88}] +2025-10-24 16:01:06,977 - Stocklogger - DEBUG - cash: 1488386.895335933, stock a: 57018, stock b:40072, debt: [{'loan': 'yes', 'amount': 2864325.1165557452, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 3118960.4729923964, stock a: 43961, stock b:3507, debt: [{'loan': 'yes', 'amount': 3306277.7691200636, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 3345689.79207097, stock a: 42090, stock b:1505, debt: [{'loan': 'yes', 'amount': 2435801.5242399005, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 983370.8030743587, stock a: 44256, stock b:48426, debt: [{'loan': 'yes', 'amount': 3140128.6205363916, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 2641141.0996722933, stock a: 8952, stock b:41074, debt: [{'loan': 'yes', 'amount': 2644648.182601583, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 474583.873748266, stock a: 22290, stock b:44084, debt: [{'loan': 'yes', 'amount': 1483716.1884894362, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 3378461.255883217, stock a: 5170, stock b:5460, debt: [{'loan': 'yes', 'amount': 3305945.3411300243, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 930518.2335784512, stock a: 28368, stock b:1135, debt: [{'loan': 'yes', 'amount': 1076755.003453944, 'loan_type': 0, 'repayment_date': 132}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 2086016.07393684, stock a: 912, stock b:56287, debt: [{'loan': 'yes', 'amount': 4099283.4300486804, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 1160640.982651459, stock a: 42648, stock b:45833, debt: [{'loan': 'yes', 'amount': 479801.8014833111, 'loan_type': 1, 'repayment_date': 110}] +2025-10-24 16:01:06,978 - Stocklogger - DEBUG - cash: 1724148.288724745, stock a: 50885, stock b:34154, debt: [{'loan': 'yes', 'amount': 2466860.7905180384, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 2010208.553111809, stock a: 10858, stock b:63587, debt: [{'loan': 'yes', 'amount': 1338600.2695924938, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 439688.2534369012, stock a: 22717, stock b:49283, debt: [{'loan': 'yes', 'amount': 2723257.3659007237, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 934343.3464666179, stock a: 122876, stock b:5269, debt: [{'loan': 'yes', 'amount': 380225.68136167503, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 1842545.431433203, stock a: 556, stock b:74041, debt: [{'loan': 'yes', 'amount': 3663408.648505916, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 2086969.5165656733, stock a: 6627, stock b:25840, debt: [{'loan': 'yes', 'amount': 3135920.4672189965, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 3137209.210600154, stock a: 14707, stock b:14249, debt: [{'loan': 'yes', 'amount': 2308494.552392972, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 1677172.31994307, stock a: 35992, stock b:7840, debt: [{'loan': 'yes', 'amount': 2215768.5136948316, 'loan_type': 0, 'repayment_date': 66}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 404120.13235807675, stock a: 59429, stock b:22196, debt: [{'loan': 'yes', 'amount': 2797066.470939952, 'loan_type': 0, 'repayment_date': 132}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 2740665.13351626, stock a: 4524, stock b:40267, debt: [{'loan': 'yes', 'amount': 2058530.0179468675, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 2732821.9780123597, stock a: 41669, stock b:9311, debt: [{'loan': 'yes', 'amount': 1694554.2698690358, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:01:06,979 - Stocklogger - DEBUG - cash: 2938783.324487879, stock a: 7666, stock b:33973, debt: [{'loan': 'yes', 'amount': 786137.115446891, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - cash: 347130.02518974134, stock a: 50732, stock b:21460, debt: [{'loan': 'yes', 'amount': 1986967.7386120055, 'loan_type': 1, 'repayment_date': 176}] +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - cash: 2108144.5904003354, stock a: 21849, stock b:37594, debt: [{'loan': 'yes', 'amount': 3822317.8379383706, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - cash: 2065479.989068924, stock a: 51412, stock b:9454, debt: [{'loan': 'yes', 'amount': 1189901.0164871477, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - cash: 317190.88872898207, stock a: 42815, stock b:45707, debt: [{'loan': 'yes', 'amount': 3074568.178822307, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - cash: 989100.3255816388, stock a: 58638, stock b:49933, debt: [{'loan': 'yes', 'amount': 2890194.5876025897, 'loan_type': 0, 'repayment_date': 176}] +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - cash: 650920.7444960513, stock a: 55103, stock b:59125, debt: [{'loan': 'yes', 'amount': 4061441.757320525, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - --------Simulation Start!-------- +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - --------DAY 1--------- +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,980 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,981 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,981 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,981 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 16:01:06,981 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,981 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,981 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,981 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,981 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,981 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 16:01:06,981 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,981 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,981 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,981 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,981 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,981 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,982 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,982 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,982 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,982 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,982 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,983 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,983 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,983 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,983 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,983 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,983 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,983 - Stocklogger - INFO - INFO: Agent 7 decide not to loan +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,984 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,984 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,984 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,984 - Stocklogger - INFO - INFO: Agent 9 decide not to loan +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,984 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,985 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,985 - Stocklogger - INFO - INFO: Agent 10 decide not to loan +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,985 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,985 - Stocklogger - INFO - INFO: Agent 11 decide not to loan +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,985 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,985 - Stocklogger - INFO - INFO: Agent 12 decide not to loan +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,985 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,986 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,986 - Stocklogger - INFO - INFO: Agent 13 decide not to loan +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,986 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,986 - Stocklogger - INFO - INFO: Agent 14 decide not to loan +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,986 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,986 - Stocklogger - INFO - INFO: Agent 15 decide not to loan +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,986 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,987 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,987 - Stocklogger - INFO - INFO: Agent 16 decide not to loan +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,987 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,987 - Stocklogger - INFO - INFO: Agent 17 decide not to loan +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,987 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,987 - Stocklogger - INFO - INFO: Agent 18 decide not to loan +2025-10-24 16:01:06,987 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,988 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,988 - Stocklogger - INFO - INFO: Agent 19 decide not to loan +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,988 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,988 - Stocklogger - INFO - INFO: Agent 20 decide not to loan +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,988 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,988 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,988 - Stocklogger - INFO - INFO: Agent 21 decide not to loan +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,989 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,989 - Stocklogger - INFO - INFO: Agent 22 decide not to loan +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,989 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,989 - Stocklogger - INFO - INFO: Agent 23 decide not to loan +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,989 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,990 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,990 - Stocklogger - INFO - INFO: Agent 24 decide not to loan +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,990 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,990 - Stocklogger - INFO - INFO: Agent 25 decide not to loan +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,990 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,990 - Stocklogger - INFO - INFO: Agent 26 decide not to loan +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,990 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,991 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,991 - Stocklogger - INFO - INFO: Agent 27 decide not to loan +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,991 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,991 - Stocklogger - INFO - INFO: Agent 28 decide not to loan +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,991 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,991 - Stocklogger - INFO - INFO: Agent 29 decide not to loan +2025-10-24 16:01:06,991 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,992 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,992 - Stocklogger - INFO - INFO: Agent 30 decide not to loan +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,992 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,992 - Stocklogger - INFO - INFO: Agent 31 decide not to loan +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,992 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,992 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,992 - Stocklogger - INFO - INFO: Agent 32 decide not to loan +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,993 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,993 - Stocklogger - INFO - INFO: Agent 33 decide not to loan +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,993 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,993 - Stocklogger - INFO - INFO: Agent 34 decide not to loan +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,993 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,994 - Stocklogger - INFO - INFO: Agent 35 decide not to loan +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,994 - Stocklogger - INFO - INFO: Agent 36 decide not to loan +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,994 - Stocklogger - INFO - INFO: Agent 37 decide not to loan +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,994 - Stocklogger - INFO - INFO: Agent 38 decide not to loan +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,994 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,995 - Stocklogger - INFO - INFO: Agent 39 decide not to loan +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,995 - Stocklogger - INFO - INFO: Agent 40 decide not to loan +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,995 - Stocklogger - INFO - INFO: Agent 41 decide not to loan +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,995 - Stocklogger - INFO - INFO: Agent 42 decide not to loan +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,995 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,995 - Stocklogger - INFO - INFO: Agent 43 decide not to loan +2025-10-24 16:01:06,995 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,996 - Stocklogger - INFO - INFO: Agent 44 decide not to loan +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,996 - Stocklogger - INFO - INFO: Agent 45 decide not to loan +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,996 - Stocklogger - INFO - INFO: Agent 46 decide not to loan +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,996 - Stocklogger - INFO - INFO: Agent 47 decide not to loan +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,996 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,997 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,997 - Stocklogger - INFO - INFO: Agent 48 decide not to loan +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 16:01:06,997 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,997 - Stocklogger - INFO - INFO: Agent 49 decide not to loan +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - SESSION 1 +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,997 - Stocklogger - DEBUG - Wrong json content in response: None +2025-10-24 16:01:06,997 - Stocklogger - WARNING - WARNING: Action format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 16:01:06,997 - Stocklogger - INFO - INFO: Agent 5 decide not to action +2025-10-24 16:01:46,078 - Stocklogger - DEBUG - Agents initial... +2025-10-24 16:01:46,078 - Stocklogger - DEBUG - cash: 373650.6281840257, stock a: 8562, stock b:71974, debt: [{'loan': 'yes', 'amount': 393379.9966567142, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 241402.44972598867, stock a: 2869, stock b:110330, debt: [{'loan': 'yes', 'amount': 3587919.5666463315, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 2355978.2095771716, stock a: 29239, stock b:41406, debt: [{'loan': 'yes', 'amount': 806953.6833873591, 'loan_type': 1, 'repayment_date': 132}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 246592.78559864327, stock a: 97837, stock b:29542, debt: [{'loan': 'yes', 'amount': 2612292.006069916, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 2743272.1613059584, stock a: 33810, stock b:8065, debt: [{'loan': 'yes', 'amount': 3886437.10824315, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 1896682.917764469, stock a: 16387, stock b:51514, debt: [{'loan': 'yes', 'amount': 4278859.69823394, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 1607463.1981898812, stock a: 30315, stock b:47886, debt: [{'loan': 'yes', 'amount': 3279629.375595889, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 1185526.2911452674, stock a: 9988, stock b:66106, debt: [{'loan': 'yes', 'amount': 2186251.4824740347, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 784677.7632543045, stock a: 1679, stock b:59223, debt: [{'loan': 'yes', 'amount': 2056317.1522175323, 'loan_type': 1, 'repayment_date': 44}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 1420985.357521466, stock a: 51365, stock b:43730, debt: [{'loan': 'yes', 'amount': 3553084.5086503415, 'loan_type': 1, 'repayment_date': 132}] +2025-10-24 16:01:46,079 - Stocklogger - DEBUG - cash: 513924.2446360565, stock a: 20520, stock b:63188, debt: [{'loan': 'yes', 'amount': 2115963.277558194, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 297805.97994902846, stock a: 6712, stock b:23504, debt: [{'loan': 'yes', 'amount': 130405.48311251732, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 1462754.2470701349, stock a: 20512, stock b:62773, debt: [{'loan': 'yes', 'amount': 2640215.4378540637, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 2625570.59952009, stock a: 31177, stock b:10708, debt: [{'loan': 'yes', 'amount': 2741625.4466739264, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 517183.4801187919, stock a: 80087, stock b:20175, debt: [{'loan': 'yes', 'amount': 2311587.7743840874, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 190820.47460838358, stock a: 50233, stock b:52773, debt: [{'loan': 'yes', 'amount': 1153712.419697656, 'loan_type': 1, 'repayment_date': 110}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 458274.1077175917, stock a: 88567, stock b:20792, debt: [{'loan': 'yes', 'amount': 537239.8099977027, 'loan_type': 0, 'repayment_date': 88}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 651025.957736654, stock a: 13229, stock b:83028, debt: [{'loan': 'yes', 'amount': 2106012.2692034873, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 895104.0363725676, stock a: 48226, stock b:7271, debt: [{'loan': 'yes', 'amount': 1801430.3294217826, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 41151.84400920902, stock a: 114636, stock b:26168, debt: [{'loan': 'yes', 'amount': 322959.3719096091, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 16:01:46,080 - Stocklogger - DEBUG - cash: 245976.44548938258, stock a: 25006, stock b:65069, debt: [{'loan': 'yes', 'amount': 785755.347935212, 'loan_type': 1, 'repayment_date': 176}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 3924098.578536076, stock a: 6529, stock b:20883, debt: [{'loan': 'yes', 'amount': 1199185.2862560577, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 1864032.5792950368, stock a: 67494, stock b:16708, debt: [{'loan': 'yes', 'amount': 2160161.152670085, 'loan_type': 0, 'repayment_date': 176}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 3645132.4369381582, stock a: 26415, stock b:3443, debt: [{'loan': 'yes', 'amount': 2705694.326796012, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 540035.6069702733, stock a: 56663, stock b:39453, debt: [{'loan': 'yes', 'amount': 2591537.9538860195, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 1247005.5196681258, stock a: 88440, stock b:13641, debt: [{'loan': 'yes', 'amount': 2168173.634752199, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 3152942.8976946585, stock a: 38498, stock b:1743, debt: [{'loan': 'yes', 'amount': 1823331.5094606855, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 1812938.17082467, stock a: 8491, stock b:22188, debt: [{'loan': 'yes', 'amount': 514710.7883921204, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 1077479.5470072434, stock a: 45312, stock b:41761, debt: [{'loan': 'yes', 'amount': 754350.1795840418, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 655819.3409927532, stock a: 71240, stock b:27173, debt: [{'loan': 'yes', 'amount': 3846976.516876182, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:01:46,081 - Stocklogger - DEBUG - cash: 1155203.7479417755, stock a: 3481, stock b:65249, debt: [{'loan': 'yes', 'amount': 190679.67763577332, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 7696.716478574616, stock a: 97231, stock b:14333, debt: [{'loan': 'yes', 'amount': 2630983.3009143453, 'loan_type': 0, 'repayment_date': 176}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 1688688.400554284, stock a: 25026, stock b:53274, debt: [{'loan': 'yes', 'amount': 2382144.8613954936, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 405337.29915068706, stock a: 9997, stock b:61975, debt: [{'loan': 'yes', 'amount': 401645.5473985636, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 1184408.963595549, stock a: 64776, stock b:3214, debt: [{'loan': 'yes', 'amount': 1833516.72604896, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 229325.8038933721, stock a: 49082, stock b:52182, debt: [{'loan': 'yes', 'amount': 3060570.751422938, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 388334.3377403364, stock a: 130837, stock b:4591, debt: [{'loan': 'yes', 'amount': 1628491.3062600852, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 2157130.6616506064, stock a: 17795, stock b:56858, debt: [{'loan': 'yes', 'amount': 3421268.9239088586, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 1667411.1280422283, stock a: 43965, stock b:36124, debt: [{'loan': 'yes', 'amount': 1144734.0866081724, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 1807954.2023783473, stock a: 47426, stock b:25492, debt: [{'loan': 'yes', 'amount': 2943916.2474611197, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 1611257.9796971087, stock a: 97258, stock b:2498, debt: [{'loan': 'yes', 'amount': 1560132.5133121153, 'loan_type': 1, 'repayment_date': 176}] +2025-10-24 16:01:46,082 - Stocklogger - DEBUG - cash: 818036.7727530879, stock a: 60881, stock b:50743, debt: [{'loan': 'yes', 'amount': 2097181.035490842, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - cash: 86543.98272122999, stock a: 157041, stock b:871, debt: [{'loan': 'yes', 'amount': 173851.19564670813, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - cash: 1203702.8050833654, stock a: 65968, stock b:20781, debt: [{'loan': 'yes', 'amount': 2543327.978930707, 'loan_type': 0, 'repayment_date': 132}] +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - cash: 907855.0306006272, stock a: 82599, stock b:10086, debt: [{'loan': 'yes', 'amount': 709983.316017187, 'loan_type': 0, 'repayment_date': 66}] +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - cash: 1720741.707021373, stock a: 49222, stock b:33240, debt: [{'loan': 'yes', 'amount': 4187925.8575181155, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - cash: 274016.05641823314, stock a: 108771, stock b:5054, debt: [{'loan': 'yes', 'amount': 94739.60824852635, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - cash: 242426.06841314962, stock a: 85304, stock b:26285, debt: [{'loan': 'yes', 'amount': 1215167.1959775372, 'loan_type': 1, 'repayment_date': 132}] +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - cash: 508525.1820933023, stock a: 109844, stock b:13694, debt: [{'loan': 'yes', 'amount': 1303598.163459006, 'loan_type': 0, 'repayment_date': 66}] +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - cash: 2426292.2688790876, stock a: 2712, stock b:44215, debt: [{'loan': 'yes', 'amount': 2326498.794975514, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - --------Simulation Start!-------- +2025-10-24 16:01:46,083 - Stocklogger - DEBUG - --------DAY 1--------- +2025-10-24 16:02:13,866 - Stocklogger - DEBUG - Agents initial... +2025-10-24 16:02:13,866 - Stocklogger - DEBUG - cash: 3241305.7616430353, stock a: 9505, stock b:21166, debt: [{'loan': 'yes', 'amount': 2560310.425855771, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 2832435.2892016787, stock a: 969, stock b:14202, debt: [{'loan': 'yes', 'amount': 787973.9544929742, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 369326.11648743117, stock a: 56731, stock b:52949, debt: [{'loan': 'yes', 'amount': 3154064.7257214542, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 544110.1880215015, stock a: 42806, stock b:73956, debt: [{'loan': 'yes', 'amount': 498847.4966436812, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 134498.0148643371, stock a: 48103, stock b:10335, debt: [{'loan': 'yes', 'amount': 754796.2517106105, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 1150947.6745369968, stock a: 37448, stock b:66118, debt: [{'loan': 'yes', 'amount': 132231.59432673547, 'loan_type': 0, 'repayment_date': 66}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 600844.274826663, stock a: 56508, stock b:48434, debt: [{'loan': 'yes', 'amount': 1451109.021552235, 'loan_type': 0, 'repayment_date': 132}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 545933.8472576119, stock a: 64394, stock b:21647, debt: [{'loan': 'yes', 'amount': 2507069.4226144957, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 330349.2237047201, stock a: 77106, stock b:46885, debt: [{'loan': 'yes', 'amount': 2185456.9156079497, 'loan_type': 1, 'repayment_date': 176}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 377771.32687154156, stock a: 107843, stock b:17112, debt: [{'loan': 'yes', 'amount': 1936674.2232387152, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 2610105.132765684, stock a: 20291, stock b:16206, debt: [{'loan': 'yes', 'amount': 3759936.84563272, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:02:13,867 - Stocklogger - DEBUG - cash: 287135.13996835524, stock a: 5158, stock b:49490, debt: [{'loan': 'yes', 'amount': 1234582.4198058592, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 16:02:13,868 - Stocklogger - DEBUG - cash: 833139.0953374524, stock a: 25751, stock b:23866, debt: [{'loan': 'yes', 'amount': 413344.198761752, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:02:13,868 - Stocklogger - DEBUG - cash: 121342.20616192515, stock a: 94769, stock b:15066, debt: [{'loan': 'yes', 'amount': 1474833.2386400036, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:02:13,868 - Stocklogger - DEBUG - cash: 1446396.6195487422, stock a: 29283, stock b:58741, debt: [{'loan': 'yes', 'amount': 3107495.3802328566, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:02:13,868 - Stocklogger - DEBUG - cash: 1432058.1076891397, stock a: 14382, stock b:41527, debt: [{'loan': 'yes', 'amount': 1117479.5908417378, 'loan_type': 2, 'repayment_date': 198}] +2025-10-24 16:02:13,868 - Stocklogger - DEBUG - cash: 1436571.930304665, stock a: 45005, stock b:28299, debt: [{'loan': 'yes', 'amount': 2265584.205095444, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:02:13,868 - Stocklogger - DEBUG - cash: 3662788.688405949, stock a: 29751, stock b:1498, debt: [{'loan': 'yes', 'amount': 2563099.017452964, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:02:13,868 - Stocklogger - DEBUG - cash: 2693811.0947996466, stock a: 4659, stock b:39164, debt: [{'loan': 'yes', 'amount': 1496645.6781074516, 'loan_type': 0, 'repayment_date': 66}] +2025-10-24 16:02:13,868 - Stocklogger - DEBUG - cash: 434867.0868696569, stock a: 27890, stock b:48698, debt: [{'loan': 'yes', 'amount': 701061.4565175988, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:02:13,868 - Stocklogger - DEBUG - cash: 861586.3225097787, stock a: 28071, stock b:47293, debt: [{'loan': 'yes', 'amount': 1824806.107061645, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 2603623.1442432995, stock a: 7282, stock b:5029, debt: [{'loan': 'yes', 'amount': 2320412.881864917, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 2081160.7587537218, stock a: 27372, stock b:31377, debt: [{'loan': 'yes', 'amount': 3885646.198576778, 'loan_type': 1, 'repayment_date': 110}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 1599498.030754007, stock a: 44029, stock b:10527, debt: [{'loan': 'yes', 'amount': 3132463.781313026, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 303947.01774782874, stock a: 53987, stock b:41692, debt: [{'loan': 'yes', 'amount': 2916182.758338165, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 598190.598716124, stock a: 104104, stock b:30659, debt: [{'loan': 'yes', 'amount': 323036.5873980262, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 1765694.6073116625, stock a: 13988, stock b:70172, debt: [{'loan': 'yes', 'amount': 1400543.6569251996, 'loan_type': 1, 'repayment_date': 110}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 3817802.1354195736, stock a: 11774, stock b:4187, debt: [{'loan': 'yes', 'amount': 4221794.728295664, 'loan_type': 2, 'repayment_date': 198}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 329098.90992620203, stock a: 116564, stock b:11014, debt: [{'loan': 'yes', 'amount': 3028541.5386535204, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 1046989.0246972502, stock a: 16512, stock b:26621, debt: [{'loan': 'yes', 'amount': 2554076.011651375, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 938785.6325690935, stock a: 67061, stock b:23498, debt: [{'loan': 'yes', 'amount': 3379907.9567053723, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:02:13,869 - Stocklogger - DEBUG - cash: 2920112.95544967, stock a: 6186, stock b:352, debt: [{'loan': 'yes', 'amount': 2617331.743129435, 'loan_type': 1, 'repayment_date': 44}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 2713728.9603891624, stock a: 58745, stock b:1506, debt: [{'loan': 'yes', 'amount': 2228674.5291780718, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 711018.7283961389, stock a: 47272, stock b:41119, debt: [{'loan': 'yes', 'amount': 688500.9037699802, 'loan_type': 1, 'repayment_date': 44}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 1915227.3097308436, stock a: 3424, stock b:43453, debt: [{'loan': 'yes', 'amount': 1935710.0562703505, 'loan_type': 0, 'repayment_date': 132}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 2668296.1877344614, stock a: 12378, stock b:34580, debt: [{'loan': 'yes', 'amount': 3108217.102113971, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 2969977.1524676722, stock a: 2612, stock b:33796, debt: [{'loan': 'yes', 'amount': 2556158.527789055, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 1057705.08316123, stock a: 93318, stock b:11624, debt: [{'loan': 'yes', 'amount': 2097374.8974524685, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 885489.8136799483, stock a: 76996, stock b:37849, debt: [{'loan': 'yes', 'amount': 4585834.620929422, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 2518385.733625818, stock a: 11019, stock b:11045, debt: [{'loan': 'yes', 'amount': 2595546.188525732, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 2197788.6181010846, stock a: 14825, stock b:489, debt: [{'loan': 'yes', 'amount': 688034.6094768886, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 762744.8267264786, stock a: 20719, stock b:57951, debt: [{'loan': 'yes', 'amount': 3144850.145011087, 'loan_type': 1, 'repayment_date': 44}] +2025-10-24 16:02:13,870 - Stocklogger - DEBUG - cash: 1419496.4962941925, stock a: 18299, stock b:57811, debt: [{'loan': 'yes', 'amount': 350051.6942597548, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:02:13,871 - Stocklogger - DEBUG - cash: 1005449.5745970849, stock a: 56671, stock b:31214, debt: [{'loan': 'yes', 'amount': 127641.33374126186, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:02:13,871 - Stocklogger - DEBUG - cash: 1023901.1513987512, stock a: 12978, stock b:88138, debt: [{'loan': 'yes', 'amount': 2779779.511352364, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:02:13,871 - Stocklogger - DEBUG - cash: 441186.63829025015, stock a: 44502, stock b:76726, debt: [{'loan': 'yes', 'amount': 3770768.6189312343, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 16:02:13,871 - Stocklogger - DEBUG - cash: 1171282.3210386364, stock a: 51095, stock b:44668, debt: [{'loan': 'yes', 'amount': 2872798.073698214, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:02:13,871 - Stocklogger - DEBUG - cash: 407827.99805787863, stock a: 50792, stock b:52348, debt: [{'loan': 'yes', 'amount': 2584397.3155022394, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 16:02:13,871 - Stocklogger - DEBUG - cash: 362215.28933724045, stock a: 63540, stock b:37169, debt: [{'loan': 'yes', 'amount': 697897.4852055626, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:02:13,871 - Stocklogger - DEBUG - cash: 1951277.6130588055, stock a: 25507, stock b:29626, debt: [{'loan': 'yes', 'amount': 3422287.040756295, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:02:13,871 - Stocklogger - DEBUG - --------Simulation Start!-------- +2025-10-24 16:02:13,871 - Stocklogger - DEBUG - --------DAY 1--------- +2025-10-24 16:02:39,741 - Stocklogger - DEBUG - Agents initial... +2025-10-24 16:02:39,741 - Stocklogger - DEBUG - cash: 1928822.4034324202, stock a: 11112, stock b:1789, debt: [{'loan': 'yes', 'amount': 1353555.2123407258, 'loan_type': 0, 'repayment_date': 88}] +2025-10-24 16:02:39,741 - Stocklogger - DEBUG - cash: 361845.6118707353, stock a: 35895, stock b:85230, debt: [{'loan': 'yes', 'amount': 2314215.76970614, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:02:39,741 - Stocklogger - DEBUG - cash: 733945.2243413575, stock a: 62571, stock b:21572, debt: [{'loan': 'yes', 'amount': 2731073.4008336114, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:02:39,741 - Stocklogger - DEBUG - cash: 1231297.450667259, stock a: 62519, stock b:11515, debt: [{'loan': 'yes', 'amount': 690314.8154552163, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:02:39,741 - Stocklogger - DEBUG - cash: 2477513.3729000464, stock a: 56062, stock b:12956, debt: [{'loan': 'yes', 'amount': 3836402.6562854773, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:02:39,741 - Stocklogger - DEBUG - cash: 1402563.9691401836, stock a: 20334, stock b:14175, debt: [{'loan': 'yes', 'amount': 2224444.169235126, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 1232922.842345564, stock a: 29165, stock b:55507, debt: [{'loan': 'yes', 'amount': 2767306.876232301, 'loan_type': 0, 'repayment_date': 66}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 236880.6695627973, stock a: 46175, stock b:55737, debt: [{'loan': 'yes', 'amount': 562447.8760959578, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 3071607.2950842693, stock a: 5533, stock b:28193, debt: [{'loan': 'yes', 'amount': 470845.1447814238, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 862672.8037234566, stock a: 53466, stock b:52019, debt: [{'loan': 'yes', 'amount': 136308.27773426447, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 3708889.9008733523, stock a: 395, stock b:21041, debt: [{'loan': 'yes', 'amount': 4347568.40526109, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 1589149.391092387, stock a: 37326, stock b:43040, debt: [{'loan': 'yes', 'amount': 2352962.830493251, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 1637881.046243876, stock a: 79417, stock b:1850, debt: [{'loan': 'yes', 'amount': 1951959.3733816599, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 942378.2608059334, stock a: 25213, stock b:32181, debt: [{'loan': 'yes', 'amount': 1188278.8555647694, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 669747.544081728, stock a: 130328, stock b:8912, debt: [{'loan': 'yes', 'amount': 1156979.413412216, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 314951.6772433331, stock a: 9026, stock b:107266, debt: [{'loan': 'yes', 'amount': 1574649.0517644223, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 1393216.0281814076, stock a: 20109, stock b:67953, debt: [{'loan': 'yes', 'amount': 2660009.299148536, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 16:02:39,742 - Stocklogger - DEBUG - cash: 417877.8763099217, stock a: 53308, stock b:70509, debt: [{'loan': 'yes', 'amount': 4453276.269655228, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:02:39,743 - Stocklogger - DEBUG - cash: 3396.7675047863468, stock a: 57566, stock b:64806, debt: [{'loan': 'yes', 'amount': 2679852.7302118903, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:02:39,743 - Stocklogger - DEBUG - cash: 1876539.388673526, stock a: 68904, stock b:15143, debt: [{'loan': 'yes', 'amount': 1702518.6056482494, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 16:02:39,743 - Stocklogger - DEBUG - cash: 2809379.539712329, stock a: 11579, stock b:36992, debt: [{'loan': 'yes', 'amount': 4236393.538161026, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:02:39,743 - Stocklogger - DEBUG - cash: 1586015.006365451, stock a: 4467, stock b:33863, debt: [{'loan': 'yes', 'amount': 2484411.831169366, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:02:39,743 - Stocklogger - DEBUG - cash: 1338037.0327151692, stock a: 76488, stock b:18417, debt: [{'loan': 'yes', 'amount': 3408258.4264357626, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:02:39,743 - Stocklogger - DEBUG - cash: 332244.1627785383, stock a: 94099, stock b:35292, debt: [{'loan': 'yes', 'amount': 3016161.589419675, 'loan_type': 1, 'repayment_date': 44}] +2025-10-24 16:02:39,743 - Stocklogger - DEBUG - cash: 2285.2583999560807, stock a: 13620, stock b:67694, debt: [{'loan': 'yes', 'amount': 1763823.0358459062, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:02:39,743 - Stocklogger - DEBUG - cash: 2096259.919945081, stock a: 30124, stock b:39428, debt: [{'loan': 'yes', 'amount': 1054117.2070037352, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:02:39,743 - Stocklogger - DEBUG - cash: 3376873.5451470274, stock a: 33424, stock b:3071, debt: [{'loan': 'yes', 'amount': 2157329.0940211066, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 1263239.592453464, stock a: 21265, stock b:64735, debt: [{'loan': 'yes', 'amount': 423557.1320892023, 'loan_type': 0, 'repayment_date': 88}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 442394.47514923924, stock a: 79419, stock b:19099, debt: [{'loan': 'yes', 'amount': 753041.2407047588, 'loan_type': 1, 'repayment_date': 110}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 599404.5701718265, stock a: 89854, stock b:30588, debt: [{'loan': 'yes', 'amount': 416973.7758336245, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 47462.13982629477, stock a: 66100, stock b:71992, debt: [{'loan': 'yes', 'amount': 3801828.2665146706, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 2757536.7475505746, stock a: 17239, stock b:34942, debt: [{'loan': 'yes', 'amount': 4187268.990301033, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 2305475.441287667, stock a: 21188, stock b:46737, debt: [{'loan': 'yes', 'amount': 2739935.8354435484, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 3106332.6122098058, stock a: 50833, stock b:3993, debt: [{'loan': 'yes', 'amount': 456931.0919926589, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 759394.5342260561, stock a: 62891, stock b:37010, debt: [{'loan': 'yes', 'amount': 436736.7129044014, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 189342.83649883143, stock a: 94071, stock b:46533, debt: [{'loan': 'yes', 'amount': 2909925.5973625323, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 16:02:39,744 - Stocklogger - DEBUG - cash: 2074627.1024490504, stock a: 52798, stock b:1360, debt: [{'loan': 'yes', 'amount': 3214281.8753098696, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 1399110.5071865073, stock a: 5175, stock b:27125, debt: [{'loan': 'yes', 'amount': 1269482.6829855805, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 303905.10620089964, stock a: 91376, stock b:18106, debt: [{'loan': 'yes', 'amount': 2795740.8869800502, 'loan_type': 0, 'repayment_date': 88}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 3097740.766596114, stock a: 13325, stock b:21589, debt: [{'loan': 'yes', 'amount': 3749074.919745185, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 1701829.3641118214, stock a: 7718, stock b:3571, debt: [{'loan': 'yes', 'amount': 1570709.8944799437, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 472185.2351556688, stock a: 628, stock b:70587, debt: [{'loan': 'yes', 'amount': 373912.35418813064, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 855190.8529531471, stock a: 34069, stock b:62470, debt: [{'loan': 'yes', 'amount': 1556687.2506208478, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 2104471.9774432546, stock a: 73778, stock b:9619, debt: [{'loan': 'yes', 'amount': 2359320.985823238, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 1218153.5076098903, stock a: 19747, stock b:55947, debt: [{'loan': 'yes', 'amount': 1667617.6788238923, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 1250893.894050185, stock a: 21212, stock b:71991, debt: [{'loan': 'yes', 'amount': 379166.4909928716, 'loan_type': 1, 'repayment_date': 110}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 1116910.126798003, stock a: 17665, stock b:60597, debt: [{'loan': 'yes', 'amount': 1347591.783113098, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 2917606.6858802484, stock a: 39897, stock b:17164, debt: [{'loan': 'yes', 'amount': 1035553.4058200206, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:02:39,745 - Stocklogger - DEBUG - cash: 1376022.460285174, stock a: 3318, stock b:84875, debt: [{'loan': 'yes', 'amount': 4316529.520317309, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:02:39,746 - Stocklogger - DEBUG - cash: 1051472.9898475434, stock a: 102533, stock b:15825, debt: [{'loan': 'yes', 'amount': 4446001.431283527, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:02:39,746 - Stocklogger - DEBUG - --------Simulation Start!-------- +2025-10-24 16:02:39,746 - Stocklogger - DEBUG - --------DAY 1--------- +2025-10-24 16:03:08,179 - Stocklogger - DEBUG - Agents initial... +2025-10-24 16:03:08,179 - Stocklogger - DEBUG - cash: 2628014.0188357686, stock a: 10327, stock b:51450, debt: [{'loan': 'yes', 'amount': 4644250.344831375, 'loan_type': 0, 'repayment_date': 176}] +2025-10-24 16:03:08,179 - Stocklogger - DEBUG - cash: 1575628.5029605287, stock a: 55775, stock b:19139, debt: [{'loan': 'yes', 'amount': 248107.81677528704, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 1227637.4636867049, stock a: 59483, stock b:23775, debt: [{'loan': 'yes', 'amount': 1296563.5203279573, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 876039.8885004605, stock a: 62761, stock b:9595, debt: [{'loan': 'yes', 'amount': 993297.4995183136, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 2423664.477855701, stock a: 6994, stock b:22976, debt: [{'loan': 'yes', 'amount': 2704921.7909743655, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 2058925.3147623339, stock a: 5385, stock b:59647, debt: [{'loan': 'yes', 'amount': 2474371.3589736633, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 713992.8207120327, stock a: 78841, stock b:23863, debt: [{'loan': 'yes', 'amount': 2277298.0493941354, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 844368.0379330548, stock a: 86290, stock b:4977, debt: [{'loan': 'yes', 'amount': 1645818.8215394858, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 987615.5422011773, stock a: 86530, stock b:5124, debt: [{'loan': 'yes', 'amount': 2195899.0027785487, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 1347021.3634425905, stock a: 46810, stock b:20942, debt: [{'loan': 'yes', 'amount': 774982.5160154855, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 3586417.6998416516, stock a: 41883, stock b:1309, debt: [{'loan': 'yes', 'amount': 4044021.3525270354, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:03:08,180 - Stocklogger - DEBUG - cash: 706319.9926034353, stock a: 45870, stock b:54234, debt: [{'loan': 'yes', 'amount': 2337198.1439261697, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 438575.62570376595, stock a: 72074, stock b:29299, debt: [{'loan': 'yes', 'amount': 1794451.6248822622, 'loan_type': 0, 'repayment_date': 176}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 2236784.1836264594, stock a: 54846, stock b:16176, debt: [{'loan': 'yes', 'amount': 2736779.6910646344, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 446206.26214482874, stock a: 121338, stock b:13651, debt: [{'loan': 'yes', 'amount': 2172631.857682243, 'loan_type': 0, 'repayment_date': 66}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 719374.0988250902, stock a: 21497, stock b:39774, debt: [{'loan': 'yes', 'amount': 1145542.5654920104, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 1207302.9943838913, stock a: 76376, stock b:13328, debt: [{'loan': 'yes', 'amount': 1267120.38334236, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 816314.3075697938, stock a: 71737, stock b:18823, debt: [{'loan': 'yes', 'amount': 894327.6118896615, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 1107587.8722931321, stock a: 2229, stock b:88834, debt: [{'loan': 'yes', 'amount': 3738821.883965082, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 21716.744226327744, stock a: 4303, stock b:72189, debt: [{'loan': 'yes', 'amount': 451868.2694524656, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 421469.52530078887, stock a: 104721, stock b:32170, debt: [{'loan': 'yes', 'amount': 1496311.2565241754, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 287486.30799195654, stock a: 145918, stock b:1639, debt: [{'loan': 'yes', 'amount': 2270831.2720787823, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 316971.3011806985, stock a: 24446, stock b:18812, debt: [{'loan': 'yes', 'amount': 1296399.8888750693, 'loan_type': 1, 'repayment_date': 110}] +2025-10-24 16:03:08,181 - Stocklogger - DEBUG - cash: 2632833.899492248, stock a: 7636, stock b:36647, debt: [{'loan': 'yes', 'amount': 945986.2117676276, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:03:08,182 - Stocklogger - DEBUG - cash: 1161623.0978763031, stock a: 68785, stock b:27672, debt: [{'loan': 'yes', 'amount': 745326.2318084275, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:03:08,182 - Stocklogger - DEBUG - cash: 21364.550522861013, stock a: 11743, stock b:34839, debt: [{'loan': 'yes', 'amount': 300190.49708657985, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:03:08,182 - Stocklogger - DEBUG - cash: 612637.0268820719, stock a: 3703, stock b:67355, debt: [{'loan': 'yes', 'amount': 799038.436524987, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:03:08,182 - Stocklogger - DEBUG - cash: 1058622.835393662, stock a: 22116, stock b:51633, debt: [{'loan': 'yes', 'amount': 1124803.1466094942, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:03:08,182 - Stocklogger - DEBUG - cash: 1640874.4919648704, stock a: 92427, stock b:6819, debt: [{'loan': 'yes', 'amount': 3958827.9959644545, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:03:08,182 - Stocklogger - DEBUG - cash: 292805.3420100607, stock a: 19988, stock b:33463, debt: [{'loan': 'yes', 'amount': 1807345.163207969, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:03:08,182 - Stocklogger - DEBUG - cash: 658341.1215092916, stock a: 23446, stock b:17238, debt: [{'loan': 'yes', 'amount': 336032.5427131927, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:03:08,182 - Stocklogger - DEBUG - cash: 77420.85862141756, stock a: 121133, stock b:20036, debt: [{'loan': 'yes', 'amount': 1657756.0315958185, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 1843464.34088831, stock a: 67740, stock b:4463, debt: [{'loan': 'yes', 'amount': 3380180.7322104005, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 2687463.881965043, stock a: 61573, stock b:8071, debt: [{'loan': 'yes', 'amount': 1039451.3902710556, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 227863.87421731447, stock a: 142838, stock b:12049, debt: [{'loan': 'yes', 'amount': 701324.6652858662, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 202964.30698378332, stock a: 76997, stock b:58465, debt: [{'loan': 'yes', 'amount': 2512424.2159376773, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 2264824.37663207, stock a: 48849, stock b:19842, debt: [{'loan': 'yes', 'amount': 446011.750864555, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 1391693.4898696076, stock a: 72652, stock b:22587, debt: [{'loan': 'yes', 'amount': 3233433.656341099, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 1369311.3805324924, stock a: 37955, stock b:37921, debt: [{'loan': 'yes', 'amount': 3380428.454978519, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 445591.1288279618, stock a: 12342, stock b:21094, debt: [{'loan': 'yes', 'amount': 821227.6764383203, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 2053585.3048055856, stock a: 54001, stock b:20496, debt: [{'loan': 'yes', 'amount': 644438.019813347, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 2636859.4626757107, stock a: 33791, stock b:29395, debt: [{'loan': 'yes', 'amount': 1888605.025988991, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 906094.7229950811, stock a: 15254, stock b:45495, debt: [{'loan': 'yes', 'amount': 1281806.7440144627, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 16:03:08,183 - Stocklogger - DEBUG - cash: 2003.5962026365705, stock a: 29157, stock b:89602, debt: [{'loan': 'yes', 'amount': 2059739.337042018, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:03:08,184 - Stocklogger - DEBUG - cash: 2446405.2716107606, stock a: 20380, stock b:46685, debt: [{'loan': 'yes', 'amount': 3803692.8822117234, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:03:08,184 - Stocklogger - DEBUG - cash: 3035444.872180986, stock a: 21826, stock b:9617, debt: [{'loan': 'yes', 'amount': 1718121.5058540478, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:03:08,184 - Stocklogger - DEBUG - cash: 949853.0306795188, stock a: 12980, stock b:66142, debt: [{'loan': 'yes', 'amount': 1680998.287422193, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:03:08,184 - Stocklogger - DEBUG - cash: 1272593.154430191, stock a: 72053, stock b:18748, debt: [{'loan': 'yes', 'amount': 4148762.489183309, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:03:08,184 - Stocklogger - DEBUG - cash: 382586.6763392249, stock a: 35122, stock b:28133, debt: [{'loan': 'yes', 'amount': 1291883.2802241864, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:03:08,184 - Stocklogger - DEBUG - cash: 20784.725433406482, stock a: 61668, stock b:45837, debt: [{'loan': 'yes', 'amount': 2116301.280517322, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:03:08,184 - Stocklogger - DEBUG - --------Simulation Start!-------- +2025-10-24 16:03:08,184 - Stocklogger - DEBUG - --------DAY 1--------- +2025-10-24 16:03:42,052 - Stocklogger - DEBUG - Agents initial... +2025-10-24 16:03:42,052 - Stocklogger - DEBUG - cash: 164169.43992627776, stock a: 56788, stock b:45620, debt: [{'loan': 'yes', 'amount': 2302979.2312148716, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:03:42,052 - Stocklogger - DEBUG - cash: 147425.03728431277, stock a: 9225, stock b:99296, debt: [{'loan': 'yes', 'amount': 931912.3262590274, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:03:42,052 - Stocklogger - DEBUG - cash: 3094104.8734811316, stock a: 15198, stock b:27234, debt: [{'loan': 'yes', 'amount': 4432112.508195672, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:03:42,053 - Stocklogger - DEBUG - cash: 364325.5214978397, stock a: 19589, stock b:58627, debt: [{'loan': 'yes', 'amount': 2362218.779352973, 'loan_type': 1, 'repayment_date': 44}] +2025-10-24 16:03:42,053 - Stocklogger - DEBUG - cash: 37871.62471707573, stock a: 58514, stock b:58551, debt: [{'loan': 'yes', 'amount': 1537692.652419549, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:03:42,053 - Stocklogger - DEBUG - cash: 205009.80695702997, stock a: 137304, stock b:4590, debt: [{'loan': 'yes', 'amount': 4140968.69933372, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:03:42,053 - Stocklogger - DEBUG - cash: 1556736.2137471747, stock a: 30469, stock b:55214, debt: [{'loan': 'yes', 'amount': 2449404.859397493, 'loan_type': 1, 'repayment_date': 44}] +2025-10-24 16:03:42,053 - Stocklogger - DEBUG - cash: 180167.58321323822, stock a: 36411, stock b:86624, debt: [{'loan': 'yes', 'amount': 3015503.723155795, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:03:42,053 - Stocklogger - DEBUG - cash: 1770587.6965900136, stock a: 4794, stock b:58444, debt: [{'loan': 'yes', 'amount': 93760.32399082024, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:03:42,053 - Stocklogger - DEBUG - cash: 1322091.912241538, stock a: 105750, stock b:4274, debt: [{'loan': 'yes', 'amount': 417399.8263113121, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:03:42,053 - Stocklogger - DEBUG - cash: 572978.7558653044, stock a: 77931, stock b:17302, debt: [{'loan': 'yes', 'amount': 2143016.559448312, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 2920271.412955147, stock a: 9676, stock b:23432, debt: [{'loan': 'yes', 'amount': 1187018.929489086, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 772232.1508928682, stock a: 71384, stock b:47775, debt: [{'loan': 'yes', 'amount': 693721.6729892209, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 831409.7433917978, stock a: 54130, stock b:46613, debt: [{'loan': 'yes', 'amount': 3647464.4809543896, 'loan_type': 1, 'repayment_date': 110}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 1499559.6082636453, stock a: 89634, stock b:50, debt: [{'loan': 'yes', 'amount': 1737644.7280586793, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 1104283.7836374408, stock a: 39041, stock b:47315, debt: [{'loan': 'yes', 'amount': 3465757.199407071, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 1926393.8639541357, stock a: 45280, stock b:7861, debt: [{'loan': 'yes', 'amount': 452395.49322963646, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 2304414.9497516593, stock a: 2652, stock b:845, debt: [{'loan': 'yes', 'amount': 735995.614889367, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 1288298.2748378485, stock a: 57709, stock b:35699, debt: [{'loan': 'yes', 'amount': 1368693.2472897212, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 469753.01202057453, stock a: 48649, stock b:39822, debt: [{'loan': 'yes', 'amount': 1377142.5430046453, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 1132654.4931622462, stock a: 21235, stock b:54255, debt: [{'loan': 'yes', 'amount': 3777183.5273830867, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:03:42,054 - Stocklogger - DEBUG - cash: 1287756.1838525808, stock a: 49462, stock b:33661, debt: [{'loan': 'yes', 'amount': 3416015.933733134, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 3342115.225360849, stock a: 20788, stock b:1151, debt: [{'loan': 'yes', 'amount': 2087875.5291276285, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 1325656.7820787334, stock a: 7996, stock b:30197, debt: [{'loan': 'yes', 'amount': 1733657.9017805692, 'loan_type': 1, 'repayment_date': 132}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 3915096.3153092125, stock a: 16261, stock b:10836, debt: [{'loan': 'yes', 'amount': 1746075.3563335158, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 1345368.125064148, stock a: 10051, stock b:63452, debt: [{'loan': 'yes', 'amount': 568330.3858680138, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 1230358.450502244, stock a: 56372, stock b:27143, debt: [{'loan': 'yes', 'amount': 2261762.2188114845, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 1572463.092586308, stock a: 6094, stock b:46975, debt: [{'loan': 'yes', 'amount': 3402457.106264234, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 740900.8527880656, stock a: 45557, stock b:41248, debt: [{'loan': 'yes', 'amount': 1755039.030827667, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 287849.33975570416, stock a: 10104, stock b:75265, debt: [{'loan': 'yes', 'amount': 634424.9921046047, 'loan_type': 0, 'repayment_date': 176}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 2723749.8841824476, stock a: 11163, stock b:42984, debt: [{'loan': 'yes', 'amount': 1676074.9039477368, 'loan_type': 1, 'repayment_date': 176}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 606541.5780529315, stock a: 97461, stock b:29302, debt: [{'loan': 'yes', 'amount': 3746610.457198696, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:03:42,055 - Stocklogger - DEBUG - cash: 3488191.327772551, stock a: 20446, stock b:7008, debt: [{'loan': 'yes', 'amount': 3111579.4234752296, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 2328407.6037961226, stock a: 64926, stock b:14058, debt: [{'loan': 'yes', 'amount': 3892576.797523147, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 1525115.5244321574, stock a: 85066, stock b:9692, debt: [{'loan': 'yes', 'amount': 738879.1687197338, 'loan_type': 2, 'repayment_date': 198}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 3065964.244959643, stock a: 2471, stock b:46454, debt: [{'loan': 'yes', 'amount': 4705051.91166967, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 1348456.2816599156, stock a: 15458, stock b:63202, debt: [{'loan': 'yes', 'amount': 617432.1558226431, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 387890.34412013646, stock a: 126955, stock b:14706, debt: [{'loan': 'yes', 'amount': 957033.7837665993, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 2655719.5028566434, stock a: 57108, stock b:10030, debt: [{'loan': 'yes', 'amount': 1879918.2143304765, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 1241240.6486391497, stock a: 39953, stock b:37077, debt: [{'loan': 'yes', 'amount': 3660597.4355508103, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 3055113.3510750546, stock a: 5022, stock b:2495, debt: [{'loan': 'yes', 'amount': 1635457.8010551168, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 905489.3965660332, stock a: 52451, stock b:6141, debt: [{'loan': 'yes', 'amount': 1568212.2251023506, 'loan_type': 1, 'repayment_date': 176}] +2025-10-24 16:03:42,056 - Stocklogger - DEBUG - cash: 1002691.8234353882, stock a: 10443, stock b:87239, debt: [{'loan': 'yes', 'amount': 4605164.329210034, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:03:42,057 - Stocklogger - DEBUG - cash: 945616.138451112, stock a: 55741, stock b:30001, debt: [{'loan': 'yes', 'amount': 660279.9804771104, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:03:42,057 - Stocklogger - DEBUG - cash: 572412.9387488991, stock a: 125989, stock b:15189, debt: [{'loan': 'yes', 'amount': 1273615.9639320916, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:03:42,057 - Stocklogger - DEBUG - cash: 3358868.820088406, stock a: 24343, stock b:18039, debt: [{'loan': 'yes', 'amount': 1143267.654184012, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:03:42,057 - Stocklogger - DEBUG - cash: 581015.6847952746, stock a: 135351, stock b:3091, debt: [{'loan': 'yes', 'amount': 3710674.2044452545, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:03:42,057 - Stocklogger - DEBUG - cash: 2075729.2461682125, stock a: 13049, stock b:56135, debt: [{'loan': 'yes', 'amount': 833882.6435993346, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:03:42,057 - Stocklogger - DEBUG - cash: 2463967.8883658196, stock a: 47000, stock b:25225, debt: [{'loan': 'yes', 'amount': 974588.5929889287, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 16:03:42,057 - Stocklogger - DEBUG - cash: 2370482.8722505243, stock a: 44994, stock b:25442, debt: [{'loan': 'yes', 'amount': 3809096.869968763, 'loan_type': 0, 'repayment_date': 176}] +2025-10-24 16:03:42,057 - Stocklogger - DEBUG - --------Simulation Start!-------- +2025-10-24 16:03:42,057 - Stocklogger - DEBUG - --------DAY 1--------- +2025-10-24 16:03:42,344 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:43,444 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:44,445 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:03:44,561 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:45,624 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:46,625 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:03:46,784 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:47,866 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:48,866 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:03:48,965 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:50,049 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:51,049 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:03:51,221 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:52,288 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:53,289 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:03:53,390 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:54,462 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:55,463 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:03:55,576 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:56,641 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:57,642 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:03:57,745 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:58,819 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:03:59,820 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:03:59,926 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:04:01,000 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:04:02,000 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:04:02,279 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:04:03,348 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:04:04,348 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:04:04,451 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:04:05,512 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:04:06,513 - Stocklogger - ERROR - ERROR: OPENAI API FAILED. SKIP THIS INTERACTION. +2025-10-24 16:04:06,624 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:04:07,703 - Stocklogger - WARNING - OpenAI api retry...Error code: 401 - {'error': {'message': "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.", 'type': 'invalid_request_error', 'param': None, 'code': None}} +2025-10-24 16:05:00,006 - Stocklogger - DEBUG - Agents initial... +2025-10-24 16:05:00,007 - Stocklogger - DEBUG - cash: 1988917.0480964796, stock a: 21136, stock b:34884, debt: [{'loan': 'yes', 'amount': 1673170.7586466437, 'loan_type': 1, 'repayment_date': 132}] +2025-10-24 16:05:00,007 - Stocklogger - DEBUG - cash: 1459803.581309611, stock a: 43750, stock b:2873, debt: [{'loan': 'yes', 'amount': 397574.43021949555, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:05:00,007 - Stocklogger - DEBUG - cash: 244183.56880907487, stock a: 80597, stock b:54300, debt: [{'loan': 'yes', 'amount': 1937637.351186417, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:05:00,008 - Stocklogger - DEBUG - cash: 1243171.9624311938, stock a: 50453, stock b:51488, debt: [{'loan': 'yes', 'amount': 52788.80875485448, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:05:00,008 - Stocklogger - DEBUG - cash: 3508409.4973358475, stock a: 17334, stock b:7166, debt: [{'loan': 'yes', 'amount': 2654024.0363341756, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:05:00,008 - Stocklogger - DEBUG - cash: 9095.79506037761, stock a: 17805, stock b:5035, debt: [{'loan': 'yes', 'amount': 259774.0337861626, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:05:00,008 - Stocklogger - DEBUG - cash: 1887657.1429756894, stock a: 52151, stock b:31940, debt: [{'loan': 'yes', 'amount': 1067710.9325647866, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:05:00,008 - Stocklogger - DEBUG - cash: 4030411.1982034235, stock a: 16213, stock b:3332, debt: [{'loan': 'yes', 'amount': 1584628.3357201512, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:05:00,008 - Stocklogger - DEBUG - cash: 945286.2543379775, stock a: 112662, stock b:15436, debt: [{'loan': 'yes', 'amount': 3628426.587963763, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:05:00,008 - Stocklogger - DEBUG - cash: 2117000.5995960594, stock a: 79127, stock b:6032, debt: [{'loan': 'yes', 'amount': 490084.26457937906, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 1044741.6674265697, stock a: 66825, stock b:23264, debt: [{'loan': 'yes', 'amount': 2684270.681079663, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 1932747.0919187772, stock a: 13435, stock b:28825, debt: [{'loan': 'yes', 'amount': 1557687.8489286539, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 499500.7445368155, stock a: 99270, stock b:9346, debt: [{'loan': 'yes', 'amount': 1357654.793854383, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 1147509.9976787146, stock a: 85552, stock b:31860, debt: [{'loan': 'yes', 'amount': 3358846.364683549, 'loan_type': 0, 'repayment_date': 132}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 1250588.7330281434, stock a: 35831, stock b:58907, debt: [{'loan': 'yes', 'amount': 4169876.904271951, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 761008.0676584635, stock a: 96782, stock b:9674, debt: [{'loan': 'yes', 'amount': 3532968.3727121013, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 2051682.696111477, stock a: 31124, stock b:2173, debt: [{'loan': 'yes', 'amount': 1811965.6261296002, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 1527818.1659294765, stock a: 50032, stock b:2892, debt: [{'loan': 'yes', 'amount': 2517934.3182363426, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 467925.4315162273, stock a: 33741, stock b:52345, debt: [{'loan': 'yes', 'amount': 896867.1776059845, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 1472675.1682109507, stock a: 100245, stock b:9007, debt: [{'loan': 'yes', 'amount': 4026302.14034215, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:05:00,009 - Stocklogger - DEBUG - cash: 3094989.873821696, stock a: 24354, stock b:17037, debt: [{'loan': 'yes', 'amount': 3819236.1645509964, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 2478707.864142312, stock a: 15475, stock b:41593, debt: [{'loan': 'yes', 'amount': 3647910.5145067736, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 1355916.5715178112, stock a: 78532, stock b:27755, debt: [{'loan': 'yes', 'amount': 2391101.971838174, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 474870.5771679884, stock a: 49317, stock b:56507, debt: [{'loan': 'yes', 'amount': 1530191.7791637843, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 325090.5521630809, stock a: 94956, stock b:43316, debt: [{'loan': 'yes', 'amount': 2858907.004952354, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 591402.6464781852, stock a: 58753, stock b:21765, debt: [{'loan': 'yes', 'amount': 1279448.8088530176, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 1524487.8217075197, stock a: 30241, stock b:50620, debt: [{'loan': 'yes', 'amount': 3304088.930366407, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 2001887.966356653, stock a: 28496, stock b:19468, debt: [{'loan': 'yes', 'amount': 2558350.8277743096, 'loan_type': 0, 'repayment_date': 176}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 205290.17176719956, stock a: 112057, stock b:5577, debt: [{'loan': 'yes', 'amount': 1253474.9652761235, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 791714.1603169236, stock a: 45010, stock b:53022, debt: [{'loan': 'yes', 'amount': 737252.9683280393, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 358888.4569619416, stock a: 30858, stock b:83258, debt: [{'loan': 'yes', 'amount': 454935.2383744415, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:05:00,010 - Stocklogger - DEBUG - cash: 1050136.9404241107, stock a: 24636, stock b:18264, debt: [{'loan': 'yes', 'amount': 36120.0813065976, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 981889.7327135134, stock a: 2388, stock b:82656, debt: [{'loan': 'yes', 'amount': 36124.22963217732, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 1922667.0487036535, stock a: 45442, stock b:8611, debt: [{'loan': 'yes', 'amount': 2649788.8885399904, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 1612553.1870141434, stock a: 34334, stock b:7508, debt: [{'loan': 'yes', 'amount': 1781377.1240577875, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 176.1241116471357, stock a: 31204, stock b:95813, debt: [{'loan': 'yes', 'amount': 2460392.6112510855, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 397325.3071997634, stock a: 67332, stock b:50214, debt: [{'loan': 'yes', 'amount': 741128.3540101516, 'loan_type': 1, 'repayment_date': 176}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 907931.8167764106, stock a: 86847, stock b:35062, debt: [{'loan': 'yes', 'amount': 462599.48744106217, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 1857178.5631877512, stock a: 55072, stock b:14250, debt: [{'loan': 'yes', 'amount': 1760467.2612432798, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 3398713.249019869, stock a: 38629, stock b:161, debt: [{'loan': 'yes', 'amount': 3891229.1946997982, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 2231675.535563858, stock a: 76578, stock b:6881, debt: [{'loan': 'yes', 'amount': 3129641.473939735, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:05:00,011 - Stocklogger - DEBUG - cash: 322248.68257340224, stock a: 63493, stock b:41877, debt: [{'loan': 'yes', 'amount': 917443.3261516895, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - cash: 469199.4238696262, stock a: 1641, stock b:31874, debt: [{'loan': 'yes', 'amount': 1076738.1819282968, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - cash: 1332944.8148218382, stock a: 37747, stock b:5011, debt: [{'loan': 'yes', 'amount': 1560644.22479062, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - cash: 1630872.3691560244, stock a: 50417, stock b:11093, debt: [{'loan': 'yes', 'amount': 1902464.9271136145, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - cash: 147781.4533594052, stock a: 33366, stock b:39431, debt: [{'loan': 'yes', 'amount': 1893381.9318071876, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - cash: 852782.9985713775, stock a: 50222, stock b:42021, debt: [{'loan': 'yes', 'amount': 2242479.687765445, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - cash: 566597.2020124671, stock a: 54917, stock b:16593, debt: [{'loan': 'yes', 'amount': 2557668.303849497, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - cash: 2013155.4959101833, stock a: 25063, stock b:34226, debt: [{'loan': 'yes', 'amount': 706087.9930631475, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - cash: 239320.2815185075, stock a: 8944, stock b:72148, debt: [{'loan': 'yes', 'amount': 1570958.9571100213, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - --------Simulation Start!-------- +2025-10-24 16:05:00,012 - Stocklogger - DEBUG - --------DAY 1--------- +2025-10-24 16:05:01,594 - Stocklogger - INFO - INFO: Agent 0 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2000000, 'repayment_date': 45} +2025-10-24 16:05:03,427 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 16:05:03,891 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 16:05:05,620 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 16:05:07,855 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 16:05:09,141 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 16:05:10,481 - Stocklogger - INFO - INFO: Agent 6 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1000000, 'repayment_date': 45} +2025-10-24 16:05:11,221 - Stocklogger - INFO - INFO: Agent 7 decide not to loan +2025-10-24 16:05:12,072 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 16:05:12,735 - Stocklogger - INFO - INFO: Agent 9 decide not to loan +2025-10-24 16:05:13,910 - Stocklogger - INFO - INFO: Agent 10 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1295780.986346907, 'repayment_date': 45} +2025-10-24 16:05:15,399 - Stocklogger - INFO - INFO: Agent 11 decide not to loan +2025-10-24 16:05:16,795 - Stocklogger - INFO - INFO: Agent 12 decide not to loan +2025-10-24 16:05:17,293 - Stocklogger - INFO - INFO: Agent 13 decide not to loan +2025-10-24 16:05:18,060 - Stocklogger - INFO - INFO: Agent 14 decide not to loan +2025-10-24 16:05:18,796 - Stocklogger - INFO - INFO: Agent 15 decide not to loan +2025-10-24 16:05:19,477 - Stocklogger - INFO - INFO: Agent 16 decide not to loan +2025-10-24 16:05:20,412 - Stocklogger - INFO - INFO: Agent 17 decide not to loan +2025-10-24 16:05:21,573 - Stocklogger - INFO - INFO: Agent 18 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 500000, 'repayment_date': 45} +2025-10-24 16:05:22,899 - Stocklogger - INFO - INFO: Agent 19 decide not to loan +2025-10-24 16:05:32,103 - Stocklogger - INFO - INFO: Agent 20 decide not to loan +2025-10-24 16:05:32,764 - Stocklogger - INFO - INFO: Agent 21 decide not to loan +2025-10-24 16:05:33,634 - Stocklogger - INFO - INFO: Agent 22 decide not to loan +2025-10-24 16:05:34,421 - Stocklogger - INFO - INFO: Agent 23 decide not to loan +2025-10-24 16:05:36,089 - Stocklogger - INFO - INFO: Agent 24 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2000000, 'repayment_date': 45} +2025-10-24 16:05:37,820 - Stocklogger - INFO - INFO: Agent 25 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 1945143.8376251678, 'repayment_date': 23} +2025-10-24 16:05:38,307 - Stocklogger - INFO - INFO: Agent 26 decide not to loan +2025-10-24 16:05:39,431 - Stocklogger - INFO - INFO: Agent 27 decide not to loan +2025-10-24 16:05:39,938 - Stocklogger - INFO - INFO: Agent 28 decide not to loan +2025-10-24 16:05:40,481 - Stocklogger - INFO - INFO: Agent 29 decide not to loan +2025-10-24 16:05:41,358 - Stocklogger - INFO - INFO: Agent 30 decide not to loan +2025-10-24 16:05:41,889 - Stocklogger - INFO - INFO: Agent 31 decide not to loan +2025-10-24 16:05:42,525 - Stocklogger - INFO - INFO: Agent 32 decide not to loan +2025-10-24 16:05:42,992 - Stocklogger - INFO - INFO: Agent 33 decide not to loan +2025-10-24 16:05:43,549 - Stocklogger - INFO - INFO: Agent 34 decide not to loan +2025-10-24 16:05:44,105 - Stocklogger - INFO - INFO: Agent 35 decide not to loan +2025-10-24 16:05:44,728 - Stocklogger - INFO - INFO: Agent 36 decide not to loan +2025-10-24 16:05:45,607 - Stocklogger - INFO - INFO: Agent 37 decide not to loan +2025-10-24 16:05:46,262 - Stocklogger - INFO - INFO: Agent 38 decide not to loan +2025-10-24 16:05:47,066 - Stocklogger - INFO - INFO: Agent 39 decide not to loan +2025-10-24 16:05:47,629 - Stocklogger - INFO - INFO: Agent 40 decide not to loan +2025-10-24 16:05:48,207 - Stocklogger - INFO - INFO: Agent 41 decide not to loan +2025-10-24 16:05:49,492 - Stocklogger - INFO - INFO: Agent 42 decide not to loan +2025-10-24 16:05:50,629 - Stocklogger - INFO - INFO: Agent 43 decide not to loan +2025-10-24 16:05:51,664 - Stocklogger - INFO - INFO: Agent 44 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1684637.44204241, 'repayment_date': 45} +2025-10-24 16:05:52,415 - Stocklogger - INFO - INFO: Agent 45 decide not to loan +2025-10-24 16:05:54,131 - Stocklogger - INFO - INFO: Agent 46 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 1797803, 'repayment_date': 67} +2025-10-24 16:05:54,844 - Stocklogger - INFO - INFO: Agent 47 decide not to loan +2025-10-24 16:05:55,477 - Stocklogger - INFO - INFO: Agent 48 decide not to loan +2025-10-24 16:05:56,101 - Stocklogger - INFO - INFO: Agent 49 decide not to loan +2025-10-24 16:05:56,101 - Stocklogger - DEBUG - SESSION 1 +2025-10-24 16:05:58,271 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 50, 'price': 41} +2025-10-24 16:21:48,855 - Stocklogger - DEBUG - Agents initial... +2025-10-24 16:21:48,856 - Stocklogger - DEBUG - cash: 3648438.6702980977, stock a: 7927, stock b:25249, debt: [{'loan': 'yes', 'amount': 2339455.269618133, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:21:48,856 - Stocklogger - DEBUG - cash: 2491604.387976668, stock a: 12651, stock b:49049, debt: [{'loan': 'yes', 'amount': 336840.712750438, 'loan_type': 0, 'repayment_date': 220}] +2025-10-24 16:21:48,856 - Stocklogger - DEBUG - cash: 310336.0863603588, stock a: 42830, stock b:81270, debt: [{'loan': 'yes', 'amount': 2907246.815329219, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 2810141.658942213, stock a: 32324, stock b:24221, debt: [{'loan': 'yes', 'amount': 1502468.6286202671, 'loan_type': 2, 'repayment_date': 198}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 872798.4911796644, stock a: 33156, stock b:44472, debt: [{'loan': 'yes', 'amount': 3228154.8742705947, 'loan_type': 1, 'repayment_date': 44}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 1032023.3582376787, stock a: 16181, stock b:18663, debt: [{'loan': 'yes', 'amount': 920800.567386914, 'loan_type': 2, 'repayment_date': 154}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 151140.25267346067, stock a: 15388, stock b:66563, debt: [{'loan': 'yes', 'amount': 1933131.2156763892, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 399858.2215775881, stock a: 139294, stock b:3141, debt: [{'loan': 'yes', 'amount': 2297340.0627793334, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 196312.139293377, stock a: 13479, stock b:8182, debt: [{'loan': 'yes', 'amount': 308825.16406163573, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 2590663.772754004, stock a: 31098, stock b:26287, debt: [{'loan': 'yes', 'amount': 4133118.6706156707, 'loan_type': 2, 'repayment_date': 176}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 592962.2762614173, stock a: 19506, stock b:22087, debt: [{'loan': 'yes', 'amount': 500284.485902725, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 3441190.42068338, stock a: 14931, stock b:23878, debt: [{'loan': 'yes', 'amount': 1948510.3901920759, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 16:21:48,857 - Stocklogger - DEBUG - cash: 882913.092719762, stock a: 7655, stock b:23287, debt: [{'loan': 'yes', 'amount': 420784.572522187, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 1610502.4305203536, stock a: 29646, stock b:58545, debt: [{'loan': 'yes', 'amount': 148506.0594122556, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 187695.14877910732, stock a: 12080, stock b:11251, debt: [{'loan': 'yes', 'amount': 415368.67016414844, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 1640436.765145225, stock a: 54522, stock b:6853, debt: [{'loan': 'yes', 'amount': 526778.9636279219, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 673735.5743854617, stock a: 46589, stock b:53366, debt: [{'loan': 'yes', 'amount': 879317.8164614895, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 2066109.6224689519, stock a: 57061, stock b:18918, debt: [{'loan': 'yes', 'amount': 164712.93942827982, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 728901.4168795366, stock a: 132834, stock b:3539, debt: [{'loan': 'yes', 'amount': 4217735.492015767, 'loan_type': 1, 'repayment_date': 132}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 840343.2391352967, stock a: 116495, stock b:12319, debt: [{'loan': 'yes', 'amount': 2199356.476399629, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 529308.9550861946, stock a: 19020, stock b:48264, debt: [{'loan': 'yes', 'amount': 1002078.2117200516, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 2441595.503087133, stock a: 6420, stock b:25731, debt: [{'loan': 'yes', 'amount': 1722243.1682001343, 'loan_type': 0, 'repayment_date': 176}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 2157760.0177429523, stock a: 14848, stock b:59487, debt: [{'loan': 'yes', 'amount': 3284108.7737011993, 'loan_type': 2, 'repayment_date': 198}] +2025-10-24 16:21:48,858 - Stocklogger - DEBUG - cash: 4536272.703214253, stock a: 1076, stock b:5384, debt: [{'loan': 'yes', 'amount': 2661951.090610586, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 1225726.2237234246, stock a: 25481, stock b:65286, debt: [{'loan': 'yes', 'amount': 3403786.1554816053, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 2819613.9206951186, stock a: 65892, stock b:1415, debt: [{'loan': 'yes', 'amount': 3173515.1055531553, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 305736.3637274374, stock a: 30937, stock b:71436, debt: [{'loan': 'yes', 'amount': 1702035.8926571428, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 759427.1273447812, stock a: 41389, stock b:11624, debt: [{'loan': 'yes', 'amount': 406234.27801465994, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 240343.83993055075, stock a: 71024, stock b:64700, debt: [{'loan': 'yes', 'amount': 1004790.8978687653, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 2845462.8827739493, stock a: 13848, stock b:33746, debt: [{'loan': 'yes', 'amount': 3548833.005783274, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 3513633.5715164584, stock a: 25649, stock b:11913, debt: [{'loan': 'yes', 'amount': 3680140.9141911534, 'loan_type': 1, 'repayment_date': 132}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 1745790.165051156, stock a: 40672, stock b:37413, debt: [{'loan': 'yes', 'amount': 2754064.023816813, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 1626813.8696657612, stock a: 59173, stock b:19281, debt: [{'loan': 'yes', 'amount': 1932668.3015953817, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 16:21:48,859 - Stocklogger - DEBUG - cash: 1141810.6815418373, stock a: 22125, stock b:45915, debt: [{'loan': 'yes', 'amount': 3256720.3671193514, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 2973363.344873069, stock a: 2619, stock b:40796, debt: [{'loan': 'yes', 'amount': 3978444.099382567, 'loan_type': 0, 'repayment_date': 110}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 1033992.5728010169, stock a: 62088, stock b:20878, debt: [{'loan': 'yes', 'amount': 531182.234272638, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 203070.74383971223, stock a: 32349, stock b:87539, debt: [{'loan': 'yes', 'amount': 743899.4202183868, 'loan_type': 1, 'repayment_date': 66}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 2384071.724025017, stock a: 70640, stock b:9993, debt: [{'loan': 'yes', 'amount': 2044007.1116374375, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 1806695.7211890954, stock a: 50152, stock b:14318, debt: [{'loan': 'yes', 'amount': 695129.2208495408, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 3218385.898352533, stock a: 6676, stock b:14691, debt: [{'loan': 'yes', 'amount': 133454.30145386027, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 3085373.3218233055, stock a: 12185, stock b:19185, debt: [{'loan': 'yes', 'amount': 1779069.6325731308, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 128903.3746670426, stock a: 7422, stock b:29983, debt: [{'loan': 'yes', 'amount': 856570.4716525812, 'loan_type': 1, 'repayment_date': 132}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 660021.2744014355, stock a: 52493, stock b:42157, debt: [{'loan': 'yes', 'amount': 2899066.090108625, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 45201.601457441895, stock a: 45114, stock b:72575, debt: [{'loan': 'yes', 'amount': 2780512.387305886, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 609648.1887012129, stock a: 21794, stock b:82294, debt: [{'loan': 'yes', 'amount': 2401315.734495465, 'loan_type': 1, 'repayment_date': 88}] +2025-10-24 16:21:48,860 - Stocklogger - DEBUG - cash: 2543137.676586133, stock a: 12078, stock b:2896, debt: [{'loan': 'yes', 'amount': 1436593.8818196545, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 16:21:48,861 - Stocklogger - DEBUG - cash: 697663.8075471892, stock a: 6031, stock b:98870, debt: [{'loan': 'yes', 'amount': 347244.6874638879, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 16:21:48,861 - Stocklogger - DEBUG - cash: 5503.703516432923, stock a: 53758, stock b:52145, debt: [{'loan': 'yes', 'amount': 3047837.8812143365, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 16:21:48,861 - Stocklogger - DEBUG - cash: 451685.2770596008, stock a: 37200, stock b:48469, debt: [{'loan': 'yes', 'amount': 2909867.667041817, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 16:21:48,861 - Stocklogger - DEBUG - cash: 1476804.8793899608, stock a: 90921, stock b:17837, debt: [{'loan': 'yes', 'amount': 4758538.943431102, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 16:21:48,861 - Stocklogger - DEBUG - --------Simulation Start!-------- +2025-10-24 16:21:48,861 - Stocklogger - DEBUG - --------DAY 1--------- +2025-10-24 16:21:49,805 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 16:21:50,994 - Stocklogger - INFO - INFO: Agent 1 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 1000000, 'repayment_date': 67} +2025-10-24 16:21:51,917 - Stocklogger - INFO - INFO: Agent 2 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 1938789.2710311394, 'repayment_date': 67} +2025-10-24 16:21:53,803 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 16:21:54,362 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 16:21:55,105 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 16:21:55,762 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 16:21:58,388 - Stocklogger - INFO - INFO: Agent 7 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2400000, 'repayment_date': 45} +2025-10-24 16:21:59,386 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 16:21:59,967 - Stocklogger - INFO - INFO: Agent 9 decide not to loan +2025-10-24 16:22:01,343 - Stocklogger - INFO - INFO: Agent 10 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 1561337.7903586922, 'repayment_date': 23} +2025-10-24 16:22:01,898 - Stocklogger - INFO - INFO: Agent 11 decide not to loan +2025-10-24 16:22:02,665 - Stocklogger - INFO - INFO: Agent 12 decide not to loan +2025-10-24 16:22:03,685 - Stocklogger - INFO - INFO: Agent 13 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 100000, 'repayment_date': 45} +2025-10-24 16:22:04,251 - Stocklogger - INFO - INFO: Agent 14 decide not to loan +2025-10-24 16:22:04,925 - Stocklogger - INFO - INFO: Agent 15 decide not to loan +2025-10-24 16:22:05,385 - Stocklogger - INFO - INFO: Agent 16 decide not to loan +2025-10-24 16:22:06,257 - Stocklogger - INFO - INFO: Agent 17 decide not to loan +2025-10-24 16:22:06,796 - Stocklogger - INFO - INFO: Agent 18 decide not to loan +2025-10-24 16:22:07,969 - Stocklogger - INFO - INFO: Agent 19 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 2628596.762735668, 'repayment_date': 67} +2025-10-24 16:22:08,919 - Stocklogger - INFO - INFO: Agent 20 decide not to loan +2025-10-24 16:22:09,484 - Stocklogger - INFO - INFO: Agent 21 decide not to loan +2025-10-24 16:22:10,098 - Stocklogger - INFO - INFO: Agent 22 decide not to loan +2025-10-24 16:22:10,819 - Stocklogger - INFO - INFO: Agent 23 decide not to loan +2025-10-24 16:22:11,341 - Stocklogger - INFO - INFO: Agent 24 decide not to loan +2025-10-24 16:22:11,921 - Stocklogger - INFO - INFO: Agent 25 decide not to loan +2025-10-24 16:22:12,561 - Stocklogger - INFO - INFO: Agent 26 decide not to loan +2025-10-24 16:22:13,265 - Stocklogger - INFO - INFO: Agent 27 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 200000, 'repayment_date': 45} +2025-10-24 16:22:13,871 - Stocklogger - INFO - INFO: Agent 28 decide not to loan +2025-10-24 16:22:14,410 - Stocklogger - INFO - INFO: Agent 29 decide not to loan +2025-10-24 16:22:15,607 - Stocklogger - INFO - INFO: Agent 30 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1079482.6573253046, 'repayment_date': 45} +2025-10-24 16:22:16,198 - Stocklogger - INFO - INFO: Agent 31 decide not to loan +2025-10-24 16:22:16,979 - Stocklogger - INFO - INFO: Agent 32 decide not to loan +2025-10-24 16:22:17,488 - Stocklogger - INFO - INFO: Agent 33 decide not to loan +2025-10-24 16:22:18,416 - Stocklogger - INFO - INFO: Agent 34 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 705329.2454905016, 'repayment_date': 23} +2025-10-24 16:22:19,223 - Stocklogger - INFO - INFO: Agent 35 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 3200570.338528379, 'repayment_date': 23} +2025-10-24 16:22:20,010 - Stocklogger - INFO - INFO: Agent 36 decide not to loan +2025-10-24 16:22:21,157 - Stocklogger - INFO - INFO: Agent 37 decide not to loan +2025-10-24 16:22:22,242 - Stocklogger - INFO - INFO: Agent 38 decide not to loan +2025-10-24 16:22:22,667 - Stocklogger - INFO - INFO: Agent 39 decide not to loan +2025-10-24 16:22:23,672 - Stocklogger - INFO - INFO: Agent 40 decide not to loan +2025-10-24 16:22:24,267 - Stocklogger - INFO - INFO: Agent 41 decide not to loan +2025-10-24 16:22:24,816 - Stocklogger - INFO - INFO: Agent 42 decide not to loan +2025-10-24 16:22:25,323 - Stocklogger - INFO - INFO: Agent 43 decide not to loan +2025-10-24 16:22:25,848 - Stocklogger - INFO - INFO: Agent 44 decide not to loan +2025-10-24 16:22:26,782 - Stocklogger - INFO - INFO: Agent 45 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1500000, 'repayment_date': 45} +2025-10-24 16:22:27,272 - Stocklogger - INFO - INFO: Agent 46 decide not to loan +2025-10-24 16:22:27,817 - Stocklogger - INFO - INFO: Agent 47 decide not to loan +2025-10-24 16:22:28,456 - Stocklogger - INFO - INFO: Agent 48 decide not to loan +2025-10-24 16:22:29,165 - Stocklogger - INFO - INFO: Agent 49 decide not to loan +2025-10-24 16:22:29,165 - Stocklogger - DEBUG - SESSION 1 +2025-10-24 16:22:30,025 - Stocklogger - INFO - INFO: Agent 35 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 1000, 'price': 41} +2025-10-24 16:22:31,200 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 2000, 'price': 31} +2025-10-24 16:22:32,308 - Stocklogger - INFO - INFO: Agent 44 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 2000, 'price': 31} +2025-10-24 16:22:33,883 - Stocklogger - INFO - INFO: Agent 33 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 4000, 'price': 31.5} +2025-10-24 16:22:34,823 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 4000, 'price': 31.5} +2025-10-24 16:22:35,747 - Stocklogger - INFO - INFO: Agent 42 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 31.5} +2025-10-24 16:22:36,569 - Stocklogger - INFO - INFO: Agent 20 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32} +2025-10-24 16:22:38,378 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32} +2025-10-24 16:22:39,002 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 4000, 'price': 32} +2025-10-24 16:22:40,206 - Stocklogger - DEBUG - Sell more than hold: ```json +{"action_type":"sell", "stock":"A", "amount":4000, "price":32} +``` +2025-10-24 16:22:40,888 - Stocklogger - INFO - INFO: Agent 23 decide not to action +2025-10-24 16:22:41,717 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32} +2025-10-24 16:22:42,862 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32} +2025-10-24 16:22:43,966 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32} +2025-10-24 16:22:45,698 - Stocklogger - INFO - INFO: Agent 31 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32} +2025-10-24 16:22:46,740 - Stocklogger - INFO - INFO: Agent 45 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 16:22:47,584 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 16:22:48,514 - Stocklogger - INFO - INFO: Agent 11 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 16:22:49,525 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 16:22:52,176 - Stocklogger - INFO - INFO: Agent 48 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:22:53,039 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:22:54,543 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:22:55,452 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:22:57,789 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:22:59,305 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 16:23:00,428 - Stocklogger - INFO - INFO: Agent 49 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:23:01,805 - Stocklogger - INFO - INFO: Agent 40 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:23:03,771 - Stocklogger - INFO - INFO: Agent 32 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 16:23:04,880 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:23:06,001 - Stocklogger - INFO - INFO: Agent 19 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 12000, 'price': 33} +2025-10-24 16:23:07,064 - Stocklogger - INFO - INFO: Agent 43 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:23:08,048 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:09,197 - Stocklogger - INFO - INFO: Agent 15 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 12000, 'price': 33} +2025-10-24 16:23:10,226 - Stocklogger - INFO - INFO: Agent 10 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 12000, 'price': 33.5} +2025-10-24 16:23:11,289 - Stocklogger - INFO - INFO: Agent 37 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 12000, 'price': 33.5} +2025-10-24 16:23:12,261 - Stocklogger - INFO - INFO: Agent 21 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:23:13,838 - Stocklogger - INFO - INFO: Agent 46 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:14,730 - Stocklogger - INFO - INFO: Agent 17 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:23:15,988 - Stocklogger - INFO - INFO: Agent 36 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:17,153 - Stocklogger - INFO - INFO: Agent 12 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:17,980 - Stocklogger - INFO - INFO: Agent 29 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:19,130 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:20,800 - Stocklogger - INFO - INFO: Agent 41 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:23:22,718 - Stocklogger - INFO - INFO: Agent 24 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:24,064 - Stocklogger - INFO - INFO: Agent 22 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:25,463 - Stocklogger - INFO - INFO: Agent 28 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 12000, 'price': 33.5} +2025-10-24 16:23:26,669 - Stocklogger - DEBUG - Sell more than hold: ```json +{"action_type": "sell", "stock": "A", "amount": 12000, "price": 34} +``` +2025-10-24 16:23:27,497 - Stocklogger - INFO - INFO: Agent 34 decide not to action +2025-10-24 16:23:28,597 - Stocklogger - INFO - INFO: Agent 26 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 12000, 'price': 33.5} +2025-10-24 16:23:30,053 - Stocklogger - INFO - INFO: Agent 47 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:30,945 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:31,973 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:23:31,996 - Stocklogger - DEBUG - SESSION 2 +2025-10-24 16:23:35,763 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 2000, 'price': 42} +2025-10-24 16:23:38,067 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:23:40,107 - Stocklogger - INFO - INFO: Agent 26 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 41} +2025-10-24 16:23:42,160 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 8000, 'price': 33.5} +2025-10-24 16:23:43,653 - Stocklogger - INFO - INFO: Agent 20 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 41} +2025-10-24 16:23:46,461 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 43} +2025-10-24 16:23:48,228 - Stocklogger - INFO - INFO: Agent 15 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 2000, 'price': 43} +2025-10-24 16:23:50,694 - Stocklogger - INFO - INFO: Agent 41 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 2000, 'price': 42} +2025-10-24 16:23:55,099 - Stocklogger - DEBUG - Buy more than cash: ```json +{"action_type": "buy", "stock": "B", "amount": 1000, "price": 42} +``` +2025-10-24 16:23:58,204 - Stocklogger - INFO - INFO: Agent 47 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41} +2025-10-24 16:23:59,231 - Stocklogger - INFO - INFO: Agent 19 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:00,347 - Stocklogger - INFO - INFO: Agent 37 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:01,779 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:03,088 - Stocklogger - INFO - INFO: Agent 28 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 2000, 'price': 43} +2025-10-24 16:24:04,892 - Stocklogger - INFO - INFO: Agent 40 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 2000, 'price': 42} +2025-10-24 16:24:06,074 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:07,197 - Stocklogger - INFO - INFO: Agent 10 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:09,474 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:10,754 - Stocklogger - INFO - INFO: Agent 12 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:12,993 - Stocklogger - INFO - INFO: Agent 21 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:13,835 - Stocklogger - INFO - INFO: Agent 23 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:14,822 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:15,838 - Stocklogger - INFO - INFO: Agent 29 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:17,476 - Stocklogger - INFO - INFO: Agent 36 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:19,003 - Stocklogger - INFO - INFO: Agent 32 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:19,953 - Stocklogger - INFO - INFO: Agent 17 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:20,952 - Stocklogger - INFO - INFO: Agent 42 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:21,831 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:22,797 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:23,951 - Stocklogger - DEBUG - Buy more than cash: ```json +{"action_type": "buy", "stock": "B", "amount": 3000, "price": 42} +``` +2025-10-24 16:24:26,561 - Stocklogger - INFO - INFO: Agent 43 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 1000, 'price': 41} +2025-10-24 16:24:28,716 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:30,416 - Stocklogger - INFO - INFO: Agent 48 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:33,600 - Stocklogger - INFO - INFO: Agent 22 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:36,192 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:37,742 - Stocklogger - INFO - INFO: Agent 34 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 500, 'price': 33} +2025-10-24 16:24:39,116 - Stocklogger - INFO - INFO: Agent 46 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:40,153 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 2000, 'price': 42} +2025-10-24 16:24:42,585 - Stocklogger - INFO - INFO: Agent 45 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:44,127 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:45,749 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:46,974 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:48,049 - Stocklogger - INFO - INFO: Agent 11 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:49,315 - Stocklogger - INFO - INFO: Agent 33 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 43} +2025-10-24 16:24:52,261 - Stocklogger - INFO - INFO: Agent 49 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:53,634 - Stocklogger - INFO - INFO: Agent 31 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:54,788 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:55,873 - Stocklogger - INFO - INFO: Agent 44 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:24:57,117 - Stocklogger - INFO - INFO: Agent 24 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:25:00,017 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:25:01,152 - Stocklogger - INFO - INFO: Agent 35 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:25:03,717 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:25:03,751 - Stocklogger - DEBUG - SESSION 3 +2025-10-24 16:25:07,055 - Stocklogger - INFO - INFO: Agent 42 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 8000, 'price': 33.5} +2025-10-24 16:25:08,350 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 4000, 'price': 33.5} +2025-10-24 16:25:09,632 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:25:11,015 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 4000, 'price': 33.5} +2025-10-24 16:25:12,124 - Stocklogger - INFO - INFO: Agent 26 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:25:21,136 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:25:27,642 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:25:29,081 - Stocklogger - INFO - INFO: Agent 49 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:25:31,648 - Stocklogger - INFO - INFO: Agent 34 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:25:33,781 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 8000, 'price': 33.5} +2025-10-24 16:25:35,106 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 4000, 'price': 42} +2025-10-24 16:25:36,976 - Stocklogger - INFO - INFO: Agent 17 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:25:42,095 - Stocklogger - INFO - INFO: Agent 47 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41} +2025-10-24 16:25:44,089 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:25:45,882 - Stocklogger - INFO - INFO: Agent 36 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 7000, 'price': 33.5} +2025-10-24 16:25:47,779 - Stocklogger - INFO - INFO: Agent 45 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33} +2025-10-24 16:25:49,112 - Stocklogger - INFO - INFO: Agent 46 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:25:50,385 - Stocklogger - INFO - INFO: Agent 22 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:25:51,624 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:25:55,413 - Stocklogger - INFO - INFO: Agent 20 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 2000, 'price': 43} +2025-10-24 16:25:55,446 - Stocklogger - INFO - ACTION - BUY:4, SELL:20, STOCK:B, PRICE:43, AMOUNT:2000 +2025-10-24 16:25:56,939 - Stocklogger - INFO - INFO: Agent 41 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:26:00,313 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 8000, 'price': 33.5} +2025-10-24 16:26:01,696 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:26:03,894 - Stocklogger - INFO - INFO: Agent 10 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:26:04,985 - Stocklogger - INFO - INFO: Agent 21 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:26:06,868 - Stocklogger - INFO - INFO: Agent 19 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 12000, 'price': 33.5} +2025-10-24 16:26:08,842 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:26:13,125 - Stocklogger - DEBUG - Sell more than hold: {"action_type": "sell", "stock": "A", "amount": 8000, "price": 33.5} +2025-10-24 16:26:14,174 - Stocklogger - INFO - INFO: Agent 12 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:26:22,286 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 33.5} +2025-10-24 16:26:32,321 - Stocklogger - INFO - INFO: Agent 48 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 8000, 'price': 34} +2025-10-24 16:26:41,473 - Stocklogger - INFO - INFO: Agent 35 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:26:43,386 - Stocklogger - INFO - INFO: Agent 33 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 3000, 'price': 43} +2025-10-24 16:26:43,425 - Stocklogger - INFO - ACTION - BUY:4, SELL:33, STOCK:B, PRICE:43, AMOUNT:1000 +2025-10-24 16:26:43,432 - Stocklogger - INFO - ACTION - BUY:15, SELL:33, STOCK:B, PRICE:43, AMOUNT:2000 +2025-10-24 16:26:48,942 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 34} +2025-10-24 16:26:53,267 - Stocklogger - INFO - INFO: Agent 11 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 34} +2025-10-24 16:26:54,368 - Stocklogger - INFO - INFO: Agent 23 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 500, 'price': 33} +2025-10-24 16:26:55,746 - Stocklogger - INFO - INFO: Agent 40 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:27:03,931 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 34} +2025-10-24 16:27:05,403 - Stocklogger - INFO - INFO: Agent 43 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 1000, 'price': 41} +2025-10-24 16:27:16,222 - Stocklogger - INFO - INFO: Agent 37 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:27:18,268 - Stocklogger - INFO - INFO: Agent 28 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 34} +2025-10-24 16:27:19,451 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 34} +2025-10-24 16:27:23,062 - Stocklogger - INFO - INFO: Agent 32 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:27:24,143 - Stocklogger - INFO - INFO: Agent 29 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 34} +2025-10-24 16:27:29,323 - Stocklogger - INFO - INFO: Agent 24 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 8000, 'price': 34} +2025-10-24 16:27:30,496 - Stocklogger - INFO - INFO: Agent 44 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 4000, 'price': 34} +2025-10-24 16:27:31,968 - Stocklogger - INFO - INFO: Agent 15 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:27:33,429 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:27:40,882 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 3000, 'price': 44} +2025-10-24 16:27:50,746 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 4000, 'price': 34} +2025-10-24 16:27:52,147 - Stocklogger - INFO - INFO: Agent 31 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 3000, 'price': 42} +2025-10-24 16:27:53,638 - Stocklogger - INFO - Agent 0 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:27:54,702 - Stocklogger - INFO - Agent 1 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:00,076 - Stocklogger - INFO - Agent 2 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:01,497 - Stocklogger - INFO - Agent 3 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:02,867 - Stocklogger - INFO - Agent 4 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'yes', 'loan': 'no'} +2025-10-24 16:28:09,291 - Stocklogger - INFO - Agent 5 tomorrow estimation: {'buy_A': 'yes', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'yes', 'loan': 'no'} +2025-10-24 16:28:10,961 - Stocklogger - INFO - Agent 6 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:17,505 - Stocklogger - INFO - Agent 7 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:19,001 - Stocklogger - INFO - Agent 8 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:20,343 - Stocklogger - INFO - Agent 9 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:21,754 - Stocklogger - INFO - Agent 10 tomorrow estimation: {'buy_A': 'yes', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'yes'} +2025-10-24 16:28:31,751 - Stocklogger - INFO - Agent 11 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'no', 'sell_B': 'yes', 'loan': 'no'} +2025-10-24 16:28:32,930 - Stocklogger - INFO - Agent 12 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:34,799 - Stocklogger - INFO - Agent 13 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:38,967 - Stocklogger - INFO - Agent 14 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:44,126 - Stocklogger - INFO - Agent 15 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:47,934 - Stocklogger - INFO - Agent 16 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:51,738 - Stocklogger - INFO - Agent 17 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:52,765 - Stocklogger - INFO - Agent 18 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:28:53,803 - Stocklogger - INFO - Agent 19 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:04,092 - Stocklogger - INFO - Agent 20 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:05,086 - Stocklogger - INFO - Agent 21 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:07,052 - Stocklogger - INFO - Agent 22 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:08,532 - Stocklogger - INFO - Agent 23 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:15,702 - Stocklogger - INFO - Agent 24 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:17,658 - Stocklogger - INFO - Agent 25 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:19,432 - Stocklogger - INFO - Agent 26 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:20,654 - Stocklogger - INFO - Agent 27 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:22,618 - Stocklogger - INFO - Agent 28 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:32,491 - Stocklogger - INFO - Agent 29 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:33,521 - Stocklogger - INFO - Agent 30 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:34,486 - Stocklogger - INFO - Agent 31 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:40,026 - Stocklogger - INFO - Agent 32 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:41,181 - Stocklogger - INFO - Agent 33 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:48,900 - Stocklogger - INFO - Agent 34 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:50,330 - Stocklogger - INFO - Agent 35 tomorrow estimation: {'buy_A': 'yes', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:51,665 - Stocklogger - INFO - Agent 36 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:29:58,222 - Stocklogger - INFO - Agent 37 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:06,064 - Stocklogger - INFO - Agent 38 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:07,358 - Stocklogger - INFO - Agent 39 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:08,722 - Stocklogger - INFO - Agent 40 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:09,598 - Stocklogger - INFO - Agent 41 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:17,318 - Stocklogger - INFO - Agent 42 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:18,623 - Stocklogger - INFO - Agent 43 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:20,286 - Stocklogger - INFO - Agent 44 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:21,844 - Stocklogger - INFO - Agent 45 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:23,701 - Stocklogger - INFO - Agent 46 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:25,495 - Stocklogger - INFO - Agent 47 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:30,629 - Stocklogger - INFO - Agent 48 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:34,790 - Stocklogger - INFO - Agent 49 tomorrow estimation: {'buy_A': 'no', 'buy_B': 'yes', 'sell_A': 'yes', 'sell_B': 'no', 'loan': 'no'} +2025-10-24 16:30:34,810 - Stocklogger - DEBUG - DAY 1 ends, display forum messages... +2025-10-24 16:30:38,076 - Stocklogger - INFO - Agent 0 says: **Trading Tips for Today:** + +1. **Market Overview:** The market has shown significant fluctuations today, particularly in technology stocks, which may affect future price movements. Keep an eye on Company B, as it is expected to rebound with strong buying interest. + +2. **Stock A:** Despite recent sell-offs, I believe Company A remains a stable long-term investment. However, short-term traders may want to take advantage of price increase opportunities. + +3. **Stock B:** With the projected revenue growth and the recent positive corporate news, it could be a good time to accumulate shares while prices are still reasonable. + +4. **Loan Decisions:** For conservative investors, avoiding loans may provide a safer strategy during uncertain times. Focus on trading based on available cash reserves. + +5. **Actionable Strategy:** Consider short-term trades in Stock B but maintain a balanced portfolio by holding onto a portion of Stock A for stability. + +Happy trading and stay informed! +2025-10-24 16:30:51,289 - Stocklogger - INFO - Agent 1 says: **Trading Tips for Today:** + +1. **Company A**: Despite the recent sell-offs, there remains potential for recovery due to new strategic directions under the new CEO. Monitor closely for upward trends and trading volume. Consider selling selective portions to capitalize on positive movements. + +2. **Company B**: With robust growth expected and positive market sentiment, now is a good time to consider accumulating shares. The recent activities suggest increasing interest, indicating a potential bullish run. + +3. **General Strategy**: Keep an eye on market news and sentiments which may impact stock prices, especially any government policies affecting these sectors. Diversification remains key to managing risk. + +Happy Trading! 📈💼 +2025-10-24 16:30:54,664 - Stocklogger - INFO - Agent 2 says: **Trading Tips for Today:** + +1. **Company A** is showing promising price resistance at $33.5, and despite recent sell-offs, the overall trend remains stable. Consider executing sells in the upcoming sessions if it approaches resistance levels. + +2. **Company B** continues to exhibit growth potential, recommended for buying, especially with its recent price movements around $42. It is advisable to take advantage of any dips to accumulate more shares in anticipation of future gains. + +3. Keep an eye on market sentiments and news developments as they can greatly influence stock prices, particularly in volatile sectors. + +4. As for financing strategies, it might be prudent to avoid new loans unless absolutely necessary, given the current market conditions. + +Happy trading! +2025-10-24 16:30:57,629 - Stocklogger - INFO - Agent 3 says: **Trading Tips:** + +Today, we witnessed some interesting movements in both Company A and Company B's stocks. Company A has shown resilience despite a steady decline in revenue over the past few years, suggesting potential opportunities for profit on the selling side as we see upward pressure on prices. + +For Company B, the continuous demand in the tech sector despite recent fluctuations points to a robust growth potential, making it a good candidate for buying. Keep an eye on news regarding government inquiries and operational transparency, as this could affect stock performance. + +Overall, consider balancing your portfolio by selling some shares of A while investing in the growth potential of B. Happy trading! +2025-10-24 16:31:01,404 - Stocklogger - INFO - Agent 4 says: **Trading Tips for Today:** + +1. **Company A**: Despite experiencing some volatility, the overall sentiment remains cautiously optimistic. I recommend considering selling shares at higher price levels as the market is seeing increased selling activity. Monitor for any news or announcements that may influence the company's trajectory. + +2. **Company B**: With a strong buying interest and promising future growth potential, I suggest taking advantage of current opportunities to acquire more shares if the price dips below 42. Keep an eye on market reactions to ongoing government inquiries, but overall confidence in the stock seems strong. + +3. **General Advice**: Ensure to manage risk, especially in fluctuating markets, by diversifying your holdings and being aware of the latest market news. Happy trading! +2025-10-24 16:31:05,276 - Stocklogger - INFO - Agent 5 says: **Trading Tips for Today's Market:** + +1. **Stock A:** The recent sell-off pressure indicates that it might be a good time to accumulate shares at lower prices. With potential improvements in the company's outlook, consider buying if the price dips further. + +2. **Stock B:** Strong buying interest has been noted, coupled with anticipated revenue growth. The stock appears to be a strong buy for those looking to capitalize on future performance. + +3. **Loan Considerations:** While the current cash position is healthy, be mindful of market volatility. Utilizing leverage might amplify gains, but assess risks carefully. + +4. **General Advice:** Diversifying your portfolio with both established companies and promising growth stocks can mitigate risks. Keep an eye on market trends and always perform due diligence. + +Happy trading! +2025-10-24 16:31:15,949 - Stocklogger - INFO - Agent 6 says: **Trading Tips for Today:** + +1. **Company A:** While the recent sell-off presented opportunities, the overall performance trends suggest a cautious approach. It's advisable to monitor market sentiment before engaging further. + +2. **Company B:** With the expected revenue growth and strong demand, I recommend considering buying shares at the current price levels. The bullish outlook paired with government support bodes well for future gains. + +3. **Overall Market Insight:** Keep an eye on external market factors and upcoming financial reports. Staying informed will help guide your trading decisions. + +Happy trading, everyone! +2025-10-24 16:31:19,274 - Stocklogger - INFO - Agent 7 says: **Trading Tips:** + +As we conclude today's trading session, here are some key insights: + +1. **Company A**: While it has shown resilience, the upward momentum seems limited due to persistent operational challenges. Consider selling if you hold significant shares. + +2. **Company B**: A promising tech stock that is experiencing a strong demand. The recent buy orders indicate confidence in its growth potential. Now might be a good time to accumulate more shares. + +3. **Market Trends**: Keep an eye on fluctuations and trends over the next few days. Stay informed about company news and regulatory inquiries that may impact prices. + +4. **Loan Strategy**: With current market conditions, it may be prudent to avoid additional loans unless significant opportunities arise. + +Happy trading, and let’s make informed decisions together! +2025-10-24 16:31:22,338 - Stocklogger - INFO - Agent 8 says: **Trading Tips:** + +1. **Stock A**: Despite recent sell-offs, Company A appears to be stabilizing. Monitor closely for potential rebounds as new initiatives from management take shape. Consider selective selling if prices meet your targets. + +2. **Stock B**: With positive outlooks on forthcoming revenue growth and current buy interest, now may be a good time to accumulate more shares. However, due diligence is necessary due to past transparency issues. + +3. **General Market**: Keep an eye on market trends and sentiment. Economic factors may influence stock performance significantly in the upcoming sessions. + +4. **Loans**: Proceed cautiously with loans unless absolutely necessary for strategic investments. Focus on managing cash flow effectively for stability. + +Happy trading! Always assess risks before making significant moves. +2025-10-24 16:31:25,006 - Stocklogger - INFO - Agent 9 says: **Trading Tips for Today:** + +1. **Stock A**: Despite some recent selling pressure, I see potential for a rebound given the new CEO's proactive approach and efforts to explore new business avenues. Consider monitoring entry points for when the stock stabilizes. + +2. **Stock B**: With a strong growth trajectory ahead and increasing buy interest, I recommend accumulating shares. The company's commitment to transparency and societal contributions may foster positive sentiment. + +3. **Market Outlook**: With government support and operational improvements expected, a cautious but optimistic approach is warranted. Keep an eye on macroeconomic factors that could impact the tech sector. + +Happy Trading! +2025-10-24 16:31:28,829 - Stocklogger - INFO - Agent 10 says: **Trading Tips for Today:** + +1. **Stock A**: Despite recent sell-offs, there are signs of potential recovery with the new management in place. Watch for buying opportunities, especially if the price dips below 32.5. + +2. **Stock B**: Strong growth potential remains, particularly given the promising tech outlook. Consider increasing positions, especially if prices stabilize around 42. + +3. **Market Strategy**: Keeping an eye on government support initiatives can provide lucrative trading windows. Don’t hesitate to utilize loans to amplify your trading capabilities during this favorable period. + +4. **General Advice**: Monitor market reactions closely and adjust your strategies accordingly. Diversifying between A and B may help hedge against risks. + +Happy trading! +2025-10-24 16:31:31,375 - Stocklogger - INFO - Agent 11 says: **Trading Tips for Today:** + +1. **Stock A:** Despite a stable revenue trend in the past, recent sell-offs indicate that caution is warranted. Expect volatility; focus on holding rather than selling unless the price surpasses recent highs. + +2. **Stock B:** The growth potential looks promising, especially considering the expected revenue growth rate resurgence. Buyers may want to consider accumulating shares as the tech market is showing signs of recovery. + +3. **General Advice:** Before making any transactions, assess market sentiment and be wary of overleveraging. It's crucial to stay informed about the company's operational outlook and any potential regulatory impacts. + +**Happy Trading!** +2025-10-24 16:31:39,531 - Stocklogger - INFO - Agent 12 says: **Trading Tips for Today:** + +1. **Stock A Performance:** Despite some volatility, Stock A showed resilience with potential for profit-taking at higher price levels. Keep an eye on market trends and consider selling if prices rise further. + +2. **Stock B Opportunities:** Stock B remains a solid buy opportunity due to expected growth in the tech sector. With plans for expansion and increasing demand, it's wise to accumulate shares while prices are still favorable. + +3. **Market Sentiment:** Traders should stay informed about developments related to both companies, particularly on government policies and subsidies that could impact stock performance. + +4. **Loan Considerations:** Given current market conditions, maintaining cash reserves without new loans is advisable. Focus on strategic buying and selling based on market movements. + +Stay vigilant and happy trading! +2025-10-24 16:31:42,105 - Stocklogger - INFO - Agent 13 says: **Trading Tips:** + +"As we close today's trading session, keep an eye on the growth potential of Company B. Despite some recent scrutiny, its revenue growth trajectory remains promising, especially with expected growth rates surpassing 20%. Meanwhile, Company A, with a stable but uncertain outlook due to past performance, offers selling opportunities if the price continues to rise. Always consider your risk tolerance when deciding to buy or sell. Stay informed and happy trading!" +2025-10-24 16:31:46,208 - Stocklogger - INFO - Agent 14 says: **Trading Tips:** + +1. **Focus on Company B:** Given the current growth trajectory and positive outlook, consider buying shares of Company B. The stock has shown resilience and the potential for significant gains as market conditions improve. + +2. **Watch Company A:** While Company A has a stable operational outlook due to recent management changes, the overall revenue trend is declining. Short-term selling opportunities may present themselves, especially if prices rise from the current levels. + +3. **No Loans Needed:** The current cash reserves are sufficient for trading activities. Avoid taking on additional debt unless a compelling investment opportunity arises. + +4. **Stay Informed:** Keep an eye on market trends and news related to both companies, especially any governmental actions or financial reports that may influence stock prices. + +Let’s stay proactive and informed in our trading strategies! +2025-10-24 17:28:00,273 - Stocklogger - DEBUG - Agents initial... +2025-10-24 17:28:00,273 - Stocklogger - DEBUG - cash: 2971416.381922153, stock a: 27269, stock b:13284, debt: [{'loan': 'yes', 'amount': 418350.6010166338, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 17:28:00,273 - Stocklogger - DEBUG - cash: 257791.36671634228, stock a: 43389, stock b:83984, debt: [{'loan': 'yes', 'amount': 4461073.2824422745, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 17:28:00,273 - Stocklogger - DEBUG - cash: 2119670.217033741, stock a: 54871, stock b:17962, debt: [{'loan': 'yes', 'amount': 1259273.963112424, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 17:28:00,273 - Stocklogger - DEBUG - cash: 1559981.0359118015, stock a: 56302, stock b:31923, debt: [{'loan': 'yes', 'amount': 35730.35012764625, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 243348.3725143637, stock a: 23236, stock b:50955, debt: [{'loan': 'yes', 'amount': 1323771.0441515504, 'loan_type': 2, 'repayment_date': 198}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 2352484.226942778, stock a: 830, stock b:56428, debt: [{'loan': 'yes', 'amount': 2710966.1366604636, 'loan_type': 0, 'repayment_date': 154}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 1255679.385476886, stock a: 2485, stock b:88355, debt: [{'loan': 'yes', 'amount': 1038237.1430997817, 'loan_type': 0, 'repayment_date': 242}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 241133.65770470686, stock a: 29604, stock b:58027, debt: [{'loan': 'yes', 'amount': 112831.95242583477, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 462197.41808303236, stock a: 44410, stock b:34709, debt: [{'loan': 'yes', 'amount': 1750828.648905275, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 152939.41868231297, stock a: 64888, stock b:57260, debt: [{'loan': 'yes', 'amount': 3939112.824741554, 'loan_type': 1, 'repayment_date': 132}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 1466126.0716949708, stock a: 32892, stock b:16179, debt: [{'loan': 'yes', 'amount': 1456093.6871134073, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 2229968.4760598154, stock a: 15589, stock b:8350, debt: [{'loan': 'yes', 'amount': 1808389.4557595032, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 3938525.8176220306, stock a: 11966, stock b:4887, debt: [{'loan': 'yes', 'amount': 248891.0131130345, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 17:28:00,274 - Stocklogger - DEBUG - cash: 1188859.1052084928, stock a: 97937, stock b:16777, debt: [{'loan': 'yes', 'amount': 1098144.035233966, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 652031.2734327477, stock a: 96405, stock b:15740, debt: [{'loan': 'yes', 'amount': 3414646.0151750525, 'loan_type': 0, 'repayment_date': 44}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 1738823.5547042452, stock a: 10437, stock b:61906, debt: [{'loan': 'yes', 'amount': 1289523.6492951545, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 919811.0479914246, stock a: 30585, stock b:44461, debt: [{'loan': 'yes', 'amount': 754132.6128214742, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 2229080.606079838, stock a: 23452, stock b:42523, debt: [{'loan': 'yes', 'amount': 1313419.3016837297, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 2678723.268656016, stock a: 5574, stock b:22278, debt: [{'loan': 'yes', 'amount': 2433082.229863293, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 2482121.711450139, stock a: 65751, stock b:3816, debt: [{'loan': 'yes', 'amount': 1795420.3488654646, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 585833.5573255308, stock a: 14976, stock b:73493, debt: [{'loan': 'yes', 'amount': 1910465.7881913423, 'loan_type': 1, 'repayment_date': 220}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 1146648.3040369856, stock a: 32309, stock b:49532, debt: [{'loan': 'yes', 'amount': 1190141.8156267186, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 1255043.7261302595, stock a: 89769, stock b:8644, debt: [{'loan': 'yes', 'amount': 1354954.289722818, 'loan_type': 2, 'repayment_date': 264}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 1551323.009407985, stock a: 104396, stock b:4381, debt: [{'loan': 'yes', 'amount': 3598203.6810443457, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 17:28:00,275 - Stocklogger - DEBUG - cash: 595864.8431036089, stock a: 32839, stock b:22713, debt: [{'loan': 'yes', 'amount': 1112805.5407413524, 'loan_type': 1, 'repayment_date': 22}] +2025-10-24 17:28:00,276 - Stocklogger - DEBUG - cash: 312986.32885195245, stock a: 19451, stock b:12877, debt: [{'loan': 'yes', 'amount': 1040843.3329357913, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 17:28:00,276 - Stocklogger - DEBUG - cash: 1936757.3630143087, stock a: 32639, stock b:9012, debt: [{'loan': 'yes', 'amount': 1103925.6194409796, 'loan_type': 2, 'repayment_date': 110}] +2025-10-24 17:28:00,276 - Stocklogger - DEBUG - cash: 735428.4418644647, stock a: 3520, stock b:95952, debt: [{'loan': 'yes', 'amount': 479112.3071269027, 'loan_type': 2, 'repayment_date': 198}] +2025-10-24 17:28:00,276 - Stocklogger - DEBUG - cash: 786887.2218549106, stock a: 94454, stock b:31938, debt: [{'loan': 'yes', 'amount': 3796521.4959727176, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 17:28:00,276 - Stocklogger - DEBUG - cash: 123726.71322173289, stock a: 26756, stock b:74581, debt: [{'loan': 'yes', 'amount': 692623.4970890455, 'loan_type': 1, 'repayment_date': 264}] +2025-10-24 17:28:00,276 - Stocklogger - DEBUG - cash: 276327.84603076, stock a: 29382, stock b:32501, debt: [{'loan': 'yes', 'amount': 1845114.9156081525, 'loan_type': 1, 'repayment_date': 198}] +2025-10-24 17:28:00,276 - Stocklogger - DEBUG - cash: 1328996.5592399922, stock a: 10218, stock b:27398, debt: [{'loan': 'yes', 'amount': 1422009.104208719, 'loan_type': 2, 'repayment_date': 242}] +2025-10-24 17:28:00,276 - Stocklogger - DEBUG - cash: 43683.85666781549, stock a: 9675, stock b:37786, debt: [{'loan': 'yes', 'amount': 1838166.416922763, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 17:28:00,328 - Stocklogger - DEBUG - cash: 435227.25621618517, stock a: 94854, stock b:2499, debt: [{'loan': 'yes', 'amount': 661509.6339606197, 'loan_type': 0, 'repayment_date': 198}] +2025-10-24 17:28:00,328 - Stocklogger - DEBUG - cash: 941400.0276917383, stock a: 69330, stock b:13601, debt: [{'loan': 'yes', 'amount': 2434419.9684983892, 'loan_type': 2, 'repayment_date': 22}] +2025-10-24 17:28:00,328 - Stocklogger - DEBUG - cash: 339381.250134978, stock a: 141935, stock b:8753, debt: [{'loan': 'yes', 'amount': 1885068.1177880906, 'loan_type': 1, 'repayment_date': 242}] +2025-10-24 17:28:00,328 - Stocklogger - DEBUG - cash: 1192354.6996584504, stock a: 79224, stock b:22062, debt: [{'loan': 'yes', 'amount': 819621.8814971035, 'loan_type': 2, 'repayment_date': 220}] +2025-10-24 17:28:00,328 - Stocklogger - DEBUG - cash: 4173173.2436399255, stock a: 9886, stock b:9107, debt: [{'loan': 'yes', 'amount': 2989290.5441342187, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 2161447.813836956, stock a: 18426, stock b:28131, debt: [{'loan': 'yes', 'amount': 307632.49958768446, 'loan_type': 0, 'repayment_date': 132}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 3953149.586093615, stock a: 4441, stock b:4609, debt: [{'loan': 'yes', 'amount': 717925.6941291612, 'loan_type': 2, 'repayment_date': 88}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 139035.07363418277, stock a: 69949, stock b:63491, debt: [{'loan': 'yes', 'amount': 3081864.817790975, 'loan_type': 0, 'repayment_date': 264}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 259575.74896684045, stock a: 76317, stock b:38715, debt: [{'loan': 'yes', 'amount': 400415.5312823088, 'loan_type': 1, 'repayment_date': 154}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 459759.7375704143, stock a: 45124, stock b:65966, debt: [{'loan': 'yes', 'amount': 2554198.062232491, 'loan_type': 2, 'repayment_date': 66}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 1950088.5582819432, stock a: 43213, stock b:11459, debt: [{'loan': 'yes', 'amount': 692917.3167105517, 'loan_type': 1, 'repayment_date': 176}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 1917454.3565207392, stock a: 24237, stock b:45100, debt: [{'loan': 'yes', 'amount': 1570910.133182448, 'loan_type': 0, 'repayment_date': 132}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 1760846.4293656957, stock a: 34717, stock b:35982, debt: [{'loan': 'yes', 'amount': 1519077.0219426875, 'loan_type': 0, 'repayment_date': 88}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 592583.5485640713, stock a: 49186, stock b:27368, debt: [{'loan': 'yes', 'amount': 2804796.8000175115, 'loan_type': 2, 'repayment_date': 44}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 467124.7001672474, stock a: 105501, stock b:858, debt: [{'loan': 'yes', 'amount': 1481456.1450906594, 'loan_type': 0, 'repayment_date': 22}] +2025-10-24 17:28:00,329 - Stocklogger - DEBUG - cash: 4126498.0328997974, stock a: 9749, stock b:11970, debt: [{'loan': 'yes', 'amount': 1553133.3923160657, 'loan_type': 1, 'repayment_date': 176}] +2025-10-24 17:28:00,330 - Stocklogger - DEBUG - cash: 620679.8254100487, stock a: 40465, stock b:14890, debt: [{'loan': 'yes', 'amount': 1536372.450772282, 'loan_type': 2, 'repayment_date': 132}] +2025-10-24 17:28:00,330 - Stocklogger - DEBUG - --------Simulation Start!-------- +2025-10-24 17:28:00,330 - Stocklogger - DEBUG - --------DAY 1--------- +2025-10-24 17:28:01,437 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:28:01,933 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:28:03,597 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:28:04,751 - Stocklogger - INFO - INFO: Agent 3 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 200000, 'repayment_date': 45} +2025-10-24 17:28:05,292 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:28:06,040 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:28:07,592 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 17:28:08,046 - Stocklogger - INFO - INFO: Agent 7 decide not to loan +2025-10-24 17:32:37,706 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:32:37,706 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:32:38,436 - Stocklogger - INFO - INFO: Agent 0 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 200000, 'repayment_date': 45} +2025-10-24 17:32:38,436 - Stocklogger - INFO - INFO: Agent 0 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 200000, 'repayment_date': 45} +2025-10-24 17:32:38,962 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:32:38,962 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:32:39,505 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:32:39,505 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:32:41,961 - Stocklogger - INFO - INFO: Agent 3 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 3000000, 'repayment_date': 67} +2025-10-24 17:32:41,961 - Stocklogger - INFO - INFO: Agent 3 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 3000000, 'repayment_date': 67} +2025-10-24 17:32:44,465 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:32:44,465 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:32:45,746 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:32:45,746 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:32:46,550 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 17:32:46,550 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 17:32:47,534 - Stocklogger - INFO - INFO: Agent 7 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1176535.821773331, 'repayment_date': 45} +2025-10-24 17:32:47,534 - Stocklogger - INFO - INFO: Agent 7 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1176535.821773331, 'repayment_date': 45} +2025-10-24 17:32:48,346 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 17:32:48,346 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 17:32:49,017 - Stocklogger - INFO - INFO: Agent 9 decide not to loan +2025-10-24 17:32:49,017 - Stocklogger - INFO - INFO: Agent 9 decide not to loan +2025-10-24 17:32:49,896 - Stocklogger - INFO - INFO: Agent 10 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1292358.8295984368, 'repayment_date': 45} +2025-10-24 17:32:49,896 - Stocklogger - INFO - INFO: Agent 10 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1292358.8295984368, 'repayment_date': 45} +2025-10-24 17:32:50,665 - Stocklogger - INFO - INFO: Agent 11 decide not to loan +2025-10-24 17:32:50,665 - Stocklogger - INFO - INFO: Agent 11 decide not to loan +2025-10-24 17:32:51,134 - Stocklogger - INFO - INFO: Agent 12 decide not to loan +2025-10-24 17:32:51,134 - Stocklogger - INFO - INFO: Agent 12 decide not to loan +2025-10-24 17:32:51,654 - Stocklogger - INFO - INFO: Agent 13 decide not to loan +2025-10-24 17:32:51,654 - Stocklogger - INFO - INFO: Agent 13 decide not to loan +2025-10-24 17:32:52,433 - Stocklogger - INFO - INFO: Agent 14 decide not to loan +2025-10-24 17:32:52,433 - Stocklogger - INFO - INFO: Agent 14 decide not to loan +2025-10-24 17:32:53,114 - Stocklogger - INFO - INFO: Agent 15 decide not to loan +2025-10-24 17:32:53,114 - Stocklogger - INFO - INFO: Agent 15 decide not to loan +2025-10-24 17:32:53,787 - Stocklogger - INFO - INFO: Agent 16 decide not to loan +2025-10-24 17:32:53,787 - Stocklogger - INFO - INFO: Agent 16 decide not to loan +2025-10-24 17:32:54,436 - Stocklogger - INFO - INFO: Agent 17 decide not to loan +2025-10-24 17:32:54,436 - Stocklogger - INFO - INFO: Agent 17 decide not to loan +2025-10-24 17:32:54,983 - Stocklogger - INFO - INFO: Agent 18 decide not to loan +2025-10-24 17:32:54,983 - Stocklogger - INFO - INFO: Agent 18 decide not to loan +2025-10-24 17:32:56,677 - Stocklogger - INFO - INFO: Agent 19 decide not to loan +2025-10-24 17:32:56,677 - Stocklogger - INFO - INFO: Agent 19 decide not to loan +2025-10-24 17:32:57,687 - Stocklogger - INFO - INFO: Agent 20 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 464514.6622505295, 'repayment_date': 67} +2025-10-24 17:32:57,687 - Stocklogger - INFO - INFO: Agent 20 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 464514.6622505295, 'repayment_date': 67} +2025-10-24 17:32:58,344 - Stocklogger - INFO - INFO: Agent 21 decide not to loan +2025-10-24 17:32:58,344 - Stocklogger - INFO - INFO: Agent 21 decide not to loan +2025-10-24 17:32:59,500 - Stocklogger - INFO - INFO: Agent 22 decide not to loan +2025-10-24 17:32:59,500 - Stocklogger - INFO - INFO: Agent 22 decide not to loan +2025-10-24 17:33:00,447 - Stocklogger - INFO - INFO: Agent 23 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2333821, 'repayment_date': 45} +2025-10-24 17:33:00,447 - Stocklogger - INFO - INFO: Agent 23 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2333821, 'repayment_date': 45} +2025-10-24 17:33:01,279 - Stocklogger - INFO - INFO: Agent 24 decide not to loan +2025-10-24 17:33:01,279 - Stocklogger - INFO - INFO: Agent 24 decide not to loan +2025-10-24 17:33:01,933 - Stocklogger - INFO - INFO: Agent 25 decide not to loan +2025-10-24 17:33:01,933 - Stocklogger - INFO - INFO: Agent 25 decide not to loan +2025-10-24 17:33:02,595 - Stocklogger - INFO - INFO: Agent 26 decide not to loan +2025-10-24 17:33:02,595 - Stocklogger - INFO - INFO: Agent 26 decide not to loan +2025-10-24 17:33:03,069 - Stocklogger - INFO - INFO: Agent 27 decide not to loan +2025-10-24 17:33:03,069 - Stocklogger - INFO - INFO: Agent 27 decide not to loan +2025-10-24 17:33:03,815 - Stocklogger - INFO - INFO: Agent 28 decide not to loan +2025-10-24 17:33:03,815 - Stocklogger - INFO - INFO: Agent 28 decide not to loan +2025-10-24 17:33:04,215 - Stocklogger - INFO - INFO: Agent 29 decide not to loan +2025-10-24 17:33:04,215 - Stocklogger - INFO - INFO: Agent 29 decide not to loan +2025-10-24 17:33:05,931 - Stocklogger - INFO - INFO: Agent 30 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 2806811.511804586, 'repayment_date': 23} +2025-10-24 17:33:05,931 - Stocklogger - INFO - INFO: Agent 30 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 2806811.511804586, 'repayment_date': 23} +2025-10-24 17:33:06,461 - Stocklogger - INFO - INFO: Agent 31 decide not to loan +2025-10-24 17:33:06,461 - Stocklogger - INFO - INFO: Agent 31 decide not to loan +2025-10-24 17:33:07,848 - Stocklogger - INFO - INFO: Agent 32 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1500000, 'repayment_date': 45} +2025-10-24 17:33:07,848 - Stocklogger - INFO - INFO: Agent 32 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1500000, 'repayment_date': 45} +2025-10-24 17:33:09,159 - Stocklogger - INFO - INFO: Agent 33 decide not to loan +2025-10-24 17:33:09,159 - Stocklogger - INFO - INFO: Agent 33 decide not to loan +2025-10-24 17:33:09,912 - Stocklogger - INFO - INFO: Agent 34 decide not to loan +2025-10-24 17:33:09,912 - Stocklogger - INFO - INFO: Agent 34 decide not to loan +2025-10-24 17:33:10,575 - Stocklogger - INFO - INFO: Agent 35 decide not to loan +2025-10-24 17:33:10,575 - Stocklogger - INFO - INFO: Agent 35 decide not to loan +2025-10-24 17:33:11,287 - Stocklogger - INFO - INFO: Agent 36 decide not to loan +2025-10-24 17:33:11,287 - Stocklogger - INFO - INFO: Agent 36 decide not to loan +2025-10-24 17:33:11,833 - Stocklogger - INFO - INFO: Agent 37 decide not to loan +2025-10-24 17:33:11,833 - Stocklogger - INFO - INFO: Agent 37 decide not to loan +2025-10-24 17:33:12,302 - Stocklogger - INFO - INFO: Agent 38 decide not to loan +2025-10-24 17:33:12,302 - Stocklogger - INFO - INFO: Agent 38 decide not to loan +2025-10-24 17:33:13,101 - Stocklogger - INFO - INFO: Agent 39 decide not to loan +2025-10-24 17:33:13,101 - Stocklogger - INFO - INFO: Agent 39 decide not to loan +2025-10-24 17:33:14,109 - Stocklogger - INFO - INFO: Agent 40 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1800000, 'repayment_date': 45} +2025-10-24 17:33:14,109 - Stocklogger - INFO - INFO: Agent 40 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1800000, 'repayment_date': 45} +2025-10-24 17:33:15,215 - Stocklogger - INFO - INFO: Agent 41 decide not to loan +2025-10-24 17:33:15,215 - Stocklogger - INFO - INFO: Agent 41 decide not to loan +2025-10-24 17:33:15,949 - Stocklogger - INFO - INFO: Agent 42 decide not to loan +2025-10-24 17:33:15,949 - Stocklogger - INFO - INFO: Agent 42 decide not to loan +2025-10-24 17:33:16,973 - Stocklogger - INFO - INFO: Agent 43 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2304505.0200585444, 'repayment_date': 45} +2025-10-24 17:33:16,973 - Stocklogger - INFO - INFO: Agent 43 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2304505.0200585444, 'repayment_date': 45} +2025-10-24 17:33:17,521 - Stocklogger - INFO - INFO: Agent 44 decide not to loan +2025-10-24 17:33:17,521 - Stocklogger - INFO - INFO: Agent 44 decide not to loan +2025-10-24 17:33:18,154 - Stocklogger - INFO - INFO: Agent 45 decide not to loan +2025-10-24 17:33:18,154 - Stocklogger - INFO - INFO: Agent 45 decide not to loan +2025-10-24 17:33:18,828 - Stocklogger - INFO - INFO: Agent 46 decide not to loan +2025-10-24 17:33:18,828 - Stocklogger - INFO - INFO: Agent 46 decide not to loan +2025-10-24 17:33:19,336 - Stocklogger - INFO - INFO: Agent 47 decide not to loan +2025-10-24 17:33:19,336 - Stocklogger - INFO - INFO: Agent 47 decide not to loan +2025-10-24 17:33:20,299 - Stocklogger - INFO - INFO: Agent 48 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 200000, 'repayment_date': 45} +2025-10-24 17:33:20,299 - Stocklogger - INFO - INFO: Agent 48 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 200000, 'repayment_date': 45} +2025-10-24 17:33:20,802 - Stocklogger - INFO - INFO: Agent 49 decide not to loan +2025-10-24 17:33:20,802 - Stocklogger - INFO - INFO: Agent 49 decide not to loan +2025-10-24 17:33:21,798 - Stocklogger - INFO - INFO: Agent 44 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 50, 'price': 41.0} +2025-10-24 17:33:21,798 - Stocklogger - INFO - INFO: Agent 44 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 50, 'price': 41.0} +2025-10-24 17:33:23,734 - Stocklogger - INFO - INFO: Agent 46 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 17:33:23,734 - Stocklogger - INFO - INFO: Agent 46 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 17:33:24,847 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 17:33:24,847 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 17:33:26,044 - Stocklogger - INFO - INFO: Agent 15 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 17:33:26,044 - Stocklogger - INFO - INFO: Agent 15 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 17:33:27,101 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 17:33:27,101 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 17:33:28,365 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:28,365 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:29,844 - Stocklogger - INFO - INFO: Agent 20 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.0} +2025-10-24 17:33:29,844 - Stocklogger - INFO - INFO: Agent 20 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.0} +2025-10-24 17:33:31,029 - Stocklogger - INFO - INFO: Agent 43 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:31,029 - Stocklogger - INFO - INFO: Agent 43 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:33,202 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.0} +2025-10-24 17:33:33,202 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.0} +2025-10-24 17:33:34,216 - Stocklogger - INFO - INFO: Agent 22 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.0} +2025-10-24 17:33:34,216 - Stocklogger - INFO - INFO: Agent 22 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.0} +2025-10-24 17:33:35,174 - Stocklogger - INFO - INFO: Agent 26 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:35,174 - Stocklogger - INFO - INFO: Agent 26 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:36,608 - Stocklogger - INFO - INFO: Agent 35 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:36,608 - Stocklogger - INFO - INFO: Agent 35 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:38,018 - Stocklogger - INFO - INFO: Agent 10 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.5} +2025-10-24 17:33:38,018 - Stocklogger - INFO - INFO: Agent 10 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.5} +2025-10-24 17:33:39,125 - Stocklogger - INFO - INFO: Agent 41 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:39,125 - Stocklogger - INFO - INFO: Agent 41 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:40,124 - Stocklogger - INFO - INFO: Agent 40 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:40,124 - Stocklogger - INFO - INFO: Agent 40 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:41,291 - Stocklogger - INFO - INFO: Agent 36 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:41,291 - Stocklogger - INFO - INFO: Agent 36 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:42,455 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:42,455 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:43,522 - Stocklogger - DEBUG - Sell more than hold: {"action_type": "sell", "stock": "A", "amount": 5000, "price": 32.0} +2025-10-24 17:33:43,522 - Stocklogger - DEBUG - Sell more than hold: {"action_type": "sell", "stock": "A", "amount": 5000, "price": 32.0} +2025-10-24 17:33:44,762 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 4917, 'price': 32.0} +2025-10-24 17:33:44,762 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 4917, 'price': 32.0} +2025-10-24 17:33:48,485 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:48,485 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:49,887 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:49,887 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:53,730 - Stocklogger - INFO - INFO: Agent 42 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:53,730 - Stocklogger - INFO - INFO: Agent 42 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:56,491 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:56,491 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:57,511 - Stocklogger - INFO - INFO: Agent 48 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:57,511 - Stocklogger - INFO - INFO: Agent 48 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:58,691 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:58,691 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:33:59,884 - Stocklogger - INFO - INFO: Agent 12 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:33:59,884 - Stocklogger - INFO - INFO: Agent 12 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:00,838 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:00,838 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:01,985 - Stocklogger - INFO - INFO: Agent 34 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:01,985 - Stocklogger - INFO - INFO: Agent 34 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:02,782 - Stocklogger - INFO - INFO: Agent 24 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.5} +2025-10-24 17:34:02,782 - Stocklogger - INFO - INFO: Agent 24 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.5} +2025-10-24 17:34:04,304 - Stocklogger - DEBUG - Sell more than hold: ```json +{"action_type":"sell", "stock":"A", "amount":10000, "price":32.0} +``` +2025-10-24 17:34:04,304 - Stocklogger - DEBUG - Sell more than hold: ```json +{"action_type":"sell", "stock":"A", "amount":10000, "price":32.0} +``` +2025-10-24 17:34:08,442 - Stocklogger - INFO - INFO: Agent 11 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.0} +2025-10-24 17:34:08,442 - Stocklogger - INFO - INFO: Agent 11 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.0} +2025-10-24 17:34:09,424 - Stocklogger - INFO - INFO: Agent 32 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:09,424 - Stocklogger - INFO - INFO: Agent 32 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:10,594 - Stocklogger - INFO - INFO: Agent 29 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:10,594 - Stocklogger - INFO - INFO: Agent 29 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:13,211 - Stocklogger - INFO - INFO: Agent 45 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:13,211 - Stocklogger - INFO - INFO: Agent 45 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:14,208 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:14,208 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:15,688 - Stocklogger - INFO - INFO: Agent 47 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:15,688 - Stocklogger - INFO - INFO: Agent 47 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:16,854 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 32.5} +2025-10-24 17:34:16,854 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 32.5} +2025-10-24 17:34:18,074 - Stocklogger - INFO - INFO: Agent 23 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:18,074 - Stocklogger - INFO - INFO: Agent 23 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:22,070 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.0} +2025-10-24 17:34:22,070 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 32.0} +2025-10-24 17:34:23,394 - Stocklogger - INFO - INFO: Agent 31 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:23,394 - Stocklogger - INFO - INFO: Agent 31 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:24,329 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:24,329 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:31,069 - Stocklogger - INFO - INFO: Agent 49 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:31,069 - Stocklogger - INFO - INFO: Agent 49 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:32,075 - Stocklogger - INFO - INFO: Agent 28 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:32,075 - Stocklogger - INFO - INFO: Agent 28 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:33,231 - Stocklogger - INFO - INFO: Agent 17 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:33,231 - Stocklogger - INFO - INFO: Agent 17 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:34,204 - Stocklogger - INFO - INFO: Agent 33 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:34,204 - Stocklogger - INFO - INFO: Agent 33 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:35,236 - Stocklogger - INFO - INFO: Agent 37 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:35,236 - Stocklogger - INFO - INFO: Agent 37 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:36,121 - Stocklogger - INFO - INFO: Agent 21 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.5} +2025-10-24 17:34:36,121 - Stocklogger - INFO - INFO: Agent 21 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.5} +2025-10-24 17:34:37,361 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:37,361 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:38,489 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:38,489 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:39,631 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:34:39,631 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:34:42,436 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:42,436 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:43,744 - Stocklogger - INFO - INFO: Agent 19 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:43,744 - Stocklogger - INFO - INFO: Agent 19 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:45,962 - Stocklogger - INFO - INFO: Agent 22 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 2000, 'price': 32.5} +2025-10-24 17:34:45,962 - Stocklogger - INFO - INFO: Agent 22 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 2000, 'price': 32.5} +2025-10-24 17:34:47,057 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:34:47,057 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:34:48,555 - Stocklogger - INFO - INFO: Agent 35 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:48,555 - Stocklogger - INFO - INFO: Agent 35 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:49,714 - Stocklogger - INFO - INFO: Agent 17 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:49,714 - Stocklogger - INFO - INFO: Agent 17 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:34:50,951 - Stocklogger - INFO - INFO: Agent 34 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:50,951 - Stocklogger - INFO - INFO: Agent 34 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:52,299 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:52,299 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:53,744 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:34:53,744 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.0} +2025-10-24 17:34:54,870 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:54,870 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:56,147 - Stocklogger - INFO - INFO: Agent 42 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:56,147 - Stocklogger - INFO - INFO: Agent 42 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:57,033 - Stocklogger - INFO - INFO: Agent 23 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:57,033 - Stocklogger - INFO - INFO: Agent 23 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:34:59,148 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:34:59,148 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:35:00,405 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:35:00,405 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 10000, 'price': 32.5} +2025-10-24 17:35:01,619 - Stocklogger - INFO - INFO: Agent 46 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:01,619 - Stocklogger - INFO - INFO: Agent 46 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:03,124 - Stocklogger - INFO - INFO: Agent 32 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:03,124 - Stocklogger - INFO - INFO: Agent 32 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:04,824 - Stocklogger - INFO - INFO: Agent 24 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:04,824 - Stocklogger - INFO - INFO: Agent 24 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:05,889 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:35:05,889 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:35:07,172 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:07,172 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:08,145 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:08,145 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:09,370 - Stocklogger - INFO - INFO: Agent 45 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:09,370 - Stocklogger - INFO - INFO: Agent 45 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:10,641 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:10,641 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:12,477 - Stocklogger - INFO - INFO: Agent 26 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:12,477 - Stocklogger - INFO - INFO: Agent 26 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:13,373 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:13,373 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:15,653 - Stocklogger - INFO - INFO: Agent 28 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:35:15,653 - Stocklogger - INFO - INFO: Agent 28 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:35:16,778 - Stocklogger - INFO - INFO: Agent 21 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:16,778 - Stocklogger - INFO - INFO: Agent 21 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:19,082 - Stocklogger - INFO - INFO: Agent 44 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.5} +2025-10-24 17:35:19,082 - Stocklogger - INFO - INFO: Agent 44 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.5} +2025-10-24 17:35:20,907 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:20,907 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:22,310 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:22,310 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:23,966 - Stocklogger - INFO - INFO: Agent 15 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:23,966 - Stocklogger - INFO - INFO: Agent 15 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:25,055 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:25,055 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:26,582 - Stocklogger - INFO - INFO: Agent 11 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:26,582 - Stocklogger - INFO - INFO: Agent 11 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:28,060 - Stocklogger - INFO - INFO: Agent 47 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:28,060 - Stocklogger - INFO - INFO: Agent 47 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:30,151 - Stocklogger - INFO - INFO: Agent 37 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:30,151 - Stocklogger - INFO - INFO: Agent 37 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:31,431 - Stocklogger - INFO - INFO: Agent 43 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:31,431 - Stocklogger - INFO - INFO: Agent 43 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:34,513 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:34,513 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:35:37,950 - Stocklogger - INFO - INFO: Agent 12 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:37,950 - Stocklogger - INFO - INFO: Agent 12 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:39,429 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:39,429 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:41,794 - Stocklogger - INFO - INFO: Agent 31 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:41,794 - Stocklogger - INFO - INFO: Agent 31 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:42,858 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:42,858 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:43,904 - Stocklogger - INFO - INFO: Agent 20 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:43,904 - Stocklogger - INFO - INFO: Agent 20 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:45,982 - Stocklogger - INFO - INFO: Agent 41 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:45,982 - Stocklogger - INFO - INFO: Agent 41 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:47,009 - Stocklogger - INFO - INFO: Agent 29 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:47,009 - Stocklogger - INFO - INFO: Agent 29 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:48,172 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:48,172 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:49,348 - Stocklogger - INFO - INFO: Agent 10 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:49,348 - Stocklogger - INFO - INFO: Agent 10 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:52,969 - Stocklogger - INFO - INFO: Agent 48 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:35:52,969 - Stocklogger - INFO - INFO: Agent 48 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:41:29,495 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:41:29,495 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:41:30,122 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:41:30,122 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:41:30,796 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:41:30,796 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:41:31,831 - Stocklogger - INFO - INFO: Agent 2 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 1535527.084900642, 'repayment_date': 67} +2025-10-24 17:41:31,831 - Stocklogger - INFO - INFO: Agent 2 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 1535527.084900642, 'repayment_date': 67} +2025-10-24 17:41:32,726 - Stocklogger - INFO - INFO: Agent 3 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1500000, 'repayment_date': 45} +2025-10-24 17:41:32,726 - Stocklogger - INFO - INFO: Agent 3 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1500000, 'repayment_date': 45} +2025-10-24 17:41:33,452 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:41:33,452 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:41:34,043 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:41:34,043 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:41:35,946 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 17:41:35,946 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 17:41:36,574 - Stocklogger - INFO - INFO: Agent 7 decide not to loan +2025-10-24 17:41:36,574 - Stocklogger - INFO - INFO: Agent 7 decide not to loan +2025-10-24 17:41:37,073 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 17:41:37,073 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 17:41:38,259 - Stocklogger - INFO - INFO: Agent 9 decide not to loan +2025-10-24 17:41:38,259 - Stocklogger - INFO - INFO: Agent 9 decide not to loan +2025-10-24 17:41:38,730 - Stocklogger - INFO - INFO: Agent 10 decide not to loan +2025-10-24 17:41:38,730 - Stocklogger - INFO - INFO: Agent 10 decide not to loan +2025-10-24 17:41:39,671 - Stocklogger - INFO - INFO: Agent 11 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 613969.1926603939, 'repayment_date': 45} +2025-10-24 17:41:39,671 - Stocklogger - INFO - INFO: Agent 11 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 613969.1926603939, 'repayment_date': 45} +2025-10-24 17:41:40,195 - Stocklogger - INFO - INFO: Agent 12 decide not to loan +2025-10-24 17:41:40,195 - Stocklogger - INFO - INFO: Agent 12 decide not to loan +2025-10-24 17:41:40,693 - Stocklogger - INFO - INFO: Agent 13 decide not to loan +2025-10-24 17:41:40,693 - Stocklogger - INFO - INFO: Agent 13 decide not to loan +2025-10-24 17:41:41,110 - Stocklogger - INFO - INFO: Agent 14 decide not to loan +2025-10-24 17:41:41,110 - Stocklogger - INFO - INFO: Agent 14 decide not to loan +2025-10-24 17:41:42,456 - Stocklogger - DEBUG - Wrong json content in response: {"loan": "yes", "loan_type": 1, "amount": 2500000} +2025-10-24 17:41:42,456 - Stocklogger - DEBUG - Wrong json content in response: {"loan": "yes", "loan_type": 1, "amount": 2500000} +2025-10-24 17:41:43,545 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 17:41:43,545 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 17:41:44,766 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 17:41:44,766 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 17:41:45,626 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 17:41:45,626 - Stocklogger - DEBUG - Wrong json content in response: 1 +2025-10-24 17:41:45,626 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 17:41:45,626 - Stocklogger - WARNING - WARNING: Loan format try times > MAX_TRY_TIMES. Skip as no loan today. +2025-10-24 17:41:45,626 - Stocklogger - INFO - INFO: Agent 15 decide not to loan +2025-10-24 17:41:45,626 - Stocklogger - INFO - INFO: Agent 15 decide not to loan +2025-10-24 17:41:46,243 - Stocklogger - INFO - INFO: Agent 16 decide not to loan +2025-10-24 17:41:46,243 - Stocklogger - INFO - INFO: Agent 16 decide not to loan +2025-10-24 17:41:46,756 - Stocklogger - INFO - INFO: Agent 17 decide not to loan +2025-10-24 17:41:46,756 - Stocklogger - INFO - INFO: Agent 17 decide not to loan +2025-10-24 17:41:47,446 - Stocklogger - INFO - INFO: Agent 18 decide not to loan +2025-10-24 17:41:47,446 - Stocklogger - INFO - INFO: Agent 18 decide not to loan +2025-10-24 17:41:48,229 - Stocklogger - INFO - INFO: Agent 19 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 300000, 'repayment_date': 45} +2025-10-24 17:41:48,229 - Stocklogger - INFO - INFO: Agent 19 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 300000, 'repayment_date': 45} +2025-10-24 17:41:48,680 - Stocklogger - INFO - INFO: Agent 20 decide not to loan +2025-10-24 17:41:48,680 - Stocklogger - INFO - INFO: Agent 20 decide not to loan +2025-10-24 17:41:49,568 - Stocklogger - INFO - INFO: Agent 21 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 1963085.8964035409, 'repayment_date': 23} +2025-10-24 17:41:49,568 - Stocklogger - INFO - INFO: Agent 21 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 1963085.8964035409, 'repayment_date': 23} +2025-10-24 17:41:50,122 - Stocklogger - INFO - INFO: Agent 22 decide not to loan +2025-10-24 17:41:50,122 - Stocklogger - INFO - INFO: Agent 22 decide not to loan +2025-10-24 17:41:50,891 - Stocklogger - INFO - INFO: Agent 23 decide not to loan +2025-10-24 17:41:50,891 - Stocklogger - INFO - INFO: Agent 23 decide not to loan +2025-10-24 17:41:51,838 - Stocklogger - INFO - INFO: Agent 24 decide not to loan +2025-10-24 17:41:51,838 - Stocklogger - INFO - INFO: Agent 24 decide not to loan +2025-10-24 17:41:52,538 - Stocklogger - INFO - INFO: Agent 25 decide not to loan +2025-10-24 17:41:52,538 - Stocklogger - INFO - INFO: Agent 25 decide not to loan +2025-10-24 17:41:53,636 - Stocklogger - INFO - INFO: Agent 26 decide not to loan +2025-10-24 17:41:53,636 - Stocklogger - INFO - INFO: Agent 26 decide not to loan +2025-10-24 17:41:54,255 - Stocklogger - INFO - INFO: Agent 27 decide not to loan +2025-10-24 17:41:54,255 - Stocklogger - INFO - INFO: Agent 27 decide not to loan +2025-10-24 17:41:54,886 - Stocklogger - INFO - INFO: Agent 28 decide not to loan +2025-10-24 17:41:54,886 - Stocklogger - INFO - INFO: Agent 28 decide not to loan +2025-10-24 17:41:55,477 - Stocklogger - INFO - INFO: Agent 29 decide not to loan +2025-10-24 17:41:55,477 - Stocklogger - INFO - INFO: Agent 29 decide not to loan +2025-10-24 17:41:55,887 - Stocklogger - INFO - INFO: Agent 30 decide not to loan +2025-10-24 17:41:55,887 - Stocklogger - INFO - INFO: Agent 30 decide not to loan +2025-10-24 17:41:56,257 - Stocklogger - INFO - INFO: Agent 31 decide not to loan +2025-10-24 17:41:56,257 - Stocklogger - INFO - INFO: Agent 31 decide not to loan +2025-10-24 17:41:57,120 - Stocklogger - INFO - INFO: Agent 32 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 1419975.7602803316, 'repayment_date': 67} +2025-10-24 17:41:57,120 - Stocklogger - INFO - INFO: Agent 32 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 1419975.7602803316, 'repayment_date': 67} +2025-10-24 17:41:57,680 - Stocklogger - INFO - INFO: Agent 33 decide not to loan +2025-10-24 17:41:57,680 - Stocklogger - INFO - INFO: Agent 33 decide not to loan +2025-10-24 17:41:58,135 - Stocklogger - INFO - INFO: Agent 34 decide not to loan +2025-10-24 17:41:58,135 - Stocklogger - INFO - INFO: Agent 34 decide not to loan +2025-10-24 17:41:58,894 - Stocklogger - INFO - INFO: Agent 35 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 100000, 'repayment_date': 45} +2025-10-24 17:41:58,894 - Stocklogger - INFO - INFO: Agent 35 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 100000, 'repayment_date': 45} +2025-10-24 17:41:59,537 - Stocklogger - INFO - INFO: Agent 36 decide not to loan +2025-10-24 17:41:59,537 - Stocklogger - INFO - INFO: Agent 36 decide not to loan +2025-10-24 17:42:00,065 - Stocklogger - INFO - INFO: Agent 37 decide not to loan +2025-10-24 17:42:00,065 - Stocklogger - INFO - INFO: Agent 37 decide not to loan +2025-10-24 17:42:00,609 - Stocklogger - INFO - INFO: Agent 38 decide not to loan +2025-10-24 17:42:00,609 - Stocklogger - INFO - INFO: Agent 38 decide not to loan +2025-10-24 17:42:01,167 - Stocklogger - INFO - INFO: Agent 39 decide not to loan +2025-10-24 17:42:01,167 - Stocklogger - INFO - INFO: Agent 39 decide not to loan +2025-10-24 17:42:02,078 - Stocklogger - INFO - INFO: Agent 40 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2000000, 'repayment_date': 45} +2025-10-24 17:42:02,078 - Stocklogger - INFO - INFO: Agent 40 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2000000, 'repayment_date': 45} +2025-10-24 17:42:02,603 - Stocklogger - INFO - INFO: Agent 41 decide not to loan +2025-10-24 17:42:02,603 - Stocklogger - INFO - INFO: Agent 41 decide not to loan +2025-10-24 17:42:03,549 - Stocklogger - INFO - INFO: Agent 42 decide not to loan +2025-10-24 17:42:03,549 - Stocklogger - INFO - INFO: Agent 42 decide not to loan +2025-10-24 17:42:04,303 - Stocklogger - INFO - INFO: Agent 43 decide not to loan +2025-10-24 17:42:04,303 - Stocklogger - INFO - INFO: Agent 43 decide not to loan +2025-10-24 17:42:05,173 - Stocklogger - INFO - INFO: Agent 44 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 4714070.392596438, 'repayment_date': 67} +2025-10-24 17:42:05,173 - Stocklogger - INFO - INFO: Agent 44 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 4714070.392596438, 'repayment_date': 67} +2025-10-24 17:42:05,794 - Stocklogger - INFO - INFO: Agent 45 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2000000, 'repayment_date': 45} +2025-10-24 17:42:05,794 - Stocklogger - INFO - INFO: Agent 45 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 2000000, 'repayment_date': 45} +2025-10-24 17:42:06,630 - Stocklogger - INFO - INFO: Agent 46 decide not to loan +2025-10-24 17:42:06,630 - Stocklogger - INFO - INFO: Agent 46 decide not to loan +2025-10-24 17:42:07,112 - Stocklogger - INFO - INFO: Agent 47 decide not to loan +2025-10-24 17:42:07,112 - Stocklogger - INFO - INFO: Agent 47 decide not to loan +2025-10-24 17:42:07,986 - Stocklogger - INFO - INFO: Agent 48 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1175968.1859453148, 'repayment_date': 45} +2025-10-24 17:42:07,986 - Stocklogger - INFO - INFO: Agent 48 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1175968.1859453148, 'repayment_date': 45} +2025-10-24 17:42:08,472 - Stocklogger - INFO - INFO: Agent 49 decide not to loan +2025-10-24 17:42:08,472 - Stocklogger - INFO - INFO: Agent 49 decide not to loan +2025-10-24 17:42:09,466 - Stocklogger - INFO - INFO: Agent 36 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 50, 'price': 42.0} +2025-10-24 17:42:09,466 - Stocklogger - INFO - INFO: Agent 36 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 50, 'price': 42.0} +2025-10-24 17:42:10,295 - Stocklogger - INFO - INFO: Agent 29 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 17:42:10,295 - Stocklogger - INFO - INFO: Agent 29 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 17:42:11,293 - Stocklogger - INFO - INFO: Agent 15 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 31.0} +2025-10-24 17:42:11,293 - Stocklogger - INFO - INFO: Agent 15 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 31.0} +2025-10-24 17:42:12,962 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 31.5} +2025-10-24 17:42:12,962 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 31.5} +2025-10-24 17:42:13,821 - Stocklogger - INFO - INFO: Agent 48 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.0} +2025-10-24 17:42:13,821 - Stocklogger - INFO - INFO: Agent 48 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.0} +2025-10-24 17:42:14,937 - Stocklogger - INFO - INFO: Agent 17 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 2000, 'price': 31.0} +2025-10-24 17:42:14,937 - Stocklogger - INFO - INFO: Agent 17 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 2000, 'price': 31.0} +2025-10-24 17:42:16,559 - Stocklogger - INFO - INFO: Agent 21 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.5} +2025-10-24 17:42:16,559 - Stocklogger - INFO - INFO: Agent 21 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.5} +2025-10-24 17:42:17,850 - Stocklogger - DEBUG - Sell more than hold: {"action_type": "sell", "stock": "A", "amount": 1000, "price": 31.5} +2025-10-24 17:42:17,850 - Stocklogger - DEBUG - Sell more than hold: {"action_type": "sell", "stock": "A", "amount": 1000, "price": 31.5} +2025-10-24 17:42:18,398 - Stocklogger - INFO - INFO: Agent 8 decide not to action +2025-10-24 17:42:18,398 - Stocklogger - INFO - INFO: Agent 8 decide not to action +2025-10-24 17:42:19,272 - Stocklogger - INFO - INFO: Agent 11 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.5} +2025-10-24 17:42:19,272 - Stocklogger - INFO - INFO: Agent 11 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.5} +2025-10-24 17:42:20,327 - Stocklogger - INFO - INFO: Agent 40 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:20,327 - Stocklogger - INFO - INFO: Agent 40 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:21,319 - Stocklogger - INFO - INFO: Agent 26 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:21,319 - Stocklogger - INFO - INFO: Agent 26 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:23,461 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:23,461 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:25,095 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:25,095 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:25,916 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:25,916 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:26,836 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:26,836 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:28,122 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:28,122 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:29,030 - Stocklogger - INFO - INFO: Agent 46 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:29,030 - Stocklogger - INFO - INFO: Agent 46 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:30,555 - Stocklogger - INFO - INFO: Agent 42 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:30,555 - Stocklogger - INFO - INFO: Agent 42 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.0} +2025-10-24 17:42:31,605 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.0} +2025-10-24 17:42:31,605 - Stocklogger - INFO - INFO: Agent 27 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.0} +2025-10-24 17:42:32,688 - Stocklogger - INFO - INFO: Agent 24 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:32,688 - Stocklogger - INFO - INFO: Agent 24 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:33,547 - Stocklogger - INFO - INFO: Agent 22 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:33,547 - Stocklogger - INFO - INFO: Agent 22 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:34,836 - Stocklogger - INFO - INFO: Agent 47 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.0} +2025-10-24 17:42:34,836 - Stocklogger - INFO - INFO: Agent 47 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.0} +2025-10-24 17:42:35,951 - Stocklogger - INFO - INFO: Agent 10 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:35,951 - Stocklogger - INFO - INFO: Agent 10 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:37,477 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:37,477 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:39,108 - Stocklogger - INFO - INFO: Agent 12 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:39,108 - Stocklogger - INFO - INFO: Agent 12 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:40,254 - Stocklogger - INFO - INFO: Agent 32 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:40,254 - Stocklogger - INFO - INFO: Agent 32 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:41,036 - Stocklogger - INFO - INFO: Agent 31 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:41,036 - Stocklogger - INFO - INFO: Agent 31 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:41,987 - Stocklogger - INFO - INFO: Agent 19 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:41,987 - Stocklogger - INFO - INFO: Agent 19 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:43,206 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:43,206 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:45,707 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:45,707 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:47,078 - Stocklogger - INFO - INFO: Agent 34 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:47,078 - Stocklogger - INFO - INFO: Agent 34 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:48,492 - Stocklogger - INFO - INFO: Agent 23 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:48,492 - Stocklogger - INFO - INFO: Agent 23 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:49,523 - Stocklogger - INFO - INFO: Agent 43 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:49,523 - Stocklogger - INFO - INFO: Agent 43 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:50,720 - Stocklogger - INFO - INFO: Agent 35 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:50,720 - Stocklogger - INFO - INFO: Agent 35 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:51,649 - Stocklogger - INFO - INFO: Agent 28 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:51,649 - Stocklogger - INFO - INFO: Agent 28 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:53,498 - Stocklogger - INFO - INFO: Agent 37 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.0} +2025-10-24 17:42:53,498 - Stocklogger - INFO - INFO: Agent 37 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.0} +2025-10-24 17:42:54,541 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:54,541 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:55,699 - Stocklogger - INFO - INFO: Agent 44 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:55,699 - Stocklogger - INFO - INFO: Agent 44 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:56,542 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:56,542 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:57,367 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:57,367 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:58,269 - Stocklogger - INFO - INFO: Agent 20 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:42:58,269 - Stocklogger - INFO - INFO: Agent 20 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:00,098 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:00,098 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:01,050 - Stocklogger - INFO - INFO: Agent 41 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:01,050 - Stocklogger - INFO - INFO: Agent 41 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:01,912 - Stocklogger - INFO - INFO: Agent 49 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.0} +2025-10-24 17:43:01,912 - Stocklogger - INFO - INFO: Agent 49 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.0} +2025-10-24 17:43:03,132 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:03,132 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:04,942 - Stocklogger - INFO - INFO: Agent 45 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:04,942 - Stocklogger - INFO - INFO: Agent 45 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:06,101 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:06,101 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:07,077 - Stocklogger - INFO - INFO: Agent 33 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.5} +2025-10-24 17:43:07,077 - Stocklogger - INFO - INFO: Agent 33 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.5} +2025-10-24 17:43:08,316 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.5} +2025-10-24 17:43:08,316 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 32.5} +2025-10-24 17:43:09,771 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:09,771 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:11,127 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:11,127 - Stocklogger - INFO - INFO: Agent 38 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:12,082 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:12,082 - Stocklogger - INFO - INFO: Agent 18 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:13,385 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:13,385 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:14,584 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:14,584 - Stocklogger - INFO - INFO: Agent 39 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.5} +2025-10-24 17:43:15,721 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.5} +2025-10-24 17:43:15,721 - Stocklogger - INFO - INFO: Agent 16 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.5} +2025-10-24 17:43:16,792 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 1000, 'price': 42.0} +2025-10-24 17:43:16,792 - Stocklogger - INFO - INFO: Agent 30 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 1000, 'price': 42.0} +2025-10-24 17:43:16,802 - Stocklogger - INFO - ACTION - BUY:36, SELL:30, STOCK:B, PRICE:42.0, AMOUNT:50 +2025-10-24 17:43:16,802 - Stocklogger - INFO - ACTION - BUY:36, SELL:30, STOCK:B, PRICE:42.0, AMOUNT:50 +2025-10-24 17:43:18,041 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:18,041 - Stocklogger - INFO - INFO: Agent 14 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:19,031 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 17:43:19,031 - Stocklogger - INFO - INFO: Agent 13 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 17:43:20,316 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:20,316 - Stocklogger - INFO - INFO: Agent 25 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:43:20,328 - Stocklogger - INFO - ACTION - BUY:38, SELL:25, STOCK:B, PRICE:41.5, AMOUNT:100 +2025-10-24 17:43:20,328 - Stocklogger - INFO - ACTION - BUY:38, SELL:25, STOCK:B, PRICE:41.5, AMOUNT:100 +2025-10-24 17:43:22,042 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.0} +2025-10-24 17:43:22,042 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 32.0} +2025-10-24 17:43:23,015 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 17:43:23,015 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 17:43:24,397 - Stocklogger - INFO - INFO: Agent 36 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.5} +2025-10-24 17:43:24,397 - Stocklogger - INFO - INFO: Agent 36 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 32.5} +2025-10-24 17:44:07,323 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:44:07,323 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:44:08,084 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:44:08,084 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:44:09,297 - Stocklogger - INFO - INFO: Agent 1 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 706263.3939059642, 'repayment_date': 45} +2025-10-24 17:44:09,297 - Stocklogger - INFO - INFO: Agent 1 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 706263.3939059642, 'repayment_date': 45} +2025-10-24 17:44:09,890 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:44:09,890 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:44:10,388 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 17:44:10,388 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 17:44:10,998 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:44:10,998 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:44:11,662 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:44:11,662 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:44:12,545 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:44:12,545 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:44:13,596 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:44:13,596 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:44:14,498 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:44:14,498 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:44:15,591 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:44:15,591 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:44:16,611 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:44:16,611 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:44:18,013 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:44:18,013 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:44:19,410 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:44:19,410 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:44:19,421 - Stocklogger - INFO - ACTION - BUY:1, SELL:3, STOCK:B, PRICE:42.0, AMOUNT:100 +2025-10-24 17:44:19,421 - Stocklogger - INFO - ACTION - BUY:1, SELL:3, STOCK:B, PRICE:42.0, AMOUNT:100 +2025-10-24 17:44:20,696 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:44:20,696 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 42.5} +2025-10-24 17:44:22,093 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 43.0} +2025-10-24 17:44:22,093 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 43.0} +2025-10-24 17:44:23,513 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 43.0} +2025-10-24 17:44:23,513 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 43.0} +2025-10-24 17:44:25,419 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 43.5} +2025-10-24 17:44:25,419 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 43.5} +2025-10-24 17:44:27,764 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 43.0} +2025-10-24 17:44:27,764 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 43.0} +2025-10-24 17:44:52,623 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:44:52,623 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:44:53,647 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:44:53,647 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:44:57,828 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 17:44:57,828 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 17:45:00,938 - Stocklogger - INFO - INFO: Agent 4 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 2000000, 'repayment_date': 24} +2025-10-24 17:45:00,938 - Stocklogger - INFO - INFO: Agent 4 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 2000000, 'repayment_date': 24} +2025-10-24 17:45:02,265 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:45:02,265 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:45:03,836 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 17:45:03,836 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 17:49:34,400 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:49:34,400 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:49:35,123 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:49:35,123 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:49:35,859 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:49:35,859 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:49:36,466 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:49:36,466 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:49:37,230 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 17:49:37,230 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 17:49:37,971 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:49:37,971 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:49:38,551 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:49:38,551 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:49:39,279 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 50, 'price': 40.5} +2025-10-24 17:49:39,279 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 50, 'price': 40.5} +2025-10-24 17:49:40,309 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.0} +2025-10-24 17:49:40,309 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.0} +2025-10-24 17:49:41,606 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 50, 'price': 45.0} +2025-10-24 17:49:41,606 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 50, 'price': 45.0} +2025-10-24 17:49:43,314 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:49:43,314 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:49:44,370 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:49:44,370 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:49:45,434 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:49:45,434 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:49:46,604 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:49:46,604 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:49:47,663 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:49:47,663 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:49:48,693 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:49:48,693 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:49:49,537 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:49:49,537 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:49:50,524 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:49:50,524 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:49:51,549 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:49:51,549 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:50:25,926 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:50:25,926 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:50:26,975 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:50:26,975 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:50:28,118 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:50:28,118 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:50:29,232 - Stocklogger - INFO - INFO: Agent 3 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 2251678.8665688443, 'repayment_date': 68} +2025-10-24 17:50:29,232 - Stocklogger - INFO - INFO: Agent 3 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 2251678.8665688443, 'repayment_date': 68} +2025-10-24 17:50:30,188 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:50:30,188 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:50:31,313 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:50:31,313 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:50:32,711 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:50:32,711 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:50:33,968 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:50:33,968 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:50:35,416 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:50:35,416 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:50:36,659 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.0} +2025-10-24 17:50:36,659 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.0} +2025-10-24 17:50:37,478 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:50:37,478 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:50:38,644 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 1000, 'price': 45.0} +2025-10-24 17:50:38,644 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 1000, 'price': 45.0} +2025-10-24 17:50:40,472 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:50:40,472 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.5} +2025-10-24 17:50:41,616 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:50:41,616 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:50:42,497 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 45.0} +2025-10-24 17:50:42,497 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 45.0} +2025-10-24 17:50:44,323 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:50:44,323 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:50:45,482 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 500, 'price': 45.0} +2025-10-24 17:50:45,482 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 500, 'price': 45.0} +2025-10-24 17:50:46,498 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.0} +2025-10-24 17:50:46,498 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 45.0} +2025-10-24 17:51:27,496 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:51:27,496 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:51:28,320 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:51:28,320 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 17:51:28,825 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:51:28,825 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:51:29,601 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:51:29,601 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:51:30,609 - Stocklogger - INFO - INFO: Agent 5 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 3034247.749039442, 'repayment_date': 69} +2025-10-24 17:51:30,609 - Stocklogger - INFO - INFO: Agent 5 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 3034247.749039442, 'repayment_date': 69} +2025-10-24 17:51:31,846 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 50, 'price': 41.0} +2025-10-24 17:51:31,846 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 50, 'price': 41.0} +2025-10-24 17:51:32,802 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 50, 'price': 41.5} +2025-10-24 17:51:32,802 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 50, 'price': 41.5} +2025-10-24 17:51:38,284 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 50, 'price': 41.5} +2025-10-24 17:51:38,284 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 50, 'price': 41.5} +2025-10-24 17:51:39,431 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:39,431 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:40,844 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:40,844 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:42,156 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:42,156 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:43,446 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:43,446 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:44,538 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 39.5} +2025-10-24 17:51:44,538 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 39.5} +2025-10-24 17:51:45,694 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:45,694 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:48,680 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:48,680 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:51:49,966 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 39.0} +2025-10-24 17:51:49,966 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 39.0} +2025-10-24 17:51:50,998 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 39.0} +2025-10-24 17:51:50,998 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 39.0} +2025-10-24 17:53:59,891 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:53:59,891 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 17:54:00,625 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:54:00,625 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 17:54:01,684 - Stocklogger - INFO - INFO: Agent 1 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 200000, 'repayment_date': 45} +2025-10-24 17:54:01,684 - Stocklogger - INFO - INFO: Agent 1 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 200000, 'repayment_date': 45} +2025-10-24 17:54:02,371 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:54:02,371 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 17:54:03,195 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 17:54:03,195 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 17:54:03,859 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:54:03,859 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 17:54:04,633 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:54:04,633 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 17:54:05,318 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 17:54:05,318 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 17:54:06,545 - Stocklogger - INFO - INFO: Agent 7 decide not to loan +2025-10-24 17:54:06,545 - Stocklogger - INFO - INFO: Agent 7 decide not to loan +2025-10-24 17:54:07,282 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 17:54:07,282 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 17:54:07,748 - Stocklogger - INFO - INFO: Agent 9 decide not to loan +2025-10-24 17:54:07,748 - Stocklogger - INFO - INFO: Agent 9 decide not to loan +2025-10-24 17:54:09,384 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:09,384 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:10,426 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 50, 'price': 30.5} +2025-10-24 17:54:10,426 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 50, 'price': 30.5} +2025-10-24 17:54:11,790 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 31.0} +2025-10-24 17:54:11,790 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 31.0} +2025-10-24 17:54:13,612 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 31.5} +2025-10-24 17:54:13,612 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 31.5} +2025-10-24 17:54:14,757 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:14,757 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:15,381 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 31.5} +2025-10-24 17:54:15,381 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 31.5} +2025-10-24 17:54:16,610 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:16,610 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:17,681 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:17,681 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:18,741 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:18,741 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:20,000 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:20,000 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:21,480 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:21,480 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:22,906 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 32.5} +2025-10-24 17:54:22,906 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 32.5} +2025-10-24 17:54:23,860 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:23,860 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:25,036 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.5} +2025-10-24 17:54:25,036 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.5} +2025-10-24 17:54:26,017 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:54:26,017 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:54:26,904 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:26,904 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 32.0} +2025-10-24 17:54:27,767 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.0} +2025-10-24 17:54:27,767 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.0} +2025-10-24 17:54:28,834 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:54:28,834 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:54:30,736 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:54:30,736 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:54:32,183 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:32,183 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:33,344 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.0} +2025-10-24 17:54:33,344 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.0} +2025-10-24 17:54:34,381 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:34,381 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:36,893 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:54:36,893 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:54:37,784 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 17:54:37,784 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 17:54:38,893 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:54:38,893 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:54:39,948 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:54:39,948 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:54:40,962 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 17:54:40,962 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 17:54:41,942 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:54:41,942 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:54:43,207 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.5} +2025-10-24 17:54:43,207 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.5} +2025-10-24 17:54:44,708 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:54:44,708 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.0} +2025-10-24 17:54:45,559 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:54:45,559 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:54:46,810 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:46,810 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:48,445 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:54:48,445 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:54:50,367 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:54:50,367 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 17:54:54,183 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.0} +2025-10-24 17:54:54,183 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.0} +2025-10-24 17:54:56,539 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 250, 'price': 42.5} +2025-10-24 17:54:56,539 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 250, 'price': 42.5} +2025-10-24 17:54:58,058 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:58,058 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:54:58,983 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:54:58,983 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 17:55:00,072 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:55:00,072 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 17:55:01,389 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.5} +2025-10-24 17:55:01,389 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.5} +2025-10-24 17:55:03,713 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.5} +2025-10-24 17:55:03,713 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.5} +2025-10-24 17:55:04,634 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 17:55:04,634 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 17:55:06,847 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:55:06,847 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:55:08,255 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.5} +2025-10-24 17:55:08,255 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.5} +2025-10-24 17:55:09,953 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.5} +2025-10-24 17:55:09,953 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.5} +2025-10-24 17:55:11,749 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 300, 'price': 42.5} +2025-10-24 17:55:11,749 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 300, 'price': 42.5} +2025-10-24 17:55:12,739 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:55:12,739 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:55:14,325 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 17:55:14,325 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 17:55:15,538 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:55:15,538 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 17:55:16,775 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.5} +2025-10-24 17:55:16,775 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'B', 'amount': 200, 'price': 42.5} +2025-10-24 18:10:23,077 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 18:10:23,077 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 18:10:25,453 - Stocklogger - INFO - INFO: Agent 0 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 648148.4391193846, 'repayment_date': 45} +2025-10-24 18:10:25,453 - Stocklogger - INFO - INFO: Agent 0 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 648148.4391193846, 'repayment_date': 45} +2025-10-24 18:10:26,971 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 18:10:26,971 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 18:10:27,578 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 18:10:27,578 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 18:10:29,094 - Stocklogger - INFO - INFO: Agent 3 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1415116.1833927361, 'repayment_date': 45} +2025-10-24 18:10:29,094 - Stocklogger - INFO - INFO: Agent 3 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 1415116.1833927361, 'repayment_date': 45} +2025-10-24 18:10:30,195 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 18:10:30,195 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 18:10:31,223 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 18:10:31,223 - Stocklogger - INFO - INFO: Agent 5 decide not to loan +2025-10-24 18:10:32,541 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 18:10:32,541 - Stocklogger - INFO - INFO: Agent 6 decide not to loan +2025-10-24 18:10:33,966 - Stocklogger - INFO - INFO: Agent 7 decide not to loan +2025-10-24 18:10:33,966 - Stocklogger - INFO - INFO: Agent 7 decide not to loan +2025-10-24 18:10:35,336 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 18:10:35,336 - Stocklogger - INFO - INFO: Agent 8 decide not to loan +2025-10-24 18:10:36,058 - Stocklogger - INFO - INFO: Agent 9 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 200000, 'repayment_date': 67} +2025-10-24 18:10:36,058 - Stocklogger - INFO - INFO: Agent 9 decide to loan: {'loan': 'yes', 'loan_type': 2, 'amount': 200000, 'repayment_date': 67} +2025-10-24 18:10:36,990 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 18:10:36,990 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.0} +2025-10-24 18:10:37,957 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 18:10:37,957 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 42.5} +2025-10-24 18:10:39,026 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 30.5} +2025-10-24 18:10:39,026 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 30.5} +2025-10-24 18:10:40,628 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 30.5} +2025-10-24 18:10:40,628 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 30.5} +2025-10-24 18:10:42,339 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:42,339 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:43,538 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:43,538 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:45,255 - Stocklogger - DEBUG - Sell more than hold: {"action_type": "sell", "stock": "A", "amount": 6000, "price": 30.5} +2025-10-24 18:10:45,255 - Stocklogger - DEBUG - Sell more than hold: {"action_type": "sell", "stock": "A", "amount": 6000, "price": 30.5} +2025-10-24 18:10:47,101 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 18:10:47,101 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 18:10:48,228 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:48,228 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:49,390 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:49,390 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:50,435 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:50,435 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:10:51,897 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.0} +2025-10-24 18:10:51,897 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 3000, 'price': 31.0} +2025-10-24 18:10:53,619 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:10:53,619 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:10:55,906 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.0} +2025-10-24 18:10:55,906 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.0} +2025-10-24 18:10:57,219 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:10:57,219 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:10:58,138 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:10:58,138 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:11:00,202 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:11:00,202 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:11:01,251 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.5} +2025-10-24 18:11:01,251 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.5} +2025-10-24 18:11:02,373 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:11:02,373 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:11:04,534 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 31.0} +2025-10-24 18:11:04,534 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 31.0} +2025-10-24 18:11:06,196 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:06,196 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:08,868 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:11:08,868 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:11:10,631 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 18:11:10,631 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.0} +2025-10-24 18:11:11,946 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:11:11,946 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 30.5} +2025-10-24 18:11:13,181 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 400, 'price': 41.0} +2025-10-24 18:11:13,181 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 400, 'price': 41.0} +2025-10-24 18:11:14,121 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:14,121 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:15,952 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 31.0} +2025-10-24 18:11:15,952 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 31.0} +2025-10-24 18:11:17,102 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 400, 'price': 41.0} +2025-10-24 18:11:17,102 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 400, 'price': 41.0} +2025-10-24 18:11:21,756 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:21,756 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:22,982 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 31.0} +2025-10-24 18:11:22,982 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 6000, 'price': 31.0} +2025-10-24 18:11:24,245 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 1000, 'price': 41.0} +2025-10-24 18:11:24,245 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 1000, 'price': 41.0} +2025-10-24 18:11:25,297 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 400, 'price': 41.0} +2025-10-24 18:11:25,297 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 400, 'price': 41.0} +2025-10-24 18:11:26,552 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:26,552 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:28,091 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.0} +2025-10-24 18:11:28,091 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.0} +2025-10-24 18:11:29,578 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.0} +2025-10-24 18:11:29,578 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.0} +2025-10-24 18:11:31,086 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:31,086 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:11:42,385 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:11:42,385 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:11:44,490 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.0} +2025-10-24 18:11:44,490 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.0} +2025-10-24 18:11:45,612 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:11:45,612 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:11:47,460 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:11:47,460 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:11:49,284 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:11:49,284 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:11:50,722 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:11:50,722 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:11:52,412 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:11:52,412 - Stocklogger - INFO - INFO: Agent 5 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:11:53,444 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:11:53,444 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:11:54,929 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 1000, 'price': 41.0} +2025-10-24 18:11:54,929 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 1000, 'price': 41.0} +2025-10-24 18:12:07,331 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 8000, 'price': 31.5} +2025-10-24 18:12:07,331 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 8000, 'price': 31.5} +2025-10-24 18:12:09,200 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:12:09,200 - Stocklogger - INFO - INFO: Agent 6 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:12:10,677 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:12:10,677 - Stocklogger - INFO - INFO: Agent 8 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.0} +2025-10-24 18:12:12,418 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.5} +2025-10-24 18:12:12,418 - Stocklogger - INFO - INFO: Agent 9 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.5} +2025-10-24 18:12:16,954 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:12:16,954 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 600, 'price': 41.0} +2025-10-24 18:12:18,100 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.5} +2025-10-24 18:12:18,100 - Stocklogger - INFO - INFO: Agent 7 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 5000, 'price': 31.5} +2025-10-24 18:13:04,861 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 18:13:04,861 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 18:13:05,469 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 18:13:05,469 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 18:13:45,252 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 18:13:45,252 - Stocklogger - INFO - 🤖 Initializing LLM agents... +2025-10-24 18:13:45,931 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 18:13:45,931 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 18:13:46,756 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 18:13:46,756 - Stocklogger - INFO - INFO: Agent 1 decide not to loan +2025-10-24 18:13:47,352 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 18:13:47,352 - Stocklogger - INFO - INFO: Agent 2 decide not to loan +2025-10-24 18:13:48,030 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 18:13:48,030 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 18:13:49,189 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 18:13:49,189 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 18:13:50,174 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 18:13:50,174 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.0} +2025-10-24 18:13:52,047 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 30.5} +2025-10-24 18:13:52,047 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 30.5} +2025-10-24 18:13:52,907 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 30.5} +2025-10-24 18:13:52,907 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 100, 'price': 30.5} +2025-10-24 18:13:53,771 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 30.8} +2025-10-24 18:13:53,771 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 30.8} +2025-10-24 18:13:54,691 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 30.9} +2025-10-24 18:13:54,691 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 30.9} +2025-10-24 18:13:55,603 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.5} +2025-10-24 18:13:55,603 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.5} +2025-10-24 18:13:57,112 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 31.0} +2025-10-24 18:13:57,112 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 200, 'price': 31.0} +2025-10-24 18:13:58,327 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.0} +2025-10-24 18:13:58,327 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 150, 'price': 41.0} +2025-10-24 18:13:59,477 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:13:59,477 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 41.5} +2025-10-24 18:14:00,461 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 18:14:00,461 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 41.5} +2025-10-24 18:14:30,504 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 18:14:30,504 - Stocklogger - INFO - INFO: Agent 0 decide not to loan +2025-10-24 18:14:34,542 - Stocklogger - INFO - INFO: Agent 1 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 205178.1873629014, 'repayment_date': 46} +2025-10-24 18:14:34,542 - Stocklogger - INFO - INFO: Agent 1 decide to loan: {'loan': 'yes', 'loan_type': 1, 'amount': 205178.1873629014, 'repayment_date': 46} +2025-10-24 18:14:35,675 - Stocklogger - INFO - INFO: Agent 2 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 100000, 'repayment_date': 24} +2025-10-24 18:14:35,675 - Stocklogger - INFO - INFO: Agent 2 decide to loan: {'loan': 'yes', 'loan_type': 0, 'amount': 100000, 'repayment_date': 24} +2025-10-24 18:14:36,279 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 18:14:36,279 - Stocklogger - INFO - INFO: Agent 3 decide not to loan +2025-10-24 18:14:37,821 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 18:14:37,821 - Stocklogger - INFO - INFO: Agent 4 decide not to loan +2025-10-24 18:14:38,967 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 40.5} +2025-10-24 18:14:38,967 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 100, 'price': 40.5} +2025-10-24 18:14:42,031 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 40.5} +2025-10-24 18:14:42,031 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 200, 'price': 40.5} +2025-10-24 18:14:42,660 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 250, 'price': 41.0} +2025-10-24 18:14:42,660 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 250, 'price': 41.0} +2025-10-24 18:14:43,637 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:14:43,637 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:14:44,775 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 18:14:44,775 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 18:14:50,903 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:14:50,903 - Stocklogger - INFO - INFO: Agent 1 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 300, 'price': 41.0} +2025-10-24 18:14:51,698 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 18:14:51,698 - Stocklogger - INFO - INFO: Agent 0 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 18:14:52,728 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 18:14:52,728 - Stocklogger - INFO - INFO: Agent 3 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 30.5} +2025-10-24 18:14:54,158 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 31.0} +2025-10-24 18:14:54,158 - Stocklogger - INFO - INFO: Agent 2 decide to action: {'action_type': 'sell', 'stock': 'A', 'amount': 1000, 'price': 31.0} +2025-10-24 18:14:55,663 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.0} +2025-10-24 18:14:55,663 - Stocklogger - INFO - INFO: Agent 4 decide to action: {'action_type': 'buy', 'stock': 'B', 'amount': 500, 'price': 41.0} diff --git a/examples/Stockagent/main.py b/examples/Stockagent/main.py new file mode 100644 index 0000000..f5a1ec0 --- /dev/null +++ b/examples/Stockagent/main.py @@ -0,0 +1,208 @@ +import argparse +import random +import pandas as pd +import openai +import tiktoken + +import util +from agent import Agent +from secretary import Secretary +from stock import Stock +from log.custom_logger import log +from record import create_stock_record, create_trade_record, AgentRecordDaily, create_agentses_record + +def get_agent(all_agents, order): + for agent in all_agents: + if agent.order == order: + return agent + return None + +def handle_action(action, stock_deals, all_agents, stock, session): + # action = JSON{"agent": 1, "action_type": "buy"|"sell", "stock": "A"|"B", "amount": 10, "price": 10} + try: + if action["action_type"] == "buy": + for sell_action in stock_deals["sell"][:]: + if action["price"] == sell_action["price"]: + # 交易成交 + close_amount = min(action["amount"], sell_action["amount"]) + get_agent(all_agents, action["agent"]).buy_stock(stock.name, close_amount, action["price"]) + if not sell_action["agent"] == -1: # B发行 + get_agent(all_agents, sell_action["agent"]).sell_stock(stock.name, close_amount, action["price"]) + stock.add_session_deal({"price": action["price"], "amount": close_amount}) + create_trade_record(action["date"], session, stock.name, action["agent"], sell_action["agent"], + close_amount, action["price"]) + + if action["amount"] > close_amount: # 买单未结束,卖单结束,继续循环 + log.logger.info(f"ACTION - BUY:{action['agent']}, SELL:{sell_action['agent']}, " + f"STOCK:{stock.name}, PRICE:{action['price']}, AMOUNT:{close_amount}") + stock_deals["sell"].remove(sell_action) + action["amount"] -= close_amount + else: # 卖单未结束,买单结束 + log.logger.info(f"ACTION - BUY:{action['agent']}, SELL:{sell_action['agent']}, " + f"STOCK:{stock.name}, PRICE:{action['price']}, AMOUNT:{close_amount}") + sell_action["amount"] -= close_amount + return + # 遍历卖单后仍然有剩余 + stock_deals["buy"].append(action) + + else: + for buy_action in stock_deals["buy"][:]: + if action["price"] == buy_action["price"]: + # 交易成交 + close_amount = min(action["amount"], buy_action["amount"]) + get_agent(all_agents, action["agent"]).sell_stock(stock.name, close_amount, action["price"]) + get_agent(all_agents, buy_action["agent"]).buy_stock(stock.name, close_amount, action["price"]) + stock.add_session_deal({"price": action["price"], "amount": close_amount}) + create_trade_record(action["date"], session, stock.name, buy_action["agent"], action["agent"], + close_amount, action["price"]) + + if action["amount"] > close_amount: # 卖单未结束,买单结束,继续循环 + log.logger.info(f"ACTION - BUY:{buy_action['agent']}, SELL:{action['agent']}, " + f"STOCK:{stock.name}, PRICE:{action['price']}, AMOUNT:{close_amount}") + stock_deals["buy"].remove(buy_action) + action["amount"] -= close_amount + else: # 买单未结束,卖单结束 + log.logger.info(f"ACTION - BUY:{buy_action['agent']}, SELL:{action['agent']}, " + f"STOCK:{stock.name}, PRICE:{action['price']}, AMOUNT:{close_amount}") + buy_action["amount"] -= close_amount + return + stock_deals["sell"].append(action) + except Exception as e: + log.logger.error(f"handle_action error: {e}") + return + + +def simulation(args): + # init + secretary = Secretary(args.model) + stock_a = Stock("A", util.STOCK_A_INITIAL_PRICE, 0, is_new=False) + #stock_b = Stock("B", util.STOCK_B_INITIAL_PRICE, util.STOCK_B_PUBLISH, is_new=True) + stock_b = Stock("B", util.STOCK_B_INITIAL_PRICE, 0, is_new=False) + all_agents = [] + log.logger.debug("Agents initial...") + for i in range(0, util.AGENTS_NUM): # agents start from 0, -1 refers to admin + agent = Agent(i, stock_a.get_price(), stock_b.get_price(), secretary, args.model) + all_agents.append(agent) + log.logger.debug("cash: {}, stock a: {}, stock b:{}, debt: {}".format(agent.cash, agent.stock_a_amount, + agent.stock_b_amount, agent.loans)) + + # start simulation + last_day_forum_message = [] + stock_a_deals = {"sell": [], "buy": []} + stock_b_deals = {"sell": [], "buy": []} + # stock b publish + # stock_b_deals["sell"].append({"agent": -1, "amount": util.STOCK_B_PUBLISH, "price": util.STOCK_B_INITIAL_PRICE}) + + log.logger.debug("--------Simulation Start!--------") + for date in range(1, util.TOTAL_DATE + 1): + + log.logger.debug(f"--------DAY {date}---------") + # 除b发行外,删除前一天的所有交易 + stock_a_deals["sell"].clear() + stock_a_deals["buy"].clear() + stock_b_deals["buy"].clear() + + # tmp_action = next((action for action in stock_b_deals["sell"] if action["agent"] == -1), None) + stock_b_deals["sell"].clear() + # if tmp_action: + # tmp_action["price"] *= 0.9 # B发行折价 + # if tmp_action["price"] < 1: + # log.logger.warning("WARNING: STOCK B WITHDRAW FROM MARKET!!!") + # stock_b_deals["sell"].append(tmp_action) + + # check if an agent needs to repay loans + for agent in all_agents[:]: + agent.chat_history.clear() # 只保存当天的聊天记录 + agent.loan_repayment(date) + + # repayment days + if date in util.REPAYMENT_DAYS: + for agent in all_agents[:]: + agent.interest_payment() + + # deal with cash<0 agents + for agent in all_agents[:]: + if agent.is_bankrupt: + quit_sig = agent.bankrupt_process(stock_a.get_price(), stock_b.get_price()) + if quit_sig: + agent.quit = True + all_agents.remove(agent) + + # special events + if date == util.EVENT_1_DAY: + util.LOAN_RATE = util.EVENT_1_LOAN_RATE + last_day_forum_message.append({"name": -1, "message": util.EVENT_1_MESSAGE}) + if date == util.EVENT_2_DAY: + util.LOAN_RATE = util.EVENT_2_LOAN_RATE + last_day_forum_message.append({"name": -1, "message": util.EVENT_2_MESSAGE}) + + # agent decide whether to loan + daily_agent_records = [] + for agent in all_agents: + loan = agent.plan_loan(date, stock_a.get_price(), stock_b.get_price(), last_day_forum_message) + daily_agent_records.append(AgentRecordDaily(date, agent.order, loan)) + + for session in range(1, util.TOTAL_SESSION + 1): + log.logger.debug(f"SESSION {session}") + # 随机定义交易顺序 + sequence = list(range(len(all_agents))) + random.shuffle(sequence) + for i in sequence: + agent = all_agents[i] + # if agent.is_bankrupt: # cash<0的当天停止交易,交易时段结束后贩卖股票 + # continue + + action = agent.plan_stock(date, session, stock_a, stock_b, stock_a_deals, stock_b_deals) + proper, cash, valua_a, value_b = agent.get_proper_cash_value(stock_a.get_price(), stock_b.get_price()) + create_agentses_record(agent.order, date, session, proper, cash, valua_a, value_b, action) + action["agent"] = agent.order + action["date"] = date + if not action["action_type"] == "no": + if action["stock"] == 'A': + handle_action(action, stock_a_deals, all_agents, stock_a, session) + else: + handle_action(action, stock_b_deals, all_agents, stock_b, session) + + # 交易时段结束,更新股票价格 + stock_a.update_price(date) + stock_b.update_price(date) + create_stock_record(date, session, stock_a.get_price(), stock_b.get_price()) + + + # agent预测明天行动 + for idx, agent in enumerate(all_agents): + estimation = agent.next_day_estimate() + log.logger.info("Agent {} tomorrow estimation: {}".format(agent.order, estimation)) + if idx >= len(daily_agent_records): + break + daily_agent_records[idx].add_estimate(estimation) + daily_agent_records[idx].write_to_excel() + daily_agent_records.clear() + + # 交易日结束,论坛信息更新 + last_day_forum_message.clear() + log.logger.debug(f"DAY {date} ends, display forum messages...") + for agent in all_agents: + chat_history = agent.chat_history + message = agent.post_message() + log.logger.info("Agent {} says: {}".format(agent.order, message)) + last_day_forum_message.append({"name": agent.order, "message": message}) + + + + log.logger.debug("--------Simulation finished!--------") + log.logger.debug("--------Agents action history--------") + # for agent in all_agents: + # log.logger.debug(f"Agent {agent.order} action history:") + # log.logger.info(agent.action_history) + # log.logger.debug("--------Stock deal history--------") + # for stock in [stock_a, stock_b]: + # log.logger.debug(f"Stock {stock.name} deal history:") + # log.logger.info(stock.history) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--model", type=str, default="gemini-pro", help="model name") + args = parser.parse_args() + simulation(args) diff --git a/examples/Stockagent/prompt/agent_prompt.py b/examples/Stockagent/prompt/agent_prompt.py new file mode 100644 index 0000000..c363584 --- /dev/null +++ b/examples/Stockagent/prompt/agent_prompt.py @@ -0,0 +1,292 @@ +from procoder.prompt import * + +# BACKGROUND_PROMPT = NamedBlock( +# name="Background", +# content=""" +# 你是一名股票交易员,接下来你将在市场中模拟与其他交易员的交互。市场中一共有两支股票,分别为A和B,其中B为新上市的股票。 +# 接下来,请根据指令完成你的交易行动。 +# """ +# ) + +BACKGROUND_PROMPT = NamedBlock( + name="Background", + content=""" + You are a stock trader, and next you will simulate interactions with other traders in the market. + There are two stocks in the market, A and B, where B is the newly listed stock. + Next, please complete your trading actions according to the order. + """ +) + +# LASTDAY_FORUM_AND_STOCK_PROMPT = NamedBlock( +# name="Last Day Forum and Stock", +# content=""" +# 昨天交易截止后,A公司股票和B公司股票的股价分别是{stock_a_price}元/股和{stock_b_price}元/股。 +# 其他交易员在论坛上发布的帖子如下: +# {lastday_forum_message} +# """ +# ) + +LASTDAY_FORUM_AND_STOCK_PROMPT = NamedBlock( + name="Last Day Forum and Stock", + content=""" + After the close of trading yesterday, the stock prices of Company A and Company B + were {stock_a_price} dollars per share and {stock_b_price} dollars per share, respectively. + Posts by other traders on the forum are as follows: {lastday_forum_message} + """ +) + +# LOAN_TYPE_PROMPT = NamedVariable( +# refname="loan_type_prompt", +# name="Loan Type", +# content=""" +# 0. 1年期,基准利率{loan_rate1} +# 1. 2年期,基准利率{loan_rate2} +# 2. 3年期,基准利率{loan_rate3} +# """ +# ) + +LOAN_TYPE_PROMPT = NamedVariable( + refname="loan_type_prompt", + name="Loan Type", + content=""" + 0. 22days, the benchmark interest rate {loan_rate1} + 1. 44days, the benchmark interest rate {loan_rate2} + 2. 66days, the benchmark interest rate {loan_rate3} + """ +) + +# DECIDE_IF_LOAN_PROMPT = NamedBlock( +# name="Instruction", +# content=""" +# 现在是第{date}天,你当前的性格是{character},持有{stock_a}股A公司股票,持有{stock_b}股B公司股票, +# 现在你有{cash}元现金,贷款情况为{debt}。 +# 你需要决定是否继续贷款和贷款金额。 +# 可供选择的种类为{loan_type_prompt},你应当用编号选择一个贷款种类。贷款金额不得超过{max_loan}。 +# 用json形式返回结果,例如: +# {{"loan": "yes", "loan_type": 3, "amount": 1000}} +# 如果不需贷款,则返回: +# {{"loan" : "no"}} +# """ +# ) + +DECIDE_IF_LOAN_PROMPT = NamedBlock( + name="Instruction", + content=""" + It is the {date} day, and your current character is {character}. + You hold {stock_a} shares of Company A, {stock_b} shares of Company B, + Now you have {cash} dollars in cash and {debt} in your loan situation. + You need to decide whether to continue the loan and the amount of the loan. + The alternative type is {loan_type_prompt}, and you should use the number to select a loan type. + The loan amount shall not exceed {max_loan}. + + Return the result as json, for example: + {{"loan": "yes", "loan_type": 3, "amount": 1000}} + + If no loan is required, return: + {{"loan" : "no"}} + """ +) + +# LOAN_RETRY_PROMPT = NamedBlock( +# name="Instruction", +# content=""" +# The following questions appeared in the loan format you last answered: {fail_response}. +# 你应当用json形式返回结果,例如: +# {{"loan": "yes", "loan_type": 2, "amount": 1000}} +# 如果不需贷款,则返回: +# {{"loan" : "no"}} +# Please answer again.""" +# ) + +LOAN_RETRY_PROMPT = NamedBlock( + name="Instruction", + content=""" + The following questions appeared in the loan format you last answered: {fail_response}. + You should return the results as json, for example: + {{"loan": "yes", "loan_type": 2, "amount": 1000}} + If no loan is required, return: + {{"loan" : "no"}} + Please answer again.""" +) + +# DECIDE_BUY_STOCK_PROMPT = NamedBlock( +# name="Instruction", +# content=""" +# 现在是第{date}天的{time}交易时段,前一时段结束后,A公司的股票股价为{stock_a_price},B公司的股票股价为{stock_b_price}。 +# 在目前时段,股票A的买卖盘为{stock_a_deals},股票B的买卖盘为{stock_b_deals} +# 你当前持有{stock_a}股A公司股票,持有{stock_b}股B公司股票,{cash}元现金。 +# 你需要决定是否购买/卖出A公司或B公司的股票,以及购买/卖出的数量与价格。你可以参考当前股价和大盘自己决定价格,无需确定为当前股价。数量必须为整数。 +# 鼓励尽可能多地买入和卖出。 +# 用json形式返回结果,例如: +# {{"action_type":"buy"|"sell", "stock":"A"|"B", amount: 100, price : 30}} +# 如果既不购买也不卖出,则返回: +# {{"action_type" : "no"}}""" +# ) + +DECIDE_BUY_STOCK_PROMPT = NamedBlock( + name="Instruction", + content=""" + It is the {time} trading session on the {date} day, and after the previous session, + the stock price of Company A is {stock_a_price} and the stock price of Company B is {stock_b_price}. + In the current session, the buy and sell order of stock A is {stock_a_deals}, + and the buy and sell order of stock B is {stock_b_deals} + You currently hold {stock_a} shares of Company A, {stock_b} shares of Company B, and {cash} yuan in cash. + You need to decide whether to buy/sell shares of Company A or Company B, and how much to buy/sell and at what price. + You can refer to the current share price and the market to determine the price yourself, not the current share price. + The quantity must be an integer. + We encourage you to buy and sell more. You can only answer one json action. + Return the result as json, for example: + {{"action_type":"buy"|"sell", "stock":"A"|"B", amount: 100, price : 30.1}} + If neither buy nor sell, return: + {{"action_type" : "no"}} + """ +) + +# BUY_STOCK_RETRY_PROMPT = NamedBlock( +# name="Instruction", +# content=""" +# The following questions appeared in the action format you last answered: {fail_response}. +# 你应当用json形式返回结果,例如: +# {{"action_type":"buy"|"sell", "stock":"A"|"B", amount: 100, price: 30}} +# 如果既不购买也不卖出,则返回: +# {{"action_type" : "no"}} +# Please answer again.""" +# ) + +BUY_STOCK_RETRY_PROMPT = NamedBlock( + name="Instruction", + content=""" + The following questions appeared in the action format you last answered: {fail_response}. + You should return the result as json, for example: + {{"action_type":"buy"|"sell", "stock":"A"|"B", amount: 100, price: 30.1}} + If neither buy nor sell, return: + {{"action_type" : "no"}} + Please answer again. You can only answer one json action. + """ +) + +# FIRST_DAY_FINANCIAL_REPORT = NamedVariable( +# refname="first_day_financial_report", +# name="The initial financial situation of Stock A and B", +# content=""" +# ●公司A:这只股票超级棒! +# ●公司B:这只股票风险大收益大!""" +# ) + +FIRST_DAY_FINANCIAL_REPORT = NamedVariable( + refname="first_day_financial_prompt", + name="The last 3 years financial report of Stock A and B", + content=""" + The following lists the financial data for the past three years, covering a total of twelve quarters. + Stock A: + Revenue million: 3696.19, 3578.00, 3595.49, 3215.64, 3973.40, 3810.57, 3840.70, 3433.02, 4344.52, 4095.22, 4114.16, 3717.96 + Net profit million: 127.711441, 217.9586418, 360.756337, 358.08228, 650.8868033, 693.3022798, 433.2338757, 517.0593354, 712.7358875, 628.310145, 250.5046675, 325.5147258 + Cash flow million: 30.0950631, 135.4141818, 344.3249477, 279.5563512, 564.624197, 642.8122273, 350.3899245, 493.4058465, 650.6526937, 579.0037013, 185.7066407, 273.1287018 + Stock B: + Revenue million: 570.00, 774.00, 643.00, 995.00, 684.46, 934.37, 782.08, 1204.05, 788.29, 1100.32, 914.96, 1418.37 + Net profit million: 85.9691, 142.086, 87.5419224, 135.7643678, 132.7973368, 169.6505746, 194.9436163, 272.1084953, 225.1707811, 356.7201332 + Cash flow million: 68.97, 90.171, 82.1754, 124.773, 75.4954968, 123.5240842, 132.7191287, 153.7571212, 194.9436163, 261.1053212, 216.3871992, 345.6568448 + """ +) + +FIRST_DAY_BACKGROUND_KNOWLEDGE = NamedBlock( + name="The initial financial situation of Stock A and B", + content=""" + + Company A has been listed for 10 years, deeply rooted in the chemical industry. However, the company's operations + have encountered bottlenecks, with revenues declining over the past three years. + Although Company A's performance has declined over the past five years, the overall trend is stable. With the recent + CEO change and the exploration of new business avenues, the new CEO appears more proactive compared to the + previous one. The future operational outlook is expected to improve. + + Company B, as a technology company, has just been listed for three years and is in a period of business growth. + Last year, its revenue declined due to the overall tech environment, but the company's operations remain robust. + According to the latest corporate news, it is expected that the future revenue growth rate will return to over 20%. + In the short term, the stock price is expected to continue rising. + While Company B's operations are good, there is a history of concealing critical data before its IPO, casting doubt + on the reliability of its revenue. + Company B recently received government inquiries regarding recent operational and stock price fluctuations, and it + provided explanations while committing to allocate more resources to social services. + + The government recently held talks with both Company A and Company B, actively encouraging their contributions + to society. Subsequently, agreements on government subsidies were signed with both companies. + + The last 3 years financial report of stock A and B is listed in {first_day_financial_prompt}. + """ +) + +# SEASONAL_FINANCIAL_REPORT = NamedVariable( +# refname="seasonal_financial_report", +# name="The Seasonal financial report of Stock A and B", +# content=""" +# Stock A: {stock_a_report} +# Stock B: {stock_b_report} +# """ +# ) + +SEASONAL_FINANCIAL_REPORT = NamedVariable( + refname="seasonal_financial_report", + name="The Seasonal financial report of Stock A and B", + content=""" + Stock A: {stock_a_report} + Stock B: {stock_b_report} + """ +) + +# POST_MESSAGE_PROMPT = NamedBlock( +# refname="post_message", +# name="Instruction", +# content=""" +# 当前交易日结束了,请在论坛上简短地发表你的交易心得,并将其发布在论坛上。你发布的内容将对所有交易员公开可见。回答中只包含需要发布的内容。""" +# ) + +POST_MESSAGE_PROMPT = NamedBlock( + refname="post_message", + name="Instruction", + content=""" + The current trading day is over, please briefly post your trading tips on the forum and post them on the forum. + What you post will be publicly visible to all traders. The responses contain only what needs to be posted. + """ +) + +# NEXT_DAY_ESTIMATE_PROMPT = NamedBlock( +# refname="next_day_estimate", +# name="Instruction", +# content=""" +# 请根据当前交易日的大盘信息和论坛信息,预估明天你是否会买入、卖出股票A和股票B,以及是否会选择贷款。预计会进行的行动标记为yes,不会进行标记为no。 +# 用json格式返回结果,例如: +# {{"buy_A":"yes", "buy_B":"no", "sell_A":"yes", "sell_B": "no", "loan": "yes"}} +# """ +# ) + +NEXT_DAY_ESTIMATE_PROMPT = NamedBlock( + refname="next_day_estimate", + name="Instruction", + content=""" + Based on the market information and forum information of the current trading day, + please estimate whether you will buy and sell stock A and stock B tomorrow, and whether you will choose loan. + Actions that are expected to take place are marked yes, and actions that will not take place are marked no. + Return the result in json format, for example: + {{"buy_A":"yes", "buy_B":"no", "sell_A":"yes", "sell_B": "no", "loan": "yes"}} + """ +) + +# NEXT_DAY_ESTIMATE_RETRY = NamedBlock( +# refname="next_day_estimate_retry", +# name="Instruction", +# content=""" +# The following questions appeared in the JSON format you last answered: {fail_response}. +# 用json格式返回结果,例如: +# {{"buy_A":"yes", "buy_B":"no", "sell_A":"yes", "sell_B": "no", "loan": "yes"}} +# """ +# ) + +NEXT_DAY_ESTIMATE_RETRY = NamedBlock( + refname="next_day_estimate_retry", + name="Instruction", + content=""" + The following questions appeared in the JSON format you last answered: {fail_response}. + Return the result in json format, for example: + {{"buy_A":"yes", "buy_B":"no", "sell_A":"yes", "sell_B": "no", "loan": "yes"}} + """ +) \ No newline at end of file diff --git a/examples/Stockagent/record.py b/examples/Stockagent/record.py new file mode 100644 index 0000000..13685e8 --- /dev/null +++ b/examples/Stockagent/record.py @@ -0,0 +1,141 @@ +import pandas as pd +import os + +# 交易记录 +class TradeRecord: + def __init__(self, date, session, stock_type, buyer, seller, quantity, price): + self.date = date + self.session = session + self.stock_type = stock_type + self.buyer = buyer + self.seller = seller + self.quantity = quantity + self.price = price + + def write_to_excel(self, file_name="res/trades.xlsx"): + if os.path.isfile(file_name): + existing_df = pd.read_excel(file_name) + else: + existing_df = pd.DataFrame(columns=["交易日", "交易阶段", "股票类型", "买入交易员", "卖出交易员", "交易数量", "交易价格"]) + + # 将新的交易记录合并到现有DataFrame + new_records = [[self.date, self.session, self.stock_type, self.buyer, self.seller, self.quantity, self.price]] + new_df = pd.DataFrame(new_records, columns=existing_df.columns) + all_records_df = pd.concat([existing_df, new_df], ignore_index=True) + + # 将所有记录写入到Excel文件 + all_records_df.to_excel(file_name, index=False) + +def create_trade_record(date, stage, stock, buy_trader, sell_trader, amount, price): + record = TradeRecord(date, stage, stock, buy_trader, sell_trader, amount, price) + record.write_to_excel() + record = None + +# 将交易记录列表写入Excel文件(如果文件不存在则创建) + +class StockRecord: + def __init__(self, date, session, stock_a_price, stock_b_price): + self.date = date + self.session = session + self.stock_a_price = stock_a_price + self.stock_b_price = stock_b_price + + def write_to_excel(self, file_name="res/stocks.xlsx"): + if os.path.isfile(file_name): + existing_df = pd.read_excel(file_name) + else: + existing_df = pd.DataFrame(columns=["交易日", "第几个交易阶段", "阶段结束后股票A价格", "阶段结束后股票B价格"]) + + # 将新的交易记录合并到现有DataFrame + new_records = [[self.date, self.session, self.stock_a_price, self.stock_b_price]] + new_df = pd.DataFrame(new_records, columns=existing_df.columns) + all_records_df = pd.concat([existing_df, new_df], ignore_index=True) + + # 将所有记录写入到Excel文件 + all_records_df.to_excel(file_name, index=False) + +def create_stock_record(date, session, stock_a_price, stock_b_price): + record = StockRecord(date, session, stock_a_price, stock_b_price) + record.write_to_excel() + record = None + + +class AgentRecordDaily: + def __init__(self, agent, date, loan_json): + self.agent = agent + self.date = date + self.if_loan = loan_json["loan"] + self.loan_type = 0 + self.loan_amount = 0 + if self.if_loan == "yes": + self.loan_type = loan_json["loan_type"] + self.loan_amount = loan_json["amount"] + self.will_loan = "no" + self.will_buy_a = "no" + self.will_sell_a = "no" + self.will_buy_b = "no" + self.will_sell_b = "no" + + def add_estimate(self, js): + self.will_loan = js["loan"] + self.will_buy_a = js["buy_A"] + self.will_sell_a = js["sell_A"] + self.will_buy_b = js["buy_B"] + self.will_sell_b = js["sell_B"] + + def write_to_excel(self, file_name="res/agent_day_record.xlsx"): + if os.path.isfile(file_name): + existing_df = pd.read_excel(file_name) + else: + existing_df = pd.DataFrame(columns=["交易员", "交易日", "是否贷款", "贷款类型", "贷款数量", + "明日是否贷款", "明日是否买入A", "明日是否卖出A", "明日是否买入B", "明日是否卖出B"]) + + # 将新的交易记录合并到现有DataFrame + new_records = [[self.agent, self.date, self.if_loan, self.loan_type, self.loan_amount, + self.will_loan, self.will_buy_a, self.will_sell_a, self.will_buy_b, self.will_sell_b]] + new_df = pd.DataFrame(new_records, columns=existing_df.columns) + all_records_df = pd.concat([existing_df, new_df], ignore_index=True) + + # 将所有记录写入到Excel文件 + all_records_df.to_excel(file_name, index=False) + +class AgentRecordSession: + def __init__(self, agent, date, session, proper, cash, stock_a_value, stock_b_value, action_json): + self.agent = agent + self.date = date + self.session = session + self.proper = proper + self.cash = cash + self.stock_a_value = stock_a_value + self.stock_b_value = stock_b_value + self.action_stock = "-" + self.amount = 0 + self.price = 0 + self.action_type = action_json["action_type"] + if not self.action_type == "no": + self.action_stock = action_json["stock"] + self.amount = action_json["amount"] + self.price = action_json["price"] + + def write_to_excel(self, file_name="res/agent_session_record.xlsx"): + if os.path.isfile(file_name): + existing_df = pd.read_excel(file_name) + else: + existing_df = pd.DataFrame(columns=["交易员", "交易日", "交易阶段", "交易前资产总额", + "交易前持有现金", "交易前持有的A股价值", "交易前持有的B股价值", + "挂单类型", "挂单股票类别", "挂单数量", "挂单价格"]) + + # 将新的交易记录合并到现有DataFrame + new_records = [[self.agent, self.date, self.session, self.proper, self.cash, + self.stock_a_value, self.stock_b_value, self.action_type, self.action_stock, + self.amount, self.price]] + new_df = pd.DataFrame(new_records, columns=existing_df.columns) + all_records_df = pd.concat([existing_df, new_df], ignore_index=True) + + # 将所有记录写入到Excel文件 + all_records_df.to_excel(file_name, index=False) + +def create_agentses_record(agent, date, session, proper, cash, stock_a_value, stock_b_value, action_json): + record = AgentRecordSession(agent, date, session, proper, cash, stock_a_value, stock_b_value, action_json) + record.write_to_excel() + record = None diff --git a/examples/Stockagent/requirements.txt b/examples/Stockagent/requirements.txt new file mode 100644 index 0000000..5d5c00e --- /dev/null +++ b/examples/Stockagent/requirements.txt @@ -0,0 +1,58 @@ +annotated-types==0.7.0 +anyio==4.11.0 +black==25.9.0 +cachetools==6.2.1 +certifi==2025.10.5 +charset-normalizer==3.4.4 +click==8.3.0 +colorama==0.4.4 +distro==1.9.0 +et_xmlfile==2.0.0 +google-ai-generativelanguage==0.6.15 +google-api-core==2.27.0 +google-api-python-client==2.185.0 +google-auth==2.41.1 +google-auth-httplib2==0.2.0 +google-generativeai==0.8.5 +googleapis-common-protos==1.71.0 +grpcio==1.76.0 +grpcio-status==1.71.2 +h11==0.16.0 +httpcore==1.0.9 +httplib2==0.31.0 +httpx==0.28.1 +idna==3.11 +jiter==0.11.1 +mypy_extensions==1.1.0 +numpy==2.3.4 +openai==2.6.1 +openpyxl==3.1.5 +packaging==25.0 +pandas==2.3.3 +pathspec==0.12.1 +platformdirs==4.5.0 +procoder @ git+https://github.com/dhh1995/PromptCoder@87155427e93f6ab95dbd658d7f500c2cedc05af6 +proto-plus==1.26.1 +protobuf==5.29.5 +pyasn1==0.6.1 +pyasn1_modules==0.4.2 +pydantic==2.12.3 +pydantic_core==2.41.4 +pyparsing==3.2.5 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.0 +pytokens==0.2.0 +pytz==2025.2 +regex==2025.10.23 +requests==2.31.0 +roman==5.1 +rsa==4.9.1 +six==1.17.0 +sniffio==1.3.1 +tiktoken==0.5.1 +tqdm==4.67.1 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +tzdata==2025.2 +uritemplate==4.2.0 +urllib3==2.5.0 diff --git a/examples/Stockagent/res/stocks.xlsx b/examples/Stockagent/res/stocks.xlsx new file mode 100644 index 0000000..d60a1bc Binary files /dev/null and b/examples/Stockagent/res/stocks.xlsx differ diff --git a/examples/Stockagent/res/trades.xlsx b/examples/Stockagent/res/trades.xlsx new file mode 100644 index 0000000..0245047 Binary files /dev/null and b/examples/Stockagent/res/trades.xlsx differ diff --git a/examples/Stockagent/runagent.config.json b/examples/Stockagent/runagent.config.json new file mode 100644 index 0000000..cf4e4c0 --- /dev/null +++ b/examples/Stockagent/runagent.config.json @@ -0,0 +1,32 @@ +{ + "agent_name": "StockAgent Multi-Agent Trading Simulator", + "description": "LLM-based multi-agent stock trading simulation with real-world market factors", + "framework": "default", + "template": "stockagent", + "version": "1.0.0", + "created_at": "2025-10-24 00:00:00", + "template_source": { + "repo_url": "https://github.com/dhh1995/StockAgent", + "path": "templates/stockagent", + "author": "stockagent-team" + }, + "agent_architecture": { + "entrypoints": [ + + { + "file": "runagent_wrapper.py", + "module": "stream_simulation", + "tag": "simulate_stream" + }, + { + "file": "runagent_wrapper.py", + "module": "get_simulation_status", + "tag": "status" + } + ] + }, + "env_vars": { + "OPENAI_API_KEY": "", + "GOOGLE_API_KEY": "" + } +} \ No newline at end of file diff --git a/examples/Stockagent/runagent_wrapper.py b/examples/Stockagent/runagent_wrapper.py new file mode 100644 index 0000000..4d0d251 --- /dev/null +++ b/examples/Stockagent/runagent_wrapper.py @@ -0,0 +1,393 @@ +""" +RunAgent Wrapper for StockAgent - RUNS REAL SIMULATION WITH FULL LOGGING +""" +import json +import time +import sys +import io +import logging +from typing import Dict, Any, Iterator +from dataclasses import dataclass, asdict +import os +import random +from contextlib import redirect_stdout, redirect_stderr + +# Import StockAgent modules +from main import handle_action +from agent import Agent +from stock import Stock +import util + + +@dataclass +class SimulationUpdate: + """Structure for simulation updates""" + type: str + day: int + session: int = 0 + message: str = "" + data: Dict[str, Any] = None + timestamp: float = None + + def __post_init__(self): + if self.timestamp is None: + self.timestamp = time.time() + if self.data is None: + self.data = {} + + +class LogCapture: + """Captures all log output and yields it""" + def __init__(self): + self.buffer = io.StringIO() + self.logs = [] + + def write(self, text): + if text and text.strip(): + self.logs.append(text) + self.buffer.write(text) + + def flush(self): + self.buffer.flush() + + def get_logs(self): + """Get and clear accumulated logs""" + logs = self.logs.copy() + self.logs.clear() + return logs + + +class StockAgentRunner: + """Wrapper class to run StockAgent simulations""" + + def __init__(self): + self.simulation_state = { + "status": "ready", + "current_day": 0, + "total_days": 0, + "agents": [], + "stocks": {}, + "events": [] + } + + def stream_simulation( + self, + num_agents: int = 10, + total_days: int = 30, + sessions_per_day: int = 3, + model: str = "gpt-4o-mini", + stock_a_price: float = 30.0, + stock_b_price: float = 40.0, + enable_events: bool = True + ) -> Iterator[Dict[str, Any]]: + """Stream REAL simulation with LLM agents and ALL logs""" + + # FIX: Convert string parameters to appropriate types + try: + num_agents = int(num_agents) if isinstance(num_agents, str) else num_agents + total_days = int(total_days) if isinstance(total_days, str) else total_days + sessions_per_day = int(sessions_per_day) if isinstance(sessions_per_day, str) else sessions_per_day + stock_a_price = float(stock_a_price) if isinstance(stock_a_price, str) else stock_a_price + stock_b_price = float(stock_b_price) if isinstance(stock_b_price, str) else stock_b_price + + # Convert string boolean to actual boolean + if isinstance(enable_events, str): + enable_events = enable_events.lower() in ('true', '1', 'yes', 'on') + except (ValueError, TypeError) as e: + yield asdict(SimulationUpdate( + type="error", + day=0, + message=f"❌ Parameter conversion error: {str(e)}", + data={"error": str(e)} + )) + return + + # Setup log capturing + log_capture = LogCapture() + + # Get the StockAgent logger and add our handler + from log.custom_logger import log as stock_logger + capture_handler = logging.StreamHandler(log_capture) + capture_handler.setLevel(logging.DEBUG) + stock_logger.logger.addHandler(capture_handler) + + try: + yield asdict(SimulationUpdate( + type="init", + day=0, + message="🚀 Initializing REAL StockAgent simulation...", + data={ + "num_agents": num_agents, + "total_days": total_days, + "sessions_per_day": sessions_per_day, + "model": model + } + )) + + # Update util settings with converted values + util.AGENTS_NUM = num_agents + util.TOTAL_DATE = total_days + util.TOTAL_SESSION = sessions_per_day + util.STOCK_A_INITIAL_PRICE = stock_a_price + util.STOCK_B_INITIAL_PRICE = stock_b_price + + if not enable_events: + util.EVENT_1_DAY = total_days + 1 + util.EVENT_2_DAY = total_days + 2 + + # Run REAL simulation with log streaming + for update in self._run_real_simulation_streaming(model, log_capture): + yield asdict(update) + + yield asdict(SimulationUpdate( + type="complete", + day=total_days, + message="🎉 Real simulation completed!", + data=self._collect_results() + )) + + except Exception as e: + import traceback + yield asdict(SimulationUpdate( + type="error", + day=0, + message=f"❌ Error: {str(e)}", + data={"error": str(e), "traceback": traceback.format_exc()} + )) + finally: + # Remove our handler + stock_logger.logger.removeHandler(capture_handler) + + def _run_real_simulation_streaming(self, model: str, log_capture: LogCapture) -> Iterator[SimulationUpdate]: + """REAL simulation with actual LLM calls and log streaming""" + from secretary import Secretary + from log.custom_logger import log + from record import create_stock_record + + secretary = Secretary(model) + stock_a = Stock("A", util.STOCK_A_INITIAL_PRICE, 0, is_new=False) + stock_b = Stock("B", util.STOCK_B_INITIAL_PRICE, 0, is_new=False) + + all_agents = [] + log.logger.info("🤖 Initializing LLM agents...") + + # Yield any logs from initialization + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=0, + message=log_line.strip() + ) + + for i in range(util.AGENTS_NUM): + agent = Agent(i, stock_a.get_price(), stock_b.get_price(), secretary, model) + all_agents.append(agent) + + # Yield logs after each agent creation + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=0, + message=log_line.strip() + ) + + yield SimulationUpdate( + type="agents_initialized", + day=0, + message=f"✅ Initialized {len(all_agents)} REAL LLM agents", + data={"num_agents": len(all_agents)} + ) + + last_day_forum_message = [] + stock_a_deals = {"sell": [], "buy": []} + stock_b_deals = {"sell": [], "buy": []} + + for date in range(1, util.TOTAL_DATE + 1): + stock_a_deals["sell"].clear() + stock_a_deals["buy"].clear() + stock_b_deals["buy"].clear() + stock_b_deals["sell"].clear() + + for agent in all_agents[:]: + agent.chat_history.clear() + agent.loan_repayment(date) + + # Yield logs + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=date, + message=log_line.strip() + ) + + if date in util.REPAYMENT_DAYS: + for agent in all_agents[:]: + agent.interest_payment() + + # Yield logs + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=date, + message=log_line.strip() + ) + + for agent in all_agents[:]: + if agent.is_bankrupt: + quit_sig = agent.bankrupt_process(stock_a.get_price(), stock_b.get_price()) + if quit_sig: + agent.quit = True + all_agents.remove(agent) + + # Yield logs + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=date, + message=log_line.strip() + ) + + if date == util.EVENT_1_DAY: + util.LOAN_RATE = util.EVENT_1_LOAN_RATE + last_day_forum_message.append({"name": -1, "message": util.EVENT_1_MESSAGE}) + + if date == util.EVENT_2_DAY: + util.LOAN_RATE = util.EVENT_2_LOAN_RATE + last_day_forum_message.append({"name": -1, "message": util.EVENT_2_MESSAGE}) + + yield SimulationUpdate( + type="day_start", + day=date, + message=f"📅 Day {date}/{util.TOTAL_DATE}", + data={ + "stock_a_price": stock_a.get_price(), + "stock_b_price": stock_b.get_price(), + "active_agents": len(all_agents) + } + ) + + # REAL LLM CALLS FOR LOANS + for agent in all_agents: + loan = agent.plan_loan(date, stock_a.get_price(), stock_b.get_price(), last_day_forum_message) + + # Yield logs after each loan decision + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=date, + message=log_line.strip() + ) + + for session in range(1, util.TOTAL_SESSION + 1): + trades_count = 0 + sequence = list(range(len(all_agents))) + random.shuffle(sequence) + + # REAL LLM CALLS FOR TRADING + for i in sequence: + agent = all_agents[i] + action = agent.plan_stock(date, session, stock_a, stock_b, stock_a_deals, stock_b_deals) + + # Yield logs after each trading decision + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=date, + session=session, + message=log_line.strip() + ) + + if action.get("action_type") != "no": + trades_count += 1 + action["agent"] = agent.order + action["date"] = date + + if action["stock"] == 'A': + handle_action(action, stock_a_deals, all_agents, stock_a, session) + else: + handle_action(action, stock_b_deals, all_agents, stock_b, session) + + # Yield logs after action handling + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=date, + session=session, + message=log_line.strip() + ) + + stock_a.update_price(date) + stock_b.update_price(date) + create_stock_record(date, session, stock_a.get_price(), stock_b.get_price()) + + yield SimulationUpdate( + type="session", + day=date, + session=session, + message=f"⏰ Session {session} - {trades_count} trades", + data={ + "stock_a_price": stock_a.get_price(), + "stock_b_price": stock_b.get_price(), + "trades": trades_count + } + ) + + # Yield any remaining logs from session + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=date, + session=session, + message=log_line.strip() + ) + + # REAL LLM CALLS FOR FORUM POSTS + last_day_forum_message.clear() + for agent in all_agents: + message = agent.post_message() + last_day_forum_message.append({"name": agent.order, "message": message}) + + # Yield logs after each forum post + for log_line in log_capture.get_logs(): + yield SimulationUpdate( + type="log", + day=date, + message=log_line.strip() + ) + + yield SimulationUpdate( + type="day_end", + day=date, + message=f"✅ Day {date} done", + data={ + "stock_a_price": stock_a.get_price(), + "stock_b_price": stock_b.get_price(), + "surviving_agents": len(all_agents) + } + ) + + + def _collect_results(self) -> Dict[str, Any]: + results = {"trades": [], "stock_prices": [], "agent_performance": []} + try: + import pandas as pd + if os.path.exists("res/trades.xlsx"): + results["trades"] = pd.read_excel("res/trades.xlsx").to_dict('records') + if os.path.exists("res/stocks.xlsx"): + results["stock_prices"] = pd.read_excel("res/stocks.xlsx").to_dict('records') + except: pass + return results + + +runner = StockAgentRunner() + + +def stream_simulation(**kwargs) -> Iterator[Dict[str, Any]]: + """Streaming simulation - yields updates AND logs in real-time""" + for update in runner.stream_simulation(**kwargs): + yield update + + +def get_simulation_status() -> Dict[str, Any]: + """Get current simulation status""" + return runner.simulation_state \ No newline at end of file diff --git a/examples/Stockagent/sdk_test/python/test_agent.py b/examples/Stockagent/sdk_test/python/test_agent.py new file mode 100644 index 0000000..3a3e67c --- /dev/null +++ b/examples/Stockagent/sdk_test/python/test_agent.py @@ -0,0 +1,16 @@ +from runagent import RunAgentClient + +client = RunAgentClient( + agent_id="6cf5351f-b228-4648-9a07-20608ef490be", + entrypoint_tag="simulate_stream", + local=False +) + +# Run and print ALL output including logs +for update in client.run( + num_agents="5", + total_days="2", + sessions_per_day="2", + model="gpt-4o-mini" +): + print(update) diff --git a/examples/Stockagent/sdk_test/rust/Cargo.toml b/examples/Stockagent/sdk_test/rust/Cargo.toml new file mode 100644 index 0000000..6012193 --- /dev/null +++ b/examples/Stockagent/sdk_test/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "temp_rust_test" +version = "0.1.1" +edition = "2021" + +[dependencies] +runagent = { path = "../../../../runagent-rust/runagent" } +tokio = { version = "1.0", features = ["full"] } +serde_json = "1.0" +anyhow = "1.0" +futures = "0.3" \ No newline at end of file diff --git a/examples/Stockagent/sdk_test/rust/src/main.rs b/examples/Stockagent/sdk_test/rust/src/main.rs new file mode 100644 index 0000000..7b0652a --- /dev/null +++ b/examples/Stockagent/sdk_test/rust/src/main.rs @@ -0,0 +1,47 @@ +use runagent::client::RunAgentClient; +use serde_json::json; +use futures::StreamExt; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("🧪 Testing StockAgent with Rust SDK"); + println!("===================================="); + + // Initialize the client + let client = RunAgentClient::new( + "6cf5351f-b228-4648-9a07-20608ef490be", // Agent ID + "simulate_stream", // Entrypoint + false // Remote connection + ).await?; + + println!("✅ Client initialized"); + + println!("🔄 Starting simulation..."); + // Run simulation with parameters (matching Python SDK exactly) + let mut stream = client.run_stream(&[ + ("num_agents", json!("5")), // String values like Python + ("total_days", json!("2")), // String values like Python + ("sessions_per_day", json!("2")), // String values like Python + ("model", json!("gpt-4o-mini")) // String values like Python + ]).await?; + + println!("🚀 Simulation started, streaming updates...\n"); + + // Process the stream - simple output like Python SDK + while let Some(chunk_result) = stream.next().await { + match chunk_result { + Ok(chunk) => { + // Just print the raw data like Python SDK + println!("{}", chunk); + }, + Err(e) => { + println!("❌ Stream Error: {}", e); + break; + } + } + } + + println!("✅ StockAgent test completed successfully!"); + + Ok(()) +} \ No newline at end of file diff --git a/examples/Stockagent/secretary.py b/examples/Stockagent/secretary.py new file mode 100644 index 0000000..369ce87 --- /dev/null +++ b/examples/Stockagent/secretary.py @@ -0,0 +1,221 @@ +import json +import os +import openai +from log.custom_logger import log + + +def run_api(model, prompt, temperature: float = 0): + openai.api_key = "" + client = openai.OpenAI(api_key=openai.api_key) + response = client.chat.completions.create( + model=model, + messages=[ + {"role": "user", "content": prompt}, + ], + temperature=temperature, + ) + resp = response.choices[0].message.content + return resp + + +class Secretary: + def __init__(self, model): + self.model = model + + def get_response(self, prompt): + return run_api(self.model, prompt) + + """ + 用json形式返回结果,例如: + {{{{"loan": "yes", "loan_type": 3, "amount": 1000}}}} + 如果不需贷款,则返回: + {{{{"loan" : "no"}}}} + :returns: loan_format_check, fail_response, loan + """ + + def check_loan(self, resp, max_loan) -> (bool, str, dict): + # format check + if isinstance(resp, str) and resp.count('{') == 1 and resp.count('}') == 1: + start_idx = resp.index('{') + end_idx = resp.index('}') + else: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Wrong json format, there is no {} or more than one {} in response." + return False, fail_response, None + + action_json = resp[start_idx: end_idx + 1] + action_json = action_json.replace("\n", "").replace(" ", "") + try: + parsed_json = json.loads(action_json) + except json.JSONDecodeError as e: + print(e) + log.logger.debug("Illegal json content in response: {}".format(resp)) + fail_response = "Illegal json format." + return False, fail_response, None + + # content check + try: + if "loan" not in parsed_json: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Key 'loan' not in response." + return False, fail_response, None + + if parsed_json["loan"].lower() not in ["yes", "no"]: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Value of key 'loan' should be yes or no." + return False, fail_response, None + + if parsed_json["loan"].lower() == "no": + if "loan_type" in parsed_json or "amount" in parsed_json: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Don't include loan_type or amount in response if value of key 'loan' is no." + return False, fail_response, None + else: + return True, "", parsed_json + + if parsed_json["loan"].lower() == "yes": + if "loan_type" not in parsed_json or "amount" not in parsed_json: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Should include loan_type and amount in response if value of key 'loan' is yes." + return False, fail_response, None + if parsed_json["loan_type"] not in [0, 1, 2]: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Value of key 'loan_type' should be 0, 1 or 2." + return False, fail_response, None + if parsed_json["amount"] <= 0 or parsed_json["amount"] > max_loan: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = f"Value of key 'amount' should be positive and less than {max_loan}" + return False, fail_response, None + return True, "", parsed_json + + log.logger.error("UNSOLVED LOAN JSON RESPONSE:{}".format(parsed_json)) + return False, "", None + except Exception as e: + log.logger.error("UNSOLVED LOAN JSON RESPONSE:{}".format(parsed_json)) + return False, "", None + + def check_action(self, resp, cash, stock_a_amount, + stock_b_amount, stock_a_price, stock_b_price) -> (bool, str, dict): + # format check + if isinstance(resp, str) and resp.count('{') == 1 and resp.count('}') == 1: + start_idx = resp.index('{') + end_idx = resp.index('}') + else: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Wrong json format, there is no {} or more than one {} in response." + return False, fail_response, None + + action_json = resp[start_idx: end_idx + 1] + action_json = action_json.replace("\n", "").replace(" ", "") + try: + parsed_json = json.loads(action_json) + except json.JSONDecodeError as e: + print(e) + log.logger.debug("Illegal json content in response: {}".format(resp)) + fail_response = "Illegal json format." + return False, fail_response, None + + # content check + try: + prices = {"A": stock_a_price, "B": stock_b_price} + holds = {"A": stock_a_amount, "B": stock_b_amount} + if "action_type" not in parsed_json: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Key 'action_type' not in response." + return False, fail_response, None + + if parsed_json["action_type"].lower() not in ["buy", "sell", "no"]: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Value of key 'action_type' should be 'buy', 'sell' or 'no'." + return False, fail_response, None + + if parsed_json["action_type"].lower() == "no": + if "stock" in parsed_json or "amount" in parsed_json: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Don't include stock or amount in response if value of key 'action_type' is no." + return False, fail_response, None + else: + return True, "", parsed_json + else: + if "stock" not in parsed_json or "amount" not in parsed_json or "price" not in parsed_json: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Should include stock, amount and price in response " \ + "if value of key 'action_type' is buy or sell." + return False, fail_response, None + if parsed_json["stock"] not in ['A', 'B']: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Value of key 'stock' should be 'A' or 'B'." + return False, fail_response, None + if parsed_json["price"] <= 0: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = f"Value of key 'price' should be positive." + return False, fail_response, None + if not isinstance(parsed_json["amount"], int): + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = f"Value of key 'amount' should be integer." + return False, fail_response, None + + # buy more than cash or sell more than hold amount + # price = prices[parsed_json["stock"]] + price = parsed_json["price"] + if parsed_json["action_type"].lower() == "buy": + if parsed_json["amount"] <= 0 or parsed_json["amount"] * price > cash: + log.logger.debug("Buy more than cash: {}".format(resp)) + fail_response = f"The cash you have now is {cash}, " \ + f"the value of 'amount' * 'price' " \ + f"should be positive and not exceed cash." + return False, fail_response, None + + hold_amount = holds[parsed_json["stock"]] + if parsed_json["action_type"].lower() == "sell": + if parsed_json["amount"] <= 0 or parsed_json["amount"] > hold_amount: + log.logger.debug("Sell more than hold: {}".format(resp)) + fail_response = f"The amount of stock you hold is {hold_amount}, " \ + f"the value of 'amount' should be positive and not exceed the " \ + f"amount of stock you hold." + return False, fail_response, None + return True, "", parsed_json + + except Exception as e: + log.logger.error("UNSOLVED ACTION JSON RESPONSE:{}".format(parsed_json)) + return False, "", None + + def check_estimate(self, resp): + # format check + if isinstance(resp, str) and resp.count('{') == 1 and resp.count('}') == 1: + start_idx = resp.index('{') + end_idx = resp.index('}') + else: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Wrong json format, there is no {} or more than one {} in response." + return False, fail_response, None + + action_json = resp[start_idx: end_idx + 1] + action_json = action_json.replace("\n", "").replace(" ", "") + try: + parsed_json = json.loads(action_json) + except json.JSONDecodeError as e: + print(e) + log.logger.debug("Illegal json content in response: {}".format(resp)) + fail_response = "Illegal json format." + return False, fail_response, None + + # content check + try: + if "buy_A" not in parsed_json or "buy_B" not in parsed_json \ + or "sell_A" not in parsed_json or "sell_B" not in parsed_json \ + or "loan" not in parsed_json: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Key 'buy_A', 'buy_B', 'sell_A', 'sell_B' and 'loan' should in response." + return False, fail_response, None + + for key, item in parsed_json.items(): + if item not in ['yes', 'no']: + log.logger.debug("Wrong json content in response: {}".format(resp)) + fail_response = "Value of all keys should be 'yes' or 'no'." + return False, fail_response, None + return True, "", parsed_json + + except Exception as e: + log.logger.error("UNSOLVED ESTIMATE JSON RESPONSE:{}".format(parsed_json)) + return False, "", None diff --git a/examples/Stockagent/stock.py b/examples/Stockagent/stock.py new file mode 100644 index 0000000..964c635 --- /dev/null +++ b/examples/Stockagent/stock.py @@ -0,0 +1,31 @@ +import util + +class Stock: + def __init__(self, name, initial_price, initial_stock, is_new=False): + self.name = name + self.price = initial_price + self.ideal_price = 0 + self.initial_stock = initial_stock + self.history = {} # {date: session_deal} + self.session_deal = [] # [{"price", "amount"}] + + def gen_financial_report(self, index): + if self.name == "A": + return util.FINANCIAL_REPORT_A[index] + elif self.name == "B": + return util.FINANCIAL_REPORT_B[index] + + def add_session_deal(self, price_and_amount): + self.session_deal.append(price_and_amount) + + def update_price(self, date): + if len(self.session_deal) == 0: + return + self.price = self.session_deal[-1]["price"] + self.history[date] = self.session_deal + self.session_deal.clear() + + def get_price(self): + return self.price + + diff --git a/examples/Stockagent/util.py b/examples/Stockagent/util.py new file mode 100644 index 0000000..01d5cf1 --- /dev/null +++ b/examples/Stockagent/util.py @@ -0,0 +1,50 @@ +""" +DONT FORGET TO DELETE!!! +""" +OPENAI_API_KEY = "" +GOOGLE_API_KEY = "" + +# 基础设置 +AGENTS_NUM = 50 # 交易员数量 +TOTAL_DATE = 264 # 模拟时长 +TOTAL_SESSION = 3 # 每日交易次数 + +# 股票初始价格 +STOCK_A_INITIAL_PRICE = 30 +STOCK_B_INITIAL_PRICE = 40 +# STOCK_B_PUBLISH = 100 # 股票B发行数量 + +# agent初始财产 +MAX_INITIAL_PROPERTY = 5000000.0 +MIN_INITIAL_PROPERTY = 100000.0 + + +# 贷款 +LOAN_TYPE = ["one-month", "two-month", "three-month"] +LOAN_TYPE_DATE = [22, 44, 66] # 贷款时长 +LOAN_RATE = [0.027, 0.03, 0.033] # 贷款利率 + +REPAYMENT_DAYS = [22, 44, 66, 88, 110, 132, 154, 176, 198, 220, 242, 264] # 付息日 + +# 财报 +SEASONAL_DAYS = 66 # 一季度的时间 +SEASON_REPORT_DAYS = [12, 78, 144, 210] # 财报发布时间 +FINANCIAL_REPORT_A = ["Last quarter's financial report of Company A. Revenue growth rate (YoY): 9.49%, Revenue million: 4483.99, Gross margin: 41.05%, Income Tax as a percentage of Revenue: 11.31%, Selling Expense Rate:6.83%, Management Expense Rate: 3.83%, Net profit million: 856.6705, Depreciation and Amortization: 0.91%, Capital Expenditures: 2.30%, Changes in working capital: 0.82%, Cash Flow(million): 756.7537", + "Last quarter's financial report of Company A. Revenue growth rate (YoY): 7.38%, Revenue million: 4417.79, Gross margin: 35.68%, Income Tax as a percentage of Revenue: 11.75%, Selling Expense Rate:8.13%, Management Expense Rate: 4.62%, Net profit million: 493.9451, Depreciation and Amortization: 1.34%, Capital Expenditures: 2.68%, Changes in working capital: 0.86%, Cash Flow(million): 396.5329", + "Last quarter's financial report of Company A. Revenue growth rate (YoY): 8.70%, Revenue million: 4041.30, Gross margin: 37.45%, Income Tax as a percentage of Revenue: 9.34%, Selling Expense Rate:6.79%, Management Expense Rate: 3.41%, Net profit million: 724.3648, Depreciation and Amortization: 1.27%, Capital Expenditures: 2.44%, Changes in working capital: 0.94%, Cash Flow(million): 639.5329", + "Last quarter's financial report of Company A. Revenue growth rate (YoY): 7.75%, Revenue million: 5024.04, Gross margin: 42.47%, Income Tax as a percentage of Revenue: 10.67%, Selling Expense Rate:6.56%, Management Expense Rate: 4.72%, Net profit million: 1031.214, Depreciation and Amortization: 1.08%, Capital Expenditures: 2.71%, Changes in working capital: 0.08%, Cash Flow(million): 945.5034"] # 各个季度的财报 +FINANCIAL_REPORT_B = ["Last quarter's financial report of Company B. Revenue growth rate (YoY): 19.96%, Revenue million: 1319.94, Gross margin: 31.21%, Income Tax as a percentage of Revenue: 0.70%, Selling Expense Rate:4.69%, Management Expense Rate: 8.78%, Net profit million: 224.9179, Depreciation and Amortization: 1.13%, Capital Expenditures: 1.77%, Changes in working capital: 0.59%, Cash Flow(million): 208.7266", + "Last quarter's financial report of Company B. Revenue growth rate (YoY): 19.86%, Revenue million: 1096.70, Gross margin: 31.26%, Income Tax as a percentage of Revenue: 0.71%, Selling Expense Rate:3.62%, Management Expense Rate: 9.90%, Net profit million: 186.7678, Depreciation and Amortization: 0.67%, Capital Expenditures: 1.44%, Changes in working capital: -0.31%, Cash Flow(million): 181.6862", + "Last quarter's financial report of Company B. Revenue growth rate (YoY): 18.21%, Revenue million: 1676.70, Gross margin: 31.58%, Income Tax as a percentage of Revenue: 0.92%, Selling Expense Rate:3.78%, Management Expense Rate: 10.27%, Net profit million: 278.3327, Depreciation and Amortization: 0.77%, Capital Expenditures: 1.56%, Changes in working capital: -0.06%, Cash Flow(million): 266.1486", + "Last quarter's financial report of Company B. Revenue growth rate (YoY): 15.98%, Revenue million: 1075.13, Gross margin: 32.41%, Income Tax as a percentage of Revenue: 1.08%, Selling Expense Rate:3.79%, Management Expense Rate: 10.70%, Net profit million: 181.1602, Depreciation and Amortization: 1.09%, Capital Expenditures: 2.28%, Changes in working capital: 0.67%, Cash Flow(million): 161.1985"] + +# 特殊事件 + +EVENT_1_DAY = 78 +EVENT_1_MESSAGE = "The government has announced a reduction in the reserve requirement ratio. " \ + "The lending interest rates have been lowered." +EVENT_1_LOAN_RATE = [0.024, 0.027, 0.030] # 降准后的利率放在这里 + +EVENT_2_DAY = 144 +EVENT_2_MESSAGE = "The government has announced an increase in interest rates." +EVENT_2_LOAN_RATE = [0.0255, 0.0285, 0.0315] \ No newline at end of file diff --git a/examples/book_writer/backend/app.py b/examples/book_writer/backend/app.py new file mode 100644 index 0000000..a44b72f --- /dev/null +++ b/examples/book_writer/backend/app.py @@ -0,0 +1,180 @@ +from flask import Flask, request, jsonify, send_file +from flask_cors import CORS +from runagent import RunAgentClient +import os +import traceback +from datetime import datetime +import io + +app = Flask(__name__) +CORS(app, resources={ + r"/api/*": { + "origins": [ + "http://localhost:5173", + "http://127.0.0.1:5173", + ] + } +}) + +@app.route('/api/generate-outline', methods=['POST']) +def generate_outline(): + """ + Generate book outline + + Expected JSON body: + { + "agent_id": "your-agent-id", + "title": "Book Title", + "topic": "Book topic", + "goal": "Book goal description" + } + """ + try: + data = request.json + + # Validate required fields + if not data.get('agent_id'): + return jsonify({'error': 'agent_id is required'}), 400 + + if not data.get('topic'): + return jsonify({'error': 'topic is required'}), 400 + + agent_id = data['agent_id'] + title = data.get('title', 'Untitled Book') + topic = data['topic'] + goal = data.get('goal', '') + + # Initialize RunAgent client for outline generation + client = RunAgentClient( + agent_id=agent_id, + entrypoint_tag="generate_outline", + local=True # Set to False when using RunAgent Cloud + ) + + # Generate the outline + result = client.run( + title=title, + topic=topic, + goal=goal + ) + + return jsonify(result), 200 + + except Exception as e: + error_trace = traceback.format_exc() + print(f"Error in generate_outline: {error_trace}") + return jsonify({ + 'error': str(e), + 'traceback': error_trace + }), 500 + + +@app.route('/api/write-book', methods=['POST']) +def write_book(): + """ + Write complete book with all chapters + + Expected JSON body: + { + "agent_id": "your-agent-id", + "title": "Book Title", + "topic": "Book topic", + "goal": "Book goal description", + "num_chapters": 5 + } + """ + try: + data = request.json + + # Validate required fields + if not data.get('agent_id'): + return jsonify({'error': 'agent_id is required'}), 400 + + if not data.get('topic'): + return jsonify({'error': 'topic is required'}), 400 + + agent_id = data['agent_id'] + title = data.get('title', 'Untitled Book') + topic = data['topic'] + goal = data.get('goal', '') + num_chapters = data.get('num_chapters', 5) + + # Initialize RunAgent client + client = RunAgentClient( + agent_id=agent_id, + entrypoint_tag="write_full_book", + local=True + ) + + # Write the complete book + result = client.run( + title=title, + topic=topic, + goal=goal, + num_chapters=num_chapters + ) + + return jsonify(result), 200 + + except Exception as e: + error_trace = traceback.format_exc() + print(f"Error in write_book: {error_trace}") + return jsonify({ + 'error': str(e), + 'traceback': error_trace + }), 500 + + +@app.route('/api/download-book', methods=['POST']) +def download_book(): + """ + Download book as markdown file + + Expected JSON body: + { + "title": "Book Title", + "content": "Book content in markdown" + } + """ + try: + data = request.json + + title = data.get('title', 'book') + content = data.get('content', '') + + # Create filename + filename = f"{title.replace(' ', '_')}.md" + + # Create file in memory + file_stream = io.BytesIO(content.encode('utf-8')) + file_stream.seek(0) + + return send_file( + file_stream, + mimetype='text/markdown', + as_attachment=True, + download_name=filename + ) + + except Exception as e: + error_trace = traceback.format_exc() + print(f"Error in download_book: {error_trace}") + return jsonify({ + 'error': str(e), + 'traceback': error_trace + }), 500 + + +@app.route('/api/health', methods=['GET']) +def health_check(): + """Health check endpoint""" + return jsonify({ + 'status': 'healthy', + 'service': 'book-writer-api', + 'version': '1.0.0' + }), 200 + + +if __name__ == '__main__': + port = int(os.getenv('PORT', 8000)) + app.run(host='0.0.0.0', port=port, debug=True) \ No newline at end of file diff --git a/examples/book_writer/frontend/package-lock.json b/examples/book_writer/frontend/package-lock.json new file mode 100644 index 0000000..85478e1 --- /dev/null +++ b/examples/book_writer/frontend/package-lock.json @@ -0,0 +1,3361 @@ +{ + "name": "book-writer-saas", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "book-writer-saas", + "version": "1.0.0", + "dependencies": { + "axios": "^1.6.0", + "lucide-react": "^0.263.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32", + "tailwindcss": "^3.3.0", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", + "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.0.tgz", + "integrity": "sha512-zt40Pz4zcRXra9CVV31KeyofwiNvAbJ5B6YPz9pMJ+yOSLikvPT4Yi5LjfgjRa9CawVYBaD1JQzIVcIvBejKeA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", + "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.241", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.241.tgz", + "integrity": "sha512-ILMvKX/ZV5WIJzzdtuHg8xquk2y0BOGlFOxBVwTpbiXqWIH0hamG45ddU4R3PQ0gYu+xgo0vdHXHli9sHIGb4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.263.1", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.263.1.tgz", + "integrity": "sha512-keqxAx97PlaEN89PXZ6ki1N8nRjGWtDa4021GFYLNj0RgruM5odbpl8GHTExj0hhPq3sF6Up0gnxt6TSHu+ovw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/examples/book_writer/frontend/package.json b/examples/book_writer/frontend/package.json new file mode 100644 index 0000000..2e38672 --- /dev/null +++ b/examples/book_writer/frontend/package.json @@ -0,0 +1,28 @@ +{ + "name": "book-writer-saas", + "version": "1.0.0", + "type": "module", + "description": "AI Book Writer SaaS Frontend", + "private": true, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "axios": "^1.6.0", + "lucide-react": "^0.263.1" + }, + "devDependencies": { + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.2.2", + "vite": "^5.0.8", + "tailwindcss": "^3.3.0", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.32" + } + } \ No newline at end of file diff --git a/examples/book_writer/frontend/postcss.config.js b/examples/book_writer/frontend/postcss.config.js new file mode 100644 index 0000000..e69de29 diff --git a/examples/book_writer/frontend/public/index.html b/examples/book_writer/frontend/public/index.html new file mode 100644 index 0000000..e69de29 diff --git a/examples/book_writer/frontend/src/App.tsx b/examples/book_writer/frontend/src/App.tsx new file mode 100644 index 0000000..2d6d4ad --- /dev/null +++ b/examples/book_writer/frontend/src/App.tsx @@ -0,0 +1,397 @@ +import React, { useState } from 'react'; +import { BookOpen, FileText, Download, Loader2, ArrowRight, ArrowLeft, Sparkles, Check } from 'lucide-react'; + +interface Chapter { + number: number; + title: string; + description?: string; + content?: string; + word_count?: number; +} + +interface BookResult { + success: boolean; + title: string; + topic: string; + goal: string; + book_content: string; + chapters: Chapter[]; + total_chapters: number; + total_words: number; + outline: Chapter[]; + error?: string; +} + +function App() { + const [step, setStep] = useState<'config' | 'processing' | 'results'>('config'); + const [agentId, setAgentId] = useState(''); + const [bookTitle, setBookTitle] = useState(''); + const [topic, setTopic] = useState(''); + const [goal, setGoal] = useState(''); + const [numChapters, setNumChapters] = useState(5); + const [result, setResult] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [progress, setProgress] = useState(''); + + const generateBook = async () => { + setLoading(true); + setError(null); + setStep('processing'); + setProgress('Initializing book generation...'); + + try { + setProgress('Generating book outline...'); + + const response = await fetch('http://localhost:8000/api/write-book', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + agent_id: agentId, + title: bookTitle, + topic: topic, + goal: goal, + num_chapters: numChapters + }), + }); + + if (!response.ok) { + throw new Error('Failed to generate book'); + } + + const data: BookResult = await response.json(); + + if (data.success) { + setResult(data); + setStep('results'); + } else { + throw new Error(data.error || 'Book generation failed'); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'An error occurred'); + setStep('config'); + } finally { + setLoading(false); + } + }; + + const downloadBook = async () => { + if (!result) return; + + try { + const response = await fetch('http://localhost:8000/api/download-book', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + title: result.title, + content: result.book_content + }), + }); + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${result.title.replace(/\s+/g, '_')}.md`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + } catch (err) { + console.error('Download error:', err); + alert('Failed to download book'); + } + }; + + const resetForm = () => { + setStep('config'); + setResult(null); + setError(null); + setProgress(''); + }; + + return ( +
+
+ {/* Header */} +
+
+ +

+ AI Book Writer +

+
+

+ Generate complete books with AI-powered research and writing +

+
+ + Powered by CrewAI & RunAgent +
+
+ + {/* Configuration Step */} + {step === 'config' && ( +
+

+ + Book Configuration +

+ +
+ {/* Agent ID */} +
+ + setAgentId(e.target.value)} + placeholder="Enter your deployed agent ID" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition" + /> +

+ Get this from: runagent serve . +

+
+ + {/* Book Title */} +
+ + setBookTitle(e.target.value)} + placeholder="e.g., The Future of AI in Healthcare" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition" + /> +
+ + {/* Topic */} +
+ + setTopic(e.target.value)} + placeholder="e.g., AI applications in medical diagnosis and treatment" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition" + /> +
+ + {/* Goal */} +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + + + +
+

Powered by RunAgent & Agno AI

+
+
+ + + + \ No newline at end of file diff --git a/examples/recipe_creator/frontend/package-lock.json b/examples/recipe_creator/frontend/package-lock.json new file mode 100644 index 0000000..3dc0e22 --- /dev/null +++ b/examples/recipe_creator/frontend/package-lock.json @@ -0,0 +1,642 @@ +{ + "name": "chefgenius-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "chefgenius-frontend", + "version": "1.0.0", + "devDependencies": { + "http-server": "^14.1.1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/portfinder": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", + "integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/portfinder/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true, + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/examples/recipe_creator/frontend/package.json b/examples/recipe_creator/frontend/package.json new file mode 100644 index 0000000..314e79f --- /dev/null +++ b/examples/recipe_creator/frontend/package.json @@ -0,0 +1,12 @@ +{ + "name": "chefgenius-frontend", + "version": "1.0.0", + "description": "AI-Powered Recipe Creator Frontend", + "scripts": { + "dev": "http-server -p 3000 -o" + }, + "devDependencies": { + "http-server": "^14.1.1" + } +} + diff --git a/examples/recipe_creator/frontend/style.css b/examples/recipe_creator/frontend/style.css new file mode 100644 index 0000000..b8324c8 --- /dev/null +++ b/examples/recipe_creator/frontend/style.css @@ -0,0 +1,443 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); + background-attachment: fixed; + min-height: 100vh; + padding: 20px; + position: relative; + overflow-x: hidden; +} + +body::before { + content: ''; + position: fixed; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle at 30% 20%, rgba(255,255,255,0.1) 0%, transparent 50%), + radial-gradient(circle at 70% 80%, rgba(255,255,255,0.1) 0%, transparent 50%); + animation: float 20s ease-in-out infinite; + pointer-events: none; + z-index: 0; +} + +.container { + max-width: 900px; + margin: 0 auto; + position: relative; + z-index: 1; +} + +/* Header */ +header { + text-align: center; + color: white; + margin-bottom: 40px; + animation: fadeInDown 0.8s ease; +} + +header h1 { + font-size: 3.5em; + margin-bottom: 10px; + text-shadow: 2px 2px 20px rgba(0,0,0,0.3); + background: linear-gradient(135deg, #fff 0%, #f0f0f0 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-weight: 700; + letter-spacing: -1px; +} + +.tagline { + font-size: 1.2em; + opacity: 0.95; + text-shadow: 1px 1px 10px rgba(0,0,0,0.2); + font-weight: 300; +} + +/* Main Content */ +main { + background: rgba(255, 255, 255, 0.98); + backdrop-filter: blur(10px); + border-radius: 24px; + padding: 50px; + box-shadow: 0 25px 80px rgba(0,0,0,0.25), + 0 0 0 1px rgba(255,255,255,0.5); + animation: fadeInUp 0.8s ease; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +/* Examples Section */ +.examples-section { + margin-bottom: 40px; +} + +.examples-section h2 { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 24px; + font-size: 1.6em; + font-weight: 700; +} + +.examples-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; + margin-bottom: 30px; +} + +.example-card { + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + border: 2px solid #e9ecef; + border-radius: 16px; + padding: 20px; + cursor: pointer; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.example-card::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); + transition: left 0.5s ease; +} + +.example-card:hover::before { + left: 100%; +} + +.example-card:hover { + border-color: #667eea; + background: #fff; + transform: translateY(-4px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.25); +} + +.example-card h3 { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-size: 1.05em; + margin-bottom: 10px; + font-weight: 600; + position: relative; + z-index: 1; +} + +.example-card p { + font-size: 0.9em; + color: #666; + margin: 6px 0; + position: relative; + z-index: 1; +} + +/* Form Section */ +.form-section h2 { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 30px; + font-size: 1.6em; + font-weight: 700; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + font-weight: 600; + color: #555; + margin-bottom: 8px; + font-size: 1em; +} + +.form-group input, +.form-group textarea { + width: 100%; + padding: 14px 16px; + border: 2px solid #e9ecef; + border-radius: 12px; + font-size: 1em; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + font-family: inherit; + background: white; +} + +.form-group input:focus, +.form-group textarea:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1), + inset 0 0 0 1px #667eea; + transform: translateY(-1px); +} + +.form-group textarea { + resize: vertical; +} + +/* Buttons */ +.form-actions { + display: flex; + gap: 15px; + margin-top: 30px; +} + +.btn { + flex: 1; + padding: 15px 30px; + border: none; + border-radius: 8px; + font-size: 1em; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + position: relative; + overflow: hidden; +} + +.btn-primary::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.btn-primary:hover::before { + width: 300px; + height: 300px; +} + +.btn-primary:hover { + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5); +} + +.btn-secondary { + background: linear-gradient(135deg, #6c757d 0%, #5a6268 100%); + color: white; +} + +.btn-secondary:hover { + background: linear-gradient(135deg, #5a6268 0%, #495057 100%); + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(108, 117, 125, 0.4); +} + +.btn-clear { + background: #dc3545; + color: white; + padding: 8px 16px; + font-size: 0.9em; +} + +.btn-clear:hover { + background: #c82333; +} + +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none !important; +} + +/* Results Section */ +.results-section { + margin-top: 40px; + padding-top: 40px; + border-top: 2px solid #e9ecef; +} + +.results-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.results-header h2 { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-size: 1.6em; + font-weight: 700; +} + +.recipe-content { + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + border: 1px solid #e9ecef; + border-radius: 16px; + padding: 30px; + line-height: 1.9; + color: #333; + animation: fadeIn 0.5s ease; + box-shadow: inset 0 2px 8px rgba(0,0,0,0.05); +} + +.recipe-content h3 { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-top: 24px; + margin-bottom: 12px; + font-weight: 700; +} + +.recipe-content h3:first-child { + margin-top: 0; +} + +.recipe-content ul, +.recipe-content ol { + margin-left: 25px; + margin-bottom: 15px; +} + +.recipe-content li { + margin-bottom: 8px; +} + +.recipe-content p { + margin-bottom: 15px; +} + +.recipe-content strong { + color: #555; +} + +/* Loading Indicator */ +.loading { + text-align: center; + padding: 40px; +} + +.spinner { + width: 60px; + height: 60px; + margin: 0 auto 20px; + border: 5px solid rgba(102, 126, 234, 0.1); + border-top: 5px solid #667eea; + border-right: 5px solid #764ba2; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.loading p { + color: #666; + font-size: 1.1em; +} + +/* Footer */ +footer { + text-align: center; + color: white; + margin-top: 30px; + padding: 20px; + opacity: 0.8; +} + +/* Animations */ +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0) translateX(0); + } + 33% { + transform: translateY(-20px) translateX(10px); + } + 66% { + transform: translateY(20px) translateX(-10px); + } +} + +/* Responsive Design */ +@media (max-width: 768px) { + body { + padding: 10px; + } + + main { + padding: 25px; + } + + header h1 { + font-size: 2em; + } + + .form-actions { + flex-direction: column; + } + + .examples-grid { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/examples/recipe_creator/sdk/python/test.py b/examples/recipe_creator/sdk/python/test.py new file mode 100644 index 0000000..d15cf0f --- /dev/null +++ b/examples/recipe_creator/sdk/python/test.py @@ -0,0 +1,156 @@ +""" +Simple test script for ChefGenius Recipe Agent using RunAgent Python SDK + +Usage: + python test_agent.py +""" + +from runagent import RunAgentClient +from dotenv import load_dotenv +import os + + + + +print("🧪 Testing ChefGenius Recipe Agent") +print("=" * 50) + +# Test 1: Non-streaming recipe creation +def test_non_streaming(): + print("\n1️⃣ Test: Non-Streaming Recipe Creation") + print("-" * 50) + + client = RunAgentClient( + agent_id="0c95d974-249d-412f-b543-2cada015c945", + entrypoint_tag="recipe_create", + local=False + ) + + result = client.run( + ingredients="chicken breast, broccoli, rice, garlic", + dietary_restrictions="", + time_limit="30 minutes" + ) + + print("✅ Result:") + if result.get("success"): + print(result["recipe"]) + else: + print("❌ Error:", result) + + return result + + +# Test 2: Streaming recipe creation +def test_streaming(): + print("\n2️⃣ Test: Streaming Recipe Creation") + print("-" * 50) + + client = RunAgentClient( + agent_id="26aa9224-adc4-4c49-9cc5-d35be2a3f09b", + entrypoint_tag="recipe_stream", + local=False + ) + + print("✅ Streaming output:\n") + + for chunk in client.run( + ingredients="pasta, mushrooms, spinach, cream", + dietary_restrictions="vegetarian", + time_limit="25 minutes" + ): + print(chunk) + + print("\n\n✅ Stream complete!") + + +# Test 3: Quick test with minimal params +def test_quick(): + print("\n3️⃣ Test: Quick Recipe (minimal params)") + print("-" * 50) + + client = RunAgentClient( + agent_id=AGENT_ID, + entrypoint_tag="recipe_create", + local=LOCAL_MODE + ) + + result = client.run( + ingredients="eggs, cheese, tomatoes" + ) + + print("✅ Result:") + if result.get("success"): + print(result["recipe"][:500] + "...") # Show first 500 chars + else: + print("❌ Error:", result) + + +# Test 4: Vegan recipe +def test_vegan(): + print("\n4️⃣ Test: Vegan Recipe") + print("-" * 50) + + client = RunAgentClient( + agent_id=AGENT_ID, + entrypoint_tag="recipe_create", + local=LOCAL_MODE + ) + + result = client.run( + ingredients="quinoa, chickpeas, sweet potato, kale", + dietary_restrictions="vegan", + time_limit="40 minutes" + ) + + print("✅ Result:") + if result.get("success"): + print(result["recipe"][:500] + "...") + else: + print("❌ Error:", result) + + +# Main test runner +def run_all_tests(): + try: + # Validate agent ID + if AGENT_ID == "your-agent-id-here": + print("❌ ERROR: Please set AGENT_ID in .env file") + print(" Run 'runagent serve .' in the agent directory first") + return + + print(f"Agent ID: {AGENT_ID}") + print(f"Local Mode: {LOCAL_MODE}") + + # Run tests + test_non_streaming() + print("\n" + "=" * 50) + + test_streaming() + print("\n" + "=" * 50) + + test_quick() + print("\n" + "=" * 50) + + test_vegan() + print("\n" + "=" * 50) + + print("\n🎉 All tests completed!") + + except Exception as e: + print(f"\n❌ Test failed with error: {e}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + # You can run individual tests or all tests + + # Run all tests + # run_all_tests() + + # Or run individual tests: + # test_non_streaming() + test_streaming() + # test_quick() + # test_vegan() \ No newline at end of file diff --git a/runagent-rust/runagent/src/client/rest_client.rs b/runagent-rust/runagent/src/client/rest_client.rs index a21b51a..4a046ec 100644 --- a/runagent-rust/runagent/src/client/rest_client.rs +++ b/runagent-rust/runagent/src/client/rest_client.rs @@ -100,14 +100,15 @@ impl RestClient { data: Option<&Value>, params: Option<&HashMap>, ) -> RunAgentResult { - let url = self.get_url(path)?; + let mut url = self.get_url(path)?; - let mut request_builder = self.client.request(method, url); - - // Add authorization header if API key is available + // Add API key as token query parameter if available (matching WebSocket behavior) if let Some(ref api_key) = self.api_key { - request_builder = request_builder.header("Authorization", format!("Bearer {}", api_key)); + url.query_pairs_mut() + .append_pair("token", api_key); } + + let mut request_builder = self.client.request(method, url); // Add query parameters if let Some(params) = params { @@ -121,6 +122,11 @@ impl RestClient { .json(data); } + // Add Authorization header if API key is available + if let Some(ref api_key) = self.api_key { + request_builder = request_builder.header("Authorization", format!("Bearer {}", api_key)); + } + let response = request_builder.send().await?; self.handle_response(response).await } @@ -163,13 +169,15 @@ impl RestClient { input_kwargs: &HashMap, ) -> RunAgentResult { let data = serde_json::json!({ - "input_data": { - "input_args": input_args, - "input_kwargs": input_kwargs - } + "id": "run_start", + "entrypoint_tag": entrypoint_tag, + "input_args": input_args, + "input_kwargs": input_kwargs, + "timeout_seconds": 600, + "async_execution": false }); - let path = format!("agents/{}/execute/{}", agent_id, entrypoint_tag); + let path = format!("agents/{}/run", agent_id); self.post(&path, &data).await } diff --git a/runagent-rust/runagent/src/client/runagent_client.rs b/runagent-rust/runagent/src/client/runagent_client.rs index d80e77c..ce28691 100644 --- a/runagent-rust/runagent/src/client/runagent_client.rs +++ b/runagent-rust/runagent/src/client/runagent_client.rs @@ -135,9 +135,20 @@ impl RunAgentClient { db_service, }; - // Get agent architecture - client.agent_architecture = Some(client.get_agent_architecture_internal().await?); - client.validate_entrypoint()?; + // Get agent architecture (skip validation to match Python SDK behavior) + match client.get_agent_architecture_internal().await { + Ok(architecture) => { + client.agent_architecture = Some(architecture); + tracing::debug!("Agent architecture loaded, skipping client-side validation"); + } + Err(e) => { + tracing::debug!("Failed to get agent architecture, skipping validation: {}", e); + // Set a minimal architecture to avoid validation errors + client.agent_architecture = Some(serde_json::json!({ + "entrypoints": [{"tag": "simulate_stream", "file": "main.py", "module": "simulate_stream"}] + })); + } + } Ok(client) } @@ -146,7 +157,7 @@ impl RunAgentClient { match self.rest_client.get_agent_architecture(&self.agent_id).await { Ok(architecture) => Ok(architecture), Err(_) => { - // Fallback: provide default architecture + // Fallback: provide default architecture with common entrypoints Ok(serde_json::json!({ "entrypoints": [ { @@ -158,6 +169,16 @@ impl RunAgentClient { "tag": "generic_stream", "file": "main.py", "module": "run_stream" + }, + { + "tag": "simulate_stream", + "file": "main.py", + "module": "simulate_stream" + }, + { + "tag": "run", + "file": "main.py", + "module": "run" } ] })) @@ -219,17 +240,52 @@ impl RunAgentClient { .await?; if response.get("success").and_then(|s| s.as_bool()).unwrap_or(false) { + // Handle new response format with nested data (matching Python SDK) + if let Some(data) = response.get("data") { + if let Some(result_data) = data.get("result_data") { + if let Some(output_data) = result_data.get("data") { + // Check if the output contains a generator object string + if let Some(content_str) = output_data.as_str() { + if content_str.contains("generator object") { + tracing::warn!("Agent returned generator object instead of content. Consider using streaming endpoint for this agent."); + // Return the raw string for now + return Ok(output_data.clone()); + } + } + return self.serializer.deserialize_object(output_data.clone()); + } + } + } + // Fallback to old format for backward compatibility if let Some(output_data) = response.get("output_data") { - self.serializer.deserialize_object(output_data.clone()) - } else { - Ok(Value::Null) + // Check if the output contains a generator object string + if let Some(content_str) = output_data.as_str() { + if content_str.contains("generator object") { + tracing::warn!("Agent returned generator object instead of content. Consider using streaming endpoint for this agent."); + // Return the raw string for now + return Ok(output_data.clone()); + } + } + return self.serializer.deserialize_object(output_data.clone()); } + Ok(Value::Null) } else { - let error_msg = response - .get("error") - .and_then(|e| e.as_str()) - .unwrap_or("Unknown error"); - Err(RunAgentError::server(error_msg)) + // Handle new error format with ErrorDetail object (matching Python SDK) + if let Some(error_info) = response.get("error") { + if let Some(error_obj) = error_info.as_object() { + if let (Some(message), Some(code)) = ( + error_obj.get("message").and_then(|m| m.as_str()), + error_obj.get("code").and_then(|c| c.as_str()) + ) { + return Err(RunAgentError::server(format!("[{}] {}", code, message))); + } + } + // Fallback to old format + if let Some(error_msg) = error_info.as_str() { + return Err(RunAgentError::server(error_msg)); + } + } + Err(RunAgentError::server("Unknown error")) } } diff --git a/runagent-rust/runagent/src/client/socket_client.rs b/runagent-rust/runagent/src/client/socket_client.rs index f936a1c..86bc476 100644 --- a/runagent-rust/runagent/src/client/socket_client.rs +++ b/runagent-rust/runagent/src/client/socket_client.rs @@ -1,8 +1,7 @@ //! WebSocket client for streaming agent interactions use crate::types::{ - RunAgentError, RunAgentResult, SafeMessage, WebSocketActionType, WebSocketAgentRequest, - AgentInputArgs, MessageType, + RunAgentError, RunAgentResult, SafeMessage, MessageType, }; use crate::utils::config::Config; use crate::utils::serializer::CoreSerializer; @@ -55,9 +54,14 @@ impl SocketClient { Self::new(&ws_url, config.api_key(), Some("/api/v1")) } - fn get_websocket_url(&self, agent_id: &str, entrypoint_tag: &str) -> RunAgentResult { - let path = format!("agents/{}/execute/{}", agent_id, entrypoint_tag); - let full_url = format!("{}{}/{}", self.base_socket_url, self.api_prefix, path); + fn get_websocket_url(&self, agent_id: &str, _entrypoint_tag: &str) -> RunAgentResult { + let path = format!("agents/{}/run-stream", agent_id); + let mut full_url = format!("{}{}/{}", self.base_socket_url, self.api_prefix, path); + + // Add API key as token parameter if available + if let Some(ref api_key) = self.api_key { + full_url = format!("{}?token={}", full_url, api_key); + } Url::parse(&full_url) .map_err(|e| RunAgentError::validation(format!("Invalid WebSocket URL: {}", e))) @@ -81,39 +85,34 @@ impl SocketClient { let (mut write, mut read) = ws_stream.split(); - // Prepare start stream request - let request = WebSocketAgentRequest { - action: WebSocketActionType::StartStream, - agent_id: agent_id.to_string(), - input_data: AgentInputArgs { - input_args: input_args.to_vec(), - input_kwargs: input_kwargs.clone(), - }, - stream_config: HashMap::new(), - }; - - let start_msg = SafeMessage::new( - "stream_start".to_string(), - MessageType::Status, - serde_json::to_value(&request)?, - ); - - // Send start stream message - let serialized_msg = self.serializer.serialize_message(&start_msg)?; + // Prepare start stream request with id field (as middleware expects) + let request_data = serde_json::json!({ + "id": "stream_start", + "entrypoint_tag": entrypoint_tag, + "input_args": input_args, + "input_kwargs": input_kwargs, + "timeout_seconds": 600, + "async_execution": false + }); + + // Send the request data directly (matching Python SDK format) + let serialized_msg = serde_json::to_string(&request_data)?; write.send(Message::Text(serialized_msg)).await .map_err(|e| RunAgentError::connection(format!("Failed to send start message: {}", e)))?; - // Create stream that processes incoming messages - let serializer = self.serializer.clone(); + // Create stream that processes incoming messages (matching Python SDK behavior) let stream = async_stream::stream! { while let Some(message) = read.next().await { match message { Ok(Message::Text(text)) => { - match serializer.deserialize_message(&text) { - Ok(safe_msg) => { - match safe_msg.message_type { - MessageType::Status => { - if let Some(status) = safe_msg.data.get("status") { + // Parse as plain JSON (matching Python SDK) + match serde_json::from_str::(&text) { + Ok(msg) => { + let message_type = msg.get("type").and_then(|v| v.as_str()); + + match message_type { + Some("status") => { + if let Some(status) = msg.get("status").and_then(|v| v.as_str()) { if status == "stream_completed" { break; } else if status == "stream_started" { @@ -121,20 +120,28 @@ impl SocketClient { } } } - MessageType::Error => { - yield Err(RunAgentError::server( - safe_msg.error.unwrap_or_else(|| "Agent error".to_string()) - )); + Some("error") => { + let error_msg = msg.get("error") + .or_else(|| msg.get("detail")) + .and_then(|v| v.as_str()) + .unwrap_or("Unknown error"); + yield Err(RunAgentError::server(format!("Stream error: {}", error_msg))); break; } + Some("data") => { + // Yield the content field (matching Python SDK) + if let Some(content) = msg.get("content") { + yield Ok(content.clone()); + } + } _ => { - // Yield the actual chunk data - yield Ok(safe_msg.data); + // For other message types, yield the whole message + yield Ok(msg); } } } Err(e) => { - yield Err(RunAgentError::server(format!("Stream error: {}", e))); + yield Err(RunAgentError::server(format!("Stream error: JSON error: {}", e))); break; } } diff --git a/runagent-rust/runagent/src/constants.rs b/runagent-rust/runagent/src/constants.rs index 36cde3d..f8175f6 100644 --- a/runagent-rust/runagent/src/constants.rs +++ b/runagent-rust/runagent/src/constants.rs @@ -31,7 +31,7 @@ pub const ENV_LOCAL_CACHE_DIRECTORY: &str = "RUNAGENT_CACHE_DIR"; pub const ENV_RUNAGENT_LOGGING_LEVEL: &str = "RUNAGENT_LOGGING_LEVEL"; /// Default base URL -pub const DEFAULT_BASE_URL: &str = "http://52.237.88.147:8330/"; +pub const DEFAULT_BASE_URL: &str = "http://20.84.81.110:8333/"; /// Agent config file name pub const AGENT_CONFIG_FILE_NAME: &str = "runagent.config.json"; diff --git a/runagent-rust/runagent/src/types/schema.rs b/runagent-rust/runagent/src/types/schema.rs index 1ff7f15..81d1511 100644 --- a/runagent-rust/runagent/src/types/schema.rs +++ b/runagent-rust/runagent/src/types/schema.rs @@ -94,6 +94,7 @@ pub enum WebSocketActionType { pub struct WebSocketAgentRequest { pub action: WebSocketActionType, pub agent_id: String, + pub entrypoint_tag: String, pub input_data: AgentInputArgs, #[serde(default)] pub stream_config: HashMap, @@ -147,6 +148,7 @@ pub enum MessageType { AgentThought, FinalResponse, Error, + ExecutionError, Status, RawData, Data, diff --git a/runagent-rust/runagent/src/utils/config.rs b/runagent-rust/runagent/src/utils/config.rs index c09fa44..cd0b3ed 100644 --- a/runagent-rust/runagent/src/utils/config.rs +++ b/runagent-rust/runagent/src/utils/config.rs @@ -15,6 +15,11 @@ use std::path::PathBuf; pub struct Config { pub api_key: Option, pub base_url: String, + pub user_email: Option, + pub user_id: Option, + pub user_tier: Option, + pub auth_validated: Option, + #[serde(default)] pub user_info: HashMap, } @@ -23,6 +28,10 @@ impl Default for Config { Self { api_key: None, base_url: DEFAULT_BASE_URL.to_string(), + user_email: None, + user_id: None, + user_tier: None, + auth_validated: None, user_info: HashMap::new(), } } @@ -69,8 +78,10 @@ impl Config { let content = fs::read_to_string(&config_path) .map_err(|e| RunAgentError::config(format!("Failed to read config file: {}", e)))?; - serde_json::from_str(&content) - .map_err(|e| RunAgentError::config(format!("Failed to parse config file: {}", e))) + match serde_json::from_str::(&content) { + Ok(parsed_config) => Ok(parsed_config), + Err(_) => Ok(Self::default()) + } } else { Ok(Self::default()) } diff --git a/runagent/__version__.py b/runagent/__version__.py index a1e8857..9eb734d 100644 --- a/runagent/__version__.py +++ b/runagent/__version__.py @@ -1,5 +1 @@ -<<<<<<< HEAD __version__ = "0.1.23" -======= -__version__ = "0.1.23" ->>>>>>> sawra/runagent_cloud_support diff --git a/runagent/cli/branding.py b/runagent/cli/branding.py new file mode 100644 index 0000000..c52a17e --- /dev/null +++ b/runagent/cli/branding.py @@ -0,0 +1,113 @@ +""" +CLI Branding - ASCII art logo and styling for RunAgent +""" + +from rich.console import Console + +console = Console() + + +def print_logo(show_tagline: bool = True, brand_color: str = "cyan"): + """ + Print the RunAgent ASCII art logo + "Run" in brand color (cyan), "Agent" in white + + Args: + show_tagline: Whether to show the tagline below the logo + brand_color: Brand color for "Run" part (default: cyan) + """ + # Split logo into "Run" part (cyan) and "Agent" part (white) + logo = f"""[dim]╔═══════════════════════════════════════════════════════════════╗ +║ ║[/dim] +[bold {brand_color}]║ ██████╗ ██╗ ██╗███╗ ██╗[/bold {brand_color}][bold white] █████╗ ██████╗ ███████╗███╗ ██╗████████╗[/bold white] +[bold {brand_color}]║ ██╔══██╗██║ ██║████╗ ██║[/bold {brand_color}][bold white]██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝[/bold white] +[bold {brand_color}]║ ██████╔╝██║ ██║██╔██╗ ██║[/bold {brand_color}][bold white]███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ [/bold white] +[bold {brand_color}]║ ██╔══██╗██║ ██║██║╚██╗██║[/bold {brand_color}][bold white]██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ [/bold white] +[bold {brand_color}]║ ██║ ██║╚██████╔╝██║ ╚████║[/bold {brand_color}][bold white]██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ [/bold white] +[bold {brand_color}]║ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝[/bold {brand_color}][bold white]╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ [/bold white] +[dim]║ ║ +╚═══════════════════════════════════════════════════════════════╝[/dim]""" + + console.print(logo, highlight=False) + + if show_tagline: + console.print(f"[dim] Deploy and manage AI agents with ease 🚀[/dim]\n") + + +def print_compact_logo(brand_color: str = "cyan"): + """ + Print a compact version of the RunAgent logo for smaller spaces + "Run" in brand color, "Agent" in white + + Args: + brand_color: Brand color for "Run" part (default: cyan) + """ + logo = f""" +[bold {brand_color}] ██████╗ ██╗ ██╗███╗ ██╗[/bold {brand_color}][bold white] █████╗ ██████╗ ███████╗███╗ ██╗████████╗[/bold white] +[bold {brand_color}] ██╔══██╗██║ ██║████╗ ██║[/bold {brand_color}][bold white]██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝[/bold white] +[bold {brand_color}] ██████╔╝██║ ██║██╔██╗ ██║[/bold {brand_color}][bold white]███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ [/bold white] +[bold {brand_color}] ██╔══██╗██║ ██║██║╚██╗██║[/bold {brand_color}][bold white]██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ [/bold white] +[bold {brand_color}] ██║ ██║╚██████╔╝██║ ╚████║[/bold {brand_color}][bold white]██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ [/bold white] +[bold {brand_color}] ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝[/bold {brand_color}][bold white]╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ [/bold white]""" + console.print(logo, highlight=False) + + +def print_minimal_logo(brand_color: str = "cyan"): + """ + Print a minimal single-line logo + "Run" in brand color, "Agent" in white + + Args: + brand_color: Brand color for "Run" part (default: cyan) + """ + console.print(f"[bold {brand_color}]Run[/bold {brand_color}][bold white]Agent[/bold white] [dim]|[/dim] [dim]Deploy AI agents with ease 🚀[/dim]") + + +def print_header(command_name: str = None, brand_color: str = "cyan"): + """ + Print a simple header bar like a webpage header + Perfect for internal pages without overwhelming the user + + Args: + command_name: Optional command name to show (e.g., "Configuration", "Database") + brand_color: Brand color for "Run" part (default: cyan) + """ + # Top border + console.print(f"[dim]{'─' * 70}[/dim]") + + # Header content + if command_name: + console.print( + f"[bold {brand_color}]Run[/bold {brand_color}][bold white]Agent[/bold white] " + f"[dim]›[/dim] {command_name}" + ) + else: + console.print( + f"[bold {brand_color}]Run[/bold {brand_color}][bold white]Agent[/bold white] " + f"[dim]CLI[/dim]" + ) + + # Bottom border + console.print(f"[dim]{'─' * 70}[/dim]\n") + + +def print_welcome_banner(version: str = None): + """ + Print a welcome banner with logo and version + + Args: + version: Version string to display + """ + print_logo(show_tagline=True, brand_color="cyan") + + if version: + console.print(f"[dim] Version {version}[/dim]\n") + else: + console.print() + + +def print_setup_banner(): + """Print a special banner for the setup command""" + print_logo(show_tagline=False, brand_color="cyan") + console.print("[bold cyan] 🎉 Welcome to RunAgent! 🎉[/bold cyan]") + console.print("[dim] Let's get you set up in a few steps...[/dim]\n") diff --git a/runagent/cli/commands.py b/runagent/cli/commands.py deleted file mode 100644 index ff33016..0000000 --- a/runagent/cli/commands.py +++ /dev/null @@ -1,1931 +0,0 @@ -""" -CLI commands that use the restructured SDK internally. -""" -import os -import json -import uuid - -from pathlib import Path - -import click -from rich.console import Console -from rich.table import Table - -from runagent import RunAgent -from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError - AuthenticationError, - TemplateError, - ValidationError, -) -from runagent.client.client import RunAgentClient -from runagent.sdk.server.local_server import LocalServer -from runagent.utils.agent import detect_framework -from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner -from runagent.utils.config import Config -from runagent.sdk.deployment.middleware_sync import get_middleware_sync -from runagent.cli.utils import add_framework_options, get_selected_framework -from runagent.utils.enums.framework import Framework -console = Console() - - -def format_error_message(error_info): - """Format error information from API responses""" - if isinstance(error_info, dict) and "message" in error_info: - # New format with ErrorDetail object - error_message = error_info.get("message", "Unknown error") - error_code = error_info.get("code", "UNKNOWN_ERROR") - return f"[{error_code}] {error_message}" - else: - # Fallback to old format for backward compatibility - return str(error_info) if error_info else "Unknown error" - - -def print_version(ctx, param, value): - """Custom version callback with colored output""" - if not value or ctx.resilient_parsing: - return - try: - from runagent.__version__ import __version__ - console.print(f"[bold cyan]runagent {__version__}[/bold cyan]") - except ImportError: - console.print("[red]runagent version unknown[/red]") - ctx.exit() - - -@click.command() -def version(): - """Show version information""" - try: - from runagent.__version__ import __version__ - console.print(f"[bold cyan]runagent {__version__}[/bold cyan]") - except ImportError: - console.print("[red]runagent version unknown[/red]") - -@click.command() -@click.option("--api-key", required=True, help="Your API key") -@click.option("--base-url", help="API base URL") -@click.option("--force", is_flag=True, help="Force reconfiguration") -def setup(api_key, base_url, force): - """Setup RunAgent authentication""" - try: - sdk = RunAgent() - - # Check if already configured - if sdk.is_configured() and not force: - config_status = sdk.get_config_status() - console.print("⚠️ RunAgent is already configured:") - console.print(f" Base URL: [blue]{config_status.get('base_url')}[/blue]") - user_info = config_status.get('user_info', {}) - if user_info.get('email'): - console.print(f" User: [green]{user_info.get('email')}[/green]") - - if not click.confirm("Do you want to reconfigure?"): - return - - console.print("🔑 [cyan]Setting up RunAgent authentication...[/cyan]") - - # Configure SDK with validation - try: - sdk.configure(api_key=api_key, base_url=base_url, save=True) - console.print("✅ [green]Setup completed successfully![/green]") - except AuthenticationError as auth_err: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Authentication failed:[/red] {auth_err}") - - # Provide specific troubleshooting based on error message - error_msg = str(auth_err).lower() - console.print("\n💡 [yellow]Troubleshooting:[/yellow]") - - if "invalid api key" in error_msg or "not authenticated" in error_msg: - console.print(" • Check that your API key is correct") - console.print(" • Verify the API key is not expired") - console.print(" • Ensure you have access to the middleware") - elif "connection" in error_msg or "timeout" in error_msg: - console.print(" • Check your internet connection") - console.print(" • Verify the middleware server is accessible") - console.print(f" • Trying to connect to: {base_url or sdk.config.base_url}") - else: - console.print(" • Check your API key and network connection") - console.print(" • Contact support if the issue persists") - - raise click.ClickException("Authentication failed") - - # Show user information (from cached data) - config_status = sdk.get_config_status() - user_info = config_status.get('user_info', {}) - - if user_info and user_info.get('email'): - console.print("\n👤 [bold]User Information:[/bold]") - console.print(f" Email: [cyan]{user_info.get('email')}[/cyan]") - if user_info.get('user_id'): - console.print(f" User ID: [dim]{user_info.get('user_id')}[/dim]") - if user_info.get('tier'): - console.print(f" Tier: [yellow]{user_info.get('tier')}[/yellow]") - - # Show sync status (simplified) - console.print("\n🔄 [bold]Middleware Sync Status:[/bold]") - try: - from runagent.sdk.deployment.middleware_sync import MiddlewareSyncService - sync_service = MiddlewareSyncService(sdk.config) - - if sync_service.is_sync_enabled(): - console.print(" Status: [green]✅ ENABLED[/green]") - console.print(" 📊 Local agent runs will sync to middleware") - else: - console.print(" Status: [yellow]⚠️ DISABLED[/yellow]") - console.print(" 📊 Only local storage will be used") - - except Exception as e: - console.print(f" Status: [yellow]Unknown - {e}[/yellow]") - - # Show next steps - console.print("\n💡 [bold]Next Steps:[/bold]") - console.print(" • Test with a local agent: [cyan]runagent serve [/cyan]") - console.print(" • Check middleware sync: [cyan]runagent local-sync --status[/cyan]") - console.print(" • Upload agent to middleware: [cyan]runagent upload --folder [/cyan]") - - except AuthenticationError: - # Already handled above - raise - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Setup error:[/red] {e}") - raise click.ClickException("Setup failed") - - - -@click.command() -@click.option("--yes", is_flag=True, help="Skip confirmation") -def teardown(yes): - """Remove RunAgent configuration""" - try: - sdk = RunAgent() - - if not yes: - config_status = sdk.get_config_status() - if config_status.get("configured"): - console.print("📋 [bold]Current configuration:[/bold]") - console.print( - f" Base URL: [blue]{config_status.get('base_url')}[/blue]" - ) - user_info = config_status.get("user_info", {}) - if user_info.get("email"): - console.print(f" User: [green]{user_info.get('email')}[/green]") - - if not click.confirm( - "⚠️ This will remove all RunAgent configuration. Continue?" - ): - console.print("Teardown cancelled.") - return - - # Clear configuration - sdk.config.clear() - - console.print("✅ [green]RunAgent teardown completed successfully![/green]") - console.print( - "💡 Run [cyan]'runagent setup --api-key '[/cyan] to reconfigure" - ) - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Teardown error:[/red] {e}") - raise click.ClickException("Teardown failed") - - -@click.command() -@click.option("--id", "agent_id", required=True, help="Agent ID to delete") -@click.option("--yes", is_flag=True, help="Skip confirmation") -def delete(agent_id, yes): - """Delete an agent from the local database""" - try: - sdk = RunAgent() - - # Get agent info first - agent = sdk.db_service.get_agent(agent_id) - if not agent: - console.print(f"❌ [red]Agent {agent_id} not found in database[/red]") - - # Show available agents - console.print("\n💡 Available agents:") - agents = sdk.db_service.list_agents() - if agents: - table = Table(title="Available Agents") - table.add_column("Agent ID", style="magenta") - table.add_column("Framework", style="green") - table.add_column("Status", style="yellow") - table.add_column("Deployed At", style="dim") - - for agent in agents[:10]: # Show first 10 - table.add_row( - agent['agent_id'][:8] + "...", - agent['framework'], - agent['status'], - agent['deployed_at'] or "Unknown" - ) - console.print(table) - else: - console.print(" No agents found in database") - - raise click.ClickException("Agent not found") - - # Show agent details - console.print(f"\n🔍 [yellow]Agent to be deleted:[/yellow]") - console.print(f" Agent ID: [bold magenta]{agent['agent_id']}[/bold magenta]") - console.print(f" Framework: [green]{agent['framework']}[/green]") - console.print(f" Path: [blue]{agent['agent_path']}[/blue]") - console.print(f" Status: [yellow]{agent['status']}[/yellow]") - console.print(f" Deployed: [dim]{agent['deployed_at']}[/dim]") - console.print(f" Total Runs: [cyan]{agent['run_count']}[/cyan]") - - # Confirmation - if not yes: - if not click.confirm("\n⚠️ This will permanently delete the agent from the database. Continue?"): - console.print("Deletion cancelled.") - return - - # Delete the agent - result = sdk.db_service.force_delete_agent(agent_id) - - if result["success"]: - console.print(f"\n✅ [green]Agent {agent_id} deleted successfully![/green]") - - # Show updated capacity - capacity_info = sdk.db_service.get_database_capacity_info() - console.print(f"📊 Updated capacity: [cyan]{capacity_info.get('current_count', 0)}/5[/cyan] agents") - else: - console.print(f"❌ [red]Failed to delete agent:[/red] {format_error_message(result.get('error'))}") - import sys - sys.exit(1) - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Delete error:[/red] {e}") - import sys - sys.exit(1) - - -@click.command() -@click.option("--template", default="default", help="Template variant (basic, advanced, default)") -@click.option("--interactive", "-i", is_flag=True, help="Enable interactive prompts") -@click.option("--overwrite", is_flag=True, help="Overwrite existing folder") -@add_framework_options # This automatically adds all framework options! -@click.argument( - "path", - type=click.Path( - file_okay=False, - dir_okay=True, - readable=True, - resolve_path=True, - path_type=Path, - ), - default=".", - required=False, -) -def init(template, interactive, overwrite, path, **kwargs): - """Initialize a new RunAgent project""" - - try: - sdk = RunAgent() - - # Extract selected framework using our helper - selected_framework = get_selected_framework(kwargs) - framework = selected_framework if selected_framework else Framework.DEFAULT - - if interactive: - if framework == Framework.DEFAULT: - console.print("🎯 [bold]Available frameworks:[/bold]") - selectable_frameworks = Framework.get_selectable_frameworks() - - for i, fw in enumerate(selectable_frameworks, 1): - category_emoji = "🐍" if fw.is_pythonic() else "🌐" if fw.is_webhook() else "❓" - console.print(f" {i}. {category_emoji} {fw.value} ({fw.category})") - - choice = click.prompt( - "Select framework", - type=click.IntRange(1, len(selectable_frameworks)), - default=1 - ) - framework = selectable_frameworks[choice - 1] - - if template == "default": - templates = sdk.list_templates(framework.value) - template_list = templates.get(framework.value, ["default"]) - - console.print(f"\n🧱 [bold]Available templates for {framework.value}:[/bold]") - for i, tmpl in enumerate(template_list, 1): - console.print(f" {i}. {tmpl}") - - choice = click.prompt( - "Select template", - type=click.IntRange(1, len(template_list)), - default=1 - ) - template = template_list[choice - 1] - - if path.resolve() == Path.cwd(): - project_name = click.prompt( - "Enter project name", - type=str, - default="runagent-project" - ) - path = Path.cwd() / project_name - - # Validate framework if it came from string input - if isinstance(framework, str): - try: - framework = Framework.from_string(framework) - except ValueError as e: - raise click.UsageError(str(e)) - - # Use the path as the project location - project_path = path.resolve() - relative_project_path = project_path.relative_to(Path.cwd()) - - # Ensure the path exists - project_path.parent.mkdir(parents=True, exist_ok=True) - - # Show configuration with enhanced formatting - console.print(f"\n🚀 [bold]Initializing project:[/bold]") - console.print(f" Path: [cyan]{relative_project_path}[/cyan]") - - # Enhanced framework display with category - framework_display = framework.value - if not framework.is_default(): - category_emoji = "🐍" if framework.is_pythonic() else "🌐" if framework.is_webhook() else "❓" - framework_display = f"{category_emoji} {framework.value} ({framework.category})" - - console.print(f" Framework: [magenta]{framework_display}[/magenta]") - console.print(f" Template: [yellow]{template}[/yellow]") - - # Initialize project - success = sdk.init_project( - folder_path=project_path, - framework=framework.value, # Pass the string value - template=template, - overwrite=overwrite - ) - - if success: - console.print(f"\n✅ [green]Project initialized successfully![/green]") - console.print(f"📁 Created at: [cyan]{relative_project_path}[/cyan]") - - # Enhanced next steps with framework-specific guidance - console.print("\n📝 [bold]Next steps:[/bold]") - console.print(f" 1. [cyan]cd {relative_project_path}[/cyan]") - console.print(f" 2. Update your API keys in [yellow].env[/yellow] file") - - # Framework-specific guidance - if framework.is_pythonic(): - console.print(f" 3. Install dependencies: [cyan]pip install -r requirements.txt[/cyan]") - console.print(f" 4. Deploy locally: [cyan]runagent serve {relative_project_path}[/cyan]") - elif framework.is_webhook(): - console.print(f" 3. Configure webhook endpoints in your workflow") - console.print(f" 4. Deploy locally: [cyan]runagent serve {relative_project_path}[/cyan]") - else: - console.print(f" 3. Deploy locally: [cyan]runagent serve {relative_project_path}[/cyan]") - - console.print( - f" 5. Test: [cyan]Test the agent with any of our SDKs. For more details, refer to: [link]https://docs.run-agent.ai/sdk/overview[/link][/cyan]" - ) - - except TemplateError as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Template error:[/red] {e}") - raise click.ClickException("Project initialization failed") - except FileExistsError as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Path exists:[/red] {e}") - console.print("💡 Use [cyan]--overwrite[/cyan] to force initialization") - raise click.ClickException("Project initialization failed") - except click.UsageError: - # Re-raise UsageError as-is for proper click handling - raise - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Initialization error:[/red] {e}") - raise click.ClickException("Project initialization failed") - - -@click.command() -@click.option( - "--list", "action_list", is_flag=True, help="List all available templates" -) -@click.option( - "--info", "action_info", is_flag=True, help="Get detailed template information" -) -@click.option("--framework", help="Framework name (required for --info)") -@click.option("--template", help="Template name (required for --info)") -@click.option("--filter-framework", help="Filter templates by framework") -@click.option( - "--format", - type=click.Choice(["table", "json"]), - default="table", - help="Output format", -) -def template(action_list, action_info, framework, template, filter_framework, format): - """Manage project templates""" - - if not action_list and not action_info: - console.print( - "❌ Please specify either [cyan]--list[/cyan] or [cyan]--info[/cyan]" - ) - raise click.ClickException("No action specified") - - try: - sdk = RunAgent() - - if action_list: - templates = sdk.list_templates(framework=filter_framework) - - if format == "json": - console.print(json.dumps(templates, indent=2)) - else: - console.print("📋 [bold cyan]Available Templates:[/bold cyan]") - for framework_name, template_list in templates.items(): - console.print(f"\n🎯 [bold blue]{framework_name}:[/bold blue]") - for tmpl in template_list: - console.print(f" • {tmpl}") - - console.print( - f"\n💡 Use [cyan]'runagent template --info --framework --template '[/cyan] for details" - ) - - elif action_info: - if not framework or not template: - console.print( - "❌ Both [cyan]--framework[/cyan] and [cyan]--template[/cyan] are required for --info" - ) - raise click.ClickException("Missing required parameters") - - template_info = sdk.get_template_info(framework, template) - - if template_info: - console.print( - f"📋 [bold cyan]Template: {framework}/{template}[/bold cyan]" - ) - console.print( - f"Framework: [magenta]{template_info['framework']}[/magenta]" - ) - console.print(f"Template: [yellow]{template_info['template']}[/yellow]") - - if "metadata" in template_info: - metadata = template_info["metadata"] - if "description" in metadata: - console.print(f"Description: {metadata['description']}") - if "requirements" in metadata: - console.print( - f"Requirements: {', '.join(metadata['requirements'])}" - ) - - console.print(f"\n📁 [bold]Structure:[/bold]") - console.print(f"Files: {', '.join(template_info['files'])}") - if template_info.get("directories"): - console.print( - f"Directories: {', '.join(template_info['directories'])}" - ) - - if "readme" in template_info: - console.print(f"\n📖 [bold]README:[/bold]") - console.print("-" * 50) - console.print( - template_info["readme"][:500] + "..." - if len(template_info["readme"]) > 500 - else template_info["readme"] - ) - console.print("-" * 50) - - console.print(f"\n🚀 [bold]To use this template:[/bold]") - console.print( - f"[cyan]runagent init --framework {framework} --template {template}[/cyan]" - ) - else: - console.print( - f"❌ Template [yellow]{framework}/{template}[/yellow] not found" - ) - - # Show available templates - templates = sdk.list_templates() - if framework in templates: - console.print( - f"Available templates for {framework}: {', '.join(templates[framework])}" - ) - else: - console.print( - f"Available frameworks: {', '.join(templates.keys())}" - ) - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Template error:[/red] {e}") - raise click.ClickException("Template operation failed") - - -@click.command() -@click.argument( - "path", - type=click.Path( - exists=True, - file_okay=False, - dir_okay=True, - readable=True, - resolve_path=True, - path_type=Path, - ), - default=".", -) -def upload(path: Path): - """Upload agent to remote server""" - - try: - sdk = RunAgent() - - # Check authentication - if not sdk.is_configured(): - console.print( - "❌ [red]Not authenticated.[/red] Run [cyan]'runagent setup --api-key '[/cyan] first" - ) - raise click.ClickException("Authentication required") - - # Validate folder - if not Path(path).exists(): - raise click.ClickException(f"Folder not found: {path}") - - console.print(f"📤 [bold]Uploading agent...[/bold]") - console.print(f"📁 Source: [cyan]{path}[/cyan]") - - # Upload agent (framework auto-detected) - result = sdk.upload_agent(folder=path) - - if result.get("success"): - agent_id = result["agent_id"] - console.print(f"\n✅ [green]Upload successful![/green]") - console.print(f"🆔 Agent ID: [bold magenta]{agent_id}[/bold magenta]") - console.print(f"\n💡 [bold]Next step:[/bold]") - console.print(f"[cyan]runagent start --id {agent_id}[/cyan]") - else: - console.print(f"❌ [red]Upload failed:[/red] {format_error_message(result.get('error'))}") - import sys - sys.exit(1) - - except AuthenticationError as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Authentication error:[/red] {e}") - import sys - sys.exit(1) - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Upload error:[/red] {e}") - import sys - sys.exit(1) - - -@click.command() -@click.option("--id", "agent_id", required=True, help="Agent ID to start") -@click.option("--config", help="JSON configuration for deployment") -def start(agent_id, config): - """Start an uploaded agent on remote server""" - - try: - sdk = RunAgent() - - # Check authentication - if not sdk.is_configured(): - console.print( - "❌ [red]Not authenticated.[/red] Run [cyan]'runagent setup --api-key '[/cyan] first" - ) - raise click.ClickException("Authentication required") - - # Parse config - config_dict = {} - if config: - try: - config_dict = json.loads(config) - except json.JSONDecodeError: - if os.getenv('DISABLE_TRY_CATCH'): - raise - raise click.ClickException("Invalid JSON in config parameter") - - console.print(f"🚀 [bold]Starting agent...[/bold]") - console.print(f"🆔 Agent ID: [magenta]{agent_id}[/magenta]") - - # Start agent - result = sdk.start_remote_agent(agent_id, config_dict) - - if result.get("success"): - console.print(f"\n✅ [green]Agent started successfully![/green]") - console.print(f"🌐 Endpoint: [link]{result.get('endpoint')}[/link]") - else: - console.print(f"❌ [red]Start failed:[/red] {format_error_message(result.get('error'))}") - import sys - sys.exit(1) - - except AuthenticationError as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Authentication error:[/red] {e}") - import sys - sys.exit(1) - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Start error:[/red] {e}") - import sys - sys.exit(1) - - -@click.command() -@click.argument( - "path", - type=click.Path( - exists=True, - file_okay=False, - dir_okay=True, - readable=True, - resolve_path=True, - path_type=Path, - ), - default=".", -) -def deploy(path: Path): - """Deploy agent (upload + start) to remote server""" - - try: - sdk = RunAgent() - - # Check authentication - if not sdk.is_configured(): - console.print( - "❌ [red]Not authenticated.[/red] Run [cyan]'runagent setup --api-key '[/cyan] first" - ) - raise click.ClickException("Authentication required") - - # Validate folder - if not Path(path).exists(): - raise click.ClickException(f"Folder not found: {path}") - - console.print(f"🎯 [bold]Deploying agent (upload + start)...[/bold]") - console.print(f"📁 Source: [cyan]{path}[/cyan]") - - # Deploy agent (framework auto-detected) - result = sdk.deploy_remote(folder=str(path)) - - if result.get("success"): - console.print(f"\n✅ [green]Deployment successful![/green]") - console.print(f"🆔 Agent ID: [bold magenta]{result.get('agent_id')}[/bold magenta]") - console.print(f"🌐 Endpoint: [link]{result.get('endpoint')}[/link]") - else: - console.print(f"❌ [red]Deployment failed:[/red] {format_error_message(result.get('error'))}") - import sys - sys.exit(1) - - except AuthenticationError as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Authentication error:[/red] {e}") - import sys - sys.exit(1) - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Deployment error:[/red] {e}") - import sys - sys.exit(1) - - - -@click.command() -@click.option("--port", type=int, help="Preferred port (auto-allocated if unavailable)") -@click.option("--host", default="127.0.0.1", help="Host to bind server to") -@click.option("--debug", is_flag=True, help="Run server in debug mode") -@click.option("--replace", help="Replace existing agent with this agent ID") -@click.option("--no-animation", is_flag=True, help="Skip startup animation") -@click.option("--animation-style", - type=click.Choice(["field", "ascii", "minimal", "quick"]), - default="field", - help="Animation style") -@click.argument( - "path", - type=click.Path( - exists=True, - file_okay=False, - dir_okay=True, - readable=True, - resolve_path=True, - path_type=Path, - ), - default=".", -) -def serve(port, host, debug, replace, no_animation, animation_style, path): - """Start local FastAPI server with subtle robotic runner animation""" - - try: - # Show subtle startup animation - if not no_animation: - console.print("\n") - - if animation_style == "quick": - show_quick_runner(duration=1.5) - else: - show_subtle_robotic_runner(duration=2.0, style=animation_style) - - sdk = RunAgent() - - # Handle replace operation - if replace: - console.print(f"🔄 [yellow]Replacing agent: {replace}[/yellow]") - - # Check if the agent to replace exists - existing_agent = sdk.db_service.get_agent(replace) - if not existing_agent: - console.print(f"⚠️ [yellow]Agent {replace} not found in database[/yellow]") - console.print("💡 Available agents:") - agents = sdk.db_service.list_agents() - for agent in agents[:5]: # Show first 5 - console.print(f" • {agent['agent_id']} ({agent['framework']})") - raise click.ClickException("Agent to replace not found") - - # Generate new agent ID - import uuid - new_agent_id = str(uuid.uuid4()) - - # Get currently used ports to avoid conflicts - used_ports = [] - all_agents = sdk.db_service.list_agents() - for agent in all_agents: - if agent.get('port') and agent['agent_id'] != replace: # Exclude the agent being replaced - used_ports.append(agent['port']) - - # Allocate host and port - from runagent.utils.port import PortManager - if port and PortManager.is_port_available(host, port): - allocated_host = host - allocated_port = port - console.print(f"🎯 Using specified address: [blue]{allocated_host}:{allocated_port}[/blue]") - else: - allocated_host, allocated_port = PortManager.allocate_unique_address(used_ports) - console.print(f"🔌 Auto-allocated address: [blue]{allocated_host}:{allocated_port}[/blue]") - - # Use the existing replace_agent method with proper port allocation - result = sdk.db_service.replace_agent( - old_agent_id=replace, - new_agent_id=new_agent_id, - agent_path=str(path), - host=allocated_host, - port=allocated_port, # Ensure port is not None - framework=detect_framework(path).value, - ) - - if not result["success"]: - raise click.ClickException(f"Failed to replace agent: {result['error']}") - - console.print(f"✅ [green]Agent replaced successfully![/green]") - console.print(f"🆔 New Agent ID: [bold magenta]{new_agent_id}[/bold magenta]") - console.print(f"🔌 Address: [bold blue]{allocated_host}:{allocated_port}[/bold blue]") - - # Create server with the new agent ID and allocated host/port - from runagent.sdk.db import DBService - db_service = DBService() - - server = LocalServer( - db_service=db_service, - agent_id=new_agent_id, - agent_path=path, - port=allocated_port, - host=allocated_host, - ) - else: - # Normal operation - check capacity if not replacing - capacity_info = sdk.db_service.get_database_capacity_info() - if capacity_info["is_full"] and not replace: - console.print("❌ [red]Database is full![/red]") - oldest_agent = capacity_info.get("oldest_agent", {}) - if oldest_agent: - console.print(f"💡 [yellow]Suggested commands:[/yellow]") - console.print(f" Replace: [cyan]runagent serve {path} --replace {oldest_agent.get('agent_id', '')}[/cyan]") - console.print(f" Delete: [cyan]runagent delete --id {oldest_agent.get('agent_id', '')}[/cyan]") - raise click.ClickException("Database at capacity. Use --replace or use 'runagent delete' to free space.") - - console.print("⚡ [bold]Starting local server with auto port allocation...[/bold]") - - # Use the existing LocalServer.from_path method - server = LocalServer.from_path(path, port=port, host=host) - - # Common server startup code - allocated_host = server.host - allocated_port = server.port - - console.print(f"🌐 URL: [bold blue]http://{allocated_host}:{allocated_port}[/bold blue]") - console.print(f"📖 Docs: [link]http://{allocated_host}:{allocated_port}/docs[/link]") - - try: - - sync_service = get_middleware_sync() - sync_enabled = sync_service.is_sync_enabled() - api_key_set = bool(Config.get_api_key()) - - console.print(f"\n🔄 [bold]Middleware Sync Status:[/bold]") - if sync_enabled: - console.print(f" Status: [green]✅ ENABLED[/green]") - console.print(f" 📊 Local invocations will sync to middleware") - - # Test connection - try: - test_result = sync_service.test_connection() - if test_result.get("success"): - console.print(f" Connection: [green]✅ Connected to middleware[/green]") - else: - console.print(f" Connection: [red]❌ Failed to connect: {test_result.get('error', 'Unknown error')}[/red]") - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f" Connection: [red]❌ Connection test failed: {e}[/red]") - else: - console.print(f" Status: [yellow]⚠️ DISABLED[/yellow]") - if not api_key_set: - console.print(f" Reason: [yellow]API key not configured[/yellow]") - console.print(f" 💡 Setup: [cyan]runagent setup --api-key [/cyan]") - else: - user_disabled = not Config.get_user_config().get("local_sync_enabled", True) - if user_disabled: - console.print(f" Reason: [yellow]Disabled by user[/yellow]") - console.print(f" 💡 Enable: [cyan]runagent local-sync --enable[/cyan]") - console.print(f" 📊 Local invocations will only be stored locally") - - except Exception as e: - console.print(f"[dim]Note: Could not check middleware sync status: {e}[/dim]") - - # Start server (this will block) - server.start(debug=debug) - - except KeyboardInterrupt: - console.print("\n🛑 [yellow]Server stopped by user[/yellow]") - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Server error:[/red] {e}") - raise click.ClickException("Server failed to start") - - -@click.command( - context_settings=dict( - ignore_unknown_options=True, - allow_extra_args=True, - )) -@click.option("--id", "agent_id", help="Agent ID to run") -@click.option("--host", help="Host to connect to (use with --port)") -@click.option("--port", type=int, help="Port to connect to (use with --host)") -@click.option( - "--input", - "input_file", - type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=Path), - help="Path to input JSON file" -) -@click.option("--local", is_flag=True, help="Run agent locally") -@click.option("--tag", required=True, help="Entrypoint tag to be used") -# @click.option("--generic-stream", is_flag=True, help="Use generic streaming mode") -@click.option("--timeout", type=int, help="Timeout in seconds") -@click.pass_context -def run(ctx, agent_id, host, port, input_file, local, tag, timeout): - """ - Run an agent with flexible configuration options - - Examples: - # Using agent ID with extra params - runagent run --agent-id my-agent --param1=value1 --param2=value2 - - # Using host/port with input file - runagent run --host localhost --port 8080 --input config.json - - # local agent - runagent run --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal --local --message=something - - # remote agent - runagent run --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal --message=something - """ - - # ============================================ - # VALIDATION 1: Either agent-id OR host/port - # ============================================ - agent_id_provided = agent_id is not None - host_port_provided = host is not None or port is not None - - if agent_id_provided and host_port_provided: - raise click.UsageError( - "Cannot specify both --agent-id and --host/--port. " - "Choose one approach." - ) - - if not agent_id_provided and not host_port_provided: - raise click.UsageError( - "Must specify either --agent-id or both --host and --port." - ) - - # If using host/port, both must be provided - if host_port_provided and (host is None or port is None): - raise click.UsageError( - "When using host/port, both --host and --port must be specified." - ) - - # ============================================ - # # VALIDATION 2: tag validation - # # ============================================ - if tag.endswith("_stream"): - console.print(f"❌ [bold red]Execution failed:[/bold red] Cannot use streaming Entrypoint tag `{tag}` through non-streaming endpoint.") - return - - - # ============================================ - # VALIDATION 3: Input file OR extra params - # ============================================ - - # Parse extra parameters from ctx.args - extra_params = {} - invalid_args = [] - - for arg in ctx.args: - if arg.startswith('--') and '=' in arg: - # Valid format: --key=value - key, value = arg[2:].split('=', 1) - extra_params[key] = value - else: - # Invalid format - invalid_args.append(arg) - - if invalid_args: - raise click.UsageError( - f"Invalid extra arguments: {invalid_args}. " - "Extra parameters must be in --key=value format." - ) - - # Check mutual exclusivity of input file and extra params - if input_file and extra_params: - raise click.UsageError( - "Cannot specify both --input file and extra parameters. " - "Use either --input config.json OR --param1=value1 --param2=value2" - ) - - if not input_file and not extra_params: - console.print("⚠️ No input file or extra parameters provided. Running with defaults.") - - # ============================================ - # DISPLAY CONFIGURATION - # ============================================ - - console.print("🚀 RunAgent Configuration:") - - # Connection info - if agent_id: - console.print(f" Agent ID: [cyan]{agent_id}[/cyan]") - else: - console.print(f" Host: [cyan]{host}[/cyan]") - console.print(f" Port: [cyan]{port}[/cyan]") - - # Tag - # mode = "Generic Streaming" if generic_stream else "Generic" - console.print(f" Tag: [magenta]{tag}[/magenta]") - - # Local execution - if local: - console.print(" Local: [green]Yes[/green]") - else: - console.print(" Local: [red]No(Deployed to RunAgent Cloud)[/red]") - - # Timeout - if timeout: - console.print(f" Timeout: [yellow]{timeout}s[/yellow]") - - # Input configuration - if input_file: - console.print(f" Input file: [blue]{input_file}[/blue]") - # Load and validate JSON file here - try: - import json - with open(input_file, 'r') as f: - input_params = json.load(f) - console.print(f" Config keys: [dim]{list(input_params.keys())}[/dim]") - except json.JSONDecodeError: - if os.getenv('DISABLE_TRY_CATCH'): - raise - raise click.ClickException(f"Invalid JSON in input file: {input_file}") - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - raise click.ClickException(f"Error reading input file: {e}") - - elif extra_params: - console.print(" Extra parameters:") - for key, value in extra_params.items(): - # Try to parse value as JSON for complex types - # TODO: Will add type inference later - console.print(f" --{key} = [green]{value}[/green]") - input_params = extra_params - - else: - input_params = {} - - # ============================================ - # EXECUTION LOGIC - # ============================================ - - try: - ra_client = RunAgentClient( - agent_id=agent_id, - local=local, - host=host, - port=port, - entrypoint_tag=tag - ) - - if tag.endswith("_stream"): - for item in ra_client.run(**input_params): - console.print(item) - else: - result = ra_client.run(**input_params) - console.print(result) - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - # Display error with red ❌ symbol - console.print(f"❌ [bold red]Execution failed:[/bold red] {e}") - # Exit with error code 1 instead of raising ClickException to avoid duplicate message - import sys - sys.exit(1) - - -@click.command( - context_settings=dict( - ignore_unknown_options=True, - allow_extra_args=True, - )) -@click.option("--id", "agent_id", help="Agent ID to run") -@click.option("--host", help="Host to connect to (use with --port)") -@click.option("--port", type=int, help="Port to connect to (use with --host)") -@click.option( - "--input", - "input_file", - type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=Path), - help="Path to input JSON file" -) -@click.option("--local", is_flag=True, help="Run agent locally") -@click.option("--tag", required=True, help="Entrypoint tag to be used") -@click.option("--timeout", type=int, help="Timeout in seconds") -@click.pass_context -def run_stream(ctx, agent_id, host, port, input_file, local, tag, timeout): - """ - Stream agent execution results in real-time. - - This command connects to an agent via WebSocket and streams the execution results - as they become available, providing real-time feedback. - - Examples: - # Local streaming agent - runagent run-stream --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal_stream --local --message=something - - # Remote streaming agent - runagent run-stream --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal_stream --message=something - - # With input file - runagent run-stream --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal_stream --local --input config.json - """ - - # ============================================ - # PARAMETER PARSING - # ============================================ - - extra_params = {} - for item in ctx.args: - if '=' in item: - key, value = item.split('=', 1) - # Remove leading dashes - key = key.lstrip('-') - extra_params[key] = value - else: - # Handle boolean flags - key = item.lstrip('-') - extra_params[key] = True - - # ============================================ - # VALIDATION - # ============================================ - - # VALIDATION 1: Agent ID or host/port required - if not agent_id and not (host and port): - console.print(f"❌ [bold red]Execution failed:[/bold red] Either --id or both --host and --port are required") - import sys - sys.exit(1) - - # VALIDATION 2: tag validation for streaming - if not tag.endswith("_stream"): - console.print(f"❌ [bold red]Execution failed:[/bold red] Streaming command requires entrypoint tag ending with '_stream'. Got: {tag}") - import sys - sys.exit(1) - - # ============================================ - # DISPLAY CONFIGURATION - # ============================================ - - console.print("🚀 RunAgent Streaming Configuration:") - - # Connection info - if agent_id: - console.print(f" Agent ID: [cyan]{agent_id}[/cyan]") - else: - console.print(f" Host: [cyan]{host}[/cyan]") - console.print(f" Port: [cyan]{port}[/cyan]") - - # Tag - console.print(f" Tag: [magenta]{tag}[/magenta]") - - # Local execution - if local: - console.print(" Local: [green]Yes[/green]") - else: - console.print(" Local: [red]No (Deployed to RunAgent Cloud)[/red]") - - # Timeout - if timeout: - console.print(f" Timeout: [yellow]{timeout}s[/yellow]") - - # Input configuration - if input_file: - console.print(f" Input file: [blue]{input_file}[/blue]") - # Load and validate JSON file here - try: - import json - with open(input_file, 'r') as f: - input_params = json.load(f) - console.print(f" Config keys: [dim]{list(input_params.keys())}[/dim]") - except json.JSONDecodeError: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [bold red]Execution failed:[/bold red] Invalid JSON in input file: {input_file}") - import sys - sys.exit(1) - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [bold red]Execution failed:[/bold red] Error reading input file: {e}") - import sys - sys.exit(1) - - elif extra_params: - console.print(" Extra parameters:") - for key, value in extra_params.items(): - console.print(f" --{key} = {value}") - input_params = extra_params - - else: - input_params = {} - - # ============================================ - # EXECUTION LOGIC - # ============================================ - - try: - ra_client = RunAgentClient( - agent_id=agent_id, - local=local, - host=host, - port=port, - entrypoint_tag=tag - ) - - console.print(f"\n🔄 [bold]Starting streaming execution...[/bold]") - console.print(f"📡 [dim]Connected to agent via WebSocket[/dim]") - console.print(f"📤 [dim]Streaming results:[/dim]\n") - - # Stream the results - for chunk in ra_client.run_stream(**input_params): - console.print(chunk) - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - # Display error with red ❌ symbol - console.print(f"❌ [bold red]Streaming failed:[/bold red] {e}") - # Exit with error code 1 instead of raising ClickException to avoid duplicate message - import sys - sys.exit(1) - - -@click.group() -def db(): - """Database management and monitoring commands""" - pass - -@db.command() -@click.option("--cleanup-days", type=int, help="Clean up records older than N days") -@click.option("--agent-id", help="Show detailed info for specific agent") -@click.option("--capacity", is_flag=True, help="Show detailed capacity information") -def status(cleanup_days, agent_id, capacity): - """Show local database status and statistics (ENHANCED with invocation stats)""" - try: - sdk = RunAgent() - - if capacity: - # Show detailed capacity info - capacity_info = sdk.db_service.get_database_capacity_info() - - console.print(f"\n📊 [bold]Database Capacity Information[/bold]") - console.print( - f"Current: [cyan]{capacity_info.get('current_count', 0)}/5[/cyan] agents" - ) - console.print( - f"Remaining slots: [green]{capacity_info.get('remaining_slots', 0)}[/green]" - ) - - status = "🔴 FULL" if capacity_info.get("is_full") else "🟢 Available" - console.print(f"Status: {status}") - - agents = capacity_info.get("agents", []) - if agents: - console.print(f"\n📋 [bold]Deployed Agents (by age):[/bold]") - - # Create table for agents - table = Table(title="Agents by Deployment Age") - table.add_column("#", style="dim", width=3) - table.add_column("Status", width=6) - table.add_column("Agent ID", style="magenta", width=36) - table.add_column("Framework", style="green", width=12) - table.add_column("Deployed At", style="cyan", width=20) - table.add_column("Age Note", style="yellow", width=10) - - for i, agent in enumerate(agents): - status_icon = ( - "🟢" - if agent["status"] == "deployed" - else "🔴" if agent["status"] == "error" else "🟡" - ) - age_label = ( - "oldest" - if i == 0 - else "newest" if i == len(agents) - 1 else "" - ) - - table.add_row( - str(i+1), - status_icon, - agent['agent_id'], - agent['framework'], - agent['deployed_at'] or "Unknown", - age_label - ) - - console.print(table) - - if capacity_info.get("is_full"): - oldest = capacity_info.get("oldest_agent", {}) - console.print( - f"\n💡 [yellow]To deploy new agent, replace oldest:[/yellow]" - ) - console.print( - f" [cyan]runagent serve --folder --replace {oldest.get('agent_id', '')}[/cyan]" - ) - console.print( - f" [cyan]runagent delete --id {oldest.get('agent_id', '')}[/cyan]" - ) - - return - - if agent_id: - # Show agent-specific details including invocations - result = sdk.get_agent_info(agent_id, local=True) - if result.get("success"): - agent_data = result["agent_info"] - console.print(f"\n🔍 [bold]Agent Details: {agent_id}[/bold]") - console.print(f"Framework: [green]{agent_data.get('framework')}[/green]") - console.print(f"Status: [yellow]{agent_data.get('status')}[/yellow]") - console.print(f"Path: [blue]{agent_data.get('deployment_path')}[/blue]") - - # Show agent-specific invocation stats - agent_inv_stats = sdk.db_service.get_invocation_stats(agent_id=agent_id) - console.print(f"\n📊 [bold]Invocation Statistics for {agent_id}[/bold]") - console.print(f"Total: [cyan]{agent_inv_stats.get('total_invocations', 0)}[/cyan]") - console.print(f"Success Rate: [blue]{agent_inv_stats.get('success_rate', 0)}%[/blue]") - - return - - # Show general database stats - stats = sdk.db_service.get_database_stats() - capacity_info = sdk.db_service.get_database_capacity_info() - - console.print("\n📊 [bold]Local Database Status[/bold]") - - current_count = capacity_info.get("current_count", 0) - is_full = capacity_info.get("is_full", False) - status = "FULL" if is_full else "OK" - console.print( - f"Agent Capacity: [cyan]{current_count}/5[/cyan] agents ([red]{status}[/red])" - if is_full - else f"Agent Capacity: [cyan]{current_count}/5[/cyan] agents ([green]{status}[/green])" - ) - - console.print(f"Total Agent Runs: [cyan]{stats.get('total_runs', 0)}[/cyan]") - console.print( - f"Database Size: [yellow]{stats.get('database_size_mb', 0)} MB[/yellow]" - ) - - # NEW: Show invocation statistics - overall_stats = sdk.db_service.get_invocation_stats() - - console.print(f"\n📊 [bold]Invocation Statistics[/bold]") - console.print(f"Total Invocations: [cyan]{overall_stats.get('total_invocations', 0)}[/cyan]") - console.print(f"Completed: [green]{overall_stats.get('completed_invocations', 0)}[/green]") - console.print(f"Failed: [red]{overall_stats.get('failed_invocations', 0)}[/red]") - console.print(f"Pending: [yellow]{overall_stats.get('pending_invocations', 0)}[/yellow]") - console.print(f"Success Rate: [blue]{overall_stats.get('success_rate', 0)}%[/blue]") - - if overall_stats.get('avg_execution_time_ms'): - avg_time = overall_stats['avg_execution_time_ms'] - if avg_time < 1000: - time_display = f"{avg_time:.1f}ms" - else: - time_display = f"{avg_time/1000:.2f}s" - console.print(f"Average Execution Time: [cyan]{time_display}[/cyan]") - - # Show agent status breakdown - status_counts = stats.get("agent_status_counts", {}) - if status_counts: - console.print("\n📈 [bold]Agent Status Breakdown:[/bold]") - for status, count in status_counts.items(): - console.print(f" [cyan]{status}[/cyan]: {count}") - - # List agents in table format - agents = sdk.db_service.list_agents() - - if agents: - console.print(f"\n📋 [bold]Deployed Agents:[/bold]") - - # Create table for better formatting - table = Table(title=f"Local Agents ({len(agents)} total)") - table.add_column("Status", width=8) - table.add_column("Files", width=6) - table.add_column("Agent ID", style="magenta", width=36) - table.add_column("Framework", style="green", width=12) - table.add_column("Host:Port", style="blue", width=15) - table.add_column("Runs", style="cyan", width=6) - table.add_column("Status", style="yellow", width=10) - - for agent in agents: - status_icon = ( - "🟢" - if agent["status"] == "deployed" - else "🔴" if agent["status"] == "error" else "🟡" - ) - exists_icon = "📁" if agent.get("exists") else "❌" - - table.add_row( - status_icon, - exists_icon, - agent['agent_id'], - agent['framework'], - f"{agent.get('host', 'N/A')}:{agent.get('port', 'N/A')}", - str(agent.get('run_count', 0)), - agent['status'] - ) - - console.print(table) - - # Show recent invocations - recent_invocations = sdk.db_service.list_invocations(limit=5) - if recent_invocations: - console.print(f"\n📋 [bold]Recent Invocations:[/bold]") - for inv in recent_invocations: - status_color = "green" if inv['status'] == "completed" else "red" if inv['status'] == "failed" else "yellow" - console.print(f" • {inv['invocation_id'][:12]}... [{status_color}]{inv['status']}[/{status_color}] ({inv.get('entrypoint_tag', 'N/A')})") - - console.print(f"\n💡 [bold]Database Commands:[/bold]") - console.print(f" • [cyan]runagent db invocations[/cyan] - Show all invocations") - console.print(f" • [cyan]runagent db invocation [/cyan] - Show specific invocation") - console.print(f" • [cyan]runagent db cleanup[/cyan] - Clean up old records") - console.print(f" • [cyan]runagent db status --agent-id [/cyan] - Agent-specific info") - console.print(f" • [cyan]runagent db status --capacity[/cyan] - Capacity management info") - - # Cleanup if requested (keep existing logic) - if cleanup_days: - console.print(f"\n🧹 Cleaning up records older than {cleanup_days} days...") - cleanup_result = sdk.cleanup_local_database(cleanup_days) - if cleanup_result.get("success"): - console.print(f"✅ [green]{cleanup_result.get('message')}[/green]") - else: - console.print(f"❌ [red]{cleanup_result.get('error')}[/red]") - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Database status error:[/red] {e}") - raise click.ClickException("Failed to get database status") - - -@db.command() -@click.option("--agent-id", help="Filter by specific agent ID") -@click.option("--status", type=click.Choice(["pending", "completed", "failed"]), help="Filter by status") -@click.option("--limit", type=int, default=20, help="Maximum number of invocations to show") -@click.option("--format", "output_format", type=click.Choice(["table", "json"]), default="table", help="Output format") -def invocations(agent_id, status, limit, output_format): - """Show agent invocation history and statistics""" - try: - sdk = RunAgent() - - # Get invocations - invocations_list = sdk.db_service.list_invocations( - agent_id=agent_id, - status=status, - limit=limit - ) - - if output_format == "json": - console.print(json.dumps(invocations_list, indent=2)) - return - - if not invocations_list: - console.print("📭 [yellow]No invocations found[/yellow]") - if agent_id: - console.print(f" • Agent ID: {agent_id}") - if status: - console.print(f" • Status: {status}") - return - - # Show statistics first - if agent_id: - stats = sdk.db_service.get_invocation_stats(agent_id=agent_id) - else: - stats = sdk.db_service.get_invocation_stats() - - console.print(f"\n📊 [bold]Invocation Statistics[/bold]") - if agent_id: - console.print(f" Agent ID: [magenta]{agent_id}[/magenta]") - console.print(f" Total: [cyan]{stats.get('total_invocations', 0)}[/cyan]") - console.print(f" Completed: [green]{stats.get('completed_invocations', 0)}[/green]") - console.print(f" Failed: [red]{stats.get('failed_invocations', 0)}[/red]") - console.print(f" Pending: [yellow]{stats.get('pending_invocations', 0)}[/yellow]") - console.print(f" Success Rate: [blue]{stats.get('success_rate', 0)}%[/blue]") - if stats.get('avg_execution_time_ms'): - console.print(f" Avg Execution Time: [cyan]{stats.get('avg_execution_time_ms', 0):.1f}ms[/cyan]") - - # Show invocations table - console.print(f"\n📋 [bold]Recent Invocations (showing {len(invocations_list)} of {limit} max)[/bold]") - - table = Table(title="Agent Invocations") - table.add_column("Invocation", style="dim", width=12) - table.add_column("Agent", style="magenta", width=12) - table.add_column("Entrypoint", style="green", width=12) - table.add_column("Status", width=10) - table.add_column("Duration", style="cyan", width=10) - table.add_column("Started", style="dim", width=16) - table.add_column("SDK", style="yellow", width=10) - - for inv in invocations_list: - # Status with color - status_text = inv['status'] - if status_text == "completed": - status_display = f"[green]{status_text}[/green]" - elif status_text == "failed": - status_display = f"[red]{status_text}[/red]" - else: - status_display = f"[yellow]{status_text}[/yellow]" - - # Duration calculation - duration_display = "N/A" - if inv.get('execution_time_ms'): - if inv['execution_time_ms'] < 1000: - duration_display = f"{inv['execution_time_ms']:.0f}ms" - else: - duration_display = f"{inv['execution_time_ms']/1000:.1f}s" - - # Format timestamp - started_display = "N/A" - if inv.get('request_timestamp'): - try: - from datetime import datetime - dt = datetime.fromisoformat(inv['request_timestamp'].replace('Z', '+00:00')) - started_display = dt.strftime('%m-%d %H:%M:%S') - except: - started_display = inv['request_timestamp'][:16] - - table.add_row( - inv['invocation_id'][:8] + "...", - inv['agent_id'][:8] + "...", - inv.get('entrypoint_tag', 'N/A')[:12], - status_display, - duration_display, - started_display, - inv.get('sdk_type', 'unknown')[:10] - ) - - console.print(table) - - # Show usage tips - console.print(f"\n💡 [dim]Usage tips:[/dim]") - console.print(f" • View specific invocation: [cyan]runagent db invocation [/cyan]") - console.print(f" • Filter by agent: [cyan]runagent db invocations --agent-id [/cyan]") - console.print(f" • Filter by status: [cyan]runagent db invocations --status completed[/cyan]") - console.print(f" • JSON output: [cyan]runagent db invocations --format json[/cyan]") - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Error getting invocations:[/red] {e}") - raise click.ClickException("Failed to get invocations") - - -@db.command() -@click.argument("invocation_id") -@click.option("--format", "output_format", type=click.Choice(["table", "json"]), default="table", help="Output format") -def invocation(invocation_id, output_format): - """Show detailed information about a specific invocation""" - try: - sdk = RunAgent() - - invocation = sdk.db_service.get_invocation(invocation_id) - - if not invocation: - console.print(f"❌ [red]Invocation {invocation_id} not found[/red]") - - # Show available invocations - console.print("\n💡 Recent invocations:") - recent = sdk.db_service.list_invocations(limit=5) - for inv in recent: - console.print(f" • {inv['invocation_id']} ({inv['status']})") - - raise click.ClickException("Invocation not found") - - if output_format == "json": - console.print(json.dumps(invocation, indent=2)) - return - - # Display detailed information - console.print(f"\n🔍 [bold]Invocation Details[/bold]") - console.print(f" Invocation ID: [bold magenta]{invocation['invocation_id']}[/bold magenta]") - console.print(f" Agent ID: [bold cyan]{invocation['agent_id']}[/bold cyan]") - console.print(f" Entrypoint: [green]{invocation.get('entrypoint_tag', 'N/A')}[/green]") - console.print(f" SDK Type: [yellow]{invocation.get('sdk_type', 'unknown')}[/yellow]") - - # Status with color - status = invocation['status'] - if status == "completed": - status_display = f"[green]{status}[/green]" - elif status == "failed": - status_display = f"[red]{status}[/red]" - else: - status_display = f"[yellow]{status}[/yellow]" - console.print(f" Status: {status_display}") - - # Timing information - console.print(f"\n⏱️ [bold]Timing Information[/bold]") - if invocation.get('request_timestamp'): - console.print(f" Started: [cyan]{invocation['request_timestamp']}[/cyan]") - if invocation.get('response_timestamp'): - console.print(f" Completed: [cyan]{invocation['response_timestamp']}[/cyan]") - if invocation.get('execution_time_ms'): - exec_time = invocation['execution_time_ms'] - if exec_time < 1000: - time_display = f"{exec_time:.1f}ms" - else: - time_display = f"{exec_time/1000:.2f}s" - console.print(f" Duration: [green]{time_display}[/green]") - - # Input data - console.print(f"\n📥 [bold]Input Data[/bold]") - if invocation.get('input_data'): - input_str = json.dumps(invocation['input_data'], indent=2) - if len(input_str) > 500: - console.print(f" [dim]{input_str[:500]}...\n (truncated - use --format json for full data)[/dim]") - else: - console.print(f" [dim]{input_str}[/dim]") - else: - console.print(" [dim]No input data[/dim]") - - # Output data or error - if invocation['status'] == 'failed' and invocation.get('error_detail'): - console.print(f"\n❌ [bold red]Error Details[/bold red]") - console.print(f" [red]{invocation['error_detail']}[/red]") - elif invocation.get('output_data'): - console.print(f"\n📤 [bold]Output Data[/bold]") - output_str = json.dumps(invocation['output_data'], indent=2) - if len(output_str) > 500: - console.print(f" [dim]{output_str[:500]}...\n (truncated - use --format json for full data)[/dim]") - else: - console.print(f" [dim]{output_str}[/dim]") - - # Client info - if invocation.get('client_info'): - console.print(f"\n🔧 [bold]Client Information[/bold]") - client_str = json.dumps(invocation['client_info'], indent=2) - console.print(f" [dim]{client_str}[/dim]") - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Error getting invocation details:[/red] {e}") - raise click.ClickException("Failed to get invocation details") - - -@db.command() -@click.option("--days", type=int, default=30, help="Clean up invocations older than N days") -@click.option("--agent-runs", is_flag=True, help="Also clean up old agent_runs records") -@click.option("--yes", is_flag=True, help="Skip confirmation") -def cleanup(days, agent_runs, yes): - """Clean up old database records""" - try: - sdk = RunAgent() - - # Get count of records to be cleaned - all_invocations = sdk.db_service.list_invocations(limit=1000) - - # Filter by date (simple approximation for preview) - from datetime import datetime, timedelta - cutoff_date = datetime.now() - timedelta(days=days) - - old_invocations_count = len([ - inv for inv in all_invocations - if inv.get('request_timestamp') and - datetime.fromisoformat(inv['request_timestamp'].replace('Z', '+00:00')) < cutoff_date - ]) - - console.print(f"🧹 [yellow]Cleanup Preview (older than {days} days):[/yellow]") - console.print(f" • Invocations: {old_invocations_count} records") - - if agent_runs: - console.print(f" • Agent runs: Will be cleaned too") - - if old_invocations_count == 0: - console.print(f"✅ [green]No records found older than {days} days[/green]") - return - - if not yes: - if not click.confirm(f"⚠️ This will permanently delete {old_invocations_count} invocation records. Continue?"): - console.print("Cleanup cancelled.") - return - - # Perform cleanup - deleted_invocations = sdk.db_service.cleanup_old_invocations(days_old=days) - - console.print(f"✅ [green]Cleaned up {deleted_invocations} old invocation records[/green]") - - if agent_runs: - deleted_runs = sdk.cleanup_local_database(days) - if deleted_runs.get("success"): - console.print(f"✅ [green]Also cleaned up old agent runs[/green]") - - # Show updated stats - stats = sdk.db_service.get_invocation_stats() - console.print(f"📊 Remaining invocations: [cyan]{stats.get('total_invocations', 0)}[/cyan]") - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Error cleaning up records:[/red] {e}") - raise click.ClickException("Cleanup failed") - - -@click.command() -@click.option("--status", is_flag=True, help="Show sync status") -@click.option("--test", is_flag=True, help="Test middleware connection") -def local_sync(status, test): - """Manage local agent sync with middleware - ENHANCED""" - try: - sdk = RunAgent() - - if not sdk.config.is_configured(): - console.print("❌ [red]RunAgent not configured[/red]") - console.print("💡 Run: [cyan]runagent setup --api-key [/cyan]") - raise click.ClickException("Setup required") - - from runagent.sdk.deployment.middleware_sync import MiddlewareSyncService - sync_service = MiddlewareSyncService(sdk.config) - - if status or (not test): - # Show detailed sync status - console.print("\n📡 [bold]Middleware Sync Status[/bold]") - console.print("=" * 40) - - # API Key status - if sync_service.api_key: - console.print("🔑 [green]API Key: CONFIGURED[/green]") - console.print(f" Key: [dim]{sync_service.api_key[:16]}...[/dim]") - else: - console.print("🔑 [red]API Key: NOT CONFIGURED[/red]") - - # Base URL - console.print(f"🌐 Base URL: [blue]{sync_service.config.base_url}[/blue]") - - # Authentication status - if sync_service.auth_validated: - console.print("🔐 [green]Authentication: VALID[/green]") - else: - console.print("🔐 [red]Authentication: INVALID[/red]") - - # Overall sync status - if sync_service.sync_enabled: - console.print("✅ [green]Sync Status: ENABLED[/green]") - console.print(" Local agent runs will sync to middleware") - else: - console.print("❌ [red]Sync Status: DISABLED[/red]") - console.print("⚠️ Local agents will only be stored locally") - - if not sync_service.sync_enabled: - console.print("\n [yellow]To enable sync:[/yellow]") - console.print(" 1. Get API key from middleware dashboard") - console.print(" 2. Run: [cyan]runagent setup --api-key [/cyan]") - - if test: - console.print("\n [bold]Testing Connection...[/bold]") - - if not sync_service.api_key: - console.print("❌ [red]No API key configured[/red]") - raise click.ClickException("API key required for testing") - - # Test basic connection - console.print("1. Testing basic connectivity...") - connection_result = sync_service._test_middleware_connection() - if connection_result: - console.print(" ✅ [green]Basic connection: SUCCESS[/green]") - else: - console.print(" ❌ [red]Basic connection: FAILED[/red]") - raise click.ClickException("Cannot connect to middleware") - - # Test authentication - console.print("2. Testing authentication...") - auth_result = sync_service._test_supabase_authentication() - if auth_result: - console.print(" ✅ [green]Authentication: SUCCESS[/green]") - else: - console.print(" ❌ [red]Authentication: FAILED[/red]") - console.print(" Check your API key") - raise click.ClickException("Authentication failed") - - # Test user info endpoint - console.print("3. Testing user info endpoint...") - try: - response = sync_service.rest_client.http.get("/users/auth-user-info", timeout=10) - if response.status_code == 200: - user_data = response.json() - user_info = user_data.get("user", {}) - console.print(" ✅ [green]User info: SUCCESS[/green]") - console.print(f" 👤 Email: [cyan]{user_info.get('email', 'Unknown')}[/cyan]") - if user_info.get('id'): - console.print(f" 🆔 User ID: [dim]{user_info.get('id')}[/dim]") - else: - console.print(f" ❌ [red]User info: FAILED (HTTP {response.status_code})[/red]") - except Exception as e: - console.print(f" ❌ [red]User info: ERROR ({e})[/red]") - - console.print("\n✅ [bold green]All tests passed! Middleware sync is working.[/bold green]") - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Local sync error: {e}[/red]") - raise click.ClickException("Local sync command failed") - - -# Add this simplified logs command to the db group in runagent/cli/commands.py - -@db.command() -@click.option("--agent-id", help="Filter by specific agent ID") -@click.option("--limit", type=int, default=100, help="Maximum number of logs to show") -@click.option("--format", "output_format", type=click.Choice(["table", "json"]), default="table", help="Output format") -def logs(agent_id, limit, output_format): - """Show all agent logs (no filtering)""" - try: - sdk = RunAgent() - - if agent_id: - # Show logs for specific agent - logs = sdk.db_service.get_agent_logs(agent_id=agent_id, limit=limit) - - if not logs: - console.print("📭 [yellow]No logs found[/yellow]") - console.print(f" • Agent ID: {agent_id}") - return - - if output_format == "json": - console.print(json.dumps(logs, indent=2)) - return - - console.print(f"\n📋 [bold]Agent Logs: {agent_id}[/bold]") - - table = Table(title=f"All Agent Logs (showing {len(logs)} entries)") - table.add_column("Time", style="dim", width=16) - table.add_column("Level", width=8) - table.add_column("Message", style="white", width=80) - table.add_column("Execution", style="cyan", width=12) - - for log in logs: - # Format timestamp - time_str = "N/A" - if log.get('created_at'): - try: - from datetime import datetime - dt = datetime.fromisoformat(log['created_at']) - time_str = dt.strftime('%m-%d %H:%M:%S') - except: - time_str = log['created_at'][:16] - - # Color code log levels - level = log.get('log_level', 'INFO') - if level == 'ERROR' or level == 'CRITICAL': - level_display = f"[red]{level}[/red]" - elif level == 'WARNING': - level_display = f"[yellow]{level}[/yellow]" - elif level == 'DEBUG': - level_display = f"[dim]{level}[/dim]" - else: - level_display = f"[green]{level}[/green]" - - # Don't truncate messages - show full log - message = log.get('message', '') - - # Show execution ID if available - exec_id = log.get('execution_id', '') - exec_display = exec_id[:8] + "..." if exec_id else "" - - table.add_row(time_str, level_display, message, exec_display) - - console.print(table) - - else: - # Show log summary for all agents - agents = sdk.db_service.list_agents() - - if not agents: - console.print("📭 [yellow]No agents found[/yellow]") - return - - console.print(f"\n📊 [bold]Agent Log Summary[/bold]") - - table = Table(title="Log Counts by Agent") - table.add_column("Agent ID", style="magenta", width=36) - table.add_column("Framework", style="green", width=12) - table.add_column("Total Logs", style="cyan", width=10) - table.add_column("Errors", style="red", width=8) - table.add_column("Last Log", style="dim", width=16) - - for agent in agents[:10]: # Show first 10 agents - agent_logs = sdk.db_service.get_agent_logs(agent['agent_id'], limit=1000) - error_logs = [log for log in agent_logs if log.get('log_level') in ['ERROR', 'CRITICAL']] - - last_log_time = "Never" - if agent_logs: - try: - from datetime import datetime - dt = datetime.fromisoformat(agent_logs[0]['created_at']) - last_log_time = dt.strftime('%m-%d %H:%M') - except: - last_log_time = "Recent" - - table.add_row( - agent['agent_id'], - agent['framework'], - str(len(agent_logs)), - str(len(error_logs)), - last_log_time - ) - - console.print(table) - - console.print(f"\n💡 [bold]Usage tips:[/bold]") - console.print(f" • View agent logs: [cyan]runagent db logs --agent-id [/cyan]") - console.print(f" • JSON output: [cyan]runagent db logs --agent-id --format json[/cyan]") - console.print(f" • More logs: [cyan]runagent db logs --agent-id --limit 500[/cyan]") - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Error getting logs:[/red] {e}") - raise click.ClickException("Failed to get logs") - - -@db.command() -@click.option("--days", type=int, default=7, help="Clean up logs older than N days") -@click.option("--yes", is_flag=True, help="Skip confirmation") -def cleanup_logs(days, yes): - """Clean up old agent logs""" - try: - sdk = RunAgent() - - if not yes: - if not click.confirm(f"⚠️ This will delete logs older than {days} days for ALL agents. Continue?"): - console.print("Cleanup cancelled.") - return - - deleted_count = sdk.db_service.cleanup_old_logs(days_old=days) - console.print(f"✅ [green]Cleaned up {deleted_count} old log entries[/green]") - - except Exception as e: - if os.getenv('DISABLE_TRY_CATCH'): - raise - console.print(f"❌ [red]Error cleaning up logs:[/red] {e}") - raise click.ClickException("Log cleanup failed") \ No newline at end of file diff --git a/runagent/cli/commands/config.py b/runagent/cli/commands/config.py new file mode 100644 index 0000000..70fcafc --- /dev/null +++ b/runagent/cli/commands/config.py @@ -0,0 +1,633 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +@click.group(invoke_without_command=True) +@click.option("--set-api-key", help="Set API key directly (e.g., runagent config --set-api-key YOUR_KEY)") +@click.option("--set-base-url", help="Set base URL directly (e.g., runagent config --set-base-url https://api.example.com)") +@click.pass_context +def config(ctx, set_api_key, set_base_url): + """ + Manage RunAgent configuration + + \b + Interactive mode (for humans): + $ runagent config + + \b + Direct flags (for scripts/agents): + $ runagent config --set-api-key YOUR_KEY + $ runagent config --set-base-url https://api.example.com + + \b + Subcommands: + $ runagent config status + $ runagent config reset + """ + + # Handle direct flag options + if set_api_key: + _set_api_key_direct(set_api_key) + return + + if set_base_url: + _set_base_url_direct(set_base_url) + return + + # If no subcommand and no flags, show interactive menu + if ctx.invoked_subcommand is None: + show_interactive_config_menu() + + +def _set_api_key_direct(api_key: str): + """Set API key directly (for --set-api-key flag) with validation""" + from rich.panel import Panel + from rich.status import Status + from runagent.constants import DEFAULT_BASE_URL + + if not api_key or not api_key.strip(): + console.print(Panel( + "[red]❌ API key cannot be empty[/red]", + title="[bold red]Error[/bold red]", + border_style="red" + )) + raise click.ClickException("Invalid API key") + + # Validate and fetch user info + try: + sdk = RunAgent() + base_url = Config.get_base_url() or DEFAULT_BASE_URL + + with Status("[bold cyan]Validating credentials...", spinner="dots"): + sdk.configure(api_key=api_key, base_url=base_url, save=True) + + # Get user info + user_config = Config.get_user_config() + + # Build success message + success_msg = ( + "[bold green]✅ API key updated successfully![/bold green]\n\n" + f"[dim]User:[/dim] [cyan]{user_config.get('user_email', 'N/A')}[/cyan]\n" + f"[dim]Tier:[/dim] [yellow]{user_config.get('user_tier', 'N/A')}[/yellow]" + ) + + # Add project if available + if user_config.get('active_project_name'): + success_msg += f"\n[dim]Project:[/dim] [green]{user_config.get('active_project_name')}[/green]" + + console.print(Panel( + success_msg, + title="[bold green]Success[/bold green]", + border_style="green" + )) + + except AuthenticationError as e: + console.print(Panel( + f"[red]❌ Authentication failed[/red]\n\n" + f"[dim]Error:[/dim] {str(e)}\n\n" + "[yellow]Please check your API key and try again[/yellow]", + title="[bold red]Validation Error[/bold red]", + border_style="red" + )) + raise click.ClickException("Authentication failed") + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(Panel( + f"[red]❌ Failed to save API key[/red]\n\n" + f"[dim]Error:[/dim] {str(e)}", + title="[bold red]Error[/bold red]", + border_style="red" + )) + raise click.ClickException("Failed to save configuration") + + +def _set_base_url_direct(base_url: str): + """Set base URL directly (for --set-base-url flag)""" + from rich.panel import Panel + + # Validate URL format + if not base_url.startswith(('http://', 'https://')): + base_url = f"https://{base_url}" + + success = Config.set_base_url(base_url) + + if success: + console.print(Panel( + f"[bold green]✅ Base URL updated successfully![/bold green]\n\n" + f"[dim]New URL:[/dim] [cyan]{base_url}[/cyan]", + title="[bold green]Success[/bold green]", + border_style="green" + )) + else: + console.print(Panel( + "[red]❌ Failed to save base URL[/red]", + title="[bold red]Error[/bold red]", + border_style="red" + )) + raise click.ClickException("Failed to save configuration") + + +def show_interactive_config_menu(): + """Show interactive configuration menu""" + try: + from rich.panel import Panel + from runagent.cli.branding import print_header + import inquirer + + print_header("Configuration") + + questions = [ + inquirer.List( + 'config_option', + message="What would you like to configure?", + choices=[ + ('🔑 API Key', 'api_key'), + ('🌐 Base URL', 'base_url'), + ('📁 Active Project', 'project'), + ('🔄 Sync Settings', 'sync'), + ('📊 View Status', 'status'), + ('🔃 Reset Configuration', 'reset'), + ], + carousel=True + ), + ] + + answers = inquirer.prompt(questions) + if not answers: + console.print("[dim]Configuration cancelled.[/dim]") + return + + option = answers['config_option'] + + # Route to appropriate handler + if option == 'api_key': + _interactive_set_api_key() + elif option == 'base_url': + _interactive_set_base_url() + elif option == 'project': + _interactive_set_project() + elif option == 'sync': + _interactive_sync_settings() + elif option == 'status': + _show_config_status() + elif option == 'reset': + _interactive_reset_config() + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"[red]Error:[/red] {e}") + + +def _interactive_set_api_key(): + """Interactive API key setup with validation""" + from rich.prompt import Prompt + from rich.panel import Panel + from rich.status import Status + from runagent.constants import DEFAULT_BASE_URL + + api_key = Prompt.ask("[cyan]Enter your API key[/cyan]", password=True) + + if not api_key or not api_key.strip(): + console.print(Panel( + "[red]❌ API key cannot be empty[/red]", + title="[bold red]Error[/bold red]", + border_style="red" + )) + return + + # Validate and fetch user info + try: + sdk = RunAgent() + base_url = Config.get_base_url() or DEFAULT_BASE_URL + + with Status("[bold cyan]Validating credentials...", spinner="dots"): + sdk.configure(api_key=api_key, base_url=base_url, save=True) + + # Get user info + user_config = Config.get_user_config() + + # Build success message + success_msg = ( + "[bold green]✅ API key updated successfully![/bold green]\n\n" + f"[dim]User:[/dim] [cyan]{user_config.get('user_email', 'N/A')}[/cyan]\n" + f"[dim]Tier:[/dim] [yellow]{user_config.get('user_tier', 'N/A')}[/yellow]" + ) + + # Add project if available + if user_config.get('active_project_name'): + success_msg += f"\n[dim]Project:[/dim] [green]{user_config.get('active_project_name')}[/green]" + + console.print(Panel( + success_msg, + title="[bold green]Success[/bold green]", + border_style="green" + )) + + except AuthenticationError as e: + console.print(Panel( + f"[red]❌ Authentication failed[/red]\n\n" + f"[dim]Error:[/dim] {str(e)}\n\n" + "[yellow]Please check your API key and try again[/yellow]", + title="[bold red]Validation Error[/bold red]", + border_style="red" + )) + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(Panel( + f"[red]❌ Failed to save API key[/red]\n\n" + f"[dim]Error:[/dim] {str(e)}", + title="[bold red]Error[/bold red]", + border_style="red" + )) + + +def _interactive_set_base_url(): + """Interactive base URL setup""" + from rich.prompt import Prompt + from rich.panel import Panel + from runagent.constants import DEFAULT_BASE_URL + + console.print(f"[dim]Current: {Config.get_base_url()}[/dim]") + console.print(f"[dim]Default: {DEFAULT_BASE_URL}[/dim]\n") + + base_url = Prompt.ask( + "[cyan]Enter base URL[/cyan]", + default=DEFAULT_BASE_URL + ) + + if not base_url.startswith(('http://', 'https://')): + base_url = f"https://{base_url}" + + success = Config.set_base_url(base_url) + + if success: + console.print(Panel( + f"[bold green]✅ Base URL updated successfully![/bold green]\n\n" + f"[dim]New URL:[/dim] [cyan]{base_url}[/cyan]", + title="[bold green]Success[/bold green]", + border_style="green" + )) + else: + console.print(Panel( + "[red]❌ Failed to save base URL[/red]", + title="[bold red]Error[/bold red]", + border_style="red" + )) + + +def _interactive_sync_settings(): + """Interactive sync settings configuration""" + try: + from rich.panel import Panel + import inquirer + + # Get current status + user_config = Config.get_user_config() + current_status = user_config.get('local_sync_enabled', True) + + # Show current status + if current_status: + status_text = "[green]Currently: ENABLED[/green]" + else: + status_text = "[red]Currently: DISABLED[/red]" + + console.print(f"\n📡 Middleware Sync {status_text}\n") + + # Ask what to do + questions = [ + inquirer.List( + 'sync_action', + message="Select sync preference", + choices=[ + ('✅ Enable Sync (sync local runs to middleware)', 'enable'), + ('❌ Disable Sync (local only)', 'disable'), + ], + default=('✅ Enable Sync (sync local runs to middleware)', 'enable') if current_status else ('❌ Disable Sync (local only)', 'disable'), + carousel=True + ), + ] + + answers = inquirer.prompt(questions) + if not answers: + console.print("[dim]Sync configuration cancelled.[/dim]") + return + + action = answers['sync_action'] + + # Set the preference + new_status = (action == 'enable') + Config.set_user_config('local_sync_enabled', new_status) + + if new_status: + console.print(Panel( + "[bold green]✅ Middleware sync enabled![/bold green]\n\n" + "[dim]Local agent runs will now sync to middleware.[/dim]\n" + "[dim]Requires valid API key.[/dim]", + title="[bold green]Success[/bold green]", + border_style="green" + )) + else: + console.print(Panel( + "[bold yellow]⚠️ Middleware sync disabled[/bold yellow]\n\n" + "[dim]Local agents will only store data locally.[/dim]\n" + "[dim]Your runs won't appear in the middleware dashboard.[/dim]", + title="[bold]Sync Disabled[/bold]", + border_style="yellow" + )) + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"[red]Error:[/red] {e}") + + +def _interactive_set_project(): + """Interactive project selection from API""" + try: + from rich.panel import Panel + from rich.status import Status + import inquirer + + # Get API key + api_key = Config.get_api_key() + if not api_key: + console.print(Panel( + "[red]❌ No API key configured[/red]\n\n" + "[dim]Run 'runagent setup' first[/dim]", + title="[bold red]Error[/bold red]", + border_style="red" + )) + return + + # Fetch projects from API + console.print("\n[cyan]📁 Fetching your projects...[/cyan]\n") + + from runagent.sdk.rest_client import RestClient + + with Status("[bold cyan]Loading projects...", spinner="dots"): + rest_client = RestClient( + api_key=api_key, + base_url=Config.get_base_url() + ) + + try: + response = rest_client.http.get("/projects?page=1&per_page=20&include_stats=false") + + if response.status_code != 200: + console.print(Panel( + f"[red]❌ Failed to fetch projects (Status: {response.status_code})[/red]", + title="[bold red]Error[/bold red]", + border_style="red" + )) + return + + projects_data = response.json() + + if not projects_data.get("success"): + console.print(Panel( + f"[red]❌ {projects_data.get('error', 'Failed to fetch projects')}[/red]", + title="[bold red]Error[/bold red]", + border_style="red" + )) + return + + projects = projects_data.get("data", {}).get("projects", []) + + if not projects: + console.print(Panel( + "[yellow]⚠️ No projects found[/yellow]\n\n" + "[dim]Create a project in the dashboard first[/dim]", + title="[bold]No Projects[/bold]", + border_style="yellow" + )) + return + + except Exception as e: + console.print(Panel( + f"[red]❌ Error fetching projects: {str(e)}[/red]", + title="[bold red]Error[/bold red]", + border_style="red" + )) + return + + # Show project selection + current_project_id = Config.get_user_config().get('active_project_id') + + project_choices = [] + default_choice = None + + for project in projects: + project_id = project.get('id') + project_name = project.get('name', 'Unnamed') + is_default = project.get('is_default', False) + + # Mark current and default projects + label = f"📁 {project_name}" + if project_id == current_project_id: + label = f"✓ {label} [current]" + default_choice = (label, project_id) + elif is_default: + label = f"{label} [default]" + + choice_tuple = (label, project_id) + project_choices.append(choice_tuple) + + if not default_choice and is_default: + default_choice = choice_tuple + + questions = [ + inquirer.List( + 'project', + message="Select active project", + choices=project_choices, + default=default_choice, + carousel=True + ), + ] + + answers = inquirer.prompt(questions) + if not answers: + console.print("[dim]Project selection cancelled.[/dim]") + return + + selected_project_id = answers['project'] + + # Find selected project details + selected_project = next( + (p for p in projects if p.get('id') == selected_project_id), + None + ) + + if not selected_project: + console.print("[red]Error: Project not found[/red]") + return + + # Save to database + Config.set_user_config('active_project_id', selected_project_id) + Config.set_user_config('active_project_name', selected_project.get('name')) + + console.print(Panel( + f"[bold green]✅ Active project updated![/bold green]\n\n" + f"[dim]Project:[/dim] [cyan]{selected_project.get('name')}[/cyan]", + title="[bold green]Success[/bold green]", + border_style="green" + )) + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"[red]Error:[/red] {e}") + + +def _show_config_status(): + """Show configuration status (helper for interactive menu and status command)""" + from rich.panel import Panel + from rich.table import Table + + user_config = Config.get_user_config() + api_key = Config.get_api_key() + base_url = Config.get_base_url() + + # Create status table + table = Table(show_header=False, box=None, padding=(0, 2)) + table.add_column("Setting", style="dim") + table.add_column("Value", style="cyan") + + # API Key status + if api_key: + masked_key = api_key[:8] + "..." + api_key[-4:] if len(api_key) > 12 else "***" + table.add_row("🔑 API Key", f"[green]✓[/green] {masked_key}") + else: + table.add_row("🔑 API Key", "[red]✗ Not set[/red]") + + # Base URL + table.add_row("🌐 Base URL", base_url or "[yellow]Using default[/yellow]") + + # User info + if user_config.get('user_email'): + table.add_row("✉️ Email", user_config.get('user_email')) + + if user_config.get('user_tier'): + table.add_row("🎯 Tier", user_config.get('user_tier')) + + # Active project + if user_config.get('active_project_name'): + table.add_row("📁 Active Project", user_config.get('active_project_name')) + + # Sync status + sync_enabled = user_config.get('local_sync_enabled', True) + if sync_enabled: + table.add_row("🔄 Middleware Sync", "[green]✓ Enabled[/green]") + else: + table.add_row("🔄 Middleware Sync", "[yellow]⚠ Disabled[/yellow]") + + console.print(Panel( + table, + title="[bold cyan]RunAgent Configuration[/bold cyan]", + border_style="cyan" + )) + + # Show helpful info + console.print("\n[dim]💡 Use arrow keys in interactive mode: 'runagent config'[/dim]") + console.print("[dim]💡 Direct flags for automation: 'runagent config --set-api-key '[/dim]\n") + + +def _interactive_reset_config(): + """Interactive reset configuration (helper for interactive menu)""" + from rich.prompt import Confirm + from rich.panel import Panel + + console.print("[yellow]⚠️ This will remove all your configuration including API key[/yellow]") + if not Confirm.ask("\n[bold]Are you sure you want to reset?[/bold]", default=False): + console.print("[dim]Reset cancelled.[/dim]") + return + + sdk = RunAgent() + sdk.config.clear() + + console.print(Panel( + "[bold green]✅ Configuration reset successfully![/bold green]\n\n" + "[dim]Run 'runagent setup' to configure again.[/dim]", + title="[bold green]Success[/bold green]", + border_style="green" + )) + + + + +@config.command("status") +def config_status_cmd(): + """Show current configuration status""" + _show_config_status() + + +@config.command("reset") +@click.option("--yes", is_flag=True, help="Skip confirmation") +def config_reset_cmd(yes): + """Reset configuration to defaults""" + if yes: + _reset_config_without_prompt() + else: + _interactive_reset_config() + + +def _reset_config_without_prompt(): + """Reset config without confirmation (for --yes flag)""" + from rich.panel import Panel + + try: + sdk = RunAgent() + sdk.config.clear() + + console.print(Panel( + "[bold green]✅ Configuration reset successfully![/bold green]\n\n" + "[dim]Run 'runagent setup' to configure again.[/dim]", + title="[bold green]Success[/bold green]", + border_style="green" + )) + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"[red]Error:[/red] {e}") + raise click.ClickException("Reset failed") diff --git a/runagent/cli/commands/db.py b/runagent/cli/commands/db.py new file mode 100644 index 0000000..6e391ef --- /dev/null +++ b/runagent/cli/commands/db.py @@ -0,0 +1,659 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + +@click.group() +def db(): + """Database management and monitoring commands""" + pass + +@db.command() +@click.option("--cleanup-days", type=int, help="Clean up records older than N days") +@click.option("--agent-id", help="Show detailed info for specific agent") +@click.option("--capacity", is_flag=True, help="Show detailed capacity information") +def status(cleanup_days, agent_id, capacity): + """Show local database status and statistics (ENHANCED with invocation stats)""" + try: + sdk = RunAgent() + + if capacity: + # Show detailed capacity info + capacity_info = sdk.db_service.get_database_capacity_info() + + console.print(f"\n📊 [bold]Database Capacity Information[/bold]") + console.print( + f"Current: [cyan]{capacity_info.get('current_count', 0)}/5[/cyan] agents" + ) + console.print( + f"Remaining slots: [green]{capacity_info.get('remaining_slots', 0)}[/green]" + ) + + status = "🔴 FULL" if capacity_info.get("is_full") else "🟢 Available" + console.print(f"Status: {status}") + + agents = capacity_info.get("agents", []) + if agents: + console.print(f"\n📋 [bold]Deployed Agents (by age):[/bold]") + + # Create table for agents + table = Table(title="Agents by Deployment Age") + table.add_column("#", style="dim", width=3) + table.add_column("Status", width=6) + table.add_column("Agent ID", style="magenta", width=36) + table.add_column("Framework", style="green", width=12) + table.add_column("Deployed At", style="cyan", width=20) + table.add_column("Age Note", style="yellow", width=10) + + for i, agent in enumerate(agents): + status_icon = ( + "🟢" + if agent["status"] == "deployed" + else "🔴" if agent["status"] == "error" else "🟡" + ) + age_label = ( + "oldest" + if i == 0 + else "newest" if i == len(agents) - 1 else "" + ) + + table.add_row( + str(i+1), + status_icon, + agent['agent_id'], + agent['framework'], + agent['deployed_at'] or "Unknown", + age_label + ) + + console.print(table) + + if capacity_info.get("is_full"): + oldest = capacity_info.get("oldest_agent", {}) + console.print( + f"\n💡 [yellow]To deploy new agent, replace oldest:[/yellow]" + ) + console.print( + f" [cyan]runagent serve --folder --replace {oldest.get('agent_id', '')}[/cyan]" + ) + console.print( + f" [cyan]runagent delete --id {oldest.get('agent_id', '')}[/cyan]" + ) + + return + + if agent_id: + # Show agent-specific details including invocations + result = sdk.get_agent_info(agent_id, local=True) + if result.get("success"): + agent_data = result["agent_info"] + console.print(f"\n🔍 [bold]Agent Details: {agent_id}[/bold]") + console.print(f"Framework: [green]{agent_data.get('framework')}[/green]") + console.print(f"Status: [yellow]{agent_data.get('status')}[/yellow]") + console.print(f"Path: [blue]{agent_data.get('deployment_path')}[/blue]") + + # Show agent-specific invocation stats + agent_inv_stats = sdk.db_service.get_invocation_stats(agent_id=agent_id) + console.print(f"\n📊 [bold]Invocation Statistics for {agent_id}[/bold]") + console.print(f"Total: [cyan]{agent_inv_stats.get('total_invocations', 0)}[/cyan]") + console.print(f"Success Rate: [blue]{agent_inv_stats.get('success_rate', 0)}%[/blue]") + + return + + # Show general database stats + stats = sdk.db_service.get_database_stats() + capacity_info = sdk.db_service.get_database_capacity_info() + + console.print("\n📊 [bold]Local Database Status[/bold]") + + current_count = capacity_info.get("current_count", 0) + is_full = capacity_info.get("is_full", False) + status = "FULL" if is_full else "OK" + console.print( + f"Agent Capacity: [cyan]{current_count}/5[/cyan] agents ([red]{status}[/red])" + if is_full + else f"Agent Capacity: [cyan]{current_count}/5[/cyan] agents ([green]{status}[/green])" + ) + + console.print(f"Total Agent Runs: [cyan]{stats.get('total_runs', 0)}[/cyan]") + console.print( + f"Database Size: [yellow]{stats.get('database_size_mb', 0)} MB[/yellow]" + ) + + # NEW: Show invocation statistics + overall_stats = sdk.db_service.get_invocation_stats() + + console.print(f"\n📊 [bold]Invocation Statistics[/bold]") + console.print(f"Total Invocations: [cyan]{overall_stats.get('total_invocations', 0)}[/cyan]") + console.print(f"Completed: [green]{overall_stats.get('completed_invocations', 0)}[/green]") + console.print(f"Failed: [red]{overall_stats.get('failed_invocations', 0)}[/red]") + console.print(f"Pending: [yellow]{overall_stats.get('pending_invocations', 0)}[/yellow]") + console.print(f"Success Rate: [blue]{overall_stats.get('success_rate', 0)}%[/blue]") + + if overall_stats.get('avg_execution_time_ms'): + avg_time = overall_stats['avg_execution_time_ms'] + if avg_time < 1000: + time_display = f"{avg_time:.1f}ms" + else: + time_display = f"{avg_time/1000:.2f}s" + console.print(f"Average Execution Time: [cyan]{time_display}[/cyan]") + + # Show agent status breakdown + status_counts = stats.get("agent_status_counts", {}) + if status_counts: + console.print("\n📈 [bold]Agent Status Breakdown:[/bold]") + for status, count in status_counts.items(): + console.print(f" [cyan]{status}[/cyan]: {count}") + + # List agents in table format + agents = sdk.db_service.list_agents() + + if agents: + console.print(f"\n📋 [bold]Deployed Agents:[/bold]") + + # Create table for better formatting + table = Table(title=f"Local Agents ({len(agents)} total)") + table.add_column("Status", width=8) + table.add_column("Files", width=6) + table.add_column("Agent ID", style="magenta", width=36) + table.add_column("Framework", style="green", width=12) + table.add_column("Host:Port", style="blue", width=15) + table.add_column("Runs", style="cyan", width=6) + table.add_column("Status", style="yellow", width=10) + + for agent in agents: + status_icon = ( + "🟢" + if agent["status"] == "deployed" + else "🔴" if agent["status"] == "error" else "🟡" + ) + exists_icon = "📁" if agent.get("exists") else "❌" + + table.add_row( + status_icon, + exists_icon, + agent['agent_id'], + agent['framework'], + f"{agent.get('host', 'N/A')}:{agent.get('port', 'N/A')}", + str(agent.get('run_count', 0)), + agent['status'] + ) + + console.print(table) + + # Show recent invocations + recent_invocations = sdk.db_service.list_invocations(limit=5) + if recent_invocations: + console.print(f"\n📋 [bold]Recent Invocations:[/bold]") + for inv in recent_invocations: + status_color = "green" if inv['status'] == "completed" else "red" if inv['status'] == "failed" else "yellow" + console.print(f" • {inv['invocation_id'][:12]}... [{status_color}]{inv['status']}[/{status_color}] ({inv.get('entrypoint_tag', 'N/A')})") + + console.print(f"\n💡 [bold]Database Commands:[/bold]") + console.print(f" • [cyan]runagent db invocations[/cyan] - Show all invocations") + console.print(f" • [cyan]runagent db invocation [/cyan] - Show specific invocation") + console.print(f" • [cyan]runagent db cleanup[/cyan] - Clean up old records") + console.print(f" • [cyan]runagent db status --agent-id [/cyan] - Agent-specific info") + console.print(f" • [cyan]runagent db status --capacity[/cyan] - Capacity management info") + + # Cleanup if requested (keep existing logic) + if cleanup_days: + console.print(f"\n🧹 Cleaning up records older than {cleanup_days} days...") + cleanup_result = sdk.cleanup_local_database(cleanup_days) + if cleanup_result.get("success"): + console.print(f"✅ [green]{cleanup_result.get('message')}[/green]") + else: + console.print(f"❌ [red]{cleanup_result.get('error')}[/red]") + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Database status error:[/red] {e}") + raise click.ClickException("Failed to get database status") + + +@db.command() +@click.option("--agent-id", help="Filter by specific agent ID") +@click.option("--status", type=click.Choice(["pending", "completed", "failed"]), help="Filter by status") +@click.option("--limit", type=int, default=20, help="Maximum number of invocations to show") +@click.option("--format", "output_format", type=click.Choice(["table", "json"]), default="table", help="Output format") +def invocations(agent_id, status, limit, output_format): + """Show agent invocation history and statistics""" + try: + sdk = RunAgent() + + # Get invocations + invocations_list = sdk.db_service.list_invocations( + agent_id=agent_id, + status=status, + limit=limit + ) + + if output_format == "json": + console.print(json.dumps(invocations_list, indent=2)) + return + + if not invocations_list: + console.print("📭 [yellow]No invocations found[/yellow]") + if agent_id: + console.print(f" • Agent ID: {agent_id}") + if status: + console.print(f" • Status: {status}") + return + + # Show statistics first + if agent_id: + stats = sdk.db_service.get_invocation_stats(agent_id=agent_id) + else: + stats = sdk.db_service.get_invocation_stats() + + console.print(f"\n📊 [bold]Invocation Statistics[/bold]") + if agent_id: + console.print(f" Agent ID: [magenta]{agent_id}[/magenta]") + console.print(f" Total: [cyan]{stats.get('total_invocations', 0)}[/cyan]") + console.print(f" Completed: [green]{stats.get('completed_invocations', 0)}[/green]") + console.print(f" Failed: [red]{stats.get('failed_invocations', 0)}[/red]") + console.print(f" Pending: [yellow]{stats.get('pending_invocations', 0)}[/yellow]") + console.print(f" Success Rate: [blue]{stats.get('success_rate', 0)}%[/blue]") + if stats.get('avg_execution_time_ms'): + console.print(f" Avg Execution Time: [cyan]{stats.get('avg_execution_time_ms', 0):.1f}ms[/cyan]") + + # Show invocations table + console.print(f"\n📋 [bold]Recent Invocations (showing {len(invocations_list)} of {limit} max)[/bold]") + + table = Table(title="Agent Invocations") + table.add_column("Invocation", style="dim", width=12) + table.add_column("Agent", style="magenta", width=12) + table.add_column("Entrypoint", style="green", width=12) + table.add_column("Status", width=10) + table.add_column("Duration", style="cyan", width=10) + table.add_column("Started", style="dim", width=16) + table.add_column("SDK", style="yellow", width=10) + + for inv in invocations_list: + # Status with color + status_text = inv['status'] + if status_text == "completed": + status_display = f"[green]{status_text}[/green]" + elif status_text == "failed": + status_display = f"[red]{status_text}[/red]" + else: + status_display = f"[yellow]{status_text}[/yellow]" + + # Duration calculation + duration_display = "N/A" + if inv.get('execution_time_ms'): + if inv['execution_time_ms'] < 1000: + duration_display = f"{inv['execution_time_ms']:.0f}ms" + else: + duration_display = f"{inv['execution_time_ms']/1000:.1f}s" + + # Format timestamp + started_display = "N/A" + if inv.get('request_timestamp'): + try: + from datetime import datetime + dt = datetime.fromisoformat(inv['request_timestamp'].replace('Z', '+00:00')) + started_display = dt.strftime('%m-%d %H:%M:%S') + except: + started_display = inv['request_timestamp'][:16] + + table.add_row( + inv['invocation_id'][:8] + "...", + inv['agent_id'][:8] + "...", + inv.get('entrypoint_tag', 'N/A')[:12], + status_display, + duration_display, + started_display, + inv.get('sdk_type', 'unknown')[:10] + ) + + console.print(table) + + # Show usage tips + console.print(f"\n💡 [dim]Usage tips:[/dim]") + console.print(f" • View specific invocation: [cyan]runagent db invocation [/cyan]") + console.print(f" • Filter by agent: [cyan]runagent db invocations --agent-id [/cyan]") + console.print(f" • Filter by status: [cyan]runagent db invocations --status completed[/cyan]") + console.print(f" • JSON output: [cyan]runagent db invocations --format json[/cyan]") + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Error getting invocations:[/red] {e}") + raise click.ClickException("Failed to get invocations") + + +@db.command() +@click.argument("invocation_id") +@click.option("--format", "output_format", type=click.Choice(["table", "json"]), default="table", help="Output format") +def invocation(invocation_id, output_format): + """Show detailed information about a specific invocation""" + try: + sdk = RunAgent() + + invocation = sdk.db_service.get_invocation(invocation_id) + + if not invocation: + console.print(f"❌ [red]Invocation {invocation_id} not found[/red]") + + # Show available invocations + console.print("\n💡 Recent invocations:") + recent = sdk.db_service.list_invocations(limit=5) + for inv in recent: + console.print(f" • {inv['invocation_id']} ({inv['status']})") + + raise click.ClickException("Invocation not found") + + if output_format == "json": + console.print(json.dumps(invocation, indent=2)) + return + + # Display detailed information + console.print(f"\n🔍 [bold]Invocation Details[/bold]") + console.print(f" Invocation ID: [bold magenta]{invocation['invocation_id']}[/bold magenta]") + console.print(f" Agent ID: [bold cyan]{invocation['agent_id']}[/bold cyan]") + console.print(f" Entrypoint: [green]{invocation.get('entrypoint_tag', 'N/A')}[/green]") + console.print(f" SDK Type: [yellow]{invocation.get('sdk_type', 'unknown')}[/yellow]") + + # Status with color + status = invocation['status'] + if status == "completed": + status_display = f"[green]{status}[/green]" + elif status == "failed": + status_display = f"[red]{status}[/red]" + else: + status_display = f"[yellow]{status}[/yellow]" + console.print(f" Status: {status_display}") + + # Timing information + console.print(f"\n⏱️ [bold]Timing Information[/bold]") + if invocation.get('request_timestamp'): + console.print(f" Started: [cyan]{invocation['request_timestamp']}[/cyan]") + if invocation.get('response_timestamp'): + console.print(f" Completed: [cyan]{invocation['response_timestamp']}[/cyan]") + if invocation.get('execution_time_ms'): + exec_time = invocation['execution_time_ms'] + if exec_time < 1000: + time_display = f"{exec_time:.1f}ms" + else: + time_display = f"{exec_time/1000:.2f}s" + console.print(f" Duration: [green]{time_display}[/green]") + + # Input data + console.print(f"\n📥 [bold]Input Data[/bold]") + if invocation.get('input_data'): + input_str = json.dumps(invocation['input_data'], indent=2) + if len(input_str) > 500: + console.print(f" [dim]{input_str[:500]}...\n (truncated - use --format json for full data)[/dim]") + else: + console.print(f" [dim]{input_str}[/dim]") + else: + console.print(" [dim]No input data[/dim]") + + # Output data or error + if invocation['status'] == 'failed' and invocation.get('error_detail'): + console.print(f"\n❌ [bold red]Error Details[/bold red]") + console.print(f" [red]{invocation['error_detail']}[/red]") + elif invocation.get('output_data'): + console.print(f"\n📤 [bold]Output Data[/bold]") + output_str = json.dumps(invocation['output_data'], indent=2) + if len(output_str) > 500: + console.print(f" [dim]{output_str[:500]}...\n (truncated - use --format json for full data)[/dim]") + else: + console.print(f" [dim]{output_str}[/dim]") + + # Client info + if invocation.get('client_info'): + console.print(f"\n🔧 [bold]Client Information[/bold]") + client_str = json.dumps(invocation['client_info'], indent=2) + console.print(f" [dim]{client_str}[/dim]") + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Error getting invocation details:[/red] {e}") + raise click.ClickException("Failed to get invocation details") + + +@db.command() +@click.option("--days", type=int, default=30, help="Clean up invocations older than N days") +@click.option("--agent-runs", is_flag=True, help="Also clean up old agent_runs records") +@click.option("--yes", is_flag=True, help="Skip confirmation") +def cleanup(days, agent_runs, yes): + """Clean up old database records""" + try: + sdk = RunAgent() + + # Get count of records to be cleaned + all_invocations = sdk.db_service.list_invocations(limit=1000) + + # Filter by date (simple approximation for preview) + from datetime import datetime, timedelta + cutoff_date = datetime.now() - timedelta(days=days) + + old_invocations_count = len([ + inv for inv in all_invocations + if inv.get('request_timestamp') and + datetime.fromisoformat(inv['request_timestamp'].replace('Z', '+00:00')) < cutoff_date + ]) + + console.print(f"🧹 [yellow]Cleanup Preview (older than {days} days):[/yellow]") + console.print(f" • Invocations: {old_invocations_count} records") + + if agent_runs: + console.print(f" • Agent runs: Will be cleaned too") + + if old_invocations_count == 0: + console.print(f"✅ [green]No records found older than {days} days[/green]") + return + + if not yes: + if not click.confirm(f"⚠️ This will permanently delete {old_invocations_count} invocation records. Continue?"): + console.print("Cleanup cancelled.") + return + + # Perform cleanup + deleted_invocations = sdk.db_service.cleanup_old_invocations(days_old=days) + + console.print(f"✅ [green]Cleaned up {deleted_invocations} old invocation records[/green]") + + if agent_runs: + deleted_runs = sdk.cleanup_local_database(days) + if deleted_runs.get("success"): + console.print(f"✅ [green]Also cleaned up old agent runs[/green]") + + # Show updated stats + stats = sdk.db_service.get_invocation_stats() + console.print(f"📊 Remaining invocations: [cyan]{stats.get('total_invocations', 0)}[/cyan]") + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Error cleaning up records:[/red] {e}") + raise click.ClickException("Cleanup failed") + + +# local-sync command removed - sync settings now managed via 'runagent config' +# Use: runagent config > Select "🔄 Sync Settings" + + +# Add this simplified logs command to the db group in runagent/cli/commands.py + +@db.command() +@click.option("--agent-id", help="Filter by specific agent ID") +@click.option("--limit", type=int, default=100, help="Maximum number of logs to show") +@click.option("--format", "output_format", type=click.Choice(["table", "json"]), default="table", help="Output format") +def logs(agent_id, limit, output_format): + """Show all agent logs (no filtering)""" + try: + sdk = RunAgent() + + if agent_id: + # Show logs for specific agent + logs = sdk.db_service.get_agent_logs(agent_id=agent_id, limit=limit) + + if not logs: + console.print("📭 [yellow]No logs found[/yellow]") + console.print(f" • Agent ID: {agent_id}") + return + + if output_format == "json": + console.print(json.dumps(logs, indent=2)) + return + + console.print(f"\n📋 [bold]Agent Logs: {agent_id}[/bold]") + + table = Table(title=f"All Agent Logs (showing {len(logs)} entries)") + table.add_column("Time", style="dim", width=16) + table.add_column("Level", width=8) + table.add_column("Message", style="white", width=80) + table.add_column("Execution", style="cyan", width=12) + + for log in logs: + # Format timestamp + time_str = "N/A" + if log.get('created_at'): + try: + from datetime import datetime + dt = datetime.fromisoformat(log['created_at']) + time_str = dt.strftime('%m-%d %H:%M:%S') + except: + time_str = log['created_at'][:16] + + # Color code log levels + level = log.get('log_level', 'INFO') + if level == 'ERROR' or level == 'CRITICAL': + level_display = f"[red]{level}[/red]" + elif level == 'WARNING': + level_display = f"[yellow]{level}[/yellow]" + elif level == 'DEBUG': + level_display = f"[dim]{level}[/dim]" + else: + level_display = f"[green]{level}[/green]" + + # Don't truncate messages - show full log + message = log.get('message', '') + + # Show execution ID if available + exec_id = log.get('execution_id', '') + exec_display = exec_id[:8] + "..." if exec_id else "" + + table.add_row(time_str, level_display, message, exec_display) + + console.print(table) + + else: + # Show log summary for all agents + agents = sdk.db_service.list_agents() + + if not agents: + console.print("📭 [yellow]No agents found[/yellow]") + return + + console.print(f"\n📊 [bold]Agent Log Summary[/bold]") + + table = Table(title="Log Counts by Agent") + table.add_column("Agent ID", style="magenta", width=36) + table.add_column("Framework", style="green", width=12) + table.add_column("Total Logs", style="cyan", width=10) + table.add_column("Errors", style="red", width=8) + table.add_column("Last Log", style="dim", width=16) + + for agent in agents[:10]: # Show first 10 agents + agent_logs = sdk.db_service.get_agent_logs(agent['agent_id'], limit=1000) + error_logs = [log for log in agent_logs if log.get('log_level') in ['ERROR', 'CRITICAL']] + + last_log_time = "Never" + if agent_logs: + try: + from datetime import datetime + dt = datetime.fromisoformat(agent_logs[0]['created_at']) + last_log_time = dt.strftime('%m-%d %H:%M') + except: + last_log_time = "Recent" + + table.add_row( + agent['agent_id'], + agent['framework'], + str(len(agent_logs)), + str(len(error_logs)), + last_log_time + ) + + console.print(table) + + console.print(f"\n💡 [bold]Usage tips:[/bold]") + console.print(f" • View agent logs: [cyan]runagent db logs --agent-id [/cyan]") + console.print(f" • JSON output: [cyan]runagent db logs --agent-id --format json[/cyan]") + console.print(f" • More logs: [cyan]runagent db logs --agent-id --limit 500[/cyan]") + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Error getting logs:[/red] {e}") + raise click.ClickException("Failed to get logs") + + +@db.command() +@click.option("--days", type=int, default=7, help="Clean up logs older than N days") +@click.option("--yes", is_flag=True, help="Skip confirmation") +def cleanup_logs(days, yes): + """Clean up old agent logs""" + try: + sdk = RunAgent() + + if not yes: + if not click.confirm(f"⚠️ This will delete logs older than {days} days for ALL agents. Continue?"): + console.print("Cleanup cancelled.") + return + + deleted_count = sdk.db_service.cleanup_old_logs(days_old=days) + console.print(f"✅ [green]Cleaned up {deleted_count} old log entries[/green]") + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Error cleaning up logs:[/red] {e}") + raise click.ClickException("Log cleanup failed") \ No newline at end of file diff --git a/runagent/cli/commands/delete.py b/runagent/cli/commands/delete.py new file mode 100644 index 0000000..ae60e0d --- /dev/null +++ b/runagent/cli/commands/delete.py @@ -0,0 +1,121 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + + +@click.command() +@click.option("--id", "agent_id", required=True, help="Agent ID to delete") +@click.option("--yes", is_flag=True, help="Skip confirmation") +def delete(agent_id, yes): + """Delete an agent from the local database""" + try: + from runagent.cli.branding import print_header + print_header("Delete Agent") + + sdk = RunAgent() + + # Get agent info first + agent = sdk.db_service.get_agent(agent_id) + if not agent: + console.print(f"❌ [red]Agent {agent_id} not found in database[/red]") + + # Show available agents + console.print("\n💡 Available agents:") + agents = sdk.db_service.list_agents() + if agents: + table = Table(title="Available Agents") + table.add_column("Agent ID", style="magenta") + table.add_column("Framework", style="green") + table.add_column("Status", style="yellow") + table.add_column("Deployed At", style="dim") + + for agent in agents[:10]: # Show first 10 + table.add_row( + agent['agent_id'][:8] + "...", + agent['framework'], + agent['status'], + agent['deployed_at'] or "Unknown" + ) + console.print(table) + else: + console.print(" No agents found in database") + + raise click.ClickException("Agent not found") + + # Show agent details + console.print(f"\n🔍 [yellow]Agent to be deleted:[/yellow]") + console.print(f" Agent ID: [bold magenta]{agent['agent_id']}[/bold magenta]") + console.print(f" Framework: [green]{agent['framework']}[/green]") + console.print(f" Path: [blue]{agent['agent_path']}[/blue]") + console.print(f" Status: [yellow]{agent['status']}[/yellow]") + console.print(f" Deployed: [dim]{agent['deployed_at']}[/dim]") + console.print(f" Total Runs: [cyan]{agent['run_count']}[/cyan]") + + # Confirmation + if not yes: + if not click.confirm("\n⚠️ This will permanently delete the agent from the database. Continue?"): + console.print("Deletion cancelled.") + return + + # Delete the agent + result = sdk.db_service.force_delete_agent(agent_id) + + if result["success"]: + console.print(f"\n✅ [green]Agent {agent_id} deleted successfully![/green]") + + # Show updated capacity + capacity_info = sdk.db_service.get_database_capacity_info() + console.print(f"📊 Updated capacity: [cyan]{capacity_info.get('current_count', 0)}/5[/cyan] agents") + else: + console.print(f"❌ [red]Failed to delete agent:[/red] {format_error_message(result.get('error'))}") + import sys + sys.exit(1) + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Delete error:[/red] {e}") + import sys + sys.exit(1) diff --git a/runagent/cli/commands/deploy.py b/runagent/cli/commands/deploy.py new file mode 100644 index 0000000..af8d06b --- /dev/null +++ b/runagent/cli/commands/deploy.py @@ -0,0 +1,108 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + + +@click.command() +@click.argument( + "path", + type=click.Path( + exists=True, + file_okay=False, + dir_okay=True, + readable=True, + resolve_path=True, + path_type=Path, + ), + default=".", +) +def deploy(path: Path): + """Deploy agent (upload + start) to remote server""" + + try: + from runagent.cli.branding import print_header + print_header("Deploy Agent") + + sdk = RunAgent() + + # Check authentication + if not sdk.is_configured(): + console.print( + "❌ [red]Not authenticated.[/red] Run [cyan]'runagent setup --api-key '[/cyan] first" + ) + raise click.ClickException("Authentication required") + + # Validate folder + if not Path(path).exists(): + raise click.ClickException(f"Folder not found: {path}") + + console.print(f"🎯 [bold]Deploying agent (upload + start)...[/bold]") + console.print(f"📁 Source: [cyan]{path}[/cyan]") + + # Deploy agent (framework auto-detected) + result = sdk.deploy_remote(folder=str(path)) + + if result.get("success"): + console.print(f"\n✅ [green]Deployment successful![/green]") + console.print(f"🆔 Agent ID: [bold magenta]{result.get('agent_id')}[/bold magenta]") + console.print(f"🌐 Endpoint: [link]{result.get('endpoint')}[/link]") + else: + console.print(f"❌ [red]Deployment failed:[/red] {format_error_message(result.get('error'))}") + import sys + sys.exit(1) + + except AuthenticationError as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Authentication error:[/red] {e}") + import sys + sys.exit(1) + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Deployment error:[/red] {e}") + import sys + sys.exit(1) + diff --git a/runagent/cli/commands/init.py b/runagent/cli/commands/init.py new file mode 100644 index 0000000..8550b74 --- /dev/null +++ b/runagent/cli/commands/init.py @@ -0,0 +1,374 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + + +@click.command() +@click.option("--template", help="Template variant (default, advanced, etc.) - for non-interactive") +@click.option("--blank", is_flag=True, help="Start from blank template - for non-interactive") +@click.option("--name", help="Agent name - for non-interactive") +@click.option("--description", help="Agent description - for non-interactive") +@click.option("--overwrite", is_flag=True, help="Overwrite existing folder") +@add_framework_options # Adds framework flags for non-interactive +@click.argument( + "path", + type=click.Path( + file_okay=False, + dir_okay=True, + readable=True, + resolve_path=True, + path_type=Path, + ), + default=".", + required=False, +) +def init(template, blank, name, description, overwrite, path, **kwargs): + """ + Initialize a new RunAgent project + + \b + Interactive mode (default - recommended): + $ runagent init + + \b + Non-interactive with template: + $ runagent init --framework langgraph --template advanced --name "My Agent" --description "Does XYZ" ./my-agent + + \b + Non-interactive blank: + $ runagent init --blank --name "Custom Agent" --description "My custom implementation" + """ + + try: + from runagent.cli.branding import print_header + from rich.prompt import Prompt + from rich.panel import Panel + import inquirer + + print_header("Initialize Project") + + sdk = RunAgent() + + # Determine if interactive mode + selected_framework = get_selected_framework(kwargs) + has_required_non_interactive = ( + (selected_framework or blank) and name and description + ) + is_interactive = not has_required_non_interactive + + # Variables to collect + agent_name = name + agent_description = description + use_blank = blank + framework = selected_framework + selected_template = template or "default" + + if is_interactive: + # Step 1: Choose blank or template + console.print("[bold cyan]How would you like to start?[/bold cyan]\n") + + start_questions = [ + inquirer.List( + 'start_type', + message="Select starting point", + choices=[ + ('📦 From Template (recommended)', 'template'), + ('📄 Blank Project (advanced)', 'blank'), + ], + default=('📦 From Template (recommended)', 'template'), + carousel=True + ), + ] + + start_answer = inquirer.prompt(start_questions) + if not start_answer: + console.print("[dim]Initialization cancelled.[/dim]") + return + + use_blank = (start_answer['start_type'] == 'blank') + + # Step 2: If template, select framework and template + if not use_blank: + # Select framework + console.print("\n[bold]Select framework:[/bold]\n") + selectable_frameworks = Framework.get_selectable_frameworks() + + framework_choices = [] + for fw in selectable_frameworks: + category_emoji = "🐍" if fw.is_pythonic() else "🌐" if fw.is_webhook() else "❓" + label = f"{category_emoji} {fw.value} ({fw.category})" + framework_choices.append((label, fw)) + + fw_questions = [ + inquirer.List( + 'framework', + message="Choose framework", + choices=framework_choices, + carousel=True + ), + ] + + fw_answer = inquirer.prompt(fw_questions) + if not fw_answer: + console.print("[dim]Initialization cancelled.[/dim]") + return + + framework = fw_answer['framework'] + + # Select template for chosen framework + console.print(f"\n[bold]Select template for {framework.value}:[/bold]") + + # Fetch templates with real progress feedback + from rich.status import Status + import time + + fetch_start = time.time() + + with Status( + "[cyan]Fetching available templates...[/cyan]", + console=console, + spinner="dots" + ) as status: + clone_start = time.time() + status.update("[cyan]Cloning template repository...[/cyan]") + + templates = sdk.list_templates(framework.value) + clone_time = time.time() - clone_start + + status.update(f"[cyan]Templates fetched ({clone_time:.1f}s)[/cyan]") + template_list = templates.get(framework.value, ["default"]) + + fetch_time = time.time() - fetch_start + + console.print(f"[dim]✓ Found {len(template_list)} template(s) in {fetch_time:.1f}s[/dim]") + + # Auto-select if only one template available + if len(template_list) == 1: + selected_template = template_list[0] + console.print(f"[dim]→ Using template: {selected_template}[/dim]\n") + else: + # Show dropdown for multiple templates + console.print() + template_choices = [(f"🧱 {tmpl}", tmpl) for tmpl in template_list] + + tmpl_questions = [ + inquirer.List( + 'template', + message="Choose template", + choices=template_choices, + carousel=True + ), + ] + + tmpl_answer = inquirer.prompt(tmpl_questions) + if not tmpl_answer: + console.print("[dim]Initialization cancelled.[/dim]") + return + + selected_template = tmpl_answer['template'] + else: + # Blank project uses default framework + framework = Framework.DEFAULT + selected_template = "default" + + # Step 3: Get agent name and description (for both blank and template) + console.print("\n[bold]Agent Details:[/bold]\n") + + agent_name = Prompt.ask( + "[cyan]Agent name[/cyan]", + default="my-agent" + ) + + agent_description = Prompt.ask( + "[cyan]Agent description[/cyan]", + default="My AI agent" + ) + + # Step 4: Get path (default based on agent name) + console.print() + # Convert agent name to valid directory name (replace spaces with hyphens, lowercase) + default_path = agent_name.lower().replace(" ", "-").replace("_", "-") + path_input = Prompt.ask( + "[cyan]Project path[/cyan]", + default=default_path + ) + path = Path(path_input) + + # Ensure framework is set + if not framework: + framework = Framework.DEFAULT + + # Validate framework if it came from string input + if isinstance(framework, str): + try: + framework = Framework.from_string(framework) + except ValueError as e: + raise click.UsageError(str(e)) + + # Use the path as the project location + project_path = path.resolve() + + # Ensure the path exists + project_path.parent.mkdir(parents=True, exist_ok=True) + + # Show configuration summary + console.print(Panel( + f"[bold]Project Configuration:[/bold]\n\n" + f"[dim]Name:[/dim] [cyan]{agent_name}[/cyan]\n" + f"[dim]Description:[/dim] [white]{agent_description}[/white]\n" + f"[dim]Framework:[/dim] [magenta]{framework.value}[/magenta]\n" + f"[dim]Template:[/dim] [yellow]{selected_template}[/yellow]\n" + f"[dim]Path:[/dim] [blue]{project_path}[/blue]", + title="[bold cyan]Creating Agent[/bold cyan]", + border_style="cyan" + )) + + # Initialize project + success = sdk.init_project( + folder_path=project_path, + framework=framework.value, + template=selected_template, + overwrite=overwrite + ) + + if not success: + raise Exception("Project initialization failed") + + # Update config file with name and description + try: + import warnings + from datetime import datetime + + # Suppress Pydantic datetime warnings during config update + warnings.filterwarnings('ignore', category=UserWarning, module='pydantic') + + config_path = project_path / "runagent.config.json" + if config_path.exists(): + with open(config_path, 'r') as f: + config_data = json.load(f) + + config_data['name'] = agent_name + config_data['description'] = agent_description + + # Fix created_at format if it exists and is a string in wrong format + if 'created_at' in config_data and isinstance(config_data['created_at'], str): + try: + # Try to parse and convert to ISO format + dt = datetime.strptime(config_data['created_at'], "%Y-%m-%d %H:%M:%S") + config_data['created_at'] = dt.isoformat() + except: + # If parsing fails, use current time in ISO format + config_data['created_at'] = datetime.now().isoformat() + + with open(config_path, 'w') as f: + json.dump(config_data, f, indent=2) + + console.print("\n[dim]✓ Updated agent name and description in config[/dim]") + except Exception as e: + console.print(f"[yellow]⚠️ Could not update config: {e}[/yellow]") + + # Success message + relative_path = project_path.relative_to(Path.cwd()) if project_path != Path.cwd() else Path(".") + + console.print(Panel( + f"[bold green]✅ Agent '{agent_name}' created successfully![/bold green]\n\n" + f"[dim]Location:[/dim] [cyan]{relative_path}[/cyan]\n" + f"[dim]Framework:[/dim] [magenta]{framework.value}[/magenta]", + title="[bold green]Success[/bold green]", + border_style="green" + )) + + # Simple next steps + console.print("\n💡 [bold]Next Steps:[/bold]") + if relative_path != Path("."): + console.print(f" 1️⃣ [cyan]cd {relative_path}[/cyan]") + console.print(f" 2️⃣ Install dependencies: [cyan]pip install -r requirements.txt[/cyan]") + console.print(f" 3️⃣ Serve locally: [cyan]runagent serve .[/cyan]") + else: + console.print(f" 1️⃣ Install dependencies: [cyan]pip install -r requirements.txt[/cyan]") + console.print(f" 2️⃣ Serve locally: [cyan]runagent serve .[/cyan]") + + except TemplateError as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(Panel( + f"[bold red]Template Error[/bold red]\n\n" + f"{str(e)}\n\n" + f"[dim]Please check that the selected framework and template are valid.[/dim]", + title="[bold red]❌ Failed[/bold red]", + border_style="red" + )) + import sys + sys.exit(1) + except FileExistsError as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + + # Extract just the path from the error message + path_match = str(e).split("'") + folder_path = path_match[1] if len(path_match) > 1 else "the specified path" + + console.print(Panel( + f"[bold yellow]Directory Already Exists[/bold yellow]\n\n" + f"[dim]Path:[/dim] [cyan]{folder_path}[/cyan]\n\n" + f"The directory already exists and is not empty.\n\n" + f"[bold]Options:[/bold]\n" + f" • Choose a different path\n" + f" • Use [cyan]--overwrite[/cyan] flag to replace existing files\n" + f" • Remove the directory manually", + title="[bold yellow]⚠️ Path Conflict[/bold yellow]", + border_style="yellow" + )) + import sys + sys.exit(1) + except click.UsageError: + # Re-raise UsageError as-is for proper click handling + raise + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Initialization error:[/red] {e}") + raise click.ClickException("Project initialization failed") + diff --git a/runagent/cli/commands/run.py b/runagent/cli/commands/run.py new file mode 100644 index 0000000..1417769 --- /dev/null +++ b/runagent/cli/commands/run.py @@ -0,0 +1,235 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + + + +@click.command( + context_settings=dict( + ignore_unknown_options=True, + allow_extra_args=True, + )) +@click.option("--id", "agent_id", help="Agent ID to run") +@click.option("--host", help="Host to connect to (use with --port)") +@click.option("--port", type=int, help="Port to connect to (use with --host)") +@click.option( + "--input", + "input_file", + type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=Path), + help="Path to input JSON file" +) +@click.option("--local", is_flag=True, help="Run agent locally") +@click.option("--tag", required=True, help="Entrypoint tag to be used") +# @click.option("--generic-stream", is_flag=True, help="Use generic streaming mode") +@click.option("--timeout", type=int, help="Timeout in seconds") +@click.pass_context +def run(ctx, agent_id, host, port, input_file, local, tag, timeout): + """ + Run an agent with flexible configuration options + + Examples: + # Using agent ID with extra params + runagent run --agent-id my-agent --param1=value1 --param2=value2 + + # Using host/port with input file + runagent run --host localhost --port 8080 --input config.json + + # local agent + runagent run --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal --local --message=something + + # remote agent + runagent run --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal --message=something + """ + from runagent.cli.branding import print_header + print_header("Run Agent") + + # ============================================ + # VALIDATION 1: Either agent-id OR host/port + # ============================================ + agent_id_provided = agent_id is not None + host_port_provided = host is not None or port is not None + + if agent_id_provided and host_port_provided: + raise click.UsageError( + "Cannot specify both --agent-id and --host/--port. " + "Choose one approach." + ) + + if not agent_id_provided and not host_port_provided: + raise click.UsageError( + "Must specify either --agent-id or both --host and --port." + ) + + # If using host/port, both must be provided + if host_port_provided and (host is None or port is None): + raise click.UsageError( + "When using host/port, both --host and --port must be specified." + ) + + # ============================================ + # # VALIDATION 2: tag validation + # # ============================================ + if tag.endswith("_stream"): + console.print(f"❌ [bold red]Execution failed:[/bold red] Cannot use streaming Entrypoint tag `{tag}` through non-streaming endpoint.") + return + + + # ============================================ + # VALIDATION 3: Input file OR extra params + # ============================================ + + # Parse extra parameters from ctx.args + extra_params = {} + invalid_args = [] + + for arg in ctx.args: + if arg.startswith('--') and '=' in arg: + # Valid format: --key=value + key, value = arg[2:].split('=', 1) + extra_params[key] = value + else: + # Invalid format + invalid_args.append(arg) + + if invalid_args: + raise click.UsageError( + f"Invalid extra arguments: {invalid_args}. " + "Extra parameters must be in --key=value format." + ) + + # Check mutual exclusivity of input file and extra params + if input_file and extra_params: + raise click.UsageError( + "Cannot specify both --input file and extra parameters. " + "Use either --input config.json OR --param1=value1 --param2=value2" + ) + + if not input_file and not extra_params: + console.print("⚠️ No input file or extra parameters provided. Running with defaults.") + + # ============================================ + # DISPLAY CONFIGURATION + # ============================================ + + console.print("🚀 RunAgent Configuration:") + + # Connection info + if agent_id: + console.print(f" Agent ID: [cyan]{agent_id}[/cyan]") + else: + console.print(f" Host: [cyan]{host}[/cyan]") + console.print(f" Port: [cyan]{port}[/cyan]") + + # Tag + # mode = "Generic Streaming" if generic_stream else "Generic" + console.print(f" Tag: [magenta]{tag}[/magenta]") + + # Local execution + if local: + console.print(" Local: [green]Yes[/green]") + else: + console.print(" Local: [red]No(Deployed to RunAgent Cloud)[/red]") + + # Timeout + if timeout: + console.print(f" Timeout: [yellow]{timeout}s[/yellow]") + + # Input configuration + if input_file: + console.print(f" Input file: [blue]{input_file}[/blue]") + # Load and validate JSON file here + try: + import json + with open(input_file, 'r') as f: + input_params = json.load(f) + console.print(f" Config keys: [dim]{list(input_params.keys())}[/dim]") + except json.JSONDecodeError: + if os.getenv('DISABLE_TRY_CATCH'): + raise + raise click.ClickException(f"Invalid JSON in input file: {input_file}") + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + raise click.ClickException(f"Error reading input file: {e}") + + elif extra_params: + console.print(" Extra parameters:") + for key, value in extra_params.items(): + # Try to parse value as JSON for complex types + # TODO: Will add type inference later + console.print(f" --{key} = [green]{value}[/green]") + input_params = extra_params + + else: + input_params = {} + + # ============================================ + # EXECUTION LOGIC + # ============================================ + + try: + ra_client = RunAgentClient( + agent_id=agent_id, + local=local, + host=host, + port=port, + entrypoint_tag=tag + ) + + if tag.endswith("_stream"): + for item in ra_client.run(**input_params): + console.print(item) + else: + result = ra_client.run(**input_params) + console.print(result) + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + # Display error with red ❌ symbol + console.print(f"❌ [bold red]Execution failed:[/bold red] {e}") + # Exit with error code 1 instead of raising ClickException to avoid duplicate message + import sys + sys.exit(1) diff --git a/runagent/cli/commands/run_stream.py b/runagent/cli/commands/run_stream.py new file mode 100644 index 0000000..65f4982 --- /dev/null +++ b/runagent/cli/commands/run_stream.py @@ -0,0 +1,204 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + + +@click.command( + context_settings=dict( + ignore_unknown_options=True, + allow_extra_args=True, + )) +@click.option("--id", "agent_id", help="Agent ID to run") +@click.option("--host", help="Host to connect to (use with --port)") +@click.option("--port", type=int, help="Port to connect to (use with --host)") +@click.option( + "--input", + "input_file", + type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=Path), + help="Path to input JSON file" +) +@click.option("--local", is_flag=True, help="Run agent locally") +@click.option("--tag", required=True, help="Entrypoint tag to be used") +@click.option("--timeout", type=int, help="Timeout in seconds") +@click.pass_context +def run_stream(ctx, agent_id, host, port, input_file, local, tag, timeout): + """ + Stream agent execution results in real-time. + + This command connects to an agent via WebSocket and streams the execution results + as they become available, providing real-time feedback. + + Examples: + # Local streaming agent + runagent run-stream --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal_stream --local --message=something + + # Remote streaming agent + runagent run-stream --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal_stream --message=something + + # With input file + runagent run-stream --id d33c497d-d3f5-462e-8ff4-c28d819b92d6 --tag minimal_stream --local --input config.json + """ + from runagent.cli.branding import print_header + print_header("Stream Agent Output") + + # ============================================ + # PARAMETER PARSING + # ============================================ + + extra_params = {} + for item in ctx.args: + if '=' in item: + key, value = item.split('=', 1) + # Remove leading dashes + key = key.lstrip('-') + extra_params[key] = value + else: + # Handle boolean flags + key = item.lstrip('-') + extra_params[key] = True + + # ============================================ + # VALIDATION + # ============================================ + + # VALIDATION 1: Agent ID or host/port required + if not agent_id and not (host and port): + console.print(f"❌ [bold red]Execution failed:[/bold red] Either --id or both --host and --port are required") + import sys + sys.exit(1) + + # VALIDATION 2: tag validation for streaming + if not tag.endswith("_stream"): + console.print(f"❌ [bold red]Execution failed:[/bold red] Streaming command requires entrypoint tag ending with '_stream'. Got: {tag}") + import sys + sys.exit(1) + + # ============================================ + # DISPLAY CONFIGURATION + # ============================================ + + console.print("🚀 RunAgent Streaming Configuration:") + + # Connection info + if agent_id: + console.print(f" Agent ID: [cyan]{agent_id}[/cyan]") + else: + console.print(f" Host: [cyan]{host}[/cyan]") + console.print(f" Port: [cyan]{port}[/cyan]") + + # Tag + console.print(f" Tag: [magenta]{tag}[/magenta]") + + # Local execution + if local: + console.print(" Local: [green]Yes[/green]") + else: + console.print(" Local: [red]No (Deployed to RunAgent Cloud)[/red]") + + # Timeout + if timeout: + console.print(f" Timeout: [yellow]{timeout}s[/yellow]") + + # Input configuration + if input_file: + console.print(f" Input file: [blue]{input_file}[/blue]") + # Load and validate JSON file here + try: + import json + with open(input_file, 'r') as f: + input_params = json.load(f) + console.print(f" Config keys: [dim]{list(input_params.keys())}[/dim]") + except json.JSONDecodeError: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [bold red]Execution failed:[/bold red] Invalid JSON in input file: {input_file}") + import sys + sys.exit(1) + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [bold red]Execution failed:[/bold red] Error reading input file: {e}") + import sys + sys.exit(1) + + elif extra_params: + console.print(" Extra parameters:") + for key, value in extra_params.items(): + console.print(f" --{key} = {value}") + input_params = extra_params + + else: + input_params = {} + + # ============================================ + # EXECUTION LOGIC + # ============================================ + + try: + ra_client = RunAgentClient( + agent_id=agent_id, + local=local, + host=host, + port=port, + entrypoint_tag=tag + ) + + console.print(f"\n🔄 [bold]Starting streaming execution...[/bold]") + console.print(f"📡 [dim]Connected to agent via WebSocket[/dim]") + console.print(f"📤 [dim]Streaming results:[/dim]\n") + + # Stream the results + for chunk in ra_client.run_stream(**input_params): + console.print(chunk) + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + # Display error with red ❌ symbol + console.print(f"❌ [bold red]Streaming failed:[/bold red] {e}") + # Exit with error code 1 instead of raising ClickException to avoid duplicate message + import sys + sys.exit(1) + diff --git a/runagent/cli/commands/serve.py b/runagent/cli/commands/serve.py new file mode 100644 index 0000000..72a7843 --- /dev/null +++ b/runagent/cli/commands/serve.py @@ -0,0 +1,222 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + + + +@click.command() +@click.option("--port", type=int, help="Preferred port (auto-allocated if unavailable)") +@click.option("--host", default="127.0.0.1", help="Host to bind server to") +@click.option("--debug", is_flag=True, help="Run server in debug mode") +@click.option("--replace", help="Replace existing agent with this agent ID") +@click.option("--no-animation", is_flag=True, help="Skip startup animation") +@click.option("--animation-style", + type=click.Choice(["field", "ascii", "minimal", "quick"]), + default="field", + help="Animation style") +@click.argument( + "path", + type=click.Path( + exists=True, + file_okay=False, + dir_okay=True, + readable=True, + resolve_path=True, + path_type=Path, + ), + default=".", +) +def serve(port, host, debug, replace, no_animation, animation_style, path): + """Start local FastAPI server with subtle robotic runner animation""" + + try: + from runagent.cli.branding import print_header + print_header("Serve Agent Locally") + + # Show subtle startup animation + if not no_animation: + console.print("\n") + + if animation_style == "quick": + show_quick_runner(duration=1.5) + else: + show_subtle_robotic_runner(duration=2.0, style=animation_style) + + sdk = RunAgent() + + # Handle replace operation + if replace: + console.print(f"🔄 [yellow]Replacing agent: {replace}[/yellow]") + + # Check if the agent to replace exists + existing_agent = sdk.db_service.get_agent(replace) + if not existing_agent: + console.print(f"⚠️ [yellow]Agent {replace} not found in database[/yellow]") + console.print("💡 Available agents:") + agents = sdk.db_service.list_agents() + for agent in agents[:5]: # Show first 5 + console.print(f" • {agent['agent_id']} ({agent['framework']})") + raise click.ClickException("Agent to replace not found") + + # Generate new agent ID + import uuid + new_agent_id = str(uuid.uuid4()) + + # Get currently used ports to avoid conflicts + used_ports = [] + all_agents = sdk.db_service.list_agents() + for agent in all_agents: + if agent.get('port') and agent['agent_id'] != replace: # Exclude the agent being replaced + used_ports.append(agent['port']) + + # Allocate host and port + from runagent.utils.port import PortManager + if port and PortManager.is_port_available(host, port): + allocated_host = host + allocated_port = port + console.print(f"🎯 Using specified address: [blue]{allocated_host}:{allocated_port}[/blue]") + else: + allocated_host, allocated_port = PortManager.allocate_unique_address(used_ports) + console.print(f"🔌 Auto-allocated address: [blue]{allocated_host}:{allocated_port}[/blue]") + + # Use the existing replace_agent method with proper port allocation + result = sdk.db_service.replace_agent( + old_agent_id=replace, + new_agent_id=new_agent_id, + agent_path=str(path), + host=allocated_host, + port=allocated_port, # Ensure port is not None + framework=detect_framework(path).value, + ) + + if not result["success"]: + raise click.ClickException(f"Failed to replace agent: {result['error']}") + + console.print(f"✅ [green]Agent replaced successfully![/green]") + console.print(f"🆔 New Agent ID: [bold magenta]{new_agent_id}[/bold magenta]") + console.print(f"🔌 Address: [bold blue]{allocated_host}:{allocated_port}[/bold blue]") + + # Create server with the new agent ID and allocated host/port + from runagent.sdk.db import DBService + db_service = DBService() + + server = LocalServer( + db_service=db_service, + agent_id=new_agent_id, + agent_path=path, + port=allocated_port, + host=allocated_host, + ) + else: + # Normal operation - check capacity if not replacing + capacity_info = sdk.db_service.get_database_capacity_info() + if capacity_info["is_full"] and not replace: + console.print("❌ [red]Database is full![/red]") + oldest_agent = capacity_info.get("oldest_agent", {}) + if oldest_agent: + console.print(f"💡 [yellow]Suggested commands:[/yellow]") + console.print(f" Replace: [cyan]runagent serve {path} --replace {oldest_agent.get('agent_id', '')}[/cyan]") + console.print(f" Delete: [cyan]runagent delete --id {oldest_agent.get('agent_id', '')}[/cyan]") + raise click.ClickException("Database at capacity. Use --replace or use 'runagent delete' to free space.") + + console.print("⚡ [bold]Starting local server with auto port allocation...[/bold]") + + # Use the existing LocalServer.from_path method + server = LocalServer.from_path(path, port=port, host=host) + + # Common server startup code + allocated_host = server.host + allocated_port = server.port + + console.print(f"🌐 URL: [bold blue]http://{allocated_host}:{allocated_port}[/bold blue]") + console.print(f"📖 Docs: [link]http://{allocated_host}:{allocated_port}/docs[/link]") + + try: + + sync_service = get_middleware_sync() + sync_enabled = sync_service.is_sync_enabled() + api_key_set = bool(Config.get_api_key()) + + console.print(f"\n🔄 [bold]Middleware Sync Status:[/bold]") + if sync_enabled: + console.print(f" Status: [green]✅ ENABLED[/green]") + console.print(f" 📊 Local invocations will sync to middleware") + + # Test connection + try: + test_result = sync_service.test_connection() + if test_result.get("success"): + console.print(f" Connection: [green]✅ Connected to middleware[/green]") + else: + console.print(f" Connection: [red]❌ Failed to connect: {test_result.get('error', 'Unknown error')}[/red]") + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f" Connection: [red]❌ Connection test failed: {e}[/red]") + else: + console.print(f" Status: [yellow]⚠️ DISABLED[/yellow]") + if not api_key_set: + console.print(f" Reason: [yellow]API key not configured[/yellow]") + console.print(f" 💡 Setup: [cyan]runagent setup --api-key [/cyan]") + else: + user_disabled = not Config.get_user_config().get("local_sync_enabled", True) + if user_disabled: + console.print(f" Reason: [yellow]Disabled by user[/yellow]") + console.print(f" 💡 Enable: [cyan]runagent local-sync --enable[/cyan]") + console.print(f" 📊 Local invocations will only be stored locally") + + except Exception as e: + console.print(f"[dim]Note: Could not check middleware sync status: {e}[/dim]") + + # Start server (this will block) + server.start(debug=debug) + + except KeyboardInterrupt: + console.print("\n🛑 [yellow]Server stopped by user[/yellow]") + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Server error:[/red] {e}") + raise click.ClickException("Server failed to start") diff --git a/runagent/cli/commands/setup.py b/runagent/cli/commands/setup.py new file mode 100644 index 0000000..2741a7c --- /dev/null +++ b/runagent/cli/commands/setup.py @@ -0,0 +1,238 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +@click.command() +@click.option("--again", is_flag=True, help="Reconfigure even if already setup") +def setup(again): + """ + Setup RunAgent authentication + + \b + First-time setup: + $ runagent setup + + \b + Reconfigure: + $ runagent setup --again + + \b + Change specific settings later: + $ runagent config set-api-key + $ runagent config set-base-url + """ + try: + from runagent.cli.branding import print_setup_banner + from rich.prompt import Prompt, Confirm + from rich.panel import Panel + + sdk = RunAgent() + api_key = Config.get_api_key() + + # Check if already configured + if api_key and not again: + config_status = sdk.get_config_status() + user_email = config_status.get('user_info', {}).get('email', 'N/A') + + console.print(Panel( + "[bold cyan]✅ RunAgent is already configured![/bold cyan]\n\n" + f"[dim]User:[/dim] [green]{user_email}[/green]\n" + f"[dim]Base URL:[/dim] [cyan]{config_status.get('base_url')}[/cyan]\n\n" + "[dim]To reconfigure, run:[/dim] [white]runagent setup --again[/white]\n" + "[dim]To view config:[/dim] [white]runagent config status[/white]", + title="[bold]Already Setup[/bold]", + border_style="cyan" + )) + return + + # Show welcome banner for new setup + if not api_key or again: + if not api_key: + print_setup_banner() + else: + console.print("\n[bold cyan]🔄 Reconfiguring RunAgent[/bold cyan]\n") + + # Show setup method options with arrow-key selection + console.print("[bold cyan]Choose your setup method:[/bold cyan]\n") + + import inquirer + + questions = [ + inquirer.List( + 'setup_method', + message="Select setup method", + choices=[ + ('🪄 Express Setup (Browser login - Coming Soon!)', 'express'), + ('🔑 Manual Setup (Enter API key)', 'manual'), + ], + default=('🔑 Manual Setup (Enter API key)', 'manual'), + carousel=True + ), + ] + + answers = inquirer.prompt(questions) + if not answers: + console.print("[dim]Setup cancelled.[/dim]") + return + + choice = answers['setup_method'] + + if choice == "express": + # Express setup - coming soon + console.print(Panel( + "[bold cyan]🚀 Express Setup - Coming Soon![/bold cyan]\n\n" + "This feature will allow you to authenticate via your browser.\n\n" + "[dim]For now, please use Manual Setup[/dim]\n\n" + "📚 [link=https://docs.runagent.dev/setup]Learn more[/link]", + title="[bold]Feature Preview[/bold]", + border_style="cyan" + )) + + if not Confirm.ask("\n[bold]Continue with Manual Setup?[/bold]", default=True): + console.print("[dim]Setup cancelled.[/dim]") + return + + # Manual setup - prompt for API key + console.print("\n[bold white]📝 Manual Setup[/bold white]\n") + api_key = Prompt.ask( + "[cyan]Enter your API key[/cyan]", + password=True + ) + + if not api_key or not api_key.strip(): + console.print(Panel( + "[red]❌ API key cannot be empty[/red]", + title="[bold red]Error[/bold red]", + border_style="red" + )) + raise click.ClickException("Invalid API key") + + console.print("\n🔑 [cyan]Configuring RunAgent...[/cyan]") + + # Configure SDK with validation + try: + from rich.status import Status + from runagent.constants import DEFAULT_BASE_URL + + # Use default base URL from constants + base_url = Config.get_base_url() or DEFAULT_BASE_URL + + with Status("[bold cyan]Validating credentials...", spinner="dots", console=console) as status: + sdk.configure(api_key=api_key, base_url=base_url, save=True) + + console.print(Panel( + "[bold green]✅ Setup completed successfully![/bold green]\n\n" + "[dim]Your credentials have been saved securely.[/dim]", + title="[bold green]Success[/bold green]", + border_style="green" + )) + except AuthenticationError as auth_err: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Authentication failed:[/red] {auth_err}") + + # Provide specific troubleshooting based on error message + error_msg = str(auth_err).lower() + console.print("\n💡 [yellow]Troubleshooting:[/yellow]") + + if "invalid api key" in error_msg or "not authenticated" in error_msg: + console.print(" • Check that your API key is correct") + console.print(" • Verify the API key is not expired") + console.print(" • Ensure you have access to the middleware") + elif "connection" in error_msg or "timeout" in error_msg: + console.print(" • Check your internet connection") + console.print(" • Verify the middleware server is accessible") + from runagent.constants import DEFAULT_BASE_URL + display_url = base_url if 'base_url' in locals() else DEFAULT_BASE_URL + console.print(f" • Trying to connect to: {display_url}") + else: + console.print(" • Check your API key and network connection") + console.print(" • Contact support if the issue persists") + + raise click.ClickException("Authentication failed") + + # Show user information (from cached data) + config_status = sdk.get_config_status() + user_info = config_status.get('user_info', {}) + + if user_info and user_info.get('email'): + from rich.panel import Panel + from rich.table import Table + + # Create info table + info_table = Table(show_header=False, box=None, padding=(0, 2)) + info_table.add_column("", style="dim", no_wrap=True) + info_table.add_column("", style="cyan") + + info_table.add_row("✉️ Email", user_info.get('email')) + info_table.add_row("🎯 Tier", user_info.get('tier', 'Free')) + + # Show active project + user_config = Config.get_user_config() + active_project = user_config.get('active_project_name') + if active_project: + info_table.add_row("📁 Active Project", active_project) + + console.print(Panel( + info_table, + title="[bold]👤 User Information[/bold]", + border_style="cyan" + )) + + # Show sync status (simplified) + console.print("\n🔄 [bold]Middleware Sync Status:[/bold]") + try: + from runagent.sdk.deployment.middleware_sync import MiddlewareSyncService + sync_service = MiddlewareSyncService(sdk.config) + + if sync_service.is_sync_enabled(): + console.print(" Status: [green]✅ ENABLED[/green]") + console.print(" 📊 Local agent runs will sync to middleware") + else: + console.print(" Status: [yellow]⚠️ DISABLED[/yellow]") + console.print(" 📊 Only local storage will be used") + + except Exception as e: + console.print(f" Status: [yellow]Unknown - {e}[/yellow]") + + # Show next steps - Simple workflow + console.print("\n💡 [bold]Next Steps:[/bold]") + console.print(" 1️⃣ Initialize a new agent: [cyan]runagent init[/cyan]") + console.print(" 2️⃣ Serve it locally: [cyan]runagent serve [/cyan]") + console.print(" 3️⃣ Invoke your agent: [cyan]runagent run --id --tag [/cyan]") + + except AuthenticationError: + # Already handled above + raise + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Setup error:[/red] {e}") + raise click.ClickException("Setup failed") + diff --git a/runagent/cli/commands/start.py b/runagent/cli/commands/start.py new file mode 100644 index 0000000..f292669 --- /dev/null +++ b/runagent/cli/commands/start.py @@ -0,0 +1,101 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + +@click.command() +@click.option("--id", "agent_id", required=True, help="Agent ID to start") +@click.option("--config", help="JSON configuration for deployment") +def start(agent_id, config): + """Start an uploaded agent on remote server""" + + try: + from runagent.cli.branding import print_header + print_header("Start Remote Agent") + + sdk = RunAgent() + + # Check authentication + if not sdk.is_configured(): + console.print( + "❌ [red]Not authenticated.[/red] Run [cyan]'runagent setup --api-key '[/cyan] first" + ) + raise click.ClickException("Authentication required") + + # Parse config + config_dict = {} + if config: + try: + config_dict = json.loads(config) + except json.JSONDecodeError: + if os.getenv('DISABLE_TRY_CATCH'): + raise + raise click.ClickException("Invalid JSON in config parameter") + + console.print(f"🚀 [bold]Starting agent...[/bold]") + console.print(f"🆔 Agent ID: [magenta]{agent_id}[/magenta]") + + # Start agent + result = sdk.start_remote_agent(agent_id, config_dict) + + if result.get("success"): + console.print(f"\n✅ [green]Agent started successfully![/green]") + console.print(f"🌐 Endpoint: [link]{result.get('endpoint')}[/link]") + else: + console.print(f"❌ [red]Start failed:[/red] {format_error_message(result.get('error'))}") + import sys + sys.exit(1) + + except AuthenticationError as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Authentication error:[/red] {e}") + import sys + sys.exit(1) + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Start error:[/red] {e}") + import sys + sys.exit(1) diff --git a/runagent/cli/commands/teardown.py b/runagent/cli/commands/teardown.py new file mode 100644 index 0000000..95ff351 --- /dev/null +++ b/runagent/cli/commands/teardown.py @@ -0,0 +1,127 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + + +@click.command() +@click.option("--yes", is_flag=True, help="Skip confirmation") +def teardown(yes): + """Complete teardown - Remove RunAgent configuration AND database""" + try: + from runagent.cli.branding import print_header + from rich.panel import Panel + from rich.prompt import Confirm + from runagent.constants import LOCAL_CACHE_DIRECTORY, DATABASE_FILE_NAME + from pathlib import Path + + print_header("Complete Teardown") + + sdk = RunAgent() + + if not yes: + config_status = sdk.get_config_status() + db_stats = sdk.db_service.get_database_stats() + + # Show what will be deleted + console.print(Panel( + "[bold red]⚠️ COMPLETE TEARDOWN[/bold red]\n\n" + "This will permanently delete:\n" + " • All configuration (API key, user info, settings)\n" + " • Complete database (all agents, runs, logs, history)\n" + " • All local agent data\n\n" + "[yellow]This action CANNOT be undone![/yellow]", + title="[bold red]Warning[/bold red]", + border_style="red" + )) + + console.print("\n📊 [bold]Current data:[/bold]") + if config_status.get("configured"): + console.print(f" User: [cyan]{config_status.get('user_info', {}).get('email', 'N/A')}[/cyan]") + console.print(f" Total agents: [yellow]{db_stats.get('total_agents', 0)}[/yellow]") + console.print(f" Total runs: [yellow]{db_stats.get('total_runs', 0)}[/yellow]") + console.print(f" Database size: [yellow]{db_stats.get('database_size_mb', 0)} MB[/yellow]\n") + + if not Confirm.ask( + "[bold red]Are you absolutely sure you want to proceed?[/bold red]", + default=False + ): + console.print("[dim]Teardown cancelled.[/dim]") + return + + # Clear configuration from database + sdk.config.clear() + + # Close database connections + sdk.db_service.close() + + # Delete database file + db_path = Path(LOCAL_CACHE_DIRECTORY) / DATABASE_FILE_NAME + if db_path.exists(): + db_path.unlink() + console.print(f"🗑️ [dim]Deleted database: {db_path}[/dim]") + + # Delete legacy JSON file if exists + json_file = Path(LOCAL_CACHE_DIRECTORY) / "user_data.json" + if json_file.exists(): + json_file.unlink() + console.print(f"🗑️ [dim]Deleted legacy config: {json_file}[/dim]") + + console.print(Panel( + "[bold green]✅ RunAgent teardown completed successfully![/bold green]\n\n" + "All configuration and data have been removed.\n\n" + "[dim]To start fresh, run:[/dim] [cyan]runagent setup[/cyan]", + title="[bold green]Complete[/bold green]", + border_style="green" + )) + + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(Panel( + f"[red]❌ Teardown error:[/red] {str(e)}", + title="[bold red]Error[/bold red]", + border_style="red" + )) + raise click.ClickException("Teardown failed") diff --git a/runagent/cli/commands/upload.py b/runagent/cli/commands/upload.py new file mode 100644 index 0000000..c885f64 --- /dev/null +++ b/runagent/cli/commands/upload.py @@ -0,0 +1,110 @@ +""" +CLI commands that use the restructured SDK internally. +""" +import os +import json +import uuid + +from pathlib import Path + +import click +from rich.console import Console +from rich.table import Table + +from runagent import RunAgent +from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError + AuthenticationError, + TemplateError, + ValidationError, +) +from runagent.client.client import RunAgentClient +from runagent.sdk.server.local_server import LocalServer +from runagent.utils.agent import detect_framework +from runagent.utils.animation import show_subtle_robotic_runner, show_quick_runner +from runagent.utils.config import Config +from runagent.sdk.deployment.middleware_sync import get_middleware_sync +from runagent.cli.utils import add_framework_options, get_selected_framework +from runagent.utils.enums.framework import Framework +console = Console() + + +def format_error_message(error_info): + """Format error information from API responses""" + if isinstance(error_info, dict) and "message" in error_info: + # New format with ErrorDetail object + error_message = error_info.get("message", "Unknown error") + error_code = error_info.get("code", "UNKNOWN_ERROR") + return f"[{error_code}] {error_message}" + else: + # Fallback to old format for backward compatibility + return str(error_info) if error_info else "Unknown error" + + +# ============================================================================ +# Config Command Group +# ============================================================================ + + +@click.command() +@click.argument( + "path", + type=click.Path( + exists=True, + file_okay=False, + dir_okay=True, + readable=True, + resolve_path=True, + path_type=Path, + ), + default=".", +) +def upload(path: Path): + """Upload agent to remote server""" + + try: + from runagent.cli.branding import print_header + print_header("Upload Agent") + + sdk = RunAgent() + + # Check authentication + if not sdk.is_configured(): + console.print( + "❌ [red]Not authenticated.[/red] Run [cyan]'runagent setup --api-key '[/cyan] first" + ) + raise click.ClickException("Authentication required") + + # Validate folder + if not Path(path).exists(): + raise click.ClickException(f"Folder not found: {path}") + + console.print(f"📤 [bold]Uploading agent...[/bold]") + console.print(f"📁 Source: [cyan]{path}[/cyan]") + + # Upload agent (framework auto-detected) + result = sdk.upload_agent(folder=path) + + if result.get("success"): + agent_id = result["agent_id"] + console.print(f"\n✅ [green]Upload successful![/green]") + console.print(f"🆔 Agent ID: [bold magenta]{agent_id}[/bold magenta]") + console.print(f"\n💡 [bold]Next step:[/bold]") + console.print(f"[cyan]runagent start --id {agent_id}[/cyan]") + else: + console.print(f"❌ [red]Upload failed:[/red] {format_error_message(result.get('error'))}") + import sys + sys.exit(1) + + except AuthenticationError as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Authentication error:[/red] {e}") + import sys + sys.exit(1) + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"❌ [red]Upload error:[/red] {e}") + import sys + sys.exit(1) + diff --git a/runagent/cli/main.py b/runagent/cli/main.py index 240e30a..4737586 100644 --- a/runagent/cli/main.py +++ b/runagent/cli/main.py @@ -1,28 +1,79 @@ +import os import click +import warnings +from rich.console import Console from . import commands +from .branding import print_logo -@click.group() -@click.option('--version', is_flag=True, expose_value=False, is_eager=True, callback=commands.print_version, help='Show version information') -def runagent(): +from .commands.setup import setup as setup_cmd +from .commands.config import config as config_cmd +from .commands.teardown import teardown as teardown_cmd +from .commands.init import init as init_cmd +from .commands.upload import upload as upload_cmd +from .commands.start import start as start_cmd +from .commands.deploy import deploy as deploy_cmd +from .commands.serve import serve as serve_cmd +from .commands.run import run as run_cmd +from .commands.run_stream import run_stream as run_stream_cmd +from .commands.delete import delete as delete_cmd +from .commands.db import db as db_cmd + +if not os.getenv('DISABLE_TRY_CATCH'): + warnings.filterwarnings( + "ignore", + message=".*Pydantic serializer warnings.*" + ) + +def show_help_with_logo(ctx, param, value): + """Custom help callback that shows logo before help text""" + if value and not ctx.resilient_parsing: + print_logo(show_tagline=True, brand_color="cyan") + click.echo(ctx.get_help()) + ctx.exit() + +console = Console() + + +def print_version(ctx, param, value): + """Custom version callback with colored output""" + if not value or ctx.resilient_parsing: + return + try: + from runagent.__version__ import __version__ + from runagent.cli.branding import print_compact_logo + print_compact_logo(brand_color="cyan") + console.print(f"\n[bold white]Version:[/bold white] [bold cyan]{__version__}[/bold cyan]") + console.print(f"[dim]Deploy and manage AI agents with ease 🚀[/dim]\n") + except ImportError: + console.print("[red]runagent version unknown[/red]") + ctx.exit() + + +@click.group(invoke_without_command=True) +@click.option('--help', '-h', is_flag=True, expose_value=False, is_eager=True, callback=show_help_with_logo, help='Show this message and exit') +@click.option('--version', is_flag=True, expose_value=False, is_eager=True, callback=print_version, help='Show version information') +@click.pass_context +def runagent(ctx): """RunAgent CLI - Deploy and manage AI agents easily""" - pass - -runagent.add_command(commands.version) -runagent.add_command(commands.setup) -runagent.add_command(commands.teardown) -runagent.add_command(commands.init) -runagent.add_command(commands.template) -runagent.add_command(commands.upload) -runagent.add_command(commands.start) -runagent.add_command(commands.deploy) -runagent.add_command(commands.serve) -runagent.add_command(commands.run) -runagent.add_command(commands.run_stream) -runagent.add_command(commands.delete) -runagent.add_command(commands.db) -runagent.add_command(commands.local_sync) + # Show logo when no subcommand is provided + if ctx.invoked_subcommand is None: + print_logo(show_tagline=True, brand_color="cyan") + click.echo(ctx.get_help()) + +runagent.add_command(setup_cmd) +runagent.add_command(config_cmd) +runagent.add_command(teardown_cmd) +runagent.add_command(init_cmd) +runagent.add_command(upload_cmd) +runagent.add_command(start_cmd) +runagent.add_command(deploy_cmd) +runagent.add_command(serve_cmd) +runagent.add_command(run_cmd) +runagent.add_command(run_stream_cmd) +runagent.add_command(delete_cmd) +runagent.add_command(db_cmd) if __name__ == "__main__": runagent() \ No newline at end of file diff --git a/runagent/client/client.py b/runagent/client/client.py index f0c1341..30cafa9 100644 --- a/runagent/client/client.py +++ b/runagent/client/client.py @@ -56,6 +56,7 @@ def run(self, *input_args, **input_kwargs): response = self.rest_client.run_agent( self.agent_id, self.entrypoint_tag, input_args=input_args, input_kwargs=input_kwargs ) + print(f"response#######################################: {response}") if response.get("success"): # Handle new response format with nested data if "data" in response and "result_data" in response["data"]: diff --git a/runagent/constants.py b/runagent/constants.py index cc3ce1b..c85d5ee 100644 --- a/runagent/constants.py +++ b/runagent/constants.py @@ -16,16 +16,16 @@ # Environment Variables ENV_RUNAGENT_API_KEY = "RUNAGENT_API_KEY" -# UPDATED: Change default port to match your middleware (8333) -ENV_RUNAGENT_BASE_URL = "http://20.84.81.110:8333/" +ENV_RUNAGENT_BASE_URL = "RUNAGENT_BASE_URL" ENV_LOCAL_CACHE_DIRECTORY = "RUNAGENT_CACHE_DIR" ENV_RUNAGENT_LOGGING_LEVEL = "RUNAGENT_LOGGING_LEVEL" # Local Configuration LOCAL_CACHE_DIRECTORY_PATH = "~/.runagent" -USER_DATA_FILE_NAME = "user_data.json" -DEFAULT_BASE_URL = "http://20.84.81.110:8333/" +DEFAULT_BASE_URL = "https://runagent-middleware-v2.onrender.com/" AGENT_CONFIG_FILE_NAME = "runagent.config.json" +DATABASE_FILE_NAME = "runagent_local.db" +DEFAULT_TIMEOUT_SECONDS = 300 # 5 minutes default timeout # Rest of the file remains the same... _cache_dir = os.environ.get(ENV_LOCAL_CACHE_DIRECTORY) diff --git a/runagent/sdk/config.py b/runagent/sdk/config.py index ff4e51e..c514278 100644 --- a/runagent/sdk/config.py +++ b/runagent/sdk/config.py @@ -12,7 +12,6 @@ ENV_RUNAGENT_API_KEY, ENV_RUNAGENT_BASE_URL, LOCAL_CACHE_DIRECTORY, - USER_DATA_FILE_NAME, ) from .exceptions import AuthenticationError, ValidationError @@ -48,22 +47,43 @@ def __init__( self._config["base_url"] = base_url def _get_default_config_path(self) -> Path: - """Get default config file path""" + """Get default config file path (legacy - for migration only)""" config_dir = Path(LOCAL_CACHE_DIRECTORY) config_dir.mkdir(exist_ok=True) - return config_dir / USER_DATA_FILE_NAME + return config_dir / "user_data.json" # Legacy file def _load_config(self) -> t.Dict[str, t.Any]: - """Load configuration from all sources""" + """Load configuration from database and environment variables""" config = {} - # 1. Load from config file - if self.config_file.exists(): - try: - with open(self.config_file, "r") as f: - config.update(json.load(f)) - except (json.JSONDecodeError, IOError): - pass + # 1. Load from database + try: + from .db import DBService + db_service = DBService() + db_config = db_service.get_all_user_metadata() + + if db_config: + config.update(db_config) + elif self.config_file.exists(): + # One-time migration from JSON file if database is empty + try: + with open(self.config_file, "r") as f: + json_config = json.load(f) + config.update(json_config) + + # Migrate to database + if json_config: + for key, value in json_config.items(): + db_service.set_user_metadata(key, value) + + # Backup old file + backup_file = self.config_file.with_suffix('.json.backup') + self.config_file.rename(backup_file) + except (json.JSONDecodeError, IOError): + pass + except Exception: + # If database fails, just continue with empty config + pass # 2. Override with environment variables if os.getenv(ENV_RUNAGENT_API_KEY): @@ -77,13 +97,18 @@ def _load_config(self) -> t.Dict[str, t.Any]: return config def save_config(self) -> bool: - """Save current configuration to file""" + """Save current configuration to database""" try: - self.config_file.parent.mkdir(exist_ok=True) - with open(self.config_file, "w") as f: - json.dump(self._config, f, indent=2) - return True - except (IOError, OSError): + from .db import DBService + db_service = DBService() + + success = True + for key, value in self._config.items(): + if not db_service.set_user_metadata(key, value): + success = False + + return success + except Exception: return False def setup( @@ -146,7 +171,11 @@ def _test_authentication(self) -> t.Dict[str, t.Any]: # Test connection using the token validation endpoint api_key = self._config.get("api_key") - response = client.http.post(f"/tokens/validate?token={api_key}", timeout=10) + response = client.http.post( + f"/tokens/validate?token={api_key}", + data={}, # Send empty body (required by endpoint) + timeout=10 + ) if response.status_code == 200: token_data = response.json() @@ -158,7 +187,9 @@ def _test_authentication(self) -> t.Dict[str, t.Any]: user_info = { "email": data.get("user_email"), "user_id": data.get("user_id"), - "tier": data.get("user_tier", "Free") + "tier": data.get("user_tier", "Free"), + "active_project_name": data.get("default_project_name"), + "active_project_id": data.get("default_project_id"), } # Store user info for later display @@ -166,6 +197,8 @@ def _test_authentication(self) -> t.Dict[str, t.Any]: "user_email": user_info["email"], "user_id": user_info["user_id"], "user_tier": user_info["tier"], + "active_project_id": user_info["active_project_id"], + "active_project_name": user_info["active_project_name"], "auth_validated": True }) diff --git a/runagent/sdk/db.py b/runagent/sdk/db.py index be291b9..06ebc8f 100644 --- a/runagent/sdk/db.py +++ b/runagent/sdk/db.py @@ -24,7 +24,7 @@ from sqlalchemy.orm import relationship, sessionmaker from sqlalchemy.sql import func -from runagent.constants import LOCAL_CACHE_DIRECTORY +from runagent.constants import LOCAL_CACHE_DIRECTORY, DATABASE_FILE_NAME from runagent.utils.port import PortManager @@ -164,6 +164,22 @@ class AgentLog(Base): Index("idx_agent_logs_level", "log_level"), ) + +class UserMetadata(Base): + """User metadata model for storing user configuration as key-value pairs""" + + __tablename__ = "user_metadata" + + key = Column(String, primary_key=True) + value = Column(Text, nullable=False) # Store JSON-serialized values + created_at = Column(DateTime, default=func.current_timestamp()) + updated_at = Column( + DateTime, default=func.current_timestamp(), onupdate=func.current_timestamp() + ) + + # Indexes + __table_args__ = (Index("idx_user_metadata_key", "key"),) + class DBManager: """Low-level database manager for SQLAlchemy operations""" @@ -175,7 +191,7 @@ def __init__(self, db_path: Path = None): db_path: Path to the SQLite database file """ if db_path is None: - db_path = Path(LOCAL_CACHE_DIRECTORY) / "runagent_local.db" + db_path = Path(LOCAL_CACHE_DIRECTORY) / DATABASE_FILE_NAME self.db_path = db_path self.engine = None @@ -2037,3 +2053,149 @@ def cleanup_old_logs(self, days_old: int = 7) -> int: session.rollback() console.print(f"Error cleaning up old logs: {e}") return 0 + + # User Metadata Methods + def set_user_metadata(self, key: str, value: Any) -> bool: + """ + Set user metadata key-value pair + + Args: + key: Metadata key (e.g., 'api_key', 'base_url', 'user_email') + value: Metadata value (will be JSON-serialized) + + Returns: + True if successful, False otherwise + """ + with self.db_manager.get_session() as session: + try: + # Serialize value to JSON + value_json = json.dumps(value) + + # Check if key exists + metadata = session.query(UserMetadata).filter( + UserMetadata.key == key + ).first() + + if metadata: + # Update existing + metadata.value = value_json + metadata.updated_at = func.current_timestamp() + else: + # Create new + metadata = UserMetadata( + key=key, + value=value_json + ) + session.add(metadata) + + session.commit() + return True + except Exception as e: + session.rollback() + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"Error setting user metadata: {e}") + return False + + def get_user_metadata(self, key: str, default: Any = None) -> Any: + """ + Get user metadata value by key + + Args: + key: Metadata key + default: Default value if key doesn't exist + + Returns: + Deserialized metadata value or default + """ + with self.db_manager.get_session() as session: + try: + metadata = session.query(UserMetadata).filter( + UserMetadata.key == key + ).first() + + if not metadata: + return default + + # Deserialize JSON value + return json.loads(metadata.value) + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"Error getting user metadata: {e}") + return default + + def get_all_user_metadata(self) -> Dict[str, Any]: + """ + Get all user metadata as a dictionary + + Returns: + Dictionary with all metadata key-value pairs + """ + with self.db_manager.get_session() as session: + try: + metadata_records = session.query(UserMetadata).all() + + result = {} + for record in metadata_records: + try: + result[record.key] = json.loads(record.value) + except json.JSONDecodeError: + # If JSON parsing fails, store as string + result[record.key] = record.value + + return result + except Exception as e: + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"Error getting all user metadata: {e}") + return {} + + def delete_user_metadata(self, key: str) -> bool: + """ + Delete user metadata by key + + Args: + key: Metadata key to delete + + Returns: + True if successful, False otherwise + """ + with self.db_manager.get_session() as session: + try: + metadata = session.query(UserMetadata).filter( + UserMetadata.key == key + ).first() + + if not metadata: + return False + + session.delete(metadata) + session.commit() + return True + except Exception as e: + session.rollback() + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"Error deleting user metadata: {e}") + return False + + def clear_all_user_metadata(self) -> bool: + """ + Clear all user metadata + + Returns: + True if successful, False otherwise + """ + with self.db_manager.get_session() as session: + try: + session.query(UserMetadata).delete() + session.commit() + console.print("🧹 [green]Cleared all user metadata[/green]") + return True + except Exception as e: + session.rollback() + if os.getenv('DISABLE_TRY_CATCH'): + raise + console.print(f"Error clearing user metadata: {e}") + return False diff --git a/runagent/sdk/rest_client.py b/runagent/sdk/rest_client.py index 8b9a335..594364d 100644 --- a/runagent/sdk/rest_client.py +++ b/runagent/sdk/rest_client.py @@ -20,6 +20,7 @@ ) from runagent.utils.config import Config +from runagent.constants import DEFAULT_TIMEOUT_SECONDS from runagent.utils.agent_id import ( generate_agent_id, generate_agent_fingerprint, @@ -160,8 +161,8 @@ def _request( method=method.upper(), url=url, params=params, - json=data if data and not files else None, - data=None if data and not files else data, + json=data if data is not None and not files else None, + data=None if data is not None and not files else data, headers=request_headers if request_headers else None, files=files, timeout=timeout, @@ -825,7 +826,8 @@ def _start_agent_core(self, agent_id: str, config: Dict = None) -> Dict: payload = config or {} try: - response = self.http.post(f"/agents/{agent_id}/start", data=payload, timeout=60) + # Increased timeout to 5 minutes to allow for background processing + response = self.http.post(f"/agents/{agent_id}/start", data=payload, timeout=300) result = response.json() return self._process_start_result(result, agent_id) @@ -1137,7 +1139,7 @@ def run_agent( entrypoint_tag: str, input_args: list = None, input_kwargs: dict = None, - timeout_seconds: int = 60, + timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS, async_execution: bool = False, ) -> Dict: """Execute an agent with given parameters""" diff --git a/runagent/sdk/socket_client.py b/runagent/sdk/socket_client.py index eadf2b8..8e3b149 100644 --- a/runagent/sdk/socket_client.py +++ b/runagent/sdk/socket_client.py @@ -10,7 +10,10 @@ class SocketClient: - """WebSocket client for agent streaming with both async and sync support""" + """WebSocket client for agent streaming with both async and sync support + + FIXED: Now properly handles cloud deployments with correct WSS URLs + """ def __init__( self, @@ -19,32 +22,75 @@ def __init__( api_prefix: Optional[str] = "/api/v1", is_local: Optional[bool] = True ): - if not base_socket_url: - base_url = Config.get_base_url() - base_url = base_url.lstrip("http://").lstrip("https://") - base_socket_url = f"ws://{base_url}" - self.is_local = is_local - self.base_socket_url = base_socket_url.rstrip("/") + api_prefix self.api_key = api_key or Config.get_api_key() self.serializer = CoreSerializer() + # FIXED: Handle cloud vs local URL construction + if base_socket_url: + # Use provided URL + self.base_socket_url = base_socket_url.rstrip("/") + api_prefix + else: + if is_local: + # Local: Use localhost + base_url = "ws://127.0.0.1:8450" + self.base_socket_url = base_url + api_prefix + else: + # Cloud: Convert HTTP(S) base URL to WS(S) + base_url = Config.get_base_url() + + # Convert https:// to wss:// or http:// to ws:// + if base_url.startswith("https://"): + ws_base = base_url.replace("https://", "wss://") + elif base_url.startswith("http://"): + ws_base = base_url.replace("http://", "ws://") + else: + # No protocol, assume secure for cloud + ws_base = f"wss://{base_url}" + + self.base_socket_url = ws_base.rstrip("/") + api_prefix + + print(f"[DEBUG] SocketClient initialized:") + print(f" - is_local: {self.is_local}") + print(f" - base_socket_url: {self.base_socket_url}") + print(f" - api_key: {'SET' if self.api_key else 'NOT SET'}") + async def run_stream_async(self, agent_id: str, entrypoint_tag: str, *input_args, **input_kwargs) -> AsyncIterator[Any]: """Stream agent execution results (async version)""" - uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream?token={self.api_key}" - # if not self.is_local: - # uri = f"{uri}?token={self.api_key}" + # FIXED: Build proper cloud URL with query param auth + if self.is_local: + uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream" + else: + # Cloud: Add token as query parameter + uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream?token={self.api_key}" + + # print(f"[DEBUG] Connecting to: {uri}") + + # FIXED: Add proper headers for cloud authentication + extra_headers = {} + if not self.is_local and self.api_key: + extra_headers["Authorization"] = f"Bearer {self.api_key}" - async with websockets.connect(uri) as websocket: + async with websockets.connect( + uri, + extra_headers=extra_headers if extra_headers else None, + ping_interval=20, + ping_timeout=60, + close_timeout=10, + max_size=10 * 1024 * 1024 + ) as websocket: # Send start stream request in the exact format required request_data = { "entrypoint_tag": entrypoint_tag, - "input_args": input_args, - "input_kwargs": input_kwargs, - "timeout_seconds": 60, + "input_args": list(input_args), # Ensure JSON serializable + "input_kwargs": dict(input_kwargs), # Ensure JSON serializable + "timeout_seconds": 600, "async_execution": False } + + print(f"[DEBUG] Sending request: {request_data}") + # Send the request as direct JSON await websocket.send(json.dumps(request_data)) @@ -53,18 +99,22 @@ async def run_stream_async(self, agent_id: str, entrypoint_tag: str, *input_args try: message = json.loads(raw_message) except json.JSONDecodeError: - continue # Skip invalid messages + print(f"[WARN] Invalid JSON message: {raw_message}") + continue message_type = message.get("type") if message_type == "error": - raise Exception(f"Stream error: {message.get('error')}") + error_msg = message.get('error') or message.get('detail', 'Unknown error') + raise Exception(f"Stream error: {error_msg}") elif message_type == "status": status = message.get("status") if status == "stream_completed": + print("[DEBUG] Stream completed") break elif status == "stream_started": - continue # Skip status messages + print("[DEBUG] Stream started") + continue elif message_type == "data": # Yield the actual chunk data yield message.get("content") @@ -73,19 +123,42 @@ def run_stream(self, agent_id: str, entrypoint_tag: str, input_args, input_kwarg """Stream agent execution results (sync version)""" from websockets.sync.client import connect - uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream?token={self.api_key}" + # FIXED: Build proper cloud URL with query param auth + if self.is_local: + uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream" + else: + # Cloud: Add token as query parameter + uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream?token={self.api_key}" + + # print(f"[DEBUG] Connecting to: {uri}") - with connect(uri) as websocket: + # FIXED: Add proper headers for cloud authentication + extra_headers = {} + if not self.is_local and self.api_key: + extra_headers["Authorization"] = f"Bearer {self.api_key}" + + # Add proper timeout and keepalive settings + with connect( + uri, + additional_headers=extra_headers if extra_headers else None, + ping_interval=20, # Send ping every 20 seconds + ping_timeout=60, # Wait up to 60 seconds for pong + close_timeout=10, # Timeout for closing handshake + max_size=10 * 1024 * 1024, # 10MB max message size + open_timeout=30 # FIXED: Add connection timeout + ) as websocket: # Send start stream request in the exact format required request_data = { "entrypoint_tag": entrypoint_tag, - "input_args": input_args, - "input_kwargs": input_kwargs, - "timeout_seconds": 60, + "input_args": list(input_args) if input_args else [], + "input_kwargs": dict(input_kwargs) if input_kwargs else {}, + "timeout_seconds": 600, "async_execution": False } + print(f"[DEBUG] Sending request: {request_data}") + # Send the request as direct JSON websocket.send(json.dumps(request_data)) @@ -94,18 +167,22 @@ def run_stream(self, agent_id: str, entrypoint_tag: str, input_args, input_kwarg try: message = json.loads(raw_message) except json.JSONDecodeError: - continue # Skip invalid messages + print(f"[WARN] Invalid JSON message: {raw_message}") + continue message_type = message.get("type") if message_type == "error": - raise Exception(f"Stream error: {message.get('error')}") + error_msg = message.get('error') or message.get('detail', 'Unknown error') + raise Exception(f"Stream error: {error_msg}") elif message_type == "status": status = message.get("status") if status == "stream_completed": + print("[DEBUG] Stream completed") break elif status == "stream_started": - continue # Skip status messages + print("[DEBUG] Stream started") + continue elif message_type == "data": # Yield the actual chunk data - yield message.get("content") + yield message.get("content") \ No newline at end of file diff --git a/runagent/sdk/template_downloader.py b/runagent/sdk/template_downloader.py index 3947ddc..cbed3b0 100644 --- a/runagent/sdk/template_downloader.py +++ b/runagent/sdk/template_downloader.py @@ -5,6 +5,7 @@ import tempfile import typing as t from pathlib import Path +import requests import git from git import Repo @@ -29,7 +30,202 @@ def __init__(self, repo_url: str, branch: str = "main"): ''' self.repo_url = repo_url self.branch = branch - + + # Extract GitHub owner/repo for API usage + self.github_token = os.getenv("GITHUB_TOKEN") + if "github.com" in self.repo_url: + parts = self.repo_url.replace(".git", "").split("/") + self.github_owner = parts[-2] + self.github_repo = parts[-1] + self.use_github_api = True + else: + self.use_github_api = False + + def _github_api_get(self, path: str) -> dict: + """ + Make a GET request to GitHub API + + Args: + path: API path (e.g., "repos/owner/repo/contents/path") + + Returns: + JSON response + """ + url = f"https://api.github.com/{path}" + headers = {"Accept": "application/vnd.github.v3+json"} + + if self.github_token: + headers["Authorization"] = f"token {self.github_token}" + + params = {"ref": self.branch} + response = requests.get(url, headers=headers, params=params, timeout=10) + + if response.status_code == 200: + return response.json() + elif response.status_code == 403 and "rate limit" in response.text.lower(): + raise TemplateDownloadError( + "GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable for higher limits." + ) + else: + raise TemplateDownloadError( + f"GitHub API request failed: {response.status_code} - {response.text}" + ) + + def _download_github_folder_api(self, folder_path: str, local_dir: Path) -> None: + """ + Download a folder from GitHub using API (much faster than git clone) + + Args: + folder_path: Path to folder in repo (e.g., "templates/letta/default") + local_dir: Local directory to save files + """ + api_path = f"repos/{self.github_owner}/{self.github_repo}/contents/{folder_path}" + + try: + contents = self._github_api_get(api_path) + except TemplateDownloadError: + # Fall back to git clone if API fails + raise + + # Create local directory + local_dir.mkdir(parents=True, exist_ok=True) + + for item in contents: + if item['type'] == 'file': + # Download file directly + file_response = requests.get(item['download_url'], timeout=30) + + if file_response.status_code == 200: + local_file_path = local_dir / item['name'] + local_file_path.write_bytes(file_response.content) + else: + raise TemplateDownloadError(f"Failed to download file: {item['name']}") + + elif item['type'] == 'dir': + # Recursively download subdirectory + sub_folder = local_dir / item['name'] + self._download_github_folder_api(item['path'], sub_folder) + + def _list_github_folder_api(self, folder_path: str) -> list: + """ + List contents of a folder on GitHub using API (instant vs cloning) + + Args: + folder_path: Path to folder in repo + + Returns: + List of items in the folder + """ + api_path = f"repos/{self.github_owner}/{self.github_repo}/contents/{folder_path}" + return self._github_api_get(api_path) + + def _list_templates_via_api(self, prepath: str, framework_filter: str = None, debug_enabled: bool = False) -> t.Dict[str, t.List[str]]: + """ + List templates using GitHub API (much faster than git clone!) + + Args: + prepath: Pre-path before framework directory + framework_filter: Optional specific framework to scan + debug_enabled: Whether to log debug information + + Returns: + Dictionary mapping framework names to list of template names + """ + import time + import logging + logger = logging.getLogger(__name__) + + start_time = time.time() + if debug_enabled: + logger.info(f"[PERF] Using GitHub API to list templates") + + templates = {} + + # If framework filter specified, only scan that framework + if framework_filter: + framework_path = f"{prepath}/{framework_filter}" if prepath else framework_filter + try: + if debug_enabled: + logger.info(f"[PERF] Fetching templates for {framework_filter} via API") + + items = self._list_github_folder_api(framework_path) + templates[framework_filter] = [] + + for item in items: + if item['type'] == 'dir' and not item['name'].startswith('.'): + # Check if it has a config file (validate it's a template) + template_name = item['name'] + try: + config_check_path = f"{framework_path}/{template_name}" + template_contents = self._list_github_folder_api(config_check_path) + + # Check if runagent.config.json exists + has_config = any( + f['name'] in ['runagent.config.json', 'runagent.config.yaml', 'runagent.config.yml'] + for f in template_contents + ) + + if has_config: + templates[framework_filter].append(template_name) + if debug_enabled: + logger.debug(f"[PERF] ✓ {template_name} valid") + except Exception as e: + if debug_enabled: + logger.debug(f"[PERF] ✗ {template_name} skipped: {e}") + + api_time = time.time() - start_time + if debug_enabled: + logger.info(f"[PERF] API listing completed in {api_time:.2f}s - found {len(templates[framework_filter])} templates") + + return templates + + except Exception as e: + raise TemplateDownloadError(f"Failed to list templates via API: {e}") + + # Scan all frameworks + try: + if debug_enabled: + logger.info(f"[PERF] Fetching all frameworks via API") + + framework_items = self._list_github_folder_api(prepath if prepath else "") + + for framework_item in framework_items: + if framework_item['type'] == 'dir' and not framework_item['name'].startswith('.'): + framework_name = framework_item['name'] + templates[framework_name] = [] + + # List templates in this framework + try: + framework_path = f"{prepath}/{framework_name}" if prepath else framework_name + template_items = self._list_github_folder_api(framework_path) + + for template_item in template_items: + if template_item['type'] == 'dir' and not template_item['name'].startswith('.'): + template_name = template_item['name'] + try: + # Quick validation: check if config exists + template_contents = self._list_github_folder_api(f"{framework_path}/{template_name}") + has_config = any( + f['name'] in ['runagent.config.json', 'runagent.config.yaml', 'runagent.config.yml'] + for f in template_contents + ) + + if has_config: + templates[framework_name].append(template_name) + except Exception: + pass # Skip invalid templates + except Exception: + pass # Skip frameworks with errors + + api_time = time.time() - start_time + if debug_enabled: + logger.info(f"[PERF] API listing completed in {api_time:.2f}s") + + return templates + + except Exception as e: + raise TemplateDownloadError(f"Failed to list all templates via API: {e}") + def download_template( self, prepath: str, framework: str, template: str, target_folder: str ) -> None: @@ -51,7 +247,18 @@ def download_template( target_dir = Path(target_folder) target_dir.mkdir(parents=True, exist_ok=True) - # Use temporary directory for sparse checkout + # Use GitHub API if available (much faster!) + if self.use_github_api: + try: + self._download_github_folder_api(template_path, target_dir) + return + except Exception as e: + # Fall back to git clone if API fails + import logging + logger = logging.getLogger(__name__) + logger.warning(f"GitHub API download failed, falling back to git clone: {e}") + + # Fallback: Use git clone with sparse checkout with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) @@ -133,12 +340,52 @@ def _copy_directory_contents(self, source_dir: Path, target_dir: Path) -> None: # Copy file shutil.copy2(item, target_item) - def list_available_templates(self, prepath: str) -> t.Dict[str, t.List[str]]: + def _scan_framework_templates(self, framework_dir: Path, template_list: list, debug_enabled: bool = False): + """ + Helper to scan templates in a framework directory. + + Args: + framework_dir: Path to framework directory + template_list: List to append valid template names to + debug_enabled: Whether to log debug information + """ + import time + import logging + logger = logging.getLogger(__name__) + + for template_dir in framework_dir.iterdir(): + if template_dir.is_dir() and not template_dir.name.startswith("."): + # Verify this is a valid template + # Skip templates that fail validation (e.g., test directories without config) + template_name = template_dir.name + try: + validate_start = time.time() + if debug_enabled: + logger.debug(f"[PERF] Validating template: {template_name}") + is_valid, _ = validate_agent(template_dir) + validate_time = time.time() - validate_start + + if is_valid: + if debug_enabled: + logger.debug(f"[PERF] ✓ {template_name} valid ({validate_time:.3f}s)") + template_list.append(template_name) + else: + if debug_enabled: + logger.debug(f"[PERF] ✗ {template_name} invalid ({validate_time:.3f}s)") + except Exception as e: + if debug_enabled: + validate_time = time.time() - validate_start + logger.debug(f"[PERF] ✗ {template_name} error ({validate_time:.3f}s): {e}") + # Skip invalid templates silently (e.g., test dirs, incomplete templates) + pass + + def list_available_templates(self, prepath: str, framework_filter: str = None) -> t.Dict[str, t.List[str]]: """ List all available templates in the repository Args: prepath: Pre-path before framework directory + framework_filter: Optional specific framework to scan (much faster) Returns: Dictionary mapping framework names to list of template names @@ -146,17 +393,45 @@ def list_available_templates(self, prepath: str) -> t.Dict[str, t.List[str]]: Raises: TemplateDownloadError: If listing fails """ + import time + import logging + import os + logger = logging.getLogger(__name__) + + # Only show debug info if explicitly enabled + debug_enabled = os.getenv('RUNAGENT_DEBUG') == '1' + + # Use GitHub API if available (much faster!) + if self.use_github_api: + try: + return self._list_templates_via_api(prepath, framework_filter, debug_enabled) + except TemplateDownloadError as e: + if debug_enabled: + logger.warning(f"[PERF] GitHub API failed, falling back to git clone: {e}") + # Fall through to git clone method + + # Fallback: Use git clone method with tempfile.TemporaryDirectory(dir="/tmp") as temp_dir: temp_path = Path(temp_dir) try: # Shallow clone for listing + start_time = time.time() + if debug_enabled: + logger.info(f"[PERF] Starting git clone from {self.repo_url}") + repo = Repo.clone_from( self.repo_url, temp_path, branch=self.branch, depth=1 ) + + clone_time = time.time() - start_time + if debug_enabled: + logger.info(f"[PERF] Git clone completed in {clone_time:.2f}s") + templates = {} # Navigate to the prepath directory + scan_start = time.time() prepath_dir = temp_path / prepath if prepath else temp_path if not prepath_dir.exists(): @@ -164,29 +439,42 @@ def list_available_templates(self, prepath: str) -> t.Dict[str, t.List[str]]: f"Pre-path '{prepath}' not found in repository branch '{self.branch}'" ) - # Scan for framework directories + # If framework filter specified, only scan that framework's directory + if framework_filter: + if debug_enabled: + logger.info(f"[PERF] Scanning framework: {framework_filter}") + framework_dir = prepath_dir / framework_filter + if framework_dir.exists() and framework_dir.is_dir(): + templates[framework_filter] = [] + fw_start = time.time() + self._scan_framework_templates(framework_dir, templates[framework_filter], debug_enabled) + fw_time = time.time() - fw_start + if debug_enabled: + logger.info(f"[PERF] Scanned {framework_filter} in {fw_time:.2f}s - found {len(templates[framework_filter])} templates") + return templates + + # Scan all framework directories for framework_dir in prepath_dir.iterdir(): - if framework_dir.is_dir() and not framework_dir.name.startswith( - "." - ): + if framework_dir.is_dir() and not framework_dir.name.startswith("."): framework_name = framework_dir.name + if debug_enabled: + logger.info(f"[PERF] Scanning framework: {framework_name}") templates[framework_name] = [] - - # Scan for template directories - for template_dir in framework_dir.iterdir(): - if ( - template_dir.is_dir() - and not template_dir.name.startswith(".") - ): - # Verify this is a valid template (has main.py) - if validate_agent(template_dir): - templates[framework_name].append(template_dir.name) - + fw_start = time.time() + self._scan_framework_templates(framework_dir, templates[framework_name], debug_enabled) + fw_time = time.time() - fw_start + if debug_enabled: + logger.info(f"[PERF] Scanned {framework_name} in {fw_time:.2f}s - found {len(templates[framework_name])} templates") + + scan_time = time.time() - scan_start + if debug_enabled: + logger.info(f"[PERF] Total scanning time: {scan_time:.2f}s") return templates except git.exc.GitCommandError as e: if os.getenv('DISABLE_TRY_CATCH'): raise + raise TemplateDownloadError(f"Git error while listing templates: {e}") except Exception as e: if os.getenv('DISABLE_TRY_CATCH'): diff --git a/runagent/sdk/template_manager.py b/runagent/sdk/template_manager.py index 07d8241..fae8960 100644 --- a/runagent/sdk/template_manager.py +++ b/runagent/sdk/template_manager.py @@ -58,13 +58,11 @@ def list_available( Dictionary mapping framework names to template lists """ try: - templates = self.downloader.list_available_templates(TEMPLATE_PREPATH) - - if framework_filter: - if framework_filter in templates: - return {framework_filter: templates[framework_filter]} - else: - return {} + # Pass framework_filter to downloader for faster scanning + templates = self.downloader.list_available_templates( + TEMPLATE_PREPATH, + framework_filter=framework_filter + ) return templates except Exception as e: @@ -111,8 +109,8 @@ def init_template( ValidationError: If template is invalid FileExistsError: If folder exists and overwrite is False """ - # Validate template exists - available_templates = self.list_available() + # Validate template exists - only fetch for this specific framework + available_templates = self.list_available(framework_filter=framework) if framework not in available_templates: raise ValidationError( diff --git a/runagent/utils/config.py b/runagent/utils/config.py index 7ebb41b..192bdc9 100644 --- a/runagent/utils/config.py +++ b/runagent/utils/config.py @@ -10,7 +10,6 @@ ENV_RUNAGENT_API_KEY, ENV_RUNAGENT_BASE_URL, LOCAL_CACHE_DIRECTORY, - USER_DATA_FILE_NAME, ) @@ -69,27 +68,54 @@ def get_config(project_dir: str) -> t.Optional[t.Dict[str, t.Any]]: @staticmethod def get_user_config() -> t.Dict[str, t.Any]: """ - Get user configuration from {ENV_LOCAL_CACHE_DIRECTORY}/config.json + Get user configuration from database (user_metadata table) Returns: User configuration content """ - config_dir = Path.home() / LOCAL_CACHE_DIRECTORY - config_file = config_dir / USER_DATA_FILE_NAME - - if not config_file.exists(): - return {} - try: - with config_file.open("r") as f: - return json.load(f) + from runagent.sdk.db import DBService + db_service = DBService() + + # Get from database + metadata = db_service.get_all_user_metadata() + + # One-time migration: Check for old JSON file only if database is empty + if not metadata: + config_dir = Path.home() / LOCAL_CACHE_DIRECTORY + config_file = config_dir / "user_data.json" # Legacy file + + if config_file.exists(): + try: + with config_file.open("r") as f: + json_config = json.load(f) + + # Migrate to database + if json_config: + for key, value in json_config.items(): + db_service.set_user_metadata(key, value) + + # Backup and remove old JSON file + backup_file = config_file.with_suffix('.json.backup') + config_file.rename(backup_file) + + return json_config + except Exception: + if os.getenv('DISABLE_TRY_CATCH'): + raise + pass + + return metadata or {} except Exception: + if os.getenv('DISABLE_TRY_CATCH'): + raise + # If database fails, return empty (no fallback) return {} @staticmethod def set_user_config(key: str, value: t.Any) -> bool: """ - Set a value in the user configuration + Set a value in the user configuration (database-backed) Args: key: Configuration key @@ -98,31 +124,10 @@ def set_user_config(key: str, value: t.Any) -> bool: Returns: True if successful, False otherwise """ - config_dir = Path.home() / LOCAL_CACHE_DIRECTORY - config_dir.mkdir(exist_ok=True) - - config_file = config_dir / USER_DATA_FILE_NAME - - # Get existing config or create new - if config_file.exists(): - try: - with config_file.open("r") as f: - config = json.load(f) - except Exception: - if os.getenv('DISABLE_TRY_CATCH'): - raise - config = {} - else: - config = {} - - # Update config - config[key] = value - - # Write config try: - with config_file.open("w") as f: - json.dump(config, f, indent=2) - return True + from runagent.sdk.db import DBService + db_service = DBService() + return db_service.set_user_metadata(key, value) except Exception: if os.getenv('DISABLE_TRY_CATCH'): raise @@ -235,23 +240,18 @@ def get_deployment_info(agent_id: str) -> t.Optional[t.Dict[str, t.Any]]: with info_file.open("r") as f: return json.load(f) - # Add these methods to your Config class - @staticmethod def clear_user_config() -> bool: """ - Clear all user configuration + Clear all user configuration from database Returns: True if successful, False otherwise """ - config_dir = Path.home() / LOCAL_CACHE_DIRECTORY - config_file = config_dir / USER_DATA_FILE_NAME - try: - if config_file.exists(): - config_file.unlink() - return True + from runagent.sdk.db import DBService + db_service = DBService() + return db_service.clear_all_user_metadata() except Exception: if os.getenv('DISABLE_TRY_CATCH'): raise @@ -283,9 +283,6 @@ def get_config_status() -> t.Dict[str, t.Any]: "base_url": Config.get_base_url(), "user_email": config.get("email"), "user_name": config.get("name"), - "config_file_exists": ( - Path.home() / LOCAL_CACHE_DIRECTORY / USER_DATA_FILE_NAME - ).exists(), } @staticmethod diff --git a/templates/agno/default/simple_assistant.py b/templates/agno/default/simple_assistant.py index 6052041..10f29c5 100644 --- a/templates/agno/default/simple_assistant.py +++ b/templates/agno/default/simple_assistant.py @@ -18,9 +18,7 @@ def agent_print_response(prompt: str): response = agent.run(prompt) # Return structured data that can be serialized - return { - "content": response.content if hasattr(response, 'content') else str(response), - } + return response def agent_print_response_stream(prompt: str): """Streaming response that yields serializable chunks""" diff --git a/test_scripts/python/client_test_agno.py b/test_scripts/python/client_test_agno.py index 3a409af..0c8b9d9 100644 --- a/test_scripts/python/client_test_agno.py +++ b/test_scripts/python/client_test_agno.py @@ -1,9 +1,9 @@ # from runagent import RunAgentClient # ra = RunAgentClient( -# agent_id="27f68f00-e8cd-4965-9b91-fac501e132e3", +# agent_id="71b31b58-c2d6-49ab-b564-d72b1a449df7", # entrypoint_tag="agno_print_response", -# local=True +# local=False # ) @@ -17,9 +17,9 @@ from runagent import RunAgentClient ra = RunAgentClient( - agent_id="27f68f00-e8cd-4965-9b91-fac501e132e3", + agent_id="c12d3486-cbf6-4d3a-b58b-e5a9c0dfe311", entrypoint_tag="agno_print_response_stream", - local=True + local=False ) for chunk in ra.run( diff --git a/test_scripts/rust/test_agno/Cargo.toml b/test_scripts/rust/test_agno/Cargo.toml index d7d2e8a..5cf216a 100644 --- a/test_scripts/rust/test_agno/Cargo.toml +++ b/test_scripts/rust/test_agno/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] # Assuming you're using the runagent Rust SDK -runagent = { path = "/home/riamdriad5/runagent/runagent/runagent-rust/runagent" } +runagent = { path = "/home/azureuser/runagent/runagent-rust/runagent" } # Required dependencies tokio = { version = "1.0", features = ["full"] } diff --git a/test_scripts/rust/test_agno/src/main.rs b/test_scripts/rust/test_agno/src/main.rs index 48b636b..3231c1c 100644 --- a/test_scripts/rust/test_agno/src/main.rs +++ b/test_scripts/rust/test_agno/src/main.rs @@ -5,8 +5,7 @@ use serde_json::json; async fn main() -> Result<(), Box> { println!("🧪 Testing agno Agent with Rust SDK"); - // Replace with the actual agent ID from `runagent serve` - let agent_id = "aacf274a-32b8-497d-85a4-aa8597686c40"; + let agent_id = "af662135-5c00-4a89-b947-300e34787f03"; // Test: Non-streaming execution println!("\n🚀 Testing Non-Streaming Execution"); @@ -16,7 +15,7 @@ async fn main() -> Result<(), Box> { let client = RunAgentClient::new( agent_id, "agno_print_response", - true, // local = true + false, // local = true // Some("127.0.0.1"), // Some(8452) // Use the port from your server output ).await?; @@ -24,12 +23,12 @@ async fn main() -> Result<(), Box> { // println!("🔗 Connected to agent at 127.0.0.1:8452"); let response = client.run_with_args( - &[json!("Write a report on NVDA")], // positional args + &[json!("Write small paragraph on breaking bad tv series")], // positional args &[] // no keyword args ).await?; println!("✅ Response received:"); - println!("{}", serde_json::to_string_pretty(&response)?); + println!("{}",(&response)); println!("\n✅ Test completed successfully!"); @@ -40,30 +39,30 @@ async fn main() -> Result<(), Box> { -use runagent::client::RunAgentClient; -use serde_json::json; -use futures::StreamExt; +// use runagent::client::RunAgentClient; +// use serde_json::json; +// use futures::StreamExt; -#[tokio::main] -async fn main() -> Result<(), Box> { - let agent_id = "d31336a7-7c02-43cb-a906-6019b06a1249"; +// #[tokio::main] +// async fn main() -> Result<(), Box> { +// let agent_id = "af662135-5c00-4a89-b947-300e34787f03"; - println!("🌊 ag2 Streaming Test"); - let client = RunAgentClient::new(agent_id, "agno_print_response_stream", true).await?; +// println!("🌊 ag2 Streaming Test"); +// let client = RunAgentClient::new(agent_id, "agno_print_response_stream", false).await?; - let mut stream = client.run_stream(&[ - ("prompt", json!("Tell me about solar system")) - ]).await?; +// let mut stream = client.run_stream(&[ +// ("prompt", json!("Tell me about solar system")) +// ]).await?; - while let Some(chunk_result) = stream.next().await { - match chunk_result { - Ok(chunk) => println!("{}", chunk), - Err(e) => { - println!("Error: {}", e); - break; - } - } - } +// while let Some(chunk_result) = stream.next().await { +// match chunk_result { +// Ok(chunk) => println!("{}", chunk), +// Err(e) => { +// println!("Error: {}", e); +// break; +// } +// } +// } - Ok(()) -} \ No newline at end of file +// Ok(()) +// } \ No newline at end of file