From 74fdd59a82d81a291abf1b747b9304e20247cf02 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 4 Feb 2026 10:35:00 +0530 Subject: [PATCH 01/68] Update AutoCode.py --- AutoCode.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AutoCode.py b/AutoCode.py index 2793f53..e99b2eb 100644 --- a/AutoCode.py +++ b/AutoCode.py @@ -10,17 +10,17 @@ user_actual_query = input("Describe the task you want to automate with NPM AutoCode AI:") llm = Ollama( - model="codeLLaMA-Instruct 7b", + model="codeLLaMA-Instruct:7b", temperature=1 ) -parser=StrOutputParser() +response=llm.invoke(prompt.format(input=user_actual_query)) -chain=prompt | llm | parser -response=chain.invoke(prompt.format(input=user_actual_query)) -print(response) +parser=StrOutputParser() +final_response=parser.invoke(response) +print(final_response) -cleaned_response = response.strip() +cleaned_response = final_response.strip() if cleaned_response.startswith('```python'): cleaned_response = cleaned_response[len('```python'):] if cleaned_response.endswith('```'): From ba489512f813dec0407d3099553bb0737625bcb0 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Fri, 6 Feb 2026 11:07:24 +0530 Subject: [PATCH 02/68] Update README.md --- README.md | 80 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index ca4c925..d9ef14d 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,54 @@ -# NPM-AutoCode-AI -NPM AutoCode AI +# NPM AutoCode AI -Install app by clicking on this link:- https://github.com/sonuramashishnpm/NPM-AutoCode-AI/releases/download/v1.0/NPM_AutoCode_AI.zip +## ๐Ÿš€ Quick Start +Install the app: +[Download Latest Release](https://github.com/sonuramashishnpm/NPM-AutoCode-AI/releases/download/v2.0/NPM_AutoCode_AI.zip) -๐Ÿš€ Project Overview +1. Unzip the file. +2. Run `NPM_AutoCode_AI.exe` (Windows) or `python main.py` (Python 3.12+). +3. Enter your task in the input box (e.g., "Plot a sine wave"). +4. Click **Generate & Execute**. +5. Watch logs and progress bar โ€“ AI generates, checks safety, executes, and fixes errors if needed. -NPM AutoCode AI is a Python desktop application that allows users to automatically generate and execute Python code using AI. It integrates NPMAI, a custom LLM ecosystem, to understand natural language task descriptions and return clean, ready-to-run Python scripts. +**Note:** Requires Ollama with models `codellama:7b-instruct` and `qwen2.5-coder:7b`. Run `ollama pull` for them. -This project demonstrates agentic AI automation, seamless integration of LLMs, and GUI-based Python automation for end-users. +## ๐Ÿ“– Project Overview +NPM AutoCode AI is a Python desktop app for automatic code generation and execution using AI. Describe tasks in plain English, and it uses NPMAI (custom LLM tools) to create, validate, and run Python scripts safely. -๐Ÿง  Features +Built for non-technical users โ€“ turns ideas into working code without manual editing. -Natural Language Code Generation: Describe your task in plain English and let AI generate Python code. +## โœจ Features +- **Natural Language to Code**: AI generates Python scripts from your description. +- **Safety Check**: Scans code for risks (e.g., file deletions, remote access) before running. +- **Auto-Debug**: If errors happen, AI fixes and retries using error logs. +- **Live Logs & Progress**: See real-time updates in GUI. +- **Dependency Handling**: Installs libraries via `subprocess` in code. +- **Isolated Execution**: Runs in safe namespace to protect your system. +- **Memory Chains**: Remembers task history for better fixes. -Safe Code Execution: Runs generated code in an isolated local environment. +## ๐Ÿ”„ How It Works +1. Enter task โ†’ AI (`codellama:7b-instruct`) generates code via NPMAI Ollama. +2. Safety AI (`qwen2.5-coder:7b`) reviews: If risky, stops with warning. +3. Execute code in thread โ†’ If error, feed back to AI for fix โ†’ Retry loop. +4. Success: Logs "Task Completed Successfully". -Real-Time Logs: View step-by-step updates in the GUI while code is generated and executed. +All in background (QThread) so UI stays responsive. Uses LangChain for prompts. -Progress Feedback: Visual progress bar for task status. +## ๐Ÿ› ๏ธ Tech Details +- **Language**: Python 3.12+ +- **GUI**: PySide6 (QWidget, QThread, etc.) +- **AI**: npmai.Ollama + Memory; LangChain Core for prompts/parsers. +- **Execution**: `exec()` in custom globals dict with error capture. -Custom AI Backend: Uses npmai.Ollama to call your Render + Colab hosted LLMs โ€” no local LLM setup required. +## ๐Ÿ‘จโ€๐Ÿ’ป Developer +**Sonu Kumar Ramashish** (a.k.a. Bihar Viral Boy) +- Age: 14 | Student | TEDx Speaker | AI & Software Developer | DevOps Enthusiast +- Reach: 410K+ Facebook followers +- Location: Kota, Rajasthan -๐Ÿ› ๏ธ Technical Details +Part of NPMAI ecosystem for AI automation tools. -Language: Python 3.12+ +## ๐Ÿค Contributing +Fork, add features (e.g., more models), and PR. License: MIT. -GUI Framework: PySide6 - -AI/LLM Integration: NPMAI ecosystem (Ollama wrapper) - -Prompt Handling: LangChain Core (PromptTemplate, StrOutputParser) - -Concurrency: QThread for non-blocking code generation & execution - -Safe Execution: exec in isolated local namespace with real-time logs - -โšก Key Highlights - -Designed and built by Sonu Kumar Ramashish, a 14-year-old AI & automation prodigy. - -Part of the NPMAI ecosystem, which includes AI-driven web apps and automation tools. - -Demonstrates full-stack AI integration: LLMs, prompt engineering, GUI, and Python execution. - -Highly modular: CodeWorker can be reused for other AI code-generation tasks. - -Developer:- -Author: Sonu Kumar Ramashish (a.k.a. โ€œBihar Viral Boyโ€) -Age: 14 | Student | TEDx Speaker | Tech & AI Enthusiast -Location: Bihar / Kota, India -Public Reach: 398K+ Facebook followers +Star if useful! ๐Ÿš€ From 6ea16427d9f0e160b83c9f837a6ef55da04a64be Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Fri, 13 Feb 2026 14:48:15 +0530 Subject: [PATCH 03/68] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d9ef14d..fbe50c7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ Install the app: **Note:** Requires Ollama with models `codellama:7b-instruct` and `qwen2.5-coder:7b`. Run `ollama pull` for them. +## Workflow:- + +![Workflow](https://ibb.co/nMSPNJ5h) + ## ๐Ÿ“– Project Overview NPM AutoCode AI is a Python desktop app for automatic code generation and execution using AI. Describe tasks in plain English, and it uses NPMAI (custom LLM tools) to create, validate, and run Python scripts safely. From dd2d30b3741fcbbaea191f93c5d699fb3d4ade01 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Fri, 13 Feb 2026 14:49:25 +0530 Subject: [PATCH 04/68] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fbe50c7..fbf3ccf 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ Install the app: ## Workflow:- -![Workflow](https://ibb.co/nMSPNJ5h) +Example Screenshot + ## ๐Ÿ“– Project Overview NPM AutoCode AI is a Python desktop app for automatic code generation and execution using AI. Describe tasks in plain English, and it uses NPMAI (custom LLM tools) to create, validate, and run Python scripts safely. From a7946e41ee7d5c996d779ef8e593f1a81380ef65 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Fri, 13 Feb 2026 14:50:27 +0530 Subject: [PATCH 05/68] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbf3ccf..aa7a351 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Install the app: ## Workflow:- -Example Screenshot +Example Screenshot ## ๐Ÿ“– Project Overview From a4a723f70a4a0915594ee803023b2ad6c6ffb0ff Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Fri, 13 Feb 2026 14:51:00 +0530 Subject: [PATCH 06/68] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa7a351..f862366 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Install the app: ## Workflow:- -Example Screenshot +Example Screenshot ## ๐Ÿ“– Project Overview From 3e5b890674429074a51a542437f8bc5c018c3c7b Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Fri, 13 Feb 2026 16:44:55 +0530 Subject: [PATCH 07/68] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f862366..ae924c0 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Install the app: ## Workflow:- -Example Screenshot +Example Screenshot ## ๐Ÿ“– Project Overview From 5fbeabbdd63af22a66033fcaa5702f23c56aea61 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:41:00 +0530 Subject: [PATCH 08/68] Delete AutoCode.py --- AutoCode.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 AutoCode.py diff --git a/AutoCode.py b/AutoCode.py deleted file mode 100644 index e99b2eb..0000000 --- a/AutoCode.py +++ /dev/null @@ -1,29 +0,0 @@ -from langchain_core.prompts import PromptTemplate -from npmai import Ollama -from langchain_core.output_parsers import StrOutputParser - -prompt = PromptTemplate( - input_variables=["input"], - template="Hey you are helpfull code assistant that write code just write code nothing else of it and maintain proper indentation and no hectics just imagine you will give the code and we will execute it, you will be asked to generate code aboout a query generate code,this is the query:{input}" -) - -user_actual_query = input("Describe the task you want to automate with NPM AutoCode AI:") - -llm = Ollama( - model="codeLLaMA-Instruct:7b", - temperature=1 -) - -response=llm.invoke(prompt.format(input=user_actual_query)) - -parser=StrOutputParser() -final_response=parser.invoke(response) -print(final_response) - -cleaned_response = final_response.strip() -if cleaned_response.startswith('```python'): - cleaned_response = cleaned_response[len('```python'):] -if cleaned_response.endswith('```'): - cleaned_response = cleaned_response[:-len('```')] - -exec(cleaned_response.strip()) From a3ab3da1e03e0f6e3bc29571d7e846e1bf08b5c9 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:42:35 +0530 Subject: [PATCH 09/68] Create NPM-AutoCode-AI.py --- Desktop_App/NPM-AutoCode-AI.py | 224 +++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 Desktop_App/NPM-AutoCode-AI.py diff --git a/Desktop_App/NPM-AutoCode-AI.py b/Desktop_App/NPM-AutoCode-AI.py new file mode 100644 index 0000000..4b93b14 --- /dev/null +++ b/Desktop_App/NPM-AutoCode-AI.py @@ -0,0 +1,224 @@ +import sys +from PySide6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QLabel, + QTextEdit, QLineEdit, QPushButton, QProgressBar +) +from PySide6.QtCore import QThread, Signal +from langchain_core.prompts import PromptTemplate +from npmai import Ollama,Memory +from langchain_core.output_parsers import StrOutputParser +import traceback +import subprocess +import os + +# ======================= +# WORKER THREAD +# ======================= +class CodeWorker(QThread): + log = Signal(str) + finished = Signal(str) + + def __init__(self, task_text): + super().__init__() + self.task_text = task_text + self.code_var= {} + + def executor(self,code): + error_log="" + try: + exec(code,self.code_var) + return None + except Exception as e: + error_log+= traceback.format_exc() + return error_log + + def run(self): + self.log.emit("npmai is doing your requested task") + memory=Memory("coder0-generator") + memory1=Memory("safety_checker") + + llm=Ollama( + model="codellama:7b-instruct", + temperature=0.3 + ) + + query=self.task_text + + prompt = f""" + Hey you are helpful code assistant that writes code just write code nothing else + and maintain proper indentation. No extra explanations. + remember that whatever imports you are using install dependencies in code using subprocess beacuase user had not installed your code requirements + Do not respond with any insturction or any other statement if you are giving any statement in english or that is not of code so give in # comment ok beacuase your code will be executed through exec() function of python so give such format code that can be executed in exec() function YOUR WHOLE RESPONSE WILL BE SENT TO exec() so do not write anything except code. + You will be asked to generate code about a task. + This is the task:{query}""" + + response=llm.invoke(prompt) + + parser=StrOutputParser() + final_response=parser.parse(response) + + cleaned_response = final_response.strip() + if cleaned_response.startswith("```python"): + cleaned_response = cleaned_response[len("```python"):] + elif cleaned_response.startswith("```"): + cleaned_response= cleaned_response[len("```"):] + else: + pass + + if cleaned_response.endswith("```"): + cleaned_response = cleaned_response[:-len("```")] + memory.save_context(query,cleaned_response) + self.log.emit("Code generation for requested task is completed now") + + + while True: + history1=memory1.load_memory_variables() + if "AI: " in history1: + last_safety_decision= history1.split("AI: ")[-1].strip() + else: + last_safety_decision= "No" + + if "Yes" not in last_safety_decision: + llm1=Ollama( + model="qwen2.5-coder:7b", + temperature=0.1 + ) + + prompt=f""" + [SYSTEM: SECURITY MONITOR] + You are a senior cyber-security auditor. Analyze the Python code provided below for MALICIOUS intent or HIGH-RISK operations that could harm a non-technical user's system. + + CRITERIA FOR 'Yes' (High Risk): + 1. Deleting system files or user documents without clear task-related necessity. + 2. Stealing private data (passwords, cookies, SSH keys, .env files). + 3. Establishing unauthorized remote connections (reverse shells). + 4. Commands that can crash the OS (Fork bombs, infinite resource loops). + 5. Obfuscated or hidden code intended to bypass detection. + + CRITERIA FOR 'No' (Safe): + 1. Standard data processing, plotting, or web scraping as per user task. + 2. Creating/Writing files specifically requested by the task. + 3. Installing standard libraries via pip/subprocess. + + CODE TO REVIEW: + {cleaned_response} + + OUTPUT INSTRUCTION: + Is this code dangerous? Respond ONLY with exactly one word: 'Yes' or 'No'. + Do not provide any explanation, warnings, or markdown. + """ + + response=llm1.invoke(prompt) + if response=="Yes": + self.finished.emit("!!! SECURITY RISK !!! Review code. To bypass, click on Execute again without changing prompt") + return + + error_log = self.executor(cleaned_response) + + if not error_log: + self.finished.emit("--- Task Completed Successfully. ---") + break + + self.log.emit("--- Execution Have Some Problem. Capturing Error and Debugging... ---") + + history=memory.load_memory_variables() + response1=llm.invoke(f""" + Hey, you wrote this code: {history.split("AI: ")[-1].strip()} + The user task is: {query} + During execution, we got this error: {error_log} + + Your Goal: Fix the error and complete the task. + You have two choices: + 1. REWRITE: If the logic is fundamentally wrong, rewrite the WHOLE code. + 2. PARTIAL: If some task is already done (variables are saved in globals()), you can write just the remaining part to complete the task. Only do this if you are 100% confident. + + IMPORTANT RULES: + 1. User is non-technical; handle everything automatically. + 2. Maintain proper indentation. No extra explanations outside of # comments. + 3. ALWAYS include subprocess imports/installs if you use new libraries. + 4. Output ONLY executable code. Do not say "Here is the fix". + 5. If writing a partial fix, assume previous successful variables are already in memory. + """) + + final_response1=parser.parse(response1) + + cleaned_response = final_response1.strip() + if cleaned_response.startswith("```python"): + cleaned_response = cleaned_response[len("```python"):] + elif cleaned_response.startswith("```"): + cleaned_response= cleaned_response[len("```"):] + else: + pass + + if cleaned_response.endswith("```"): + cleaned_response = cleaned_response[:-len("```")] + memory.save_context(error_log,cleaned_response) + + +# ======================= +# UI +# ======================= +class AutoCodeApp(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("NPM AutoCode AI") + self.resize(600, 600) + layout = QVBoxLayout() + + self.task_input = QLineEdit() + self.task_input.setPlaceholderText("Describe your automation task here...") + + self.done_btn = QPushButton("Generate & Execute") + self.log_box = QTextEdit() + self.log_box.setReadOnly(True) + + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(0) + + layout.addWidget(QLabel("Task Description:")) + layout.addWidget(self.task_input) + layout.addWidget(self.done_btn) + layout.addWidget(QLabel("Logs:")) + layout.addWidget(self.log_box) + layout.addWidget(self.progress_bar) + + self.setLayout(layout) + + self.done_btn.clicked.connect(self.start_task) + + def start_task(self): + task_text = self.task_input.text().strip() + if not task_text: + self.log_box.append("Please enter a task description") + return + + self.log_box.append("Starting task...") + self.progress_bar.setValue(10) + + self.worker = CodeWorker(task_text) + self.worker.log.connect(self.log_box.append) + self.worker.finished.connect(self.task_finished) + self.worker.start() + + def task_finished(self, msg): + self.log_box.append(msg) + self.progress_bar.setValue(100) + +# ======================= +# MAIN +# ======================= +if __name__ == "__main__": + app = QApplication(sys.argv) + window = AutoCodeApp() + window.show() + base_dir = os.path.dirname(sys.argv[0]) + for filename in ["coder0-generator.json", "safety_checker.json"]: + file_path = os.path.join(base_dir, filename) + if os.path.exists(file_path): + try: + os.remove(file_path) + except Exception as e: + pass + + sys.exit(app.exec()) From bd67d39aed137642cfa26d49cc69ddad404fa725 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:43:58 +0530 Subject: [PATCH 10/68] Create NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 371 ++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 Docs/templates/NPM_AutoCode_AI.html diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html new file mode 100644 index 0000000..549df4b --- /dev/null +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -0,0 +1,371 @@ + + + + + + NPM AutoCode AI + + + + + + + + + + +
+

NPM AutoCode AI

+
Automate Python Tasks. Generate & Execute Instantly.
+

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

+
+ +
+

Quick Buttons

Click on buttons to explore

+
+
+

How it Works

+

Learn the workflow of NPM AutoCode AI

+
+
+

Download

+

Get the pre-built desktop app here

+
+
+

Features

+

Explore NPM AutoCode AI capabilities

+
+
+

Developer

+

About Sonu Kumar, the creator

+
+
+

Knowledge

+

Understand project overview and technical details

+
+
+ + +
+
+

โœจ Features

+
+
+ +

Natural Language Code Generation

+
    +
  • Describe your task in plain English and get ready-to-run Python scripts.
  • +
+
+
+ +

Safe Code Execution

+
    +
  • Runs generated scripts in an isolated local environment for safety.
  • +
+
+
+ +

Real-Time Logs & Progress

+
    +
  • Step-by-step execution updates and visual progress bar.
  • +
+
+
+ +

Custom AI Backend

+
    +
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • +
+
+
+
+
+ + +
+
+

โšก How It Works

+
+
+

1. Describe your task in natural language.

+

Type what you want automated using plain English in the GUI input field.

+
+
+

2. AI generates Python code automatically.

+

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

+
+
+

3. Execute code safely on your local machine.

+

The code runs in an isolated namespace while logging progress in real-time.

+
+
+

4. Monitor and reuse code modules as needed.

+

Generated scripts can be reused or customized for other tasks.

+
+
+
+
+ + +
+
+

๐Ÿ›  No Technical Knowledge Needed

+
+
+

Simple GUI Interface

+
    +
  • One-click automation without coding experience.
  • +
+
+
+

Fully Automated Execution

+
    +
  • Describe tasks and watch them execute instantly.
  • +
+
+
+

Integrated AI Backend

+
    +
  • No manual LLM setup โ€” everything handled internally.
  • +
+
+
+
+ Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! +
+
+
+ + +
+
+

๐ŸŒŸ Future Vision

+
+
    +
  • More apps will be automated
  • +
  • More efficient versions will be made
  • +
+
+
+
+ + +
+
+

๐Ÿš€ Quick Start

+
+

Install the app by clicking the link below:

+ + Download NPM AutoCode AI + +

+ Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! +

+
+
+
+ + +
+
+
+
+ Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker +
+
+
+ Sonu Kumar, 14-year-old tech innovator from Bihar +
+

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

+

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

+
+ From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. +
+
+
+ + + +
+ +
+
+

๐Ÿ”ฅ Who Am I?

+

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

+

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

+

Open Source Innovator โ€” Creating tools for education and democracy.

+

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

+
+ +
+

โœจ Tech Toolbox

+ Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI +
+
+ +
+

๐ŸŒŸ Major Projects & Repos

+
+
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
+
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
+
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
+
NPM-Edu-AI Helper: Free large-file study tool for students.
+
NPM-Youtube-Automation: Fully automated video upload workflow.
+
Blockchain Voting: Tamper-proof hardware-secure election system.
+
NPM Legal AI: Free legal intelligence toolkit.
+
+
+ +
+
+

๐Ÿ’ซ Achievements

+

405K+ Facebook Followers.

+

Allen Career Institute 100% Scholarship.

+

Supported by Lok Sabha Speaker & MIT/Oxford circles.

+
+
+

๐Ÿ’ฌ Social Impact

+

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

+
+
+ +
+

๐Ÿ—๏ธ Mission & Goals

+

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

+
+ +
+ Sonu Kumar's GitHub Stats + Sonu Kumar's GitHub Streak +
+ +
+

๐Ÿง  "Rural-Zero to Revolution"

+

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

+
+
+
+
+
+ +
+

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

+
+ + + From b81652aacd46e6fca111acb40a4788cfb731be50 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:45:28 +0530 Subject: [PATCH 11/68] Delete Docs/templates/NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 371 ---------------------------- 1 file changed, 371 deletions(-) delete mode 100644 Docs/templates/NPM_AutoCode_AI.html diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html deleted file mode 100644 index 549df4b..0000000 --- a/Docs/templates/NPM_AutoCode_AI.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - - NPM AutoCode AI - - - - - - - - - - -
-

NPM AutoCode AI

-
Automate Python Tasks. Generate & Execute Instantly.
-

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

-
- -
-

Quick Buttons

Click on buttons to explore

-
-
-

How it Works

-

Learn the workflow of NPM AutoCode AI

-
-
-

Download

-

Get the pre-built desktop app here

-
-
-

Features

-

Explore NPM AutoCode AI capabilities

-
-
-

Developer

-

About Sonu Kumar, the creator

-
-
-

Knowledge

-

Understand project overview and technical details

-
-
- - -
-
-

โœจ Features

-
-
- -

Natural Language Code Generation

-
    -
  • Describe your task in plain English and get ready-to-run Python scripts.
  • -
-
-
- -

Safe Code Execution

-
    -
  • Runs generated scripts in an isolated local environment for safety.
  • -
-
-
- -

Real-Time Logs & Progress

-
    -
  • Step-by-step execution updates and visual progress bar.
  • -
-
-
- -

Custom AI Backend

-
    -
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • -
-
-
-
-
- - -
-
-

โšก How It Works

-
-
-

1. Describe your task in natural language.

-

Type what you want automated using plain English in the GUI input field.

-
-
-

2. AI generates Python code automatically.

-

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

-
-
-

3. Execute code safely on your local machine.

-

The code runs in an isolated namespace while logging progress in real-time.

-
-
-

4. Monitor and reuse code modules as needed.

-

Generated scripts can be reused or customized for other tasks.

-
-
-
-
- - -
-
-

๐Ÿ›  No Technical Knowledge Needed

-
-
-

Simple GUI Interface

-
    -
  • One-click automation without coding experience.
  • -
-
-
-

Fully Automated Execution

-
    -
  • Describe tasks and watch them execute instantly.
  • -
-
-
-

Integrated AI Backend

-
    -
  • No manual LLM setup โ€” everything handled internally.
  • -
-
-
-
- Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! -
-
-
- - -
-
-

๐ŸŒŸ Future Vision

-
-
    -
  • More apps will be automated
  • -
  • More efficient versions will be made
  • -
-
-
-
- - -
-
-

๐Ÿš€ Quick Start

-
-

Install the app by clicking the link below:

- - Download NPM AutoCode AI - -

- Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! -

-
-
-
- - -
-
-
-
- Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker -
-
-
- Sonu Kumar, 14-year-old tech innovator from Bihar -
-

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

-

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

-
- From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. -
-
-
- - - -
- -
-
-

๐Ÿ”ฅ Who Am I?

-

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

-

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

-

Open Source Innovator โ€” Creating tools for education and democracy.

-

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

-
- -
-

โœจ Tech Toolbox

- Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI -
-
- -
-

๐ŸŒŸ Major Projects & Repos

-
-
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
-
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
-
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
-
NPM-Edu-AI Helper: Free large-file study tool for students.
-
NPM-Youtube-Automation: Fully automated video upload workflow.
-
Blockchain Voting: Tamper-proof hardware-secure election system.
-
NPM Legal AI: Free legal intelligence toolkit.
-
-
- -
-
-

๐Ÿ’ซ Achievements

-

405K+ Facebook Followers.

-

Allen Career Institute 100% Scholarship.

-

Supported by Lok Sabha Speaker & MIT/Oxford circles.

-
-
-

๐Ÿ’ฌ Social Impact

-

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

-
-
- -
-

๐Ÿ—๏ธ Mission & Goals

-

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

-
- -
- Sonu Kumar's GitHub Stats - Sonu Kumar's GitHub Streak -
- -
-

๐Ÿง  "Rural-Zero to Revolution"

-

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

-
-
-
-
-
- -
-

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

-
- - - From e38049e7b10ed07cb297c0fd65d76fd318454682 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:45:56 +0530 Subject: [PATCH 12/68] Create NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 371 ++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 Docs/templates/NPM_AutoCode_AI.html diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html new file mode 100644 index 0000000..549df4b --- /dev/null +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -0,0 +1,371 @@ + + + + + + NPM AutoCode AI + + + + + + + + + + +
+

NPM AutoCode AI

+
Automate Python Tasks. Generate & Execute Instantly.
+

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

+
+ +
+

Quick Buttons

Click on buttons to explore

+
+
+

How it Works

+

Learn the workflow of NPM AutoCode AI

+
+
+

Download

+

Get the pre-built desktop app here

+
+
+

Features

+

Explore NPM AutoCode AI capabilities

+
+
+

Developer

+

About Sonu Kumar, the creator

+
+
+

Knowledge

+

Understand project overview and technical details

+
+
+ + +
+
+

โœจ Features

+
+
+ +

Natural Language Code Generation

+
    +
  • Describe your task in plain English and get ready-to-run Python scripts.
  • +
+
+
+ +

Safe Code Execution

+
    +
  • Runs generated scripts in an isolated local environment for safety.
  • +
+
+
+ +

Real-Time Logs & Progress

+
    +
  • Step-by-step execution updates and visual progress bar.
  • +
+
+
+ +

Custom AI Backend

+
    +
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • +
+
+
+
+
+ + +
+
+

โšก How It Works

+
+
+

1. Describe your task in natural language.

+

Type what you want automated using plain English in the GUI input field.

+
+
+

2. AI generates Python code automatically.

+

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

+
+
+

3. Execute code safely on your local machine.

+

The code runs in an isolated namespace while logging progress in real-time.

+
+
+

4. Monitor and reuse code modules as needed.

+

Generated scripts can be reused or customized for other tasks.

+
+
+
+
+ + +
+
+

๐Ÿ›  No Technical Knowledge Needed

+
+
+

Simple GUI Interface

+
    +
  • One-click automation without coding experience.
  • +
+
+
+

Fully Automated Execution

+
    +
  • Describe tasks and watch them execute instantly.
  • +
+
+
+

Integrated AI Backend

+
    +
  • No manual LLM setup โ€” everything handled internally.
  • +
+
+
+
+ Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! +
+
+
+ + +
+
+

๐ŸŒŸ Future Vision

+
+
    +
  • More apps will be automated
  • +
  • More efficient versions will be made
  • +
+
+
+
+ + +
+
+

๐Ÿš€ Quick Start

+
+

Install the app by clicking the link below:

+ + Download NPM AutoCode AI + +

+ Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! +

+
+
+
+ + +
+
+
+
+ Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker +
+
+
+ Sonu Kumar, 14-year-old tech innovator from Bihar +
+

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

+

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

+
+ From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. +
+
+
+ + + +
+ +
+
+

๐Ÿ”ฅ Who Am I?

+

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

+

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

+

Open Source Innovator โ€” Creating tools for education and democracy.

+

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

+
+ +
+

โœจ Tech Toolbox

+ Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI +
+
+ +
+

๐ŸŒŸ Major Projects & Repos

+
+
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
+
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
+
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
+
NPM-Edu-AI Helper: Free large-file study tool for students.
+
NPM-Youtube-Automation: Fully automated video upload workflow.
+
Blockchain Voting: Tamper-proof hardware-secure election system.
+
NPM Legal AI: Free legal intelligence toolkit.
+
+
+ +
+
+

๐Ÿ’ซ Achievements

+

405K+ Facebook Followers.

+

Allen Career Institute 100% Scholarship.

+

Supported by Lok Sabha Speaker & MIT/Oxford circles.

+
+
+

๐Ÿ’ฌ Social Impact

+

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

+
+
+ +
+

๐Ÿ—๏ธ Mission & Goals

+

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

+
+ +
+ Sonu Kumar's GitHub Stats + Sonu Kumar's GitHub Streak +
+ +
+

๐Ÿง  "Rural-Zero to Revolution"

+

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

+
+
+
+
+
+ +
+

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

+
+ + + From 37028482f027a60042402bce73410baf7db2bc17 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:46:44 +0530 Subject: [PATCH 13/68] Delete Docs/templates/NPM_AutoCode_AI.html\ --- Docs/templates/NPM_AutoCode_AI.html | 371 ---------------------------- 1 file changed, 371 deletions(-) delete mode 100644 Docs/templates/NPM_AutoCode_AI.html diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html deleted file mode 100644 index 549df4b..0000000 --- a/Docs/templates/NPM_AutoCode_AI.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - - NPM AutoCode AI - - - - - - - - - - -
-

NPM AutoCode AI

-
Automate Python Tasks. Generate & Execute Instantly.
-

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

-
- -
-

Quick Buttons

Click on buttons to explore

-
-
-

How it Works

-

Learn the workflow of NPM AutoCode AI

-
-
-

Download

-

Get the pre-built desktop app here

-
-
-

Features

-

Explore NPM AutoCode AI capabilities

-
-
-

Developer

-

About Sonu Kumar, the creator

-
-
-

Knowledge

-

Understand project overview and technical details

-
-
- - -
-
-

โœจ Features

-
-
- -

Natural Language Code Generation

-
    -
  • Describe your task in plain English and get ready-to-run Python scripts.
  • -
-
-
- -

Safe Code Execution

-
    -
  • Runs generated scripts in an isolated local environment for safety.
  • -
-
-
- -

Real-Time Logs & Progress

-
    -
  • Step-by-step execution updates and visual progress bar.
  • -
-
-
- -

Custom AI Backend

-
    -
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • -
-
-
-
-
- - -
-
-

โšก How It Works

-
-
-

1. Describe your task in natural language.

-

Type what you want automated using plain English in the GUI input field.

-
-
-

2. AI generates Python code automatically.

-

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

-
-
-

3. Execute code safely on your local machine.

-

The code runs in an isolated namespace while logging progress in real-time.

-
-
-

4. Monitor and reuse code modules as needed.

-

Generated scripts can be reused or customized for other tasks.

-
-
-
-
- - -
-
-

๐Ÿ›  No Technical Knowledge Needed

-
-
-

Simple GUI Interface

-
    -
  • One-click automation without coding experience.
  • -
-
-
-

Fully Automated Execution

-
    -
  • Describe tasks and watch them execute instantly.
  • -
-
-
-

Integrated AI Backend

-
    -
  • No manual LLM setup โ€” everything handled internally.
  • -
-
-
-
- Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! -
-
-
- - -
-
-

๐ŸŒŸ Future Vision

-
-
    -
  • More apps will be automated
  • -
  • More efficient versions will be made
  • -
-
-
-
- - -
-
-

๐Ÿš€ Quick Start

-
-

Install the app by clicking the link below:

- - Download NPM AutoCode AI - -

- Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! -

-
-
-
- - -
-
-
-
- Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker -
-
-
- Sonu Kumar, 14-year-old tech innovator from Bihar -
-

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

-

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

-
- From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. -
-
-
- - - -
- -
-
-

๐Ÿ”ฅ Who Am I?

-

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

-

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

-

Open Source Innovator โ€” Creating tools for education and democracy.

-

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

-
- -
-

โœจ Tech Toolbox

- Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI -
-
- -
-

๐ŸŒŸ Major Projects & Repos

-
-
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
-
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
-
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
-
NPM-Edu-AI Helper: Free large-file study tool for students.
-
NPM-Youtube-Automation: Fully automated video upload workflow.
-
Blockchain Voting: Tamper-proof hardware-secure election system.
-
NPM Legal AI: Free legal intelligence toolkit.
-
-
- -
-
-

๐Ÿ’ซ Achievements

-

405K+ Facebook Followers.

-

Allen Career Institute 100% Scholarship.

-

Supported by Lok Sabha Speaker & MIT/Oxford circles.

-
-
-

๐Ÿ’ฌ Social Impact

-

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

-
-
- -
-

๐Ÿ—๏ธ Mission & Goals

-

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

-
- -
- Sonu Kumar's GitHub Stats - Sonu Kumar's GitHub Streak -
- -
-

๐Ÿง  "Rural-Zero to Revolution"

-

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

-
-
-
-
-
- -
-

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

-
- - - From 5ca9a5c02772baac495881b8c0047fb3784bb33b Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:48:27 +0530 Subject: [PATCH 14/68] Create NPM_AutoCode_AI.html --- Docs/NPM_AutoCode_AI.html | 371 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 Docs/NPM_AutoCode_AI.html diff --git a/Docs/NPM_AutoCode_AI.html b/Docs/NPM_AutoCode_AI.html new file mode 100644 index 0000000..549df4b --- /dev/null +++ b/Docs/NPM_AutoCode_AI.html @@ -0,0 +1,371 @@ + + + + + + NPM AutoCode AI + + + + + + + + + + +
+

NPM AutoCode AI

+
Automate Python Tasks. Generate & Execute Instantly.
+

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

+
+ +
+

Quick Buttons

Click on buttons to explore

+
+
+

How it Works

+

Learn the workflow of NPM AutoCode AI

+
+
+

Download

+

Get the pre-built desktop app here

+
+
+

Features

+

Explore NPM AutoCode AI capabilities

+
+
+

Developer

+

About Sonu Kumar, the creator

+
+
+

Knowledge

+

Understand project overview and technical details

+
+
+ + +
+
+

โœจ Features

+
+
+ +

Natural Language Code Generation

+
    +
  • Describe your task in plain English and get ready-to-run Python scripts.
  • +
+
+
+ +

Safe Code Execution

+
    +
  • Runs generated scripts in an isolated local environment for safety.
  • +
+
+
+ +

Real-Time Logs & Progress

+
    +
  • Step-by-step execution updates and visual progress bar.
  • +
+
+
+ +

Custom AI Backend

+
    +
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • +
+
+
+
+
+ + +
+
+

โšก How It Works

+
+
+

1. Describe your task in natural language.

+

Type what you want automated using plain English in the GUI input field.

+
+
+

2. AI generates Python code automatically.

+

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

+
+
+

3. Execute code safely on your local machine.

+

The code runs in an isolated namespace while logging progress in real-time.

+
+
+

4. Monitor and reuse code modules as needed.

+

Generated scripts can be reused or customized for other tasks.

+
+
+
+
+ + +
+
+

๐Ÿ›  No Technical Knowledge Needed

+
+
+

Simple GUI Interface

+
    +
  • One-click automation without coding experience.
  • +
+
+
+

Fully Automated Execution

+
    +
  • Describe tasks and watch them execute instantly.
  • +
+
+
+

Integrated AI Backend

+
    +
  • No manual LLM setup โ€” everything handled internally.
  • +
+
+
+
+ Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! +
+
+
+ + +
+
+

๐ŸŒŸ Future Vision

+
+
    +
  • More apps will be automated
  • +
  • More efficient versions will be made
  • +
+
+
+
+ + +
+
+

๐Ÿš€ Quick Start

+
+

Install the app by clicking the link below:

+ + Download NPM AutoCode AI + +

+ Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! +

+
+
+
+ + +
+
+
+
+ Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker +
+
+
+ Sonu Kumar, 14-year-old tech innovator from Bihar +
+

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

+

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

+
+ From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. +
+
+
+ + + +
+ +
+
+

๐Ÿ”ฅ Who Am I?

+

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

+

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

+

Open Source Innovator โ€” Creating tools for education and democracy.

+

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

+
+ +
+

โœจ Tech Toolbox

+ Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI +
+
+ +
+

๐ŸŒŸ Major Projects & Repos

+
+
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
+
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
+
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
+
NPM-Edu-AI Helper: Free large-file study tool for students.
+
NPM-Youtube-Automation: Fully automated video upload workflow.
+
Blockchain Voting: Tamper-proof hardware-secure election system.
+
NPM Legal AI: Free legal intelligence toolkit.
+
+
+ +
+
+

๐Ÿ’ซ Achievements

+

405K+ Facebook Followers.

+

Allen Career Institute 100% Scholarship.

+

Supported by Lok Sabha Speaker & MIT/Oxford circles.

+
+
+

๐Ÿ’ฌ Social Impact

+

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

+
+
+ +
+

๐Ÿ—๏ธ Mission & Goals

+

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

+
+ +
+ Sonu Kumar's GitHub Stats + Sonu Kumar's GitHub Streak +
+ +
+

๐Ÿง  "Rural-Zero to Revolution"

+

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

+
+
+
+
+
+ +
+

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

+
+ + + From e693255aa9575ed08192a49764773ec75e446523 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:48:41 +0530 Subject: [PATCH 15/68] Create templates --- Docs/templates | 1 + 1 file changed, 1 insertion(+) create mode 100644 Docs/templates diff --git a/Docs/templates b/Docs/templates new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Docs/templates @@ -0,0 +1 @@ + From 320ce7269eb8431fa6263d741bfa0eee79256bcb Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:49:35 +0530 Subject: [PATCH 16/68] Delete Docs/NPM_AutoCode_AI.html --- Docs/NPM_AutoCode_AI.html | 371 -------------------------------------- 1 file changed, 371 deletions(-) delete mode 100644 Docs/NPM_AutoCode_AI.html diff --git a/Docs/NPM_AutoCode_AI.html b/Docs/NPM_AutoCode_AI.html deleted file mode 100644 index 549df4b..0000000 --- a/Docs/NPM_AutoCode_AI.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - - NPM AutoCode AI - - - - - - - - - - -
-

NPM AutoCode AI

-
Automate Python Tasks. Generate & Execute Instantly.
-

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

-
- -
-

Quick Buttons

Click on buttons to explore

-
-
-

How it Works

-

Learn the workflow of NPM AutoCode AI

-
-
-

Download

-

Get the pre-built desktop app here

-
-
-

Features

-

Explore NPM AutoCode AI capabilities

-
-
-

Developer

-

About Sonu Kumar, the creator

-
-
-

Knowledge

-

Understand project overview and technical details

-
-
- - -
-
-

โœจ Features

-
-
- -

Natural Language Code Generation

-
    -
  • Describe your task in plain English and get ready-to-run Python scripts.
  • -
-
-
- -

Safe Code Execution

-
    -
  • Runs generated scripts in an isolated local environment for safety.
  • -
-
-
- -

Real-Time Logs & Progress

-
    -
  • Step-by-step execution updates and visual progress bar.
  • -
-
-
- -

Custom AI Backend

-
    -
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • -
-
-
-
-
- - -
-
-

โšก How It Works

-
-
-

1. Describe your task in natural language.

-

Type what you want automated using plain English in the GUI input field.

-
-
-

2. AI generates Python code automatically.

-

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

-
-
-

3. Execute code safely on your local machine.

-

The code runs in an isolated namespace while logging progress in real-time.

-
-
-

4. Monitor and reuse code modules as needed.

-

Generated scripts can be reused or customized for other tasks.

-
-
-
-
- - -
-
-

๐Ÿ›  No Technical Knowledge Needed

-
-
-

Simple GUI Interface

-
    -
  • One-click automation without coding experience.
  • -
-
-
-

Fully Automated Execution

-
    -
  • Describe tasks and watch them execute instantly.
  • -
-
-
-

Integrated AI Backend

-
    -
  • No manual LLM setup โ€” everything handled internally.
  • -
-
-
-
- Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! -
-
-
- - -
-
-

๐ŸŒŸ Future Vision

-
-
    -
  • More apps will be automated
  • -
  • More efficient versions will be made
  • -
-
-
-
- - -
-
-

๐Ÿš€ Quick Start

-
-

Install the app by clicking the link below:

- - Download NPM AutoCode AI - -

- Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! -

-
-
-
- - -
-
-
-
- Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker -
-
-
- Sonu Kumar, 14-year-old tech innovator from Bihar -
-

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

-

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

-
- From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. -
-
-
- - - -
- -
-
-

๐Ÿ”ฅ Who Am I?

-

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

-

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

-

Open Source Innovator โ€” Creating tools for education and democracy.

-

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

-
- -
-

โœจ Tech Toolbox

- Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI -
-
- -
-

๐ŸŒŸ Major Projects & Repos

-
-
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
-
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
-
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
-
NPM-Edu-AI Helper: Free large-file study tool for students.
-
NPM-Youtube-Automation: Fully automated video upload workflow.
-
Blockchain Voting: Tamper-proof hardware-secure election system.
-
NPM Legal AI: Free legal intelligence toolkit.
-
-
- -
-
-

๐Ÿ’ซ Achievements

-

405K+ Facebook Followers.

-

Allen Career Institute 100% Scholarship.

-

Supported by Lok Sabha Speaker & MIT/Oxford circles.

-
-
-

๐Ÿ’ฌ Social Impact

-

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

-
-
- -
-

๐Ÿ—๏ธ Mission & Goals

-

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

-
- -
- Sonu Kumar's GitHub Stats - Sonu Kumar's GitHub Streak -
- -
-

๐Ÿง  "Rural-Zero to Revolution"

-

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

-
-
-
-
-
- -
-

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

-
- - - From cf1bf83f32018459e257603f7ce69d86e95c0675 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:51:20 +0530 Subject: [PATCH 17/68] Delete Docs/templates --- Docs/templates | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Docs/templates diff --git a/Docs/templates b/Docs/templates deleted file mode 100644 index 8b13789..0000000 --- a/Docs/templates +++ /dev/null @@ -1 +0,0 @@ - From db3fef330d99333fe219b4faf11f0e6ce19e8b79 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:53:16 +0530 Subject: [PATCH 18/68] Create NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 371 ++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 Docs/templates/NPM_AutoCode_AI.html diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html new file mode 100644 index 0000000..549df4b --- /dev/null +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -0,0 +1,371 @@ + + + + + + NPM AutoCode AI + + + + + + + + + + +
+

NPM AutoCode AI

+
Automate Python Tasks. Generate & Execute Instantly.
+

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

+
+ +
+

Quick Buttons

Click on buttons to explore

+
+
+

How it Works

+

Learn the workflow of NPM AutoCode AI

+
+
+

Download

+

Get the pre-built desktop app here

+
+
+

Features

+

Explore NPM AutoCode AI capabilities

+
+
+

Developer

+

About Sonu Kumar, the creator

+
+
+

Knowledge

+

Understand project overview and technical details

+
+
+ + +
+
+

โœจ Features

+
+
+ +

Natural Language Code Generation

+
    +
  • Describe your task in plain English and get ready-to-run Python scripts.
  • +
+
+
+ +

Safe Code Execution

+
    +
  • Runs generated scripts in an isolated local environment for safety.
  • +
+
+
+ +

Real-Time Logs & Progress

+
    +
  • Step-by-step execution updates and visual progress bar.
  • +
+
+
+ +

Custom AI Backend

+
    +
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • +
+
+
+
+
+ + +
+
+

โšก How It Works

+
+
+

1. Describe your task in natural language.

+

Type what you want automated using plain English in the GUI input field.

+
+
+

2. AI generates Python code automatically.

+

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

+
+
+

3. Execute code safely on your local machine.

+

The code runs in an isolated namespace while logging progress in real-time.

+
+
+

4. Monitor and reuse code modules as needed.

+

Generated scripts can be reused or customized for other tasks.

+
+
+
+
+ + +
+
+

๐Ÿ›  No Technical Knowledge Needed

+
+
+

Simple GUI Interface

+
    +
  • One-click automation without coding experience.
  • +
+
+
+

Fully Automated Execution

+
    +
  • Describe tasks and watch them execute instantly.
  • +
+
+
+

Integrated AI Backend

+
    +
  • No manual LLM setup โ€” everything handled internally.
  • +
+
+
+
+ Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! +
+
+
+ + +
+
+

๐ŸŒŸ Future Vision

+
+
    +
  • More apps will be automated
  • +
  • More efficient versions will be made
  • +
+
+
+
+ + +
+
+

๐Ÿš€ Quick Start

+
+

Install the app by clicking the link below:

+ + Download NPM AutoCode AI + +

+ Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! +

+
+
+
+ + +
+
+
+
+ Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker +
+
+
+ Sonu Kumar, 14-year-old tech innovator from Bihar +
+

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

+

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

+
+ From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. +
+
+
+ + + +
+ +
+
+

๐Ÿ”ฅ Who Am I?

+

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

+

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

+

Open Source Innovator โ€” Creating tools for education and democracy.

+

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

+
+ +
+

โœจ Tech Toolbox

+ Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI +
+
+ +
+

๐ŸŒŸ Major Projects & Repos

+
+
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
+
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
+
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
+
NPM-Edu-AI Helper: Free large-file study tool for students.
+
NPM-Youtube-Automation: Fully automated video upload workflow.
+
Blockchain Voting: Tamper-proof hardware-secure election system.
+
NPM Legal AI: Free legal intelligence toolkit.
+
+
+ +
+
+

๐Ÿ’ซ Achievements

+

405K+ Facebook Followers.

+

Allen Career Institute 100% Scholarship.

+

Supported by Lok Sabha Speaker & MIT/Oxford circles.

+
+
+

๐Ÿ’ฌ Social Impact

+

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

+
+
+ +
+

๐Ÿ—๏ธ Mission & Goals

+

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

+
+ +
+ Sonu Kumar's GitHub Stats + Sonu Kumar's GitHub Streak +
+ +
+

๐Ÿง  "Rural-Zero to Revolution"

+

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

+
+
+
+
+
+ +
+

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

+
+ + + From e48a0b9e7e2b1746cd60b7ecfdb0bc6060e33470 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:54:33 +0530 Subject: [PATCH 19/68] Delete Docs/templates directory --- Docs/templates/NPM_AutoCode_AI.html | 371 ---------------------------- 1 file changed, 371 deletions(-) delete mode 100644 Docs/templates/NPM_AutoCode_AI.html diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html deleted file mode 100644 index 549df4b..0000000 --- a/Docs/templates/NPM_AutoCode_AI.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - - NPM AutoCode AI - - - - - - - - - - -
-

NPM AutoCode AI

-
Automate Python Tasks. Generate & Execute Instantly.
-

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

-
- -
-

Quick Buttons

Click on buttons to explore

-
-
-

How it Works

-

Learn the workflow of NPM AutoCode AI

-
-
-

Download

-

Get the pre-built desktop app here

-
-
-

Features

-

Explore NPM AutoCode AI capabilities

-
-
-

Developer

-

About Sonu Kumar, the creator

-
-
-

Knowledge

-

Understand project overview and technical details

-
-
- - -
-
-

โœจ Features

-
-
- -

Natural Language Code Generation

-
    -
  • Describe your task in plain English and get ready-to-run Python scripts.
  • -
-
-
- -

Safe Code Execution

-
    -
  • Runs generated scripts in an isolated local environment for safety.
  • -
-
-
- -

Real-Time Logs & Progress

-
    -
  • Step-by-step execution updates and visual progress bar.
  • -
-
-
- -

Custom AI Backend

-
    -
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • -
-
-
-
-
- - -
-
-

โšก How It Works

-
-
-

1. Describe your task in natural language.

-

Type what you want automated using plain English in the GUI input field.

-
-
-

2. AI generates Python code automatically.

-

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

-
-
-

3. Execute code safely on your local machine.

-

The code runs in an isolated namespace while logging progress in real-time.

-
-
-

4. Monitor and reuse code modules as needed.

-

Generated scripts can be reused or customized for other tasks.

-
-
-
-
- - -
-
-

๐Ÿ›  No Technical Knowledge Needed

-
-
-

Simple GUI Interface

-
    -
  • One-click automation without coding experience.
  • -
-
-
-

Fully Automated Execution

-
    -
  • Describe tasks and watch them execute instantly.
  • -
-
-
-

Integrated AI Backend

-
    -
  • No manual LLM setup โ€” everything handled internally.
  • -
-
-
-
- Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! -
-
-
- - -
-
-

๐ŸŒŸ Future Vision

-
-
    -
  • More apps will be automated
  • -
  • More efficient versions will be made
  • -
-
-
-
- - -
-
-

๐Ÿš€ Quick Start

-
-

Install the app by clicking the link below:

- - Download NPM AutoCode AI - -

- Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! -

-
-
-
- - -
-
-
-
- Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker -
-
-
- Sonu Kumar, 14-year-old tech innovator from Bihar -
-

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

-

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

-
- From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. -
-
-
- - - -
- -
-
-

๐Ÿ”ฅ Who Am I?

-

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

-

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

-

Open Source Innovator โ€” Creating tools for education and democracy.

-

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

-
- -
-

โœจ Tech Toolbox

- Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI -
-
- -
-

๐ŸŒŸ Major Projects & Repos

-
-
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
-
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
-
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
-
NPM-Edu-AI Helper: Free large-file study tool for students.
-
NPM-Youtube-Automation: Fully automated video upload workflow.
-
Blockchain Voting: Tamper-proof hardware-secure election system.
-
NPM Legal AI: Free legal intelligence toolkit.
-
-
- -
-
-

๐Ÿ’ซ Achievements

-

405K+ Facebook Followers.

-

Allen Career Institute 100% Scholarship.

-

Supported by Lok Sabha Speaker & MIT/Oxford circles.

-
-
-

๐Ÿ’ฌ Social Impact

-

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

-
-
- -
-

๐Ÿ—๏ธ Mission & Goals

-

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

-
- -
- Sonu Kumar's GitHub Stats - Sonu Kumar's GitHub Streak -
- -
-

๐Ÿง  "Rural-Zero to Revolution"

-

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

-
-
-
-
-
- -
-

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

-
- - - From 443e008a475826294883a9d35a6794a32b2618cf Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:58:50 +0530 Subject: [PATCH 20/68] Create NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 371 ++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 Docs/templates/NPM_AutoCode_AI.html diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html new file mode 100644 index 0000000..549df4b --- /dev/null +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -0,0 +1,371 @@ + + + + + + NPM AutoCode AI + + + + + + + + + + +
+

NPM AutoCode AI

+
Automate Python Tasks. Generate & Execute Instantly.
+

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

+
+ +
+

Quick Buttons

Click on buttons to explore

+
+
+

How it Works

+

Learn the workflow of NPM AutoCode AI

+
+
+

Download

+

Get the pre-built desktop app here

+
+
+

Features

+

Explore NPM AutoCode AI capabilities

+
+
+

Developer

+

About Sonu Kumar, the creator

+
+
+

Knowledge

+

Understand project overview and technical details

+
+
+ + +
+
+

โœจ Features

+
+
+ +

Natural Language Code Generation

+
    +
  • Describe your task in plain English and get ready-to-run Python scripts.
  • +
+
+
+ +

Safe Code Execution

+
    +
  • Runs generated scripts in an isolated local environment for safety.
  • +
+
+
+ +

Real-Time Logs & Progress

+
    +
  • Step-by-step execution updates and visual progress bar.
  • +
+
+
+ +

Custom AI Backend

+
    +
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • +
+
+
+
+
+ + +
+
+

โšก How It Works

+
+
+

1. Describe your task in natural language.

+

Type what you want automated using plain English in the GUI input field.

+
+
+

2. AI generates Python code automatically.

+

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

+
+
+

3. Execute code safely on your local machine.

+

The code runs in an isolated namespace while logging progress in real-time.

+
+
+

4. Monitor and reuse code modules as needed.

+

Generated scripts can be reused or customized for other tasks.

+
+
+
+
+ + +
+
+

๐Ÿ›  No Technical Knowledge Needed

+
+
+

Simple GUI Interface

+
    +
  • One-click automation without coding experience.
  • +
+
+
+

Fully Automated Execution

+
    +
  • Describe tasks and watch them execute instantly.
  • +
+
+
+

Integrated AI Backend

+
    +
  • No manual LLM setup โ€” everything handled internally.
  • +
+
+
+
+ Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! +
+
+
+ + +
+
+

๐ŸŒŸ Future Vision

+
+
    +
  • More apps will be automated
  • +
  • More efficient versions will be made
  • +
+
+
+
+ + +
+
+

๐Ÿš€ Quick Start

+
+

Install the app by clicking the link below:

+ + Download NPM AutoCode AI + +

+ Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! +

+
+
+
+ + +
+
+
+
+ Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker +
+
+
+ Sonu Kumar, 14-year-old tech innovator from Bihar +
+

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

+

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

+
+ From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. +
+
+
+ + + +
+ +
+
+

๐Ÿ”ฅ Who Am I?

+

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

+

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

+

Open Source Innovator โ€” Creating tools for education and democracy.

+

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

+
+ +
+

โœจ Tech Toolbox

+ Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI +
+
+ +
+

๐ŸŒŸ Major Projects & Repos

+
+
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
+
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
+
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
+
NPM-Edu-AI Helper: Free large-file study tool for students.
+
NPM-Youtube-Automation: Fully automated video upload workflow.
+
Blockchain Voting: Tamper-proof hardware-secure election system.
+
NPM Legal AI: Free legal intelligence toolkit.
+
+
+ +
+
+

๐Ÿ’ซ Achievements

+

405K+ Facebook Followers.

+

Allen Career Institute 100% Scholarship.

+

Supported by Lok Sabha Speaker & MIT/Oxford circles.

+
+
+

๐Ÿ’ฌ Social Impact

+

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

+
+
+ +
+

๐Ÿ—๏ธ Mission & Goals

+

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

+
+ +
+ Sonu Kumar's GitHub Stats + Sonu Kumar's GitHub Streak +
+ +
+

๐Ÿง  "Rural-Zero to Revolution"

+

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

+
+
+
+
+
+ +
+

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

+
+ + + From 3756218f117ac30e43db42e97f64a050409278c2 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 11:59:32 +0530 Subject: [PATCH 21/68] Create app.py --- Docs/app.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 Docs/app.py diff --git a/Docs/app.py b/Docs/app.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Docs/app.py @@ -0,0 +1 @@ + From 115e36021fe34d92622841aab29ebd373baed94f Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 12:00:41 +0530 Subject: [PATCH 22/68] Create requirements.txt --- Docs/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Docs/requirements.txt diff --git a/Docs/requirements.txt b/Docs/requirements.txt new file mode 100644 index 0000000..cef5a16 --- /dev/null +++ b/Docs/requirements.txt @@ -0,0 +1,2 @@ +Flask +gunicorn From 4b942c8b25f3ef5cc449ec4451fa566451765a4b Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 12:01:23 +0530 Subject: [PATCH 23/68] Create Docs.md --- Docs/Docs.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Docs/Docs.md diff --git a/Docs/Docs.md b/Docs/Docs.md new file mode 100644 index 0000000..ae924c0 --- /dev/null +++ b/Docs/Docs.md @@ -0,0 +1,59 @@ +# NPM AutoCode AI + +## ๐Ÿš€ Quick Start +Install the app: +[Download Latest Release](https://github.com/sonuramashishnpm/NPM-AutoCode-AI/releases/download/v2.0/NPM_AutoCode_AI.zip) + +1. Unzip the file. +2. Run `NPM_AutoCode_AI.exe` (Windows) or `python main.py` (Python 3.12+). +3. Enter your task in the input box (e.g., "Plot a sine wave"). +4. Click **Generate & Execute**. +5. Watch logs and progress bar โ€“ AI generates, checks safety, executes, and fixes errors if needed. + +**Note:** Requires Ollama with models `codellama:7b-instruct` and `qwen2.5-coder:7b`. Run `ollama pull` for them. + +## Workflow:- + +Example Screenshot + + +## ๐Ÿ“– Project Overview +NPM AutoCode AI is a Python desktop app for automatic code generation and execution using AI. Describe tasks in plain English, and it uses NPMAI (custom LLM tools) to create, validate, and run Python scripts safely. + +Built for non-technical users โ€“ turns ideas into working code without manual editing. + +## โœจ Features +- **Natural Language to Code**: AI generates Python scripts from your description. +- **Safety Check**: Scans code for risks (e.g., file deletions, remote access) before running. +- **Auto-Debug**: If errors happen, AI fixes and retries using error logs. +- **Live Logs & Progress**: See real-time updates in GUI. +- **Dependency Handling**: Installs libraries via `subprocess` in code. +- **Isolated Execution**: Runs in safe namespace to protect your system. +- **Memory Chains**: Remembers task history for better fixes. + +## ๐Ÿ”„ How It Works +1. Enter task โ†’ AI (`codellama:7b-instruct`) generates code via NPMAI Ollama. +2. Safety AI (`qwen2.5-coder:7b`) reviews: If risky, stops with warning. +3. Execute code in thread โ†’ If error, feed back to AI for fix โ†’ Retry loop. +4. Success: Logs "Task Completed Successfully". + +All in background (QThread) so UI stays responsive. Uses LangChain for prompts. + +## ๐Ÿ› ๏ธ Tech Details +- **Language**: Python 3.12+ +- **GUI**: PySide6 (QWidget, QThread, etc.) +- **AI**: npmai.Ollama + Memory; LangChain Core for prompts/parsers. +- **Execution**: `exec()` in custom globals dict with error capture. + +## ๐Ÿ‘จโ€๐Ÿ’ป Developer +**Sonu Kumar Ramashish** (a.k.a. Bihar Viral Boy) +- Age: 14 | Student | TEDx Speaker | AI & Software Developer | DevOps Enthusiast +- Reach: 410K+ Facebook followers +- Location: Kota, Rajasthan + +Part of NPMAI ecosystem for AI automation tools. + +## ๐Ÿค Contributing +Fork, add features (e.g., more models), and PR. License: MIT. + +Star if useful! ๐Ÿš€ From 5e793755764507358416cc3268c1468a2f0b6c6f Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Tue, 24 Feb 2026 12:02:49 +0530 Subject: [PATCH 24/68] Create requirements.txt --- Desktop_App/requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Desktop_App/requirements.txt diff --git a/Desktop_App/requirements.txt b/Desktop_App/requirements.txt new file mode 100644 index 0000000..3c8a822 --- /dev/null +++ b/Desktop_App/requirements.txt @@ -0,0 +1,5 @@ +PySide6 +langchain +langchain-core +langchain-community +npmai From de2fbfc10a0dc6b28ccfb5e296538aba4fdfcc7b Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Fri, 27 Feb 2026 12:35:13 +0530 Subject: [PATCH 25/68] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ae924c0..c976c50 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ Install the app: **Note:** Requires Ollama with models `codellama:7b-instruct` and `qwen2.5-coder:7b`. Run `ollama pull` for them. +## To understand repo project with AI in detail with full documentation visit here:- +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/sonuramashishnpm/NPM-AutoCode-AI) + ## Workflow:- Example Screenshot From 2f001724269fda9f2b03ffa39f89a919e82295d5 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sat, 28 Feb 2026 19:54:17 +0530 Subject: [PATCH 26/68] Update NPM-AutoCode-AI.py --- Desktop_App/NPM-AutoCode-AI.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Desktop_App/NPM-AutoCode-AI.py b/Desktop_App/NPM-AutoCode-AI.py index 4b93b14..1fcbc6f 100644 --- a/Desktop_App/NPM-AutoCode-AI.py +++ b/Desktop_App/NPM-AutoCode-AI.py @@ -111,6 +111,7 @@ def run(self): response=llm1.invoke(prompt) if response=="Yes": self.finished.emit("!!! SECURITY RISK !!! Review code. To bypass, click on Execute again without changing prompt") + memory.save_context("Latest_AI_Response",response) return error_log = self.executor(cleaned_response) From dce7b3b44bf1110e90cea3053f2abe4aa70c77aa Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sat, 28 Feb 2026 20:22:15 +0530 Subject: [PATCH 27/68] Update NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html index 549df4b..be6e265 100644 --- a/Docs/templates/NPM_AutoCode_AI.html +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -264,7 +264,7 @@

๐ŸŒŸ Future Vision

๐Ÿš€ Quick Start

Install the app by clicking the link below:

- + Download NPM AutoCode AI

From 3a257f17c570ae6ef1deec18d122f2a98353c6fd Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sat, 28 Feb 2026 20:33:21 +0530 Subject: [PATCH 28/68] Update NPM-AutoCode-AI.py --- Desktop_App/NPM-AutoCode-AI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_App/NPM-AutoCode-AI.py b/Desktop_App/NPM-AutoCode-AI.py index 1fcbc6f..47beb36 100644 --- a/Desktop_App/NPM-AutoCode-AI.py +++ b/Desktop_App/NPM-AutoCode-AI.py @@ -111,7 +111,7 @@ def run(self): response=llm1.invoke(prompt) if response=="Yes": self.finished.emit("!!! SECURITY RISK !!! Review code. To bypass, click on Execute again without changing prompt") - memory.save_context("Latest_AI_Response",response) + memory1.save_context("Latest_AI_Response",response) return error_log = self.executor(cleaned_response) From 5d39d4ff8d7916716d9d3605642f40aab950c38d Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 8 Mar 2026 16:45:41 +0530 Subject: [PATCH 29/68] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c976c50..d76f6cf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## ๐Ÿš€ Quick Start Install the app: -[Download Latest Release](https://github.com/sonuramashishnpm/NPM-AutoCode-AI/releases/download/v2.0/NPM_AutoCode_AI.zip) +[Download Latest Release](https://github.com/sonuramashishnpm/NPM-AutoCode-AI/releases/download/v2.2/NPM_AutoCode_AI.zip) 1. Unzip the file. 2. Run `NPM_AutoCode_AI.exe` (Windows) or `python main.py` (Python 3.12+). From 1df99d778b94a6714abee58aab3a7cf90c706997 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 8 Mar 2026 16:46:15 +0530 Subject: [PATCH 30/68] Update NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html index be6e265..551d6db 100644 --- a/Docs/templates/NPM_AutoCode_AI.html +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -264,7 +264,7 @@

๐ŸŒŸ Future Vision

๐Ÿš€ Quick Start

Install the app by clicking the link below:

- + Download NPM AutoCode AI

From 694bccf8c5a325eb0f66852db9a78287e557ee02 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 8 Mar 2026 16:47:00 +0530 Subject: [PATCH 31/68] Update NPM-AutoCode-AI.py --- Desktop_App/NPM-AutoCode-AI.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Desktop_App/NPM-AutoCode-AI.py b/Desktop_App/NPM-AutoCode-AI.py index 47beb36..29dbfcb 100644 --- a/Desktop_App/NPM-AutoCode-AI.py +++ b/Desktop_App/NPM-AutoCode-AI.py @@ -33,6 +33,7 @@ def executor(self,code): return error_log def run(self): + tried=0 self.log.emit("npmai is doing your requested task") memory=Memory("coder0-generator") memory1=Memory("safety_checker") @@ -72,6 +73,11 @@ def run(self): while True: + if tried==15: + self.log.emit("!!! Debugging Limit reached please enter prompt again with more clarity and simplicity this will help A.I in understanding your task.") + self.finished.emit("!!! Debugging Limit Crossed !!!") + return + history1=memory1.load_memory_variables() if "AI: " in history1: last_safety_decision= history1.split("AI: ")[-1].strip() @@ -110,8 +116,9 @@ def run(self): response=llm1.invoke(prompt) if response=="Yes": - self.finished.emit("!!! SECURITY RISK !!! Review code. To bypass, click on Execute again without changing prompt") + self.finished.emit("!!! SECURITY RISK !!! We cannot execute this code beacuause it can harm your system.") memory1.save_context("Latest_AI_Response",response) + memory1.clear_memory() return error_log = self.executor(cleaned_response) @@ -142,6 +149,8 @@ def run(self): """) final_response1=parser.parse(response1) + + tried+=1 cleaned_response = final_response1.strip() if cleaned_response.startswith("```python"): @@ -156,6 +165,7 @@ def run(self): memory.save_context(error_log,cleaned_response) + # ======================= # UI # ======================= From b5697c618cc2c30be0bc9309b684f15e94a3f9a9 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 7 Jun 2026 09:58:51 +0530 Subject: [PATCH 32/68] Update NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 1541 +++++++++++++++++++++------ 1 file changed, 1223 insertions(+), 318 deletions(-) diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html index 551d6db..ae0f94d 100644 --- a/Docs/templates/NPM_AutoCode_AI.html +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -1,371 +1,1276 @@ + - + NPM AutoCode AI + + + + +

+
+
- - - - - -
-

NPM AutoCode AI

-
Automate Python Tasks. Generate & Execute Instantly.
-

Welcome to the future of AI-powered automation! NPM AutoCode AI generates Python scripts from plain English task descriptions and executes them safely on your computer.

-
- -
-

Quick Buttons

Click on buttons to explore

-
-
-

How it Works

-

Learn the workflow of NPM AutoCode AI

-
-
-

Download

-

Get the pre-built desktop app here

+ +
- - -
-
-

โœจ Features

-
-
- -

Natural Language Code Generation

-
    -
  • Describe your task in plain English and get ready-to-run Python scripts.
  • -
-
-
- -

Safe Code Execution

-
    -
  • Runs generated scripts in an isolated local environment for safety.
  • -
-
-
- -

Real-Time Logs & Progress

-
    -
  • Step-by-step execution updates and visual progress bar.
  • -
-
-
- -

Custom AI Backend

-
    -
  • Powered by NPMAI ecosystem and Ollama-wrapped LLMs, no local AI setup needed.
  • -
-
-
-
-
- - -
-
-

โšก How It Works

-
-
-

1. Describe your task in natural language.

-

Type what you want automated using plain English in the GUI input field.

-
-
-

2. AI generates Python code automatically.

-

NPMAI analyzes your description and returns clean, ready-to-run Python scripts.

-
-
-

3. Execute code safely on your local machine.

-

The code runs in an isolated namespace while logging progress in real-time.

-
-
-

4. Monitor and reuse code modules as needed.

-

Generated scripts can be reused or customized for other tasks.

-
-
-
-
- - -
-
-

๐Ÿ›  No Technical Knowledge Needed

-
-
-

Simple GUI Interface

-
    -
  • One-click automation without coding experience.
  • -
-
-
-

Fully Automated Execution

-
    -
  • Describe tasks and watch them execute instantly.
  • -
+ + Download + + + +
+ + +
+
Free & Open Source ยท v2.2
+

Automate Python
Without Writing Code

+

Describe any task in plain English. NPM AutoCode AI generates and executes Python scripts instantly โ€” powered by NPMAI ecosystem LLMs with built-in security auditing.

+ -
-

Integrated AI Backend

-
    -
  • No manual LLM setup โ€” everything handled internally.
  • -
+
+
50K+
Downloads
+
Free
Forever
+
15
Retry Loops
+
v2.2
Latest
-
-
- Pro Tip: Future updates will make NPM AutoCode AI even faster and support more types of automation! -
-
-
- - -
-
-

๐ŸŒŸ Future Vision

-
-
    -
  • More apps will be automated
  • -
  • More efficient versions will be made
  • -
-
-
-
- - -
-
-

๐Ÿš€ Quick Start

-
-

Install the app by clicking the link below:

- - Download NPM AutoCode AI - -

- Unzip โ†’ Run the .exe โ†’ Start automating Python tasks instantly! -

-
-
-
- - -
-
-
-
- Animated Banner: Bihar Viral Boy Sonu Kumar | 14 y/old Tech Changemaker | Open Source AI, Votes, Education Innovation! | TEDx Speaker & Global Impact Maker +
+ + +
+
+ +

See It In Action

+

Click a task below to watch the app work

-
-
- Sonu Kumar, 14-year-old tech innovator from Bihar -
-

Hi, I'm Sonu Kumar! ๐Ÿ‡ฎ๐Ÿ‡ณ๐Ÿš€

-

14-year-old tech prodigy & changemaker from Nalanda, Bihar.

-
- From rural school to TEDx, building open source platforms and fixing democracyโ€”one line of code at a time. -
+ +
+
+ +
+
+
+
+
+
+
NPM AutoCode AI โ€” Desktop Application
+
+
+ + +
+ +
+
Task Description
+
+
+ +
+
+ +
+
+ + +
+
+ Waiting... + 0% +
+
+
+
+
+ + +
+
Execution Logs
+
+
Select a demo task below to begin โ†’
+
+
+
+ + +
+
Ready
+
Model: codellama:7b-instruct ยท Security: qwen2.5-coder:7b
+
NPMAI Ecosystem
- +
+ + +
+ +

Built Different

+

Everything you need to automate Python tasks โ€” without touching a single line of code.

+
+
+
+

Natural Language โ†’ Code

+

Describe your task in plain English. CodeLlama 7B understands context and generates clean, executable Python scripts tailored to your exact need.

+
AI Generation
+
+
+
+

Dual-LLM Security Audit

+

Before any code runs, Qwen2.5-Coder audits for 5 high-risk categories: data theft, system file deletion, reverse shells, resource bombs, and obfuscated code.

+
Security First
+
+
+

Auto-Debugging Loop

+

If execution fails, the AI captures the error, analyzes it, and rewrites or patches the code. Up to 15 retry attempts โ€” most tasks succeed in 1-3.

+
Self-Healing
+
+
+
+

Real-Time Log Viewer

+

Watch every step live: generation, security check, execution, and completion. A progress bar keeps you informed at all times โ€” no black-box mystery.

+
Transparency
+
+
+
+

Zero Setup Required

+

No local LLM installation, no Ollama setup, no API keys. The NPMAI cloud load balancer handles everything. Unzip the .exe and start automating.

+
Plug & Play
+
+
+
+

Auto-Installs Dependencies

+

The generated code automatically installs any missing Python libraries using subprocess. Your machine stays clean โ€” the AI handles package management.

+
Smart Imports
+
+
+
+ + +
+ +

How to Use the App

+

The entire workflow from download to completed task โ€” no coding experience needed.

-
+
+
-
-
-

๐Ÿ”ฅ Who Am I?

-

โ€œBihar Viral Boyโ€ โ€” Challenged Chief Minister & inspired millions at age 11.

-

TEDx Speaker (13) โ€” Shared "Zid aur Junoon" journey on the global stage.

-

Open Source Innovator โ€” Creating tools for education and democracy.

-

Self-taught Engineer โ€” No formal CS background, just pure grit and internet.

+
+
1
+
+

Download & Open the App

+

Download the ZIP, extract it, and double-click the .exe to launch.

+
+

No installation wizard. No admin permissions needed. The app runs as a portable executable. On first launch, Windows Defender may ask โ€” click "Run Anyway" (the app is open-source and safe).

+ NPM_AutoCode_AI.zip โ†’ Extract โ†’ NPM_AutoCode_AI.exe โ†’ Double-click +
+
-
-

โœจ Tech Toolbox

- Tech stack icons: Python, Flask, Streamlit, HTML, CSS, JS, Solidity, Raspberry Pi, Selenium, Git, GitHub, Docker, AI +
+
2
+
+

Type Your Task

+

In the "Task Description" box, write what you want Python to do โ€” in plain English.

+
+

Be specific for best results. Instead of "process my files," say "rename all .jpg files in Desktop/Photos folder by adding today's date as prefix." The clearer your prompt, the better the code.

+ Good: "Download all images from https://example.com and save them to a folder called 'downloads' on my Desktop" +
+
+ +
+
3
+
+

Click "Generate & Execute"

+

The AI generates Python code and runs a security audit before anything executes.

+
+

The app connects to NPMAI's load balancer and sends your task to CodeLlama 7B. The generated code is then reviewed by Qwen2.5-Coder for safety risks. This takes 5-15 seconds depending on task complexity.

+ Status: Generating โ†’ Auditing โ†’ Executing +
+
+
+ +
+
4
+
+

Watch the Logs

+

The log box shows every step in real-time โ€” errors are caught and auto-fixed.

+
+

If the code fails, you'll see the error captured and a debug attempt starting. The AI rewrites or patches the failing part. You don't need to do anything โ€” just wait. If a task fails after 15 attempts, try rephrasing it more simply.

+ โ†’ Debugging: captured error, retrying (attempt 1/15)... +
+
+
+ +
+
5
+
+

Task Complete โ€” Check Your Files

+

When you see "Task Completed Successfully," open the relevant folder to find your output.

+
+

Output files are saved wherever your task specified โ€” Desktop, Downloads, or any path you mentioned in the prompt. Re-open the app for a new task. Memory files are auto-cleaned on exit.

+ --- Task Completed Successfully. --- +
+
+
+
-
-

๐ŸŒŸ Major Projects & Repos

-
-
npmexplorer.netlify.app: Educational notes, 20+ news sources, AI insights.
-
npmexplorer-flask: Automated Gemini/Google LLM search (No API!).
-
npmai: LangChain-based wrapper for Grok, ChatGPT, Gemini, Copilot.
-
NPM-Edu-AI Helper: Free large-file study tool for students.
-
NPM-Youtube-Automation: Fully automated video upload workflow.
-
Blockchain Voting: Tamper-proof hardware-secure election system.
-
NPM Legal AI: Free legal intelligence toolkit.
+ +
+
+
+
+
+
+
NPM AutoCode AI
+
+
+
Step 1 โ€” App is open and ready
+
Task Description:
+
Describe your automation task here...
+ +
Logs will appear here after execution
+
+
+ +
+
+
+
+
+
NPM AutoCode AI
+
+
+
Step 2 โ€” Typing your task
+
Task Description:
+
+ Rename all .jpg files in Desktop/Photos with today's date prefixโ–Œ +
+
๐Ÿ’ก Tip: Be specific about folder paths for best results
+ +
+
+ +
+
+
+
+
+
NPM AutoCode AI
+
+
+
Step 3 โ€” AI generating code
+
+
+ # AI-generated code
+ import os, datetime
+ from pathlib import Path

+ folder = Path("~/Desktop/Photos")
+ today = datetime.date.today()
+ for f in folder.glob("*.jpg"):
+   new = f.parent / f("{today}_{f.name}")
+   f.rename(new) +
+
๐Ÿ”’ Security audit running...
+
+ +
+
+
+
+
+
NPM AutoCode AI
+
+
+
Step 4 โ€” Execution logs
+
+
+
โ†’ npmai is doing your requested task
+
โœ“ Code generation completed
+
โ†’ Security audit: PASS (No)
+
โœ“ Executing script...
+
โ†’ Renamed 47 files successfully
+
+
+
+ +
+
+
+
+
+
NPM AutoCode AI
+
+
+
Step 5 โ€” Task done! Check your folder
+
+
โœ“ Task Completed Successfully
+
+
Desktop/Photos/
+
2025-06-03_photo1.jpg
+
2025-06-03_photo2.jpg
+
2025-06-03_photo3.jpg
+
+44 more files renamed
+
+
+
+
+
+
+ + +
+
+ +

Who Uses NPM AutoCode AI?

+

Trusted by 50,000+ users across education, NGOs, and creative fields

+
+
+
+ ๐Ÿซ +

Schools & Teachers

+

Run data analysis, create mark sheets, automate admin tasks โ€” without any Python knowledge needed.

+
+
+ ๐Ÿค +

NGOs & Non-Profits

+

Process donor data, generate reports, send bulk emails โ€” all automated with a plain English description.

+
+
+ ๐ŸŽ“ +

Students

+

Automate assignments, process research data, generate charts โ€” focus on learning, not boilerplate code.

+
+
+ ๐Ÿ’ผ +

Small Businesses

+

Rename files, scrape prices, organize spreadsheets โ€” save hours of manual work every week.

+
+ ๐Ÿ”ฌ +

Researchers

+

Process datasets, generate plots, convert formats โ€” rapid prototyping without fighting syntax errors.

+
+
+ ๐ŸŒ +

Rural Communities

+

Built for everyone โ€” no CS degree required. Access AI automation from any Windows computer.

+
+
+
-
-
-

๐Ÿ’ซ Achievements

-

405K+ Facebook Followers.

-

Allen Career Institute 100% Scholarship.

-

Supported by Lok Sabha Speaker & MIT/Oxford circles.

+ +
+
+ +

Get Started in 3 Steps

+

Download, unzip, and run. No installation required.

+
+
+
1
+

Download ZIP

-
-

๐Ÿ’ฌ Social Impact

-

Building a 400K+ youth movement. Blending rural activism with world-class tech to create ethical, free AI tools for "broke students."

+
+
2
+

Unzip folder

+
+
+
3
+

Run .exe file

+ + Download NPM AutoCode AI v2.2 + +
Windows ยท Free Forever ยท Open Source ยท No API Key Needed
+
+
-
-

๐Ÿ—๏ธ Mission & Goals

-

Democratize education, AI, & civic tech for everyone. Give rural students a global stage and affordable tools. Transform adversity into action.

+ +
+
+ +

Meet the Founder

+
+ +
+ +
+
+ +
-
- Sonu Kumar's GitHub Stats - Sonu Kumar's GitHub Streak + +
+ +
+
+ Sonu Kumar, founder of NPMAI ECOSYSTEM +
+
+
Age 15
+
Kota, Rajasthan
+
Bihar, India
+
434K+ Followers
+
TEDx Speaker
+
Allen 100% Scholar
+
+ +
+ + +
+

Hi, I'm Sonu Kumar ๐Ÿ‡ฎ๐Ÿ‡ณ

+
Founder ยท NPMAI ECOSYSTEM ยท Bihar Viral Boy
+

+ Software Developer ยท AI Developer ยท Web Developer ยท Cloud Developer ยท DevOps ยท TEDx Speaker ยท Social Thinker ยท Researcher. Student at Allen Career Institute & Disha Delphi Public School, Kota (100% Scholarship, since 2022). +

+ +
+
1.5M+
PyPI downloads
+
433K+
Social followers
+
3
Research papers
+
TEDx
Speaker at 13
+
45+
LLMs in ecosystem
+
100%
Allen scholarship
+
+ +
The Journey
+
+
+
+
+
Age 9
+
Started Teaching Nursery Kids in Class 4th
+
Taught younger children while still in 4th grade โ€” planting the seed for his education equity mission.
+
+
+
+
+
+
Age 11 โ€” "The Moment"
+
Challenged Bihar's Chief Minister โ€” Went Viral Across India
+
Raised education and liquor ban issues directly to the CM. Went viral. Earned the name Bihar Viral Boy. Received support from Cabinet Ministers, Bollywood actors, and the founder of ALLEN Institute.
+
+
+
+
+
+
Age 13
+
Delivered a TEDx Talk on Education & Social Change
+
One of India's youngest TEDx speakers โ€” addressing rural education failures and the role of technology in bridging India's urban-rural divide.
+
+
+
+
+
+
Age 13-14
+
Founded NPMAI ECOSYSTEM โ€” 1.5 Million+ Downloads
+
Self-taught, no CS background. Built the entire ecosystem on free cloud infrastructure. Now serves hundreds of thousands of developers worldwide.
+
+
+
+
+
+
2026
+
Published Three Research Papers โ€” LARA, RID, IAST
+
LARA (novel RAG architecture), Representative Ideal Democracy, and Ideal Administrative System Theory.
+
+
+
+
-
-

๐Ÿง  "Rural-Zero to Revolution"

-

โ€œNo matter where you start, you can be the change. I choose code, courage, and kindness. Let's build... together.โ€

+ +
+

"Promoting Individual Journalism to every nation's village so that the democratic values of a nation can be strengthened and we can achieve Representative Ideal Democracy."

+
โ€” Sonu Kumar, Founder, NPMAI ECOSYSTEM
-
-
-
+ + + +
+

ยฉ 2026 NPM AutoCode AI ยท Powered by npmai ยท Made with โค๏ธ for people worldwide ยท Open source & free forever

+
-
-

ยฉ 2026 NPM AutoCode AI | Powered by npmai โ€ข Made with โค๏ธ for people worldwide. Open source & free forever.

-
+
+ From 955f43902346cf7a5557a48729842842936bba64 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 7 Jun 2026 10:04:08 +0530 Subject: [PATCH 33/68] Update NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 2804 +++++++++++++++------------ 1 file changed, 1554 insertions(+), 1250 deletions(-) diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html index ae0f94d..a9c10d5 100644 --- a/Docs/templates/NPM_AutoCode_AI.html +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -1,1276 +1,1580 @@ - - - - NPM AutoCode AI - - - - - + + +NPM Agent โ€” NPMAI Ecosystem + + + + - -
-
-
- - - - -
- - -
-
Free & Open Source ยท v2.2
-

Automate Python
Without Writing Code

-

Describe any task in plain English. NPM AutoCode AI generates and executes Python scripts instantly โ€” powered by NPMAI ecosystem LLMs with built-in security auditing.

- -
-
50K+
Downloads
-
Free
Forever
-
15
Retry Loops
-
v2.2
Latest
-
-
- - -
-
- -

See It In Action

-

Click a task below to watch the app work

-
- -
-
- -
-
-
-
-
-
-
NPM AutoCode AI โ€” Desktop Application
-
-
- - -
- -
-
Task Description
-
-
- -
-
- -
-
- - -
-
- Waiting... - 0% -
-
-
-
-
- - -
-
Execution Logs
-
-
Select a demo task below to begin โ†’
-
-
-
- - -
-
Ready
-
Model: codellama:7b-instruct ยท Security: qwen2.5-coder:7b
-
NPMAI Ecosystem
-
-
- - -
-
๐Ÿ“Š Plot a bar chart from CSV
-
๐Ÿ“ Rename 100 files in folder
-
๐ŸŒ Scrape website & save to Excel
-
๐Ÿ“ง Send email via Gmail
-
๐Ÿ”’ Sort & zip all PDF files
+ See it in action โ†’ +
+
+
0
Downloads
+
21
Tools
+
Free
Forever
+
45+
LLMs
+
+
+
+ scroll +
+
+ +
+ + +
+
+
Live Demo
+

Watch the Agent Think

+

Click a task chip to see the full pipeline in motion

+
+
+
+ +
+
+
+
+
+
NPM Agent โ€” NPMAI Ecosystem v3.0
+
MCP :7799
-
- - -
- -

Built Different

-

Everything you need to automate Python tasks โ€” without touching a single line of code.

-
-
-
-

Natural Language โ†’ Code

-

Describe your task in plain English. CodeLlama 7B understands context and generates clean, executable Python scripts tailored to your exact need.

-
AI Generation
-
-
-
-

Dual-LLM Security Audit

-

Before any code runs, Qwen2.5-Coder audits for 5 high-risk categories: data theft, system file deletion, reverse shells, resource bombs, and obfuscated code.

-
Security First
-
-
-
-

Auto-Debugging Loop

-

If execution fails, the AI captures the error, analyzes it, and rewrites or patches the code. Up to 15 retry attempts โ€” most tasks succeed in 1-3.

-
Self-Healing
-
-
-
-

Real-Time Log Viewer

-

Watch every step live: generation, security check, execution, and completion. A progress bar keeps you informed at all times โ€” no black-box mystery.

-
Transparency
-
-
-
-

Zero Setup Required

-

No local LLM installation, no Ollama setup, no API keys. The NPMAI cloud load balancer handles everything. Unzip the .exe and start automating.

-
Plug & Play
-
-
-
-

Auto-Installs Dependencies

-

The generated code automatically installs any missing Python libraries using subprocess. Your machine stays clean โ€” the AI handles package management.

-
Smart Imports
-
-
-
- - -
- -

How to Use the App

-

The entire workflow from download to completed task โ€” no coding experience needed.

- -
-
- -
-
1
-
-

Download & Open the App

-

Download the ZIP, extract it, and double-click the .exe to launch.

-
-

No installation wizard. No admin permissions needed. The app runs as a portable executable. On first launch, Windows Defender may ask โ€” click "Run Anyway" (the app is open-source and safe).

- NPM_AutoCode_AI.zip โ†’ Extract โ†’ NPM_AutoCode_AI.exe โ†’ Double-click -
-
+ +
+ +
+ +
+
Navigation
+
๐Ÿ’ฌ Agent
+
๐Ÿ“‹ History
+
๐Ÿ›  Tools
+
โš™ Settings
+
๐Ÿ‘ค Founder
+
๐Ÿ“– Docs
+
+
+
+ MCP Server Active :7799
- -
-
2
-
-

Type Your Task

-

In the "Task Description" box, write what you want Python to do โ€” in plain English.

-
-

Be specific for best results. Instead of "process my files," say "rename all .jpg files in Desktop/Photos folder by adding today's date as prefix." The clearer your prompt, the better the code.

- Good: "Download all images from https://example.com and save them to a folder called 'downloads' on my Desktop" -
-
-
- -
-
3
-
-

Click "Generate & Execute"

-

The AI generates Python code and runs a security audit before anything executes.

-
-

The app connects to NPMAI's load balancer and sends your task to CodeLlama 7B. The generated code is then reviewed by Qwen2.5-Coder for safety risks. This takes 5-15 seconds depending on task complexity.

- Status: Generating โ†’ Auditing โ†’ Executing -
-
-
- -
-
4
-
-

Watch the Logs

-

The log box shows every step in real-time โ€” errors are caught and auto-fixed.

-
-

If the code fails, you'll see the error captured and a debug attempt starting. The AI rewrites or patches the failing part. You don't need to do anything โ€” just wait. If a task fails after 15 attempts, try rephrasing it more simply.

- โ†’ Debugging: captured error, retrying (attempt 1/15)... -
-
-
- -
-
5
-
-

Task Complete โ€” Check Your Files

-

When you see "Task Completed Successfully," open the relevant folder to find your output.

-
-

Output files are saved wherever your task specified โ€” Desktop, Downloads, or any path you mentioned in the prompt. Re-open the app for a new task. Memory files are auto-cleaned on exit.

- --- Task Completed Successfully. --- -
-
-
-
- - -
-
-
-
-
-
-
NPM AutoCode AI
-
-
-
Step 1 โ€” App is open and ready
-
Task Description:
-
Describe your automation task here...
- -
Logs will appear here after execution
-
-
- -
-
-
-
-
-
NPM AutoCode AI
-
-
-
Step 2 โ€” Typing your task
-
Task Description:
-
- Rename all .jpg files in Desktop/Photos with today's date prefixโ–Œ -
-
๐Ÿ’ก Tip: Be specific about folder paths for best results
- -
+ +
+
+
Ready0%
+
- -
-
-
-
-
-
NPM AutoCode AI
-
-
-
Step 3 โ€” AI generating code
-
-
- # AI-generated code
- import os, datetime
- from pathlib import Path

- folder = Path("~/Desktop/Photos")
- today = datetime.date.today()
- for f in folder.glob("*.jpg"):
-   new = f.parent / f("{today}_{f.name}")
-   f.rename(new) -
-
๐Ÿ”’ Security audit running...
+
+
+
Hello! I'm NPM Agent. Describe any task โ€” I'll plan it, generate secure code, and execute it on your machine. What would you like to automate?
- -
-
-
-
-
-
NPM AutoCode AI
-
-
-
Step 4 โ€” Execution logs
-
-
-
โ†’ npmai is doing your requested task
-
โœ“ Code generation completed
-
โ†’ Security audit: PASS (No)
-
โœ“ Executing script...
-
โ†’ Renamed 47 files successfully
-
-
+
+
Execution Logs
- -
-
-
-
-
-
NPM AutoCode AI
-
-
-
Step 5 โ€” Task done! Check your folder
-
-
โœ“ Task Completed Successfully
-
-
Desktop/Photos/
-
2025-06-03_photo1.jpg
-
2025-06-03_photo2.jpg
-
2025-06-03_photo3.jpg
-
+44 more files renamed
-
-
+
+ + +
-
- - -
-
- -

Who Uses NPM AutoCode AI?

-

Trusted by 50,000+ users across education, NGOs, and creative fields

+
+ +
+ + + + + + +
+
+ + +
+ + +
+
+
Capabilities
+

Built to Compete.
Free to Use.

+

Everything Claude Code does โ€” on local models, with a GUI, for everyone.

+
+
+
+ ๐Ÿง  +

Multi-LLM Agent Pipeline

+

4 separate LLMs for planning, code generation, security auditing, and verification โ€” each with fallback chains. Not a chatbot. A real agent loop.

+ Plan โ†’ Generate โ†’ Audit โ†’ Execute โ†’ Verify +
+
+ ๐Ÿ”’ +

Dual-LLM Security Audit

+

Every generated script is reviewed by qwen2.5-coder:7b (fallback: falcon:7b) before any execution. 5 risk categories checked. Kill button always available.

+ Security First +
+
+ ๐Ÿ”„ +

Auto-Debug Loop (12 retries)

+

Execution fails? The agent captures the error, sends it back to CodeLlama, and rewrites or patches the code. Partial progress is preserved between retries.

+ Self-Healing +
+
+ ๐Ÿ“ +

File System Awareness

+

On startup, the agent scans Desktop, Downloads, Documents, Pictures, Videos, Music. Knows your files before you type. No paths needed.

+ Workspace Scanner +
+
+ ๐Ÿ”Œ +

Embedded MCP Server

+

Start a TCP MCP server from the sidebar. 50+ tool endpoints exposed. Connect from Claude Desktop or any MCP client. Power your other projects.

+ MCP Protocol +
+
+ ๐ŸŽค +

Voice Input + subprocess

+

Speak your task. Code runs as a real child process โ€” not exec(). Streams stdout live. Kill any script instantly. Real isolation, real safety.

+ Voice + Isolation +
+
+
+ +
+ + +
+
+
Tool Registry
+

21 Integrated Tools

+

All tools available to the agent automatically. No configuration needed. Just describe your task.

+
+
+
+ ๐Ÿ“ง EmailTool ยท Gmail SMTP/IMAP ยท Bulk CSV + ๐Ÿ“ FileTool ยท Rename ยท Zip ยท Organize + ๐Ÿ“„ PDFTool ยท Merge ยท Split ยท Extract + ๐ŸŒ WebTool ยท Scrape ยท Playwright ยท API + ๐Ÿ“Š SpreadsheetTool ยท CSV ยท Excel ยท Sheets + โญ GitHubTool ยท Issues ยท Push ยท Clone + ๐Ÿ’ฌ SlackTool ยท Messages ยท Upload ยท Read + ๐ŸŽฎ DiscordTool ยท Webhooks ยท Embeds + ๐Ÿ“ฑ WhatsAppTool ยท Instant Messages + ๐Ÿ““ NotionTool ยท Pages ยท Databases + + ๐Ÿ“ง EmailTool ยท Gmail SMTP/IMAP ยท Bulk CSV + ๐Ÿ“ FileTool ยท Rename ยท Zip ยท Organize + ๐Ÿ“„ PDFTool ยท Merge ยท Split ยท Extract + ๐ŸŒ WebTool ยท Scrape ยท Playwright ยท API + ๐Ÿ“Š SpreadsheetTool ยท CSV ยท Excel ยท Sheets + โญ GitHubTool ยท Issues ยท Push ยท Clone + ๐Ÿ’ฌ SlackTool ยท Messages ยท Upload ยท Read + ๐ŸŽฎ DiscordTool ยท Webhooks ยท Embeds + ๐Ÿ“ฑ WhatsAppTool ยท Instant Messages + ๐Ÿ““ NotionTool ยท Pages ยท Databases +
+
+
+
+ ๐Ÿฆ TwitterTool ยท Tweets ยท Timeline + ๐Ÿ–ฅ SystemTool ยท Shell ยท Clipboard ยท Screenshot + ๐Ÿ–ผ ImageTool ยท Resize ยท OCR ยท Compress + โฐ SchedulerTool ยท Cron ยท Background + ๐ŸŽซ JiraTool ยท Issues ยท Sprints + โœˆ TelegramTool ยท Bot Messages + ๐Ÿ”ฒ QRTool ยท Generate QR Codes + ๐ŸŽค VoiceTool ยท TTS ยท STT + ๐Ÿ‘ WatcherTool ยท File Change Detection + ๐Ÿ“š RAGTool ยท Large Doc Query ยท Summarize + ๐Ÿ–ง SSHTool ยท Remote Commands ยท SFTP + ๐Ÿฆ TwitterTool ยท Tweets ยท Timeline + ๐Ÿ–ฅ SystemTool ยท Shell ยท Clipboard ยท Screenshot + ๐Ÿ–ผ ImageTool ยท Resize ยท OCR ยท Compress + โฐ SchedulerTool ยท Cron ยท Background + ๐ŸŽซ JiraTool ยท Issues ยท Sprints + โœˆ TelegramTool ยท Bot Messages + ๐Ÿ”ฒ QRTool ยท Generate QR Codes + ๐ŸŽค VoiceTool ยท TTS ยท STT + ๐Ÿ‘ WatcherTool ยท File Change Detection + ๐Ÿ“š RAGTool ยท Large Doc Query ยท Summarize + ๐Ÿ–ง SSHTool ยท Remote Commands ยท SFTP +
+
+
+ +
+ + +
+
+
Agent Architecture
+

How the Agent Thinks

+

Every task goes through 6 stages. Each stage runs a different LLM for the right job.

+
+
+
+
01
+ ๐Ÿ” +
Scan
+
Workspace scanner reads your file system โ€” folders, files, paths โ€” before anything else
+
โ†’
+
+
+
02
+ ๐Ÿ—บ +
Plan
+
llama3.2:3b breaks your task into 2-5 atomic, independently verifiable steps
+
โ†’
+
+
+
03
+ โšก +
Generate
+
codellama:7b writes Python for each step using the 21 tool templates as scaffolding
+
โ†’
+
+
+
04
+ ๐Ÿ”’ +
Audit
+
qwen2.5-coder checks 5 risk categories. BLOCK or ALLOW โ€” no ambiguity
+
โ†’
+
+
+
05
+ โ–ถ +
Execute
+
Real subprocess.Popen โ€” not exec(). stdout streams live. Kill anytime.
+
โ†’
+
+
+
06
+ โœ“ +
Verify
+
A second LLM checks "did this actually complete?" โ€” not just exit code 0
+
+
+
+ +
+ + +
+
+
MCP Protocol
+

Power Your Other Projects

+

Start the embedded MCP server from the app sidebar. All 21 tools become available to Claude Desktop, your own LLMs, or any MCP client.

+
+
+
+
MCP Client ยท TCP ยท Port 7799
+
# Connect from Claude Desktop
+# Add to mcp_config.json:
+{
+  "npm_agent": {
+    "type": "tcp",
+    "host": "localhost",
+    "port": 7799
+  }
+}
+
+# Or call directly:
+import socket, json
+
+def call_tool(tool, params):
+  s = socket.socket()
+  s.connect(("localhost", 7799))
+  s.sendall(json.dumps({
+    "tool": tool,
+    "params": params
+  }).encode() + b"\n")
+  return json.loads(s.recv(65536))
+
+# Example: send an email
+result = call_tool("email_send", {
+  "to": "teacher@school.edu",
+  "subject": "Attendance Report",
+  "body": "Today's report attached."
+})
+
+
+
50+ Exposed Endpoints
+
๐Ÿ’ฌ
npmai_chat
Chat with any of 45+ models with named memory
+
๐Ÿค–
npmai_run_task
Full agent pipeline from any external client
+
๐Ÿ“ง
email_send
Send email ยท email_bulk ยท email_inbox
+
โญ
github_issue
Create issues ยท push files ยท clone repos
+
๐Ÿ’ฌ
slack_send
Send messages ยท read channels ยท upload files
+
๐Ÿ“š
rag_query
Query large documents via npmai Rag class
+
๐Ÿ–ฅ
system_run
Execute shell commands on the host machine
+
๐Ÿ“Š
workspace_scan
Scan file system, returns full path map
+
+
+
+ +
+ + +
+
+
The Builder
+

Meet the Founder

+
+
+
+
+ Sonu Kumar +
-
-
- ๐Ÿซ -

Schools & Teachers

-

Run data analysis, create mark sheets, automate admin tasks โ€” without any Python knowledge needed.

-
-
- ๐Ÿค -

NGOs & Non-Profits

-

Process donor data, generate reports, send bulk emails โ€” all automated with a plain English description.

-
-
- ๐ŸŽ“ -

Students

-

Automate assignments, process research data, generate charts โ€” focus on learning, not boilerplate code.

-
-
- ๐Ÿ’ผ -

Small Businesses

-

Rename files, scrape prices, organize spreadsheets โ€” save hours of manual work every week.

-
-
- ๐Ÿ”ฌ -

Researchers

-

Process datasets, generate plots, convert formats โ€” rapid prototyping without fighting syntax errors.

-
-
- ๐ŸŒ -

Rural Communities

-

Built for everyone โ€” no CS degree required. Access AI automation from any Windows computer.

-
+
Sonu Kumar ๐Ÿ‡ฎ๐Ÿ‡ณ
+
Founder ยท NPMAI ECOSYSTEM ยท Bihar Viral Boy
+
+ Age 15 + Bihar, India + Kota, Rajasthan + 434K+ Followers + TEDx Speaker + Allen 100% Scholar
-
- - -
-
- -

Get Started in 3 Steps

-

Download, unzip, and run. No installation required.

-
-
-
1
-

Download ZIP

-
-
-
2
-

Unzip folder

-
-
-
3
-

Run .exe file

-
-
- - Download NPM AutoCode AI v2.2 - -
Windows ยท Free Forever ยท Open Source ยท No API Key Needed
+ -
- - -
-
- -

Meet the Founder

+
+
+
+
1.5M+
PyPI downloads
+
433K+
Social followers
+
TEDx
Speaker at 13
+
100%
Allen scholarship
+
45+
LLMs in ecosystem
+
3
Research papers 2026
- -
- -
-
- -
-
- - -
- -
-
- Sonu Kumar, founder of NPMAI ECOSYSTEM -
-
-
Age 15
-
Kota, Rajasthan
-
Bihar, India
-
434K+ Followers
-
TEDx Speaker
-
Allen 100% Scholar
-
- -
- - -
-

Hi, I'm Sonu Kumar ๐Ÿ‡ฎ๐Ÿ‡ณ

-
Founder ยท NPMAI ECOSYSTEM ยท Bihar Viral Boy
-

- Software Developer ยท AI Developer ยท Web Developer ยท Cloud Developer ยท DevOps ยท TEDx Speaker ยท Social Thinker ยท Researcher. Student at Allen Career Institute & Disha Delphi Public School, Kota (100% Scholarship, since 2022). -

- -
-
1.5M+
PyPI downloads
-
433K+
Social followers
-
3
Research papers
-
TEDx
Speaker at 13
-
45+
LLMs in ecosystem
-
100%
Allen scholarship
-
- -
The Journey
-
-
-
-
-
Age 9
-
Started Teaching Nursery Kids in Class 4th
-
Taught younger children while still in 4th grade โ€” planting the seed for his education equity mission.
-
-
-
-
-
-
Age 11 โ€” "The Moment"
-
Challenged Bihar's Chief Minister โ€” Went Viral Across India
-
Raised education and liquor ban issues directly to the CM. Went viral. Earned the name Bihar Viral Boy. Received support from Cabinet Ministers, Bollywood actors, and the founder of ALLEN Institute.
-
-
-
-
-
-
Age 13
-
Delivered a TEDx Talk on Education & Social Change
-
One of India's youngest TEDx speakers โ€” addressing rural education failures and the role of technology in bridging India's urban-rural divide.
-
-
-
-
-
-
Age 13-14
-
Founded NPMAI ECOSYSTEM โ€” 1.5 Million+ Downloads
-
Self-taught, no CS background. Built the entire ecosystem on free cloud infrastructure. Now serves hundreds of thousands of developers worldwide.
-
-
-
-
-
-
2026
-
Published Three Research Papers โ€” LARA, RID, IAST
-
LARA (novel RAG architecture), Representative Ideal Democracy, and Ideal Administrative System Theory.
-
-
-
-
-
- - -
-

"Promoting Individual Journalism to every nation's village so that the democratic values of a nation can be strengthened and we can achieve Representative Ideal Democracy."

-
โ€” Sonu Kumar, Founder, NPMAI ECOSYSTEM
-
+
+
๐Ÿง’
Age 9
Started Teaching Nursery Kids
While in 4th grade, taught nursery children โ€” planting the seed for education equity.
+
๐Ÿ”ฅ
Age 11 โ€” "Bihar Viral Boy"
Challenged Bihar's Chief Minister
Raised education & liquor ban issues. Went viral nationally. Received support from Cabinet Ministers, Bollywood actors & ALLEN Institute founder.
+
๐ŸŽค
Age 13
TEDx Speaker
One of India's youngest TEDx speakers โ€” rural education and using tech to bridge India's urban-rural divide.
+
๐Ÿ’ป
Age 13-14
Founded NPMAI ECOSYSTEM โ€” 1.5M+ Downloads
Self-taught, zero CS background. Built entire ecosystem on free cloud infrastructure.
+
๐Ÿ“„
2026
3 Research Papers Published
LARA ยท Representative Ideal Democracy ยท Ideal Administrative System Theory
-
- - -
-

ยฉ 2026 NPM AutoCode AI ยท Powered by npmai ยท Made with โค๏ธ for people worldwide ยท Open source & free forever

-
- -
- - + },step.t*1000); + demoTimers.push(timer); + }); +} + +function killDemo(){ + clearTimers(); + removeThinking(); + addBubble('Task killed by user.',true); + document.getElementById('demo-status').textContent='Killed'; +} + +// allow typing + enter in demo input +document.getElementById('demo-input').addEventListener('keypress',e=>{ + if(e.key==='Enter') runDemo(); +}); + From 1aac61e79b7063d99ebcdc66848873ff69eb1d9a Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 7 Jun 2026 10:13:03 +0530 Subject: [PATCH 34/68] Create NPM_Agent(Under Development) --- Desktop_App/NPM_Agent(Under Development) | 1369 ++++++++++++++++++++++ 1 file changed, 1369 insertions(+) create mode 100644 Desktop_App/NPM_Agent(Under Development) diff --git a/Desktop_App/NPM_Agent(Under Development) b/Desktop_App/NPM_Agent(Under Development) new file mode 100644 index 0000000..940f1da --- /dev/null +++ b/Desktop_App/NPM_Agent(Under Development) @@ -0,0 +1,1369 @@ +"""Still Under Development""" +import os, sys, json, re, shutil, subprocess, tempfile, traceback +import threading, time, smtplib, imaplib, email as email_lib +import hashlib, base64, platform, glob, zipfile, tarfile +from pathlib import Path +from datetime import datetime +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email import encoders +from typing import Callable, Optional + + +def _ensure(pkg, import_name=None): + n = import_name or pkg + try: + __import__(n) + except ImportError: + subprocess.run([sys.executable,"-m","pip","install",pkg,"-q"],check=False) + +class ensure: + def __init__(): + for _p,_i in [ + ("npmai", "npmai"), + ("requests", "requests"), + ("beautifulsoup4","bs4"), + ("playwright", "playwright"), + ("PyGithub", "github"), + ("slack-sdk", "slack_sdk"), + ("gspread", "gspread"), + ("google-auth", "google.auth"), + ("openpyxl", "openpyxl"), + ("pandas", "pandas"), + ("Pillow", "PIL"), + ("pypdf", "pypdf"), + ("python-docx", "docx"), + ("pyttsx3", "pyttsx3"), + ("SpeechRecognition","speech_recognition"), + ("pyperclip", "pyperclip"), + ("schedule", "schedule"), + ("psutil", "psutil"), + ("watchdog", "watchdog"), + ("tweepy", "tweepy"), + ("pywhatkit", "pywhatkit"), + ("qrcode", "qrcode"), + ("cryptography", "cryptography"), + ("paramiko", "paramiko"), + ("python-dotenv","dotenv"), + ("pyautogui", "pyautogui"), + ("opencv-python","cv2"), + ("pytesseract", "pytesseract"), + ("youtube-dl", "youtube_dl"), + ("yt-dlp", "yt_dlp"), + ("discord.py", "discord"), + ("telethon", "telethon"), + ("notion-client","notion_client"), + ("todoist-api-python","todoist_api_python"), + ("jira", "jira"), + ("trello", "trello"), + ("pywin32", "win32api") if platform.system()=="Windows" else ("",""), + ]: + if _p: _ensure(_p, _i) + return "Done all requirements are ensured." + +from npmai import Ollama, Memory, Rag +from langchain_core.output_parsers import StrOutputParser + +class CredStore(ensure): + """Encrypts credentials with a machine-specific key and stores locally.""" + _PATH = Path.home() / ".npmai_agent" / "creds.json" + + @staticmethod + def _key() -> bytes: + from cryptography.fernet import Fernet + kf = Path.home() / ".npmai_agent" / ".key" + kf.parent.mkdir(exist_ok=True) + if kf.exists(): + return kf.read_bytes() + k = Fernet.generate_key() + kf.write_bytes(k) + kf.chmod(0o600) + return k + + @classmethod + def save(cls, name: str, data: dict): + from cryptography.fernet import Fernet + f = Fernet(cls._key()) + store = {} + if cls._PATH.exists(): + try: store = json.loads(f.decrypt(cls._PATH.read_bytes())) + except: store = {} + store[name] = data + cls._PATH.write_bytes(f.encrypt(json.dumps(store).encode())) + + @classmethod + def load(cls, name: str) -> dict: + from cryptography.fernet import Fernet + if not cls._PATH.exists(): return {} + try: + f = Fernet(cls._key()) + store = json.loads(f.decrypt(cls._PATH.read_bytes())) + return store.get(name, {}) + except: return {} + + @classmethod + def all_keys(cls) -> list: + from cryptography.fernet import Fernet + if not cls._PATH.exists(): return [] + try: + f = Fernet(cls._key()) + return list(json.loads(f.decrypt(cls._PATH.read_bytes())).keys()) + except: return [] + +class Workspace(ensure): + """Scans the file system and builds a context profile for the agent.""" + _PATH = Path.home() / ".npmai_agent" / "workspace.json" + + def __init__(self): + self._PATH.parent.mkdir(exist_ok=True) + self.data = self._load() + + def _load(self) -> dict: + if self._PATH.exists(): + try: return json.loads(self._PATH.read_text()) + except: pass + return {} + + def scan(self): + home = Path.home() + scan_dirs = { + "desktop": home/"Desktop", + "downloads": home/"Downloads", + "documents": home/"Documents", + "pictures": home/"Pictures", + "videos": home/"Videos", + "music": home/"Music", + } + found = {} + for name, path in scan_dirs.items(): + if path.exists(): + files = [] + try: + for f in path.iterdir(): + if f.is_file(): + files.append({"name":f.name,"size":f.stat().st_size, + "modified":datetime.fromtimestamp(f.stat().st_mtime).isoformat()}) + except: pass + found[name] = {"path":str(path),"files":files[:40]} + self.data["paths"] = found + self.data["home"] = str(home) + self.data["os"] = platform.system() + self.data["python"] = sys.version + self.data["scanned"] = datetime.now().isoformat() + self._save() + return self.data + + def update_profile(self, key, value): + self.data[key] = value + self._save() + + def _save(self): + self._PATH.write_text(json.dumps(self.data, indent=2)) + + def context_summary(self) -> str: + """Returns a short text summary passed to the planner LLM.""" + lines = [f"OS: {self.data.get('os','unknown')}", f"Home: {self.data.get('home','~')}"] + for k,v in self.data.get("paths",{}).items(): + lines.append(f"{k.capitalize()}: {v.get('path','')} ({len(v.get('files',[]))} files)") + if "user_name" in self.data: + lines.append(f"User: {self.data['user_name']}") + return "\n".join(lines) + +class ToolResult(ensure): + def __init__(self, success:bool, output:str, data=None): + self.success = success + self.output = output + self.data = data + def __str__(self): return self.output + +class EmailTool(ensure): + name = "email" + description = "Send emails via Gmail/SMTP, read inbox via IMAP, bulk email from CSV" + + @staticmethod + def send(to:str, subject:str, body:str, attachments:list=None, + cred_key:str="gmail") -> ToolResult: + creds = CredStore.load(cred_key) + user = creds.get("email","") + pwd = creds.get("password","") + host = creds.get("smtp_host","smtp.gmail.com") + port = int(creds.get("smtp_port",587)) + if not user or not pwd: + return ToolResult(False,"No email credentials. Add them in Settings โ†’ Credentials.") + try: + msg = MIMEMultipart() + msg["From"] = user; msg["To"] = to; msg["Subject"] = subject + msg.attach(MIMEText(body,"html")) + if attachments: + for fp in attachments: + with open(fp,"rb") as fh: + part = MIMEBase("application","octet-stream") + part.set_payload(fh.read()) + encoders.encode_base64(part) + part.add_header("Content-Disposition",f"attachment; filename={Path(fp).name}") + msg.attach(part) + with smtplib.SMTP(host,port) as s: + s.starttls(); s.login(user,pwd); s.sendmail(user,to,msg.as_string()) + return ToolResult(True,f"โœ“ Email sent to {to}") + except Exception as e: + return ToolResult(False,f"โœ— Email failed: {e}") + + @staticmethod + def send_bulk(csv_path:str, subject:str, body_template:str, + name_col:str="name", email_col:str="email", + cred_key:str="gmail") -> ToolResult: + import pandas as pd + try: + df = pd.read_csv(csv_path) + sent,failed = 0,0 + for _,row in df.iterrows(): + body = body_template.replace("{name}", str(row.get(name_col,""))) + for col in df.columns: + body = body.replace(f"{{{col}}}", str(row.get(col,""))) + r = EmailTool.send(str(row[email_col]),subject,body,cred_key=cred_key) + if r.success: sent+=1 + else: failed+=1 + time.sleep(0.5) + return ToolResult(True,f"โœ“ Sent {sent} emails, {failed} failed") + except Exception as e: + return ToolResult(False,f"โœ— Bulk email failed: {e}") + + @staticmethod + def read_inbox(count:int=10, cred_key:str="gmail") -> ToolResult: + creds = CredStore.load(cred_key) + user = creds.get("email",""); pwd = creds.get("password","") + host = creds.get("imap_host","imap.gmail.com") + if not user: return ToolResult(False,"No credentials.") + try: + mail = imaplib.IMAP4_SSL(host) + mail.login(user,pwd); mail.select("inbox") + _,ids = mail.search(None,"ALL") + msgs = [] + for mid in ids[0].split()[-count:]: + _,data = mail.fetch(mid,"(RFC822)") + msg = email_lib.message_from_bytes(data[0][1]) + msgs.append({"from":msg["From"],"subject":msg["Subject"],"date":msg["Date"]}) + mail.logout() + return ToolResult(True,f"โœ“ Fetched {len(msgs)} emails",msgs) + except Exception as e: + return ToolResult(False,f"โœ— IMAP failed: {e}") + +class FileTool(ensure): + name = "files" + description = "Rename, move, copy, zip, unzip, search, organize, convert files" + + @staticmethod + def bulk_rename(folder:str, pattern:str="*", prefix:str="", + suffix:str="", add_date:bool=False) -> ToolResult: + p = Path(folder); count = 0 + today = datetime.now().strftime("%Y-%m-%d") + try: + for f in p.glob(pattern): + if not f.is_file(): continue + stem = f.stem; ext = f.suffix + new_stem = f"{prefix}{today+'_' if add_date else ''}{stem}{suffix}" + f.rename(p / f"{new_stem}{ext}") + count += 1 + return ToolResult(True,f"โœ“ Renamed {count} files") + except Exception as e: + return ToolResult(False,f"โœ— Rename failed: {e}") + + @staticmethod + def zip_folder(source:str, dest:str=None) -> ToolResult: + sp = Path(source) + dp = Path(dest) if dest else sp.parent / f"{sp.name}.zip" + try: + with zipfile.ZipFile(dp,"w",zipfile.ZIP_DEFLATED) as zf: + if sp.is_dir(): + for f in sp.rglob("*"): + if f.is_file(): zf.write(f, f.relative_to(sp.parent)) + else: + zf.write(sp, sp.name) + return ToolResult(True,f"โœ“ Zipped to {dp}") + except Exception as e: + return ToolResult(False,f"โœ— Zip failed: {e}") + + @staticmethod + def unzip(zip_path:str, dest:str=None) -> ToolResult: + zp = Path(zip_path) + dp = Path(dest) if dest else zp.parent / zp.stem + try: + with zipfile.ZipFile(zp,"r") as zf: zf.extractall(dp) + return ToolResult(True,f"โœ“ Extracted to {dp}") + except Exception as e: + return ToolResult(False,f"โœ— Unzip failed: {e}") + + @staticmethod + def find_files(folder:str, pattern:str, recursive:bool=True) -> ToolResult: + p = Path(folder) + fn = p.rglob if recursive else p.glob + files = [str(f) for f in fn(pattern) if f.is_file()] + return ToolResult(True,f"โœ“ Found {len(files)} files", files) + + @staticmethod + def organize_by_type(folder:str) -> ToolResult: + type_map = { + "Images": [".jpg",".jpeg",".png",".gif",".bmp",".webp",".svg",".ico"], + "Videos": [".mp4",".avi",".mkv",".mov",".wmv",".flv"], + "Audio": [".mp3",".wav",".flac",".aac",".ogg"], + "Docs": [".pdf",".doc",".docx",".txt",".odt"], + "Sheets": [".xls",".xlsx",".csv",".ods"], + "Code": [".py",".js",".ts",".html",".css",".java",".cpp",".c"], + "Archives":[".zip",".tar",".gz",".rar",".7z"], + } + p = Path(folder); moved = 0 + try: + for f in p.iterdir(): + if not f.is_file(): continue + for cat,exts in type_map.items(): + if f.suffix.lower() in exts: + dest = p/cat; dest.mkdir(exist_ok=True) + shutil.move(str(f),str(dest/f.name)) + moved += 1; break + return ToolResult(True,f"โœ“ Organized {moved} files by type") + except Exception as e: + return ToolResult(False,f"โœ— Organize failed: {e}") + + @staticmethod + def read_file(path:str, max_chars:int=50000) -> ToolResult: + try: + content = Path(path).read_text(errors="replace") + return ToolResult(True,f"โœ“ Read {len(content)} chars", content[:max_chars]) + except Exception as e: + return ToolResult(False,f"โœ— Read failed: {e}") + + @staticmethod + def write_file(path:str, content:str) -> ToolResult: + try: + Path(path).parent.mkdir(parents=True,exist_ok=True) + Path(path).write_text(content) + return ToolResult(True,f"โœ“ Written to {path}") + except Exception as e: + return ToolResult(False,f"โœ— Write failed: {e}") + + @staticmethod + def duplicate_tree(src:str, dst:str) -> ToolResult: + try: + shutil.copytree(src,dst) + return ToolResult(True,f"โœ“ Copied {src} โ†’ {dst}") + except Exception as e: + return ToolResult(False,f"โœ— Copy tree failed: {e}") + +class PDFTool(ensure): + name = "pdf" + description = "Merge, split, extract text, add watermark, rotate PDF pages" + + @staticmethod + def extract_text(path:str) -> ToolResult: + try: + from pypdf import PdfReader + r = PdfReader(path) + text = "\n".join(p.extract_text() or "" for p in r.pages) + return ToolResult(True,f"โœ“ Extracted {len(text)} chars", text) + except Exception as e: + return ToolResult(False,f"โœ— PDF extract failed: {e}") + + @staticmethod + def merge(paths:list, out:str) -> ToolResult: + try: + from pypdf import PdfMerger + m = PdfMerger() + for p in paths: m.append(p) + m.write(out); m.close() + return ToolResult(True,f"โœ“ Merged {len(paths)} PDFs โ†’ {out}") + except Exception as e: + return ToolResult(False,f"โœ— Merge failed: {e}") + + @staticmethod + def split(path:str, out_dir:str) -> ToolResult: + try: + from pypdf import PdfReader, PdfWriter + r = PdfReader(path); Path(out_dir).mkdir(exist_ok=True) + for i,page in enumerate(r.pages): + w = PdfWriter(); w.add_page(page) + out = str(Path(out_dir)/f"page_{i+1}.pdf") + with open(out,"wb") as fh: w.write(fh) + return ToolResult(True,f"โœ“ Split into {len(r.pages)} pages in {out_dir}") + except Exception as e: + return ToolResult(False,f"โœ— Split failed: {e}") + +class WebTool(ensure): + name = "web" + description = "Scrape websites, search Google, download files, check URLs, take screenshots" + + @staticmethod + def scrape(url:str, selector:str=None) -> ToolResult: + try: + import requests + from bs4 import BeautifulSoup + r = requests.get(url, headers={"User-Agent":"Mozilla/5.0"},timeout=15) + soup = BeautifulSoup(r.text,"html.parser") + if selector: + items = [el.get_text(strip=True) for el in soup.select(selector)] + return ToolResult(True,f"โœ“ Scraped {len(items)} items", items) + return ToolResult(True,"โœ“ Page fetched", soup.get_text(separator="\n",strip=True)[:20000]) + except Exception as e: + return ToolResult(False,f"โœ— Scrape failed: {e}") + + @staticmethod + def download_file(url:str, dest:str) -> ToolResult: + try: + import requests + r = requests.get(url,stream=True,timeout=30) + Path(dest).parent.mkdir(parents=True,exist_ok=True) + with open(dest,"wb") as fh: + for chunk in r.iter_content(8192): fh.write(chunk) + return ToolResult(True,f"โœ“ Downloaded to {dest}") + except Exception as e: + return ToolResult(False,f"โœ— Download failed: {e}") + + @staticmethod + def screenshot_url(url:str, out:str="screenshot.png") -> ToolResult: + try: + from playwright.sync_api import sync_playwright + with sync_playwright() as pw: + b = pw.chromium.launch(headless=True) + pg = b.new_page(); pg.goto(url,timeout=20000) + pg.screenshot(path=out,full_page=True); b.close() + return ToolResult(True,f"โœ“ Screenshot saved to {out}") + except Exception as e: + return ToolResult(False,f"โœ— Screenshot failed: {e}") + + @staticmethod + def browser_action(url:str, actions:list) -> ToolResult: + """actions: list of dicts like {type:'click',selector:'...'} or {type:'fill',selector:'...',value:'...'}""" + try: + from playwright.sync_api import sync_playwright + results = [] + with sync_playwright() as pw: + b = pw.chromium.launch(headless=False) + pg = b.new_page(); pg.goto(url,timeout=20000) + for act in actions: + t = act.get("type","") + sel = act.get("selector","") + if t=="click": + pg.click(sel); results.append(f"Clicked {sel}") + elif t=="fill": + pg.fill(sel,act.get("value","")); results.append(f"Filled {sel}") + elif t=="wait": + pg.wait_for_timeout(int(act.get("ms",1000))) + elif t=="screenshot": + pg.screenshot(path=act.get("path","action_shot.png")) + elif t=="extract": + val = pg.inner_text(sel); results.append(val) + b.close() + return ToolResult(True,"โœ“ Browser actions completed", results) + except Exception as e: + return ToolResult(False,f"โœ— Browser action failed: {e}") + + @staticmethod + def api_call(url:str, method:str="GET", headers:dict=None, + payload:dict=None) -> ToolResult: + try: + import requests + fn = getattr(requests, method.lower()) + r = fn(url, headers=headers or {}, json=payload, timeout=20) + return ToolResult(True,f"โœ“ {method} {url} โ†’ {r.status_code}", r.json() if r.content else {}) + except Exception as e: + return ToolResult(False,f"โœ— API call failed: {e}") + +class SpreadsheetTool(ensure): + name = "spreadsheet" + description = "Read/write Excel, CSV, Google Sheets; generate charts, pivot tables" + + @staticmethod + def read_csv(path:str) -> ToolResult: + try: + import pandas as pd + df = pd.read_csv(path) + return ToolResult(True,f"โœ“ {len(df)} rows ร— {len(df.columns)} cols",df) + except Exception as e: + return ToolResult(False,f"โœ— CSV read failed: {e}") + + @staticmethod + def write_excel(data, path:str, sheet:str="Sheet1") -> ToolResult: + try: + import pandas as pd + df = data if hasattr(data,"to_excel") else pd.DataFrame(data) + df.to_excel(path,index=False,sheet_name=sheet) + return ToolResult(True,f"โœ“ Written to {path}") + except Exception as e: + return ToolResult(False,f"โœ— Excel write failed: {e}") + + @staticmethod + def google_sheets_read(sheet_id:str, range_:str="Sheet1", + cred_key:str="google") -> ToolResult: + try: + import gspread + from google.oauth2.service_account import Credentials + creds_data = CredStore.load(cred_key) + if not creds_data: + return ToolResult(False,"No Google credentials saved.") + scopes = ["https://spreadsheets.google.com/feeds", + "https://www.googleapis.com/auth/drive"] + creds = Credentials.from_service_account_info(creds_data,scopes=scopes) + gc = gspread.authorize(creds) + sh = gc.open_by_key(sheet_id) + ws = sh.worksheet(range_) + data = ws.get_all_records() + return ToolResult(True,f"โœ“ {len(data)} rows from Google Sheets", data) + except Exception as e: + return ToolResult(False,f"โœ— Sheets read failed: {e}") + +class GitHubTool(ensure): + name = "github" + description = "Create issues, push files, read repos, manage PRs, search code" + + @staticmethod + def _gh(cred_key="github"): + from github import Github + t = CredStore.load(cred_key).get("token","") + if not t: raise ValueError("No GitHub token. Add it in Settings โ†’ Credentials.") + return Github(t) + + @staticmethod + def create_issue(repo:str, title:str, body:str="", + labels:list=None, cred_key:str="github") -> ToolResult: + try: + gh = GitHubTool._gh(cred_key) + r = gh.get_repo(repo) + issue = r.create_issue(title=title, body=body, + labels=labels or []) + return ToolResult(True,f"โœ“ Issue #{issue.number} created: {issue.html_url}") + except Exception as e: + return ToolResult(False,f"โœ— GitHub issue failed: {e}") + + @staticmethod + def push_file(repo:str, path:str, content:str, + message:str="Update via NPM Agent", + cred_key:str="github") -> ToolResult: + try: + gh = GitHubTool._gh(cred_key) + r = gh.get_repo(repo) + try: + existing = r.get_contents(path) + r.update_file(path, message, content, existing.sha) + return ToolResult(True,f"โœ“ Updated {path} in {repo}") + except: + r.create_file(path, message, content) + return ToolResult(True,f"โœ“ Created {path} in {repo}") + except Exception as e: + return ToolResult(False,f"โœ— Push failed: {e}") + + @staticmethod + def list_issues(repo:str, state:str="open", + cred_key:str="github") -> ToolResult: + try: + gh = GitHubTool._gh(cred_key) + issues = gh.get_repo(repo).get_issues(state=state) + data = [{"#":i.number,"title":i.title,"state":i.state,"url":i.html_url} + for i in issues] + return ToolResult(True,f"โœ“ {len(data)} issues",data) + except Exception as e: + return ToolResult(False,f"โœ— List issues failed: {e}") + + @staticmethod + def get_readme(repo:str, cred_key:str="github") -> ToolResult: + try: + gh = GitHubTool._gh(cred_key) + r = gh.get_repo(repo) + readme = r.get_readme() + return ToolResult(True,"โœ“ README fetched", + base64.b64decode(readme.content).decode()) + except Exception as e: + return ToolResult(False,f"โœ— README failed: {e}") + + @staticmethod + def clone_repo(url:str, dest:str) -> ToolResult: + try: + subprocess.run(["git","clone",url,dest],check=True,capture_output=True) + return ToolResult(True,f"โœ“ Cloned to {dest}") + except Exception as e: + return ToolResult(False,f"โœ— Clone failed: {e}") + + @staticmethod + def git_commit_push(repo_path:str, message:str) -> ToolResult: + try: + for cmd in [["git","add","."], + ["git","commit","-m",message], + ["git","push"]]: + subprocess.run(cmd,cwd=repo_path,check=True,capture_output=True) + return ToolResult(True,f"โœ“ Committed and pushed: {message}") + except Exception as e: + return ToolResult(False,f"โœ— Git push failed: {e}") + +class SlackTool(ensure): + name = "slack" + description = "Send messages, read channels, post to threads, upload files" + + @staticmethod + def _client(cred_key="slack"): + from slack_sdk import WebClient + t = CredStore.load(cred_key).get("bot_token","") + if not t: raise ValueError("No Slack bot token. Add in Settings โ†’ Credentials.") + return WebClient(token=t) + + @staticmethod + def send_message(channel:str, text:str, + cred_key:str="slack") -> ToolResult: + try: + c = SlackTool._client(cred_key) + r = c.chat_postMessage(channel=channel,text=text) + return ToolResult(True,f"โœ“ Sent to #{channel}") + except Exception as e: + return ToolResult(False,f"โœ— Slack send failed: {e}") + + @staticmethod + def read_channel(channel:str, limit:int=20, + cred_key:str="slack") -> ToolResult: + try: + c = SlackTool._client(cred_key) + r = c.conversations_history(channel=channel,limit=limit) + msgs = [{"user":m.get("user","?"),"text":m.get("text",""), + "ts":m.get("ts","")} for m in r["messages"]] + return ToolResult(True,f"โœ“ {len(msgs)} messages",msgs) + except Exception as e: + return ToolResult(False,f"โœ— Slack read failed: {e}") + + @staticmethod + def upload_file(channel:str, file_path:str, + comment:str="", cred_key:str="slack") -> ToolResult: + try: + c = SlackTool._client(cred_key) + c.files_upload(channels=channel,file=file_path, + initial_comment=comment) + return ToolResult(True,f"โœ“ File uploaded to #{channel}") + except Exception as e: + return ToolResult(False,f"โœ— Upload failed: {e}") + +class DiscordTool(ensure): + name = "discord" + description = "Send messages to Discord channels via webhook or bot" + + @staticmethod + def send_webhook(webhook_url:str, content:str, + embeds:list=None) -> ToolResult: + try: + import requests + payload = {"content":content} + if embeds: payload["embeds"] = embeds + r = requests.post(webhook_url,json=payload,timeout=10) + return ToolResult(r.status_code in (200,204), + f"โœ“ Discord sent" if r.status_code in (200,204) + else f"โœ— Discord failed: {r.status_code}") + except Exception as e: + return ToolResult(False,f"โœ— Discord webhook failed: {e}") + +class WhatsAppTool(ensure): + name = "whatsapp" + description = "Send WhatsApp messages (requires WhatsApp Web open)" + + @staticmethod + def send(phone:str, message:str, wait:int=15) -> ToolResult: + try: + import pywhatkit + pywhatkit.sendwhatmsg_instantly(phone,message,wait_time=wait,tab_close=True) + return ToolResult(True,f"โœ“ WhatsApp sent to {phone}") + except Exception as e: + return ToolResult(False,f"โœ— WhatsApp failed: {e}") + +class NotionTool(ensure): + name = "notion" + description = "Create/read Notion pages and database entries" + + @staticmethod + def create_page(parent_id:str, title:str, content:str, + cred_key:str="notion") -> ToolResult: + try: + from notion_client import Client + token = CredStore.load(cred_key).get("token","") + if not token: return ToolResult(False,"No Notion token.") + n = Client(auth=token) + page = n.pages.create( + parent={"page_id":parent_id}, + properties={"title":{"title":[{"text":{"content":title}}]}}, + children=[{"object":"block","type":"paragraph", + "paragraph":{"rich_text":[{"text":{"content":content}}]}}] + ) + return ToolResult(True,f"โœ“ Notion page created: {page['url']}") + except Exception as e: + return ToolResult(False,f"โœ— Notion failed: {e}") + + @staticmethod + def add_db_entry(db_id:str, props:dict, + cred_key:str="notion") -> ToolResult: + try: + from notion_client import Client + token = CredStore.load(cred_key).get("token","") + n = Client(auth=token) + n.pages.create(parent={"database_id":db_id},properties=props) + return ToolResult(True,"โœ“ Notion DB entry added") + except Exception as e: + return ToolResult(False,f"โœ— Notion DB failed: {e}") + +class TwitterTool(ensure): + name = "twitter" + description = "Post tweets, read timeline, search tweets" + + @staticmethod + def tweet(text:str, cred_key:str="twitter") -> ToolResult: + try: + import tweepy + c = CredStore.load(cred_key) + client = tweepy.Client( + consumer_key=c.get("api_key",""), + consumer_secret=c.get("api_secret",""), + access_token=c.get("access_token",""), + access_token_secret=c.get("access_token_secret","") + ) + r = client.create_tweet(text=text) + return ToolResult(True,f"โœ“ Tweeted: {r.data['id']}") + except Exception as e: + return ToolResult(False,f"โœ— Tweet failed: {e}") + +class SystemTool(ensure): + name = "system" + description = "Run shell commands, manage processes, clipboard, screenshots, notifications" + + @staticmethod + def run_command(cmd:str, cwd:str=None, timeout:int=60) -> ToolResult: + try: + r = subprocess.run(cmd,shell=True,cwd=cwd,capture_output=True, + text=True,timeout=timeout) + out = r.stdout+r.stderr + return ToolResult(r.returncode==0, out.strip() or "โœ“ Done") + except subprocess.TimeoutExpired: + return ToolResult(False,"โœ— Command timed out") + except Exception as e: + return ToolResult(False,f"โœ— Command failed: {e}") + + @staticmethod + def get_clipboard() -> ToolResult: + try: + import pyperclip + return ToolResult(True,"โœ“ Clipboard read", pyperclip.paste()) + except Exception as e: + return ToolResult(False,f"โœ— Clipboard failed: {e}") + + @staticmethod + def set_clipboard(text:str) -> ToolResult: + try: + import pyperclip; pyperclip.copy(text) + return ToolResult(True,"โœ“ Copied to clipboard") + except Exception as e: + return ToolResult(False,f"โœ— Clipboard set failed: {e}") + + @staticmethod + def screenshot(out:str="screenshot.png") -> ToolResult: + try: + import pyautogui + img = pyautogui.screenshot(); img.save(out) + return ToolResult(True,f"โœ“ Screenshot saved: {out}") + except Exception as e: + return ToolResult(False,f"โœ— Screenshot failed: {e}") + + @staticmethod + def get_processes() -> ToolResult: + try: + import psutil + procs = [{"pid":p.pid,"name":p.name(),"cpu":p.cpu_percent(), + "mem_mb":round(p.memory_info().rss/1024/1024,1)} + for p in psutil.process_iter(["pid","name","cpu_percent","memory_info"])] + return ToolResult(True,f"โœ“ {len(procs)} processes", procs) + except Exception as e: + return ToolResult(False,f"โœ— Process list failed: {e}") + + @staticmethod + def notify(title:str, message:str) -> ToolResult: + try: + os_name = platform.system() + if os_name=="Windows": + from win10toast import ToastNotifier + ToastNotifier().show_toast(title,message,duration=5) + elif os_name=="Darwin": + subprocess.run(["osascript","-e",f'display notification "{message}" with title "{title}"']) + else: + subprocess.run(["notify-send",title,message]) + return ToolResult(True,"โœ“ Notification sent") + except Exception as e: + return ToolResult(False,f"โœ— Notify failed: {e}") + +class ImageTool(ensure): + name = "image" + description = "Resize, convert, compress, watermark images; OCR text from images" + + @staticmethod + def resize(path:str, width:int, height:int, out:str=None) -> ToolResult: + try: + from PIL import Image + img = Image.open(path) + img = img.resize((width,height), Image.LANCZOS) + dest = out or path + img.save(dest) + return ToolResult(True,f"โœ“ Resized to {width}ร—{height}: {dest}") + except Exception as e: + return ToolResult(False,f"โœ— Resize failed: {e}") + + @staticmethod + def convert(path:str, format:str, out:str=None) -> ToolResult: + try: + from PIL import Image + img = Image.open(path) + dest = out or str(Path(path).with_suffix(f".{format.lower()}")) + img.save(dest,format.upper()) + return ToolResult(True,f"โœ“ Converted to {dest}") + except Exception as e: + return ToolResult(False,f"โœ— Convert failed: {e}") + + @staticmethod + def ocr(path:str) -> ToolResult: + try: + import pytesseract + from PIL import Image + text = pytesseract.image_to_string(Image.open(path)) + return ToolResult(True,f"โœ“ OCR extracted {len(text)} chars", text) + except Exception as e: + return ToolResult(False,f"โœ— OCR failed: {e}") + + @staticmethod + def bulk_compress(folder:str, quality:int=75) -> ToolResult: + from PIL import Image + count=0 + for ext in ("*.jpg","*.jpeg","*.png"): + for fp in Path(folder).glob(ext): + try: + img = Image.open(fp) + img.save(fp,optimize=True,quality=quality) + count+=1 + except: pass + return ToolResult(True,f"โœ“ Compressed {count} images") + +class SchedulerTool(ensure): + name = "scheduler" + description = "Schedule tasks to run at specific times or intervals" + _jobs = {} + + @staticmethod + def schedule_task(task_id:str, cron_like:str, callback:Callable) -> ToolResult: + """cron_like: 'every 5 minutes' | 'every day at 09:00' | 'every monday at 08:00'""" + try: + import schedule + parts = cron_like.lower().split() + if "minutes" in parts: + n = int(parts[1]); schedule.every(n).minutes.do(callback) + elif "hours" in parts: + n = int(parts[1]); schedule.every(n).hours.do(callback) + elif "day" in parts and "at" in parts: + t = parts[parts.index("at")+1] + schedule.every().day.at(t).do(callback) + elif "monday" in parts: + t = parts[-1]; schedule.every().monday.at(t).do(callback) + SchedulerTool._jobs[task_id] = callback + + def _run(): + while task_id in SchedulerTool._jobs: + schedule.run_pending(); time.sleep(30) + threading.Thread(target=_run,daemon=True).start() + return ToolResult(True,f"โœ“ Scheduled task '{task_id}': {cron_like}") + except Exception as e: + return ToolResult(False,f"โœ— Schedule failed: {e}") + + @staticmethod + def cancel_task(task_id:str) -> ToolResult: + SchedulerTool._jobs.pop(task_id,None) + return ToolResult(True,f"โœ“ Cancelled '{task_id}'") + +class JiraTool(ensure): + name = "jira" + description = "Create/update Jira issues, list sprints, manage projects" + + @staticmethod + def create_issue(project:str, summary:str, description:str="", + issue_type:str="Task", cred_key:str="jira") -> ToolResult: + try: + from jira import JIRA + c = CredStore.load(cred_key) + j = JIRA(server=c.get("server",""), + basic_auth=(c.get("email",""),c.get("api_token",""))) + issue = j.create_issue(project=project,summary=summary, + description=description,issuetype={"name":issue_type}) + return ToolResult(True,f"โœ“ Jira issue {issue.key} created") + except Exception as e: + return ToolResult(False,f"โœ— Jira failed: {e}") + +class TelegramTool(ensure): + name = "telegram" + description = "Send Telegram messages via bot token" + + @staticmethod + def send(chat_id:str, text:str, cred_key:str="telegram") -> ToolResult: + try: + import requests + token = CredStore.load(cred_key).get("bot_token","") + if not token: return ToolResult(False,"No Telegram token.") + url = f"https://api.telegram.org/bot{token}/sendMessage" + r = requests.post(url,json={"chat_id":chat_id,"text":text},timeout=10) + return ToolResult(r.ok,f"โœ“ Telegram sent" if r.ok else f"โœ— {r.text}") + except Exception as e: + return ToolResult(False,f"โœ— Telegram failed: {e}") + +class QRTool(ensure): + name = "qr" + description = "Generate QR codes from any text or URL" + + @staticmethod + def generate(data:str, out:str="qrcode.png", size:int=10) -> ToolResult: + try: + import qrcode + img = qrcode.make(data,box_size=size) + img.save(out) + return ToolResult(True,f"โœ“ QR code saved: {out}") + except Exception as e: + return ToolResult(False,f"โœ— QR failed: {e}") + +class VoiceTool(ensure): + name = "voice" + description = "Text-to-speech output and speech-to-text input" + + @staticmethod + def speak(text:str) -> ToolResult: + try: + import pyttsx3 + e = pyttsx3.init(); e.say(text); e.runAndWait() + return ToolResult(True,"โœ“ Spoken") + except Exception as e: + return ToolResult(False,f"โœ— TTS failed: {e}") + + @staticmethod + def listen(seconds:int=5) -> ToolResult: + try: + import speech_recognition as sr + r = sr.Recognizer() + with sr.Microphone() as src: + audio = r.listen(src,timeout=seconds) + text = r.recognize_google(audio) + return ToolResult(True,"โœ“ Heard speech", text) + except Exception as e: + return ToolResult(False,f"โœ— Listen failed: {e}") + +class WatcherTool(ensure): + name = "watcher" + description = "Watch folders for file changes and trigger actions" + + @staticmethod + def watch(folder:str, callback:Callable, patterns:list=None) -> ToolResult: + try: + from watchdog.observers import Observer + from watchdog.events import FileSystemEventHandler + class Handler(FileSystemEventHandler): + def on_created(self,event): + if not event.is_directory: callback(event.src_path) + def on_modified(self,event): + if not event.is_directory: callback(event.src_path) + obs = Observer() + obs.schedule(Handler(),folder,recursive=True) + obs.start() + return ToolResult(True,f"โœ“ Watching {folder}") + except Exception as e: + return ToolResult(False,f"โœ— Watch failed: {e}") + +class RAGTool(ensure): + name = "rag" + description = "Query large documents using RAG pipeline via npmai Rag class" + + @staticmethod + def query_document(doc_path:str, question:str, + chunk_size:int=500) -> ToolResult: + try: + rag = Rag() + text = Path(doc_path).read_text(errors="replace") + if doc_path.endswith(".pdf"): + from pypdf import PdfReader + text = "\n".join(p.extract_text() or "" for p in PdfReader(doc_path).pages) + rag.load_text(text, chunk_size=chunk_size) + answer = rag.query(question) + return ToolResult(True,"โœ“ RAG query answered", answer) + except Exception as e: + return ToolResult(False,f"โœ— RAG query failed: {e}") + + @staticmethod + def summarize_large_file(path:str, model:str="mistral:7b") -> ToolResult: + try: + if path.endswith(".pdf"): + from pypdf import PdfReader + text = "\n".join(p.extract_text() or "" for p in PdfReader(path).pages) + else: + text = Path(path).read_text(errors="replace") + chunks = [text[i:i+3000] for i in range(0,len(text),3000)] + llm = Ollama(model=model, temperature=0.2, + change=True, Models=["llama3.2:3b"]) + summaries = [] + for ch in chunks[:10]: + s = llm.invoke(f"Summarize this section briefly:\n{ch}") + summaries.append(s) + combined = "\n".join(summaries) + final = llm.invoke(f"Create a final comprehensive summary from these section summaries:\n{combined}") + return ToolResult(True,"โœ“ Document summarized", final) + except Exception as e: + return ToolResult(False,f"โœ— Summarize failed: {e}") + +class SSHTool(ensure): + name = "ssh" + description = "Run commands on remote servers via SSH, transfer files via SFTP" + + @staticmethod + def run(host:str, command:str, cred_key:str="ssh") -> ToolResult: + try: + import paramiko + c = CredStore.load(cred_key) + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect(host,username=c.get("user",""), + password=c.get("password",""), + key_filename=c.get("key_path",None)) + _,stdout,stderr = client.exec_command(command) + out = stdout.read().decode(); err = stderr.read().decode() + client.close() + return ToolResult(True,out or "โœ“ Done", out+err) + except Exception as e: + return ToolResult(False,f"โœ— SSH failed: {e}") + + @staticmethod + def upload(host:str, local:str, remote:str, cred_key:str="ssh") -> ToolResult: + try: + import paramiko + c = CredStore.load(cred_key) + t = paramiko.Transport((host,22)) + t.connect(username=c.get("user",""),password=c.get("password","")) + sftp = paramiko.SFTPClient.from_transport(t) + sftp.put(local,remote); sftp.close(); t.close() + return ToolResult(True,f"โœ“ Uploaded {local} โ†’ {remote}") + except Exception as e: + return ToolResult(False,f"โœ— SFTP upload failed: {e}") + + +class Executor(ensure): + """Writes code to temp file, runs as child process, streams output.""" + + def __init__(self, log_cb:Callable=None, timeout:int=120): + self._log = log_cb or print + self._timeout = timeout + self._proc = None + self._killed = False + + def run(self, code:str) -> tuple: + """Returns (success:bool, full_output:str)""" + self._killed = False + tmp = tempfile.NamedTemporaryFile(suffix=".py", delete=False, + mode="w", encoding="utf-8") + tmp.write(code); tmp.close() + output_lines = [] + try: + self._proc = subprocess.Popen( + [sys.executable, tmp.name], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, bufsize=1 + ) + for line in self._proc.stdout: + line = line.rstrip() + output_lines.append(line) + self._log(f'{line}') + self._proc.wait(timeout=self._timeout) + success = (self._proc.returncode == 0) and not self._killed + return success, "\n".join(output_lines) + except subprocess.TimeoutExpired: + self.kill() + return False, "TIMEOUT: Script exceeded time limit." + except Exception as e: + return False, f"EXECUTOR ERROR: {e}" + finally: + try: os.unlink(tmp.name) + except: pass + self._proc = None + + def kill(self): + self._killed = True + if self._proc: + try: self._proc.kill() + except: pass + +class AgentBrain(ensure): + """ + Full pipeline: + Intent โ†’ Plan โ†’ [for each step: Generate โ†’ Audit โ†’ Execute โ†’ Verify] โ†’ Done + """ + PARSER = StrOutputParser() + + TOOLS = { + "email": EmailTool, + "files": FileTool, + "pdf": PDFTool, + "web": WebTool, + "spreadsheet": SpreadsheetTool, + "github": GitHubTool, + "slack": SlackTool, + "discord": DiscordTool, + "whatsapp": WhatsAppTool, + "notion": NotionTool, + "twitter": TwitterTool, + "system": SystemTool, + "image": ImageTool, + "scheduler": SchedulerTool, + "jira": JiraTool, + "telegram": TelegramTool, + "qr": QRTool, + "voice": VoiceTool, + "watcher": WatcherTool, + "rag": RAGTool, + "ssh": SSHTool, + } + + TOOLS_SUMMARY = "\n".join( + f"- {k}: {v.description}" for k,v in TOOLS.items() + ) + + def __init__(self, log_cb:Callable=None, progress_cb:Callable=None, + status_cb:Callable=None): + self._log = log_cb or print + self._progress = progress_cb or (lambda v: None) + self._status = status_cb or (lambda s: None) + self.workspace = Workspace() + self.executor = Executor(log_cb=log_cb) + self.planner = Ollama(model="llama3.2:3b", temperature=0.2, + change=True, Models=["mistral:7b"]) + self.coder = Ollama(model="codellama:7b-instruct", temperature=0.3, + change=True, Models=["deepseek-coder:6.7b"]) + self.auditor = Ollama(model="qwen2.5-coder:7b", temperature=0.1, + change=True, Models=["falcon:7b-instruct"]) + self.verifier = Ollama(model="llama3.2:3b", temperature=0.1, + change=True, Models=["mistral:7b"]) + self.chatter = Ollama(model="granite3.3:2b", temperature=0.7, + change=True, Models=["llama3.2:1b"]) + self.mem_plan = Memory("agent_plan") + self.mem_code = Memory("agent_code") + self.mem_chat = Memory("agent_chat") + self.mem_tasks = Memory("agent_tasks") + + def _emit(self, msg, color="#8E8AAE"): + self._log(f'{msg}') + + def _clean_code(self, raw:str) -> str: + s = raw.strip() + for tag in ("```python","```"): + if s.startswith(tag): s = s[len(tag):] + if s.endswith("```"): s = s[:-3] + return s.strip() + + def chat(self, user_msg:str) -> str: + self.mem_chat.save_context("user", user_msg) + hist = self.mem_chat.load_memory_variables() + prompt = f"""You are NPM Agent, an advanced AI assistant powered by NPMAI Ecosystem. +You help with coding, automation, analysis, and general questions. +Be concise, helpful, and friendly. If the user wants to run something on their computer, +tell them to phrase it as a task (e.g. "do X on my computer"). + +Conversation history: +{hist} + +User: {user_msg} +Assistant:""" + resp = self.chatter.invoke(prompt) + self.mem_chat.save_context("assistant", resp) + return resp + + def plan(self, task:str) -> list: + ws = self.workspace.context_summary() + tool_list = TOOLS_SUMMARY + prompt = f"""You are a task planner. Break the following user task into 2-5 clear, +atomic executable steps. Each step should be independently verifiable. + +User's computer context: +{ws} + +Available tools (Python modules): +{tool_list} + +User Task: {task} + +Return ONLY a JSON array of step strings, no explanation: +["step 1 description", "step 2 description", ...]""" + raw = self.planner.invoke(prompt) + try: + match = re.search(r'\[.*?\]', raw, re.DOTALL) + if match: + steps = json.loads(match.group()) + return [str(s) for s in steps] + except: pass + return [task] + + def generate_code(self, step:str, task:str, prev_globals:str="", + error:str="") -> str: + ws = self.workspace.context_summary() + fix = f"\nPrevious error to fix:\n{error}" if error else "" + reuse = f"\nPreviously executed globals available:\n{prev_globals}" if prev_globals else "" + prompt = f"""You are an expert Python code generator for a desktop automation agent. +Write ONLY executable Python code โ€” NO explanations outside # comments. +Install any missing libraries via subprocess pip install in the code. +Your entire response goes into a .py file run by Python subprocess. +Do NOT use exec() โ€” write complete standalone scripts. + +User's system: +{ws} + +Available tool templates you can import and use: +from agent_core import EmailTool, FileTool, WebTool, SpreadsheetTool +from agent_core import GitHubTool, SlackTool, PDFTool, ImageTool +from agent_core import SystemTool, TelegramTool, QRTool, RAGTool, SSHTool +from agent_core import CredStore, Workspace + +Full task context: {task} +This specific step to implement: {step} +{reuse}{fix} + +Write complete Python code:""" + raw = self.coder.invoke(prompt) + return self._clean_code(raw) + + def audit(self, code:str) -> tuple: + """Returns (is_safe:bool, reason:str)""" + prompt = f"""[SECURITY AUDITOR] Analyze this Python code. +HIGH RISK โ€” answer 'BLOCK': deleting system files unprompted, stealing credentials/passwords/cookies, +reverse shells, fork bombs, obfuscated destructive code, accessing /etc/passwd or Windows SAM. +SAFE โ€” answer 'ALLOW': file operations, web scraping, sending emails, pip installs, +reading user files, making API calls, browser automation for user tasks. + +CODE: +{code} + +Answer format: ALLOW or BLOCK followed by one line reason.""" + r = self.auditor.invoke(prompt).strip() + safe = r.upper().startswith("ALLOW") + return safe, r + + def verify(self, step:str, output:str, success:bool) -> tuple: + """Returns (verified:bool, feedback:str)""" + if not success: + return False, output + prompt = f"""Did this step complete successfully? +Step: {step} +Execution output: {output[:500]} + +Answer 'YES' if step completed, 'NO' if it clearly failed. One word only.""" + r = self.verifier.invoke(prompt).strip().upper() + return r.startswith("YES"), output + + def run_task(self, task:str, killed_flag:list=None) -> bool: + """Main entry point. killed_flag=[False] passed from UI kill button.""" + if killed_flag is None: killed_flag = [False] + + self._emit(f"โ–ธ Starting task: {task}", "#00E5FF") + self._progress(5) + + if len(task.split()) < 4 and "?" in task: + resp = self.chat(task) + self._emit(resp, "#F0EEFF") + self._progress(100) + return True + + self._emit("โ–ธ Scanning workspaceโ€ฆ", "#4E4B6A") + try: self.workspace.scan() + except: pass + self._progress(10) + + self._status("Planningโ€ฆ") + self._emit("โ–ธ Planning stepsโ€ฆ", "#A78BFA") + steps = self.plan(task) + self._emit(f"โ–ธ Plan: {len(steps)} step(s)", "#A78BFA") + for i,s in enumerate(steps): + self._emit(f" {i+1}. {s}", "#4E4B6A") + self._progress(20) + + total_steps = len(steps) + prev_globals = "" + + for step_idx, step in enumerate(steps): + if killed_flag[0]: + self._emit("โœ— Task cancelled by user.", "#FF6B9D"); return False + + step_num = step_idx + 1 + prog_base = 20 + int((step_idx / total_steps) * 70) + self._emit(f"\nโ–ธ Step {step_num}/{total_steps}: {step}", "#00E5FF") + self._status(f"Step {step_num}/{total_steps}โ€ฆ") + + tried = 0 + max_retries = 12 + last_error = "" + + while tried < max_retries: + if killed_flag[0]: + self._emit("โœ— Cancelled.", "#FF6B9D"); return False + + self._emit(f" โ†’ Generating code (attempt {tried+1})โ€ฆ", "#4E4B6A") + self._progress(prog_base + 2) + code = self.generate_code(step, task, prev_globals, last_error) + self.mem_code.save_context(step, code) + + self._emit(" โ†’ Security auditโ€ฆ", "#A78BFA") + safe, reason = self.audit(code) + if not safe: + self._emit(f" ๐Ÿšซ BLOCKED: {reason}", "#FF6B9D") + self._progress(100) + return False + + self._emit(" โœ“ Audit passed", "#2AFFA0") + self._progress(prog_base + 6) + + self._emit(" โ†’ Executingโ€ฆ", "#4E4B6A") + self._status("Executingโ€ฆ") + success, output = self.executor.run(code) + self._progress(prog_base + 10) + + verified, feedback = self.verify(step, output, success) + + if verified: + self._emit(f" โœ“ Step {step_num} complete", "#2AFFA0") + prev_globals += f"\n# After step {step_num}:\n{output[:300]}" + self.mem_plan.save_context(f"step_{step_num}", "DONE: "+output[:200]) + break + else: + tried += 1 + last_error = output + self._emit(f" โš  Retry {tried}/{max_retries}: {output[:120]}", "#FFB347") + self._progress(prog_base + 5 + tried) + + else: + self._emit(f" โœ— Step {step_num} failed after {max_retries} attempts.", "#FF6B9D") + self._status("Failed") + self._save_task_history(task, False) + return False + + self._progress(100) + self._status("Done โœ“") + self._emit("\nโœ“ โ”€โ”€โ”€ All steps completed successfully โ”€โ”€โ”€", "#2AFFA0") + self._save_task_history(task, True) + return True + + def _save_task_history(self, task:str, success:bool): + hist_path = Path.home()/".npmai_agent"/"history.json" + hist_path.parent.mkdir(exist_ok=True) + history = [] + if hist_path.exists(): + try: history = json.loads(hist_path.read_text()) + except: history = [] + history.insert(0,{ + "task": task, + "success":success, + "time": datetime.now().isoformat(), + }) + history = history[:50] + hist_path.write_text(json.dumps(history,indent=2)) + + @staticmethod + def load_task_history() -> list: + p = Path.home()/".npmai_agent"/"history.json" + if p.exists(): + try: return json.loads(p.read_text()) + except: pass + return [] +"""Still Under Development""" From 9172c56f90a2e29a7e23f4b455fadaf7cdd63de8 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 7 Jun 2026 10:14:09 +0530 Subject: [PATCH 35/68] Create NPM-AutoCode-AI(V3).py --- Desktop_App/NPM-AutoCode-AI(V3).py | 1245 ++++++++++++++++++++++++++++ 1 file changed, 1245 insertions(+) create mode 100644 Desktop_App/NPM-AutoCode-AI(V3).py diff --git a/Desktop_App/NPM-AutoCode-AI(V3).py b/Desktop_App/NPM-AutoCode-AI(V3).py new file mode 100644 index 0000000..a0ccdbc --- /dev/null +++ b/Desktop_App/NPM-AutoCode-AI(V3).py @@ -0,0 +1,1245 @@ +import sys, os, json, threading, time, math, random, socket +from pathlib import Path +from datetime import datetime +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from agent_core import ( + AgentBrain, Workspace, CredStore, TOOLS, TOOLS_SUMMARY, + EmailTool, FileTool, WebTool, SpreadsheetTool, GitHubTool, + SlackTool, PDFTool, ImageTool, SystemTool, TelegramTool, + QRTool, RAGTool, SSHTool, DiscordTool, VoiceTool, SchedulerTool, + WatcherTool, NotionTool, TwitterTool, JiraTool, WhatsAppTool +) + +from PySide6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QTextEdit, QLineEdit, QPushButton, QFrame, QScrollArea, + QStackedWidget, QSizePolicy, QGraphicsOpacityEffect, + QSpacerItem, QTabWidget, QFormLayout, QComboBox, + QCheckBox, QDialog, QDialogButtonBox, QListWidget, QListWidgetItem, + QSplitter, QProgressBar, QFileDialog, QMessageBox, QToolButton, + QGroupBox +) +from PySide6.QtCore import ( + QThread, Signal, Qt, QTimer, QPointF, QRectF, QRect, + QPropertyAnimation, QEasingCurve, QSize, QObject, Property +) +from PySide6.QtGui import ( + QFont, QColor, QPalette, QLinearGradient, QPainter, QBrush, QPen, + QPainterPath, QRadialGradient, QCursor, QTextCursor, QPixmap, + QFontMetrics +) + +P = { + "void": "#04030C", "deep": "#080618", "space": "#0D0B20", + "panel": "#111028", "lift": "#171530", "ridge": "#1E1C38", + "rim": "#2A2748", "glow": "#3D3960", + "mint": "#2AFFA0", "cyan": "#00E5FF", "violet":"#A78BFA", + "rose": "#FF6B9D", "amber": "#FFB347", "sky": "#38BDF8", + "bright": "#F0EEFF", "mid": "#8E8AAE", "dim": "#4E4B6A", + "ghost": "#2A2744", +} + +class AgentWorker(QThread): + log_sig = Signal(str) + progress_sig = Signal(int) + status_sig = Signal(str) + done_sig = Signal(bool, str) + bubble_sig = Signal(str, bool) + + def __init__(self, task: str): + super().__init__() + self.task = task + self._killed = [False] + self._brain = None + + def kill(self): + self._killed[0] = True + if self._brain and self._brain.executor: + self._brain.executor.kill() + + def run(self): + self._brain = AgentBrain( + log_cb = self.log_sig.emit, + progress_cb = self.progress_sig.emit, + status_cb = self.status_sig.emit, + ) + words = self.task.strip().split() + is_chat = ( + len(words) <= 6 and + not any(kw in self.task.lower() for kw in + ["file","folder","send","email","scrape","download","create", + "rename","zip","run","open","install","write","read","github", + "slack","tweet","whatsapp","telegram","pdf","image","resize", + "schedule","watch","ssh","notion","jira","qr","voice"]) + ) + if is_chat: + resp = self._brain.chat(self.task) + self.bubble_sig.emit(resp, True) + self.progress_sig.emit(100) + self.done_sig.emit(True, resp) + else: + ok = self._brain.run_task(self.task, killed_flag=self._killed) + msg = "โœ“ Task completed!" if ok else "โœ— Task failed." + self.bubble_sig.emit(msg, True) + self.done_sig.emit(ok, msg) + +class CosmicBG(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.setAttribute(Qt.WA_TransparentForMouseEvents) + self.setAttribute(Qt.WA_TranslucentBackground) + self._phase = 0.0 + self._particles = self._init_particles(140) + self._orbs = [ + (0.12, 0.22, 460, QColor(42,255,160,20), 0.007, 0.0), + (0.82, 0.38, 380, QColor(167,139,250,16), 0.005, 2.1), + (0.50, 0.78, 310, QColor(0,229,255,14), 0.009, 4.2), + (0.28, 0.68, 260, QColor(255,107,157,12), 0.006, 1.0), + (0.88, 0.82, 210, QColor(255,179,71,11), 0.011, 3.3), + ] + QTimer(self, timeout=self._tick, interval=16).start() + + def _init_particles(self, n): + cols = ["#2AFFA0","#00E5FF","#A78BFA","#FF6B9D","#FFB347","#38BDF8"] + return [{"x":random.random(),"y":random.random(), + "vx":(random.random()-0.5)*0.35,"vy":(random.random()-0.5)*0.35, + "r":random.random()*1.6+0.3, + "col":QColor(random.choice(cols)), + "ba":random.randint(50,150), + "life":random.random()} for _ in range(n)] + + def _tick(self): + self._phase = (self._phase + 0.011) % (math.pi * 2) + w,h = max(self.width(),1), max(self.height(),1) + for p in self._particles: + p["x"] += p["vx"]/w*100; p["y"] += p["vy"]/h*100 + p["life"] += 0.003 + if p["x"]<0 or p["x"]>1 or p["y"]<0 or p["y"]>1 or p["life"]>1: + p["x"]=random.random(); p["y"]=random.random(); p["life"]=0 + self.update() + + def paintEvent(self, _): + p = QPainter(self); p.setRenderHint(QPainter.Antialiasing) + w,h = self.width(), self.height() + p.fillRect(0,0,w,h, QColor(P["void"])) + for cx_r,cy_r,r,col,spd,off in self._orbs: + cx = cx_r*w + math.sin(self._phase*spd*80+off)*55 + cy = cy_r*h + math.cos(self._phase*spd*60+off)*40 + g = QRadialGradient(cx,cy,r); g.setColorAt(0,col) + outer=QColor(col); outer.setAlpha(0); g.setColorAt(1,outer) + p.fillRect(0,0,w,h,QBrush(g)) + p.setPen(QPen(QColor(255,255,255,4),1)) + for gx in range(0,w+70,70): p.drawLine(gx,0,gx,h) + for gy in range(0,h+70,70): p.drawLine(0,gy,w,gy) + p.setPen(Qt.NoPen) + for pt in self._particles: + a = int(pt["ba"]*abs(math.sin(self._phase*1.4+pt["life"]*8))) + c=QColor(pt["col"]); c.setAlpha(max(15,min(200,a))) + p.setBrush(c); p.drawEllipse(QPointF(pt["x"]*w,pt["y"]*h),pt["r"],pt["r"]) + vg=QRadialGradient(w/2,h/2,max(w,h)*0.72) + vg.setColorAt(0,QColor(0,0,0,0)); vg.setColorAt(1,QColor(0,0,0,165)) + p.fillRect(0,0,w,h,QBrush(vg)); p.end() + +class GlowCard(QWidget): + def __init__(self, parent=None, accent="#2AFFA0", radius=18, alpha=205): + super().__init__(parent) + self._accent=QColor(accent); self._radius=radius + self._alpha=alpha; self._glow=0.0; self._hover=False + self.setAttribute(Qt.WA_TranslucentBackground) + QTimer(self,timeout=self._anim,interval=16).start() + + def _anim(self): + t=1.0 if self._hover else 0.0 + self._glow += (t-self._glow)*0.09 + if abs(self._glow-t)>0.01: self.update() + + def enterEvent(self,e): self._hover=True + def leaveEvent(self,e): self._hover=False + + def paintEvent(self,_): + p=QPainter(self); p.setRenderHint(QPainter.Antialiasing) + rect=self.rect().adjusted(2,2,-2,-2) + path=QPainterPath(); path.addRoundedRect(QRectF(rect),self._radius,self._radius) + p.setClipPath(path) + p.fillPath(path,QColor(17,16,40,self._alpha)) + sh=QLinearGradient(0,rect.top(),0,rect.top()+55) + sh.setColorAt(0,QColor(255,255,255,13)); sh.setColorAt(1,QColor(255,255,255,0)) + p.fillPath(path,QBrush(sh)); p.setClipping(False) + a=QColor(self._accent); border_a=int(50+(180-50)*self._glow) + a.setAlpha(border_a); p.setPen(QPen(a,1.5)); p.drawPath(path) + if self._glow>0.05: + for ring in range(3): + exp=(ring+1)*3 + hp=QPainterPath() + hp.addRoundedRect(QRectF(rect.adjusted(-exp,-exp,exp,exp)), + self._radius+exp,self._radius+exp) + ha=int(28*self._glow/(ring+1)) + hc=QColor(self._accent.red(),self._accent.green(),self._accent.blue(),ha) + p.setPen(QPen(hc,1)); p.setBrush(Qt.NoBrush); p.drawPath(hp) + p.end() + +class PulseBtn(QPushButton): + def __init__(self, text, accent="#2AFFA0", dark_text=True, parent=None): + super().__init__(text,parent) + self._ac=QColor(accent); self._dt=dark_text + self._h=0.0; self._pulse=0.0; self._pd=1; self._press=False + self.setCursor(Qt.PointingHandCursor); self.setFixedHeight(48) + self.setFont(QFont("Segoe UI",12,QFont.Bold)) + QTimer(self,timeout=self._tick,interval=16).start() + + def _tick(self): + self._pulse+=0.035*self._pd + if self._pulse>=1: self._pd=-1 + if self._pulse<=0: self._pd=1 + self.update() + + def enterEvent(self,e): self._h=1.0 + def leaveEvent(self,e): self._h=0.0 + def mousePressEvent(self,e): self._press=True; super().mousePressEvent(e) + def mouseReleaseEvent(self,e): self._press=False; super().mouseReleaseEvent(e) + + def paintEvent(self,_): + p=QPainter(self); p.setRenderHint(QPainter.Antialiasing) + r=self.rect(); path=QPainterPath() + path.addRoundedRect(QRectF(r.adjusted(1,1,-1,-1)),13,13) + g=QLinearGradient(r.left(),r.top(),r.right(),r.bottom()) + base=QColor(self._ac) + light=base.darker(110) if self._press else base.lighter(int(108+self._h*14+self._pulse*8)) + g.setColorAt(0,base); g.setColorAt(1,light) + p.fillPath(path,QBrush(g)) + ga=int((0.3+self._pulse*0.2+self._h*0.25)*255*0.4) + for ring in range(3): + exp=(ring+1)*3; hp=QPainterPath() + hp.addRoundedRect(QRectF(r.adjusted(-exp+1,-exp+1,exp-1,exp-1)),13+exp,13+exp) + hc=QColor(self._ac.red(),self._ac.green(),self._ac.blue(),max(0,ga//(ring+1))) + p.setPen(QPen(hc,1)); p.setBrush(Qt.NoBrush); p.drawPath(hp) + p.setPen(QColor("#050310") if self._dt else QColor(P["bright"])) + p.setFont(self.font()); p.drawText(r,Qt.AlignCenter,self.text()); p.end() + +class GhostBtn(QPushButton): + def __init__(self,text,accent="#2AFFA0",parent=None): + super().__init__(text,parent); self._ac=QColor(accent); self._h=0.0 + self.setCursor(Qt.PointingHandCursor); self.setFixedHeight(48) + self.setFont(QFont("Segoe UI",11,QFont.DemiBold)) + QTimer(self,timeout=self.update,interval=16).start() + + def enterEvent(self,e): self._h=1.0 + def leaveEvent(self,e): self._h=0.0 + + def paintEvent(self,_): + p=QPainter(self); p.setRenderHint(QPainter.Antialiasing) + r=self.rect(); path=QPainterPath() + path.addRoundedRect(QRectF(r.adjusted(1,1,-1,-1)),13,13) + p.fillPath(path,QColor(self._ac.red(),self._ac.green(),self._ac.blue(),int(self._h*30))) + ba=int(70+self._h*150) + p.setPen(QPen(QColor(self._ac.red(),self._ac.green(),self._ac.blue(),ba),1.5)) + p.drawPath(path) + tc=QColor(self._ac) if self._h>0.3 else QColor(P["mid"]) + p.setPen(tc); p.setFont(self.font()); p.drawText(r,Qt.AlignCenter,self.text()); p.end() + +class GlowInput(QLineEdit): + def __init__(self,placeholder="",parent=None): + super().__init__(parent); self.setPlaceholderText(placeholder) + self.setFixedHeight(52); self.setFont(QFont("Segoe UI",12)) + self._focused=False; self._g=0.0 + QTimer(self,timeout=self._tick,interval=16).start() + + def _tick(self): + t=1.0 if self._focused else 0.0 + self._g+=(t-self._g)*0.1 + self.setStyleSheet(f""" + QLineEdit{{background:rgba(14,12,32,{int(185+self._g*40)}); + border:1.5px solid rgba({int(42+self._g*160)},{int(142+self._g*100)},{int(self._g*60+180)},{int(80+self._g*175)}); + border-radius:13px;padding:0 18px;color:{P['bright']};font-size:12px; + selection-background-color:rgba(42,255,160,80);}} + QLineEdit::placeholder{{color:{P['dim']};}}""") + + def focusInEvent(self,e): self._focused=True; super().focusInEvent(e) + def focusOutEvent(self,e): self._focused=False; super().focusOutEvent(e) + +class GlowProgress(QWidget): + def __init__(self,parent=None): + super().__init__(parent); self.setFixedHeight(7) + self._val=0.0; self._target=0.0; self._phase=0.0 + QTimer(self,timeout=self._tick,interval=16).start() + + def set_value(self,v): self._target=v + + def _tick(self): + self._val+=(self._target-self._val)*0.07 + self._phase+=0.05; self.update() + + def paintEvent(self,_): + p=QPainter(self); p.setRenderHint(QPainter.Antialiasing) + w,h=self.width(),self.height() + tp=QPainterPath(); tp.addRoundedRect(QRectF(0,0,w,h),h/2,h/2) + p.fillPath(tp,QColor(P["ridge"])) + fw=w*self._val/100 + if fw>2: + fp=QPainterPath(); fp.addRoundedRect(QRectF(0,0,fw,h),h/2,h/2) + g=QLinearGradient(0,0,fw,0) + g.setColorAt(0,QColor("#2AFFA0")); g.setColorAt(0.5,QColor("#00E5FF")) + g.setColorAt(1,QColor("#A78BFA")); p.fillPath(fp,QBrush(g)) + sx=fw*(0.5+math.sin(self._phase)*0.5) + sg=QRadialGradient(sx,h/2,fw*0.3) + sg.setColorAt(0,QColor(255,255,255,55)); sg.setColorAt(1,QColor(255,255,255,0)) + p.setClipPath(fp); p.fillRect(QRectF(0,0,fw,h),QBrush(sg)); p.setClipping(False) + p.end() + +class ChatBubble(QWidget): + def __init__(self, text:str, is_agent:bool, parent=None): + super().__init__(parent) + self._is_agent = is_agent + self.setAttribute(Qt.WA_TranslucentBackground) + lay = QHBoxLayout(self); lay.setContentsMargins(8,4,8,4) + + bubble = QLabel(text) + bubble.setWordWrap(True) + bubble.setFont(QFont("Segoe UI",12)) + bubble.setTextInteractionFlags(Qt.TextSelectableByMouse) + bubble.setMaximumWidth(680) + bubble.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Minimum) + + if is_agent: + bubble.setStyleSheet(f""" + QLabel{{background:rgba(17,16,40,220); + border:1px solid rgba(42,255,160,80); + border-radius:16px;border-top-left-radius:4px; + padding:12px 16px;color:{P['bright']};line-height:1.6;}}""") + lay.addWidget(bubble); lay.addStretch() + else: + bubble.setStyleSheet(f""" + QLabel{{background:qlineargradient(x1:0,y1:0,x2:1,y2:1, + stop:0 rgba(42,255,160,40),stop:1 rgba(0,229,255,30)); + border:1px solid rgba(42,255,160,120); + border-radius:16px;border-top-right-radius:4px; + padding:12px 16px;color:{P['bright']};}}""") + lay.addStretch(); lay.addWidget(bubble) + + + eff = QGraphicsOpacityEffect(bubble); bubble.setGraphicsEffect(eff) + anim = QPropertyAnimation(eff,b"opacity",self) + anim.setDuration(350); anim.setStartValue(0); anim.setEndValue(1) + anim.setEasingCurve(QEasingCurve.OutCubic); anim.start() + self._anim = anim + +class NavBtn(QWidget): + clicked = Signal(int) + ICONS = ["๐Ÿ’ฌ","๐Ÿ“‹","๐Ÿ› ","โš™","๐Ÿ‘ค","๐Ÿ“–"] + LABELS = ["Agent","History","Tools","Settings","Founder","Docs"] + COLORS = [P["mint"],P["cyan"],P["amber"],P["violet"],P["rose"],P["sky"]] + + def __init__(self,idx,parent=None): + super().__init__(parent); self._idx=idx + self._active=False; self._h=0.0; self._hf=False + self.setCursor(Qt.PointingHandCursor); self.setFixedHeight(58) + self.setAttribute(Qt.WA_TranslucentBackground) + QTimer(self,timeout=self._tick,interval=16).start() + + def _tick(self): + t=1.0 if self._hf else 0.0 + self._h+=(t-self._h)*0.1; self.update() + + def set_active(self,v): self._active=v; self.update() + def enterEvent(self,e): self._hf=True + def leaveEvent(self,e): self._hf=False + def mousePressEvent(self,e): self.clicked.emit(self._idx) + + def paintEvent(self,_): + p=QPainter(self); p.setRenderHint(QPainter.Antialiasing) + w,h=self.width(),self.height() + col=QColor(self.COLORS[self._idx]) + blend=1.0 if self._active else self._h + if blend>0.01: + path=QPainterPath() + path.addRoundedRect(QRectF(8,5,w-16,h-10),11,11) + p.fillPath(path,QColor(col.red(),col.green(),col.blue(),int(blend*(60 if self._active else 35)))) + if self._active: + p.setPen(QPen(QColor(col.red(),col.green(),col.blue(),110),1.5)) + p.drawPath(path) + if self._active: + bar=QPainterPath(); bar.addRoundedRect(QRectF(0,h*0.2,3,h*0.6),2,2) + p.fillPath(bar,col) + bg=QRadialGradient(1,h/2,22) + bg.setColorAt(0,QColor(col.red(),col.green(),col.blue(),75)) + bg.setColorAt(1,QColor(col.red(),col.green(),col.blue(),0)) + p.fillRect(0,0,30,h,QBrush(bg)) + p.setFont(QFont("Segoe UI",16)) + p.setPen(col if (self._active or blend>0.3) else QColor(P["mid"])) + p.drawText(QRect(14,0,34,h),Qt.AlignVCenter|Qt.AlignLeft,self.ICONS[self._idx]) + p.setPen(QColor(P["bright"]) if (self._active or blend>0.3) else QColor(P["mid"])) + p.setFont(QFont("Segoe UI",11,QFont.Bold if self._active else QFont.Normal)) + p.drawText(QRect(52,0,w-60,h),Qt.AlignVCenter|Qt.AlignLeft,self.LABELS[self._idx]) + p.end() + +class Sidebar(QWidget): + page_changed = Signal(int) + + def __init__(self,parent=None): + super().__init__(parent); self.setFixedWidth(215) + self.setAttribute(Qt.WA_TranslucentBackground) + self._btns=[]; self._build() + + def _build(self): + lay=QVBoxLayout(self); lay.setContentsMargins(0,0,0,0); lay.setSpacing(0) + + lw=QWidget(); lw.setAttribute(Qt.WA_TranslucentBackground); lw.setFixedHeight(86) + ll=QVBoxLayout(lw); ll.setContentsMargins(18,20,18,10); ll.setSpacing(3) + lg=QLabel("โš™ NPM Agent"); lg.setFont(QFont("Segoe UI",14,QFont.Bold)) + lg.setStyleSheet(f"color:{P['mint']};background:transparent;") + lv=QLabel("v3.0 ยท NPMAI Ecosystem"); lv.setFont(QFont("Segoe UI",9)) + lv.setStyleSheet(f"color:{P['dim']};background:transparent;") + ll.addWidget(lg); ll.addWidget(lv); lay.addWidget(lw) + self._sep(lay) + + self._mcp_lbl=QLabel(" โ— MCP Server: Stopped") + self._mcp_lbl.setFont(QFont("Segoe UI",9)) + self._mcp_lbl.setStyleSheet(f"color:{P['dim']};background:transparent;padding:6px 0;") + lay.addWidget(self._mcp_lbl) + self._sep(lay); lay.addSpacing(6) + + nl=QLabel(" NAVIGATION"); nl.setFont(QFont("Segoe UI",8,QFont.Bold)) + nl.setStyleSheet(f"color:{P['dim']};background:transparent;letter-spacing:3px;") + lay.addWidget(nl); lay.addSpacing(2) + + for i in range(6): + b=NavBtn(i); b.set_active(i==0) + b.clicked.connect(self._on_nav) + self._btns.append(b); lay.addWidget(b) + + lay.addStretch(); self._sep(lay) + + fw=QWidget(); fw.setAttribute(Qt.WA_TranslucentBackground) + fl=QVBoxLayout(fw); fl.setContentsMargins(12,10,12,16); fl.setSpacing(8) + + self._mcp_btn=QPushButton("โ–ถ Start MCP Server") + self._mcp_btn.setCursor(Qt.PointingHandCursor); self._mcp_btn.setFixedHeight(34) + self._mcp_btn.setStyleSheet(self._btn_style(P["mint"])) + self._mcp_btn.clicked.connect(self._toggle_mcp) + fl.addWidget(self._mcp_btn) + + for lbl,url in [("๐Ÿ PyPI","https://pypi.org/project/npmai"), + ("โญ GitHub","https://github.com/sonuramashishnpm")]: + b2=QPushButton(lbl); b2.setCursor(Qt.PointingHandCursor); b2.setFixedHeight(32) + b2.setStyleSheet(self._btn_style(P["dim"])) + import webbrowser + b2.clicked.connect(lambda _,u=url: webbrowser.open(u)) + fl.addWidget(b2) + + eco=QLabel("Powered by NPMAI Ecosystem"); eco.setFont(QFont("Segoe UI",9)) + eco.setStyleSheet(f"color:{P['dim']};background:transparent;"); eco.setAlignment(Qt.AlignCenter) + fl.addWidget(eco); lay.addWidget(fw) + + self._mcp_running=False; self._mcp_thread=None; self._mcp_port=7799 + + def _btn_style(self,col): + return f"""QPushButton{{background:rgba(42,255,160,10);border:1px solid rgba(42,255,160,35); +border-radius:9px;color:{P['mid']};font-size:11px;font-family:'Segoe UI';}} +QPushButton:hover{{background:rgba(42,255,160,22);border-color:rgba(42,255,160,90);color:{P['mint']};}}""" + + def _sep(self,lay): + s=QFrame(); s.setFixedHeight(1) + s.setStyleSheet(f"background:{P['ghost']};border:none;"); lay.addWidget(s) + + def _on_nav(self,idx): + for i,b in enumerate(self._btns): b.set_active(i==idx) + self.page_changed.emit(idx) + + def _toggle_mcp(self): + if self._mcp_running: + self._mcp_running=False + self._mcp_btn.setText("โ–ถ Start MCP Server") + self._mcp_lbl.setText(" โ— MCP Server: Stopped") + self._mcp_lbl.setStyleSheet(f"color:{P['dim']};background:transparent;padding:6px 0;") + else: + self._mcp_running=True + self._mcp_btn.setText("โ–  Stop MCP Server") + self._mcp_lbl.setText(f" โ— MCP Server: :{self._mcp_port}") + self._mcp_lbl.setStyleSheet(f"color:{P['mint']};background:transparent;padding:6px 0;") + self._mcp_thread=threading.Thread( + target=run_mcp_server, args=(self._mcp_port,self._mcp_running_ref()), daemon=True) + self._mcp_thread.start() + + def _mcp_running_ref(self): + class Ref: + def __init__(s): s.val=True + def __bool__(s): return s.val + r=Ref() + self._mcp_ref=r; return r + + def paintEvent(self,_): + p=QPainter(self); p.setRenderHint(QPainter.Antialiasing) + g=QLinearGradient(0,0,self.width(),0) + g.setColorAt(0,QColor(6,4,18,245)); g.setColorAt(1,QColor(9,7,22,215)) + p.fillRect(self.rect(),QBrush(g)) + p.setPen(QPen(QColor(P["ghost"]),1)) + p.drawLine(self.width()-1,0,self.width()-1,self.height()); p.end() + +class AgentPage(QWidget): + def __init__(self,parent=None): + super().__init__(parent) + self.setAttribute(Qt.WA_TranslucentBackground) + self._worker=None; self._killed=[False] + self._build() + + def _build(self): + outer=QVBoxLayout(self); outer.setContentsMargins(0,0,0,0) + topbar=QWidget(); topbar.setAttribute(Qt.WA_TranslucentBackground); topbar.setFixedHeight(64) + tbl=QHBoxLayout(topbar); tbl.setContentsMargins(36,12,36,12); tbl.setSpacing(12) + title=QLabel("NPM Agent"); title.setFont(QFont("Segoe UI",18,QFont.Bold)) + title.setStyleSheet(f"color:{P['mint']};background:transparent;") + sub=QLabel("Claude Code ยท Cowork ยท MCP โ€” all in one"); sub.setFont(QFont("Segoe UI",10)) + sub.setStyleSheet(f"color:{P['dim']};background:transparent;") + tc=QVBoxLayout(); tc.setSpacing(0); tc.addWidget(title); tc.addWidget(sub) + tbl.addLayout(tc); tbl.addStretch() + + for badge,col in [("โšก 21 Tools",P["mint"]),("๐Ÿ”’ Secure",P["violet"]),("๐Ÿค– Agentic",P["cyan"])]: + b=QLabel(f" {badge} "); b.setFont(QFont("Segoe UI",9,QFont.Bold)) + b.setStyleSheet(f"color:{col};background:rgba(42,255,160,12);border:1px solid rgba(42,255,160,35);border-radius:9px;padding:3px 8px;") + tbl.addWidget(b) + + sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") + outer.addWidget(topbar); outer.addWidget(sep) + chat_scroll=QScrollArea(); chat_scroll.setWidgetResizable(True) + chat_scroll.setStyleSheet("QScrollArea{border:none;background:transparent;}") + chat_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self._chat_container=QWidget(); self._chat_container.setAttribute(Qt.WA_TranslucentBackground) + self._chat_layout=QVBoxLayout(self._chat_container) + self._chat_layout.setContentsMargins(28,20,28,20); self._chat_layout.setSpacing(12) + self._chat_layout.addStretch() + chat_scroll.setWidget(self._chat_container) + self._chat_scroll=chat_scroll + outer.addWidget(chat_scroll,1) + bottom=QWidget(); bottom.setAttribute(Qt.WA_TranslucentBackground) + bl=QVBoxLayout(bottom); bl.setContentsMargins(28,14,28,20); bl.setSpacing(10) + prow=QHBoxLayout(); prow.setSpacing(10) + self._status_lbl=QLabel("Ready"); self._status_lbl.setFont(QFont("Segoe UI",10)) + self._status_lbl.setStyleSheet(f"color:{P['mid']};background:transparent;") + self._pct_lbl=QLabel(""); self._pct_lbl.setFont(QFont("Segoe UI",10,QFont.Bold)) + self._pct_lbl.setStyleSheet(f"color:{P['mint']};background:transparent;") + prow.addWidget(self._status_lbl); prow.addStretch(); prow.addWidget(self._pct_lbl) + bl.addLayout(prow) + self._prog=GlowProgress(); bl.addWidget(self._prog) + self._log_box=QTextEdit(); self._log_box.setReadOnly(True) + self._log_box.setFont(QFont("Cascadia Code",10)) + self._log_box.setFixedHeight(140) + self._log_box.setStyleSheet(f"""QTextEdit{{background:rgba(4,3,12,190); +border:1px solid {P['ghost']};border-radius:11px;padding:10px;color:{P['bright']};}}""") + self._log_box.setHtml(f'Execution logs appear hereโ€ฆ') + bl.addWidget(self._log_box) + irow=QHBoxLayout(); irow.setSpacing(10) + self._input=GlowInput("Ask anything or describe a task to automateโ€ฆ") + self._input.returnPressed.connect(self._send) + self._run_btn=PulseBtn("โ–ถ Run",P["mint"]); self._run_btn.setFixedWidth(100) + self._run_btn.clicked.connect(self._send) + self._kill_btn=GhostBtn("โ–  Kill",P["rose"]); self._kill_btn.setFixedWidth(90) + self._kill_btn.clicked.connect(self._kill); self._kill_btn.setEnabled(False) + self._voice_btn=GhostBtn("๐ŸŽค",P["sky"]); self._voice_btn.setFixedWidth(60) + self._voice_btn.clicked.connect(self._voice_input) + irow.addWidget(self._input); irow.addWidget(self._run_btn) + irow.addWidget(self._kill_btn); irow.addWidget(self._voice_btn) + bl.addLayout(irow) + chips=QHBoxLayout(); chips.setSpacing(8) + self._chip_tasks=[ + ("๐Ÿ“Š Plot CSV","Read data.csv from Desktop and create a bar chart PNG"), + ("๐Ÿ“ Rename files","Rename all files in Downloads adding today's date prefix"), + ("๐ŸŒ Scrape web","Scrape top 20 headlines from https://news.ycombinator.com save to news.xlsx on Desktop"), + ("๐Ÿ“ง Bulk email","Send a hello email to all addresses in contacts.csv on Desktop"), + ("๐Ÿ”’ Zip PDFs","Find all PDFs in Documents sort by date zip into archive.zip on Desktop"), + ("๐Ÿ–ผ Resize imgs","Resize all images in Desktop/Photos folder to 800x600"), + ] + for label,task in self._chip_tasks: + c=QPushButton(label); c.setCursor(Qt.PointingHandCursor); c.setFixedHeight(30) + c.setStyleSheet(f"""QPushButton{{background:rgba(42,255,160,10); +border:1px solid rgba(42,255,160,38);border-radius:9px; +color:rgba(42,255,160,180);font-size:10px;padding:0 12px;}} +QPushButton:hover{{background:rgba(42,255,160,22);border-color:rgba(42,255,160,100);color:{P['mint']};}}""") + c.clicked.connect(lambda _,t=task: self._input.setText(t)) + chips.addWidget(c) + chips.addStretch(); bl.addLayout(chips) + sep2=QFrame(); sep2.setFixedHeight(1); sep2.setStyleSheet(f"background:{P['ghost']};border:none;") + outer.addWidget(sep2); outer.addWidget(bottom) + + def _add_bubble(self, text:str, is_agent:bool): + b=ChatBubble(text, is_agent) + self._chat_layout.insertWidget(self._chat_layout.count()-1, b) + QTimer.singleShot(50, lambda: self._chat_scroll.verticalScrollBar().setValue( + self._chat_scroll.verticalScrollBar().maximum())) + + def _log(self, html:str): + self._log_box.append(html) + self._log_box.verticalScrollBar().setValue(self._log_box.verticalScrollBar().maximum()) + + def _voice_input(self): + r=VoiceTool.listen(5) + if r.success and r.data: + self._input.setText(r.data) + + def _kill(self): + if self._worker: self._worker.kill() + self._kill_btn.setEnabled(False) + self._run_btn.setEnabled(True); self._run_btn.setText("โ–ถ Run") + + def _send(self): + task=self._input.text().strip() + if not task: return + self._input.clear() + self._add_bubble(task, False) + self._run_btn.setEnabled(False); self._run_btn.setText("โณ") + self._kill_btn.setEnabled(True) + self._prog.set_value(3) + self._log_box.clear() + self._worker=AgentWorker(task) + self._worker.log_sig.connect(self._log) + self._worker.progress_sig.connect(self._prog.set_value) + self._worker.progress_sig.connect(lambda v: self._pct_lbl.setText(f"{v}%")) + self._worker.status_sig.connect(lambda s: self._status_lbl.setText(s)) + self._worker.bubble_sig.connect(self._add_bubble) + self._worker.done_sig.connect(self._done) + self._worker.start() + + def _done(self, ok:bool, msg:str): + self._run_btn.setEnabled(True); self._run_btn.setText("โ–ถ Run") + self._kill_btn.setEnabled(False) + col=P["mint"] if ok else P["rose"] + self._status_lbl.setText("Done โœ“" if ok else "Failed") + +class HistoryPage(QWidget): + def __init__(self,parent=None): + super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() + + def _build(self): + outer=QVBoxLayout(self); outer.setContentsMargins(0,0,0,0) + scroll=QScrollArea(); scroll.setWidgetResizable(True) + scroll.setStyleSheet("QScrollArea{border:none;background:transparent;}") + page=QWidget(); page.setAttribute(Qt.WA_TranslucentBackground) + self._lay=QVBoxLayout(page); self._lay.setContentsMargins(36,36,36,36); self._lay.setSpacing(10) + header=QHBoxLayout() + t=QLabel("๐Ÿ“‹ Task History"); t.setFont(QFont("Segoe UI",20,QFont.Bold)) + t.setStyleSheet(f"color:{P['cyan']};background:transparent;") + refresh=GhostBtn("โ†บ Refresh",P["cyan"]); refresh.setFixedSize(110,36) + refresh.clicked.connect(self.load) + header.addWidget(t); header.addStretch(); header.addWidget(refresh) + self._lay.addLayout(header) + sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") + self._lay.addWidget(sep); self._lay.addSpacing(10) + self._items_layout=QVBoxLayout(); self._lay.addLayout(self._items_layout) + self._lay.addStretch() + scroll.setWidget(page); outer.addWidget(scroll) + self.load() + + def load(self): + while self._items_layout.count(): + item=self._items_layout.takeAt(0) + if item.widget(): item.widget().deleteLater() + history=AgentBrain.load_task_history() + if not history: + lbl=QLabel("No tasks run yet. Go to Agent tab and run your first task!") + lbl.setFont(QFont("Segoe UI",12)); lbl.setStyleSheet(f"color:{P['mid']};background:transparent;") + self._items_layout.addWidget(lbl); return + for entry in history: + row=GlowCard(accent=P["mint"] if entry["success"] else P["rose"],radius=14,alpha=190) + row.setFixedHeight(70) + rl=QHBoxLayout(row); rl.setContentsMargins(20,12,20,12); rl.setSpacing(14) + dot=QLabel("โœ“" if entry["success"] else "โœ—") + dot.setFont(QFont("Segoe UI",14,QFont.Bold)) + dot.setStyleSheet(f"color:{P['mint'] if entry['success'] else P['rose']};background:transparent;") + dot.setFixedWidth(22) + tc=QVBoxLayout(); tc.setSpacing(2) + task_lbl=QLabel(entry["task"][:80]+("โ€ฆ" if len(entry["task"])>80 else "")) + task_lbl.setFont(QFont("Segoe UI",11)) + task_lbl.setStyleSheet(f"color:{P['bright']};background:transparent;") + time_lbl=QLabel(entry["time"][:19].replace("T"," ")) + time_lbl.setFont(QFont("Segoe UI",9)) + time_lbl.setStyleSheet(f"color:{P['dim']};background:transparent;") + tc.addWidget(task_lbl); tc.addWidget(time_lbl) + rl.addWidget(dot); rl.addLayout(tc); rl.addStretch() + self._items_layout.addWidget(row) + +class ToolsPage(QWidget): + def __init__(self,parent=None): + super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() + + def _build(self): + outer=QVBoxLayout(self); outer.setContentsMargins(0,0,0,0) + scroll=QScrollArea(); scroll.setWidgetResizable(True) + scroll.setStyleSheet("QScrollArea{border:none;background:transparent;}") + page=QWidget(); page.setAttribute(Qt.WA_TranslucentBackground) + lay=QVBoxLayout(page); lay.setContentsMargins(36,36,36,36); lay.setSpacing(20) + t=QLabel("๐Ÿ›  Tool Registry โ€” 21 Integrated Tools") + t.setFont(QFont("Segoe UI",20,QFont.Bold)) + t.setStyleSheet(f"color:{P['amber']};background:transparent;") + lay.addWidget(t) + desc=QLabel("All tools are available to the agent automatically. Just describe your task in plain English.") + desc.setFont(QFont("Segoe UI",11)); desc.setWordWrap(True) + desc.setStyleSheet(f"color:{P['mid']};background:transparent;") + lay.addWidget(desc) + sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") + lay.addWidget(sep); lay.addSpacing(6) + tool_info=[ + ("๐Ÿ“ง","Email","EmailTool",P["mint"], + "Send/receive emails via Gmail SMTP/IMAP ยท Bulk email from CSV ยท Attachments ยท Template variables"), + ("๐Ÿ“","Files","FileTool",P["cyan"], + "Bulk rename ยท Zip/unzip ยท Find files ยท Organize by type ยท Read/write ยท Copy folder trees"), + ("๐Ÿ“„","PDF","PDFTool",P["violet"], + "Extract text ยท Merge multiple PDFs ยท Split by page ยท Works with large documents"), + ("๐ŸŒ","Web","WebTool",P["amber"], + "Scrape with CSS selectors ยท Download files ยท Screenshot URLs ยท Full browser automation (Playwright) ยท API calls"), + ("๐Ÿ“Š","Spreadsheet","SpreadsheetTool",P["sky"], + "Read CSV ยท Write Excel ยท Google Sheets read/write ยท Pandas integration"), + ("โญ","GitHub","GitHubTool",P["mint"], + "Create/list issues ยท Push/update files ยท Get README ยท Clone repos ยท Git commit+push"), + ("๐Ÿ’ฌ","Slack","SlackTool",P["cyan"], + "Send messages ยท Read channel history ยท Upload files to channels"), + ("๐ŸŽฎ","Discord","DiscordTool",P["violet"], + "Send messages via webhook ยท Embed rich content"), + ("๐Ÿ“ฑ","WhatsApp","WhatsAppTool",P["amber"], + "Send WhatsApp messages instantly (requires WhatsApp Web)"), + ("๐Ÿ““","Notion","NotionTool",P["sky"], + "Create pages ยท Add database entries ยท Full Notion API integration"), + ("๐Ÿฆ","Twitter/X","TwitterTool",P["mint"], + "Post tweets ยท Read timeline ยท Manage account"), + ("๐Ÿ–ฅ","System","SystemTool",P["cyan"], + "Run shell commands ยท Clipboard get/set ยท Screenshot ยท Process list ยท OS notifications"), + ("๐Ÿ–ผ","Image","ImageTool",P["violet"], + "Resize ยท Convert format ยท OCR text extraction ยท Bulk compress"), + ("โฐ","Scheduler","SchedulerTool",P["amber"], + "Schedule tasks by interval or time ยท Background threads ยท Cancel anytime"), + ("๐ŸŽซ","Jira","JiraTool",P["sky"], + "Create/update issues ยท Sprint management ยท Project tracking"), + ("โœˆ","Telegram","TelegramTool",P["mint"], + "Send messages via bot token ยท No WhatsApp Web required"), + ("๐Ÿ”ฒ","QR Code","QRTool",P["cyan"], + "Generate QR codes from any text or URL ยท Configurable size"), + ("๐ŸŽค","Voice","VoiceTool",P["violet"], + "Text-to-speech output ยท Speech-to-text input from microphone"), + ("๐Ÿ‘","Watcher","WatcherTool",P["amber"], + "Watch folders for file changes ยท Trigger automated actions"), + ("๐Ÿ“š","RAG","RAGTool",P["sky"], + "Query large documents using npmai Rag class ยท Summarize huge files by chunking ยท PDF/TXT support"), + ("๐Ÿ–ง","SSH","SSHTool",P["mint"], + "Run commands on remote servers ยท Upload files via SFTP ยท Paramiko-powered"), + ] + grid_cols=2 + grid_row=None + for i,(icon,name,cls,col,desc2) in enumerate(tool_info): + if i%grid_cols==0: + grid_row=QHBoxLayout(); grid_row.setSpacing(16); lay.addLayout(grid_row) + card=GlowCard(accent=col,radius=16,alpha=195); card.setFixedHeight(118) + cl=QHBoxLayout(card); cl.setContentsMargins(20,16,20,16); cl.setSpacing(14) + ic_lbl=QLabel(icon); ic_lbl.setFont(QFont("Segoe UI",22)) + ic_lbl.setStyleSheet("background:transparent;"); ic_lbl.setFixedWidth(36) + cv=QVBoxLayout(); cv.setSpacing(4) + n=QLabel(name); n.setFont(QFont("Segoe UI",13,QFont.Bold)) + n.setStyleSheet(f"color:{P['bright']};background:transparent;") + c2=QLabel(cls); c2.setFont(QFont("Cascadia Code",9)) + c2.setStyleSheet(f"color:{col};background:transparent;") + d=QLabel(desc2); d.setFont(QFont("Segoe UI",10)); d.setWordWrap(True) + d.setStyleSheet(f"color:{P['mid']};background:transparent;") + cv.addWidget(n); cv.addWidget(c2); cv.addWidget(d) + cl.addWidget(ic_lbl); cl.addLayout(cv) + grid_row.addWidget(card) + if len(tool_info)%grid_cols!=0: + grid_row.addStretch() + + lay.addStretch() + scroll.setWidget(page); outer.addWidget(scroll) + +class SettingsPage(QWidget): + def __init__(self,parent=None): + super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() + + def _section(self, lay, title, fields, cred_key, accent): + card=GlowCard(accent=accent,radius=18,alpha=200) + cl=QVBoxLayout(card); cl.setContentsMargins(28,22,28,22); cl.setSpacing(14) + h=QLabel(title); h.setFont(QFont("Segoe UI",13,QFont.Bold)) + h.setStyleSheet(f"color:{P['bright']};background:transparent;"); cl.addWidget(h) + sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") + cl.addWidget(sep) + inputs={} + for field_key,label,placeholder,is_pw in fields: + lbl=QLabel(label); lbl.setFont(QFont("Segoe UI",10)) + lbl.setStyleSheet(f"color:{P['mid']};background:transparent;"); cl.addWidget(lbl) + inp=GlowInput(placeholder) + if is_pw: inp.setEchoMode(QLineEdit.Password) + existing=CredStore.load(cred_key).get(field_key,"") + if existing: inp.setText("โ—"*8 if is_pw else existing) + inputs[field_key]=inp; cl.addWidget(inp) + save_btn=PulseBtn(f"๐Ÿ’พ Save {title.split()[0]} Credentials",accent,True) + save_btn.setFixedHeight(42) + def _save(ck=cred_key,inp_ref=inputs): + data={} + for k,w in inp_ref.items(): + v=w.text().strip() + if v and v!="โ—"*8: data[k]=v + existing=CredStore.load(ck) + existing.update(data); CredStore.save(ck,existing) + QMessageBox.information(self,"Saved",f"{ck} credentials saved securely.") + save_btn.clicked.connect(_save); cl.addWidget(save_btn) + lay.addWidget(card) + + def _build(self): + outer=QVBoxLayout(self); outer.setContentsMargins(0,0,0,0) + scroll=QScrollArea(); scroll.setWidgetResizable(True) + scroll.setStyleSheet("QScrollArea{border:none;background:transparent;}") + page=QWidget(); page.setAttribute(Qt.WA_TranslucentBackground) + lay=QVBoxLayout(page); lay.setContentsMargins(36,36,36,36); lay.setSpacing(20) + t=QLabel("โš™ Settings & Credentials"); t.setFont(QFont("Segoe UI",20,QFont.Bold)) + t.setStyleSheet(f"color:{P['violet']};background:transparent;"); lay.addWidget(t) + sub=QLabel("All credentials are encrypted with a machine-specific key and stored locally. Never sent anywhere.") + sub.setFont(QFont("Segoe UI",10)); sub.setWordWrap(True) + sub.setStyleSheet(f"color:{P['mid']};background:transparent;"); lay.addWidget(sub) + sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") + lay.addWidget(sep); lay.addSpacing(6) + ws_card=GlowCard(accent=P["cyan"],radius=16,alpha=195) + wl=QHBoxLayout(ws_card); wl.setContentsMargins(24,16,24,16); wl.setSpacing(16) + wl_v=QVBoxLayout(); wl_v.setSpacing(4) + wl_t=QLabel("๐Ÿ–ฅ Workspace Scanner"); wl_t.setFont(QFont("Segoe UI",13,QFont.Bold)) + wl_t.setStyleSheet(f"color:{P['bright']};background:transparent;") + wl_d=QLabel("Scans your Desktop, Downloads, Documents, Pictures, Videos, Music โ€” agent uses this to understand your file system") + wl_d.setFont(QFont("Segoe UI",10)); wl_d.setWordWrap(True) + wl_d.setStyleSheet(f"color:{P['mid']};background:transparent;") + wl_v.addWidget(wl_t); wl_v.addWidget(wl_d) + scan_btn=PulseBtn("๐Ÿ” Scan Now",P["cyan"],True); scan_btn.setFixedSize(130,38) + def _scan(): + ws=Workspace(); ws.scan() + QMessageBox.information(self,"Scanned","Workspace scanned successfully!") + scan_btn.clicked.connect(_scan); wl.addLayout(wl_v); wl.addStretch(); wl.addWidget(scan_btn) + lay.addWidget(ws_card) + prof_card=GlowCard(accent=P["mint"],radius=16,alpha=195) + pl=QVBoxLayout(prof_card); pl.setContentsMargins(24,18,24,18); pl.setSpacing(12) + pt=QLabel("๐Ÿ‘ค User Profile"); pt.setFont(QFont("Segoe UI",13,QFont.Bold)) + pt.setStyleSheet(f"color:{P['bright']};background:transparent;"); pl.addWidget(pt) + ws=Workspace() + self._name_inp=GlowInput("Your name (agent uses this)"); self._name_inp.setText(ws.data.get("user_name","")) + pl.addWidget(QLabel("Your Name:")); pl.addWidget(self._name_inp) + save_prof=PulseBtn("๐Ÿ’พ Save Profile",P["mint"],True); save_prof.setFixedHeight(40) + def _save_prof(): + ws2=Workspace(); ws2.update_profile("user_name",self._name_inp.text().strip()) + QMessageBox.information(self,"Saved","Profile saved!") + save_prof.clicked.connect(_save_prof); pl.addWidget(save_prof) + lay.addWidget(prof_card) + + self._section(lay,"๐Ÿ“ง Gmail / Email",[ + ("email","Email address","your@gmail.com",False), + ("password","App password (not your login password)","Gmail app password",True), + ("smtp_host","SMTP host","smtp.gmail.com",False), + ("imap_host","IMAP host","imap.gmail.com",False), + ],"gmail",P["mint"]) + + self._section(lay,"โญ GitHub",[ + ("token","Personal access token (repo + issues scope)","ghp_xxxxxxxxxxxx",True), + ],"github",P["cyan"]) + + self._section(lay,"๐Ÿ’ฌ Slack",[ + ("bot_token","Bot OAuth token","xoxb-xxxxxxxxxxxx",True), + ],"slack",P["violet"]) + + self._section(lay,"๐Ÿฆ Twitter/X",[ + ("api_key","API Key","",True),("api_secret","API Secret","",True), + ("access_token","Access Token","",True),("access_token_secret","Access Token Secret","",True), + ],"twitter",P["sky"]) + + self._section(lay,"๐Ÿ““ Notion",[ + ("token","Integration token","secret_xxxxxxxxxxxx",True), + ],"notion",P["amber"]) + + self._section(lay,"โœˆ Telegram",[ + ("bot_token","Bot token (from @BotFather)","",True), + ],"telegram",P["rose"]) + + self._section(lay,"๐ŸŽซ Jira",[ + ("server","Jira server URL","https://yourcompany.atlassian.net",False), + ("email","Jira account email","",False), + ("api_token","API token","",True), + ],"jira",P["mint"]) + + self._section(lay,"๐Ÿ–ง SSH",[ + ("user","SSH username","ubuntu",False), + ("password","SSH password (or leave blank for key)","",True), + ("key_path","Path to private key (optional)","~/.ssh/id_rsa",False), + ],"ssh",P["cyan"]) + + mcp_card=GlowCard(accent=P["mint"],radius=16,alpha=195) + ml=QVBoxLayout(mcp_card); ml.setContentsMargins(24,18,24,18); ml.setSpacing(8) + mt=QLabel("๐Ÿ”Œ MCP Server Info"); mt.setFont(QFont("Segoe UI",13,QFont.Bold)) + mt.setStyleSheet(f"color:{P['bright']};background:transparent;"); ml.addWidget(mt) + md=QLabel( + "Start the MCP Server from the sidebar to expose all 21 tools via the MCP protocol.\n" + "Default port: 7799\n" + "Connect from Claude Desktop by adding to your MCP config:\n" + '{ "type": "tcp", "host": "localhost", "port": 7799 }' + ) + md.setFont(QFont("Cascadia Code",10)); md.setWordWrap(True) + md.setStyleSheet(f"color:{P['mid']};background:transparent;"); ml.addWidget(md) + lay.addWidget(mcp_card) + lay.addStretch(); scroll.setWidget(page); outer.addWidget(scroll) + +class FounderPage(QWidget): + def __init__(self,parent=None): + super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() + + def _build(self): + outer=QVBoxLayout(self); outer.setContentsMargins(0,0,0,0) + scroll=QScrollArea(); scroll.setWidgetResizable(True) + scroll.setStyleSheet("QScrollArea{border:none;background:transparent;}") + page=QWidget(); page.setAttribute(Qt.WA_TranslucentBackground) + lay=QVBoxLayout(page); lay.setContentsMargins(36,36,36,36); lay.setSpacing(20) + + import webbrowser + t=QLabel("๐Ÿ‘ค The Founder"); t.setFont(QFont("Segoe UI",20,QFont.Bold)) + t.setStyleSheet(f"color:{P['violet']};background:transparent;"); lay.addWidget(t) + hero=GlowCard(accent=P["violet"],radius=22,alpha=210) + hl=QVBoxLayout(hero); hl.setContentsMargins(32,28,32,28); hl.setSpacing(14) + name=QLabel("Sonu Kumar ๐Ÿ‡ฎ๐Ÿ‡ณ"); name.setFont(QFont("Segoe UI",22,QFont.Bold)) + name.setStyleSheet(f"color:{P['bright']};background:transparent;"); hl.addWidget(name) + tag=QLabel("Founder ยท NPMAI ECOSYSTEM ยท Bihar Viral Boy") + tag.setFont(QFont("Segoe UI",12)); tag.setStyleSheet(f"color:{P['violet']};background:transparent;") + hl.addWidget(tag) + roles=QLabel("Software Developer ยท AI Developer ยท Web Developer ยท Cloud Developer ยท DevOps\nTEDx Speaker ยท Social Thinker ยท Researcher\nStudent at Allen Career Institute & Disha Delphi Public School, Kota (100% Scholarship)") + roles.setFont(QFont("Segoe UI",11)); roles.setWordWrap(True) + roles.setStyleSheet(f"color:{P['mid']};background:transparent;"); hl.addWidget(roles) + badges=QHBoxLayout(); badges.setSpacing(8) + for txt,col in [("Age 15",P["mint"]),("Bihar, India",P["cyan"]),("Kota, Rajasthan",P["violet"]), + ("434K+ Followers",P["amber"]),("TEDx Speaker",P["rose"]),("Allen Scholar",P["mint"])]: + b=QLabel(f" {txt} "); b.setFont(QFont("Segoe UI",10,QFont.Bold)) + b.setStyleSheet(f"color:{col};padding:4px 8px;background:rgba(42,255,160,10);border:1px solid rgba(42,255,160,30);border-radius:9px;") + badges.addWidget(b) + badges.addStretch(); hl.addLayout(badges) + links=QHBoxLayout(); links.setSpacing(10) + for lbl2,url in [("โญ GitHub","https://github.com/sonuramashishnpm"), + ("๐Ÿ PyPI","https://pypi.org/project/npmai"), + ("๐ŸŽค TEDx","https://youtu.be/qWTkKsegx9c"), + ("๐Ÿ“˜ Facebook","https://facebook.com/share/19dvhX1mH7/")]: + b2=QPushButton(lbl2); b2.setCursor(Qt.PointingHandCursor); b2.setFixedHeight(32) + b2.setStyleSheet(f"QPushButton{{background:rgba(167,139,250,10);border:1px solid rgba(167,139,250,40);border-radius:9px;color:{P['mid']};font-size:11px;padding:0 12px;}}QPushButton:hover{{background:rgba(167,139,250,25);border-color:rgba(167,139,250,110);color:{P['bright']};}}") + b2.clicked.connect(lambda _,u=url: webbrowser.open(u)); links.addWidget(b2) + links.addStretch(); hl.addLayout(links); lay.addWidget(hero) + stats_card=GlowCard(accent=P["cyan"],radius=18,alpha=195) + scl=QVBoxLayout(stats_card); scl.setContentsMargins(28,22,28,22); scl.setSpacing(14) + st=QLabel("๐Ÿ“Š Key Numbers"); st.setFont(QFont("Segoe UI",13,QFont.Bold)) + st.setStyleSheet(f"color:{P['bright']};background:transparent;"); scl.addWidget(st) + sg=QHBoxLayout(); sg.setSpacing(12) + for num,lbl3,col in [("1.5M+","PyPI downloads",P["mint"]),("433K+","Social followers",P["cyan"]), + ("TEDx","Speaker at 13",P["violet"]),("100%","Allen scholarship",P["amber"]), + ("45+","LLMs in ecosystem",P["mint"]),("3","Research papers",P["rose"])]: + sc=GlowCard(accent=col,radius=12,alpha=175); scl2=QVBoxLayout(sc) + scl2.setContentsMargins(14,12,14,12); scl2.setSpacing(3) + sn=QLabel(num); sn.setFont(QFont("Segoe UI",17,QFont.Bold)) + sn.setStyleSheet(f"color:{col};background:transparent;"); sn.setAlignment(Qt.AlignCenter) + sl2=QLabel(lbl3); sl2.setFont(QFont("Segoe UI",9)); sl2.setWordWrap(True) + sl2.setStyleSheet(f"color:{P['dim']};background:transparent;"); sl2.setAlignment(Qt.AlignCenter) + scl2.addWidget(sn); scl2.addWidget(sl2); sg.addWidget(sc) + scl.addLayout(sg); lay.addWidget(stats_card) + tl_card=GlowCard(accent=P["mint"],radius=18,alpha=195) + tll=QVBoxLayout(tl_card); tll.setContentsMargins(28,22,28,22); tll.setSpacing(16) + tt=QLabel("๐Ÿ—บ๏ธ The Journey"); tt.setFont(QFont("Segoe UI",13,QFont.Bold)) + tt.setStyleSheet(f"color:{P['bright']};background:transparent;"); tll.addWidget(tt) + sep2=QFrame(); sep2.setFixedHeight(1); sep2.setStyleSheet(f"background:{P['ghost']};border:none;") + tll.addWidget(sep2) + + for age,col,title3,desc3 in [ + ("Age 9",P["mint"],"๐Ÿง’ Started Teaching Nursery Kids", + "While in 4th grade, Sonu began teaching nursery children โ€” planting the seed for education equity."), + ("Age 11",P["cyan"],"๐Ÿ”ฅ Bihar Viral Boy โ€” Met the Chief Minister", + "Challenged the CM on education & liquor ban. Went viral across India. Support from Cabinet Ministers, Bollywood actors & ALLEN founder."), + ("Age 13",P["violet"],"๐ŸŽค TEDx Speaker", + "One of India's youngest TEDx speakers โ€” rural education failures and using tech to bridge India's urban-rural divide."), + ("Age 13-14",P["amber"],"๐Ÿ’ป Founded NPMAI ECOSYSTEM โ€” 1.5M+ Downloads", + "Self-taught, no CS background. Built entire ecosystem on free cloud infrastructure."), + ("2026",P["rose"],"๐Ÿ“„ Published 3 Research Papers", + "LARA (RAG architecture) ยท RID (Representative Ideal Democracy) ยท IAST (Administrative System Theory)"), + ]: + row=QHBoxLayout(); row.setSpacing(16) + dot=QLabel("โ—‰"); dot.setFont(QFont("Segoe UI",13,QFont.Bold)) + dot.setStyleSheet(f"color:{col};background:transparent;"); dot.setFixedWidth(20); dot.setAlignment(Qt.AlignTop) + ev=QVBoxLayout(); ev.setSpacing(2) + al=QLabel(age); al.setFont(QFont("Cascadia Code",9,QFont.Bold)) + al.setStyleSheet(f"color:{col};background:transparent;") + tl2=QLabel(title3); tl2.setFont(QFont("Segoe UI",12,QFont.Bold)) + tl2.setStyleSheet(f"color:{P['bright']};background:transparent;") + dl=QLabel(desc3); dl.setFont(QFont("Segoe UI",11)); dl.setWordWrap(True) + dl.setStyleSheet(f"color:{P['mid']};background:transparent;") + ev.addWidget(al); ev.addWidget(tl2); ev.addWidget(dl) + row.addWidget(dot); row.addLayout(ev); tll.addLayout(row) + lay.addWidget(tl_card) + mc=GlowCard(accent=P["violet"],radius=18,alpha=195) + ml2=QVBoxLayout(mc); ml2.setContentsMargins(28,22,28,22); ml2.setSpacing(8) + mi=QLabel("๐Ÿงญ Mission"); mi.setFont(QFont("Segoe UI",13,QFont.Bold)) + mi.setStyleSheet(f"color:{P['violet']};background:transparent;"); ml2.addWidget(mi) + mq=QLabel('"Promoting Individual Journalism to every nation\'s village so that the democratic values of a nation can be strengthened and we can achieve Representative Ideal Democracy."') + mq.setFont(QFont("Segoe UI",12)); mq.setWordWrap(True) + mq.setStyleSheet(f"color:{P['mid']};font-style:italic;background:transparent;"); ml2.addWidget(mq) + ma=QLabel("โ€” Sonu Kumar, Founder, NPMAI ECOSYSTEM"); ma.setFont(QFont("Segoe UI",10)) + ma.setStyleSheet(f"color:{P['dim']};background:transparent;"); ml2.addWidget(ma) + lay.addWidget(mc); lay.addStretch(); scroll.setWidget(page); outer.addWidget(scroll) + +class DocsPage(QWidget): + def __init__(self,parent=None): + super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() + + def _sec(self,lay,icon,title,col,items): + card=GlowCard(accent=col,radius=18,alpha=195) + cl=QVBoxLayout(card); cl.setContentsMargins(28,22,28,22); cl.setSpacing(14) + h=QHBoxLayout() + ic=QLabel(icon); ic.setFont(QFont("Segoe UI",16)); ic.setStyleSheet("background:transparent;"); ic.setFixedWidth(28) + tt=QLabel(title); tt.setFont(QFont("Segoe UI",13,QFont.Bold)) + tt.setStyleSheet(f"color:{P['bright']};background:transparent;") + h.addWidget(ic); h.addWidget(tt); h.addStretch(); cl.addLayout(h) + sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") + cl.addWidget(sep) + for it,ib in items: + row=QHBoxLayout(); row.setSpacing(12) + dot=QLabel("โ–ธ"); dot.setFont(QFont("Segoe UI",11,QFont.Bold)) + dot.setStyleSheet(f"color:{col};background:transparent;"); dot.setFixedWidth(16); dot.setAlignment(Qt.AlignTop) + cv=QVBoxLayout(); cv.setSpacing(1) + it_l=QLabel(it); it_l.setFont(QFont("Segoe UI",11,QFont.Bold)) + it_l.setStyleSheet(f"color:{P['bright']};background:transparent;") + ib_l=QLabel(ib); ib_l.setFont(QFont("Segoe UI",11)); ib_l.setWordWrap(True) + ib_l.setStyleSheet(f"color:{P['mid']};background:transparent;") + cv.addWidget(it_l); cv.addWidget(ib_l) + row.addWidget(dot); row.addLayout(cv); cl.addLayout(row) + lay.addWidget(card) + + def _build(self): + outer=QVBoxLayout(self); outer.setContentsMargins(0,0,0,0) + scroll=QScrollArea(); scroll.setWidgetResizable(True) + scroll.setStyleSheet("QScrollArea{border:none;background:transparent;}") + page=QWidget(); page.setAttribute(Qt.WA_TranslucentBackground) + lay=QVBoxLayout(page); lay.setContentsMargins(36,36,36,36); lay.setSpacing(20) + + t=QLabel("๐Ÿ“– Documentation"); t.setFont(QFont("Segoe UI",20,QFont.Bold)) + t.setStyleSheet(f"color:{P['sky']};background:transparent;"); lay.addWidget(t) + sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") + lay.addWidget(sep); lay.addSpacing(4) + + self._sec(lay,"๐Ÿš€","Getting Started",P["mint"],[ + ("Download","Get NPM_AutoCode_AI from GitHub releases. Unzip anywhere. No installer."), + ("Launch","Double-click the .exe. If Windows shows SmartScreen, click 'Run Anyway'."), + ("First scan","Go to Settings and click 'Scan Now' โ€” agent learns your folder structure."), + ("Add credentials","Add Gmail/GitHub/Slack tokens in Settings โ†’ Credentials. Encrypted locally."), + ("Type a task","In the Agent tab, describe any task in plain English and press Run."), + ]) + self._sec(lay,"๐Ÿค–","How the Agent Works",P["cyan"],[ + ("Detect intent","Agent first decides: is this a conversation or a task to execute?"), + ("Plan","Planner LLM breaks the task into 2-5 atomic steps. Each step is independent."), + ("Generate","CodeLlama generates Python code for each step, using the 21 tool templates."), + ("Audit","Qwen2.5-Coder reviews generated code for 5 categories of high-risk behavior."), + ("Execute","Code runs as a real child process (not exec()). Stdout streams to log panel."), + ("Verify","A verifier LLM checks if the step actually completed, not just if Python exited 0."), + ("Retry","If a step fails, agent auto-debugs up to 12 times. Step context is preserved."), + ]) + self._sec(lay,"๐Ÿ”Œ","MCP Server",P["violet"],[ + ("Start","Click 'Start MCP Server' in the sidebar. Default port: 7799."), + ("Connect","Add to Claude Desktop MCP config: {\"type\":\"tcp\",\"host\":\"localhost\",\"port\":7799}"), + ("Tools exposed","All 21 tools + full agent pipeline available as MCP tools."), + ("Use from Claude","Type in Claude Desktop: 'use npm_agent to send an email to...' โ€” it works."), + ("Power other projects","Any MCP-compatible LLM or app can call your tools via this server."), + ]) + self._sec(lay,"โœ๏ธ","Writing Good Prompts",P["amber"],[ + ("Be specific","Say 'rename all .jpg files in C:/Users/Me/Desktop/Photos' not just 'rename photos'."), + ("Mention output","Say 'save as results.xlsx on Desktop' โ€” specify file name, format, location."), + ("One task at a time","Complex workflows run better as separate tasks. Chain them manually."), + ("Use examples","Click any chip below the input for a ready-made example task."), + ]) + self._sec(lay,"๐Ÿ”’","Security",P["rose"],[ + ("Dual-LLM audit","Every script reviewed by qwen2.5-coder:7b (fallback: falcon:7b) before execution."), + ("Subprocess isolation","Code runs as a child process โ€” a broken script cannot crash the app."), + ("Kill button","Click โ–  Kill at any time to terminate the running script immediately."), + ("Credential encryption","Fernet symmetric encryption, machine-specific key, local only. Never uploaded."), + ]) + lay.addStretch(); scroll.setWidget(page); outer.addWidget(scroll) + +def run_mcp_server(port:int, running_ref): + """ + Minimal TCP-based MCP server. + Receives JSON: {"tool": "...", "params": {...}} + Returns JSON: {"success": bool, "output": "...", "data": ...} + """ + import json as _json + brain = AgentBrain() + + TOOL_MAP = { + "npmai_chat": lambda p: _chat(brain, p), + "npmai_run_task": lambda p: _run_task(brain, p), + "email_send": lambda p: EmailTool.send(**p), + "email_bulk": lambda p: EmailTool.send_bulk(**p), + "email_inbox": lambda p: EmailTool.read_inbox(**p), + "file_rename": lambda p: FileTool.bulk_rename(**p), + "file_zip": lambda p: FileTool.zip_folder(**p), + "file_find": lambda p: FileTool.find_files(**p), + "file_organize": lambda p: FileTool.organize_by_type(**p), + "file_read": lambda p: FileTool.read_file(**p), + "file_write": lambda p: FileTool.write_file(**p), + "web_scrape": lambda p: WebTool.scrape(**p), + "web_download": lambda p: WebTool.download_file(**p), + "web_screenshot": lambda p: WebTool.screenshot_url(**p), + "web_api": lambda p: WebTool.api_call(**p), + "pdf_extract": lambda p: PDFTool.extract_text(**p), + "pdf_merge": lambda p: PDFTool.merge(**p), + "pdf_split": lambda p: PDFTool.split(**p), + "github_issue": lambda p: GitHubTool.create_issue(**p), + "github_push": lambda p: GitHubTool.push_file(**p), + "github_issues_list":lambda p: GitHubTool.list_issues(**p), + "github_clone": lambda p: GitHubTool.clone_repo(**p), + "github_push_commit":lambda p: GitHubTool.git_commit_push(**p), + "slack_send": lambda p: SlackTool.send_message(**p), + "slack_read": lambda p: SlackTool.read_channel(**p), + "slack_upload": lambda p: SlackTool.upload_file(**p), + "discord_webhook": lambda p: DiscordTool.send_webhook(**p), + "whatsapp_send": lambda p: WhatsAppTool.send(**p), + "notion_page": lambda p: NotionTool.create_page(**p), + "notion_db": lambda p: NotionTool.add_db_entry(**p), + "twitter_tweet": lambda p: TwitterTool.tweet(**p), + "system_run": lambda p: SystemTool.run_command(**p), + "system_screenshot": lambda p: SystemTool.screenshot(**p), + "system_clipboard_get": lambda p: SystemTool.get_clipboard(), + "system_clipboard_set": lambda p: SystemTool.set_clipboard(**p), + "system_notify": lambda p: SystemTool.notify(**p), + "image_resize": lambda p: ImageTool.resize(**p), + "image_convert": lambda p: ImageTool.convert(**p), + "image_ocr": lambda p: ImageTool.ocr(**p), + "image_compress": lambda p: ImageTool.bulk_compress(**p), + "telegram_send": lambda p: TelegramTool.send(**p), + "qr_generate": lambda p: QRTool.generate(**p), + "voice_speak": lambda p: VoiceTool.speak(**p), + "voice_listen": lambda p: VoiceTool.listen(**p), + "rag_query": lambda p: RAGTool.query_document(**p), + "rag_summarize": lambda p: RAGTool.summarize_large_file(**p), + "ssh_run": lambda p: SSHTool.run(**p), + "ssh_upload": lambda p: SSHTool.upload(**p), + "cred_save": lambda p: _cred_save(p), + "cred_load": lambda p: _cred_load(p), + "workspace_scan": lambda p: _ws_scan(), + "list_tools": lambda p: _list_tools(), + } + + def _chat(br, p): + r=br.chat(p.get("message","")) + from agent_core import ToolResult + return ToolResult(True,r) + + def _run_task(br, p): + results=[] + br2=AgentBrain(log_cb=results.append) + ok=br2.run_task(p.get("task","")) + from agent_core import ToolResult + return ToolResult(ok,"\n".join(results)) + + def _cred_save(p): + CredStore.save(p["key"],p["data"]) + from agent_core import ToolResult + return ToolResult(True,"Saved") + + def _cred_load(p): + data=CredStore.load(p["key"]) + from agent_core import ToolResult + return ToolResult(True,"Loaded",data) + + def _ws_scan(): + ws=Workspace(); ws.scan() + from agent_core import ToolResult + return ToolResult(True,"Scanned",ws.data) + + def _list_tools(): + from agent_core import ToolResult + return ToolResult(True,"Tools listed",list(TOOL_MAP.keys())) + + def handle_client(conn): + try: + data=b"" + while True: + chunk=conn.recv(4096) + if not chunk: break + data+=chunk + if data.endswith(b"\n"): break + req=_json.loads(data.decode()) + tool=req.get("tool","") + params=req.get("params",{}) + if tool in TOOL_MAP: + result=TOOL_MAP[tool](params) + resp={"success":result.success,"output":result.output, + "data":result.data if hasattr(result,"data") else None} + else: + resp={"success":False,"output":f"Unknown tool: {tool}","data":None} + except Exception as e: + resp={"success":False,"output":f"MCP error: {e}","data":None} + try: + conn.sendall((_json.dumps(resp)+"\n").encode()) + except: pass + conn.close() + + srv=socket.socket(socket.AF_INET,socket.SOCK_STREAM) + srv.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) + srv.bind(("0.0.0.0",port)); srv.listen(10); srv.settimeout(1.0) + while getattr(running_ref,"val",True): + try: + conn,_=srv.accept() + threading.Thread(target=handle_client,args=(conn,),daemon=True).start() + except socket.timeout: continue + except: break + srv.close() + +class AppWindow(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("NPM Agent โ€” NPMAI Ecosystem v3.0") + self.resize(1220,780); self.setMinimumSize(920,640) + self._build() + + def _build(self): + self.bg=CosmicBG(self); self.bg.lower() + root=QHBoxLayout(self); root.setContentsMargins(0,0,0,0); root.setSpacing(0) + self.sidebar=Sidebar(); self.sidebar.page_changed.connect(self._switch) + root.addWidget(self.sidebar) + content=QWidget(); content.setAttribute(Qt.WA_TranslucentBackground) + cl=QVBoxLayout(content); cl.setContentsMargins(0,0,0,0) + self.stack=QStackedWidget(); self.stack.setAttribute(Qt.WA_TranslucentBackground) + self.stack.setStyleSheet("background:transparent;") + self.stack.addWidget(AgentPage()) + self.stack.addWidget(HistoryPage()) + self.stack.addWidget(ToolsPage()) + self.stack.addWidget(SettingsPage()) + self.stack.addWidget(FounderPage()) + self.stack.addWidget(DocsPage()) + cl.addWidget(self.stack); root.addWidget(content) + + def resizeEvent(self,e): + super().resizeEvent(e); self.bg.setGeometry(0,0,self.width(),self.height()) + + def _switch(self,idx): + if idx==1: + self.stack.widget(1).load() + + widget=self.stack.widget(idx) + eff=QGraphicsOpacityEffect(widget); widget.setGraphicsEffect(eff) + anim=QPropertyAnimation(eff,b"opacity",self) + anim.setDuration(280); anim.setStartValue(0); anim.setEndValue(1) + anim.setEasingCurve(QEasingCurve.OutCubic) + self.stack.setCurrentIndex(idx); anim.start() + self._anim=anim + + def closeEvent(self,e): + base=os.path.dirname(os.path.abspath(__file__)) + for fn in ["coder0-generator.json","safety_checker.json", + "agent_plan.json","agent_code.json","agent_chat.json","agent_tasks.json"]: + fp=os.path.join(base,fn) + if os.path.exists(fp): + try: os.remove(fp) + except: pass + super().closeEvent(e) + +if __name__=="__main__": + app=QApplication(sys.argv); app.setStyle("Fusion") + pal=QPalette() + pal.setColor(QPalette.Window, QColor(P["void"])) + pal.setColor(QPalette.WindowText, QColor(P["bright"])) + pal.setColor(QPalette.Base, QColor(P["deep"])) + pal.setColor(QPalette.AlternateBase, QColor(P["panel"])) + pal.setColor(QPalette.Text, QColor(P["bright"])) + pal.setColor(QPalette.Button, QColor(P["lift"])) + pal.setColor(QPalette.ButtonText, QColor(P["bright"])) + pal.setColor(QPalette.Highlight, QColor(P["mint"])) + pal.setColor(QPalette.HighlightedText, QColor("#050310")) + app.setPalette(pal) + win=AppWindow(); win.show(); sys.exit(app.exec()) From 22357da1d69d0f5e66803d7d3c7fb5a8793d9616 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 1 Jul 2026 10:54:44 +0530 Subject: [PATCH 36/68] Create codeql.yml --- .github/workflows/codeql.yml | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e71b99c --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,51 @@ +name: "CodeQL Advanced" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '41 21 * * 6' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + security-events: write + + packages: read + + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: python + build-mode: none + - language: javascript + build-mode: none + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo "Reviewing code" + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" From b96acb6abcbac71b3e578740873e6bc61b71bcc5 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 12 Jul 2026 19:44:44 +0530 Subject: [PATCH 37/68] Update NPM-AutoCode-AI(V3).py --- Desktop_App/NPM-AutoCode-AI(V3).py | 666 +++++++++++------------------ 1 file changed, 239 insertions(+), 427 deletions(-) diff --git a/Desktop_App/NPM-AutoCode-AI(V3).py b/Desktop_App/NPM-AutoCode-AI(V3).py index a0ccdbc..1369767 100644 --- a/Desktop_App/NPM-AutoCode-AI(V3).py +++ b/Desktop_App/NPM-AutoCode-AI(V3).py @@ -1,32 +1,28 @@ -import sys, os, json, threading, time, math, random, socket +""" +NPMAI Agent Desktop โ€” v4 (npmai_agents edition) + +Here it is about Desktop APP. +""" +import sys, os, math, random, threading from pathlib import Path -from datetime import datetime sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from agent_core import ( - AgentBrain, Workspace, CredStore, TOOLS, TOOLS_SUMMARY, - EmailTool, FileTool, WebTool, SpreadsheetTool, GitHubTool, - SlackTool, PDFTool, ImageTool, SystemTool, TelegramTool, - QRTool, RAGTool, SSHTool, DiscordTool, VoiceTool, SchedulerTool, - WatcherTool, NotionTool, TwitterTool, JiraTool, WhatsAppTool -) + +from npmai_agents import AgentBrain, Workspace, CredStore from PySide6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QTextEdit, QLineEdit, QPushButton, QFrame, QScrollArea, QStackedWidget, QSizePolicy, QGraphicsOpacityEffect, - QSpacerItem, QTabWidget, QFormLayout, QComboBox, - QCheckBox, QDialog, QDialogButtonBox, QListWidget, QListWidgetItem, - QSplitter, QProgressBar, QFileDialog, QMessageBox, QToolButton, + QTabWidget, QDialog, QDialogButtonBox, QMessageBox, QGroupBox ) from PySide6.QtCore import ( QThread, Signal, Qt, QTimer, QPointF, QRectF, QRect, - QPropertyAnimation, QEasingCurve, QSize, QObject, Property + QPropertyAnimation, QEasingCurve, ) from PySide6.QtGui import ( QFont, QColor, QPalette, QLinearGradient, QPainter, QBrush, QPen, - QPainterPath, QRadialGradient, QCursor, QTextCursor, QPixmap, - QFontMetrics + QPainterPath, QRadialGradient, ) P = { @@ -39,18 +35,40 @@ "ghost": "#2A2744", } +_TASK_KEYWORDS = [ + "file", "folder", "git", "github", "gitlab", "docker", "kubernetes", "k8s", + "terraform", "aws", "s3", "lambda", "cloudflare", "vercel", "netlify", "railway", + "stripe", "razorpay", "shopify", "invoice", "crm", "inventory", "contract", + "email", "smtp", "teams", "zoom", "twilio", "sendgrid", "webhook", "calendar", + "notion", "linear", "asana", "trello", "clickup", "todoist", "obsidian", + "figma", "blender", "svg", "canva", "diagram", "3d", + "scrape", "download", "screenshot", "database", "report", "chart", + "ffmpeg", "youtube", "audio", "video", "image", "resize", "convert", "podcast", + "ssh", "terminal", "run command", "backup", "zip", "unzip", "schedule", + "encrypt", "scan", "deploy", "network", "printer", "clipboard", +] + + +def _looks_like_task(text: str) -> bool: + words = text.strip().split() + if len(words) > 6: + return True + lower = text.lower() + return any(kw in lower for kw in _TASK_KEYWORDS) + + class AgentWorker(QThread): log_sig = Signal(str) progress_sig = Signal(int) status_sig = Signal(str) done_sig = Signal(bool, str) - bubble_sig = Signal(str, bool) + bubble_sig = Signal(str, bool) def __init__(self, task: str): super().__init__() - self.task = task - self._killed = [False] - self._brain = None + self.task = task + self._killed = [False] + self._brain = None def kill(self): self._killed[0] = True @@ -63,25 +81,17 @@ def run(self): progress_cb = self.progress_sig.emit, status_cb = self.status_sig.emit, ) - words = self.task.strip().split() - is_chat = ( - len(words) <= 6 and - not any(kw in self.task.lower() for kw in - ["file","folder","send","email","scrape","download","create", - "rename","zip","run","open","install","write","read","github", - "slack","tweet","whatsapp","telegram","pdf","image","resize", - "schedule","watch","ssh","notion","jira","qr","voice"]) - ) - if is_chat: - resp = self._brain.chat(self.task) - self.bubble_sig.emit(resp, True) - self.progress_sig.emit(100) - self.done_sig.emit(True, resp) - else: + if _looks_like_task(self.task): ok = self._brain.run_task(self.task, killed_flag=self._killed) msg = "โœ“ Task completed!" if ok else "โœ— Task failed." self.bubble_sig.emit(msg, True) self.done_sig.emit(ok, msg) + else: + resp = self._brain.chat(self.task) + self.bubble_sig.emit(resp, True) + self.progress_sig.emit(100) + self.done_sig.emit(True, resp) + class CosmicBG(QWidget): def __init__(self, parent=None): @@ -289,34 +299,27 @@ def paintEvent(self,_): class ChatBubble(QWidget): def __init__(self, text:str, is_agent:bool, parent=None): super().__init__(parent) - self._is_agent = is_agent self.setAttribute(Qt.WA_TranslucentBackground) lay = QHBoxLayout(self); lay.setContentsMargins(8,4,8,4) - bubble = QLabel(text) bubble.setWordWrap(True) bubble.setFont(QFont("Segoe UI",12)) bubble.setTextInteractionFlags(Qt.TextSelectableByMouse) bubble.setMaximumWidth(680) bubble.setSizePolicy(QSizePolicy.Preferred,QSizePolicy.Minimum) - if is_agent: - bubble.setStyleSheet(f""" - QLabel{{background:rgba(17,16,40,220); + bubble.setStyleSheet(f"""QLabel{{background:rgba(17,16,40,220); border:1px solid rgba(42,255,160,80); border-radius:16px;border-top-left-radius:4px; padding:12px 16px;color:{P['bright']};line-height:1.6;}}""") lay.addWidget(bubble); lay.addStretch() else: - bubble.setStyleSheet(f""" - QLabel{{background:qlineargradient(x1:0,y1:0,x2:1,y2:1, + bubble.setStyleSheet(f"""QLabel{{background:qlineargradient(x1:0,y1:0,x2:1,y2:1, stop:0 rgba(42,255,160,40),stop:1 rgba(0,229,255,30)); border:1px solid rgba(42,255,160,120); border-radius:16px;border-top-right-radius:4px; padding:12px 16px;color:{P['bright']};}}""") lay.addStretch(); lay.addWidget(bubble) - - eff = QGraphicsOpacityEffect(bubble); bubble.setGraphicsEffect(eff) anim = QPropertyAnimation(eff,b"opacity",self) anim.setDuration(350); anim.setStartValue(0); anim.setEndValue(1) @@ -371,6 +374,57 @@ def paintEvent(self,_): p.setFont(QFont("Segoe UI",11,QFont.Bold if self._active else QFont.Normal)) p.drawText(QRect(52,0,w-60,h),Qt.AlignVCenter|Qt.AlignLeft,self.LABELS[self._idx]) p.end() + +class LoginDialog(QDialog): + """Email/password only, per spec. Appears only when the user clicks + 'Get MCP Link' โ€” never blocks any other part of the app.""" + + def __init__(self, link_mgr, parent=None): + super().__init__(parent) + self._mgr = link_mgr + self.setWindowTitle("Log in โ€” MCP Link") + self.setFixedSize(360, 260) + self.setStyleSheet(f"QDialog{{background:{P['void']};}}") + lay = QVBoxLayout(self); lay.setContentsMargins(28,24,28,24); lay.setSpacing(12) + + title = QLabel("๐Ÿ”— Get your MCP Link") + title.setFont(QFont("Segoe UI",15,QFont.Bold)) + title.setStyleSheet(f"color:{P['mint']};background:transparent;") + sub = QLabel("Login is only needed here โ€” chat and tasks never require it.") + sub.setFont(QFont("Segoe UI",9)); sub.setWordWrap(True) + sub.setStyleSheet(f"color:{P['mid']};background:transparent;") + lay.addWidget(title); lay.addWidget(sub) + + self._email = GlowInput("Email"); lay.addWidget(self._email) + self._pw = GlowInput("Password"); self._pw.setEchoMode(QLineEdit.Password) + lay.addWidget(self._pw) + + row = QHBoxLayout(); row.setSpacing(10) + login_btn = PulseBtn("Log in", P["mint"], True) + signup_btn = GhostBtn("Sign up", P["cyan"]) + login_btn.clicked.connect(self._do_login) + signup_btn.clicked.connect(self._do_signup) + row.addWidget(login_btn); row.addWidget(signup_btn) + lay.addLayout(row) + + self._status = QLabel(""); self._status.setWordWrap(True) + self._status.setFont(QFont("Segoe UI",9)) + self._status.setStyleSheet(f"color:{P['rose']};background:transparent;") + lay.addWidget(self._status) + + def _do_login(self): + try: + self._mgr.log_in(self._email.text().strip(), self._pw.text()) + self.accept() + except Exception as e: + self._status.setText(str(e)) + + def _do_signup(self): + try: + self._mgr.sign_up(self._email.text().strip(), self._pw.text()) + self.accept() + except Exception as e: + self._status.setText(str(e)) class Sidebar(QWidget): page_changed = Signal(int) @@ -378,7 +432,9 @@ class Sidebar(QWidget): def __init__(self,parent=None): super().__init__(parent); self.setFixedWidth(215) self.setAttribute(Qt.WA_TranslucentBackground) - self._btns=[]; self._build() + self._btns=[] + self._link_mgr = None + self._build() def _build(self): lay=QVBoxLayout(self); lay.setContentsMargins(0,0,0,0); lay.setSpacing(0) @@ -387,12 +443,12 @@ def _build(self): ll=QVBoxLayout(lw); ll.setContentsMargins(18,20,18,10); ll.setSpacing(3) lg=QLabel("โš™ NPM Agent"); lg.setFont(QFont("Segoe UI",14,QFont.Bold)) lg.setStyleSheet(f"color:{P['mint']};background:transparent;") - lv=QLabel("v3.0 ยท NPMAI Ecosystem"); lv.setFont(QFont("Segoe UI",9)) + lv=QLabel("v4.0 ยท npmai_agents"); lv.setFont(QFont("Segoe UI",9)) lv.setStyleSheet(f"color:{P['dim']};background:transparent;") ll.addWidget(lg); ll.addWidget(lv); lay.addWidget(lw) self._sep(lay) - self._mcp_lbl=QLabel(" โ— MCP Server: Stopped") + self._mcp_lbl=QLabel(" โ— MCP Link: not connected") self._mcp_lbl.setFont(QFont("Segoe UI",9)) self._mcp_lbl.setStyleSheet(f"color:{P['dim']};background:transparent;padding:6px 0;") lay.addWidget(self._mcp_lbl) @@ -412,16 +468,16 @@ def _build(self): fw=QWidget(); fw.setAttribute(Qt.WA_TranslucentBackground) fl=QVBoxLayout(fw); fl.setContentsMargins(12,10,12,16); fl.setSpacing(8) - self._mcp_btn=QPushButton("โ–ถ Start MCP Server") + self._mcp_btn=QPushButton("๐Ÿ”— Get MCP Link") self._mcp_btn.setCursor(Qt.PointingHandCursor); self._mcp_btn.setFixedHeight(34) - self._mcp_btn.setStyleSheet(self._btn_style(P["mint"])) - self._mcp_btn.clicked.connect(self._toggle_mcp) + self._mcp_btn.setStyleSheet(self._btn_style()) + self._mcp_btn.clicked.connect(self._get_mcp_link) fl.addWidget(self._mcp_btn) for lbl,url in [("๐Ÿ PyPI","https://pypi.org/project/npmai"), ("โญ GitHub","https://github.com/sonuramashishnpm")]: b2=QPushButton(lbl); b2.setCursor(Qt.PointingHandCursor); b2.setFixedHeight(32) - b2.setStyleSheet(self._btn_style(P["dim"])) + b2.setStyleSheet(self._btn_style()) import webbrowser b2.clicked.connect(lambda _,u=url: webbrowser.open(u)) fl.addWidget(b2) @@ -430,9 +486,7 @@ def _build(self): eco.setStyleSheet(f"color:{P['dim']};background:transparent;"); eco.setAlignment(Qt.AlignCenter) fl.addWidget(eco); lay.addWidget(fw) - self._mcp_running=False; self._mcp_thread=None; self._mcp_port=7799 - - def _btn_style(self,col): + def _btn_style(self): return f"""QPushButton{{background:rgba(42,255,160,10);border:1px solid rgba(42,255,160,35); border-radius:9px;color:{P['mid']};font-size:11px;font-family:'Segoe UI';}} QPushButton:hover{{background:rgba(42,255,160,22);border-color:rgba(42,255,160,90);color:{P['mint']};}}""" @@ -445,27 +499,32 @@ def _on_nav(self,idx): for i,b in enumerate(self._btns): b.set_active(i==idx) self.page_changed.emit(idx) - def _toggle_mcp(self): - if self._mcp_running: - self._mcp_running=False - self._mcp_btn.setText("โ–ถ Start MCP Server") - self._mcp_lbl.setText(" โ— MCP Server: Stopped") - self._mcp_lbl.setStyleSheet(f"color:{P['dim']};background:transparent;padding:6px 0;") - else: - self._mcp_running=True - self._mcp_btn.setText("โ–  Stop MCP Server") - self._mcp_lbl.setText(f" โ— MCP Server: :{self._mcp_port}") - self._mcp_lbl.setStyleSheet(f"color:{P['mint']};background:transparent;padding:6px 0;") - self._mcp_thread=threading.Thread( - target=run_mcp_server, args=(self._mcp_port,self._mcp_running_ref()), daemon=True) - self._mcp_thread.start() + def _get_mcp_link(self): + """ import here, not at module load time โ€” mcp_link.py needs supabase """ + try: + from mcp_link import MCPLinkManager, SupabaseAuthError + except ImportError as e: + QMessageBox.warning(self, "Missing dependency", + f"MCP link needs the 'supabase' package.\nRun: pip install supabase\n\n{e}") + return + + if self._link_mgr is None: + self._link_mgr = MCPLinkManager(log_cb=lambda s: self._mcp_lbl.setText(f" โ— {s[:28]}")) - def _mcp_running_ref(self): - class Ref: - def __init__(s): s.val=True - def __bool__(s): return s.val - r=Ref() - self._mcp_ref=r; return r + if not self._link_mgr.is_logged_in: + dlg = LoginDialog(self._link_mgr, self) + if dlg.exec() != QDialog.Accepted: + return + + try: + link = self._link_mgr.get_or_create_link() + self._link_mgr.start_listener() + self._mcp_lbl.setText(" โ— MCP Link: connected") + self._mcp_lbl.setStyleSheet(f"color:{P['mint']};background:transparent;padding:6px 0;") + QMessageBox.information(self, "Your MCP Link", + f"Paste this into Claude/Grok's custom connector settings:\n\n{link}") + except Exception as e: + QMessageBox.warning(self, "Couldn't get MCP link", str(e)) def paintEvent(self,_): p=QPainter(self); p.setRenderHint(QPainter.Antialiasing) @@ -488,12 +547,12 @@ def _build(self): tbl=QHBoxLayout(topbar); tbl.setContentsMargins(36,12,36,12); tbl.setSpacing(12) title=QLabel("NPM Agent"); title.setFont(QFont("Segoe UI",18,QFont.Bold)) title.setStyleSheet(f"color:{P['mint']};background:transparent;") - sub=QLabel("Claude Code ยท Cowork ยท MCP โ€” all in one"); sub.setFont(QFont("Segoe UI",10)) + sub=QLabel("Runs fully locally ยท no login required"); sub.setFont(QFont("Segoe UI",10)) sub.setStyleSheet(f"color:{P['dim']};background:transparent;") tc=QVBoxLayout(); tc.setSpacing(0); tc.addWidget(title); tc.addWidget(sub) tbl.addLayout(tc); tbl.addStretch() - for badge,col in [("โšก 21 Tools",P["mint"]),("๐Ÿ”’ Secure",P["violet"]),("๐Ÿค– Agentic",P["cyan"])]: + for badge,col in [("โšก 100 Tools",P["mint"]),("๐Ÿ”’ Local-first",P["violet"]),("๐Ÿค– Agentic",P["cyan"])]: b=QLabel(f" {badge} "); b.setFont(QFont("Segoe UI",9,QFont.Bold)) b.setStyleSheet(f"color:{col};background:rgba(42,255,160,12);border:1px solid rgba(42,255,160,35);border-radius:9px;padding:3px 8px;") tbl.addWidget(b) @@ -541,11 +600,11 @@ def _build(self): bl.addLayout(irow) chips=QHBoxLayout(); chips.setSpacing(8) self._chip_tasks=[ - ("๐Ÿ“Š Plot CSV","Read data.csv from Desktop and create a bar chart PNG"), ("๐Ÿ“ Rename files","Rename all files in Downloads adding today's date prefix"), ("๐ŸŒ Scrape web","Scrape top 20 headlines from https://news.ycombinator.com save to news.xlsx on Desktop"), - ("๐Ÿ“ง Bulk email","Send a hello email to all addresses in contacts.csv on Desktop"), - ("๐Ÿ”’ Zip PDFs","Find all PDFs in Documents sort by date zip into archive.zip on Desktop"), + ("โญ GitHub issue","Create a GitHub issue titled 'test' in my repo"), + ("โ˜ AWS S3","List all objects in my S3 bucket"), + ("๐Ÿ”’ Zip files","Find all PDFs in Documents sort by date zip into archive.zip on Desktop"), ("๐Ÿ–ผ Resize imgs","Resize all images in Desktop/Photos folder to 800x600"), ] for label,task in self._chip_tasks: @@ -571,9 +630,14 @@ def _log(self, html:str): self._log_box.verticalScrollBar().setValue(self._log_box.verticalScrollBar().maximum()) def _voice_input(self): - r=VoiceTool.listen(5) + """ If you are working with voice input kindly read the documentation of npmai_agents and this code uses + npmai_agents version 1.0.2, npmai_agents change frequently although not syntax but it will be better to read + before making any change.""" + """ old VoiceTool.listen(): SpeechAITool.transcribe_realtime """ + from npmai_agents.Tools_security_ai import SpeechAITool + r = SpeechAITool.transcribe_realtime(duration=5) if r.success and r.data: - self._input.setText(r.data) + self._input.setText(r.data.get("text","")) def _kill(self): if self._worker: self._worker.kill() @@ -601,9 +665,9 @@ def _send(self): def _done(self, ok:bool, msg:str): self._run_btn.setEnabled(True); self._run_btn.setText("โ–ถ Run") self._kill_btn.setEnabled(False) - col=P["mint"] if ok else P["rose"] self._status_lbl.setText("Done โœ“" if ok else "Failed") + class HistoryPage(QWidget): def __init__(self,parent=None): super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() @@ -656,6 +720,17 @@ def load(self): rl.addWidget(dot); rl.addLayout(tc); rl.addStretch() self._items_layout.addWidget(row) + +def _discover_real_tools(): + import npmai_agents as pkg + tools = [] + for attr_name in dir(pkg): + obj = getattr(pkg, attr_name) + if isinstance(obj, type) and hasattr(obj, "name") and hasattr(obj, "description"): + tools.append((getattr(obj, "name"), attr_name, getattr(obj, "description"))) + return sorted(tools, key=lambda t: t[1]) + + class ToolsPage(QWidget): def __init__(self,parent=None): super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() @@ -666,85 +741,47 @@ def _build(self): scroll.setStyleSheet("QScrollArea{border:none;background:transparent;}") page=QWidget(); page.setAttribute(Qt.WA_TranslucentBackground) lay=QVBoxLayout(page); lay.setContentsMargins(36,36,36,36); lay.setSpacing(20) - t=QLabel("๐Ÿ›  Tool Registry โ€” 21 Integrated Tools") + + tools = _discover_real_tools() + t=QLabel(f"๐Ÿ›  Tool Registry โ€” {len(tools)} Integrated Tools") t.setFont(QFont("Segoe UI",20,QFont.Bold)) t.setStyleSheet(f"color:{P['amber']};background:transparent;") lay.addWidget(t) - desc=QLabel("All tools are available to the agent automatically. Just describe your task in plain English.") + desc=QLabel("Read directly from npmai_agents โ€” always matches what's actually installed. " + "All tools are available to the agent automatically; just describe your task.") desc.setFont(QFont("Segoe UI",11)); desc.setWordWrap(True) desc.setStyleSheet(f"color:{P['mid']};background:transparent;") lay.addWidget(desc) sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") lay.addWidget(sep); lay.addSpacing(6) - tool_info=[ - ("๐Ÿ“ง","Email","EmailTool",P["mint"], - "Send/receive emails via Gmail SMTP/IMAP ยท Bulk email from CSV ยท Attachments ยท Template variables"), - ("๐Ÿ“","Files","FileTool",P["cyan"], - "Bulk rename ยท Zip/unzip ยท Find files ยท Organize by type ยท Read/write ยท Copy folder trees"), - ("๐Ÿ“„","PDF","PDFTool",P["violet"], - "Extract text ยท Merge multiple PDFs ยท Split by page ยท Works with large documents"), - ("๐ŸŒ","Web","WebTool",P["amber"], - "Scrape with CSS selectors ยท Download files ยท Screenshot URLs ยท Full browser automation (Playwright) ยท API calls"), - ("๐Ÿ“Š","Spreadsheet","SpreadsheetTool",P["sky"], - "Read CSV ยท Write Excel ยท Google Sheets read/write ยท Pandas integration"), - ("โญ","GitHub","GitHubTool",P["mint"], - "Create/list issues ยท Push/update files ยท Get README ยท Clone repos ยท Git commit+push"), - ("๐Ÿ’ฌ","Slack","SlackTool",P["cyan"], - "Send messages ยท Read channel history ยท Upload files to channels"), - ("๐ŸŽฎ","Discord","DiscordTool",P["violet"], - "Send messages via webhook ยท Embed rich content"), - ("๐Ÿ“ฑ","WhatsApp","WhatsAppTool",P["amber"], - "Send WhatsApp messages instantly (requires WhatsApp Web)"), - ("๐Ÿ““","Notion","NotionTool",P["sky"], - "Create pages ยท Add database entries ยท Full Notion API integration"), - ("๐Ÿฆ","Twitter/X","TwitterTool",P["mint"], - "Post tweets ยท Read timeline ยท Manage account"), - ("๐Ÿ–ฅ","System","SystemTool",P["cyan"], - "Run shell commands ยท Clipboard get/set ยท Screenshot ยท Process list ยท OS notifications"), - ("๐Ÿ–ผ","Image","ImageTool",P["violet"], - "Resize ยท Convert format ยท OCR text extraction ยท Bulk compress"), - ("โฐ","Scheduler","SchedulerTool",P["amber"], - "Schedule tasks by interval or time ยท Background threads ยท Cancel anytime"), - ("๐ŸŽซ","Jira","JiraTool",P["sky"], - "Create/update issues ยท Sprint management ยท Project tracking"), - ("โœˆ","Telegram","TelegramTool",P["mint"], - "Send messages via bot token ยท No WhatsApp Web required"), - ("๐Ÿ”ฒ","QR Code","QRTool",P["cyan"], - "Generate QR codes from any text or URL ยท Configurable size"), - ("๐ŸŽค","Voice","VoiceTool",P["violet"], - "Text-to-speech output ยท Speech-to-text input from microphone"), - ("๐Ÿ‘","Watcher","WatcherTool",P["amber"], - "Watch folders for file changes ยท Trigger automated actions"), - ("๐Ÿ“š","RAG","RAGTool",P["sky"], - "Query large documents using npmai Rag class ยท Summarize huge files by chunking ยท PDF/TXT support"), - ("๐Ÿ–ง","SSH","SSHTool",P["mint"], - "Run commands on remote servers ยท Upload files via SFTP ยท Paramiko-powered"), - ] - grid_cols=2 - grid_row=None - for i,(icon,name,cls,col,desc2) in enumerate(tool_info): - if i%grid_cols==0: - grid_row=QHBoxLayout(); grid_row.setSpacing(16); lay.addLayout(grid_row) - card=GlowCard(accent=col,radius=16,alpha=195); card.setFixedHeight(118) + + cols_palette = [P["mint"],P["cyan"],P["violet"],P["amber"],P["sky"],P["rose"]] + grid_cols = 2 + grid_row = None + for i, (tool_name, cls_name, description) in enumerate(tools): + if i % grid_cols == 0: + grid_row = QHBoxLayout(); grid_row.setSpacing(16); lay.addLayout(grid_row) + col = cols_palette[i % len(cols_palette)] + card=GlowCard(accent=col,radius=16,alpha=195); card.setFixedHeight(108) cl=QHBoxLayout(card); cl.setContentsMargins(20,16,20,16); cl.setSpacing(14) - ic_lbl=QLabel(icon); ic_lbl.setFont(QFont("Segoe UI",22)) - ic_lbl.setStyleSheet("background:transparent;"); ic_lbl.setFixedWidth(36) cv=QVBoxLayout(); cv.setSpacing(4) - n=QLabel(name); n.setFont(QFont("Segoe UI",13,QFont.Bold)) + n=QLabel(cls_name); n.setFont(QFont("Segoe UI",13,QFont.Bold)) n.setStyleSheet(f"color:{P['bright']};background:transparent;") - c2=QLabel(cls); c2.setFont(QFont("Cascadia Code",9)) + c2=QLabel(tool_name); c2.setFont(QFont("Cascadia Code",9)) c2.setStyleSheet(f"color:{col};background:transparent;") - d=QLabel(desc2); d.setFont(QFont("Segoe UI",10)); d.setWordWrap(True) + d=QLabel(description[:140]+("โ€ฆ" if len(description)>140 else "")) + d.setFont(QFont("Segoe UI",10)); d.setWordWrap(True) d.setStyleSheet(f"color:{P['mid']};background:transparent;") cv.addWidget(n); cv.addWidget(c2); cv.addWidget(d) - cl.addWidget(ic_lbl); cl.addLayout(cv) + cl.addLayout(cv) grid_row.addWidget(card) - if len(tool_info)%grid_cols!=0: + if len(tools) % grid_cols != 0 and grid_row is not None: grid_row.addStretch() lay.addStretch() scroll.setWidget(page); outer.addWidget(scroll) + class SettingsPage(QWidget): def __init__(self,parent=None): super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() @@ -786,11 +823,13 @@ def _build(self): lay=QVBoxLayout(page); lay.setContentsMargins(36,36,36,36); lay.setSpacing(20) t=QLabel("โš™ Settings & Credentials"); t.setFont(QFont("Segoe UI",20,QFont.Bold)) t.setStyleSheet(f"color:{P['violet']};background:transparent;"); lay.addWidget(t) - sub=QLabel("All credentials are encrypted with a machine-specific key and stored locally. Never sent anywhere.") + sub=QLabel("All credentials are encrypted with a machine-specific key (CredStore) and stored " + "locally. Never sent anywhere. No login needed for anything on this page.") sub.setFont(QFont("Segoe UI",10)); sub.setWordWrap(True) sub.setStyleSheet(f"color:{P['mid']};background:transparent;"); lay.addWidget(sub) sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") lay.addWidget(sep); lay.addSpacing(6) + ws_card=GlowCard(accent=P["cyan"],radius=16,alpha=195) wl=QHBoxLayout(ws_card); wl.setContentsMargins(24,16,24,16); wl.setSpacing(16) wl_v=QVBoxLayout(); wl_v.setSpacing(4) @@ -806,75 +845,55 @@ def _scan(): QMessageBox.information(self,"Scanned","Workspace scanned successfully!") scan_btn.clicked.connect(_scan); wl.addLayout(wl_v); wl.addStretch(); wl.addWidget(scan_btn) lay.addWidget(ws_card) - prof_card=GlowCard(accent=P["mint"],radius=16,alpha=195) - pl=QVBoxLayout(prof_card); pl.setContentsMargins(24,18,24,18); pl.setSpacing(12) - pt=QLabel("๐Ÿ‘ค User Profile"); pt.setFont(QFont("Segoe UI",13,QFont.Bold)) - pt.setStyleSheet(f"color:{P['bright']};background:transparent;"); pl.addWidget(pt) - ws=Workspace() - self._name_inp=GlowInput("Your name (agent uses this)"); self._name_inp.setText(ws.data.get("user_name","")) - pl.addWidget(QLabel("Your Name:")); pl.addWidget(self._name_inp) - save_prof=PulseBtn("๐Ÿ’พ Save Profile",P["mint"],True); save_prof.setFixedHeight(40) - def _save_prof(): - ws2=Workspace(); ws2.update_profile("user_name",self._name_inp.text().strip()) - QMessageBox.information(self,"Saved","Profile saved!") - save_prof.clicked.connect(_save_prof); pl.addWidget(save_prof) - lay.addWidget(prof_card) - - self._section(lay,"๐Ÿ“ง Gmail / Email",[ - ("email","Email address","your@gmail.com",False), - ("password","App password (not your login password)","Gmail app password",True), - ("smtp_host","SMTP host","smtp.gmail.com",False), - ("imap_host","IMAP host","imap.gmail.com",False), - ],"gmail",P["mint"]) + # โ”€โ”€ Credential sections โ€” cred_key + fields verified against source โ”€โ”€ self._section(lay,"โญ GitHub",[ ("token","Personal access token (repo + issues scope)","ghp_xxxxxxxxxxxx",True), ],"github",P["cyan"]) - self._section(lay,"๐Ÿ’ฌ Slack",[ - ("bot_token","Bot OAuth token","xoxb-xxxxxxxxxxxx",True), - ],"slack",P["violet"]) - - self._section(lay,"๐Ÿฆ Twitter/X",[ - ("api_key","API Key","",True),("api_secret","API Secret","",True), - ("access_token","Access Token","",True),("access_token_secret","Access Token Secret","",True), - ],"twitter",P["sky"]) + self._section(lay,"๐Ÿ“ง Email (SMTP)",[ + ("email","Email address","your@gmail.com",False), + ("password","App password","Gmail app password",True), + ("smtp_host","SMTP host","smtp.gmail.com",False), + ("smtp_port","SMTP port","587",False), + ],"smtp",P["mint"]) self._section(lay,"๐Ÿ““ Notion",[ ("token","Integration token","secret_xxxxxxxxxxxx",True), ],"notion",P["amber"]) - self._section(lay,"โœˆ Telegram",[ - ("bot_token","Bot token (from @BotFather)","",True), - ],"telegram",P["rose"]) + self._section(lay,"๐Ÿ’ณ Stripe",[ + ("secret_key","Secret key","sk_live_xxxxxxxxxxxx",True), + ],"stripe",P["violet"]) - self._section(lay,"๐ŸŽซ Jira",[ - ("server","Jira server URL","https://yourcompany.atlassian.net",False), - ("email","Jira account email","",False), - ("api_token","API token","",True), - ],"jira",P["mint"]) + self._section(lay,"โ˜ AWS",[ + ("access_key_id","Access key ID","AKIA...",False), + ("secret_access_key","Secret access key","",True), + ("region","Region","us-east-1",False), + ],"aws",P["sky"]) - self._section(lay,"๐Ÿ–ง SSH",[ - ("user","SSH username","ubuntu",False), - ("password","SSH password (or leave blank for key)","",True), - ("key_path","Path to private key (optional)","~/.ssh/id_rsa",False), - ],"ssh",P["cyan"]) + note=QLabel("More tools follow the same pattern โ€” CredStore.save('', {...}). " + "Check each tool's `use` docstring (visible to the agent's Coder role) for its " + "exact cred_key and field names before wiring up a new section here.") + note.setFont(QFont("Segoe UI",9)); note.setWordWrap(True) + note.setStyleSheet(f"color:{P['dim']};background:transparent;"); lay.addWidget(note) mcp_card=GlowCard(accent=P["mint"],radius=16,alpha=195) ml=QVBoxLayout(mcp_card); ml.setContentsMargins(24,18,24,18); ml.setSpacing(8) - mt=QLabel("๐Ÿ”Œ MCP Server Info"); mt.setFont(QFont("Segoe UI",13,QFont.Bold)) + mt=QLabel("๐Ÿ”— MCP Link"); mt.setFont(QFont("Segoe UI",13,QFont.Bold)) mt.setStyleSheet(f"color:{P['bright']};background:transparent;"); ml.addWidget(mt) md=QLabel( - "Start the MCP Server from the sidebar to expose all 21 tools via the MCP protocol.\n" - "Default port: 7799\n" - "Connect from Claude Desktop by adding to your MCP config:\n" - '{ "type": "tcp", "host": "localhost", "port": 7799 }' + "Click 'Get MCP Link' in the sidebar to log in and receive your personal link. " + "Paste it into Claude/Grok's custom connector settings โ€” this app then executes " + "already-audited code sent by the hosted MCP server and reports results back.\n" + "This is the ONLY feature in the app that requires login." ) - md.setFont(QFont("Cascadia Code",10)); md.setWordWrap(True) + md.setFont(QFont("Segoe UI",10)); md.setWordWrap(True) md.setStyleSheet(f"color:{P['mid']};background:transparent;"); ml.addWidget(md) lay.addWidget(mcp_card) lay.addStretch(); scroll.setWidget(page); outer.addWidget(scroll) + class FounderPage(QWidget): def __init__(self,parent=None): super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() @@ -896,83 +915,15 @@ def _build(self): tag=QLabel("Founder ยท NPMAI ECOSYSTEM ยท Bihar Viral Boy") tag.setFont(QFont("Segoe UI",12)); tag.setStyleSheet(f"color:{P['violet']};background:transparent;") hl.addWidget(tag) - roles=QLabel("Software Developer ยท AI Developer ยท Web Developer ยท Cloud Developer ยท DevOps\nTEDx Speaker ยท Social Thinker ยท Researcher\nStudent at Allen Career Institute & Disha Delphi Public School, Kota (100% Scholarship)") - roles.setFont(QFont("Segoe UI",11)); roles.setWordWrap(True) - roles.setStyleSheet(f"color:{P['mid']};background:transparent;"); hl.addWidget(roles) - badges=QHBoxLayout(); badges.setSpacing(8) - for txt,col in [("Age 15",P["mint"]),("Bihar, India",P["cyan"]),("Kota, Rajasthan",P["violet"]), - ("434K+ Followers",P["amber"]),("TEDx Speaker",P["rose"]),("Allen Scholar",P["mint"])]: - b=QLabel(f" {txt} "); b.setFont(QFont("Segoe UI",10,QFont.Bold)) - b.setStyleSheet(f"color:{col};padding:4px 8px;background:rgba(42,255,160,10);border:1px solid rgba(42,255,160,30);border-radius:9px;") - badges.addWidget(b) - badges.addStretch(); hl.addLayout(badges) links=QHBoxLayout(); links.setSpacing(10) for lbl2,url in [("โญ GitHub","https://github.com/sonuramashishnpm"), - ("๐Ÿ PyPI","https://pypi.org/project/npmai"), - ("๐ŸŽค TEDx","https://youtu.be/qWTkKsegx9c"), - ("๐Ÿ“˜ Facebook","https://facebook.com/share/19dvhX1mH7/")]: + ("๐Ÿ PyPI","https://pypi.org/project/npmai")]: b2=QPushButton(lbl2); b2.setCursor(Qt.PointingHandCursor); b2.setFixedHeight(32) b2.setStyleSheet(f"QPushButton{{background:rgba(167,139,250,10);border:1px solid rgba(167,139,250,40);border-radius:9px;color:{P['mid']};font-size:11px;padding:0 12px;}}QPushButton:hover{{background:rgba(167,139,250,25);border-color:rgba(167,139,250,110);color:{P['bright']};}}") b2.clicked.connect(lambda _,u=url: webbrowser.open(u)); links.addWidget(b2) links.addStretch(); hl.addLayout(links); lay.addWidget(hero) - stats_card=GlowCard(accent=P["cyan"],radius=18,alpha=195) - scl=QVBoxLayout(stats_card); scl.setContentsMargins(28,22,28,22); scl.setSpacing(14) - st=QLabel("๐Ÿ“Š Key Numbers"); st.setFont(QFont("Segoe UI",13,QFont.Bold)) - st.setStyleSheet(f"color:{P['bright']};background:transparent;"); scl.addWidget(st) - sg=QHBoxLayout(); sg.setSpacing(12) - for num,lbl3,col in [("1.5M+","PyPI downloads",P["mint"]),("433K+","Social followers",P["cyan"]), - ("TEDx","Speaker at 13",P["violet"]),("100%","Allen scholarship",P["amber"]), - ("45+","LLMs in ecosystem",P["mint"]),("3","Research papers",P["rose"])]: - sc=GlowCard(accent=col,radius=12,alpha=175); scl2=QVBoxLayout(sc) - scl2.setContentsMargins(14,12,14,12); scl2.setSpacing(3) - sn=QLabel(num); sn.setFont(QFont("Segoe UI",17,QFont.Bold)) - sn.setStyleSheet(f"color:{col};background:transparent;"); sn.setAlignment(Qt.AlignCenter) - sl2=QLabel(lbl3); sl2.setFont(QFont("Segoe UI",9)); sl2.setWordWrap(True) - sl2.setStyleSheet(f"color:{P['dim']};background:transparent;"); sl2.setAlignment(Qt.AlignCenter) - scl2.addWidget(sn); scl2.addWidget(sl2); sg.addWidget(sc) - scl.addLayout(sg); lay.addWidget(stats_card) - tl_card=GlowCard(accent=P["mint"],radius=18,alpha=195) - tll=QVBoxLayout(tl_card); tll.setContentsMargins(28,22,28,22); tll.setSpacing(16) - tt=QLabel("๐Ÿ—บ๏ธ The Journey"); tt.setFont(QFont("Segoe UI",13,QFont.Bold)) - tt.setStyleSheet(f"color:{P['bright']};background:transparent;"); tll.addWidget(tt) - sep2=QFrame(); sep2.setFixedHeight(1); sep2.setStyleSheet(f"background:{P['ghost']};border:none;") - tll.addWidget(sep2) - - for age,col,title3,desc3 in [ - ("Age 9",P["mint"],"๐Ÿง’ Started Teaching Nursery Kids", - "While in 4th grade, Sonu began teaching nursery children โ€” planting the seed for education equity."), - ("Age 11",P["cyan"],"๐Ÿ”ฅ Bihar Viral Boy โ€” Met the Chief Minister", - "Challenged the CM on education & liquor ban. Went viral across India. Support from Cabinet Ministers, Bollywood actors & ALLEN founder."), - ("Age 13",P["violet"],"๐ŸŽค TEDx Speaker", - "One of India's youngest TEDx speakers โ€” rural education failures and using tech to bridge India's urban-rural divide."), - ("Age 13-14",P["amber"],"๐Ÿ’ป Founded NPMAI ECOSYSTEM โ€” 1.5M+ Downloads", - "Self-taught, no CS background. Built entire ecosystem on free cloud infrastructure."), - ("2026",P["rose"],"๐Ÿ“„ Published 3 Research Papers", - "LARA (RAG architecture) ยท RID (Representative Ideal Democracy) ยท IAST (Administrative System Theory)"), - ]: - row=QHBoxLayout(); row.setSpacing(16) - dot=QLabel("โ—‰"); dot.setFont(QFont("Segoe UI",13,QFont.Bold)) - dot.setStyleSheet(f"color:{col};background:transparent;"); dot.setFixedWidth(20); dot.setAlignment(Qt.AlignTop) - ev=QVBoxLayout(); ev.setSpacing(2) - al=QLabel(age); al.setFont(QFont("Cascadia Code",9,QFont.Bold)) - al.setStyleSheet(f"color:{col};background:transparent;") - tl2=QLabel(title3); tl2.setFont(QFont("Segoe UI",12,QFont.Bold)) - tl2.setStyleSheet(f"color:{P['bright']};background:transparent;") - dl=QLabel(desc3); dl.setFont(QFont("Segoe UI",11)); dl.setWordWrap(True) - dl.setStyleSheet(f"color:{P['mid']};background:transparent;") - ev.addWidget(al); ev.addWidget(tl2); ev.addWidget(dl) - row.addWidget(dot); row.addLayout(ev); tll.addLayout(row) - lay.addWidget(tl_card) - mc=GlowCard(accent=P["violet"],radius=18,alpha=195) - ml2=QVBoxLayout(mc); ml2.setContentsMargins(28,22,28,22); ml2.setSpacing(8) - mi=QLabel("๐Ÿงญ Mission"); mi.setFont(QFont("Segoe UI",13,QFont.Bold)) - mi.setStyleSheet(f"color:{P['violet']};background:transparent;"); ml2.addWidget(mi) - mq=QLabel('"Promoting Individual Journalism to every nation\'s village so that the democratic values of a nation can be strengthened and we can achieve Representative Ideal Democracy."') - mq.setFont(QFont("Segoe UI",12)); mq.setWordWrap(True) - mq.setStyleSheet(f"color:{P['mid']};font-style:italic;background:transparent;"); ml2.addWidget(mq) - ma=QLabel("โ€” Sonu Kumar, Founder, NPMAI ECOSYSTEM"); ma.setFont(QFont("Segoe UI",10)) - ma.setStyleSheet(f"color:{P['dim']};background:transparent;"); ml2.addWidget(ma) - lay.addWidget(mc); lay.addStretch(); scroll.setWidget(page); outer.addWidget(scroll) + lay.addStretch(); scroll.setWidget(page); outer.addWidget(scroll) + class DocsPage(QWidget): def __init__(self,parent=None): @@ -1014,176 +965,42 @@ def _build(self): lay.addWidget(sep); lay.addSpacing(4) self._sec(lay,"๐Ÿš€","Getting Started",P["mint"],[ - ("Download","Get NPM_AutoCode_AI from GitHub releases. Unzip anywhere. No installer."), - ("Launch","Double-click the .exe. If Windows shows SmartScreen, click 'Run Anyway'."), + ("Install","pip install npmai_agents, then run this app."), ("First scan","Go to Settings and click 'Scan Now' โ€” agent learns your folder structure."), - ("Add credentials","Add Gmail/GitHub/Slack tokens in Settings โ†’ Credentials. Encrypted locally."), - ("Type a task","In the Agent tab, describe any task in plain English and press Run."), + ("Add credentials","Add GitHub/Notion/Stripe/AWS/SMTP creds in Settings. Encrypted locally."), + ("Type a task","In the Agent tab, describe any task in plain English and press Run. No login needed."), ]) - self._sec(lay,"๐Ÿค–","How the Agent Works",P["cyan"],[ - ("Detect intent","Agent first decides: is this a conversation or a task to execute?"), - ("Plan","Planner LLM breaks the task into 2-5 atomic steps. Each step is independent."), - ("Generate","CodeLlama generates Python code for each step, using the 21 tool templates."), - ("Audit","Qwen2.5-Coder reviews generated code for 5 categories of high-risk behavior."), - ("Execute","Code runs as a real child process (not exec()). Stdout streams to log panel."), - ("Verify","A verifier LLM checks if the step actually completed, not just if Python exited 0."), - ("Retry","If a step fails, agent auto-debugs up to 12 times. Step context is preserved."), + self._sec(lay,"๐Ÿค–","How the Agent Works (Standalone)",P["cyan"],[ + ("Detect intent","Local heuristic decides: conversation, or a task to execute?"), + ("Plan","Planner LLM breaks the task into atomic steps."), + ("Select tools","Tool Manager picks from the real 100-tool registry."), + ("Generate","Coder LLM writes Python for each step."), + ("Audit","Auditor LLM reviews the code before anything runs."), + ("Execute","Executor runs it as a real child process โ€” streams to the log panel."), + ("Verify","Verifier LLM confirms the step actually completed."), + ("Retry","On failure, agent auto-debugs up to 12 times with prior error context."), ]) - self._sec(lay,"๐Ÿ”Œ","MCP Server",P["violet"],[ - ("Start","Click 'Start MCP Server' in the sidebar. Default port: 7799."), - ("Connect","Add to Claude Desktop MCP config: {\"type\":\"tcp\",\"host\":\"localhost\",\"port\":7799}"), - ("Tools exposed","All 21 tools + full agent pipeline available as MCP tools."), - ("Use from Claude","Type in Claude Desktop: 'use npm_agent to send an email to...' โ€” it works."), - ("Power other projects","Any MCP-compatible LLM or app can call your tools via this server."), - ]) - self._sec(lay,"โœ๏ธ","Writing Good Prompts",P["amber"],[ - ("Be specific","Say 'rename all .jpg files in C:/Users/Me/Desktop/Photos' not just 'rename photos'."), - ("Mention output","Say 'save as results.xlsx on Desktop' โ€” specify file name, format, location."), - ("One task at a time","Complex workflows run better as separate tasks. Chain them manually."), - ("Use examples","Click any chip below the input for a ready-made example task."), + self._sec(lay,"๐Ÿ”—","MCP Link (opt-in, login required)",P["violet"],[ + ("Get a link","Sidebar โ†’ 'Get MCP Link' โ†’ log in (or sign up) โ†’ link issued for your account."), + ("Connect","Paste the link into Claude/Grok's custom connector settings."), + ("What runs here","Only the execution step โ€” the LLM you connected does the planning/coding, " + "already audited before it reaches this app."), + ("Everything else stays local","Chat, tasks, and credentials never touch this โ€” login is scoped " + "to this one feature only."), ]) self._sec(lay,"๐Ÿ”’","Security",P["rose"],[ - ("Dual-LLM audit","Every script reviewed by qwen2.5-coder:7b (fallback: falcon:7b) before execution."), + ("Dual-role audit","Every script is reviewed by a separate Auditor role before execution."), ("Subprocess isolation","Code runs as a child process โ€” a broken script cannot crash the app."), ("Kill button","Click โ–  Kill at any time to terminate the running script immediately."), ("Credential encryption","Fernet symmetric encryption, machine-specific key, local only. Never uploaded."), ]) lay.addStretch(); scroll.setWidget(page); outer.addWidget(scroll) -def run_mcp_server(port:int, running_ref): - """ - Minimal TCP-based MCP server. - Receives JSON: {"tool": "...", "params": {...}} - Returns JSON: {"success": bool, "output": "...", "data": ...} - """ - import json as _json - brain = AgentBrain() - - TOOL_MAP = { - "npmai_chat": lambda p: _chat(brain, p), - "npmai_run_task": lambda p: _run_task(brain, p), - "email_send": lambda p: EmailTool.send(**p), - "email_bulk": lambda p: EmailTool.send_bulk(**p), - "email_inbox": lambda p: EmailTool.read_inbox(**p), - "file_rename": lambda p: FileTool.bulk_rename(**p), - "file_zip": lambda p: FileTool.zip_folder(**p), - "file_find": lambda p: FileTool.find_files(**p), - "file_organize": lambda p: FileTool.organize_by_type(**p), - "file_read": lambda p: FileTool.read_file(**p), - "file_write": lambda p: FileTool.write_file(**p), - "web_scrape": lambda p: WebTool.scrape(**p), - "web_download": lambda p: WebTool.download_file(**p), - "web_screenshot": lambda p: WebTool.screenshot_url(**p), - "web_api": lambda p: WebTool.api_call(**p), - "pdf_extract": lambda p: PDFTool.extract_text(**p), - "pdf_merge": lambda p: PDFTool.merge(**p), - "pdf_split": lambda p: PDFTool.split(**p), - "github_issue": lambda p: GitHubTool.create_issue(**p), - "github_push": lambda p: GitHubTool.push_file(**p), - "github_issues_list":lambda p: GitHubTool.list_issues(**p), - "github_clone": lambda p: GitHubTool.clone_repo(**p), - "github_push_commit":lambda p: GitHubTool.git_commit_push(**p), - "slack_send": lambda p: SlackTool.send_message(**p), - "slack_read": lambda p: SlackTool.read_channel(**p), - "slack_upload": lambda p: SlackTool.upload_file(**p), - "discord_webhook": lambda p: DiscordTool.send_webhook(**p), - "whatsapp_send": lambda p: WhatsAppTool.send(**p), - "notion_page": lambda p: NotionTool.create_page(**p), - "notion_db": lambda p: NotionTool.add_db_entry(**p), - "twitter_tweet": lambda p: TwitterTool.tweet(**p), - "system_run": lambda p: SystemTool.run_command(**p), - "system_screenshot": lambda p: SystemTool.screenshot(**p), - "system_clipboard_get": lambda p: SystemTool.get_clipboard(), - "system_clipboard_set": lambda p: SystemTool.set_clipboard(**p), - "system_notify": lambda p: SystemTool.notify(**p), - "image_resize": lambda p: ImageTool.resize(**p), - "image_convert": lambda p: ImageTool.convert(**p), - "image_ocr": lambda p: ImageTool.ocr(**p), - "image_compress": lambda p: ImageTool.bulk_compress(**p), - "telegram_send": lambda p: TelegramTool.send(**p), - "qr_generate": lambda p: QRTool.generate(**p), - "voice_speak": lambda p: VoiceTool.speak(**p), - "voice_listen": lambda p: VoiceTool.listen(**p), - "rag_query": lambda p: RAGTool.query_document(**p), - "rag_summarize": lambda p: RAGTool.summarize_large_file(**p), - "ssh_run": lambda p: SSHTool.run(**p), - "ssh_upload": lambda p: SSHTool.upload(**p), - "cred_save": lambda p: _cred_save(p), - "cred_load": lambda p: _cred_load(p), - "workspace_scan": lambda p: _ws_scan(), - "list_tools": lambda p: _list_tools(), - } - - def _chat(br, p): - r=br.chat(p.get("message","")) - from agent_core import ToolResult - return ToolResult(True,r) - - def _run_task(br, p): - results=[] - br2=AgentBrain(log_cb=results.append) - ok=br2.run_task(p.get("task","")) - from agent_core import ToolResult - return ToolResult(ok,"\n".join(results)) - - def _cred_save(p): - CredStore.save(p["key"],p["data"]) - from agent_core import ToolResult - return ToolResult(True,"Saved") - - def _cred_load(p): - data=CredStore.load(p["key"]) - from agent_core import ToolResult - return ToolResult(True,"Loaded",data) - - def _ws_scan(): - ws=Workspace(); ws.scan() - from agent_core import ToolResult - return ToolResult(True,"Scanned",ws.data) - - def _list_tools(): - from agent_core import ToolResult - return ToolResult(True,"Tools listed",list(TOOL_MAP.keys())) - - def handle_client(conn): - try: - data=b"" - while True: - chunk=conn.recv(4096) - if not chunk: break - data+=chunk - if data.endswith(b"\n"): break - req=_json.loads(data.decode()) - tool=req.get("tool","") - params=req.get("params",{}) - if tool in TOOL_MAP: - result=TOOL_MAP[tool](params) - resp={"success":result.success,"output":result.output, - "data":result.data if hasattr(result,"data") else None} - else: - resp={"success":False,"output":f"Unknown tool: {tool}","data":None} - except Exception as e: - resp={"success":False,"output":f"MCP error: {e}","data":None} - try: - conn.sendall((_json.dumps(resp)+"\n").encode()) - except: pass - conn.close() - - srv=socket.socket(socket.AF_INET,socket.SOCK_STREAM) - srv.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) - srv.bind(("0.0.0.0",port)); srv.listen(10); srv.settimeout(1.0) - while getattr(running_ref,"val",True): - try: - conn,_=srv.accept() - threading.Thread(target=handle_client,args=(conn,),daemon=True).start() - except socket.timeout: continue - except: break - srv.close() class AppWindow(QWidget): def __init__(self): super().__init__() - self.setWindowTitle("NPM Agent โ€” NPMAI Ecosystem v3.0") + self.setWindowTitle("NPM Agent โ€” NPMAI Ecosystem v4.0") self.resize(1220,780); self.setMinimumSize(920,640) self._build() @@ -1196,12 +1013,12 @@ def _build(self): cl=QVBoxLayout(content); cl.setContentsMargins(0,0,0,0) self.stack=QStackedWidget(); self.stack.setAttribute(Qt.WA_TranslucentBackground) self.stack.setStyleSheet("background:transparent;") - self.stack.addWidget(AgentPage()) - self.stack.addWidget(HistoryPage()) - self.stack.addWidget(ToolsPage()) + self.stack.addWidget(AgentPage()) + self.stack.addWidget(HistoryPage()) + self.stack.addWidget(ToolsPage()) self.stack.addWidget(SettingsPage()) - self.stack.addWidget(FounderPage()) - self.stack.addWidget(DocsPage()) + self.stack.addWidget(FounderPage()) + self.stack.addWidget(DocsPage()) cl.addWidget(self.stack); root.addWidget(content) def resizeEvent(self,e): @@ -1210,7 +1027,6 @@ def resizeEvent(self,e): def _switch(self,idx): if idx==1: self.stack.widget(1).load() - widget=self.stack.widget(idx) eff=QGraphicsOpacityEffect(widget); widget.setGraphicsEffect(eff) anim=QPropertyAnimation(eff,b"opacity",self) @@ -1220,15 +1036,11 @@ def _switch(self,idx): self._anim=anim def closeEvent(self,e): - base=os.path.dirname(os.path.abspath(__file__)) - for fn in ["coder0-generator.json","safety_checker.json", - "agent_plan.json","agent_code.json","agent_chat.json","agent_tasks.json"]: - fp=os.path.join(base,fn) - if os.path.exists(fp): - try: os.remove(fp) - except: pass + if hasattr(self.sidebar, "_link_mgr") and self.sidebar._link_mgr: + self.sidebar._link_mgr.stop_listener() super().closeEvent(e) + if __name__=="__main__": app=QApplication(sys.argv); app.setStyle("Fusion") pal=QPalette() From efe2834c33b863e1747a1a9425ce96889ff7939d Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 12 Jul 2026 19:45:16 +0530 Subject: [PATCH 38/68] Delete Desktop_App/NPM_Agent(Under Development) --- Desktop_App/NPM_Agent(Under Development) | 1369 ---------------------- 1 file changed, 1369 deletions(-) delete mode 100644 Desktop_App/NPM_Agent(Under Development) diff --git a/Desktop_App/NPM_Agent(Under Development) b/Desktop_App/NPM_Agent(Under Development) deleted file mode 100644 index 940f1da..0000000 --- a/Desktop_App/NPM_Agent(Under Development) +++ /dev/null @@ -1,1369 +0,0 @@ -"""Still Under Development""" -import os, sys, json, re, shutil, subprocess, tempfile, traceback -import threading, time, smtplib, imaplib, email as email_lib -import hashlib, base64, platform, glob, zipfile, tarfile -from pathlib import Path -from datetime import datetime -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart -from email.mime.base import MIMEBase -from email import encoders -from typing import Callable, Optional - - -def _ensure(pkg, import_name=None): - n = import_name or pkg - try: - __import__(n) - except ImportError: - subprocess.run([sys.executable,"-m","pip","install",pkg,"-q"],check=False) - -class ensure: - def __init__(): - for _p,_i in [ - ("npmai", "npmai"), - ("requests", "requests"), - ("beautifulsoup4","bs4"), - ("playwright", "playwright"), - ("PyGithub", "github"), - ("slack-sdk", "slack_sdk"), - ("gspread", "gspread"), - ("google-auth", "google.auth"), - ("openpyxl", "openpyxl"), - ("pandas", "pandas"), - ("Pillow", "PIL"), - ("pypdf", "pypdf"), - ("python-docx", "docx"), - ("pyttsx3", "pyttsx3"), - ("SpeechRecognition","speech_recognition"), - ("pyperclip", "pyperclip"), - ("schedule", "schedule"), - ("psutil", "psutil"), - ("watchdog", "watchdog"), - ("tweepy", "tweepy"), - ("pywhatkit", "pywhatkit"), - ("qrcode", "qrcode"), - ("cryptography", "cryptography"), - ("paramiko", "paramiko"), - ("python-dotenv","dotenv"), - ("pyautogui", "pyautogui"), - ("opencv-python","cv2"), - ("pytesseract", "pytesseract"), - ("youtube-dl", "youtube_dl"), - ("yt-dlp", "yt_dlp"), - ("discord.py", "discord"), - ("telethon", "telethon"), - ("notion-client","notion_client"), - ("todoist-api-python","todoist_api_python"), - ("jira", "jira"), - ("trello", "trello"), - ("pywin32", "win32api") if platform.system()=="Windows" else ("",""), - ]: - if _p: _ensure(_p, _i) - return "Done all requirements are ensured." - -from npmai import Ollama, Memory, Rag -from langchain_core.output_parsers import StrOutputParser - -class CredStore(ensure): - """Encrypts credentials with a machine-specific key and stores locally.""" - _PATH = Path.home() / ".npmai_agent" / "creds.json" - - @staticmethod - def _key() -> bytes: - from cryptography.fernet import Fernet - kf = Path.home() / ".npmai_agent" / ".key" - kf.parent.mkdir(exist_ok=True) - if kf.exists(): - return kf.read_bytes() - k = Fernet.generate_key() - kf.write_bytes(k) - kf.chmod(0o600) - return k - - @classmethod - def save(cls, name: str, data: dict): - from cryptography.fernet import Fernet - f = Fernet(cls._key()) - store = {} - if cls._PATH.exists(): - try: store = json.loads(f.decrypt(cls._PATH.read_bytes())) - except: store = {} - store[name] = data - cls._PATH.write_bytes(f.encrypt(json.dumps(store).encode())) - - @classmethod - def load(cls, name: str) -> dict: - from cryptography.fernet import Fernet - if not cls._PATH.exists(): return {} - try: - f = Fernet(cls._key()) - store = json.loads(f.decrypt(cls._PATH.read_bytes())) - return store.get(name, {}) - except: return {} - - @classmethod - def all_keys(cls) -> list: - from cryptography.fernet import Fernet - if not cls._PATH.exists(): return [] - try: - f = Fernet(cls._key()) - return list(json.loads(f.decrypt(cls._PATH.read_bytes())).keys()) - except: return [] - -class Workspace(ensure): - """Scans the file system and builds a context profile for the agent.""" - _PATH = Path.home() / ".npmai_agent" / "workspace.json" - - def __init__(self): - self._PATH.parent.mkdir(exist_ok=True) - self.data = self._load() - - def _load(self) -> dict: - if self._PATH.exists(): - try: return json.loads(self._PATH.read_text()) - except: pass - return {} - - def scan(self): - home = Path.home() - scan_dirs = { - "desktop": home/"Desktop", - "downloads": home/"Downloads", - "documents": home/"Documents", - "pictures": home/"Pictures", - "videos": home/"Videos", - "music": home/"Music", - } - found = {} - for name, path in scan_dirs.items(): - if path.exists(): - files = [] - try: - for f in path.iterdir(): - if f.is_file(): - files.append({"name":f.name,"size":f.stat().st_size, - "modified":datetime.fromtimestamp(f.stat().st_mtime).isoformat()}) - except: pass - found[name] = {"path":str(path),"files":files[:40]} - self.data["paths"] = found - self.data["home"] = str(home) - self.data["os"] = platform.system() - self.data["python"] = sys.version - self.data["scanned"] = datetime.now().isoformat() - self._save() - return self.data - - def update_profile(self, key, value): - self.data[key] = value - self._save() - - def _save(self): - self._PATH.write_text(json.dumps(self.data, indent=2)) - - def context_summary(self) -> str: - """Returns a short text summary passed to the planner LLM.""" - lines = [f"OS: {self.data.get('os','unknown')}", f"Home: {self.data.get('home','~')}"] - for k,v in self.data.get("paths",{}).items(): - lines.append(f"{k.capitalize()}: {v.get('path','')} ({len(v.get('files',[]))} files)") - if "user_name" in self.data: - lines.append(f"User: {self.data['user_name']}") - return "\n".join(lines) - -class ToolResult(ensure): - def __init__(self, success:bool, output:str, data=None): - self.success = success - self.output = output - self.data = data - def __str__(self): return self.output - -class EmailTool(ensure): - name = "email" - description = "Send emails via Gmail/SMTP, read inbox via IMAP, bulk email from CSV" - - @staticmethod - def send(to:str, subject:str, body:str, attachments:list=None, - cred_key:str="gmail") -> ToolResult: - creds = CredStore.load(cred_key) - user = creds.get("email","") - pwd = creds.get("password","") - host = creds.get("smtp_host","smtp.gmail.com") - port = int(creds.get("smtp_port",587)) - if not user or not pwd: - return ToolResult(False,"No email credentials. Add them in Settings โ†’ Credentials.") - try: - msg = MIMEMultipart() - msg["From"] = user; msg["To"] = to; msg["Subject"] = subject - msg.attach(MIMEText(body,"html")) - if attachments: - for fp in attachments: - with open(fp,"rb") as fh: - part = MIMEBase("application","octet-stream") - part.set_payload(fh.read()) - encoders.encode_base64(part) - part.add_header("Content-Disposition",f"attachment; filename={Path(fp).name}") - msg.attach(part) - with smtplib.SMTP(host,port) as s: - s.starttls(); s.login(user,pwd); s.sendmail(user,to,msg.as_string()) - return ToolResult(True,f"โœ“ Email sent to {to}") - except Exception as e: - return ToolResult(False,f"โœ— Email failed: {e}") - - @staticmethod - def send_bulk(csv_path:str, subject:str, body_template:str, - name_col:str="name", email_col:str="email", - cred_key:str="gmail") -> ToolResult: - import pandas as pd - try: - df = pd.read_csv(csv_path) - sent,failed = 0,0 - for _,row in df.iterrows(): - body = body_template.replace("{name}", str(row.get(name_col,""))) - for col in df.columns: - body = body.replace(f"{{{col}}}", str(row.get(col,""))) - r = EmailTool.send(str(row[email_col]),subject,body,cred_key=cred_key) - if r.success: sent+=1 - else: failed+=1 - time.sleep(0.5) - return ToolResult(True,f"โœ“ Sent {sent} emails, {failed} failed") - except Exception as e: - return ToolResult(False,f"โœ— Bulk email failed: {e}") - - @staticmethod - def read_inbox(count:int=10, cred_key:str="gmail") -> ToolResult: - creds = CredStore.load(cred_key) - user = creds.get("email",""); pwd = creds.get("password","") - host = creds.get("imap_host","imap.gmail.com") - if not user: return ToolResult(False,"No credentials.") - try: - mail = imaplib.IMAP4_SSL(host) - mail.login(user,pwd); mail.select("inbox") - _,ids = mail.search(None,"ALL") - msgs = [] - for mid in ids[0].split()[-count:]: - _,data = mail.fetch(mid,"(RFC822)") - msg = email_lib.message_from_bytes(data[0][1]) - msgs.append({"from":msg["From"],"subject":msg["Subject"],"date":msg["Date"]}) - mail.logout() - return ToolResult(True,f"โœ“ Fetched {len(msgs)} emails",msgs) - except Exception as e: - return ToolResult(False,f"โœ— IMAP failed: {e}") - -class FileTool(ensure): - name = "files" - description = "Rename, move, copy, zip, unzip, search, organize, convert files" - - @staticmethod - def bulk_rename(folder:str, pattern:str="*", prefix:str="", - suffix:str="", add_date:bool=False) -> ToolResult: - p = Path(folder); count = 0 - today = datetime.now().strftime("%Y-%m-%d") - try: - for f in p.glob(pattern): - if not f.is_file(): continue - stem = f.stem; ext = f.suffix - new_stem = f"{prefix}{today+'_' if add_date else ''}{stem}{suffix}" - f.rename(p / f"{new_stem}{ext}") - count += 1 - return ToolResult(True,f"โœ“ Renamed {count} files") - except Exception as e: - return ToolResult(False,f"โœ— Rename failed: {e}") - - @staticmethod - def zip_folder(source:str, dest:str=None) -> ToolResult: - sp = Path(source) - dp = Path(dest) if dest else sp.parent / f"{sp.name}.zip" - try: - with zipfile.ZipFile(dp,"w",zipfile.ZIP_DEFLATED) as zf: - if sp.is_dir(): - for f in sp.rglob("*"): - if f.is_file(): zf.write(f, f.relative_to(sp.parent)) - else: - zf.write(sp, sp.name) - return ToolResult(True,f"โœ“ Zipped to {dp}") - except Exception as e: - return ToolResult(False,f"โœ— Zip failed: {e}") - - @staticmethod - def unzip(zip_path:str, dest:str=None) -> ToolResult: - zp = Path(zip_path) - dp = Path(dest) if dest else zp.parent / zp.stem - try: - with zipfile.ZipFile(zp,"r") as zf: zf.extractall(dp) - return ToolResult(True,f"โœ“ Extracted to {dp}") - except Exception as e: - return ToolResult(False,f"โœ— Unzip failed: {e}") - - @staticmethod - def find_files(folder:str, pattern:str, recursive:bool=True) -> ToolResult: - p = Path(folder) - fn = p.rglob if recursive else p.glob - files = [str(f) for f in fn(pattern) if f.is_file()] - return ToolResult(True,f"โœ“ Found {len(files)} files", files) - - @staticmethod - def organize_by_type(folder:str) -> ToolResult: - type_map = { - "Images": [".jpg",".jpeg",".png",".gif",".bmp",".webp",".svg",".ico"], - "Videos": [".mp4",".avi",".mkv",".mov",".wmv",".flv"], - "Audio": [".mp3",".wav",".flac",".aac",".ogg"], - "Docs": [".pdf",".doc",".docx",".txt",".odt"], - "Sheets": [".xls",".xlsx",".csv",".ods"], - "Code": [".py",".js",".ts",".html",".css",".java",".cpp",".c"], - "Archives":[".zip",".tar",".gz",".rar",".7z"], - } - p = Path(folder); moved = 0 - try: - for f in p.iterdir(): - if not f.is_file(): continue - for cat,exts in type_map.items(): - if f.suffix.lower() in exts: - dest = p/cat; dest.mkdir(exist_ok=True) - shutil.move(str(f),str(dest/f.name)) - moved += 1; break - return ToolResult(True,f"โœ“ Organized {moved} files by type") - except Exception as e: - return ToolResult(False,f"โœ— Organize failed: {e}") - - @staticmethod - def read_file(path:str, max_chars:int=50000) -> ToolResult: - try: - content = Path(path).read_text(errors="replace") - return ToolResult(True,f"โœ“ Read {len(content)} chars", content[:max_chars]) - except Exception as e: - return ToolResult(False,f"โœ— Read failed: {e}") - - @staticmethod - def write_file(path:str, content:str) -> ToolResult: - try: - Path(path).parent.mkdir(parents=True,exist_ok=True) - Path(path).write_text(content) - return ToolResult(True,f"โœ“ Written to {path}") - except Exception as e: - return ToolResult(False,f"โœ— Write failed: {e}") - - @staticmethod - def duplicate_tree(src:str, dst:str) -> ToolResult: - try: - shutil.copytree(src,dst) - return ToolResult(True,f"โœ“ Copied {src} โ†’ {dst}") - except Exception as e: - return ToolResult(False,f"โœ— Copy tree failed: {e}") - -class PDFTool(ensure): - name = "pdf" - description = "Merge, split, extract text, add watermark, rotate PDF pages" - - @staticmethod - def extract_text(path:str) -> ToolResult: - try: - from pypdf import PdfReader - r = PdfReader(path) - text = "\n".join(p.extract_text() or "" for p in r.pages) - return ToolResult(True,f"โœ“ Extracted {len(text)} chars", text) - except Exception as e: - return ToolResult(False,f"โœ— PDF extract failed: {e}") - - @staticmethod - def merge(paths:list, out:str) -> ToolResult: - try: - from pypdf import PdfMerger - m = PdfMerger() - for p in paths: m.append(p) - m.write(out); m.close() - return ToolResult(True,f"โœ“ Merged {len(paths)} PDFs โ†’ {out}") - except Exception as e: - return ToolResult(False,f"โœ— Merge failed: {e}") - - @staticmethod - def split(path:str, out_dir:str) -> ToolResult: - try: - from pypdf import PdfReader, PdfWriter - r = PdfReader(path); Path(out_dir).mkdir(exist_ok=True) - for i,page in enumerate(r.pages): - w = PdfWriter(); w.add_page(page) - out = str(Path(out_dir)/f"page_{i+1}.pdf") - with open(out,"wb") as fh: w.write(fh) - return ToolResult(True,f"โœ“ Split into {len(r.pages)} pages in {out_dir}") - except Exception as e: - return ToolResult(False,f"โœ— Split failed: {e}") - -class WebTool(ensure): - name = "web" - description = "Scrape websites, search Google, download files, check URLs, take screenshots" - - @staticmethod - def scrape(url:str, selector:str=None) -> ToolResult: - try: - import requests - from bs4 import BeautifulSoup - r = requests.get(url, headers={"User-Agent":"Mozilla/5.0"},timeout=15) - soup = BeautifulSoup(r.text,"html.parser") - if selector: - items = [el.get_text(strip=True) for el in soup.select(selector)] - return ToolResult(True,f"โœ“ Scraped {len(items)} items", items) - return ToolResult(True,"โœ“ Page fetched", soup.get_text(separator="\n",strip=True)[:20000]) - except Exception as e: - return ToolResult(False,f"โœ— Scrape failed: {e}") - - @staticmethod - def download_file(url:str, dest:str) -> ToolResult: - try: - import requests - r = requests.get(url,stream=True,timeout=30) - Path(dest).parent.mkdir(parents=True,exist_ok=True) - with open(dest,"wb") as fh: - for chunk in r.iter_content(8192): fh.write(chunk) - return ToolResult(True,f"โœ“ Downloaded to {dest}") - except Exception as e: - return ToolResult(False,f"โœ— Download failed: {e}") - - @staticmethod - def screenshot_url(url:str, out:str="screenshot.png") -> ToolResult: - try: - from playwright.sync_api import sync_playwright - with sync_playwright() as pw: - b = pw.chromium.launch(headless=True) - pg = b.new_page(); pg.goto(url,timeout=20000) - pg.screenshot(path=out,full_page=True); b.close() - return ToolResult(True,f"โœ“ Screenshot saved to {out}") - except Exception as e: - return ToolResult(False,f"โœ— Screenshot failed: {e}") - - @staticmethod - def browser_action(url:str, actions:list) -> ToolResult: - """actions: list of dicts like {type:'click',selector:'...'} or {type:'fill',selector:'...',value:'...'}""" - try: - from playwright.sync_api import sync_playwright - results = [] - with sync_playwright() as pw: - b = pw.chromium.launch(headless=False) - pg = b.new_page(); pg.goto(url,timeout=20000) - for act in actions: - t = act.get("type","") - sel = act.get("selector","") - if t=="click": - pg.click(sel); results.append(f"Clicked {sel}") - elif t=="fill": - pg.fill(sel,act.get("value","")); results.append(f"Filled {sel}") - elif t=="wait": - pg.wait_for_timeout(int(act.get("ms",1000))) - elif t=="screenshot": - pg.screenshot(path=act.get("path","action_shot.png")) - elif t=="extract": - val = pg.inner_text(sel); results.append(val) - b.close() - return ToolResult(True,"โœ“ Browser actions completed", results) - except Exception as e: - return ToolResult(False,f"โœ— Browser action failed: {e}") - - @staticmethod - def api_call(url:str, method:str="GET", headers:dict=None, - payload:dict=None) -> ToolResult: - try: - import requests - fn = getattr(requests, method.lower()) - r = fn(url, headers=headers or {}, json=payload, timeout=20) - return ToolResult(True,f"โœ“ {method} {url} โ†’ {r.status_code}", r.json() if r.content else {}) - except Exception as e: - return ToolResult(False,f"โœ— API call failed: {e}") - -class SpreadsheetTool(ensure): - name = "spreadsheet" - description = "Read/write Excel, CSV, Google Sheets; generate charts, pivot tables" - - @staticmethod - def read_csv(path:str) -> ToolResult: - try: - import pandas as pd - df = pd.read_csv(path) - return ToolResult(True,f"โœ“ {len(df)} rows ร— {len(df.columns)} cols",df) - except Exception as e: - return ToolResult(False,f"โœ— CSV read failed: {e}") - - @staticmethod - def write_excel(data, path:str, sheet:str="Sheet1") -> ToolResult: - try: - import pandas as pd - df = data if hasattr(data,"to_excel") else pd.DataFrame(data) - df.to_excel(path,index=False,sheet_name=sheet) - return ToolResult(True,f"โœ“ Written to {path}") - except Exception as e: - return ToolResult(False,f"โœ— Excel write failed: {e}") - - @staticmethod - def google_sheets_read(sheet_id:str, range_:str="Sheet1", - cred_key:str="google") -> ToolResult: - try: - import gspread - from google.oauth2.service_account import Credentials - creds_data = CredStore.load(cred_key) - if not creds_data: - return ToolResult(False,"No Google credentials saved.") - scopes = ["https://spreadsheets.google.com/feeds", - "https://www.googleapis.com/auth/drive"] - creds = Credentials.from_service_account_info(creds_data,scopes=scopes) - gc = gspread.authorize(creds) - sh = gc.open_by_key(sheet_id) - ws = sh.worksheet(range_) - data = ws.get_all_records() - return ToolResult(True,f"โœ“ {len(data)} rows from Google Sheets", data) - except Exception as e: - return ToolResult(False,f"โœ— Sheets read failed: {e}") - -class GitHubTool(ensure): - name = "github" - description = "Create issues, push files, read repos, manage PRs, search code" - - @staticmethod - def _gh(cred_key="github"): - from github import Github - t = CredStore.load(cred_key).get("token","") - if not t: raise ValueError("No GitHub token. Add it in Settings โ†’ Credentials.") - return Github(t) - - @staticmethod - def create_issue(repo:str, title:str, body:str="", - labels:list=None, cred_key:str="github") -> ToolResult: - try: - gh = GitHubTool._gh(cred_key) - r = gh.get_repo(repo) - issue = r.create_issue(title=title, body=body, - labels=labels or []) - return ToolResult(True,f"โœ“ Issue #{issue.number} created: {issue.html_url}") - except Exception as e: - return ToolResult(False,f"โœ— GitHub issue failed: {e}") - - @staticmethod - def push_file(repo:str, path:str, content:str, - message:str="Update via NPM Agent", - cred_key:str="github") -> ToolResult: - try: - gh = GitHubTool._gh(cred_key) - r = gh.get_repo(repo) - try: - existing = r.get_contents(path) - r.update_file(path, message, content, existing.sha) - return ToolResult(True,f"โœ“ Updated {path} in {repo}") - except: - r.create_file(path, message, content) - return ToolResult(True,f"โœ“ Created {path} in {repo}") - except Exception as e: - return ToolResult(False,f"โœ— Push failed: {e}") - - @staticmethod - def list_issues(repo:str, state:str="open", - cred_key:str="github") -> ToolResult: - try: - gh = GitHubTool._gh(cred_key) - issues = gh.get_repo(repo).get_issues(state=state) - data = [{"#":i.number,"title":i.title,"state":i.state,"url":i.html_url} - for i in issues] - return ToolResult(True,f"โœ“ {len(data)} issues",data) - except Exception as e: - return ToolResult(False,f"โœ— List issues failed: {e}") - - @staticmethod - def get_readme(repo:str, cred_key:str="github") -> ToolResult: - try: - gh = GitHubTool._gh(cred_key) - r = gh.get_repo(repo) - readme = r.get_readme() - return ToolResult(True,"โœ“ README fetched", - base64.b64decode(readme.content).decode()) - except Exception as e: - return ToolResult(False,f"โœ— README failed: {e}") - - @staticmethod - def clone_repo(url:str, dest:str) -> ToolResult: - try: - subprocess.run(["git","clone",url,dest],check=True,capture_output=True) - return ToolResult(True,f"โœ“ Cloned to {dest}") - except Exception as e: - return ToolResult(False,f"โœ— Clone failed: {e}") - - @staticmethod - def git_commit_push(repo_path:str, message:str) -> ToolResult: - try: - for cmd in [["git","add","."], - ["git","commit","-m",message], - ["git","push"]]: - subprocess.run(cmd,cwd=repo_path,check=True,capture_output=True) - return ToolResult(True,f"โœ“ Committed and pushed: {message}") - except Exception as e: - return ToolResult(False,f"โœ— Git push failed: {e}") - -class SlackTool(ensure): - name = "slack" - description = "Send messages, read channels, post to threads, upload files" - - @staticmethod - def _client(cred_key="slack"): - from slack_sdk import WebClient - t = CredStore.load(cred_key).get("bot_token","") - if not t: raise ValueError("No Slack bot token. Add in Settings โ†’ Credentials.") - return WebClient(token=t) - - @staticmethod - def send_message(channel:str, text:str, - cred_key:str="slack") -> ToolResult: - try: - c = SlackTool._client(cred_key) - r = c.chat_postMessage(channel=channel,text=text) - return ToolResult(True,f"โœ“ Sent to #{channel}") - except Exception as e: - return ToolResult(False,f"โœ— Slack send failed: {e}") - - @staticmethod - def read_channel(channel:str, limit:int=20, - cred_key:str="slack") -> ToolResult: - try: - c = SlackTool._client(cred_key) - r = c.conversations_history(channel=channel,limit=limit) - msgs = [{"user":m.get("user","?"),"text":m.get("text",""), - "ts":m.get("ts","")} for m in r["messages"]] - return ToolResult(True,f"โœ“ {len(msgs)} messages",msgs) - except Exception as e: - return ToolResult(False,f"โœ— Slack read failed: {e}") - - @staticmethod - def upload_file(channel:str, file_path:str, - comment:str="", cred_key:str="slack") -> ToolResult: - try: - c = SlackTool._client(cred_key) - c.files_upload(channels=channel,file=file_path, - initial_comment=comment) - return ToolResult(True,f"โœ“ File uploaded to #{channel}") - except Exception as e: - return ToolResult(False,f"โœ— Upload failed: {e}") - -class DiscordTool(ensure): - name = "discord" - description = "Send messages to Discord channels via webhook or bot" - - @staticmethod - def send_webhook(webhook_url:str, content:str, - embeds:list=None) -> ToolResult: - try: - import requests - payload = {"content":content} - if embeds: payload["embeds"] = embeds - r = requests.post(webhook_url,json=payload,timeout=10) - return ToolResult(r.status_code in (200,204), - f"โœ“ Discord sent" if r.status_code in (200,204) - else f"โœ— Discord failed: {r.status_code}") - except Exception as e: - return ToolResult(False,f"โœ— Discord webhook failed: {e}") - -class WhatsAppTool(ensure): - name = "whatsapp" - description = "Send WhatsApp messages (requires WhatsApp Web open)" - - @staticmethod - def send(phone:str, message:str, wait:int=15) -> ToolResult: - try: - import pywhatkit - pywhatkit.sendwhatmsg_instantly(phone,message,wait_time=wait,tab_close=True) - return ToolResult(True,f"โœ“ WhatsApp sent to {phone}") - except Exception as e: - return ToolResult(False,f"โœ— WhatsApp failed: {e}") - -class NotionTool(ensure): - name = "notion" - description = "Create/read Notion pages and database entries" - - @staticmethod - def create_page(parent_id:str, title:str, content:str, - cred_key:str="notion") -> ToolResult: - try: - from notion_client import Client - token = CredStore.load(cred_key).get("token","") - if not token: return ToolResult(False,"No Notion token.") - n = Client(auth=token) - page = n.pages.create( - parent={"page_id":parent_id}, - properties={"title":{"title":[{"text":{"content":title}}]}}, - children=[{"object":"block","type":"paragraph", - "paragraph":{"rich_text":[{"text":{"content":content}}]}}] - ) - return ToolResult(True,f"โœ“ Notion page created: {page['url']}") - except Exception as e: - return ToolResult(False,f"โœ— Notion failed: {e}") - - @staticmethod - def add_db_entry(db_id:str, props:dict, - cred_key:str="notion") -> ToolResult: - try: - from notion_client import Client - token = CredStore.load(cred_key).get("token","") - n = Client(auth=token) - n.pages.create(parent={"database_id":db_id},properties=props) - return ToolResult(True,"โœ“ Notion DB entry added") - except Exception as e: - return ToolResult(False,f"โœ— Notion DB failed: {e}") - -class TwitterTool(ensure): - name = "twitter" - description = "Post tweets, read timeline, search tweets" - - @staticmethod - def tweet(text:str, cred_key:str="twitter") -> ToolResult: - try: - import tweepy - c = CredStore.load(cred_key) - client = tweepy.Client( - consumer_key=c.get("api_key",""), - consumer_secret=c.get("api_secret",""), - access_token=c.get("access_token",""), - access_token_secret=c.get("access_token_secret","") - ) - r = client.create_tweet(text=text) - return ToolResult(True,f"โœ“ Tweeted: {r.data['id']}") - except Exception as e: - return ToolResult(False,f"โœ— Tweet failed: {e}") - -class SystemTool(ensure): - name = "system" - description = "Run shell commands, manage processes, clipboard, screenshots, notifications" - - @staticmethod - def run_command(cmd:str, cwd:str=None, timeout:int=60) -> ToolResult: - try: - r = subprocess.run(cmd,shell=True,cwd=cwd,capture_output=True, - text=True,timeout=timeout) - out = r.stdout+r.stderr - return ToolResult(r.returncode==0, out.strip() or "โœ“ Done") - except subprocess.TimeoutExpired: - return ToolResult(False,"โœ— Command timed out") - except Exception as e: - return ToolResult(False,f"โœ— Command failed: {e}") - - @staticmethod - def get_clipboard() -> ToolResult: - try: - import pyperclip - return ToolResult(True,"โœ“ Clipboard read", pyperclip.paste()) - except Exception as e: - return ToolResult(False,f"โœ— Clipboard failed: {e}") - - @staticmethod - def set_clipboard(text:str) -> ToolResult: - try: - import pyperclip; pyperclip.copy(text) - return ToolResult(True,"โœ“ Copied to clipboard") - except Exception as e: - return ToolResult(False,f"โœ— Clipboard set failed: {e}") - - @staticmethod - def screenshot(out:str="screenshot.png") -> ToolResult: - try: - import pyautogui - img = pyautogui.screenshot(); img.save(out) - return ToolResult(True,f"โœ“ Screenshot saved: {out}") - except Exception as e: - return ToolResult(False,f"โœ— Screenshot failed: {e}") - - @staticmethod - def get_processes() -> ToolResult: - try: - import psutil - procs = [{"pid":p.pid,"name":p.name(),"cpu":p.cpu_percent(), - "mem_mb":round(p.memory_info().rss/1024/1024,1)} - for p in psutil.process_iter(["pid","name","cpu_percent","memory_info"])] - return ToolResult(True,f"โœ“ {len(procs)} processes", procs) - except Exception as e: - return ToolResult(False,f"โœ— Process list failed: {e}") - - @staticmethod - def notify(title:str, message:str) -> ToolResult: - try: - os_name = platform.system() - if os_name=="Windows": - from win10toast import ToastNotifier - ToastNotifier().show_toast(title,message,duration=5) - elif os_name=="Darwin": - subprocess.run(["osascript","-e",f'display notification "{message}" with title "{title}"']) - else: - subprocess.run(["notify-send",title,message]) - return ToolResult(True,"โœ“ Notification sent") - except Exception as e: - return ToolResult(False,f"โœ— Notify failed: {e}") - -class ImageTool(ensure): - name = "image" - description = "Resize, convert, compress, watermark images; OCR text from images" - - @staticmethod - def resize(path:str, width:int, height:int, out:str=None) -> ToolResult: - try: - from PIL import Image - img = Image.open(path) - img = img.resize((width,height), Image.LANCZOS) - dest = out or path - img.save(dest) - return ToolResult(True,f"โœ“ Resized to {width}ร—{height}: {dest}") - except Exception as e: - return ToolResult(False,f"โœ— Resize failed: {e}") - - @staticmethod - def convert(path:str, format:str, out:str=None) -> ToolResult: - try: - from PIL import Image - img = Image.open(path) - dest = out or str(Path(path).with_suffix(f".{format.lower()}")) - img.save(dest,format.upper()) - return ToolResult(True,f"โœ“ Converted to {dest}") - except Exception as e: - return ToolResult(False,f"โœ— Convert failed: {e}") - - @staticmethod - def ocr(path:str) -> ToolResult: - try: - import pytesseract - from PIL import Image - text = pytesseract.image_to_string(Image.open(path)) - return ToolResult(True,f"โœ“ OCR extracted {len(text)} chars", text) - except Exception as e: - return ToolResult(False,f"โœ— OCR failed: {e}") - - @staticmethod - def bulk_compress(folder:str, quality:int=75) -> ToolResult: - from PIL import Image - count=0 - for ext in ("*.jpg","*.jpeg","*.png"): - for fp in Path(folder).glob(ext): - try: - img = Image.open(fp) - img.save(fp,optimize=True,quality=quality) - count+=1 - except: pass - return ToolResult(True,f"โœ“ Compressed {count} images") - -class SchedulerTool(ensure): - name = "scheduler" - description = "Schedule tasks to run at specific times or intervals" - _jobs = {} - - @staticmethod - def schedule_task(task_id:str, cron_like:str, callback:Callable) -> ToolResult: - """cron_like: 'every 5 minutes' | 'every day at 09:00' | 'every monday at 08:00'""" - try: - import schedule - parts = cron_like.lower().split() - if "minutes" in parts: - n = int(parts[1]); schedule.every(n).minutes.do(callback) - elif "hours" in parts: - n = int(parts[1]); schedule.every(n).hours.do(callback) - elif "day" in parts and "at" in parts: - t = parts[parts.index("at")+1] - schedule.every().day.at(t).do(callback) - elif "monday" in parts: - t = parts[-1]; schedule.every().monday.at(t).do(callback) - SchedulerTool._jobs[task_id] = callback - - def _run(): - while task_id in SchedulerTool._jobs: - schedule.run_pending(); time.sleep(30) - threading.Thread(target=_run,daemon=True).start() - return ToolResult(True,f"โœ“ Scheduled task '{task_id}': {cron_like}") - except Exception as e: - return ToolResult(False,f"โœ— Schedule failed: {e}") - - @staticmethod - def cancel_task(task_id:str) -> ToolResult: - SchedulerTool._jobs.pop(task_id,None) - return ToolResult(True,f"โœ“ Cancelled '{task_id}'") - -class JiraTool(ensure): - name = "jira" - description = "Create/update Jira issues, list sprints, manage projects" - - @staticmethod - def create_issue(project:str, summary:str, description:str="", - issue_type:str="Task", cred_key:str="jira") -> ToolResult: - try: - from jira import JIRA - c = CredStore.load(cred_key) - j = JIRA(server=c.get("server",""), - basic_auth=(c.get("email",""),c.get("api_token",""))) - issue = j.create_issue(project=project,summary=summary, - description=description,issuetype={"name":issue_type}) - return ToolResult(True,f"โœ“ Jira issue {issue.key} created") - except Exception as e: - return ToolResult(False,f"โœ— Jira failed: {e}") - -class TelegramTool(ensure): - name = "telegram" - description = "Send Telegram messages via bot token" - - @staticmethod - def send(chat_id:str, text:str, cred_key:str="telegram") -> ToolResult: - try: - import requests - token = CredStore.load(cred_key).get("bot_token","") - if not token: return ToolResult(False,"No Telegram token.") - url = f"https://api.telegram.org/bot{token}/sendMessage" - r = requests.post(url,json={"chat_id":chat_id,"text":text},timeout=10) - return ToolResult(r.ok,f"โœ“ Telegram sent" if r.ok else f"โœ— {r.text}") - except Exception as e: - return ToolResult(False,f"โœ— Telegram failed: {e}") - -class QRTool(ensure): - name = "qr" - description = "Generate QR codes from any text or URL" - - @staticmethod - def generate(data:str, out:str="qrcode.png", size:int=10) -> ToolResult: - try: - import qrcode - img = qrcode.make(data,box_size=size) - img.save(out) - return ToolResult(True,f"โœ“ QR code saved: {out}") - except Exception as e: - return ToolResult(False,f"โœ— QR failed: {e}") - -class VoiceTool(ensure): - name = "voice" - description = "Text-to-speech output and speech-to-text input" - - @staticmethod - def speak(text:str) -> ToolResult: - try: - import pyttsx3 - e = pyttsx3.init(); e.say(text); e.runAndWait() - return ToolResult(True,"โœ“ Spoken") - except Exception as e: - return ToolResult(False,f"โœ— TTS failed: {e}") - - @staticmethod - def listen(seconds:int=5) -> ToolResult: - try: - import speech_recognition as sr - r = sr.Recognizer() - with sr.Microphone() as src: - audio = r.listen(src,timeout=seconds) - text = r.recognize_google(audio) - return ToolResult(True,"โœ“ Heard speech", text) - except Exception as e: - return ToolResult(False,f"โœ— Listen failed: {e}") - -class WatcherTool(ensure): - name = "watcher" - description = "Watch folders for file changes and trigger actions" - - @staticmethod - def watch(folder:str, callback:Callable, patterns:list=None) -> ToolResult: - try: - from watchdog.observers import Observer - from watchdog.events import FileSystemEventHandler - class Handler(FileSystemEventHandler): - def on_created(self,event): - if not event.is_directory: callback(event.src_path) - def on_modified(self,event): - if not event.is_directory: callback(event.src_path) - obs = Observer() - obs.schedule(Handler(),folder,recursive=True) - obs.start() - return ToolResult(True,f"โœ“ Watching {folder}") - except Exception as e: - return ToolResult(False,f"โœ— Watch failed: {e}") - -class RAGTool(ensure): - name = "rag" - description = "Query large documents using RAG pipeline via npmai Rag class" - - @staticmethod - def query_document(doc_path:str, question:str, - chunk_size:int=500) -> ToolResult: - try: - rag = Rag() - text = Path(doc_path).read_text(errors="replace") - if doc_path.endswith(".pdf"): - from pypdf import PdfReader - text = "\n".join(p.extract_text() or "" for p in PdfReader(doc_path).pages) - rag.load_text(text, chunk_size=chunk_size) - answer = rag.query(question) - return ToolResult(True,"โœ“ RAG query answered", answer) - except Exception as e: - return ToolResult(False,f"โœ— RAG query failed: {e}") - - @staticmethod - def summarize_large_file(path:str, model:str="mistral:7b") -> ToolResult: - try: - if path.endswith(".pdf"): - from pypdf import PdfReader - text = "\n".join(p.extract_text() or "" for p in PdfReader(path).pages) - else: - text = Path(path).read_text(errors="replace") - chunks = [text[i:i+3000] for i in range(0,len(text),3000)] - llm = Ollama(model=model, temperature=0.2, - change=True, Models=["llama3.2:3b"]) - summaries = [] - for ch in chunks[:10]: - s = llm.invoke(f"Summarize this section briefly:\n{ch}") - summaries.append(s) - combined = "\n".join(summaries) - final = llm.invoke(f"Create a final comprehensive summary from these section summaries:\n{combined}") - return ToolResult(True,"โœ“ Document summarized", final) - except Exception as e: - return ToolResult(False,f"โœ— Summarize failed: {e}") - -class SSHTool(ensure): - name = "ssh" - description = "Run commands on remote servers via SSH, transfer files via SFTP" - - @staticmethod - def run(host:str, command:str, cred_key:str="ssh") -> ToolResult: - try: - import paramiko - c = CredStore.load(cred_key) - client = paramiko.SSHClient() - client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - client.connect(host,username=c.get("user",""), - password=c.get("password",""), - key_filename=c.get("key_path",None)) - _,stdout,stderr = client.exec_command(command) - out = stdout.read().decode(); err = stderr.read().decode() - client.close() - return ToolResult(True,out or "โœ“ Done", out+err) - except Exception as e: - return ToolResult(False,f"โœ— SSH failed: {e}") - - @staticmethod - def upload(host:str, local:str, remote:str, cred_key:str="ssh") -> ToolResult: - try: - import paramiko - c = CredStore.load(cred_key) - t = paramiko.Transport((host,22)) - t.connect(username=c.get("user",""),password=c.get("password","")) - sftp = paramiko.SFTPClient.from_transport(t) - sftp.put(local,remote); sftp.close(); t.close() - return ToolResult(True,f"โœ“ Uploaded {local} โ†’ {remote}") - except Exception as e: - return ToolResult(False,f"โœ— SFTP upload failed: {e}") - - -class Executor(ensure): - """Writes code to temp file, runs as child process, streams output.""" - - def __init__(self, log_cb:Callable=None, timeout:int=120): - self._log = log_cb or print - self._timeout = timeout - self._proc = None - self._killed = False - - def run(self, code:str) -> tuple: - """Returns (success:bool, full_output:str)""" - self._killed = False - tmp = tempfile.NamedTemporaryFile(suffix=".py", delete=False, - mode="w", encoding="utf-8") - tmp.write(code); tmp.close() - output_lines = [] - try: - self._proc = subprocess.Popen( - [sys.executable, tmp.name], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - text=True, bufsize=1 - ) - for line in self._proc.stdout: - line = line.rstrip() - output_lines.append(line) - self._log(f'{line}') - self._proc.wait(timeout=self._timeout) - success = (self._proc.returncode == 0) and not self._killed - return success, "\n".join(output_lines) - except subprocess.TimeoutExpired: - self.kill() - return False, "TIMEOUT: Script exceeded time limit." - except Exception as e: - return False, f"EXECUTOR ERROR: {e}" - finally: - try: os.unlink(tmp.name) - except: pass - self._proc = None - - def kill(self): - self._killed = True - if self._proc: - try: self._proc.kill() - except: pass - -class AgentBrain(ensure): - """ - Full pipeline: - Intent โ†’ Plan โ†’ [for each step: Generate โ†’ Audit โ†’ Execute โ†’ Verify] โ†’ Done - """ - PARSER = StrOutputParser() - - TOOLS = { - "email": EmailTool, - "files": FileTool, - "pdf": PDFTool, - "web": WebTool, - "spreadsheet": SpreadsheetTool, - "github": GitHubTool, - "slack": SlackTool, - "discord": DiscordTool, - "whatsapp": WhatsAppTool, - "notion": NotionTool, - "twitter": TwitterTool, - "system": SystemTool, - "image": ImageTool, - "scheduler": SchedulerTool, - "jira": JiraTool, - "telegram": TelegramTool, - "qr": QRTool, - "voice": VoiceTool, - "watcher": WatcherTool, - "rag": RAGTool, - "ssh": SSHTool, - } - - TOOLS_SUMMARY = "\n".join( - f"- {k}: {v.description}" for k,v in TOOLS.items() - ) - - def __init__(self, log_cb:Callable=None, progress_cb:Callable=None, - status_cb:Callable=None): - self._log = log_cb or print - self._progress = progress_cb or (lambda v: None) - self._status = status_cb or (lambda s: None) - self.workspace = Workspace() - self.executor = Executor(log_cb=log_cb) - self.planner = Ollama(model="llama3.2:3b", temperature=0.2, - change=True, Models=["mistral:7b"]) - self.coder = Ollama(model="codellama:7b-instruct", temperature=0.3, - change=True, Models=["deepseek-coder:6.7b"]) - self.auditor = Ollama(model="qwen2.5-coder:7b", temperature=0.1, - change=True, Models=["falcon:7b-instruct"]) - self.verifier = Ollama(model="llama3.2:3b", temperature=0.1, - change=True, Models=["mistral:7b"]) - self.chatter = Ollama(model="granite3.3:2b", temperature=0.7, - change=True, Models=["llama3.2:1b"]) - self.mem_plan = Memory("agent_plan") - self.mem_code = Memory("agent_code") - self.mem_chat = Memory("agent_chat") - self.mem_tasks = Memory("agent_tasks") - - def _emit(self, msg, color="#8E8AAE"): - self._log(f'{msg}') - - def _clean_code(self, raw:str) -> str: - s = raw.strip() - for tag in ("```python","```"): - if s.startswith(tag): s = s[len(tag):] - if s.endswith("```"): s = s[:-3] - return s.strip() - - def chat(self, user_msg:str) -> str: - self.mem_chat.save_context("user", user_msg) - hist = self.mem_chat.load_memory_variables() - prompt = f"""You are NPM Agent, an advanced AI assistant powered by NPMAI Ecosystem. -You help with coding, automation, analysis, and general questions. -Be concise, helpful, and friendly. If the user wants to run something on their computer, -tell them to phrase it as a task (e.g. "do X on my computer"). - -Conversation history: -{hist} - -User: {user_msg} -Assistant:""" - resp = self.chatter.invoke(prompt) - self.mem_chat.save_context("assistant", resp) - return resp - - def plan(self, task:str) -> list: - ws = self.workspace.context_summary() - tool_list = TOOLS_SUMMARY - prompt = f"""You are a task planner. Break the following user task into 2-5 clear, -atomic executable steps. Each step should be independently verifiable. - -User's computer context: -{ws} - -Available tools (Python modules): -{tool_list} - -User Task: {task} - -Return ONLY a JSON array of step strings, no explanation: -["step 1 description", "step 2 description", ...]""" - raw = self.planner.invoke(prompt) - try: - match = re.search(r'\[.*?\]', raw, re.DOTALL) - if match: - steps = json.loads(match.group()) - return [str(s) for s in steps] - except: pass - return [task] - - def generate_code(self, step:str, task:str, prev_globals:str="", - error:str="") -> str: - ws = self.workspace.context_summary() - fix = f"\nPrevious error to fix:\n{error}" if error else "" - reuse = f"\nPreviously executed globals available:\n{prev_globals}" if prev_globals else "" - prompt = f"""You are an expert Python code generator for a desktop automation agent. -Write ONLY executable Python code โ€” NO explanations outside # comments. -Install any missing libraries via subprocess pip install in the code. -Your entire response goes into a .py file run by Python subprocess. -Do NOT use exec() โ€” write complete standalone scripts. - -User's system: -{ws} - -Available tool templates you can import and use: -from agent_core import EmailTool, FileTool, WebTool, SpreadsheetTool -from agent_core import GitHubTool, SlackTool, PDFTool, ImageTool -from agent_core import SystemTool, TelegramTool, QRTool, RAGTool, SSHTool -from agent_core import CredStore, Workspace - -Full task context: {task} -This specific step to implement: {step} -{reuse}{fix} - -Write complete Python code:""" - raw = self.coder.invoke(prompt) - return self._clean_code(raw) - - def audit(self, code:str) -> tuple: - """Returns (is_safe:bool, reason:str)""" - prompt = f"""[SECURITY AUDITOR] Analyze this Python code. -HIGH RISK โ€” answer 'BLOCK': deleting system files unprompted, stealing credentials/passwords/cookies, -reverse shells, fork bombs, obfuscated destructive code, accessing /etc/passwd or Windows SAM. -SAFE โ€” answer 'ALLOW': file operations, web scraping, sending emails, pip installs, -reading user files, making API calls, browser automation for user tasks. - -CODE: -{code} - -Answer format: ALLOW or BLOCK followed by one line reason.""" - r = self.auditor.invoke(prompt).strip() - safe = r.upper().startswith("ALLOW") - return safe, r - - def verify(self, step:str, output:str, success:bool) -> tuple: - """Returns (verified:bool, feedback:str)""" - if not success: - return False, output - prompt = f"""Did this step complete successfully? -Step: {step} -Execution output: {output[:500]} - -Answer 'YES' if step completed, 'NO' if it clearly failed. One word only.""" - r = self.verifier.invoke(prompt).strip().upper() - return r.startswith("YES"), output - - def run_task(self, task:str, killed_flag:list=None) -> bool: - """Main entry point. killed_flag=[False] passed from UI kill button.""" - if killed_flag is None: killed_flag = [False] - - self._emit(f"โ–ธ Starting task: {task}", "#00E5FF") - self._progress(5) - - if len(task.split()) < 4 and "?" in task: - resp = self.chat(task) - self._emit(resp, "#F0EEFF") - self._progress(100) - return True - - self._emit("โ–ธ Scanning workspaceโ€ฆ", "#4E4B6A") - try: self.workspace.scan() - except: pass - self._progress(10) - - self._status("Planningโ€ฆ") - self._emit("โ–ธ Planning stepsโ€ฆ", "#A78BFA") - steps = self.plan(task) - self._emit(f"โ–ธ Plan: {len(steps)} step(s)", "#A78BFA") - for i,s in enumerate(steps): - self._emit(f" {i+1}. {s}", "#4E4B6A") - self._progress(20) - - total_steps = len(steps) - prev_globals = "" - - for step_idx, step in enumerate(steps): - if killed_flag[0]: - self._emit("โœ— Task cancelled by user.", "#FF6B9D"); return False - - step_num = step_idx + 1 - prog_base = 20 + int((step_idx / total_steps) * 70) - self._emit(f"\nโ–ธ Step {step_num}/{total_steps}: {step}", "#00E5FF") - self._status(f"Step {step_num}/{total_steps}โ€ฆ") - - tried = 0 - max_retries = 12 - last_error = "" - - while tried < max_retries: - if killed_flag[0]: - self._emit("โœ— Cancelled.", "#FF6B9D"); return False - - self._emit(f" โ†’ Generating code (attempt {tried+1})โ€ฆ", "#4E4B6A") - self._progress(prog_base + 2) - code = self.generate_code(step, task, prev_globals, last_error) - self.mem_code.save_context(step, code) - - self._emit(" โ†’ Security auditโ€ฆ", "#A78BFA") - safe, reason = self.audit(code) - if not safe: - self._emit(f" ๐Ÿšซ BLOCKED: {reason}", "#FF6B9D") - self._progress(100) - return False - - self._emit(" โœ“ Audit passed", "#2AFFA0") - self._progress(prog_base + 6) - - self._emit(" โ†’ Executingโ€ฆ", "#4E4B6A") - self._status("Executingโ€ฆ") - success, output = self.executor.run(code) - self._progress(prog_base + 10) - - verified, feedback = self.verify(step, output, success) - - if verified: - self._emit(f" โœ“ Step {step_num} complete", "#2AFFA0") - prev_globals += f"\n# After step {step_num}:\n{output[:300]}" - self.mem_plan.save_context(f"step_{step_num}", "DONE: "+output[:200]) - break - else: - tried += 1 - last_error = output - self._emit(f" โš  Retry {tried}/{max_retries}: {output[:120]}", "#FFB347") - self._progress(prog_base + 5 + tried) - - else: - self._emit(f" โœ— Step {step_num} failed after {max_retries} attempts.", "#FF6B9D") - self._status("Failed") - self._save_task_history(task, False) - return False - - self._progress(100) - self._status("Done โœ“") - self._emit("\nโœ“ โ”€โ”€โ”€ All steps completed successfully โ”€โ”€โ”€", "#2AFFA0") - self._save_task_history(task, True) - return True - - def _save_task_history(self, task:str, success:bool): - hist_path = Path.home()/".npmai_agent"/"history.json" - hist_path.parent.mkdir(exist_ok=True) - history = [] - if hist_path.exists(): - try: history = json.loads(hist_path.read_text()) - except: history = [] - history.insert(0,{ - "task": task, - "success":success, - "time": datetime.now().isoformat(), - }) - history = history[:50] - hist_path.write_text(json.dumps(history,indent=2)) - - @staticmethod - def load_task_history() -> list: - p = Path.home()/".npmai_agent"/"history.json" - if p.exists(): - try: return json.loads(p.read_text()) - except: pass - return [] -"""Still Under Development""" From d94173b4f62e4ccfc56f8ce2ccbc1c6d76297ad4 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 12 Jul 2026 19:45:41 +0530 Subject: [PATCH 39/68] Update NPM-AutoCode-AI(V3).py --- Desktop_App/NPM-AutoCode-AI(V3).py | 1 + 1 file changed, 1 insertion(+) diff --git a/Desktop_App/NPM-AutoCode-AI(V3).py b/Desktop_App/NPM-AutoCode-AI(V3).py index 1369767..04e60a1 100644 --- a/Desktop_App/NPM-AutoCode-AI(V3).py +++ b/Desktop_App/NPM-AutoCode-AI(V3).py @@ -1,6 +1,7 @@ """ NPMAI Agent Desktop โ€” v4 (npmai_agents edition) +THIS IS NOT YET DEPLOYED. Here it is about Desktop APP. """ import sys, os, math, random, threading From 2606cc4f6ad79d4ae3bdddcdbd0858448bfaa84c Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 12 Jul 2026 19:47:27 +0530 Subject: [PATCH 40/68] Update requirements.txt --- Desktop_App/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Desktop_App/requirements.txt b/Desktop_App/requirements.txt index 3c8a822..3920f8e 100644 --- a/Desktop_App/requirements.txt +++ b/Desktop_App/requirements.txt @@ -3,3 +3,4 @@ langchain langchain-core langchain-community npmai +npmai-agents From 7d9c7fb825b5efee0468c9378f87010ed4a6dda8 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 12 Jul 2026 19:47:39 +0530 Subject: [PATCH 41/68] Update requirements.txt --- Desktop_App/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_App/requirements.txt b/Desktop_App/requirements.txt index 3920f8e..19b8a78 100644 --- a/Desktop_App/requirements.txt +++ b/Desktop_App/requirements.txt @@ -3,4 +3,4 @@ langchain langchain-core langchain-community npmai -npmai-agents +npmai-agents==1.0.2 From e6d880357fba2e92583e58b5718150030520cbfe Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 12 Jul 2026 19:47:55 +0530 Subject: [PATCH 42/68] Update requirements.txt --- Desktop_App/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_App/requirements.txt b/Desktop_App/requirements.txt index 19b8a78..10307ee 100644 --- a/Desktop_App/requirements.txt +++ b/Desktop_App/requirements.txt @@ -2,5 +2,5 @@ PySide6 langchain langchain-core langchain-community -npmai +npmai==0.1.9 npmai-agents==1.0.2 From f25841a550b3babfbdbfb9d4c7f42ee136490742 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Mon, 20 Jul 2026 00:06:16 +0530 Subject: [PATCH 43/68] Create mcp_link.py --- Desktop_App/mcp_link.py | 180 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 Desktop_App/mcp_link.py diff --git a/Desktop_App/mcp_link.py b/Desktop_App/mcp_link.py new file mode 100644 index 0000000..1ed0b18 --- /dev/null +++ b/Desktop_App/mcp_link.py @@ -0,0 +1,180 @@ +import os +import json +import uuid +import threading +import time +from pathlib import Path +from typing import Callable, Optional + +CONFIG_PATH = Path.home() / ".npmai_agent" / "supabase_config.json" + + +def load_config() -> dict: + if CONFIG_PATH.exists(): + try: + return json.loads(CONFIG_PATH.read_text()) + except Exception: + pass + return { + "url": os.environ.get("SUPABASE_URL", ""), + "anon_key": os.environ.get("SUPABASE_ANON_KEY", ""), + "mcp_base_url": os.environ.get("NPMAI_MCP_BASE_URL", "https://YOUR-HF-SPACE.hf.space/mcp"), + } + + +def save_config(url: str, anon_key: str, mcp_base_url: str = None): + CONFIG_PATH.parent.mkdir(exist_ok=True) + cfg = load_config() + cfg["url"] = url + cfg["anon_key"] = anon_key + if mcp_base_url: + cfg["mcp_base_url"] = mcp_base_url + CONFIG_PATH.write_text(json.dumps(cfg)) + + +class SupabaseAuthError(Exception): + pass + + +class MCPLinkManager: + + JOBS_TABLE = "mcp_jobs" + RESULTS_TABLE = "mcp_job_results" + LINKS_TABLE = "mcp_links" + + def __init__(self, log_cb: Callable[[str], None] = None): + self._log = log_cb or print + self._client = None + self._session = None + self._listener_thread = None + self._stop = threading.Event() + from npmai_agents import Executor + self._executor = Executor(log_cb=self._log) + + def _get_client(self): + if self._client is not None: + return self._client + try: + from supabase import create_client + except ImportError: + raise SupabaseAuthError( + "The 'supabase' package isn't installed. Run: pip install supabase" + ) + cfg = load_config() + if not cfg.get("url") or not cfg.get("anon_key"): + raise SupabaseAuthError( + "Supabase isn't configured yet. Set SUPABASE_URL and SUPABASE_ANON_KEY " + "env vars, or call mcp_link.save_config(url, anon_key) once from Settings." + ) + self._client = create_client(cfg["url"], cfg["anon_key"]) + return self._client + + def sign_up(self, email: str, password: str): + client = self._get_client() + res = client.auth.sign_up({"email": email, "password": password}) + if res.user is None: + raise SupabaseAuthError("Sign-up failed โ€” check the email/password and try again.") + self._session = res.session + return res.user + + def log_in(self, email: str, password: str): + client = self._get_client() + res = client.auth.sign_in_with_password({"email": email, "password": password}) + if res.user is None: + raise SupabaseAuthError("Login failed โ€” check your credentials.") + self._session = res.session + return res.user + + def log_out(self): + self.stop_listener() + if self._client: + try: + self._client.auth.sign_out() + except Exception: + pass + self._session = None + + @property + def is_logged_in(self) -> bool: + return self._session is not None + + @property + def user_id(self) -> Optional[str]: + if self._session and getattr(self._session, "user", None): + return self._session.user.id + return None + + def get_or_create_link(self) -> str: + if not self.user_id: + raise SupabaseAuthError("Log in first โ€” an MCP link can only be issued to a logged-in account.") + client = self._get_client() + existing = ( + client.table(self.LINKS_TABLE) + .select("*") + .eq("user_id", self.user_id) + .eq("platform", "desktop") + .execute() + ) + if existing.data: + token = existing.data[0]["token"] + else: + token = uuid.uuid4().hex + client.table(self.LINKS_TABLE).insert({ + "user_id": self.user_id, + "platform": "desktop", + "token": token, + }).execute() + base = load_config().get("mcp_base_url", "https://YOUR-HF-SPACE.hf.space/mcp") + return f"{base}/{token}" + + def start_listener(self): + if self._listener_thread and self._listener_thread.is_alive(): + return + if not self.user_id: + raise SupabaseAuthError("Log in first โ€” the job bridge is scoped to a logged-in account.") + self._stop.clear() + self._listener_thread = threading.Thread(target=self._listen_loop, daemon=True) + self._listener_thread.start() + + def stop_listener(self): + self._stop.set() + + def _listen_loop(self): + client = self._get_client() + channel = client.channel(f"jobs-{self.user_id}") + + def _on_insert(payload): + row = payload.get("record") or payload.get("new") or {} + if row.get("user_id") != self.user_id: + return + self._handle_job(row) + + channel.on_postgres_changes( + event="INSERT", schema="public", table=self.JOBS_TABLE, + callback=_on_insert, + ) + channel.subscribe() + self._log("MCP link listener connected โ€” waiting for jobs from your connected LLM.") + try: + while not self._stop.is_set(): + time.sleep(0.5) + finally: + try: + channel.unsubscribe() + except Exception: + pass + self._log("MCP link listener stopped.") + + def _handle_job(self, row: dict): + job_id = row.get("id") + code = row.get("code", "") + self._log(f"Job {job_id} received โ€” executing locally.") + success, output = self._executor.run(code) + client = self._get_client() + client.table(self.RESULTS_TABLE).insert({ + "job_id": job_id, + "user_id": self.user_id, + "success": success, + "output": output, + }).execute() + self._log(f"Job {job_id} {'completed' if success else 'failed'} โ€” result sent back.") From cc9c935ea1b049110b6b3e7a7f941adcabe42061 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Mon, 20 Jul 2026 00:06:36 +0530 Subject: [PATCH 44/68] Rename NPM-AutoCode-AI(V3).py to NPM-AutoCode-AI(V4).py --- Desktop_App/{NPM-AutoCode-AI(V3).py => NPM-AutoCode-AI(V4).py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Desktop_App/{NPM-AutoCode-AI(V3).py => NPM-AutoCode-AI(V4).py} (100%) diff --git a/Desktop_App/NPM-AutoCode-AI(V3).py b/Desktop_App/NPM-AutoCode-AI(V4).py similarity index 100% rename from Desktop_App/NPM-AutoCode-AI(V3).py rename to Desktop_App/NPM-AutoCode-AI(V4).py From d3242989ed9bbdb7ab92d8639ab8e2886d5e2d9a Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Mon, 20 Jul 2026 01:21:45 +0530 Subject: [PATCH 45/68] Update NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 1606 ++++++++++----------------- 1 file changed, 578 insertions(+), 1028 deletions(-) diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html index a9c10d5..7e73fc7 100644 --- a/Docs/templates/NPM_AutoCode_AI.html +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -3,7 +3,7 @@ -NPM Agent โ€” NPMAI Ecosystem +NPM-AutoCode-AI โ€” v3 ยท NPMAI Ecosystem @@ -29,7 +29,7 @@ ::-webkit-scrollbar-track{background:var(--void);} ::-webkit-scrollbar-thumb{background:var(--mint);border-radius:2px;} -/* โ”€โ”€ CUSTOM CURSOR โ”€โ”€ */ +/* โ”€โ”€ CUSTOM CURSOR (unchanged) โ”€โ”€ */ #cursor{ position:fixed;width:12px;height:12px;border-radius:50%; background:var(--mint);pointer-events:none;z-index:9999; @@ -44,35 +44,35 @@ transition:transform 0.12s cubic-bezier(0.23,1,0.32,1),width 0.4s,height 0.4s,opacity 0.3s; } body:hover #cursor{opacity:1;} +@media(hover:none){ #cursor,#cursor-ring{display:none;} body{cursor:auto;} } -/* โ”€โ”€ THREE.JS CANVAS โ”€โ”€ */ +/* โ”€โ”€ THREE.JS CANVAS (unchanged) โ”€โ”€ */ #three-canvas{ position:fixed;top:0;left:0;width:100%;height:100%; z-index:0;pointer-events:none; } -/* โ”€โ”€ NOISE OVERLAY โ”€โ”€ */ +/* โ”€โ”€ NOISE OVERLAY (unchanged) โ”€โ”€ */ body::after{ content:'';position:fixed;inset:0;z-index:1;pointer-events:none; background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E"); opacity:0.03; } -/* โ”€โ”€ LAYOUT โ”€โ”€ */ .wrap{position:relative;z-index:2;} /* โ”€โ”€ NAV โ”€โ”€ */ nav{ position:fixed;top:0;left:0;right:0;z-index:100; display:flex;align-items:center;justify-content:space-between; - padding:20px 6vw; + padding:18px 6vw; background:rgba(3,2,10,0.6); backdrop-filter:blur(24px); border-bottom:1px solid rgba(255,255,255,0.04); } .nav-logo{ - font-family:var(--font-mono);font-size:14px;font-weight:700; - color:var(--mint);letter-spacing:0.08em; + font-family:var(--font-mono);font-size:13px;font-weight:700; + color:var(--mint);letter-spacing:0.04em; display:flex;align-items:center;gap:10px; } .nav-logo-dot{ @@ -84,10 +84,10 @@ 0%,100%{box-shadow:0 0 8px var(--mint);} 50%{box-shadow:0 0 20px var(--mint),0 0 40px rgba(42,255,160,0.4);} } -.nav-links{display:flex;gap:32px;} +.nav-links{display:flex;gap:30px;} .nav-links a{ - font-size:13px;color:var(--dim);text-decoration:none; - font-family:var(--font-mono);letter-spacing:0.06em; + font-size:12px;color:var(--dim);text-decoration:none; + font-family:var(--font-mono);letter-spacing:0.05em; transition:color 0.3s;position:relative; } .nav-links a::after{ @@ -98,8 +98,8 @@ .nav-links a:hover::after{width:100%;} .nav-cta{ background:transparent;border:1px solid rgba(42,255,160,0.4); - color:var(--mint);padding:10px 22px;border-radius:8px; - font-family:var(--font-mono);font-size:12px;letter-spacing:0.06em; + color:var(--mint);padding:9px 20px;border-radius:8px; + font-family:var(--font-mono);font-size:11px;letter-spacing:0.05em; cursor:none;text-decoration:none; transition:all 0.3s; box-shadow:0 0 20px rgba(42,255,160,0.1); @@ -109,35 +109,30 @@ box-shadow:0 0 30px rgba(42,255,160,0.25); } -/* โ”€โ”€ HERO โ”€โ”€ */ +/* โ”€โ”€ HERO โ€” asymmetric, version tag as the eyebrow signature โ”€โ”€ */ #hero{ - min-height:100vh;display:flex;flex-direction:column; - align-items:center;justify-content:center; - padding:120px 6vw 80px;text-align:center; + min-height:100vh;display:grid;grid-template-columns:1.3fr 0.9fr;gap:40px; + align-items:center; + padding:150px 6vw 80px; position:relative;overflow:hidden; } -.hero-eyebrow{ - display:inline-flex;align-items:center;gap:8px; - font-family:var(--font-mono);font-size:11px; - letter-spacing:0.18em;text-transform:uppercase;color:var(--mint); - margin-bottom:32px;opacity:0; - animation:rise 0.9s cubic-bezier(0.16,1,0.3,1) 0.3s forwards; -} -.hero-eyebrow::before, -.hero-eyebrow::after{ - content:'';display:inline-block;height:1px; - background:linear-gradient(90deg,transparent,var(--mint)); - width:30px; +.hero-version-tag{ + display:inline-flex;align-items:baseline;gap:10px; + font-family:var(--font-mono); + margin-bottom:26px; + opacity:0;animation:rise 0.9s cubic-bezier(0.16,1,0.3,1) 0.3s forwards; } -.hero-eyebrow::after{transform:scaleX(-1);} +.hero-version-tag .v-big{font-size:13px;color:var(--void);background:var(--mint); + padding:4px 10px;border-radius:5px;font-weight:700;letter-spacing:0.05em;} +.hero-version-tag .v-txt{font-size:11px;color:var(--mid);letter-spacing:0.14em;text-transform:uppercase;} .hero-title{ - font-size:clamp(3.2rem,8vw,6.5rem); - font-weight:700;line-height:1.0;letter-spacing:-0.04em; - margin-bottom:28px; + font-size:clamp(2.6rem,5.6vw,4.6rem); + font-weight:700;line-height:1.02;letter-spacing:-0.03em; + margin-bottom:24px; opacity:0;animation:rise 0.9s cubic-bezier(0.16,1,0.3,1) 0.5s forwards; } -.hero-title .word-agent{ +.hero-title .grad{ display:inline-block; background:linear-gradient(135deg,var(--mint) 0%,var(--cyan) 50%,var(--violet) 100%); -webkit-background-clip:text;-webkit-text-fill-color:transparent; @@ -145,604 +140,303 @@ filter:drop-shadow(0 0 30px rgba(42,255,160,0.3)); } .hero-sub{ - font-size:clamp(1rem,2vw,1.2rem);color:var(--mid); - max-width:580px;margin:0 auto 48px;line-height:1.8; + font-size:clamp(0.98rem,1.6vw,1.12rem);color:var(--mid); + max-width:520px;margin-bottom:36px;line-height:1.8; opacity:0;animation:rise 0.9s cubic-bezier(0.16,1,0.3,1) 0.7s forwards; } .hero-actions{ - display:flex;gap:14px;justify-content:center;flex-wrap:wrap; - margin-bottom:80px; + display:flex;gap:14px;flex-wrap:wrap;margin-bottom:52px; opacity:0;animation:rise 0.9s cubic-bezier(0.16,1,0.3,1) 0.9s forwards; } .btn-primary{ display:inline-flex;align-items:center;gap:10px; background:var(--mint);color:#040210; font-weight:700;font-size:14px; - padding:15px 32px;border-radius:10px; + padding:15px 30px;border-radius:10px; text-decoration:none;cursor:none; transition:all 0.3s; box-shadow:0 0 40px rgba(42,255,160,0.35),inset 0 1px 0 rgba(255,255,255,0.2); font-family:var(--font-display); } -.btn-primary:hover{ - transform:translateY(-3px) scale(1.03); - box-shadow:0 0 60px rgba(42,255,160,0.5); -} +.btn-primary:hover{transform:translateY(-3px) scale(1.03);box-shadow:0 0 60px rgba(42,255,160,0.5);} .btn-ghost{ display:inline-flex;align-items:center;gap:10px; background:transparent;color:var(--bright); - font-size:14px;padding:15px 32px;border-radius:10px; + font-size:14px;padding:15px 28px;border-radius:10px; border:1px solid rgba(255,255,255,0.12); text-decoration:none;cursor:none; transition:all 0.3s;font-family:var(--font-display); } -.btn-ghost:hover{ - background:rgba(255,255,255,0.06); - border-color:rgba(255,255,255,0.25); - transform:translateY(-3px); -} +.btn-ghost:hover{background:rgba(255,255,255,0.06);border-color:rgba(255,255,255,0.25);transform:translateY(-3px);} -/* โ”€โ”€ STATS STRIP โ”€โ”€ */ .stats-strip{ - display:flex;gap:0; + display:grid;grid-template-columns:repeat(4,1fr);gap:0; border:1px solid rgba(255,255,255,0.08); - border-radius:16px;overflow:hidden; - max-width:700px;width:100%;margin:0 auto; - background:rgba(255,255,255,0.03); - backdrop-filter:blur(20px); + border-radius:16px;overflow:hidden;max-width:560px; + background:rgba(255,255,255,0.03);backdrop-filter:blur(20px); opacity:0;animation:rise 0.9s cubic-bezier(0.16,1,0.3,1) 1.1s forwards; } -.stat{ - flex:1;padding:24px 16px;text-align:center; - border-right:1px solid rgba(255,255,255,0.06); - transition:background 0.3s;cursor:default; -} +.stat{padding:20px 12px;text-align:center;border-right:1px solid rgba(255,255,255,0.06);transition:background 0.3s;cursor:default;} .stat:last-child{border-right:none;} .stat:hover{background:rgba(255,255,255,0.04);} -.stat-num{ - font-family:var(--font-mono);font-size:1.6rem;font-weight:700; - color:var(--mint);margin-bottom:5px;letter-spacing:-0.02em; -} -.stat-lbl{font-size:11px;color:var(--dim);letter-spacing:0.06em;text-transform:uppercase;} +.stat-num{font-family:var(--font-mono);font-size:1.4rem;font-weight:700;color:var(--mint);margin-bottom:4px;letter-spacing:-0.02em;} +.stat-lbl{font-size:10px;color:var(--dim);letter-spacing:0.05em;text-transform:uppercase;} -@keyframes rise{ - from{opacity:0;transform:translateY(32px);} - to{opacity:1;transform:translateY(0);} +/* hero right โ€” vertical stage rail, a real signature element */ +.hero-rail{ + opacity:0;animation:rise 0.9s cubic-bezier(0.16,1,0.3,1) 1.0s forwards; + border-left:1px solid rgba(255,255,255,0.08); + padding-left:28px;display:flex;flex-direction:column;gap:0; } +.hero-rail-label{font-family:var(--font-mono);font-size:10px;color:var(--dim); + letter-spacing:0.16em;text-transform:uppercase;margin-bottom:18px;} +.rail-node{display:flex;gap:14px;padding:11px 0;position:relative;} +.rail-node::before{ + content:'';position:absolute;left:-33px;top:16px;width:9px;height:9px; + border-radius:50%;background:var(--void);border:2px solid var(--mint); +} +.rail-node:not(:last-child)::after{ + content:'';position:absolute;left:-29px;top:26px;width:1px;height:calc(100% + 4px); + background:linear-gradient(to bottom,rgba(42,255,160,0.5),rgba(42,255,160,0.05)); +} +.rail-node b{font-size:12.5px;color:var(--bright);display:block;} +.rail-node span{font-size:11px;color:var(--dim);} + +@keyframes rise{from{opacity:0;transform:translateY(28px);}to{opacity:1;transform:translateY(0);}} -/* โ”€โ”€ SCROLL INDICATOR โ”€โ”€ */ .scroll-indicator{ - position:absolute;bottom:40px;left:50%;transform:translateX(-50%); - display:flex;flex-direction:column;align-items:center;gap:8px; + position:absolute;bottom:28px;left:6vw; + display:flex;align-items:center;gap:10px; color:var(--dim);font-family:var(--font-mono);font-size:10px; - letter-spacing:0.12em;text-transform:uppercase; - animation:float-y 3s ease-in-out infinite; -} -.scroll-line{ - width:1px;height:50px; - background:linear-gradient(to bottom,var(--mint),transparent); - animation:scroll-line 2s ease-in-out infinite; -} -@keyframes scroll-line{ - 0%{transform:scaleY(0);transform-origin:top;} - 50%{transform:scaleY(1);transform-origin:top;} - 51%{transform:scaleY(1);transform-origin:bottom;} - 100%{transform:scaleY(0);transform-origin:bottom;} -} -@keyframes float-y{ - 0%,100%{transform:translateX(-50%) translateY(0);} - 50%{transform:translateX(-50%) translateY(8px);} + letter-spacing:0.1em;text-transform:uppercase; } +.scroll-line{width:40px;height:1px;background:linear-gradient(to right,var(--mint),transparent);} -/* โ”€โ”€ SECTION BASE โ”€โ”€ */ -section{padding:120px 6vw;position:relative;} +section{padding:110px 6vw;position:relative;} .section-eyebrow{ - font-family:var(--font-mono);font-size:10px; - letter-spacing:0.18em;text-transform:uppercase; - color:var(--mint);margin-bottom:16px; - display:flex;align-items:center;gap:12px; -} -.section-eyebrow::after{ - content:'';flex:1;height:1px;max-width:60px; - background:linear-gradient(90deg,var(--mint),transparent); -} -h2.section-title{ - font-size:clamp(2.2rem,4.5vw,3.5rem);font-weight:700; - letter-spacing:-0.04em;margin-bottom:18px;line-height:1.05; -} -.section-desc{font-size:16px;color:var(--mid);max-width:520px;line-height:1.8;margin-bottom:72px;} - -/* โ”€โ”€ DIVIDER โ”€โ”€ */ -.divider{ - height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,0.06),transparent); - margin:0 6vw; + font-family:var(--font-mono);font-size:10px;letter-spacing:0.16em;text-transform:uppercase; + color:var(--mint);margin-bottom:16px;display:flex;align-items:center;gap:12px; } +.section-eyebrow::after{content:'';flex:1;height:1px;max-width:60px;background:linear-gradient(90deg,var(--mint),transparent);} +h2.section-title{font-size:clamp(2rem,4vw,3.2rem);font-weight:700;letter-spacing:-0.03em;margin-bottom:16px;line-height:1.08;} +.section-desc{font-size:15px;color:var(--mid);max-width:540px;line-height:1.8;margin-bottom:64px;} +.divider{height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,0.06),transparent);margin:0 6vw;} /* โ”€โ”€ APP DEMO WINDOW โ”€โ”€ */ -#demo{padding:120px 6vw;} .demo-center{display:flex;flex-direction:column;align-items:center;gap:0;} - .app-frame{ - width:100%;max-width:900px; + width:100%;max-width:920px; background:rgba(11,9,28,0.96); border:1px solid rgba(255,255,255,0.1); border-radius:20px;overflow:hidden; - box-shadow: - 0 80px 160px rgba(0,0,0,0.8), - 0 0 0 1px rgba(42,255,160,0.08), - 0 0 120px rgba(42,255,160,0.06); + box-shadow:0 80px 160px rgba(0,0,0,0.8),0 0 0 1px rgba(42,255,160,0.08),0 0 120px rgba(42,255,160,0.06); transform:perspective(1200px) rotateX(3deg); transition:transform 0.6s cubic-bezier(0.23,1,0.32,1),box-shadow 0.6s; animation:frame-float 6s ease-in-out infinite; } -@keyframes frame-float{ - 0%,100%{transform:perspective(1200px) rotateX(3deg) translateY(0);} - 50%{transform:perspective(1200px) rotateX(2deg) translateY(-12px);} -} -.app-frame:hover{ - transform:perspective(1200px) rotateX(0deg) translateY(-8px); - box-shadow:0 80px 160px rgba(0,0,0,0.7),0 0 80px rgba(42,255,160,0.12); - animation:none; -} - -/* titlebar */ -.app-tb{ - display:flex;align-items:center;gap:14px; - padding:14px 20px;background:rgba(7,5,18,0.98); - border-bottom:1px solid rgba(255,255,255,0.06); -} +@keyframes frame-float{0%,100%{transform:perspective(1200px) rotateX(3deg) translateY(0);}50%{transform:perspective(1200px) rotateX(2deg) translateY(-12px);}} +.app-frame:hover{transform:perspective(1200px) rotateX(0deg) translateY(-8px);box-shadow:0 80px 160px rgba(0,0,0,0.7),0 0 80px rgba(42,255,160,0.12);animation:none;} +.app-tb{display:flex;align-items:center;gap:14px;padding:14px 20px;background:rgba(7,5,18,0.98);border-bottom:1px solid rgba(255,255,255,0.06);} .tb-dots{display:flex;gap:7px;} .tb-dot{width:12px;height:12px;border-radius:50%;} .td-r{background:#FF5F56;} .td-y{background:#FFBD2E;} .td-g{background:#28C940;} -.tb-title{ - flex:1;text-align:center;font-family:var(--font-mono); - font-size:11px;color:var(--dim);letter-spacing:0.06em; -} -.tb-badge{ - font-family:var(--font-mono);font-size:9px;padding:3px 8px; - border-radius:4px;background:rgba(42,255,160,0.12); - color:var(--mint);border:1px solid rgba(42,255,160,0.25); - letter-spacing:0.08em; -} - -/* app body */ -.app-body{display:grid;grid-template-columns:220px 1fr;height:480px;} - -/* sidebar */ -.app-sidebar{ - background:rgba(6,4,16,0.9); - border-right:1px solid rgba(255,255,255,0.06); - padding:20px 0;display:flex;flex-direction:column; -} -.app-sidebar-logo{padding:0 18px 16px;font-family:var(--font-mono);font-size:12px;color:var(--mint);letter-spacing:0.04em;} +.tb-title{flex:1;text-align:center;font-family:var(--font-mono);font-size:11px;color:var(--dim);letter-spacing:0.05em;} +.tb-badge{font-family:var(--font-mono);font-size:9px;padding:3px 8px;border-radius:4px;background:rgba(42,255,160,0.12);color:var(--mint);border:1px solid rgba(42,255,160,0.25);letter-spacing:0.06em;} +.app-body{display:grid;grid-template-columns:210px 1fr;height:500px;} +.app-sidebar{background:rgba(6,4,16,0.9);border-right:1px solid rgba(255,255,255,0.06);padding:20px 0;display:flex;flex-direction:column;} +.app-sidebar-logo{padding:0 18px 16px;font-family:var(--font-mono);font-size:11px;color:var(--mint);letter-spacing:0.02em;} .app-sidebar-sep{height:1px;background:rgba(255,255,255,0.05);margin-bottom:10px;} -.app-sidebar-label{ - padding:10px 18px 6px;font-family:var(--font-mono);font-size:9px; - color:var(--dim);letter-spacing:0.14em;text-transform:uppercase; -} -.app-nav-item{ - display:flex;align-items:center;gap:10px; - padding:10px 18px;font-size:12px;color:var(--mid); - border-left:2px solid transparent;cursor:pointer; - transition:all 0.2s; -} -.app-nav-item.active{ - color:var(--mint); - border-left-color:var(--mint); - background:linear-gradient(90deg,rgba(42,255,160,0.1),transparent); -} +.app-sidebar-label{padding:10px 18px 6px;font-family:var(--font-mono);font-size:9px;color:var(--dim);letter-spacing:0.12em;text-transform:uppercase;} +.app-nav-item{display:flex;align-items:center;gap:10px;padding:10px 18px;font-size:12px;color:var(--mid);border-left:2px solid transparent;cursor:pointer;transition:all 0.2s;} +.app-nav-item.active{color:var(--mint);border-left-color:var(--mint);background:linear-gradient(90deg,rgba(42,255,160,0.1),transparent);} .app-nav-item:hover:not(.active){color:var(--bright);background:rgba(255,255,255,0.04);} .app-sidebar-spacer{flex:1;} -.app-mcp{ - margin:0 12px 12px;padding:10px 12px; - background:rgba(42,255,160,0.06); - border:1px solid rgba(42,255,160,0.2); - border-radius:8px; - font-family:var(--font-mono);font-size:9px;color:var(--mint); - display:flex;align-items:center;gap:8px; -} -.mcp-dot-live{ - width:6px;height:6px;border-radius:50%;background:var(--mint); - animation:pulse-dot 1.5s infinite;flex-shrink:0; -} - -/* main content */ +.app-mcp{margin:0 12px 12px;padding:10px 12px;background:rgba(42,255,160,0.06);border:1px solid rgba(42,255,160,0.2);border-radius:8px;font-family:var(--font-mono);font-size:9px;color:var(--mint);display:flex;align-items:center;gap:8px;} +.mcp-dot-live{width:6px;height:6px;border-radius:50%;background:var(--mint);animation:pulse-dot 1.5s infinite;flex-shrink:0;} .app-main{display:flex;flex-direction:column;overflow:hidden;} -.app-chat{flex:1;overflow:hidden;padding:20px 22px;display:flex;flex-direction:column;gap:14px;} - -/* chat bubbles */ -.bubble{ - display:flex;max-width:78%; - animation:bubble-in 0.3s cubic-bezier(0.34,1.56,0.64,1) forwards; -} +.app-chat{flex:1;overflow:hidden;padding:18px 22px;display:flex;flex-direction:column;gap:12px;} +.bubble{display:flex;max-width:80%;animation:bubble-in 0.3s cubic-bezier(0.34,1.56,0.64,1) forwards;} @keyframes bubble-in{from{opacity:0;transform:translateY(10px) scale(0.95);}to{opacity:1;transform:translateY(0) scale(1);}} .bubble.user{margin-left:auto;flex-direction:row-reverse;} -.bubble-text{ - padding:10px 15px;border-radius:14px; - font-size:12px;line-height:1.6; -} -.bubble.agent .bubble-text{ - background:rgba(17,16,40,0.9);border:1px solid rgba(42,255,160,0.2); - border-top-left-radius:3px;color:var(--bright); -} -.bubble.user .bubble-text{ - background:linear-gradient(135deg,rgba(42,255,160,0.2),rgba(0,229,255,0.15)); - border:1px solid rgba(42,255,160,0.3);border-top-right-radius:3px;color:var(--bright); -} +.bubble-text{padding:10px 15px;border-radius:14px;font-size:12px;line-height:1.6;} +.bubble.agent .bubble-text{background:rgba(17,16,40,0.9);border:1px solid rgba(42,255,160,0.2);border-top-left-radius:3px;color:var(--bright);} +.bubble.user .bubble-text{background:linear-gradient(135deg,rgba(42,255,160,0.2),rgba(0,229,255,0.15));border:1px solid rgba(42,255,160,0.3);border-top-right-radius:3px;color:var(--bright);} .bubble-thinking{display:flex;gap:5px;padding:12px 16px;align-items:center;} -.think-dot{ - width:6px;height:6px;border-radius:50%;background:var(--mint); - animation:think 1.2s ease-in-out infinite; -} -.think-dot:nth-child(2){animation-delay:0.2s;} -.think-dot:nth-child(3){animation-delay:0.4s;} +.think-dot{width:6px;height:6px;border-radius:50%;background:var(--mint);animation:think 1.2s ease-in-out infinite;} +.think-dot:nth-child(2){animation-delay:0.2s;} .think-dot:nth-child(3){animation-delay:0.4s;} @keyframes think{0%,100%{opacity:0.3;transform:scale(0.8);}50%{opacity:1;transform:scale(1.2);}} - -/* log panel */ -.app-log{ - height:110px;border-top:1px solid rgba(255,255,255,0.06); - padding:10px 18px;overflow:hidden; - background:rgba(3,2,10,0.5); -} -.log-header{ - font-family:var(--font-mono);font-size:9px;color:var(--dim); - text-transform:uppercase;letter-spacing:0.12em;margin-bottom:8px; - display:flex;align-items:center;gap:8px; -} -.log-header::before{ - content:'';width:6px;height:6px;border-radius:50%;background:var(--mint); - animation:pulse-dot 1.5s infinite; -} -.log-line{ - font-family:var(--font-mono);font-size:10px; - padding:2px 0;line-height:1.5; - opacity:0;animation:log-appear 0.4s ease forwards; -} +.app-log{height:104px;border-top:1px solid rgba(255,255,255,0.06);padding:10px 18px;overflow:hidden;background:rgba(3,2,10,0.5);} +.log-header{font-family:var(--font-mono);font-size:9px;color:var(--dim);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;display:flex;align-items:center;gap:8px;} +.log-header::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--mint);animation:pulse-dot 1.5s infinite;} +.log-line{font-family:var(--font-mono);font-size:10px;padding:2px 0;line-height:1.5;opacity:0;animation:log-appear 0.4s ease forwards;} @keyframes log-appear{from{opacity:0;transform:translateX(-8px);}to{opacity:1;transform:translateX(0);}} - -/* input bar */ -.app-input-bar{ - padding:14px 18px;border-top:1px solid rgba(255,255,255,0.06); - display:flex;gap:10px;align-items:center; - background:rgba(7,5,18,0.6); -} -.app-input-field{ - flex:1;background:rgba(255,255,255,0.05); - border:1px solid rgba(255,255,255,0.08);border-radius:10px; - padding:10px 14px;color:var(--bright);font-size:12px; - font-family:var(--font-display); -} -.app-run{ - background:var(--mint);color:#040210;border:none; - border-radius:8px;padding:10px 18px;font-size:12px; - font-weight:700;cursor:pointer;font-family:var(--font-display); - transition:all 0.2s; - box-shadow:0 0 20px rgba(42,255,160,0.3); -} +.app-input-bar{padding:14px 18px;border-top:1px solid rgba(255,255,255,0.06);display:flex;gap:10px;align-items:center;background:rgba(7,5,18,0.6);} +.app-input-field{flex:1;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);border-radius:10px;padding:10px 14px;color:var(--bright);font-size:12px;font-family:var(--font-display);} +.app-run{background:var(--mint);color:#040210;border:none;border-radius:8px;padding:10px 18px;font-size:12px;font-weight:700;cursor:pointer;font-family:var(--font-display);transition:all 0.2s;box-shadow:0 0 20px rgba(42,255,160,0.3);} .app-run:hover{transform:scale(1.05);} -.app-kill{ - background:rgba(255,107,157,0.1);color:var(--rose); - border:1px solid rgba(255,107,157,0.3);border-radius:8px; - padding:10px 14px;font-size:11px;cursor:pointer; - font-family:var(--font-mono); -} - -/* progress bar */ +.app-kill{background:rgba(255,107,157,0.1);color:var(--rose);border:1px solid rgba(255,107,157,0.3);border-radius:8px;padding:10px 14px;font-size:11px;cursor:pointer;font-family:var(--font-mono);} .app-prog-wrap{padding:6px 18px;background:rgba(7,5,18,0.4);} .app-prog-row{display:flex;justify-content:space-between;font-family:var(--font-mono);font-size:9px;color:var(--dim);margin-bottom:5px;} .app-prog-track{height:3px;background:rgba(255,255,255,0.06);border-radius:2px;overflow:hidden;} -.app-prog-fill{ - height:100%;border-radius:2px; - background:linear-gradient(90deg,var(--mint),var(--cyan),var(--violet)); - width:0%;transition:width 0.8s cubic-bezier(0.4,0,0.2,1); - position:relative;overflow:hidden; -} -.app-prog-fill::after{ - content:'';position:absolute;top:0;left:-100%;width:100%;height:100%; - background:linear-gradient(90deg,transparent,rgba(255,255,255,0.4),transparent); - animation:shimmer 2s infinite; -} +.app-prog-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--mint),var(--cyan),var(--violet));width:0%;transition:width 0.8s cubic-bezier(0.4,0,0.2,1);position:relative;overflow:hidden;} +.app-prog-fill::after{content:'';position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,0.4),transparent);animation:shimmer 2s infinite;} @keyframes shimmer{to{left:200%;}} -/* โ”€โ”€ FEATURES SECTION โ”€โ”€ */ -#features{background:rgba(0,0,0,0.15);} -.features-grid{ - display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:20px; -} -.feat-card{ - background:rgba(255,255,255,0.03); - border:1px solid rgba(255,255,255,0.07); - border-radius:20px;padding:32px 28px; - position:relative;overflow:hidden; - transition:all 0.4s cubic-bezier(0.23,1,0.32,1); - cursor:default; +/* demo chips โ€” now a scrollable multi-row bank ("a lot of options") */ +.chip-bank{ + display:flex;flex-wrap:wrap;gap:9px;margin-top:26px;justify-content:center;max-width:920px; } -.feat-card::before{ - content:'';position:absolute;top:0;left:0;right:0;height:1px; - background:var(--card-accent,linear-gradient(90deg,transparent,var(--mint),transparent)); - transform:scaleX(0);transition:transform 0.4s; -} -.feat-card::after{ - content:'';position:absolute;inset:0; - background:radial-gradient(circle at var(--mx,50%) var(--my,50%),rgba(42,255,160,0.05) 0%,transparent 60%); - opacity:0;transition:opacity 0.3s;pointer-events:none; -} -.feat-card:hover{ - transform:translateY(-10px); - border-color:rgba(42,255,160,0.2); - box-shadow:0 30px 60px rgba(0,0,0,0.4),0 0 0 1px rgba(42,255,160,0.08); +.tool-chip{ + display:inline-flex;align-items:center;gap:7px;padding:9px 16px;border-radius:30px;white-space:nowrap; + font-size:11.5px;font-weight:500;border:1px solid rgba(255,255,255,0.08); + background:rgba(255,255,255,0.04);color:var(--mid);transition:all 0.2s;flex-shrink:0;cursor:pointer; } +.tool-chip:hover{color:var(--bright);border-color:rgba(42,255,160,0.3);background:rgba(42,255,160,0.08);} + +/* โ”€โ”€ FEATURES โ”€โ”€ */ +#features{background:rgba(0,0,0,0.15);} +.features-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:20px;} +.feat-card{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07);border-radius:20px;padding:30px 26px;position:relative;overflow:hidden;transition:all 0.4s cubic-bezier(0.23,1,0.32,1);cursor:default;} +.feat-card::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:var(--card-accent,linear-gradient(90deg,transparent,var(--mint),transparent));transform:scaleX(0);transition:transform 0.4s;} +.feat-card::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at var(--mx,50%) var(--my,50%),rgba(42,255,160,0.05) 0%,transparent 60%);opacity:0;transition:opacity 0.3s;pointer-events:none;} +.feat-card:hover{transform:translateY(-10px);border-color:rgba(42,255,160,0.2);box-shadow:0 30px 60px rgba(0,0,0,0.4),0 0 0 1px rgba(42,255,160,0.08);} .feat-card:hover::before{transform:scaleX(1);} .feat-card:hover::after{opacity:1;} -.feat-icon{ - font-size:2.2rem;margin-bottom:18px;display:block; - transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1); -} +.feat-icon{font-size:2.1rem;margin-bottom:16px;display:block;transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1);} .feat-card:hover .feat-icon{transform:scale(1.15) rotate(-5deg);} -.feat-card h3{font-size:17px;font-weight:600;margin-bottom:10px;letter-spacing:-0.02em;} -.feat-card p{font-size:13px;color:var(--mid);line-height:1.75;} -.feat-tag{ - display:inline-block;margin-top:16px; - font-family:var(--font-mono);font-size:10px; - letter-spacing:0.08em;text-transform:uppercase; - padding:4px 10px;border-radius:5px; -} +.feat-card h3{font-size:16.5px;font-weight:600;margin-bottom:10px;letter-spacing:-0.02em;} +.feat-card p{font-size:12.5px;color:var(--mid);line-height:1.75;} +.feat-tag{display:inline-block;margin-top:14px;font-family:var(--font-mono);font-size:9.5px;letter-spacing:0.06em;text-transform:uppercase;padding:4px 10px;border-radius:5px;} -/* โ”€โ”€ TOOLS SECTION โ”€โ”€ */ -#tools{} -.tools-marquee-wrap{overflow:hidden;margin-bottom:20px;mask-image:linear-gradient(90deg,transparent,black 10%,black 90%,transparent);} -.tools-marquee{ - display:flex;gap:14px; - animation:marquee 28s linear infinite; - width:max-content; -} -.tools-marquee2{animation-direction:reverse;animation-duration:22s;} -@keyframes marquee{from{transform:translateX(0);}to{transform:translateX(-50%);} } -.tool-chip{ - display:inline-flex;align-items:center;gap:8px; - padding:10px 18px;border-radius:30px;white-space:nowrap; - font-size:12px;font-weight:500; - border:1px solid rgba(255,255,255,0.08); - background:rgba(255,255,255,0.04); - color:var(--mid);transition:all 0.2s;flex-shrink:0; -} -.tool-chip:hover{color:var(--bright);border-color:rgba(42,255,160,0.3);background:rgba(42,255,160,0.08);} +/* โ”€โ”€ TOOL REGISTRY marquee โ”€โ”€ */ +.tools-marquee-wrap{overflow:hidden;margin-bottom:14px;mask-image:linear-gradient(90deg,transparent,black 10%,black 90%,transparent);} +.tools-marquee{display:flex;gap:14px;animation:marquee 30s linear infinite;width:max-content;} +.tools-marquee2{animation-direction:reverse;animation-duration:24s;} +@keyframes marquee{from{transform:translateX(0);}to{transform:translateX(-50%);}} -/* โ”€โ”€ AGENT PIPELINE โ”€โ”€ */ +/* โ”€โ”€ PIPELINE โ”€โ”€ */ #pipeline{background:rgba(0,0,0,0.12);} -.pipeline{ - display:flex;align-items:stretch;gap:0; - max-width:1000px;margin:0 auto;flex-wrap:wrap; -} -.pipe-step{ - flex:1;min-width:140px; - background:rgba(255,255,255,0.03); - border:1px solid rgba(255,255,255,0.07); - border-radius:16px;padding:28px 20px; - text-align:center;position:relative; - transition:all 0.4s;cursor:default; - margin:0 8px; -} -.pipe-step:hover{ - background:rgba(255,255,255,0.06); - border-color:rgba(42,255,160,0.25); - transform:translateY(-8px); -} -.pipe-num{ - font-family:var(--font-mono);font-size:11px;color:var(--dim); - margin-bottom:12px;letter-spacing:0.1em; -} -.pipe-icon{font-size:2rem;display:block;margin-bottom:10px;} -.pipe-name{font-size:14px;font-weight:600;color:var(--bright);margin-bottom:6px;} -.pipe-desc{font-size:11px;color:var(--mid);line-height:1.6;} -.pipe-arrow{ - position:absolute;right:-20px;top:50%;transform:translateY(-50%); - font-size:16px;color:var(--dim);z-index:1; -} - -/* โ”€โ”€ MCP SECTION โ”€โ”€ */ -.mcp-grid{display:grid;grid-template-columns:1fr 1fr;gap:28px;align-items:start;} -.mcp-left{} -.mcp-code{ - background:rgba(3,2,10,0.9);border:1px solid rgba(42,255,160,0.15); - border-radius:16px;overflow:hidden; - box-shadow:0 0 60px rgba(42,255,160,0.05); -} -.mcp-code-header{ - padding:12px 18px;background:rgba(255,255,255,0.03); - border-bottom:1px solid rgba(255,255,255,0.05); - display:flex;align-items:center;gap:10px; - font-family:var(--font-mono);font-size:11px;color:var(--dim); -} -.mcp-code-header::before{ - content:'';width:7px;height:7px;border-radius:50%; - background:var(--mint);box-shadow:0 0 8px var(--mint); - animation:pulse-dot 2s infinite; -} -.mcp-code pre{ - padding:22px;font-family:var(--font-mono);font-size:12px; - line-height:1.9;color:rgba(200,195,255,0.85);overflow-x:auto; -} -.mcp-code pre .kw{color:#c792ea;} -.mcp-code pre .str{color:#c3e88d;} -.mcp-code pre .cm{color:rgba(255,255,255,0.25);font-style:italic;} -.mcp-code pre .fn{color:#82aaff;} -.mcp-code pre .num{color:var(--mint);} - -.mcp-tools-list{display:flex;flex-direction:column;gap:10px;} -.mcp-tool-row{ - display:flex;align-items:center;gap:14px; - padding:14px 18px; - background:rgba(255,255,255,0.03); - border:1px solid rgba(255,255,255,0.06); - border-radius:12px;transition:all 0.25s;cursor:default; -} -.mcp-tool-row:hover{border-color:rgba(42,255,160,0.2);background:rgba(42,255,160,0.04);} -.mcp-tool-icon{font-size:18px;width:32px;text-align:center;flex-shrink:0;} -.mcp-tool-name{font-family:var(--font-mono);font-size:11px;color:var(--mint);font-weight:700;flex-shrink:0;width:140px;} -.mcp-tool-desc{font-size:12px;color:var(--mid);} - -/* โ”€โ”€ FOUNDER SECTION โ”€โ”€ */ +.pipeline{display:flex;align-items:stretch;gap:0;max-width:1080px;margin:0 auto;flex-wrap:wrap;} +.pipe-step{flex:1;min-width:130px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07);border-radius:16px;padding:26px 16px;text-align:center;position:relative;transition:all 0.4s;cursor:default;margin:0 6px;} +.pipe-step:hover{background:rgba(255,255,255,0.06);border-color:rgba(42,255,160,0.25);transform:translateY(-8px);} +.pipe-num{font-family:var(--font-mono);font-size:10px;color:var(--dim);margin-bottom:10px;letter-spacing:0.1em;} +.pipe-icon{font-size:1.8rem;display:block;margin-bottom:9px;} +.pipe-name{font-size:13px;font-weight:600;color:var(--bright);margin-bottom:5px;} +.pipe-desc{font-size:10.5px;color:var(--mid);line-height:1.6;} +.pipe-arrow{position:absolute;right:-16px;top:50%;transform:translateY(-50%);font-size:14px;color:var(--dim);z-index:1;} + +/* โ”€โ”€ MCP ARCHITECTURE โ€” signature relay diagram โ”€โ”€ */ +#mcp-arch{background:rgba(0,0,0,0.1);} +.relay{ + display:grid;grid-template-columns:1fr auto 1fr auto 1fr;align-items:center; + max-width:980px;margin:0 auto 60px;gap:6px; +} +.relay-node{ + background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.09); + border-radius:16px;padding:22px 18px;text-align:center;transition:all 0.3s; +} +.relay-node:hover{border-color:rgba(42,255,160,0.3);transform:translateY(-4px);} +.relay-node .rn-icon{font-size:1.7rem;margin-bottom:8px;display:block;} +.relay-node .rn-title{font-size:13.5px;font-weight:700;color:var(--bright);margin-bottom:4px;} +.relay-node .rn-sub{font-size:10.5px;color:var(--dim);font-family:var(--font-mono);} +.relay-link{position:relative;height:2px;width:100%;min-width:36px;background:rgba(255,255,255,0.08);overflow:visible;} +.relay-link::before{ + content:'';position:absolute;top:50%;left:0;width:10px;height:10px;margin-top:-5px; + border-radius:50%;background:var(--mint);box-shadow:0 0 10px var(--mint); + animation:travel 2.4s linear infinite; +} +.relay-link.rev::before{animation-direction:reverse;background:var(--cyan);box-shadow:0 0 10px var(--cyan);} +@keyframes travel{0%{left:0;}100%{left:calc(100% - 10px);}} +.relay-label{grid-column:1/-1;display:flex;justify-content:space-between;font-family:var(--font-mono);font-size:9.5px;color:var(--dim);letter-spacing:0.06em;text-transform:uppercase;margin-top:10px;padding:0 6px;} + +.mcp-explain-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px;} +.mcp-explain-card{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07);border-radius:16px;padding:24px 22px;transition:all 0.3s;} +.mcp-explain-card:hover{border-color:rgba(167,139,250,0.25);transform:translateY(-4px);} +.mcp-explain-card .mx-step{font-family:var(--font-mono);font-size:10px;color:var(--violet);letter-spacing:0.1em;margin-bottom:8px;} +.mcp-explain-card h4{font-size:14.5px;font-weight:600;margin-bottom:8px;} +.mcp-explain-card p{font-size:12px;color:var(--mid);line-height:1.7;} + +/* โ”€โ”€ FOUNDER โ”€โ”€ */ #founder{background:rgba(0,0,0,0.1);} -.founder-grid{display:grid;grid-template-columns:1fr 1.4fr;gap:60px;align-items:start;} -.founder-left{} -.founder-avatar-wrap{ - position:relative;margin-bottom:28px;display:inline-block; -} -.founder-avatar{ - width:180px;height:180px;border-radius:24px; - object-fit:cover; - border:2px solid rgba(167,139,250,0.4); - box-shadow:0 0 0 6px rgba(167,139,250,0.06),0 0 60px rgba(167,139,250,0.2); - animation:avatar-float 4s ease-in-out infinite;display:block; -} +.founder-grid{display:grid;grid-template-columns:1fr 1.4fr;gap:56px;align-items:start;} +.founder-avatar-wrap{position:relative;margin-bottom:26px;display:inline-block;} +.founder-avatar{width:172px;height:172px;border-radius:24px;object-fit:cover;border:2px solid rgba(167,139,250,0.4);box-shadow:0 0 0 6px rgba(167,139,250,0.06),0 0 60px rgba(167,139,250,0.2);animation:avatar-float 4s ease-in-out infinite;display:block;} @keyframes avatar-float{0%,100%{transform:translateY(0);}50%{transform:translateY(-10px);}} -.founder-avatar-ring{ - position:absolute;inset:-12px;border-radius:30px; - border:1px solid rgba(167,139,250,0.15); - animation:ring-spin 12s linear infinite; -} -.founder-avatar-ring::before{ - content:'';position:absolute;top:-3px;left:50%; - width:6px;height:6px;border-radius:50%; - background:var(--violet);margin-left:-3px; - box-shadow:0 0 12px var(--violet); -} +.founder-avatar-ring{position:absolute;inset:-12px;border-radius:30px;border:1px solid rgba(167,139,250,0.15);animation:ring-spin 12s linear infinite;} +.founder-avatar-ring::before{content:'';position:absolute;top:-3px;left:50%;width:6px;height:6px;border-radius:50%;background:var(--violet);margin-left:-3px;box-shadow:0 0 12px var(--violet);} @keyframes ring-spin{from{transform:rotate(0deg);}to{transform:rotate(360deg);}} - -.founder-name{font-size:28px;font-weight:700;letter-spacing:-0.03em;margin-bottom:6px;} -.founder-role{font-family:var(--font-mono);font-size:11px;color:var(--violet);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:16px;} +.founder-name{font-size:26px;font-weight:700;letter-spacing:-0.03em;margin-bottom:6px;} +.founder-role{font-family:var(--font-mono);font-size:10.5px;color:var(--violet);letter-spacing:0.09em;text-transform:uppercase;margin-bottom:16px;} .founder-badges{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:22px;} -.f-badge{ - font-size:11px;padding:5px 13px;border-radius:20px; - border:1px solid rgba(255,255,255,0.08);color:var(--mid); - background:rgba(255,255,255,0.04);transition:all 0.2s;cursor:default; -} +.f-badge{font-size:10.5px;padding:5px 12px;border-radius:20px;border:1px solid rgba(255,255,255,0.08);color:var(--mid);background:rgba(255,255,255,0.04);transition:all 0.2s;cursor:default;} .f-badge:hover{border-color:rgba(167,139,250,0.3);color:var(--violet);} .founder-links{display:flex;gap:10px;flex-wrap:wrap;} -.f-link{ - display:inline-flex;align-items:center;gap:6px; - padding:8px 16px;border-radius:8px;font-size:12px; - border:1px solid rgba(255,255,255,0.1);color:var(--mid); - text-decoration:none;transition:all 0.2s;background:rgba(255,255,255,0.03); -} +.f-link{display:inline-flex;align-items:center;gap:6px;padding:8px 15px;border-radius:8px;font-size:11.5px;border:1px solid rgba(255,255,255,0.1);color:var(--mid);text-decoration:none;transition:all 0.2s;background:rgba(255,255,255,0.03);} .f-link:hover{border-color:rgba(167,139,250,0.4);color:var(--bright);background:rgba(167,139,250,0.08);} - -.founder-right{} -.founder-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:36px;} -.f-stat{ - background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07); - border-radius:14px;padding:18px;text-align:center;transition:all 0.3s;cursor:default; -} +.founder-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:34px;} +.f-stat{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07);border-radius:14px;padding:17px;text-align:center;transition:all 0.3s;cursor:default;} .f-stat:hover{border-color:rgba(42,255,160,0.2);transform:translateY(-4px);} -.f-stat-num{font-family:var(--font-mono);font-size:20px;font-weight:700;color:var(--mint);margin-bottom:4px;} -.f-stat-lbl{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:0.06em;} - +.f-stat-num{font-family:var(--font-mono);font-size:19px;font-weight:700;color:var(--mint);margin-bottom:4px;} +.f-stat-lbl{font-size:9.5px;color:var(--dim);text-transform:uppercase;letter-spacing:0.05em;} .timeline{display:flex;flex-direction:column;gap:0;} -.tl-item{ - display:flex;gap:18px;padding:20px 0; - border-bottom:1px solid rgba(255,255,255,0.04); - transition:all 0.2s;cursor:default; -} +.tl-item{display:flex;gap:16px;padding:18px 0;border-bottom:1px solid rgba(255,255,255,0.04);transition:all 0.2s;cursor:default;} .tl-item:last-child{border-bottom:none;} .tl-item:hover{padding-left:8px;} -.tl-dot{ - width:34px;height:34px;border-radius:10px;flex-shrink:0; - display:flex;align-items:center;justify-content:center; - font-size:14px;background:rgba(42,255,160,0.1); - border:1px solid rgba(42,255,160,0.2);margin-top:2px; - transition:all 0.3s; -} +.tl-dot{width:32px;height:32px;border-radius:10px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:13px;background:rgba(42,255,160,0.1);border:1px solid rgba(42,255,160,0.2);margin-top:2px;transition:all 0.3s;} .tl-item:hover .tl-dot{background:rgba(42,255,160,0.2);box-shadow:0 0 20px rgba(42,255,160,0.2);} -.tl-age{font-family:var(--font-mono);font-size:10px;color:var(--mint);letter-spacing:0.08em;text-transform:uppercase;margin-bottom:4px;} -.tl-title{font-size:14px;font-weight:600;color:var(--bright);margin-bottom:4px;} -.tl-desc{font-size:12px;color:var(--mid);line-height:1.7;} +.tl-age{font-family:var(--font-mono);font-size:9.5px;color:var(--mint);letter-spacing:0.07em;text-transform:uppercase;margin-bottom:4px;} +.tl-title{font-size:13.5px;font-weight:600;color:var(--bright);margin-bottom:3px;} +.tl-desc{font-size:11.5px;color:var(--mid);line-height:1.7;} /* โ”€โ”€ DOWNLOAD CTA โ”€โ”€ */ #download{text-align:center;} -.cta-box{ - max-width:700px;margin:0 auto; - background:rgba(255,255,255,0.03); - border:1px solid rgba(42,255,160,0.15); - border-radius:28px;padding:72px 48px; - position:relative;overflow:hidden; -} -.cta-box::before{ - content:'';position:absolute;top:0;left:0;right:0;height:1px; - background:linear-gradient(90deg,transparent,var(--mint),var(--cyan),transparent); -} -.cta-box::after{ - content:'';position:absolute; - width:400px;height:400px;border-radius:50%; - top:-100px;left:50%;transform:translateX(-50%); - background:radial-gradient(circle,rgba(42,255,160,0.06),transparent 70%); - pointer-events:none; -} -.cta-box h2{font-size:2.4rem;font-weight:700;letter-spacing:-0.04em;margin-bottom:16px;} -.cta-box p{color:var(--mid);font-size:16px;margin-bottom:40px;} -.dl-steps{display:flex;justify-content:center;gap:0;margin-bottom:40px;} -.dl-step{padding:0 28px;text-align:center;position:relative;} -.dl-step:not(:last-child)::after{ - content:'โ†’';position:absolute;right:-8px;top:12px; - color:var(--dim);font-size:12px; -} -.dl-step-n{ - width:34px;height:34px;border-radius:50%; - background:rgba(42,255,160,0.12);border:1px solid rgba(42,255,160,0.3); - display:flex;align-items:center;justify-content:center;margin:0 auto 8px; - font-family:var(--font-mono);font-size:13px;font-weight:700;color:var(--mint); -} -.dl-step p{font-size:12px;color:var(--mid);} -.btn-download{ - display:inline-flex;align-items:center;gap:14px; - background:linear-gradient(135deg,var(--mint),var(--cyan)); - color:#040210;font-weight:700;font-size:16px; - padding:20px 44px;border-radius:14px;text-decoration:none;cursor:none; - transition:all 0.3s; - box-shadow:0 0 60px rgba(42,255,160,0.3); -} +.cta-box{max-width:700px;margin:0 auto;background:rgba(255,255,255,0.03);border:1px solid rgba(42,255,160,0.15);border-radius:28px;padding:68px 46px;position:relative;overflow:hidden;} +.cta-box::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--mint),var(--cyan),transparent);} +.cta-box::after{content:'';position:absolute;width:400px;height:400px;border-radius:50%;top:-100px;left:50%;transform:translateX(-50%);background:radial-gradient(circle,rgba(42,255,160,0.06),transparent 70%);pointer-events:none;} +.cta-box h2{font-size:2.2rem;font-weight:700;letter-spacing:-0.03em;margin-bottom:14px;} +.cta-box p{color:var(--mid);font-size:15px;margin-bottom:36px;} +.dl-steps{display:flex;justify-content:center;gap:0;margin-bottom:36px;flex-wrap:wrap;} +.dl-step{padding:0 26px;text-align:center;position:relative;} +.dl-step:not(:last-child)::after{content:'โ†’';position:absolute;right:-8px;top:11px;color:var(--dim);font-size:12px;} +.dl-step-n{width:32px;height:32px;border-radius:50%;background:rgba(42,255,160,0.12);border:1px solid rgba(42,255,160,0.3);display:flex;align-items:center;justify-content:center;margin:0 auto 8px;font-family:var(--font-mono);font-size:12.5px;font-weight:700;color:var(--mint);} +.dl-step p{font-size:11.5px;color:var(--mid);} +.btn-download{display:inline-flex;align-items:center;gap:14px;background:linear-gradient(135deg,var(--mint),var(--cyan));color:#040210;font-weight:700;font-size:15.5px;padding:19px 42px;border-radius:14px;text-decoration:none;cursor:none;transition:all 0.3s;box-shadow:0 0 60px rgba(42,255,160,0.3);} .btn-download:hover{transform:translateY(-4px) scale(1.02);box-shadow:0 0 80px rgba(42,255,160,0.45);} -.dl-note{margin-top:20px;font-family:var(--font-mono);font-size:11px;color:var(--dim);} +.dl-note{margin-top:18px;font-family:var(--font-mono);font-size:10.5px;color:var(--dim);} +.dl-methodology{margin-top:10px;font-family:var(--font-mono);font-size:9.5px;color:rgba(74,70,104,0.85);max-width:480px;margin-left:auto;margin-right:auto;line-height:1.6;} /* โ”€โ”€ FOOTER โ”€โ”€ */ -footer{ - padding:48px 6vw;border-top:1px solid rgba(255,255,255,0.05); - display:flex;align-items:center;justify-content:space-between; - flex-wrap:wrap;gap:16px; - background:rgba(0,0,0,0.2); -} -.footer-brand{font-family:var(--font-mono);font-size:13px;color:var(--mint);} -.footer-mid{font-size:12px;color:var(--dim);} +footer{padding:44px 6vw;border-top:1px solid rgba(255,255,255,0.05);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;background:rgba(0,0,0,0.2);} +.footer-brand{font-family:var(--font-mono);font-size:12.5px;color:var(--mint);} +.footer-mid{font-size:11.5px;color:var(--dim);} .footer-mid span{color:var(--mint);} -.footer-links{display:flex;gap:24px;} -.footer-links a{font-size:12px;color:var(--dim);text-decoration:none;transition:color 0.2s;} +.footer-links{display:flex;gap:22px;} +.footer-links a{font-size:11.5px;color:var(--dim);text-decoration:none;transition:color 0.2s;} .footer-links a:hover{color:var(--mint);} -/* โ”€โ”€ SCROLL REVEAL โ”€โ”€ */ -.reveal{opacity:0;transform:translateY(36px);transition:opacity 0.8s cubic-bezier(0.16,1,0.3,1),transform 0.8s cubic-bezier(0.16,1,0.3,1);} +.reveal{opacity:0;transform:translateY(34px);transition:opacity 0.8s cubic-bezier(0.16,1,0.3,1),transform 0.8s cubic-bezier(0.16,1,0.3,1);} .reveal.visible{opacity:1;transform:translateY(0);} -.reveal-d1{transition-delay:0.1s;} -.reveal-d2{transition-delay:0.2s;} -.reveal-d3{transition-delay:0.3s;} -.reveal-d4{transition-delay:0.4s;} +.reveal-d1{transition-delay:0.1s;} .reveal-d2{transition-delay:0.2s;} .reveal-d3{transition-delay:0.3s;} .reveal-d4{transition-delay:0.4s;} -/* โ”€โ”€ RESPONSIVE โ”€โ”€ */ @media(max-width:900px){ + #hero{grid-template-columns:1fr;padding-top:130px;} + .hero-rail{border-left:none;padding-left:0;border-top:1px solid rgba(255,255,255,0.08);padding-top:24px;} .app-body{grid-template-columns:1fr;} .app-sidebar{display:none;} - .mcp-grid{grid-template-columns:1fr;} .founder-grid{grid-template-columns:1fr;} .pipeline{gap:10px;} .pipe-arrow{display:none;} nav .nav-links{display:none;} - .stats-strip{flex-wrap:wrap;} - .stat{min-width:50%;} + .stats-strip{grid-template-columns:repeat(2,1fr);} + .relay{grid-template-columns:1fr;gap:14px;} + .relay-link{width:2px;height:26px;margin:0 auto;} + .relay-link::before{left:50%;top:0;margin-left:-5px;margin-top:0;animation:travelv 2.4s linear infinite;} + @keyframes travelv{0%{top:0;}100%{top:calc(100% - 10px);}} } @media(max-width:600px){ - section{padding:80px 5vw;} - #demo{padding:80px 5vw;} + section{padding:76px 5vw;} .founder-stats{grid-template-columns:repeat(2,1fr);} - .cta-box{padding:48px 24px;} + .cta-box{padding:44px 22px;} .dl-steps{flex-wrap:wrap;} } @@ -754,76 +448,65 @@
-
-
NPMAI ECOSYSTEM ยท v3.0 ยท Free & Open Source
-

- The Open Source
- Agentic AI
- Desktop Platform -

-

21 integrated tools. Plan โ†’ Generate โ†’ Audit โ†’ Execute โ†’ Verify. A complete agent loop that rivals Claude Code โ€” powered by free local LLMs.

- -
-
0
Downloads
-
21
Tools
-
Free
Forever
-
45+
LLMs
+
+
v3NPMAI Ecosystem ยท Free & Open Source
+

NPM-AutoCode-AI
turns plain English
into things done.

+

Describe a task. It plans it, writes the code, checks the code, runs it on your machine, and confirms it actually worked โ€” a five-role agent loop, not a chatbot pretending to be one.

+ +
+
0
Downloads
+
100
Tools
+
Free
Forever
+
45+
LLMs
+
-
-
- scroll +
+
Every task, five roles
+
Plannerbreaks your task into steps
+
Tool Managerpicks from 100 real tools
+
Coderwrites the Python for each step
+
Auditorchecks it before anything runs
+
Verifierconfirms it actually worked
+
scroll to see it run
-
+
Live Demo
-

Watch the Agent Think

-

Click a task chip to see the full pipeline in motion

+

Pick any task. Watch it run.

+

These are real categories from the tool registry โ€” dev tooling, cloud, business, communication, creative, data, media, and system automation.

-
-
-
-
-
-
-
NPM Agent โ€” NPMAI Ecosystem v3.0
-
MCP :7799
+
+
NPM-AutoCode-AI โ€” v3
+
MCP LINKED
-
-
- +
Navigation
๐Ÿ’ฌ Agent
@@ -833,25 +516,17 @@

Watch the Agent Think

๐Ÿ‘ค Founder
๐Ÿ“– Docs
-
-
- MCP Server Active :7799 -
+
MCP Link Connected
-
Ready0%
-
-
Hello! I'm NPM Agent. Describe any task โ€” I'll plan it, generate secure code, and execute it on your machine. What would you like to automate?
-
-
-
-
Execution Logs
+
Hi! Describe any task โ€” I'll plan it, write the code, audit it, run it, and confirm it actually worked. Pick one below to see it live.
+
Execution Logs
@@ -860,125 +535,154 @@

Watch the Agent Think

- -
+
- + - - + + + + + + + + + +
+ +
+
+
MCP Architecture
+

Connect it to Claude or Grok.
No API key required.

+

NPM-AutoCode-AI hosts its own MCP endpoint. Link it once from the app, paste the link into your Claude or Grok connector settings, and the LLM you already talk to becomes the brain โ€” planning, coding, and auditing โ€” while this app is the hands that actually execute on your machine.

+
+ +
+
๐Ÿ–ฅ
Your Desktop
executes locally
+ +
๐Ÿ›ฐ
MCP Server
plan ยท route ยท verify
+ +
๐Ÿค–
Claude / Grok
thinks, writes, reviews
+
+
+ Code + results flow back โ†’โ† Prompts + tasks flow out +
+ +
+
01

Get your link, once

Log in from the app's MCP tab. That's the only time you'll ever need to log in โ€” everything else in the app works without an account.

+
02

Paste it into your connector

Drop the link into Claude or Grok's custom connector settings. From then on, just talk to your normal LLM chat like always.

+
03

Ask for anything

"Clear my Downloads folder," "email my team the report" โ€” the LLM detects it needs a real action and calls into your link automatically.

+
04

It's audited before it runs

Every generated script passes an Auditor review, plus a separate safety check, before it's ever sent to your desktop to execute.

+
05

Runs on your machine, not the cloud

Execution happens locally โ€” your files, your credentials, your machine. The MCP server only ever relays code and results.

+
06

You confirm it, not just the exit code

A Verifier role checks the actual output against the task โ€” not just whether the script ran without crashing.

+
+
+ +
+
Capabilities
-

Built to Compete.
Free to Use.

-

Everything Claude Code does โ€” on local models, with a GUI, for everyone.

+

Built to compete.
Free to use.

+

Everything a coding agent should do โ€” on your own machine, with a GUI, for everyone.

- ๐Ÿง  -

Multi-LLM Agent Pipeline

-

4 separate LLMs for planning, code generation, security auditing, and verification โ€” each with fallback chains. Not a chatbot. A real agent loop.

- Plan โ†’ Generate โ†’ Audit โ†’ Execute โ†’ Verify + ๐Ÿง 

Five-Role Agent Pipeline

+

Planner, Tool Manager, Coder, Auditor, and Verifier โ€” each with its own job and its own fallback chain. Not a chatbot. A real agent loop.

+ Plan โ†’ Select โ†’ Code โ†’ Audit โ†’ Execute โ†’ Verify
- ๐Ÿ”’ -

Dual-LLM Security Audit

-

Every generated script is reviewed by qwen2.5-coder:7b (fallback: falcon:7b) before any execution. 5 risk categories checked. Kill button always available.

+ ๐Ÿ”’

Independent Security Audit

+

Every generated script is reviewed by a separate Auditor role before execution, plus a second non-LLM safety check that can't be talked around.

Security First
- ๐Ÿ”„ -

Auto-Debug Loop (12 retries)

-

Execution fails? The agent captures the error, sends it back to CodeLlama, and rewrites or patches the code. Partial progress is preserved between retries.

+ ๐Ÿ”„

Auto-Debug Loop (12 retries)

+

Execution fails? The agent captures the error, feeds it back to the Coder, and retries with the prior attempt as context โ€” up to 12 times per step.

Self-Healing
- ๐Ÿ“ -

File System Awareness

-

On startup, the agent scans Desktop, Downloads, Documents, Pictures, Videos, Music. Knows your files before you type. No paths needed.

+ ๐Ÿ“

Workspace Awareness

+

On first run, the agent scans Desktop, Downloads, Documents, Pictures, Videos, and Music โ€” it knows your files before you type a path.

Workspace Scanner
- ๐Ÿ”Œ -

Embedded MCP Server

-

Start a TCP MCP server from the sidebar. 50+ tool endpoints exposed. Connect from Claude Desktop or any MCP client. Power your other projects.

- MCP Protocol + ๐Ÿ”—

MCP Link, Not an API Key

+

Connect Claude or Grok directly from the sidebar. Login happens once, only for this โ€” everything else in the app stays account-free.

+ Bring Your Own LLM
- ๐ŸŽค -

Voice Input + subprocess

-

Speak your task. Code runs as a real child process โ€” not exec(). Streams stdout live. Kill any script instantly. Real isolation, real safety.

- Voice + Isolation + โ–ถ

Real Subprocess Execution

+

Code runs as an actual child process โ€” not exec(). Output streams live to the log panel. Kill any script instantly, mid-run.

+ Real Isolation
- +
-
+
Tool Registry
-

21 Integrated Tools

-

All tools available to the agent automatically. No configuration needed. Just describe your task.

+

100 Tools. Zero Setup.

+

Every tool below is available automatically. Just describe the task โ€” the agent picks what it needs.

- ๐Ÿ“ง EmailTool ยท Gmail SMTP/IMAP ยท Bulk CSV - ๐Ÿ“ FileTool ยท Rename ยท Zip ยท Organize - ๐Ÿ“„ PDFTool ยท Merge ยท Split ยท Extract - ๐ŸŒ WebTool ยท Scrape ยท Playwright ยท API - ๐Ÿ“Š SpreadsheetTool ยท CSV ยท Excel ยท Sheets - โญ GitHubTool ยท Issues ยท Push ยท Clone - ๐Ÿ’ฌ SlackTool ยท Messages ยท Upload ยท Read - ๐ŸŽฎ DiscordTool ยท Webhooks ยท Embeds - ๐Ÿ“ฑ WhatsAppTool ยท Instant Messages - ๐Ÿ““ NotionTool ยท Pages ยท Databases - - ๐Ÿ“ง EmailTool ยท Gmail SMTP/IMAP ยท Bulk CSV - ๐Ÿ“ FileTool ยท Rename ยท Zip ยท Organize - ๐Ÿ“„ PDFTool ยท Merge ยท Split ยท Extract - ๐ŸŒ WebTool ยท Scrape ยท Playwright ยท API - ๐Ÿ“Š SpreadsheetTool ยท CSV ยท Excel ยท Sheets - โญ GitHubTool ยท Issues ยท Push ยท Clone - ๐Ÿ’ฌ SlackTool ยท Messages ยท Upload ยท Read - ๐ŸŽฎ DiscordTool ยท Webhooks ยท Embeds - ๐Ÿ“ฑ WhatsAppTool ยท Instant Messages - ๐Ÿ““ NotionTool ยท Pages ยท Databases + ๐ŸŒฑ GitTool ยท commit ยท branch ยท rebase + โญ GitHubTool ยท issues ยท PRs ยท releases + ๐ŸฆŠ GitLabTool ยท pipelines ยท merge requests + ๐Ÿณ DockerTool ยท build ยท compose ยท registry + โ˜ AWSS3Tool ยท buckets ยท presigned URLs + ๐Ÿ’ณ StripeTool ยท payments ยท invoices ยท payouts + ๐Ÿ“ง SMTPAdvancedTool ยท HTML email ยท attachments + ๐Ÿ““ NotionAdvancedTool ยท pages ยท databases + ๐ŸŽฅ ZoomTool ยท meetings ยท recordings + ๐Ÿ“ž TwilioTool ยท SMS ยท voice ยท WhatsApp + ๐ŸŒฑ GitTool ยท commit ยท branch ยท rebase + โญ GitHubTool ยท issues ยท PRs ยท releases + ๐ŸฆŠ GitLabTool ยท pipelines ยท merge requests + ๐Ÿณ DockerTool ยท build ยท compose ยท registry + โ˜ AWSS3Tool ยท buckets ยท presigned URLs + ๐Ÿ’ณ StripeTool ยท payments ยท invoices ยท payouts + ๐Ÿ“ง SMTPAdvancedTool ยท HTML email ยท attachments + ๐Ÿ““ NotionAdvancedTool ยท pages ยท databases + ๐ŸŽฅ ZoomTool ยท meetings ยท recordings + ๐Ÿ“ž TwilioTool ยท SMS ยท voice ยท WhatsApp
-
+
- ๐Ÿฆ TwitterTool ยท Tweets ยท Timeline - ๐Ÿ–ฅ SystemTool ยท Shell ยท Clipboard ยท Screenshot - ๐Ÿ–ผ ImageTool ยท Resize ยท OCR ยท Compress - โฐ SchedulerTool ยท Cron ยท Background - ๐ŸŽซ JiraTool ยท Issues ยท Sprints - โœˆ TelegramTool ยท Bot Messages - ๐Ÿ”ฒ QRTool ยท Generate QR Codes - ๐ŸŽค VoiceTool ยท TTS ยท STT - ๐Ÿ‘ WatcherTool ยท File Change Detection - ๐Ÿ“š RAGTool ยท Large Doc Query ยท Summarize - ๐Ÿ–ง SSHTool ยท Remote Commands ยท SFTP - ๐Ÿฆ TwitterTool ยท Tweets ยท Timeline - ๐Ÿ–ฅ SystemTool ยท Shell ยท Clipboard ยท Screenshot - ๐Ÿ–ผ ImageTool ยท Resize ยท OCR ยท Compress - โฐ SchedulerTool ยท Cron ยท Background - ๐ŸŽซ JiraTool ยท Issues ยท Sprints - โœˆ TelegramTool ยท Bot Messages - ๐Ÿ”ฒ QRTool ยท Generate QR Codes - ๐ŸŽค VoiceTool ยท TTS ยท STT - ๐Ÿ‘ WatcherTool ยท File Change Detection - ๐Ÿ“š RAGTool ยท Large Doc Query ยท Summarize - ๐Ÿ–ง SSHTool ยท Remote Commands ยท SFTP + ๐Ÿ–ฅ TerminalTool ยท run commands ยท pipe output + ๐Ÿ“ฆ PackageManagerTool ยท npm ยท pip ยท cargo + ๐Ÿงฉ VSCodeTool ยท open ยท edit ยท extensions + ๐Ÿ›  MakefileTool ยท targets ยท build steps + ๐Ÿงฑ CMakeTool ยท configure ยท build ยท test + ๐Ÿž DebuggerTool ยท breakpoints ยท stack traces + ๐ŸŽ™ SpeechAITool ยท transcribe ยท diarize ยท clone + ๐Ÿ‘ ComputerVisionTool ยท detect ยท classify ยท OCR + ๐ŸŽฌ FFmpegTool ยท convert ยท compress ยท trim + ๐Ÿ–ผ ImageAdvancedTool ยท resize ยท batch edit + ๐Ÿ–ฅ TerminalTool ยท run commands ยท pipe output + ๐Ÿ“ฆ PackageManagerTool ยท npm ยท pip ยท cargo + ๐Ÿงฉ VSCodeTool ยท open ยท edit ยท extensions + ๐Ÿ›  MakefileTool ยท targets ยท build steps + ๐Ÿงฑ CMakeTool ยท configure ยท build ยท test + ๐Ÿž DebuggerTool ยท breakpoints ยท stack traces + ๐ŸŽ™ SpeechAITool ยท transcribe ยท diarize ยท clone + ๐Ÿ‘ ComputerVisionTool ยท detect ยท classify ยท OCR + ๐ŸŽฌ FFmpegTool ยท convert ยท compress ยท trim + ๐Ÿ–ผ ImageAdvancedTool ยท resize ยท batch edit
@@ -987,108 +691,19 @@

21 Integrated Tools

-
+
Agent Architecture
-

How the Agent Thinks

-

Every task goes through 6 stages. Each stage runs a different LLM for the right job.

+

How the agent thinks

+

Every task moves through these stages in order โ€” each one runs a different role for the right job.

-
-
01
- ๐Ÿ” -
Scan
-
Workspace scanner reads your file system โ€” folders, files, paths โ€” before anything else
-
โ†’
-
-
-
02
- ๐Ÿ—บ -
Plan
-
llama3.2:3b breaks your task into 2-5 atomic, independently verifiable steps
-
โ†’
-
-
-
03
- โšก -
Generate
-
codellama:7b writes Python for each step using the 21 tool templates as scaffolding
-
โ†’
-
-
-
04
- ๐Ÿ”’ -
Audit
-
qwen2.5-coder checks 5 risk categories. BLOCK or ALLOW โ€” no ambiguity
-
โ†’
-
-
-
05
- โ–ถ -
Execute
-
Real subprocess.Popen โ€” not exec(). stdout streams live. Kill anytime.
-
โ†’
-
-
-
06
- โœ“ -
Verify
-
A second LLM checks "did this actually complete?" โ€” not just exit code 0
-
-
-
- -
- - -
-
-
MCP Protocol
-

Power Your Other Projects

-

Start the embedded MCP server from the app sidebar. All 21 tools become available to Claude Desktop, your own LLMs, or any MCP client.

-
-
-
-
MCP Client ยท TCP ยท Port 7799
-
# Connect from Claude Desktop
-# Add to mcp_config.json:
-{
-  "npm_agent": {
-    "type": "tcp",
-    "host": "localhost",
-    "port": 7799
-  }
-}
-
-# Or call directly:
-import socket, json
-
-def call_tool(tool, params):
-  s = socket.socket()
-  s.connect(("localhost", 7799))
-  s.sendall(json.dumps({
-    "tool": tool,
-    "params": params
-  }).encode() + b"\n")
-  return json.loads(s.recv(65536))
-
-# Example: send an email
-result = call_tool("email_send", {
-  "to": "teacher@school.edu",
-  "subject": "Attendance Report",
-  "body": "Today's report attached."
-})
-
-
-
50+ Exposed Endpoints
-
๐Ÿ’ฌ
npmai_chat
Chat with any of 45+ models with named memory
-
๐Ÿค–
npmai_run_task
Full agent pipeline from any external client
-
๐Ÿ“ง
email_send
Send email ยท email_bulk ยท email_inbox
-
โญ
github_issue
Create issues ยท push files ยท clone repos
-
๐Ÿ’ฌ
slack_send
Send messages ยท read channels ยท upload files
-
๐Ÿ“š
rag_query
Query large documents via npmai Rag class
-
๐Ÿ–ฅ
system_run
Execute shell commands on the host machine
-
๐Ÿ“Š
workspace_scan
Scan file system, returns full path map
-
+
01
๐Ÿ”
Scan
Reads your file system before anything else
โ†’
+
02
๐Ÿ—บ
Plan
Breaks the task into ordered, atomic steps
โ†’
+
03
๐Ÿงฐ
Select Tools
Picks exactly what each step needs from the registry
โ†’
+
04
โšก
Generate
Writes real Python for each step
โ†’
+
05
๐Ÿ”’
Audit
Reviewed for anything destructive โ€” BLOCK or ALLOW
โ†’
+
06
โ–ถ
Execute
Real subprocess. Streams live. Kill anytime.
โ†’
+
07
โœ“
Verify
Confirms the task actually completed
@@ -1125,19 +740,19 @@

Meet the Founder

-
1.5M+
PyPI downloads
+
4M+
npmai PyPI downloads
+
300K+
npmai-agents downloads
433K+
Social followers
TEDx
Speaker at 13
100%
Allen scholarship
-
45+
LLMs in ecosystem
-
3
Research papers 2026
+
10
Research papers
๐Ÿง’
Age 9
Started Teaching Nursery Kids
While in 4th grade, taught nursery children โ€” planting the seed for education equity.
๐Ÿ”ฅ
Age 11 โ€” "Bihar Viral Boy"
Challenged Bihar's Chief Minister
Raised education & liquor ban issues. Went viral nationally. Received support from Cabinet Ministers, Bollywood actors & ALLEN Institute founder.
๐ŸŽค
Age 13
TEDx Speaker
One of India's youngest TEDx speakers โ€” rural education and using tech to bridge India's urban-rural divide.
-
๐Ÿ’ป
Age 13-14
Founded NPMAI ECOSYSTEM โ€” 1.5M+ Downloads
Self-taught, zero CS background. Built entire ecosystem on free cloud infrastructure.
-
๐Ÿ“„
2026
3 Research Papers Published
LARA ยท Representative Ideal Democracy ยท Ideal Administrative System Theory
+
๐Ÿ’ป
Age 13-15
Founded NPMAI ECOSYSTEM
Self-taught, zero CS background. Built the entire ecosystem on free cloud infrastructure.
+
๐Ÿ“„
2026
10 Research Papers Published
Spanning AI/agent architecture, chemistry, mathematics, and constitutional/political science.
@@ -1149,22 +764,22 @@

Meet the Founder

Get Started
-

Download Free.
Run Instantly.

-

No API keys. No local GPU. No Ollama setup. Unzip and run.

+

Download free.
Run instantly.

+

No API keys required to start. No local GPU needed. Unzip and run.

1

Download ZIP

2

Unzip folder

3

Run .exe

4

Start automating

- โ†“ Download NPM Agent v3.0 -
Windows ยท Free Forever ยท Open Source ยท No API Key Needed ยท NPMAI Ecosystem
+ โ†“ Download NPM-AutoCode-AI v3 +
Windows ยท Free Forever ยท Open Source ยท NPMAI Ecosystem
+
57,000+ downloads โ€” determined as per the LLM requests received on our npmai_LLM server; that's how we identified this figure.
-
- +
-
+
From fee7dee2ef0a3140d8b1bdb69248b20582cc5eef Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 22 Jul 2026 14:18:32 +0530 Subject: [PATCH 46/68] Delete Desktop_App/NPM-AutoCode-AI.py --- Desktop_App/NPM-AutoCode-AI.py | 235 --------------------------------- 1 file changed, 235 deletions(-) delete mode 100644 Desktop_App/NPM-AutoCode-AI.py diff --git a/Desktop_App/NPM-AutoCode-AI.py b/Desktop_App/NPM-AutoCode-AI.py deleted file mode 100644 index 29dbfcb..0000000 --- a/Desktop_App/NPM-AutoCode-AI.py +++ /dev/null @@ -1,235 +0,0 @@ -import sys -from PySide6.QtWidgets import ( - QApplication, QWidget, QVBoxLayout, QLabel, - QTextEdit, QLineEdit, QPushButton, QProgressBar -) -from PySide6.QtCore import QThread, Signal -from langchain_core.prompts import PromptTemplate -from npmai import Ollama,Memory -from langchain_core.output_parsers import StrOutputParser -import traceback -import subprocess -import os - -# ======================= -# WORKER THREAD -# ======================= -class CodeWorker(QThread): - log = Signal(str) - finished = Signal(str) - - def __init__(self, task_text): - super().__init__() - self.task_text = task_text - self.code_var= {} - - def executor(self,code): - error_log="" - try: - exec(code,self.code_var) - return None - except Exception as e: - error_log+= traceback.format_exc() - return error_log - - def run(self): - tried=0 - self.log.emit("npmai is doing your requested task") - memory=Memory("coder0-generator") - memory1=Memory("safety_checker") - - llm=Ollama( - model="codellama:7b-instruct", - temperature=0.3 - ) - - query=self.task_text - - prompt = f""" - Hey you are helpful code assistant that writes code just write code nothing else - and maintain proper indentation. No extra explanations. - remember that whatever imports you are using install dependencies in code using subprocess beacuase user had not installed your code requirements - Do not respond with any insturction or any other statement if you are giving any statement in english or that is not of code so give in # comment ok beacuase your code will be executed through exec() function of python so give such format code that can be executed in exec() function YOUR WHOLE RESPONSE WILL BE SENT TO exec() so do not write anything except code. - You will be asked to generate code about a task. - This is the task:{query}""" - - response=llm.invoke(prompt) - - parser=StrOutputParser() - final_response=parser.parse(response) - - cleaned_response = final_response.strip() - if cleaned_response.startswith("```python"): - cleaned_response = cleaned_response[len("```python"):] - elif cleaned_response.startswith("```"): - cleaned_response= cleaned_response[len("```"):] - else: - pass - - if cleaned_response.endswith("```"): - cleaned_response = cleaned_response[:-len("```")] - memory.save_context(query,cleaned_response) - self.log.emit("Code generation for requested task is completed now") - - - while True: - if tried==15: - self.log.emit("!!! Debugging Limit reached please enter prompt again with more clarity and simplicity this will help A.I in understanding your task.") - self.finished.emit("!!! Debugging Limit Crossed !!!") - return - - history1=memory1.load_memory_variables() - if "AI: " in history1: - last_safety_decision= history1.split("AI: ")[-1].strip() - else: - last_safety_decision= "No" - - if "Yes" not in last_safety_decision: - llm1=Ollama( - model="qwen2.5-coder:7b", - temperature=0.1 - ) - - prompt=f""" - [SYSTEM: SECURITY MONITOR] - You are a senior cyber-security auditor. Analyze the Python code provided below for MALICIOUS intent or HIGH-RISK operations that could harm a non-technical user's system. - - CRITERIA FOR 'Yes' (High Risk): - 1. Deleting system files or user documents without clear task-related necessity. - 2. Stealing private data (passwords, cookies, SSH keys, .env files). - 3. Establishing unauthorized remote connections (reverse shells). - 4. Commands that can crash the OS (Fork bombs, infinite resource loops). - 5. Obfuscated or hidden code intended to bypass detection. - - CRITERIA FOR 'No' (Safe): - 1. Standard data processing, plotting, or web scraping as per user task. - 2. Creating/Writing files specifically requested by the task. - 3. Installing standard libraries via pip/subprocess. - - CODE TO REVIEW: - {cleaned_response} - - OUTPUT INSTRUCTION: - Is this code dangerous? Respond ONLY with exactly one word: 'Yes' or 'No'. - Do not provide any explanation, warnings, or markdown. - """ - - response=llm1.invoke(prompt) - if response=="Yes": - self.finished.emit("!!! SECURITY RISK !!! We cannot execute this code beacuause it can harm your system.") - memory1.save_context("Latest_AI_Response",response) - memory1.clear_memory() - return - - error_log = self.executor(cleaned_response) - - if not error_log: - self.finished.emit("--- Task Completed Successfully. ---") - break - - self.log.emit("--- Execution Have Some Problem. Capturing Error and Debugging... ---") - - history=memory.load_memory_variables() - response1=llm.invoke(f""" - Hey, you wrote this code: {history.split("AI: ")[-1].strip()} - The user task is: {query} - During execution, we got this error: {error_log} - - Your Goal: Fix the error and complete the task. - You have two choices: - 1. REWRITE: If the logic is fundamentally wrong, rewrite the WHOLE code. - 2. PARTIAL: If some task is already done (variables are saved in globals()), you can write just the remaining part to complete the task. Only do this if you are 100% confident. - - IMPORTANT RULES: - 1. User is non-technical; handle everything automatically. - 2. Maintain proper indentation. No extra explanations outside of # comments. - 3. ALWAYS include subprocess imports/installs if you use new libraries. - 4. Output ONLY executable code. Do not say "Here is the fix". - 5. If writing a partial fix, assume previous successful variables are already in memory. - """) - - final_response1=parser.parse(response1) - - tried+=1 - - cleaned_response = final_response1.strip() - if cleaned_response.startswith("```python"): - cleaned_response = cleaned_response[len("```python"):] - elif cleaned_response.startswith("```"): - cleaned_response= cleaned_response[len("```"):] - else: - pass - - if cleaned_response.endswith("```"): - cleaned_response = cleaned_response[:-len("```")] - memory.save_context(error_log,cleaned_response) - - - -# ======================= -# UI -# ======================= -class AutoCodeApp(QWidget): - def __init__(self): - super().__init__() - self.setWindowTitle("NPM AutoCode AI") - self.resize(600, 600) - layout = QVBoxLayout() - - self.task_input = QLineEdit() - self.task_input.setPlaceholderText("Describe your automation task here...") - - self.done_btn = QPushButton("Generate & Execute") - self.log_box = QTextEdit() - self.log_box.setReadOnly(True) - - self.progress_bar = QProgressBar() - self.progress_bar.setRange(0, 100) - self.progress_bar.setValue(0) - - layout.addWidget(QLabel("Task Description:")) - layout.addWidget(self.task_input) - layout.addWidget(self.done_btn) - layout.addWidget(QLabel("Logs:")) - layout.addWidget(self.log_box) - layout.addWidget(self.progress_bar) - - self.setLayout(layout) - - self.done_btn.clicked.connect(self.start_task) - - def start_task(self): - task_text = self.task_input.text().strip() - if not task_text: - self.log_box.append("Please enter a task description") - return - - self.log_box.append("Starting task...") - self.progress_bar.setValue(10) - - self.worker = CodeWorker(task_text) - self.worker.log.connect(self.log_box.append) - self.worker.finished.connect(self.task_finished) - self.worker.start() - - def task_finished(self, msg): - self.log_box.append(msg) - self.progress_bar.setValue(100) - -# ======================= -# MAIN -# ======================= -if __name__ == "__main__": - app = QApplication(sys.argv) - window = AutoCodeApp() - window.show() - base_dir = os.path.dirname(sys.argv[0]) - for filename in ["coder0-generator.json", "safety_checker.json"]: - file_path = os.path.join(base_dir, filename) - if os.path.exists(file_path): - try: - os.remove(file_path) - except Exception as e: - pass - - sys.exit(app.exec()) From 797ea35fe136d12bbac4e0b11588589f12fa13ea Mon Sep 17 00:00:00 2001 From: sonuramashishnpm Date: Wed, 22 Jul 2026 16:15:54 +0530 Subject: [PATCH 47/68] Added features and fixed busg --- Desktop_App/NPM-AutoCode-AI.py | 235 --------- .../__pycache__/mcp_link.cpython-312.pyc | Bin 0 -> 10747 bytes .../{NPM-AutoCode-AI(V4).py => app.py} | 449 +++++++++++++++++- Desktop_App/app_config.json | 5 + Desktop_App/mcp_link.py | 20 +- Desktop_App/npmai.png | Bin 0 -> 106287 bytes Desktop_App/requirements.txt | 1 + 7 files changed, 452 insertions(+), 258 deletions(-) delete mode 100644 Desktop_App/NPM-AutoCode-AI.py create mode 100644 Desktop_App/__pycache__/mcp_link.cpython-312.pyc rename Desktop_App/{NPM-AutoCode-AI(V4).py => app.py} (67%) create mode 100644 Desktop_App/app_config.json create mode 100644 Desktop_App/npmai.png diff --git a/Desktop_App/NPM-AutoCode-AI.py b/Desktop_App/NPM-AutoCode-AI.py deleted file mode 100644 index 29dbfcb..0000000 --- a/Desktop_App/NPM-AutoCode-AI.py +++ /dev/null @@ -1,235 +0,0 @@ -import sys -from PySide6.QtWidgets import ( - QApplication, QWidget, QVBoxLayout, QLabel, - QTextEdit, QLineEdit, QPushButton, QProgressBar -) -from PySide6.QtCore import QThread, Signal -from langchain_core.prompts import PromptTemplate -from npmai import Ollama,Memory -from langchain_core.output_parsers import StrOutputParser -import traceback -import subprocess -import os - -# ======================= -# WORKER THREAD -# ======================= -class CodeWorker(QThread): - log = Signal(str) - finished = Signal(str) - - def __init__(self, task_text): - super().__init__() - self.task_text = task_text - self.code_var= {} - - def executor(self,code): - error_log="" - try: - exec(code,self.code_var) - return None - except Exception as e: - error_log+= traceback.format_exc() - return error_log - - def run(self): - tried=0 - self.log.emit("npmai is doing your requested task") - memory=Memory("coder0-generator") - memory1=Memory("safety_checker") - - llm=Ollama( - model="codellama:7b-instruct", - temperature=0.3 - ) - - query=self.task_text - - prompt = f""" - Hey you are helpful code assistant that writes code just write code nothing else - and maintain proper indentation. No extra explanations. - remember that whatever imports you are using install dependencies in code using subprocess beacuase user had not installed your code requirements - Do not respond with any insturction or any other statement if you are giving any statement in english or that is not of code so give in # comment ok beacuase your code will be executed through exec() function of python so give such format code that can be executed in exec() function YOUR WHOLE RESPONSE WILL BE SENT TO exec() so do not write anything except code. - You will be asked to generate code about a task. - This is the task:{query}""" - - response=llm.invoke(prompt) - - parser=StrOutputParser() - final_response=parser.parse(response) - - cleaned_response = final_response.strip() - if cleaned_response.startswith("```python"): - cleaned_response = cleaned_response[len("```python"):] - elif cleaned_response.startswith("```"): - cleaned_response= cleaned_response[len("```"):] - else: - pass - - if cleaned_response.endswith("```"): - cleaned_response = cleaned_response[:-len("```")] - memory.save_context(query,cleaned_response) - self.log.emit("Code generation for requested task is completed now") - - - while True: - if tried==15: - self.log.emit("!!! Debugging Limit reached please enter prompt again with more clarity and simplicity this will help A.I in understanding your task.") - self.finished.emit("!!! Debugging Limit Crossed !!!") - return - - history1=memory1.load_memory_variables() - if "AI: " in history1: - last_safety_decision= history1.split("AI: ")[-1].strip() - else: - last_safety_decision= "No" - - if "Yes" not in last_safety_decision: - llm1=Ollama( - model="qwen2.5-coder:7b", - temperature=0.1 - ) - - prompt=f""" - [SYSTEM: SECURITY MONITOR] - You are a senior cyber-security auditor. Analyze the Python code provided below for MALICIOUS intent or HIGH-RISK operations that could harm a non-technical user's system. - - CRITERIA FOR 'Yes' (High Risk): - 1. Deleting system files or user documents without clear task-related necessity. - 2. Stealing private data (passwords, cookies, SSH keys, .env files). - 3. Establishing unauthorized remote connections (reverse shells). - 4. Commands that can crash the OS (Fork bombs, infinite resource loops). - 5. Obfuscated or hidden code intended to bypass detection. - - CRITERIA FOR 'No' (Safe): - 1. Standard data processing, plotting, or web scraping as per user task. - 2. Creating/Writing files specifically requested by the task. - 3. Installing standard libraries via pip/subprocess. - - CODE TO REVIEW: - {cleaned_response} - - OUTPUT INSTRUCTION: - Is this code dangerous? Respond ONLY with exactly one word: 'Yes' or 'No'. - Do not provide any explanation, warnings, or markdown. - """ - - response=llm1.invoke(prompt) - if response=="Yes": - self.finished.emit("!!! SECURITY RISK !!! We cannot execute this code beacuause it can harm your system.") - memory1.save_context("Latest_AI_Response",response) - memory1.clear_memory() - return - - error_log = self.executor(cleaned_response) - - if not error_log: - self.finished.emit("--- Task Completed Successfully. ---") - break - - self.log.emit("--- Execution Have Some Problem. Capturing Error and Debugging... ---") - - history=memory.load_memory_variables() - response1=llm.invoke(f""" - Hey, you wrote this code: {history.split("AI: ")[-1].strip()} - The user task is: {query} - During execution, we got this error: {error_log} - - Your Goal: Fix the error and complete the task. - You have two choices: - 1. REWRITE: If the logic is fundamentally wrong, rewrite the WHOLE code. - 2. PARTIAL: If some task is already done (variables are saved in globals()), you can write just the remaining part to complete the task. Only do this if you are 100% confident. - - IMPORTANT RULES: - 1. User is non-technical; handle everything automatically. - 2. Maintain proper indentation. No extra explanations outside of # comments. - 3. ALWAYS include subprocess imports/installs if you use new libraries. - 4. Output ONLY executable code. Do not say "Here is the fix". - 5. If writing a partial fix, assume previous successful variables are already in memory. - """) - - final_response1=parser.parse(response1) - - tried+=1 - - cleaned_response = final_response1.strip() - if cleaned_response.startswith("```python"): - cleaned_response = cleaned_response[len("```python"):] - elif cleaned_response.startswith("```"): - cleaned_response= cleaned_response[len("```"):] - else: - pass - - if cleaned_response.endswith("```"): - cleaned_response = cleaned_response[:-len("```")] - memory.save_context(error_log,cleaned_response) - - - -# ======================= -# UI -# ======================= -class AutoCodeApp(QWidget): - def __init__(self): - super().__init__() - self.setWindowTitle("NPM AutoCode AI") - self.resize(600, 600) - layout = QVBoxLayout() - - self.task_input = QLineEdit() - self.task_input.setPlaceholderText("Describe your automation task here...") - - self.done_btn = QPushButton("Generate & Execute") - self.log_box = QTextEdit() - self.log_box.setReadOnly(True) - - self.progress_bar = QProgressBar() - self.progress_bar.setRange(0, 100) - self.progress_bar.setValue(0) - - layout.addWidget(QLabel("Task Description:")) - layout.addWidget(self.task_input) - layout.addWidget(self.done_btn) - layout.addWidget(QLabel("Logs:")) - layout.addWidget(self.log_box) - layout.addWidget(self.progress_bar) - - self.setLayout(layout) - - self.done_btn.clicked.connect(self.start_task) - - def start_task(self): - task_text = self.task_input.text().strip() - if not task_text: - self.log_box.append("Please enter a task description") - return - - self.log_box.append("Starting task...") - self.progress_bar.setValue(10) - - self.worker = CodeWorker(task_text) - self.worker.log.connect(self.log_box.append) - self.worker.finished.connect(self.task_finished) - self.worker.start() - - def task_finished(self, msg): - self.log_box.append(msg) - self.progress_bar.setValue(100) - -# ======================= -# MAIN -# ======================= -if __name__ == "__main__": - app = QApplication(sys.argv) - window = AutoCodeApp() - window.show() - base_dir = os.path.dirname(sys.argv[0]) - for filename in ["coder0-generator.json", "safety_checker.json"]: - file_path = os.path.join(base_dir, filename) - if os.path.exists(file_path): - try: - os.remove(file_path) - except Exception as e: - pass - - sys.exit(app.exec()) diff --git a/Desktop_App/__pycache__/mcp_link.cpython-312.pyc b/Desktop_App/__pycache__/mcp_link.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b22e271c15849259cb2f9e83d3edecfad090b8f GIT binary patch literal 10747 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 (display label, [(field_key, field_label, is_secret), ...]) +# "model" is handled separately (every provider except a couple takes a model/model_id name). +LLM_PROVIDERS = { + "npmai": {"label": "๐ŸŒ NPMAI (cloud Ollama โ€” default)", "needs_key": False, "model_label": "Model", "model_default": "llama3.2:3b", "extra_fields": []}, + "local": {"label": "๐Ÿ’ป Local Ollama", "needs_key": False, "model_label": "Model", "model_default": "llama3.2:3b", "extra_fields": []}, + "openai": {"label": "๐ŸŸข OpenAI", "needs_key": True, "model_label": "Model", "model_default": "gpt-4o", "extra_fields": []}, + "anthropic": {"label": "๐ŸŸฃ Anthropic", "needs_key": True, "model_label": "Model", "model_default": "claude-sonnet-4-6", "extra_fields": []}, + "gemini": {"label": "๐Ÿ”ต Google Gemini", "needs_key": True, "model_label": "Model", "model_default": "gemini-2.0-flash", "extra_fields": []}, + "groq": {"label": "โšก Groq", "needs_key": True, "model_label": "Model", "model_default": "llama-3.3-70b-versatile", "extra_fields": []}, + "mistral": {"label": "๐ŸŒฌ Mistral", "needs_key": True, "model_label": "Model", "model_default": "mistral-large-latest", "extra_fields": []}, + "cohere": {"label": "๐Ÿ”ถ Cohere", "needs_key": True, "model_label": "Model", "model_default": "command-r-plus", "extra_fields": []}, + "azure": {"label": "๐Ÿ”ท Azure OpenAI", "needs_key": True, "model_label": "Deployment", "model_default": "", "extra_fields": [("endpoint","Endpoint URL"),("api_version","API version (default 2024-08-01-preview)")]}, + "bedrock": {"label": "๐ŸŸ  AWS Bedrock", "needs_key": False, "model_label": "Model ID", "model_default": "anthropic.claude-3-sonnet-20240229-v1:0", "extra_fields": [("region","AWS region (default us-east-1)")]}, + "hf": {"label": "๐Ÿค— HuggingFace", "needs_key": True, "model_label": "Model", "model_default": "meta-llama/Llama-3.1-8B-Instruct", "extra_fields": []}, + "llamacpp": {"label": "๐Ÿฆ™ llama.cpp (local server)", "needs_key": False, "model_label": "Base URL", "model_default": "http://localhost:8080", "extra_fields": []}, +} + +# The 6 AgentBrain roles/stages that can each use a different LLM +AGENT_STAGES = [ + ("planner", "๐Ÿงญ Planner", "Breaks the task into atomic steps"), + ("tool_manager", "๐Ÿงฐ Tool Manager", "Selects which of the 1371 tools to use"), + ("coder", "๐Ÿ‘จโ€๐Ÿ’ป Coder", "Writes the Python for each step"), + ("auditor", "๐Ÿ›ก Auditor", "Reviews code for safety before execution"), + ("verifier", "โœ… Verifier", "Confirms a step actually completed"), + ("chatter", "๐Ÿ’ฌ Chatter", "Handles plain conversation (non-task replies)"), +] + +_LLM_CONFIG_PATH = Path.home() / ".npmai_agent" / "llm_roles.json" + + +def _load_llm_stage_config() -> dict: + """Returns {stage: {'provider':..., 'model':...}} โ€” falls back to npmai defaults.""" + cfg = {} + if _LLM_CONFIG_PATH.exists(): + try: + cfg = json.loads(_LLM_CONFIG_PATH.read_text()) + except Exception: + cfg = {} + for stage_key, _, _ in AGENT_STAGES: + if stage_key not in cfg: + cfg[stage_key] = {"provider": "npmai", "model": LLM_PROVIDERS["npmai"]["model_default"]} + return cfg + + +def _save_llm_stage_config(cfg: dict): + _LLM_CONFIG_PATH.parent.mkdir(exist_ok=True) + _LLM_CONFIG_PATH.write_text(json.dumps(cfg, indent=2)) + + +def _credstore_delete(name: str): + """CredStore has no delete() โ€” this mirrors its own encryption logic to remove one group.""" + from cryptography.fernet import Fernet + if not CredStore._PATH.exists(): + return + try: + f = Fernet(CredStore._key()) + store = json.loads(f.decrypt(CredStore._PATH.read_bytes())) + if name in store: + del store[name] + CredStore._PATH.write_bytes(f.encrypt(json.dumps(store).encode())) + except Exception: + pass + + +def _build_llm_backend(provider: str, model: str): + """Mirrors npmai_agents.cli.build_backend โ€” constructs a real LLMBackend from CredStore creds.""" + from npmai_agents import (Ollama_Local, OpenAIBackend, AnthropicBackend, GeminiBackend, + GroqBackend, MistralBackend, CohereBackend, AzureOpenAIBackend, + BedrockBackend, HuggingFaceBackend, LlamaCppBackend) + from npmai import Ollama + p = provider.lower() + if p == "npmai": return Ollama(model=model) + if p == "local": return Ollama_Local(model=model) + if p == "openai": return OpenAIBackend(model=model, api_key=CredStore.load("openai").get("api_key","")) + if p == "anthropic": return AnthropicBackend(model=model, api_key=CredStore.load("anthropic").get("api_key","")) + if p == "gemini": return GeminiBackend(model=model, api_key=CredStore.load("gemini").get("api_key","")) + if p == "groq": return GroqBackend(model=model, api_key=CredStore.load("groq").get("api_key","")) + if p == "mistral": return MistralBackend(model=model, api_key=CredStore.load("mistral").get("api_key","")) + if p == "cohere": return CohereBackend(model=model, api_key=CredStore.load("cohere").get("api_key","")) + if p == "azure": + c = CredStore.load("azure") + return AzureOpenAIBackend(api_key=c.get("api_key",""), endpoint=c.get("endpoint",""), + deployment=model, api_version=c.get("api_version","2024-08-01-preview")) + if p == "bedrock": + c = CredStore.load("bedrock") + return BedrockBackend(model_id=model, region=c.get("region","us-east-1")) + if p == "hf": return HuggingFaceBackend(model=model, api_key=CredStore.load("hf").get("api_key","")) + if p == "llamacpp": return LlamaCppBackend(base_url=model or "http://localhost:8080") + return Ollama(model=model) # unknown provider -> safe fallback + + _TASK_KEYWORDS = [ "file", "folder", "git", "github", "gitlab", "docker", "kubernetes", "k8s", "terraform", "aws", "s3", "lambda", "cloudflare", "vercel", "netlify", "railway", @@ -77,10 +169,25 @@ def kill(self): self._brain.executor.kill() def run(self): + stage_cfg = _load_llm_stage_config() + backends = {} + for stage_key, _, _ in AGENT_STAGES: + s = stage_cfg.get(stage_key, {"provider": "npmai", "model": LLM_PROVIDERS["npmai"]["model_default"]}) + try: + backends[stage_key] = _build_llm_backend(s.get("provider","npmai"), s.get("model","")) + except Exception as e: + self.log_sig.emit(f'LLM config error for {stage_key} ({s.get("provider")}): {e} โ€” falling back to default.') + backends[stage_key] = None self._brain = AgentBrain( log_cb = self.log_sig.emit, progress_cb = self.progress_sig.emit, status_cb = self.status_sig.emit, + planner = backends.get("planner"), + tool_manager = backends.get("tool_manager"), + coder = backends.get("coder"), + auditor = backends.get("auditor"), + verifier = backends.get("verifier"), + chatter = backends.get("chatter"), ) if _looks_like_task(self.task): ok = self._brain.run_task(self.task, killed_flag=self._killed) @@ -384,6 +491,7 @@ def __init__(self, link_mgr, parent=None): super().__init__(parent) self._mgr = link_mgr self.setWindowTitle("Log in โ€” MCP Link") + self.setWindowIcon(QIcon("npmai.png")) self.setFixedSize(360, 260) self.setStyleSheet(f"QDialog{{background:{P['void']};}}") lay = QVBoxLayout(self); lay.setContentsMargins(28,24,28,24); lay.setSpacing(12) @@ -442,7 +550,7 @@ def _build(self): lw=QWidget(); lw.setAttribute(Qt.WA_TranslucentBackground); lw.setFixedHeight(86) ll=QVBoxLayout(lw); ll.setContentsMargins(18,20,18,10); ll.setSpacing(3) - lg=QLabel("โš™ NPM Agent"); lg.setFont(QFont("Segoe UI",14,QFont.Bold)) + lg=QLabel("โš™ NPM-AutoCode-AI"); lg.setFont(QFont("Segoe UI",14,QFont.Bold)) lg.setStyleSheet(f"color:{P['mint']};background:transparent;") lv=QLabel("v4.0 ยท npmai_agents"); lv.setFont(QFont("Segoe UI",9)) lv.setStyleSheet(f"color:{P['dim']};background:transparent;") @@ -476,7 +584,7 @@ def _build(self): fl.addWidget(self._mcp_btn) for lbl,url in [("๐Ÿ PyPI","https://pypi.org/project/npmai"), - ("โญ GitHub","https://github.com/sonuramashishnpm")]: + ("โญ GitHub","https://github.com/npmaiecosystem")]: b2=QPushButton(lbl); b2.setCursor(Qt.PointingHandCursor); b2.setFixedHeight(32) b2.setStyleSheet(self._btn_style()) import webbrowser @@ -535,6 +643,137 @@ def paintEvent(self,_): p.setPen(QPen(QColor(P["ghost"]),1)) p.drawLine(self.width()-1,0,self.width()-1,self.height()); p.end() +class LLMConfigDialog(QDialog): + """'Configure LLMs' โ€” Part โ‘  sets up credentials/args per provider (only the + fields that provider's backend class actually needs). Part โ‘ก assigns a + provider+model to each of the 6 AgentBrain stages. Saved locally so the + Agent tab uses it automatically on every run without repeating setup.""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("Configure LLMs") + self.setWindowIcon(QIcon("npmai.png")) + self.resize(640, 720) + self.setStyleSheet(f"QDialog{{background:{P['void']};}}") + + outer=QVBoxLayout(self); outer.setContentsMargins(0,0,0,0) + scroll=QScrollArea(); scroll.setWidgetResizable(True) + scroll.setStyleSheet("QScrollArea{border:none;background:transparent;}") + page=QWidget(); page.setAttribute(Qt.WA_TranslucentBackground) + lay=QVBoxLayout(page); lay.setContentsMargins(26,22,26,22); lay.setSpacing(16) + + t=QLabel("๐Ÿค– Configure LLMs"); t.setFont(QFont("Segoe UI",17,QFont.Bold)) + t.setStyleSheet(f"color:{P['mint']};background:transparent;"); lay.addWidget(t) + sub=QLabel("โ‘  Add credentials for any provider you plan to use โ€” only the fields that provider " + "actually needs are shown. โ‘ก Then assign which provider+model handles each of the 6 " + "pipeline stages. Everything is saved locally and reused automatically on every run.") + sub.setFont(QFont("Segoe UI",9)); sub.setWordWrap(True) + sub.setStyleSheet(f"color:{P['mid']};background:transparent;"); lay.addWidget(sub) + + h1=QLabel("โ‘  Provider Credentials"); h1.setFont(QFont("Segoe UI",13,QFont.Bold)) + h1.setStyleSheet(f"color:{P['bright']};background:transparent;"); lay.addWidget(h1) + + self._provider_fields={} + for pkey,meta in LLM_PROVIDERS.items(): + card=GlowCard(accent=P["cyan"],radius=16,alpha=200) + cl=QVBoxLayout(card); cl.setContentsMargins(22,16,22,16); cl.setSpacing(8) + hl=QLabel(meta["label"]); hl.setFont(QFont("Segoe UI",12,QFont.Bold)) + hl.setStyleSheet(f"color:{P['bright']};background:transparent;"); cl.addWidget(hl) + existing=CredStore.load(pkey) + fields={} + if meta["needs_key"]: + lbl=QLabel("API key"); lbl.setFont(QFont("Segoe UI",9)) + lbl.setStyleSheet(f"color:{P['mid']};background:transparent;"); cl.addWidget(lbl) + api_in=GlowInput(f"{pkey} API key"); api_in.setEchoMode(QLineEdit.Password) + if existing.get("api_key"): api_in.setText("โ—"*8) + cl.addWidget(api_in); fields["api_key"]=api_in + for fkey,flabel in meta["extra_fields"]: + lbl=QLabel(flabel); lbl.setFont(QFont("Segoe UI",9)) + lbl.setStyleSheet(f"color:{P['mid']};background:transparent;"); cl.addWidget(lbl) + inp=GlowInput(flabel) + if existing.get(fkey): inp.setText(existing.get(fkey)) + cl.addWidget(inp); fields[fkey]=inp + if not fields: + nolbl=QLabel("No credentials needed โ€” runs without an API key.") + nolbl.setFont(QFont("Segoe UI",9)); nolbl.setStyleSheet(f"color:{P['dim']};background:transparent;") + cl.addWidget(nolbl) + else: + save_btn=PulseBtn(f"๐Ÿ’พ Save {pkey}",P["cyan"],True); save_btn.setFixedHeight(36) + def _save(pk=pkey, f=fields): + data={} + for k,w in f.items(): + v=w.text().strip() + if v and v!="โ—"*8: data[k]=v + if data: + ex=CredStore.load(pk); ex.update(data); CredStore.save(pk,ex) + QMessageBox.information(self,"Saved",f"'{pk}' configuration saved.") + save_btn.clicked.connect(_save); cl.addWidget(save_btn) + lay.addWidget(card) + self._provider_fields[pkey]=fields + + sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") + lay.addWidget(sep) + + h2=QLabel("โ‘ก Assign Provider + Model per Stage"); h2.setFont(QFont("Segoe UI",13,QFont.Bold)) + h2.setStyleSheet(f"color:{P['bright']};background:transparent;"); lay.addWidget(h2) + + stage_cfg=_load_llm_stage_config() + self._stage_combo={}; self._stage_model={} + for skey,slabel,sdesc in AGENT_STAGES: + card=GlowCard(accent=P["violet"],radius=16,alpha=200) + cl=QVBoxLayout(card); cl.setContentsMargins(22,16,22,16); cl.setSpacing(8) + hl=QLabel(slabel); hl.setFont(QFont("Segoe UI",12,QFont.Bold)) + hl.setStyleSheet(f"color:{P['bright']};background:transparent;"); cl.addWidget(hl) + dl=QLabel(sdesc); dl.setFont(QFont("Segoe UI",9)) + dl.setStyleSheet(f"color:{P['mid']};background:transparent;"); cl.addWidget(dl) + combo=QComboBox() + for pkey,meta in LLM_PROVIDERS.items(): + combo.addItem(meta["label"], pkey) + cur=stage_cfg.get(skey,{}).get("provider","npmai") + idx=combo.findData(cur) + if idx>=0: combo.setCurrentIndex(idx) + combo.setStyleSheet(f"QComboBox{{background:rgba(255,255,255,8);border:1px solid {P['ghost']};" + f"border-radius:8px;color:{P['bright']};padding:6px 10px;}}") + model_in=GlowInput("model name") + saved_model=stage_cfg.get(skey,{}).get("model","") + model_in.setText(saved_model or LLM_PROVIDERS[cur]["model_default"]) + def _provider_changed(i, combo=combo, model_in=model_in): + pkey=combo.itemData(i) + if not model_in.text().strip(): + model_in.setText(LLM_PROVIDERS[pkey]["model_default"]) + combo.currentIndexChanged.connect(_provider_changed) + cl.addWidget(combo); cl.addWidget(model_in) + lay.addWidget(card) + self._stage_combo[skey]=combo; self._stage_model[skey]=model_in + + save_all_btn=PulseBtn("๐Ÿ’พ Save Stage Assignments",P["mint"],True); save_all_btn.setFixedHeight(44) + save_all_btn.clicked.connect(self._save_all_stages) + lay.addWidget(save_all_btn) + + close_btn=GhostBtn("Close",P["rose"]); close_btn.clicked.connect(self.accept) + lay.addWidget(close_btn) + + lay.addStretch(); scroll.setWidget(page); outer.addWidget(scroll) + + def _save_all_stages(self): + cfg=_load_llm_stage_config() + missing=[] + for skey,slabel,_ in AGENT_STAGES: + combo=self._stage_combo[skey]; pkey=combo.currentData() + model_val=self._stage_model[skey].text().strip() or LLM_PROVIDERS[pkey]["model_default"] + meta=LLM_PROVIDERS[pkey] + if meta["needs_key"] and not CredStore.load(pkey).get("api_key"): + missing.append(f"{slabel} โ†’ {meta['label']}") + cfg[skey]={"provider":pkey,"model":model_val} + _save_llm_stage_config(cfg) + if missing: + QMessageBox.warning(self,"Missing API keys", + "Stage assignments saved, but these still need an API key added in section โ‘  above " + "before they'll actually run:\n\n" + "\n".join(f"- {m}" for m in missing)) + else: + QMessageBox.information(self,"Saved","LLM configuration saved โ€” used automatically on every run.") + + class AgentPage(QWidget): def __init__(self,parent=None): super().__init__(parent) @@ -546,18 +785,22 @@ def _build(self): outer=QVBoxLayout(self); outer.setContentsMargins(0,0,0,0) topbar=QWidget(); topbar.setAttribute(Qt.WA_TranslucentBackground); topbar.setFixedHeight(64) tbl=QHBoxLayout(topbar); tbl.setContentsMargins(36,12,36,12); tbl.setSpacing(12) - title=QLabel("NPM Agent"); title.setFont(QFont("Segoe UI",18,QFont.Bold)) + title=QLabel("NPM-AutoCode-AI"); title.setFont(QFont("Segoe UI",18,QFont.Bold)) title.setStyleSheet(f"color:{P['mint']};background:transparent;") sub=QLabel("Runs fully locally ยท no login required"); sub.setFont(QFont("Segoe UI",10)) sub.setStyleSheet(f"color:{P['dim']};background:transparent;") tc=QVBoxLayout(); tc.setSpacing(0); tc.addWidget(title); tc.addWidget(sub) tbl.addLayout(tc); tbl.addStretch() - for badge,col in [("โšก 100 Tools",P["mint"]),("๐Ÿ”’ Local-first",P["violet"]),("๐Ÿค– Agentic",P["cyan"])]: + for badge,col in [("โšก 1371 Tools",P["mint"]),("๐Ÿ”’ Local-first",P["violet"]),("๐Ÿค– Agentic",P["cyan"])]: b=QLabel(f" {badge} "); b.setFont(QFont("Segoe UI",9,QFont.Bold)) b.setStyleSheet(f"color:{col};background:rgba(42,255,160,12);border:1px solid rgba(42,255,160,35);border-radius:9px;padding:3px 8px;") tbl.addWidget(b) + llm_cfg_btn=GhostBtn("โš™ Configure LLMs",P["violet"]); llm_cfg_btn.setFixedHeight(30) + llm_cfg_btn.clicked.connect(self._open_llm_config) + tbl.addWidget(llm_cfg_btn) + sep=QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") outer.addWidget(topbar); outer.addWidget(sep) chat_scroll=QScrollArea(); chat_scroll.setWidgetResizable(True) @@ -620,6 +863,10 @@ def _build(self): sep2=QFrame(); sep2.setFixedHeight(1); sep2.setStyleSheet(f"background:{P['ghost']};border:none;") outer.addWidget(sep2); outer.addWidget(bottom) + def _open_llm_config(self): + dlg=LLMConfigDialog(self) + dlg.exec() + def _add_bubble(self, text:str, is_agent:bool): b=ChatBubble(text, is_agent) self._chat_layout.insertWidget(self._chat_layout.count()-1, b) @@ -783,6 +1030,81 @@ def _build(self): scroll.setWidget(page); outer.addWidget(scroll) +class CredKeyValueDialog(QDialog): + """Generic credential-group editor โ€” user names the group (cred_key) and adds + any number of key/value pairs. Used by '+ Add Credential Group' in Settings + and by the per-provider forms inside Configure LLMs on the Agent page.""" + + def __init__(self, parent=None, group_name="", existing_data=None, lock_name=False, + title="๐Ÿ”‘ Credential Group", subtitle=None): + super().__init__(parent) + self.setWindowTitle(title.replace("๐Ÿ”‘","").strip() or "Credential Group") + self.setWindowIcon("npmai.png") + self.setMinimumWidth(440) + self.setStyleSheet(f"QDialog{{background:{P['void']};}}") + self._row_widgets = [] + + lay = QVBoxLayout(self); lay.setContentsMargins(26,22,26,22); lay.setSpacing(12) + + t = QLabel(title); t.setFont(QFont("Segoe UI",15,QFont.Bold)) + t.setStyleSheet(f"color:{P['mint']};background:transparent;"); lay.addWidget(t) + + sub = QLabel(subtitle or ("Give this group a name (e.g. 'twilio', 'mailchimp') โ€” check Docs for " + "the exact keys a tool expects โ€” then add each key/value pair below.")) + sub.setFont(QFont("Segoe UI",9)); sub.setWordWrap(True) + sub.setStyleSheet(f"color:{P['mid']};background:transparent;"); lay.addWidget(sub) + + self._name_input = GlowInput("Group name, e.g. twilio") + if group_name: self._name_input.setText(group_name) + self._name_input.setEnabled(not lock_name) + lay.addWidget(self._name_input) + + sep = QFrame(); sep.setFixedHeight(1); sep.setStyleSheet(f"background:{P['ghost']};border:none;") + lay.addWidget(sep) + + self._rows_container = QWidget(); self._rows_container.setAttribute(Qt.WA_TranslucentBackground) + self._rows_layout = QVBoxLayout(self._rows_container) + self._rows_layout.setContentsMargins(0,0,0,0); self._rows_layout.setSpacing(8) + lay.addWidget(self._rows_container) + + add_row_btn = GhostBtn("+ Add Key", P["cyan"]) + add_row_btn.clicked.connect(lambda: self._add_row()) + lay.addWidget(add_row_btn) + + existing_data = existing_data or {} + if existing_data: + for k, v in existing_data.items(): self._add_row(k, v) + else: + self._add_row() + + btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) + btns.accepted.connect(self.accept); btns.rejected.connect(self.reject) + lay.addWidget(btns) + + def _add_row(self, key="", value=""): + row = QWidget(); row.setAttribute(Qt.WA_TranslucentBackground) + rl = QHBoxLayout(row); rl.setContentsMargins(0,0,0,0); rl.setSpacing(8) + key_in = GlowInput("key name, e.g. token"); key_in.setText(key) + val_in = GlowInput("value"); val_in.setText(value) + rm_btn = GhostBtn("โœ•", P["rose"]); rm_btn.setFixedWidth(36) + def _remove(): + self._rows_layout.removeWidget(row); row.deleteLater() + self._row_widgets[:] = [r for r in self._row_widgets if r[2] is not row] + rm_btn.clicked.connect(_remove) + rl.addWidget(key_in,1); rl.addWidget(val_in,2); rl.addWidget(rm_btn) + self._rows_layout.addWidget(row) + self._row_widgets.append((key_in, val_in, row)) + + def get_data(self): + """Returns (group_name:str, data:dict) โ€” rows with an empty key are skipped.""" + name = self._name_input.text().strip() + data = {} + for key_in, val_in, _ in self._row_widgets: + k = key_in.text().strip(); v = val_in.text().strip() + if k: data[k] = v + return name, data + + class SettingsPage(QWidget): def __init__(self,parent=None): super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() @@ -873,12 +1195,28 @@ def _scan(): ("region","Region","us-east-1",False), ],"aws",P["sky"]) - note=QLabel("More tools follow the same pattern โ€” CredStore.save('', {...}). " - "Check each tool's `use` docstring (visible to the agent's Coder role) for its " - "exact cred_key and field names before wiring up a new section here.") + note=QLabel("Need a tool that isn't listed above (Twilio, GitLab, Stripe alternatives, Mailchimp, " + "Notion, Zoom, Cloudflare, and 30+ more)? Use '+ Add Credential Group' below โ€” see the " + "Docs tab for the exact cred_key and field names each tool expects.") note.setFont(QFont("Segoe UI",9)); note.setWordWrap(True) note.setStyleSheet(f"color:{P['dim']};background:transparent;"); lay.addWidget(note) + # โ”€โ”€ Generic custom credential groups โ€” for any tool not hardcoded above โ”€โ”€ + custom_hdr=QHBoxLayout(); custom_hdr.setSpacing(12) + custom_t=QLabel("๐Ÿ—‚ Custom Credential Groups"); custom_t.setFont(QFont("Segoe UI",13,QFont.Bold)) + custom_t.setStyleSheet(f"color:{P['bright']};background:transparent;") + custom_hdr.addWidget(custom_t); custom_hdr.addStretch() + add_group_btn=PulseBtn("โž• Add Credential Group",P["violet"],True); add_group_btn.setFixedHeight(38) + add_group_btn.clicked.connect(self._open_add_dialog) + custom_hdr.addWidget(add_group_btn) + lay.addLayout(custom_hdr) + + self._custom_creds_container=QWidget(); self._custom_creds_container.setAttribute(Qt.WA_TranslucentBackground) + self._custom_creds_layout=QVBoxLayout(self._custom_creds_container) + self._custom_creds_layout.setContentsMargins(0,0,0,0); self._custom_creds_layout.setSpacing(10) + lay.addWidget(self._custom_creds_container) + self._refresh_custom_creds() + mcp_card=GlowCard(accent=P["mint"],radius=16,alpha=195) ml=QVBoxLayout(mcp_card); ml.setContentsMargins(24,18,24,18); ml.setSpacing(8) mt=QLabel("๐Ÿ”— MCP Link"); mt.setFont(QFont("Segoe UI",13,QFont.Bold)) @@ -894,6 +1232,65 @@ def _scan(): lay.addWidget(mcp_card) lay.addStretch(); scroll.setWidget(page); outer.addWidget(scroll) + _HARDCODED_KEYS = {"github","smtp","notion","stripe","aws"} + + def _refresh_custom_creds(self): + while self._custom_creds_layout.count(): + item=self._custom_creds_layout.takeAt(0) + w=item.widget() + if w: w.deleteLater() + hidden=self._HARDCODED_KEYS | set(LLM_PROVIDERS.keys()) + try: names=[n for n in CredStore.all_keys() if n not in hidden] + except Exception: names=[] + if not names: + empty=QLabel("No custom credential groups yet โ€” click 'โž• Add Credential Group' above.") + empty.setFont(QFont("Segoe UI",9)); empty.setStyleSheet(f"color:{P['dim']};background:transparent;") + self._custom_creds_layout.addWidget(empty); return + for name in names: + data=CredStore.load(name) + card=GlowCard(accent=P["sky"],radius=14,alpha=195) + cl=QHBoxLayout(card); cl.setContentsMargins(20,14,20,14); cl.setSpacing(12) + info=QVBoxLayout(); info.setSpacing(2) + nl=QLabel(f"๐Ÿ”‘ {name}"); nl.setFont(QFont("Segoe UI",11,QFont.Bold)) + nl.setStyleSheet(f"color:{P['bright']};background:transparent;") + kl=QLabel(", ".join(data.keys()) if data else "no keys saved") + kl.setFont(QFont("Segoe UI",9)); kl.setStyleSheet(f"color:{P['mid']};background:transparent;") + info.addWidget(nl); info.addWidget(kl) + cl.addLayout(info); cl.addStretch() + edit_btn=GhostBtn("Edit",P["cyan"]); edit_btn.setFixedWidth(70) + edit_btn.clicked.connect(lambda _,n=name: self._open_edit_dialog(n)) + del_btn=GhostBtn("Delete",P["rose"]); del_btn.setFixedWidth(70) + del_btn.clicked.connect(lambda _,n=name: self._delete_group(n)) + cl.addWidget(edit_btn); cl.addWidget(del_btn) + self._custom_creds_layout.addWidget(card) + + def _open_add_dialog(self): + dlg=CredKeyValueDialog(self) + if dlg.exec(): + name,data=dlg.get_data() + if not name: + QMessageBox.warning(self,"Missing name","Please enter a group name."); return + if not data: + QMessageBox.warning(self,"No keys","Add at least one key/value pair."); return + existing=CredStore.load(name); existing.update(data); CredStore.save(name,existing) + QMessageBox.information(self,"Saved",f"'{name}' credentials saved securely.") + self._refresh_custom_creds() + + def _open_edit_dialog(self, name): + data=CredStore.load(name) + dlg=CredKeyValueDialog(self, group_name=name, existing_data=data, lock_name=True, + title=f"๐Ÿ”‘ Edit '{name}'") + if dlg.exec(): + _,new_data=dlg.get_data(); CredStore.save(name,new_data) + QMessageBox.information(self,"Updated",f"'{name}' credentials updated.") + self._refresh_custom_creds() + + def _delete_group(self, name): + reply=QMessageBox.question(self,"Delete",f"Delete all credentials saved under '{name}'?", + QMessageBox.Yes | QMessageBox.No) + if reply==QMessageBox.Yes: + _credstore_delete(name); self._refresh_custom_creds() + class FounderPage(QWidget): def __init__(self,parent=None): @@ -989,6 +1386,31 @@ def _build(self): ("Everything else stays local","Chat, tasks, and credentials never touch this โ€” login is scoped " "to this one feature only."), ]) + self._sec(lay,"๐Ÿ—‚","Credential Key Reference (for '+ Add Credential Group')",P["sky"],[ + ("github","{ token }"), + ("gitlab","{ token, url }"), + ("stripe","{ secret_key }"), + ("razorpay","{ key_id, key_secret }"), + ("shopify","{ store, access_token }"), + ("mailchimp","{ api_key, server_prefix }"), + ("aws","{ access_key_id, secret_access_key, region }"), + ("cloudflare","{ token } or { api_key, email }"), + ("vercel","{ token }"), + ("netlify","{ token }"), + ("railway","{ token }"), + ("twilio","{ account_sid, auth_token, from_number, whatsapp_from, verify_service_sid }"), + ("sendgrid","{ api_key }"), + ("zoom","{ account_id, client_id, client_secret }"), + ("smtp / gmail","{ email, password, smtp_host, smtp_port }"), + ("notion","{ token }"), + ("linear / asana / clickup / todoist / trello","{ token } (trello also needs { key } )"), + ("figma / canva / elevenlabs / stability","{ token } or { api_key }"), + ("googlemaps / openweather / virustotal / shodan / hibp","{ api_key }"), + ("google_analytics / google_calendar / google","{ ...service account or OAuth JSON... }"), + ("postgres / mysql / mongodb / redis","{ host, port, user, password, database } (varies)"), + ("ssh","{ host, user, key_path or password }"), + ("docker","usually no creds needed for local Docker"), + ]) self._sec(lay,"๐Ÿ”’","Security",P["rose"],[ ("Dual-role audit","Every script is reviewed by a separate Auditor role before execution."), ("Subprocess isolation","Code runs as a child process โ€” a broken script cannot crash the app."), @@ -1001,7 +1423,8 @@ def _build(self): class AppWindow(QWidget): def __init__(self): super().__init__() - self.setWindowTitle("NPM Agent โ€” NPMAI Ecosystem v4.0") + self.setWindowTitle("NPM-AutoCode-AI โ€” NPMAI Ecosystem v3.0") + self.setWindowIcon(QIcon("npmai.png")) self.resize(1220,780); self.setMinimumSize(920,640) self._build() @@ -1055,4 +1478,4 @@ def closeEvent(self,e): pal.setColor(QPalette.Highlight, QColor(P["mint"])) pal.setColor(QPalette.HighlightedText, QColor("#050310")) app.setPalette(pal) - win=AppWindow(); win.show(); sys.exit(app.exec()) + win=AppWindow(); win.show(); sys.exit(app.exec()) \ No newline at end of file diff --git a/Desktop_App/app_config.json b/Desktop_App/app_config.json new file mode 100644 index 0000000..3ca7ba3 --- /dev/null +++ b/Desktop_App/app_config.json @@ -0,0 +1,5 @@ +{ + "SUPABASE_URL": "https://qyxuvuhhaspkuhbognyk.supabase.co", + "SUPABASE_ANON_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "MCP_BASE_URL": "https://your-mcp-server.hf.space/mcp" +} \ No newline at end of file diff --git a/Desktop_App/mcp_link.py b/Desktop_App/mcp_link.py index 1ed0b18..2a5be2d 100644 --- a/Desktop_App/mcp_link.py +++ b/Desktop_App/mcp_link.py @@ -3,23 +3,23 @@ import uuid import threading import time +import requests from pathlib import Path from typing import Callable, Optional CONFIG_PATH = Path.home() / ".npmai_agent" / "supabase_config.json" +CONFIG_URL = "https://raw.githubusercontent.com/yourusername/yourrepo/main/app_config.json" + def load_config() -> dict: - if CONFIG_PATH.exists(): - try: - return json.loads(CONFIG_PATH.read_text()) - except Exception: - pass - return { - "url": os.environ.get("SUPABASE_URL", ""), - "anon_key": os.environ.get("SUPABASE_ANON_KEY", ""), - "mcp_base_url": os.environ.get("NPMAI_MCP_BASE_URL", "https://YOUR-HF-SPACE.hf.space/mcp"), - } + """Fetch latest config from GitHub""" + try: + response = requests.get(CONFIG_URL, timeout=8) + if response.status_code == 200: + return response.json() + except Exception as e: + print(f"Failed to fetch remote config: {e}") def save_config(url: str, anon_key: str, mcp_base_url: str = None): diff --git a/Desktop_App/npmai.png b/Desktop_App/npmai.png new file mode 100644 index 0000000000000000000000000000000000000000..ec2e599f1928b0150ff8b94757bc52543bd9d1a1 GIT binary patch literal 106287 zcmZU)by$?`^FB;>qrd{vA<`k;-Kikb4bt6>E8Qg^EunNvOLt56k^<7bz;9O{{qlL= zKRgb+k9%k4JZI*dxvqsURb?4WG-5Ou7#K`BSxI#m7`OoFKNKX$6O7@0Y8V(C7&%F? zS01qYEy&rlQb`XFr9T}e74Z9W%RHBUDEad|xGm}FIcm+2%7$kL!HUt!Njtp~i%%7e zGZe=NKtpimd(HR0q%QJ=%oW+Nm9;HByyFr5`be^P~05r>;MgFMb0LYRW_ z;fQIy(EInEhdQ1r{E-~rzGvZi*`L=TRs{J_i(s!dNZk)=A)kjn@IA!&56T(F6%ehhD-dGz&?gEt> zdu)if0mWp0g8gGMkWa3Cq+ioIF9{m7ykQywS32|un41*QSE#>{{O7=wI-5Q~r4AQ4 zTkrosXD&j~ImNVH=>I{NDTq*0C~p`K2{GaMRNg;u&jaa^VPC>zk%CHEUcvFfwlMz3 z83p7isu=5|nD+Y+rVrCyM~pP8{E+@X1TP?*AvoB@WYzt4SMd>n>*MdRnITqYs#LT7 z5civ?%qP&$)G?fw<^DN?Vn!)~eg5RtUu63Y2sIY+hVXFjVC(`U;r~8{Sg0_;SA}=z zB{Mkqgx80<^EZ2<*brCVhDBQcvZ)FufEO5bE``}16*uni7)UlGf<-Z8&8E*#9{w7s zpoCc9G0mm?m#6@|*&Imj8zivpm-!wCo1FsTv4v}@{68KIdnvPp)Zj>HA-R?ww+wj% z*G&-u;a4i?ON;trdQ*cS7Boc1DIUc&WCj;AR+JLOY%eadO#Lsgz;6noI%NoCP#7^0 zviN@+1?sJr3gRvLW#&8yZ7lv+)89`h;Xu6MJ8;QA^5z`YzE$@cvwfKM^X}se1w-FO z2FM-&p`WIp&;A`tI|vf+(J{aZ?NMa=s?c2I067mMm;aRd@3yUAfF?dXx`*L!3kx9B ze3mz?hNfe#h%4tY!x&J8i9kgys*f3-g+8`JqEX6yRP1w#t6_CYnC zjh)SH0EPD#x8W(&-E|4q=g00)pzd%wQ{BaX=i$EqXGx*%uxOS3_LkuV-dHk2AiW}l zdiqF3&c__vLEZgnmkorozf!Y`3gPVtwS6N0Q3a+dArveSyo4!KP(J-j1~g645UFyA z#>(KqXH0gc@aR8|*ns_rcwARdnR0fTJ(1q~+ul$WoTP+Ua4E9-CzJ~@0n;T2=1_(K)KhSeN|3D{ zA}74{JqkKs5uD(>{>H%u)sqpJO!L=|0;{8``24eKgwN%(P7ym&&&C9%#$yqIbOZ%N zVFh}4-?2hGcue4oMZbV1-CP;s_ao;N-ebRoP``yhM*EV-<;;c;^&678_1AC30g9!P zykP_`B#99@Q$deS@IX!Q^vLNHKMuVD5%RbJk>VZhqqNMXA=J6dO{aoaZaI4tlb7z? zVY@$Jyoz~Lu8HkfVxlTA?x99g%8)Eofbhr2eurO23dvnPsRFayW9Ff<1S^p2_A zM_4OpQEf`AAOi5#v{v_#Dh#PZ`I-nEH~(Goe=}&~3GFe@g15Pk0Ghvq;IKG9+K8I` zD^}3#)IpW7o<@mT;Xioo$<*1j41uN6kgSygHPQcXSSGgb(?D?SlM>n{{hJ05rES-w zfIQX=_#*Wf9vwoRn?A%ew-NJivG~1VsKQl4r+>ALXbm4UKY9jesHt?B-#$vJN=SR1{8yyaI;zt1$Bmi{mFZ-R5N-+R%l|}g51PL{&S~ODiDLT- zEy}MzYWx3{>6cJ}e7XB>?r%|YX9X0zf?I%+v4Bylng0m&92DLhJJvkfKVMMPwxW>7 zwlLaXA*%LU`=QJipxEs3LXf<4ELC}gWZ@&!t|v^57qnRZO+s54sEOXU*6NQ;lt7bL zLIiBa{n!3b%XQGlb=f7Zf0>x&g^f~!TgZS~CjF*a^eEy31&G~)K?7^8$D%h2gpMp= zMeU166?=v^tM3O1n9#HIv3M+mLCvBzYkq~A{kLfxKxsSpXdU$rF=W*!gPJY7Ivsel zdqU`e){3p1&fZ^a70CfNiS2GsHFc9HfBJZe-9i(3>oD^+=`S_EYXTlx6Ywsj>i?MZ zaKfKmZqb5w@P;Vp-zDaEGN4*&i=q2f1Ipyz_MP>EhUT|lYyJqa4I#8F3HLoe{|~i( z+*Ib4*YFN?ks)=ztM$LGTL^{ni&CTcd+Pq<_YTVMU9YIlf3HDl4z!hrVha~2SA0AL zXEUMLGS6!5{$TUqz#VIS5vVNzd0CtD$vn;R7*9F>&Ljw-tE0=s6 za4IGw;0(;XY}-E#6Zj0muoCJJKP0x1M#^!d$6LZ4l;ORT9~1vm5$3p1@fbI>)c8MV z&m!7w4x%6u=4x7__qZ(Oq5Q}j*1mxf@wcu5=b$Y&$3r(y^_ZX6P<~!Fd#?RYN!UO) zN*iKK+lW8>?8_q5u*w@wLt~r9isDv$%+CiXKOep(e|bFo*wCR{0y?r(`u_>akS$zw z%Q*z|1@6SfW6bDK{X&QTvHly}-y@AVD-`qLtmWW8%#dmL8ah;8S8ToipK(_m2MMCu z@l}n|A0pUNP%Rgd+Do9*u7sS*Hv3#q~qq&v-frFev zx8$vVz+^B|e?Ist@zDAqfbv5CSKR%(H~tR0AwOtB@7X5^9#0<#g^-P5eF*)3hMhSl z6x`{K<-#9uhAg;abuR+>phe8b`D$MEagqSgBmtt|hW}3ylR}G_l!(Um|7I&&6Y5ay zbTKUNjI`pyZvp$S;sDSffG{tA&)a|N{UUTdTLduL7C)v31%Eb4@b6s!lq!^-W}L@o zDMJ>hYOzGd*Qx!X3IfJ99WT`xTIS9Y1(=Tq^$@gAIQ`Oe{)cQV=o*EEnautA|H#%u z*m`FTvGA3;@ZhlpE@&kwIi&s0%fFfT3u@t4)W^Cv|17A&aYFMFd9?ZQe+2oPB!tv& zTyN(!|Bwn<&}*3G4M(7yj9`7@)_r7Q5~|W?4OT4wGx7Ljp=sf@oHzZK7Qrc0v?Q5pR(#_J)HMY zyHK7OHCzgqu>f-)Bka3c}fsDh~+*|E9W2GePpPY!Y|6Xgq7?T2MeU>-K&jgopj(iF&@4_V*=zDnL`Xg@)P7q0N6 z40g4nlqN0(ACg z_6H8rWcUAWaGh8f+ZF}|bU0pOpD1bggD%QySVbaAP|lJhqg29#UEvY3KhO%0Sw(@A zCzZ|`Nj=MkpmM$HZxm3W8}h~v#F!EW3O+CRzgYv3LOoQX>1AAEN4jvFlowoJxK|6J zNyuKz1r?4c8qYhP3|h|NxDv-vDVYz8Q=kVJnG@O56-~;P@T3(fy^0%44XNERsFB4K zb@J&*G5@0z&|#j~u87)a(3jeX5u7Nk*Fh~bADF#_OmDK<;m3&^=qH?pXm-iWy-r2VtC=p`F1RuZvv=~qp>r> zvHdsj8}@&)f)rNnXvv8C#|)VVdvx9i(`gz3>Skl!<$b0P*vL(JEM&~Enaj4#;=v)u zPABxnaDMlj^(z9ud|(PQ9EJ7ozs!J!B3OFq%dTJ8-Hfw3Z2MxbY599SslNxa=-$<) zJB$~K#9aFB&@_Wx?R(-1^+IT^d(_B^vU||9W_}9dlB&YTGC}g3t|F@OSMO)%5imr~ z3jjOW(+Fj)_XN$3hxI0hcgx}BD`&Pt3s&w^x{+TPvd!i7)5HL!6+xO)+qBGMmF}E41-Y4{@ zB@g@Q?+HYjZq+h8Mx=NTt5nu9Wm1fm%T&Z3f<9Gx)c}hGL$kXQn#B?6Sm4$ABd(Y` zzc6PfBZMLok8|^TmHgAk?c6l0vEv)o$IIfS`~zv- zFhieyfJ8qnf1nG{U~R!m@!h0j8l_S^GPQ3|qORl>4K5Gj;^bOqQbA?b{kd9&F-R-Q-c&&+Y)>NbJWhU8j3&Ced+HNMxawXAD z^M0{h!kh1G^mLg~YW4!JtWk3Zd@zL0zrAT~1SX8Msv75r{L}D1J7#;jZ77K9Z7G6M z=K5!hFWdpF{^v+Ow*vLz_Xn*NZ^WzT#G5`*IExPG5OrrqeNei~aAxbF2&c*_(fmq@ z0T*y`U#xtNB0zLjZt6uBs>4GA0)EETao0dwa5c$Wm) zD!uzXYFpD~4AGkk6#S^?Q1xNl+7|C#InWirLZb{aW~tl&*a)f4QKgo-5(O~_I3 z@6qA&4e+rQdfgE>AynD3nN6eWZ!!NUVb zo6B>FfB6;ms_UZ_)}OxdaKahWe-T(J6VOA7eSUQ}V-V}VOlua9x3;}uSz!Xw272Z= zS~B!xVVbjHWXXXb8y%|9@Z)8aUOu4b(`Y>GG>p^wjL0mu1+%)}{Y&nnai`NlKQ4k6 zTv^blhY@TujbCFf(yI7j`*ykqk)(LLE9 z=PkXftPCV1JQWkRU@2p`e0?U4goxAK?vgX@H*nKWdo99UnM^g{{*$WTU-3Ztgbxl# zkec&@(6mU4WG5y->MoeA6oHl`Z%NVPh2?mG+Dxuk%8@{2o)SHh@lZ)lH5XW%CVpDl zQUed4gGp4nb8LKrbI9803V}FTbjQi)HBRN&f8V?)iirmQ*&HHZN-Ik+$8btsg)htA z4sHu!4*_I$s7;D9&fg8QUm_K}uQ@i+lr9MznFKYEM2+3zmo`uQ}4ZM$gBYOcB403AO-ntDygYIom00wJQ3@G30E87AOuo{C0_OI9POS zxl;h!2mM{%*D*p8$+GUzI@>q)W3bUcUHQ&ZTPjm<5eY}LD2VkWo+4k2s3CHflh|KS z$R&C=An|2Bh8%wf9wp>{237jsH(EjnQ(qD4SYZ7o5HTCYzm3jlga$YvfG;B+uvC`J zDCsnrKf}&S_Gt(yNT>|8MS51b_tU8f1Zuy)5 zpn@T#o*efU9`_~8``GZuI4A>4KEZJfy%R)yLK1U9t_TM{RJg}1WA7PlRO;K+7lj`k zYj76)KIn0ha`J@j^PZ(>cqMXg6TJVo%I(w~uzmQV^zjecqI{?|vQ}N+7V|+Ct073);E(NH zx77K@FcV$QTufFE$9M(V?_a>zF+vEFWhTeXegSs;+>yqV;?+sHneQzdQKF`D514du;KepX z9_mPxh$9%PG@nVO8~M>nTAT6_Bw{R|C-a1d_09iXr?FsN|MWywz+Q33k>V&-o*G>| zNnrW%-iqT<;O*MV{o0|bPBm%Fl0P;QIubg;Sq@(-l5a44sHYc-H6=m50$z=dlAqF+ z6?w3mese&fA1c8`3Y%z?!*15SC}?!Oou>uuaN&}+1pV zNVT0wO&G}~OiXE$MO;{=Q-niW?hnY1!(uX`+z;=Zh=GuOGLol(>%SA)Z=4I60U)DH zS)ixy)0@C;Ay-^wfvrzheY^DQhYu&(&*?z3Ci=Li=%@pAf$?8+BZ)spi)%{sbFsaG z>BtYa_|=N?aHcid+q6>0IwHLL2E4E$h;AkHJs=Qr1Lx;ZEB&VuQ$HirXv-UF;;;## zl9K78`h7e1r>l6aa;)Nir??&4D*E>>z~??*!(s9~tGn`02*&@e=K87{k>HdKE>bY$ z;>#_rB2%a8Sl7rMtp1=%AGy#ejn75=>2dPR1Np_9H*g2yepuY0Z(85qKdMlOU(1hDG6A=$NVsr%gCsH`XNJvRVzwVoz@ zbr?($`CzZ>&M?CF3rEm8<`dg1)6Dlz$Hb=JWgBulv_)z_4!vKW2eQDr{{52D1{rRI z#Lwx%!sh^!*t%5nnOq)FGjAZF9b=PCqKy9CVi&ZStDv8qrnwz;Rd@!Y5u0x_KuiTfTm*< zbxEYFxr`CAF?4`Qh8Z1)_PD7GuOhvlyAG)q>_2S1w9$BJuaCMQ@Z&~kHuA!y+9hEm zz@Q$*v8g+pF^!VyzD$-l=^WK%S8+zqy-p-fecHa?TLK~3zCifuS%NKnp7l*JV zF`PD$!Is!od%~QPGw}s2QRasB9*)|2NffVjZpahF0u;`UckW=HI_>cLRy5=xBV!Lt zQHe1)q}jxZNTt$Iz-r3cxn4h=(Rw)B+*FFB+k1vC#%uMPLcB11?LsBRrjhC=Rcc0( z?%#8?PFzWk<>ihtm)rII?eh~SN$T1C^}n>0XLTXlmFv{A!?~DDC{dT?YC+FWBD7|cl zwq_iH8M4AQ_O!qA_}DCzadfZWbe>!8JalSXiyTR1Z=L80Tw)*0y=4@AOoGpbHSw#A z(T|m>eh-Zt7by(%d2-qg$D5h9gc^^2Z9UwjYKTMTEnO)M<8Jw3h9GA~ihsty!9_=J zf0QT9Lq6Nk?XDa8?y+07cRl_Ho}fsb=|EhfhunO6FW~`vW(f}^l_|HhQU|S{2^FWw z&0b;R3+A1tB6uyZd$)Jlu5foZAm@FU`b8|xXF4{eBo)2Mjkd)z1j$I09dI1@Fu>k` zI&4vZ1GvFjL2#V0oi`#Nr;`2odsvZkxEgHS$<;h;!{t^_bl6P#x6Abdv=TIk?s~`e z6nJw<=TuGKTkLqkcD)+QXnx-C%p7cKw^i6(z1o!3EtgGOwm4Qu?dt*K@ucK0U^~uO z{P0XJ+U=X}9ka^LdZp9Ky?Y8ud0{)7DCmb;&GY^&qVzPn?byh2EYG`% zwz#t;mv!ZWB;QW!4-)UA>~?tVLLCma>UqgmjqS zZe+tG{))Z%Qvxdewd-a%cRIaHB~M?G{uukjqYkM)m-i8IA=k%o>?pD0SqifLvjBWK zdb7|_f3&r$jtC|fq4mLfP732$tnv4TRm{=0x?1_*Vp<6mUP28;wC^4wdTMAADJdTG zpC{iT*}r;C6V??WARzUY9)s7sBgofnX<7D_ecqtSoGzYW z?&@CElcSbaV)YktDQbIHn{yGSJK@+K9Lop+JFP?A#gglijn0ybwQwrQCfj-pdtqqY zS4z?4Pip;2LOk`dWpoV=Z^GO%EG{P8r5B4Osotmbvb_!(MyUoUd@N)8TCVddn{jW3 z!K#2-UAcZpU#sKW+j8a!k5?f6~^r&HUs;M{f;+@X7uR)tFv zPNYROO`;8`x2TUsz`zE-8c8+i8h6!In|_KeV`3<8uo(hOB!A;Gd&h@C*MHwt#b*UZ2(w~pI<}2 zid9KkwxigT<9qb3cH5xdCjzbY{23152V?XCsIiJ$^F$#5%H{p;z>TZ&>*)NCFLZkh6=dz_ zNWLm`@X)rH4ZUo$lqkOsHRzD+Vy&udRWP7c*2O(8XH{1&m-W<(bC6^8tgi{RD={bt zDP@h9oy>x?xlBPdLCg+^cuw;E34~wi_7MP(Y|$cMN~^m?z_|-78N~7gB9%CN%qc>AkEh2!2VGJEzS76_7(T5*NX`?Xbi3h>vscE3#uFr(8 zHfLdr9~!%nkZu1PxL&m$H{#p!A#H++VErkRpY~cQ#Z&th^okt;?2k z_+ynuOJ5fff1R2)#G7%(Z_3eBqGa(aV@W?L*2-6E={R#z+KT&7Cflg2uV#v^nd#ke zXWYHre?M_JIiAE(7;kr6J$t~dbB|IVs`wmr&$Y=975ODCp^INMB|vI%SFSLd8N^F) zDjCv$x))~pQ*Pl0;dn$@XqijLDP~dU2P3^s@x?OAPctI!hrqj9lF(CD#9Yo?5h}g5 zvlcB;me_8Ckj(&N-ulMfnSKSu6Q;%&&|VR|8=M(sKX9ExvO=DXc9r^K^wzQ2+k&f2!qMbw)3)7n?|RGCA8n*@joI0>el3r!9ewG(bl#aN zv)<(DqKBH0pU~GuO%?b;B~_I0VXHCWsf``5c%pySP12=5rdUwntz%P%1;CwL& zTsP7zc!s51NbMP`MqjW0buUJD(v*58{Dd1?iYhzN_I!y*l7@h^;tRyPs==%6+U~Fz zcHvixKBqY29q027bR)1mg06@1zhC;0M{@R6x?I?T(X(CngrjS%F|GIJ z)(cELM6*3>4rPh^c$8Q3^EDArXWD|7koj)-ZIW~W+aa^Hgnv9upJ@hnB%F`;8=gH; z_lzp;hIY2v#3kJ6dQG09Q@@-(J%%9KD}A|l=DObO>eHdkWIY32X^Q<8hbd0HDYGQ9 z->Q1T25Xt|eV?=umYgQ&Qtctwbt4%uG7EvbnA$fYy_JQXh1M_K}A!vAO}6CxD&CX|AuLlRMwtfOmPI>1P47ONLEYhYb2h1qBnT=%^cK6hEap*DM%e_oe&)`rGZ z{#nolVrM><8ywXEw+1q7ka(c0yb#cJ)3_4_cduTo-HYyZqAOqiD_J$gp_44d<#t@8 z^6%es`jt2CjQv}3EC9<-Gg}&>Q5ds$Ou$pd;svyy^833b>B?+~ULcyZ0^c8WBm8z$@= z(^S!*k?>f@Y8##gv8s2n3(fg#IZ`+Z&lsxcK>fhE?W3gcP9|6-E?l5U*XIPrl4^az zW>03Q&u$btx+Yz*@;FBS-UZN>hYDWGBN-9NCLZNg+P)#=jZi>LOV8Da>14%_coSV2 z8ATM_60e4=!$qT~ps$`^YJ)#1eikTnEJcFu(la+c%u60Wl%2%O^%n4UUFCY{ynxv; z_FS4K05osb0k_n?_jW+gDt_Etz9G4Gd_67sh|fg7*T?pT-3X}dlm^Gqau9i3-6Dd&x9PNV8^X+`*=fTGxBk9+d>?N z45y|D4f z5=Kc0EYLSE@x@(*@nx)vxLyi-z=Puun@}a25Ii{Kj+98m5mth>kJk{09)wcQFUghG zINX8MW~Vr_MDg7;028jOn+ooQDVp|u;9G%C+1kJ`X%ZzQI^E~$4mlOD6BLs2hPDMs zn_u_b?oKi)7O0{R+|+QjiSo?Ex%2|e&Bxc*$IU&hRP!r|j&y^K7*z8nW^@?vdMwx$ zQSgY{m}{(tXcn_G$Wh?6*^~_L63%d{k9&b1DFSe*qx{+w9CLs4()(EJEJik4d?C3G zyh8?TmcfEg8|~U=5qsd44LNG!NP`*zKHkk*I$0bu0sWM3=C6q8dZb~uagrGQ zK!z2m*bZE+IL;Ee4SiIf>$|t=yu0BJ%49ueRzTd|o!Xue{0XY=qR*Vf_IX&e8<=j> zCU6uzs4}OlnHrMnIqflR0wc&JwoYhSaXN^(er+ZST5*5NqS1egNqn~vTt*a>{sd-O zr6QM*D%)Q@l>lklDZ>ileEF1D0^ktpiF6~ z%?*RGK!MKm!z#mCp@X}xD9l61$OVtc6nAn0Ww=X4E@~|eUX~wM+vM%gLWmSK`k18T z9V46?DqR`H^D|1O3$baXUg2;`t2`9PVgyGLpE+ji_+EWdxJfyvas)6Sd+t-L2^q>SbUq*OEqFhxL>Mear;Cb2_ zt_`@i$D@%%r4rQ3`!lyh3=3eO$r;UzjEBOTOp4v7yGrE1Aoa6d2wT6`tl9LgrW=PN z^MqK(#3#vw!miVSl({!$A}YZlj1adibi>x!uLf6@oarZ1LysJtx-H+Y{YA z0Avwk7&AK20UuXCUq!3XaJA<*vm5i&RL(*ukUy!}ZpXn}eBw2W`ScfaD^cAprTzuU zH-|PyQ@Chs^P_4k%CA|Orlbhb_@v0Xl9}lYq6-+*pqISdiXkIjDIv?qE(ia3=2o+`x3Y{6Jt7I&~s4aEynGJo-LuAI(wp4yJ1i ze+dWo6Hbm?PZS`bk;;VCi-L--B6KHWC#VkS;ak)n*25DLV|-` zY7t~yQ8T@MZc6+VhIm##|3L8r-vhNfKqlIX0^J4Ydkl|C>$)*w2Bm{(LS-An>b6={ zv^9}=T)fsm6vbZX27G6bIO=q!?O7J0M`oOAP1#TohRGeUyuGCsj4i=`W zitu|nCjaK?E)VL!NUH%)oaBhu<3x+`BuC^>77wG^JRgDM3&+HtsiqL)20X zHlmJfheUuqTB7LK`x|PRD5$_@`(H4~=`8{HxFS7@)K*f5w4eG+Nee&i zgoLULTFA{j^LHeH9gD``2nRJDH1rM<4Mqf)tf*^h@w)QLG!P;aP@b!tsnJB6AWUf- z(X%qSm|si9`q70o*=f**gvL4EaRsgsqxQ{>b_tQ=PZ3nxs10JkK-`n1*Y0%$Rz8_y?3~ShgT;4rgkH^rkZVfy zwO9$e#0Io|S=NQQa*N4wv(MuI=F934Bj?4p7ue@&J{O`l$qJ6?O~>L#D)-)t^BC5N z?)rKm0QYdW)p;C#Clbm-?gNJ7#dnu`@v5cCVVhh_d?3Z^ zoy`odz~E2A;-ZeyVU<|97}HoIP86F}Z{WHA{{4;E!sZPiJd>hO4fF=J&<<`6ct@q2{iOl$wc&6HEaz3F6ERr@ZrBG) z4M6%606l>G2~!uV7Zv)@3#5B1Ga&hcA787}p1s4SmR+wAy+M#WxjQ8mc$IJVp@qC> z`A`tn-s{%U!$ju7{}R>X7IHYg{rD>}MmH+P;9AM&-r9YYMC1nF?M~RMmY&u`Ul?% z09XqVsjB$;(p?8QK4Yq9F{gk9v(rS+%=qYUM-TM^5AWB-bloRec`sHv;)PL@*_o7&Q;;^yM4H2awYU5ldU(60#&VbdZ6rd&gijhgsso0>=N9q+t9{M zKAAH_NBn79h=qzDTp=H<-J`{A$v0=kroS~$45!!OC)|M4a8Z!8R?V{*9ixBEoY8mf zyc0%f@^JYnaHjdr13Y>!v-ZPY2(4UT_4$bal6M=3cmMKNf1rJv#s*J_V zd}CgFSWnmi9H(Xe$X7Z(C{B-g&L5&V9>a*bIAzEN=;|FQD@ z18XDJp~$9s^8viYTGWS(P%Ux~jEnhg2UgaFTedTK9x(GE=nc6e*0TRZH}AR0%xe7C zwkT`+ds@5e-K?X|6{hbY@Zc~inIf^cT1^+lt)dbD{*>6b)W^w4AiFf_FKfU?{=CT0 zJc$6k?Wwaim0mE&7z@j!Bt+Tb$H)V2<}>gY)?2ZYBD`r(4+L@WV(i zPurzV$yQ11WI$3$mPtTBdi>{5>&Wk*Ot9_P< z>C#}FxUfq}sS_oKcDf?^D$7aDBYHcZuJAbE3_z2;RN*XGO5+-qT~XtLc2l3#zBF&D z_jQRb#wGbuEyA`Bn1BE7g8Imy+zbAGJL(qHe)%knny;1TVMp2LmcFga-T%h==Yaij zr@=j}$c?LvmY=_f-%0wd1EcHCU_BT{OZW8k&mT{(R^=?#ZHFGVf|rB+{rc072wDYo z{SHbSyyftn(qQMkxRLT>)YyY;UI{kz5Q#9Ph ztyO+ZFP%liYCMLZ`#O*J%!JyDfy$MsaPu;jzl>HU>%>v_;lN<+GG=OZTMNnWK1c*JksC!~+?LEnktOLwaIX7T z@Cq-TM16QKQ80_dnpXJ?W#O0i;(@FnM3Ekj%P!g2^P>v`;h<{PX1wy{OA7E0kvn9p zyRm*qaa23-&*|O2FJJw-a*gYEwC8Pw!P?m}D1C%p*4b+i1_pz!x4b@JXCBD##JFwIQXZAz z9kXswO&mZBWdZJg*eTp8h>YH`@%L}@u&8(0t{{(WF4ik=V7_q9kXdBNlPuR@amNTk z8UE^XXGp46@(EpKbx!!`%~Q@enG~f}_Oi6gdn<-y%=+WK6_KEoyEV$TO2P&Q1^UJg zw^DC81c_Pt<-*nt+AMFF0W*r#9LxavIEHc)OR)W&jfHF; z(2dxXTVu%cir^p`IVI+Xqi3F=9(;t=e7FOuocVg$eea_qbdV9V9-Z}ocPZi`LVN%; zC}HN2RBLsczwl?lslKAW%Kx=%_V+Ge^Q>xk91qcY0)|QRW)?emW!agBOt+B~TevQ$ z3Jse{PXh2ca_#;zenS?>?_uHvneN-p;22w7vbWRi5{eHy;f`QOxu}l;Dc3b>F z&(EG}&z!-^-F{wj9f?H!mCwN-NtXBH@^kqL5Tw!q!|J=)%6nBoee}_~EwYLHZFtF8 z%zd|aHm|)!wqVFpA8+m9InK;(OKD$O1qp|wYyGMd!KvV?nYc#gyl2=jNr)&&1f3VG zK_<<`L!F=euc(zp9;m=8=@%xxhwTE({`{Elb&gI&1R*PLBKL7Ns$*q92v9-(nH-L2 zt7>or>(qK{fA8YNjyG&q`7^HR7W!$dgzmK@L%tRQ{dZelVO=O`#DCnqmnkHL-ssbI&b0*~9N2d|s)+$V6{xqb!np0Ea< zb0Z6dW>(xwsX5o|oO4U!rs2{{KobI1ORyC~7L4#ZKzBH%18T;=oB~3=*V5Zk% z>O`W_OMOM0a*imamN1xQ43w^vI|e9&=SYz%Mbxfa#Rcp=SHmSrpE#xp@n+Hj8mmnk zfaU&|ZlwM<*Rv@-n-9%DMKc^PXs^x>$h@qs zl8+s=Inf2_T&Wy-A!Ie)G5w$d^_=} z>953n#r7+lNP${X*WOHs0J!S&^Mu6y1JT;JO2Vni)zc4dL1unymr*+YA^0N=Ft^Xn z?3y>8-hvV*g@IWQKVxw865Ybm^N;p7JYTS5JjprDZ;>qAs3;JNL66GPI;pzfyG>pj zux<;}7`-FXznKp{9^fGh30}U1&A1*{px7tZ(dcX+@m^|ws%aRpw1O;}kWDet-S0ulVUhVaN-MCY2TmaZMX$IhC}@vV2t zYGo28lOB6GGWoM!ldtn{2Kcz~7nDxsA=8{ZGfm0h{`TeVs{is4LG#9I`>sKQQ}gC^ zO#2>o`cFZh7hivyxfY8J>!~Ga)xx8KC4C;+SAAIQ!Rz7TAq#i?gvLJE3Pq3`ZPDj; z>1wPvWv$)aABH~D6V~sjx@oLRYU&wJNCkq%z6`-@RghJe$3o8cQ9D~dCucQOmKPoG z5%aml7XVZEAKnUddsIge@R+cUvyho9C1t5AwN=pG&p4Hj7Ckf9}~*7b>_yhTM?q#wUDH| z58_`sTNk!lSE3Ki|Kx)nm&-xahRUqR38apOY{Z_Md(C#>OY2LJcm;Xa-QmnZL`sXD z{eJi4bW4NwukP*ID(nldPkpT}m)(Qc`lSMEJ1aJPFv*mi6i z$+Z?$!Tw?r0YAKzO9__2l`fCyT4J*sEl53!uv2Bq3MWsDwj?|bcl##cJ)(=$x=zMN zrGfBD;+|qcz_|)Wz$S=!@gz74G)^+UAh+wk>Z=)L}|OL%|l zdaGt}0YTivy5zePzOPiz2&t15(Hv#)D9Htgze+^^9(Y$ts4{S|o>oP8zP0IM(DU-= z5%woZc&DM$clh0swLmtCA=kzclyXX!;>q=wJvSF|H0qf*YR@R>Q7%~k-=7KAdY@AE zuE$bG_jYHsBqQD45!oWkM!;KsFstF4(Wz_qk)n9f*=5!!WvVOFK$<OE`*&0y^nW*_Bp1*a?%)AWeN?GSbi80N7jdL zZwx%|FlV>h-Ho#9{c*DH*KIvadBucaisFahr!n)pdKiZ7COU;nXgA60_DbDRxO=mB zG}V>IBLq`%*KxJe-}WS=p7M3A(n(F{{diT8wbT-6Fwq*$LwW_3Tfbvw(h&-m>>1qi2|Ue17(C~UJ+GmzIyBc+X|gNNl8GWs zhY7><<+Jqz#8vH)mJpj!!CqblI9K)&GLelrkdu`a3hsX+$+`lc^Zq(JJqqV}7C^1c zN652$p&?lF1<1cJnX5M*-&$GC)G_L>B5tSeF(}(h|s;mqXOprr>wxIr8wh3dPh5Hj!%#yS_LM`&0E6ie0t9*u~66O zGxk4*hB)Krm;vbsyOM(qu1s@qo9<6}?y!)_y}6ei`r;bE5i1?xZbSSRl0u|i^6$zG zFnD*28(vy1(sHkg<5N-o2uT+;XXfDU01z-4KeJiILC_tP2o9z4Glx%4K!?weB{B&D z8YgT9qkl_?qWmQhOd|0~Jpw6?*$2r_z{X+1YzDNl?16G|+g^Nip*P4j;Fx$ZSinl6 zoG{9e9$QzO=4Y~9a>_zYG;*7N-w%4bdy|^Gr?+A@<-*dbn2eVWv(D-~z9|eeHiH3e zWH{JZA1kLm-xr_zq407UaPGmbGDwgO7`<%dRO?$Jc9qMQch9YW0Ue7^F*X?|?aAa- z;ZNLmXJjMVp-}>5WLmgQOWLL_bALU2L)|5k(3K}ZHF6FoMG@YvP`=e5?l^fGd90&*{52m#MJ5@CwD`8Ngx zSu4xWg4c@RFx!a2I_AD=2_y+#z#c(c=oPK9Wab_OYRw1S;S*Es?+^ zYOj-7qy_R9NtaP3eNQNK{t z^-O+J!e&Ayr^11wl?kX>Dl;A$skmrh6`drgD8b?yh+xMX?)9*uS4?!1PV$<1?j(qY zK8}~8UxOC0lNH0Qvb-Sp(f$}AM=dRezqeG=ymf#j_4(ltIAZ+g0&0v>zF^C09 z=xYgI*?HUYdi<06X^n|hAN!dvvezH+*6q3ZGDn<=>Djn3JHVisJDhT#?Mjb5we8gp zZ3aPRm@mf1>|_Vf*^{3ZaU#;Fk#88l$Tl#1)+PFX0MkG$zhsG$e>TR+-#VvGh9*TA z8AJgP7{e3}ppHttj&(a2WDL)!L?WpT;kXeZDnTR*i~eP@8ufDxT}WAgJ0DtwBhR`J z|M~j2;Mk8{gFlT>Cs?ZJN}FzjU7VQU%INsOH53i%3w77AJwV3VMZ^c+=Y+|a#rre* zMd^_x@^36!wl{DI0@e_i48+7XN!wYu z0h|ceZO28qP4I07CtRa)NBzI8+dh zGPYTopjRYJ;tU+qIanqZ=|1Oi860&XqoiEtys*8ZyE5i&&Kuc(<>jd^ZQzSp%6%9- zd2|CiV|BO)U;FukIOw>K;NRc)E*y5+r|{!H)-c90q>m%QhW?CFud?&vksu46jq{gQxbrgaer$pDtQr5useholT)3wXjh z506z-WP`ilF`ja)35U=e*bQUcUq{3Fn1~n5NNkg*Xfu;C6@+3+XxbPp8==ibFJT9S zH64_S1w@4c**RqGgs?LtgqO0bwpfDvqr;+5LD3>5i{%`-&wuR~c=Z7%;+WIV#rN<0 zE$rYT6y|eN;3wU2CJOrIp?~4d5G6@jU$5i~uYtIJF)^%~4RNfq>)V(sLm+u1uSi~Q z;Oj|DNH=8vq>mB%TEGN~SUQox^zV#?PyKWJ{GNw!;Gsw2pd&88^|#yw`HKfKz8UM! z43Nl!EWWa^Rb^RXgj`awxa)`n_6j5Ak&f?*xLZg}r(AJFQpD`1O#Ctz^gc8U#a1}R z6|_vr$F)t742o!4aSQZ!rHBT@B)k!n_z9rG{EXuSPPqwYGG5%kq@G%nHBmQ;am=1b zh(pFrHsxaMH(Q2|(paoFIg_~|*f>PWu#_yX{rZpanzx>W)6csYzxe(AXrM@AY!GQs zhT{evRfdq5$V&?5hQdO>M%?PvxCG^jTX=E1)4?H@x+oIO>ecv4Xpq z=m*sw<-Q7Ss03(92y5kfX*zn6v0ZI$Y;DdhnIqB^C$}j@VjOB?ES%nUy@5JURVAqj zEaFlOsX_j7hy0o6(=NImfBmojhl{WJ5{jnCiP8W75CBO;K~#(OL}{m|fggE%n`O=K zN}C(DNW%W5#&VvK0p|&;V9}0@L^8U$tX+p0&CB%)^JQM79Lq83Nogop*ZTppTch+pf0f72F3p&dTARirZySMKWo$Cdc!EhQAEe_ zWVh*MowiBC7`rttN1e)LZH-c0SENqyPZetzS>V!1+X*e6Nb?bpy@lEl`1O<6P1 z!U(NC>HmYvtGM`cH{p$MKbEKRd*kchy92-d(_?7z#YaBH7x<|?sLbQqRlpeEI1%`9Lb{#(*RL5xr1HlCxc@F| z7_4Xp_{>+nh5zN(rUMSU0AIiDZnQ&Sn0@ELAqo|aZ^jDvwLen&?nuSJ@gaE)l$nDeZFhAl!4oe_bjR6!{>b*A3Eg}{Q91IuySOCn|XwY3wwiakmHRy7xoNw4%~{>tKg_CQu?+K zWAe(3F)${V`Sa(4>n=uzhcQA!CE?q}nvqf5@||08`dR1Ts1Kcmdmo;FSo15!+7JUK z3nIv$KZ^PW0fYS4b@?P-QHxv{;4kt6MIz<7>xzFCi<61dX{-1xbwpg*xFtB?E#gLQ zkRjzloN+_9L5P5#UQJkpqw@C+EkIBxV%bOy3H!QaO&jm}z)3jf_>=MbKiq>gBWqCL zOGf{|0D>^!LdcUm>c=Eo=2?4@p>SLqu*H2+gB0r6fP}}s#ZnplgY&?~;lT$V#tmQl zI!-(D9K7rOr{S(Yt>lS@L4%VaWu`XsjE>h4R{AkM!p)hRD5z^NjO1kftxetU>mR^QyX}S`ig7=624Tj9=oG^AkhZ7o*NhC!c>MKOydq3oic*?t644R##gX9^q*V z^}&ixu^BDuf~?QN#B!}>&?Ya()=yW!yqLjH#=%4p5C))8fkJ-;^LOTmV_}#eXYd0bunujJgzy3vwVd4VDn+=}W)?qo|eS?E2(rA==A@y!TSoB7(=|mV~ zSYg@X{!SxT7@j2?q%U|;*-1?JWqQ>2-k9tzzDRM?#syBFwL6ObW za`M_BfH9^=(Btn7pRQlZ6OsIYHC;bF*7~~Q0^izNZH!LTV50(d+G9^F*l9N`T{Vnb zZu=n)JMuW3eAXpgjJY@g7_K>#2Ktd8MBltcfC&ItilGn$?_vao^maoTG-oddLrtd` z>7R&nMMA-t%=g@L<$}Ug{y8@U8OP*tA;!v;E4Yytxu^rnhR0AE?1Q{;$qCgLZoD0@ z-~T{d_qA_ffL{!ju3C*I4Xr(&`y8xUvl>#ZF^2Be5j@V%7=w@o2aH9v*1+iKc>Xt? zOBD>vUx?9a6KkpseDIj#aqfkm#Zn$;tfXa@1O~0t`g!7DSc?b{TI(f$f{(F)cZDBF z8~hvDMw?h<*;D4J+W-n8l=u*-X2durBY#; zteHg^V_?km1T*>>uwn$<6CAx&e)=EbiDrYRUlG@Yfq4s1fQ}DCj zK8yswcpH%QIHTW?y@5f4drX~WFvwpSjX`)`&Oggd>hZ4Jwvb5Oxqz)$E0%SO$he6s zVjw?hPcGH5VGIuS0bCTu$3|ho(D#qFF8b#8W65Y8?>+8({KJ2~7T16CyGV+IkQ35} zR*qulJ)eO-8uSB0LnzWz28&SjkzF~-1{jSI+bQ{o`4-?$jF@v@ECbQ~Bz zHa?0n+g}iayuUw6G089{JEwo%C ztlwiq2c(@~UlQ6V{(o`EG_ZzuM_KoBknpao8#mXU>`WT$d}aaRJuoKsivj584ud^I z7|5m!0;WzQ;b!e%VHqikdiSaZPCV~o9Cg~6xPSR7*uEidgiVBf1DN3ES*Jm1X#~a` zI5X>q^)xm9Ehzt}LhljWY4KxY$UOeHYRe z!oZ@)&lM1!JT0Z(%V>#F+^c!`pnegN~gNq6% z4=ljoqFs49yd0N)_FBHF9)R2Ld>C=rVChKW7xC0smMr-xiUkzkGhAXHH*& z&H29N-4K?-Q2HifIapSVKOVE2Eb%EN9KwNNj$Z{9E?R^MZrllrC=CsuPFMb&vZFqH z0X}fz=~z16L~-6?jJGlr`-hNmOjhy4zS7^%l-u{>QV5YSiXrQVqrpEmQOD?bod%vk zSnPx2XW_7K9?tscr8w!V3ozEUkbl%Z4N1X9G|GU4ELuE4cP;@npOPWvOO+)j<0WNB zMQ`Z%%kpknhl7n-2A^nKA|8nF{h!^9|9s_J@cm!>7R{iHhAE=qBEOb3lZ=K^3;v{x zh8MGK*DE8ul$%Y!7sr@JU&fw~PE^USiU!-dXqVl2*bwq%5%|vSKjr#y0$(D(4;KL- zzp+{i3G+K@R-sTr%>4zO^;X7&)2!1!*Ksi!v^Hci>Ir)c>lLxgX}oYeSc9dQ9+|9F zDxDNZ1Fj!QZ35s)ct7{Yj0Lq=&V5Tpap9FW;`MKP7ru1M53wc*G2F5kp@j2xM$o?i zkI|qXBP;m~o{>So^|QeKi!o@7@n2F{)J8AIw&#&~%i*7&h+ldo=`pRJldNoSonID3 zr1~Zg0G9Jmrq0OueA!-AYhv-!o{4XK|3~=Wz4ym?pZN+N9d{rxG+1kc`csv)gRby%j|}91u)>x-{{Kan23U2k#I^kS=VLIY`nzS zv(|FloEIAekj18Z0%*B8$^TBwFdPKX<|p(F0g9CpxVQNe)1UnMZoKNvZ^d~dh1~o+e3o*3wo|s@?-t@M2;($XxOanLc z{g%@}F@Q7hCxz3YLh#NIZuFp*<72;9SZUV1Xb5oytQ2FMPMsXG_fD}WT4*MxMLO#a5Du)E;WFXEkt z9l_6K32LqYTUme+zKKO_%fS3aXwqN^qZkVoEWpGg53>{lW2P^d>Cak@V}d46btBF* zF@h@GGm4dd6nTKKbmcJQe;X=)p3M~dG1_!E|Kd;Lr1P&rjdd5xF=Pzte2GtqNcI?Z z$bW5uXU0J9GBGdH5jy^oSnt^ax3y)nR53!muwXv=CWy;NKvHthWHvA?q|wr3<6D6L zY=6~PZ^hdWI|^rB_z8^g$SWxhK;}U^D)9uYfS}xu0vTBvyoT$ptd+87GrMOYJOYMC zH1LfvFlOTd!**;!9H?KL5Mwr8wlR+%7sWA`0>`0pv>gU9u7%tq!QXiV!cbc?KSP~5Tp2=hqAnS?5cLly* znA1Ow-ptx>j1*yfA;#ox#kETqC1BO)iUiGaF~;~wDmo#Hi?|6F?7Rz(aowI9jPY|90(b-YffX&B zd+C>O2MQAop27$`q(9CM}07r`6zRajVA^bo>Y(UL{c`?fn9b(i*xspM<2#6 z&v_1f6;j}K5cUu;jlPH?`?dVjKx6#u*Pt;c2HiMMe}X~@+*UAer`;%fU^(a6eUGld zx4w4=4nF)0?mgrA+G|3{$*YtJGRocFPIW@W5$TQE5`HPOSJn<#Pw9q!OW~TqHo^pO zK@o|Gkg=cN`^E2Y&|$~onA6U|-H-B1+Q1?tg?`9ij%nH$ZT3<2<|eDK5R<^7+AbJZvVw^aO!y%;)>6I3-^*!%4$OH7vuFNI2}CsR6G#g zjGXc^Ot-1{DKpKwK+&cdH%j!jw-8YtSG6AghE4 zmyQCT`uz2ff8=|l;ZUWqmqumuFWwEq)QOdAILWCOrM`aHFhIh^Ov)>kN+^}f$f$s0 z<6|sIE*hEQu(q6@r0j~1nC6Xx!EtgO=`BQJ1v$D1?q&eu9(yhVdBTzNXa5n`rH>`2hR^{e@?t|b3UeP z{7Gmo?7wi}1{lU=a8*jc1C!T&betGvF5f3cni&qJ&Myn|D6y6lML2x~pw`eDF z?)N7=VHh&k_Pxf&#v%GqE>{rJcuQ%?7_OHgjX4vn2glhShVLe z5V3#nJL)*x^WYL1H4V-kv?kx{9y4{aU-G-(gS9KXW61wdHf{sI`TvnDE0FZxjA;Y))r zGjf`33;lRx*)TqG@h5QEXFiXIR*ZR;GX63zG5{@a0TT2&->lh2#6`Toffb2^2rPD{UD#xPV+@SRrJGRtCjuH7P8Mt! zw%a13zs0vt`J5yA*88}e2udSQ$~-T%8(4ojCUg=Ld7-V0oy>#Y=`A7R1bz9m&0 z^zOs)g|B>_3syfj@}hU!h#;TDER96V#mw0d!r9mWQCvV6Mx6K=8qF5u-}j5+m^F0v zDC59Y4{{=yhwvF9nD$R{iynAkS&owNw8eD#mQfQM{!LV#w-u6y>|0loj zYlv`=5wG!-6Y>+d;+p(c=Lttb{gX9jIgd+a-MQgAzrb;)o`s+O`gfSW%O02@t1+HH z_VaUZ#x*FFg?nHgU_;cH-;!aLIn=o67PvM`$ukjS42;>hY>KVK#l;emG)0|#cAyc* zIWKPF7Y2hSPl!{72RtFOTsPVXxt_!T{R#J+U-4z(s1rVnhgUT~)R^EWV)<-X&|H#7zGwl6u}q+*}pTcBViaqBqdio z&M%V}Z>E!%G|RNTB4J>8BJyYIM8q%Q0qYB_q-LhhCF3a~Z4i09`h~yP1O?9R*o1K8 zlk(GH$h9>@1o4!FhcU*Fk1@vADQv`JO#UqX>583h#%{hQkBd{G%K&Th;v5V_ z7=(apm;h}%V5|vz^SeL6M=rSn^2xWFSiZGJOvXg~Tw^#`1_AeJ3%;CC48>7^fQy+( z9u6NSdpfrB&#-HLv4m9CPGSP8FeG``N)P`96Rjjb>5%rocI~cixIp#O84sZcf)4tN}0VPwdO4W z_1O8A$>J;F@IQ&oLnKyo#Ia8qEc+NBVLuc0xy=`#7Ei^0efRy?@9pox&wsZBaeo0W zv}k1(6Fi}bXxtbZBH)RK?aVz{97K#U=!Rhy|D12zS~~ubv#1*q1R+Lwy1v`)dq51o zk*Y%&2V6HSqVj;>1V3}ljrjZ*zlv4V85b6jaEr4vP8?Z+Is>u=M`Ep@^UG`RJj@Em zuW6#xzP*+tEc+TDWj|!?NZF^v1Q2P<*wY9+_v4r2yLbEnjSNf&IKEgk_!7jOodFDF zSxp*91sVdG0pw+~e<;Q`Zu>PZ`P7xT`+-OJEMnlzJL`nU~FaYAA;7Vm&N( z>iBIJ>e!#|D2nSv!VG358EEJb4H zve=n6Ufy1%49g8jgyXD1@Dg3fG>aJ`#9nBbJ4Ixtl?)pQyd_|kQ=4p zKpDE00!~*73$j$)fMTh<^44Gg#ybv&!|eGrt0UlVo0=M7!8ytDc#fWGGt{QjT;0&3uBxhNa2>o#McZFVF@4J({dkaza>i9I?J@owjO5_mQAaZBOy-}R4>2S} zVG_qWBtpU&1rdktPkg=74WF(n<0E3Io5mMnSiBLPl@^Kqc3eY0TgY@inZL<17%y#h zCP2ysCZ(HjrEgJ|@>BQHHC4RheILa4?zjiz>~hNLVt)EanU+=ga=N#SZ`@tp-LSdr z%3tWQL4XN^E1>gjv~e7ooqw<}<*NUk3inK2ZLly1U4%0JPvWc37&JmFLfPXH5~ zOJNkEB}aXnB?u!VD~LlObUo^cs2 zyYgDBsAUKTcfrsuPlYKC@+6p%{Ok!EqB=IlwMKfHaji_(752_hz%iX}^9}Plu5*rt zWUqkb8p7#{r5c5NagE~;#bVqe1(J0z#&`jL#^ioua+g6Hy$oY4$5(8GAOiwN>A04D zHi7*&lY+^yw9IMPN{cpMvL9Z$8cS)Q?9UhNRbws4KYX4UfbRfIWTtMLeVI<~&i4Vny}VOFGm2xB7iMr2 zaP!J&0G7D`$)9h#`pY-siZ6T@m3{-hz=2U1_y01#BxYuR`4Z@Jozm%@1DE9rARov;RbNVc^Bg6zquO|JQkGG9mCHwDMF5V!2aeOEOjH`f)nub zg+^i9>{R05 z|CtlEP}=3&Vwi&)RBA$=iWabVkEcWC4A&*RDB-M5CBO;K~ybWCg@zI<8FV)zZ|gX z-eo(zE6;3Nx0QTk47DMaDq@1) z4hy9a4=t$d>4D}}}#BlOY?wE|ao6v9&Nntq? zSuI<{HgIg5VfmbQK42@|j4?1~<@m=fc9SwG+}B`Os29V!jc-SxQUn4()&TjZ ze?G*+0{JIxe)qtm=wG-STvW#Jcmv~W#?ax+f9fhtp>W1b@GUgZLDfz42&@wi=GUZ6e7Mi9-(7f z9OE4%+!zB(F~-1{wZaED8p$h0s@U_-{|4i2i^uo|_0B^-fK?+c@C!Ql{tbDL;u7m$ zqB6c*gFi#DFnPI{+dn&#=6ev<5Jg^!b2Lnx_zfVF3;FM!KP|ywM;wE=e?Dw!h;OOZ zTh|G#Z)cd6s)$L+04_Wt&ZKbkXCgL3Y8|EPI%ItI)nPmRI+Yu`(hEOj>6gCbVe+uP zuz$$I9LWXcehiP+vFp>Gg;UQtAK(4SpCF&h`7(}5f0-T2OQvT|UNUKnF%YA~7z24u zx_@K<@Sk01>#*;a;0rW@2N&3oI2X$?Fw_!D%u)kp!B5&DZR)y~Z8BU(^Du`=zYoj60D$uV?1wX!r#^icrp32# z*Qet88@`FpeEwPlw0(Klhm7$VwNL(bV^oU#i{;>=681mnU@TodhT_0NKi8`54AnX} za_Uk4KtBrdiM>^akv}KKnHadX*HPgIunla1)a_U{z1#V6tUnh+CL&_!bj46x4BNcc z&6qxeaLiz^VwfC;7NZ@MP)jX7O@68dZ*66aD;T9P&k3 z#CrV!ThFxuA*smARK#Z%$~vZZIT#v60ZS1HS(J$78t0NW(pD3dVgaQ%LV=%(C5^<1 zD-v>ka^XU5H@N9F!RPAcrOP%*FGadso!^Z4WHN}~Q6D5eWBs-~h*kWi9`?$lv80N z@Fas-^8$HiN4X%-)45n~$2Z#@(FqZ0r!fXW5Fq3!gBUH4A2+K@KJyKH;Y&9_{_wv1 zZN0uBZcL3foE81xfHR*VKSOSo3tB)4Gj2+B4Z}qTx&m?%I%}V?!B6st^b+16(Pjr2 zrj*jerI%O!9;4}x&+cxSKUq-l<<;02wZ^R9vXt=P;74~wx{ zilKhs;T0)PI`u3(#>GWWz+#^KrM7^Kn`F#W<1EckoftrTu=(>Ch02^gLL_7x-Y<2*w` zH=*vPzKyJwmv0$eL)m(lF$TtLT=JS7jPD`cNmd!ddWCT$V!Y&j$8te^X>cD}$)nS_ zZwSk2SY2@8#rV@h!=U>NnU|K_5?V1CCu%pFm2+bX0!*oB7fGe)oh?mV^HD@dn8H2z(ORWLDd!6C%S^o z{0T8h>R4`W+|efu<1-9nWirWguSggrW8c|aq){2;O~)4igQzlyKiu~KF1YXtKX+IH zL`?#=H{cwRx-#-*E%Zt(ojH6F!^~PQaKxDL1Iwa}F~~B)tkxgy8Pycc^;d8>4r4ve(KAsUk5FsXq6~m(_@|0uwsx`Ri(l3Gt(P;V<>)l-E zx$AZwXQ^YYMaD^;@mk78)y&PWY6p}542zKQ;Yi}SzP zXYxT7c9skSp5g>F{6cO@mac4)dDjgcrpg`qH*#&kV8^l?2UCRJ^)AQtha!5w#n*O( zEo6Ac@y}#A=0>0LbS3xuIF=^`Ntqe@KG6AR4HBl3&A!GBzdX2*pYYDWw{O3jbBv!@ zX!uDU`O7uN2mpBkj9Kf)Z2kH9B8_u$>k(1bvFUo0I=SVVDfKB6dQo)#Pp;ed0`QfDl6CU+fqGp%DGB9J=h->#Q5#IYrgt)Ng( zAi^iF57K_$4@bR$Pv^q{Fa)Wya6y6oz|WtkSivc$o{oUirrJmuF6n@j=|6tiRk;4k zH(+4FB2>s~_4ov$azAXbjG){HP~WOF{^YY>iKl=O*JCjt0?spGXxJ7bZF6~<%;3w% zcKpc*ib#tLTa!!H!^zRl=T6(D{atZvci+5)-~lRJSb)r}V*en%anrZ)-P?Y^6-G*B zqTWj~iS&j|+rPFT@vRl<1vu)Ukq5TJ;OOH{#4mpRTi8MgLyLAsh3i6t-zEcVA)i+3 zBf}V5y%K4oigIL7iKqZclZJarooD|^!TA-3D<=)WlrNMCT?w0Z^O@0*ITv`y|Lj2| z<2o;V<1j=q=3Hzv_*uFM$2F=eNvnlMt%hoKf>|@FyYnLZMArc9x_F$QGY*-EUC%Y- z$8bj-7LhcLVUA(q&*;w#Z`^ZI&Y$AId>%}VA>a$aZhJlrpZmhK`0@=u@F#&u)@_80 z5PVyuecjNx>=GM0#2!f3rXvB5oP)r^1_3Mw-4UpKz8tBxuwt}@>%V>z%JX+Z%SIS) zr5M;{H>`Pd2~a42AJ$oOPyHN^e2@TP?l?eRd@4qI*LhV270+-|2VB}@ftrql+!#U{ zCjl2V+Yu>Os&T9?>k)aDa0dB>Sil*!M8X_zdA#?@6JxTDIJ&tof~}Ujp=;rmj?Hjvm6yq;IYeQ=W@jAg zHsqf_aW+I75r(S?9$8hxktdvng}XlukFH$96Htp5Kj8&oh?JiYxH^z^%2Gu#UqfEL zp?+96BQjYT6s zz!2f$%F^H%<^Z}BL!TEeawcPF#=Op_kTMO+1)oF=U1`sxFizq}mo0d|8Of z1OCs>g1U0(QODz+M^|C_c#BL~pS1|d4x|Il3-C=9z9sqHA$RHUTK9q4f*H1I=LH?pm0sejOb0kXU;3u)4dqW8Xa}QO^$!S z@ejOCqzn^D=;mUPaMTm#NhukNaV!(aI(1|wpr682(ibLRI>+)emXUgaDQ;m+UQgWG@g01dkUbpQ>@=e}|quDjt|i2LVZ zjHm1U3l>8D!bhDOamLdb%QO+^Y8-~>uT&64Ax1|>8EPnU(Ph6}S6HvKN}4VrbHh?+ zyOMLu*hi6+bF3=~{c9!gY%|ZqGQX6?npmOSyTV6ahI|Z|R)qO1bzJ61o=Waa)Vbto zZCRZ_?-*Z-^g|ve#@4{H@iBF#G&qDcJeY|2b?S3p{yOgb!%{!5Qc1}EdWX6Fn7RCE zrgz^?7LvP$wTLjFbFJoyIhq-2%BpVG={yz?NRiZI)RG#cdBHPw#q*!G6P~flAaTWO;E~XSq#8X%k`ALdchoK>Fhx8zf_AIRXJPgh-907-R+=k@=Skn95L?X`RcF zgX_#k$NXg;G_+T)LW_oL|E{}ZO|5|km#;*b2NEZqc^;OHG_j`Y_@W79-Mnm%LCU9; zWjo5;y)1iyX$t|jv3iRx})1g$j+p5OrGr zwyzAd>Ok6p;~8u_Ng;y~MnxDKf{)1fxhsmw9I6Oun@QU;j<6h)BXjRb_dG3mB)i4l;a9jk@Y+|5NL0_c|e`L)Q%%D_6p})^> zByHzVD6`$%?0GneV!%b0Z35vrW6)It@ri&TvN#0*tO1PS4H8PFg@J!_XQqe?S(|U& zMQ(88tqhIA02<{5IPWvpV3g_Wlu^D#IjSF6CdkVoFRPs}a;6vi1&X6|i;|`qV_=Nw z25rP;2c%6DVURJsh=dxp*9#YffSniK)+01Q$tQc_046j)Y)wU7quwOsIwG3<)-A}=MyVgX=2 z#|fK}TgKEZ0x%9K+m*DXqy(`nHq-^vtODn~I|cv%5CBO;K~#K$b`;ZUz_eQ&XA9XN z6OKUuAmXCosC(8K^l<@BxDXpeh{6(JXxwrRBmffD+vMV$ax+g^pS*dBfX{|;3}RCZ z_1qw3SsuRwEZZ6|jr3Wh&|gME7Scb<)2&8kz@uVJqycJSKlVH91pHxT3lprfNtvjz z4_q{z^o88OKC@WJQYGyYH)A@m4PYF+>jS2B3)l<3LxDq#7LTmsD5CM6K-w#(zHIGe zxzY})Te`q5PuA?%dB=YHG3ty{#@LkO=?$A^0{K4~+<5CxaPkEoM>e=KmQ18*7YF>m z^Txv}Zhirx!1{J1JT+|4r~!@Ugq4Pc5`wshHs@}_btNoTVB!)Dpv)5x28Q*4rxY1= zG;P%3D5hCQoU{;8Pr`N`p{y;`6Wgv*Hzp9!4YLL^t~ZvZLKIq#rK4ELKS$HbELti3 zL4i#vkV$}mNBc4R8fR_Tq~XV#O+W!}haqh}bMZnd?_$Vgv*H z{MSk{xVR6^pn?y7^a_jsSWWr$V;N5R$S3gFM1pu|Clq+vv7BEV`uYb@FrYYC>bjG) z*#Yf#O0F4%Q>~pr{x*KbKGf-#KVumbisTug%{dMBCE)l4tVdi!hKAuXcLK6Qvj*d( z3R5449%_kZHjDb$~{U;o2 z7lE;i0jlzEMlv4E)vj0B+bwY!7AImmuJ{dGZrEzj1-|s9ui~da{RPG+n%IRe%HyMT zG+m5^yFHytRSVWQq}<=y+)phB!Gs~Wji8ya$f$sXedS29)lvtjZ|u2iBcgE?aPLVu zK@t>@aoTfjUxQ+61dF0Jp0S{SH@x_-@&0{Y zhKo;p4{p5TOnm>ki}9l`Uyh%C?UT6k#!K<7&zy#Defm^<>r)@b&Gg^;g>&)4FMb?% z-0&&P~}ucpS$l^F>ki-i!xML1viWvfq|-#{x2cIQGH>Tn}2~RRrY< z3>UE)=Q+wHn4$St&2$rSKMp_X9Ii74ZDvgXj8z+m$hg@Y<5P>0QMTTdaz07v#&8WF z14jvh0G9im_$6*8VjXi&S701ME_%&o1C{=MYFEICLMViH>kof|`yP0NMqf;cIA9@C zW8v5lc7+3yP*TW%8Fr|}MY~$7Qvqe`WC{*QgGQL)g4xHnvwjY0#I{zV5Q_%~L}jsN z$s=5ZYlyQZN+v;(Q#GbB7$$WT`Ib_)sW%LRb`9WyV>wX@OxsN-&l9VbW2jU>Um?Wm zWlJ#1WAu6R2Qjc{KAPiWn0WLNEZTKf^!N4ghH$2(6;M;oDlqfq#ZnPj z%q+e*irgO(E>iI09I=mW_I=f84fj2~9CzLOASYIgwh7tyfQvNs!}rILUqsF_2TPxbVV^-^2+OfuLFU82pQA71 z9LoD2qQ%cRRa3&--g_ig)PdnDP~>99WXR;LiFpCX|Kz?_SQM{}0gmp>FSC9TEOz}c z2KjbK+a&qqvph^8KAwdX`BK)TQP$#O{iPefiFPBwgG-j9xM)|u0e}Ts_S1M_F6MC} z40jY8MyS&;ZPREkl}d=B2=eCx8}%B>#Tb^GOUMnefD|&e6)rOUe4!N?l+S4OF$C0? zn46%i9fhocD5?1{*BHe>>=33^r1epN2BGE41iZ8nSPMCgO!zsv#VU@5Qbs7_f za>o{1;J81B8_!$m!pzu?ZL&2NV1RY{)GTU88H67A)COCo?1SF+j{u!)>m?^0%?e z9E$=C&ieQW8WW>@K?}$X-jGc>A1sYI;Wn=np^qG8zR7wl%t~l;zGajYWwf|1H+bq( zN7=N=vI@iXIp$atI9CI@Aq~P(6rja<)#m=PaKQpt&aIUXJb*&6gr`33=~(gDWAHy` zfDOdLu`gg>a@Y80@#kV0>%e87DcVSAtV%x}*=1Z?R`bQ~k(HzP&K-Ybi&#n&`>L1; zvW|)C$VJ>dVj6cRc?@q9-+p@KVmL#5(fjYaFLv8)cPv`C5Gz-#q~W(a>n~#2J@;W? z@nWWSOqY4r@t(0aA}Io-oiWT^mgq0%Q!tip3UIxQD`jMy583KfFrh)Y6m#9oFu%V9 z4m8)m7RolkK$zh#pRqd*+5Zi=@{$X13yr5KHyKfto)(STeKC-|r5?S{Sn>3Mk1K6~Ngr@kNGWSN(rcRF76 zl7Gculwstt2O&ma$bGv$I*dvo!T|SwaIH_PRTN4E1nff;#|WbkGPg#!H>aFyvS%C8 z@gjWLV?zN&D2P80gNh>B;W!r)&e^u?DK-3A9y=26VIO8H;W zluIT538GReLKc8F-x!^|b#qZoYNHrg{wRhYdl<5~?6PnWPupby{`qhI0tdY5H8}0K zBk;+K&d0T%z6{r2`AK}~s!!o7SAPaKUV9b3`Nga8wQH`#XD_`FryqY54%+t(c;!o9 zj6d6BXBck!t%)@lEQJ^>1gNfBfw5&v5prQHvo9-`JWAtl7}Ce2oxrf46}~;!_{Jf9 zGIHBYk+fauLJ7!8MA&6q%!_2PW_TE%yZUPU>7i9#&17)`I9x_l6%b}-Gwa-*$=clf z~H49r-Y=-#j#tCZ>dcTaY5K=@j{eB zgWBk7+WNy>wAz?gB(I5KgnVnQWL50m7vS%ovnTfZpZ~xqhrJz_pM4T8I^!hj%z3!x zGne8kSAPoEeCkqMboPgF{0HBI_a3+({^A*XV2_3UG!nO7|01vPi5iyjlf@B7 zod)jDn4p0K*40i8%e547>F^R?E{;#V!pB*9iJpMGVlMEH-G4t!Umw8vA}?P}Ipt%! zr6&OA1=Bgsk+s4wMp^@;e7kSf#(5gh!u(2%!2)30;~3%*SdpKevg&Hs#t3#Ei19Cf z^L!k%_v^e4T>Z(5am}YM<$7{CzIfFq@ukmwlKv}j&1Wvd7Z`WwLHpwW{_9`x?A;ci z?CSo6#MVc6S~5nXW(-+lj7DagFF+AWl|GaP<{_vIV!UPW$jUWdA25TTp?(<~AfRFE zVBpB0($|mCi7KwW{!94Ax9`K%*IWzBxz~^rz9598lRR;WUo<3-ub+9n0GdE$zhCr| z;{Ap(2ExzLKx%dP4UykKiFJ}6*w>0}V?KHI*%m64=j^!?_IvFsaLy?o!ly1e51+aC zBe?3akKuElxCmEWei5#`>_Q**NydHP-~;fwm%kV<__N)ypy*IYCj2-A%`u4Ky=bV6 zi1TXIisioD1A9Il6FlC!f5}Re7VQQxZoobxZ>mp&$p;C<ve&|LEvimrx;24b6rYmHy!6dTw9Bq6-cY)ikjq6vMZg6~-sr>t6ys-Nj2rF9 zFzWJ2nG4SR{vr|@e$BC!7~+QhjNKRG|NF~lE9ah@E&`TqE^@c6vCrsT z1oEzRI}0=E!#aL+ei9$BOi|vHhF|U`kj<^!*N3IcmgAQ1-Ug~3f~ZLSwBT1B7-K-2 zsZ+}Ab^`X#7%n*ECkC$cQNoIRgOYysw!Vu`7OG707|5Cc*YP_(OBz7#6vLGK+?jGA ztd6ZleQcQH)Wkflko{cPqV@z{NCWP*Fa0;1dGz79{)&&``b$s4Ssyq6`@HO*@sH1a zDqi*v&%(dHaCiLeGw0z2PwT_qJ!d{%`cKcm-mm<79K7Fu;jWK zIl8@lpN`+bz>x3aU3bH+Kll+o_r+Tg6+{0w8H@cxoP%=uXFV;$3f4+SgpMI1bY}4- ze@3lua1f$9g`pw;61I@1A+qRO%f3mc$iW%v7~M>GQ-rVMv&<{`Oja&L=;v{d;VE5p z^@mNLy|@%hU?hD*;q42K`|GQ57T7vcraTEJ6}dH9Pz z>&IU_wT!=fY9IYWc)>FkGj2ER|C;~AxhEZlFMal6eB!**aPYpb!9Ty?S=hBd;^*UW zxY`IR+>_*GR@%H~bR3P;!j^{+l-Mt}6*KsS-0%$K?ZYhq01yC4L_t(_DHVf+x{x`8 zVyQx7vmeV>uENJIx)^PKJt+4L&{i8mnZ{w6r^NE&-Ora;zyK6sC6ckMjBRpW3|eAj z^D9M>C*b|mt$-)xjgjR@$5)|(rf0a@P!W6m+lz4eaUaBu*Ia=wTyi{)eCI3hiWff* z|M*wC;vfHVXT0dS3-PzlnuouA_I$kPd5iInFW8-Nd*W?xcrng6`fd2~m1pDf^H0Y? zZ+aEy;8U@Pr$`YE%;gWq=dck*Vu z&oPzsEMxC7@8>%{V;D>Qv2X_p8AO8g3|KTqoYjEP2fH}c%(MLL%(NBt- zEMT1nU$lg9Tfo!z7{o`;JqwHG_49>d0s&8>`-duM*T!JDu1Y&avdOUv@^xl%-61FT zPtu5VtF-Ku~*Vl_OLb5CTZ~4Lju8 zx$|Iz7d~rGuG?quMdDmMWkDJ3k)`Oj4HT1c^qCe)QGkH`W;SFz0XOuEev3U(t=72q z84BAe3A{LeH@tUa98<`CD$lvgNPijh4Gd!G@)h{<*SatU5z`e>ZB8!W_yZ}6I27)#aCg9rvP-0Exj`Y#( zrxY2-7aE^V!sds-P8Hy9p1%mkyytbe`MQtd^Os$O|N8sCL0{TpxdxBjb2py0(_$>_ z??Y{L6vaY}go?g$^)Sj@Ky!7%WG}&=p>(fFKNC*+JCO_m=MN2qjFKXb*dQvS{Mnik z7wIBDzufrEn{n4Y55aJ8PdT|#)+G7(NFTgl7XL!N99VAD(nQ&e6Milg5#111`qC?z zp>}woh7gz&KQlY_)X`-q@8WY@{4&fRD5JV&1wUboW6zxzU_KX;bbK{l{P!>9rhGX* z^|7;Y>XGlp8(;eOc-~HZn8(}$oXQLN`FGcngXoCp3T-%^2mxIGxUm7EAEGlNi(=rf zpR+TLIOI+E#^*nRTfX=?9C7el@t05E1wmsCN<3Y({2bV#5n8Yrc3HFl13c|%jgE0N z17zq37i34j<2X97pA(O1IXJo_p5`n8|Nr$2Tk=l|RAivRcL__IaUkAIQn$yzd>V=pg+ zTqgXQBH^MFvgR!0W+?svrkzLsi~js+IP%~(hj;Dw8a(eQ3wff_f~;RDKXHa^ zr{&2<#?RSJ7r_?#ka5Fe_Gw>^9nUzYE)=BjbEuR7G5!2fju`NUgZQJwaozv{zpL>;9Yv=3$LH^cV-9%( zuK&d8xbTeQvG1#1hG*}w7`4^Q_%)`De!d7y(5S7A*AdNI4743Gwh>-Ls}11&9oOqY z9s=iJ3CnWr&L#EhhZu6!h6wpmFH=KU`Y=96+?yJ$6tV`V0K#Gcj%5NC-eK65y-cUL zd_*jtiUH891JBvBAD3NtJ{Iyoq<|(8e%UAo4hD|@@y-V2PZk+e$p!0$2yPRkuVYez?-z86c|KEVEAH@@k?L8eRnW6m`523 z;p=W!6TJMNpNp???fu|8_s6bmL?s!6oEBH&0OdlFF9-$5o+2;bQb)N`h7AMO!i#Jm zLa(E6;CUCHq|9BfJ@m>57#PO;5ZE9>wb{n!ue}bx{QU|91@dKVoBC@_OkbXso;i8h znYOycqw*jiXc~M$7(t}L&wH`}jqx)CnXXfXys^l@3r2$sGTGWppYl^-xfo-puZ&X2 z*k%pw>ImdC=a9GVgU|8P!Ex_>GZk_^20~znEgz%=G7vl#<3SAg(#-`5C>i*LCapJ7 z8ySQArI8|AA%B)@kXkHm$UHV>Af>|e{KCw2I>SHw`C?pr#`|#Dxo6=m4q0m|c#)ko(D)SW@AqPHYnHB5c^(SCDZWL*~fd)@&EcJ`oAx1#N zG07TyLuP>o?)_@&-tAW!r6*mW^XYXWI)4PP9e z%W-=bK5@b6-iZIRU5Bu!1jrg8V;uvaI-wkZn;gXf5kDyfJgo>h?}My`AZtSYHe`!> z6q9Lx01VLx6n9TrgKvNRf8p9IF2uXv@{eJuZ}B!wBOOHPmU~Ij$A`JMRM8gjls=75Wz}@^Njh)$#lV{IEeKAuA`s z{1BHSu84ud$_MVpy#5NFviq(ud>L=^U}*j#{Op%^;ogUa!AC!z#1)a!x2#AjMOZ2uCO%7Ve+dZbKIiE} zIOF7FQA7*!XD1zBDlEUl$~Z|{CV}J4v6mTpC_Q!bcZE@W0_~s7ODF~)4KLJ&F}n0Y z{OjNSCBAy~XK?1Rhv2!p_xl$i4i%CHdGK&1X6Ydb6hniE<6*eMWEtZ)FWTgmkej1{ zEp4%^DW)!pzgM{E^U460Whh|#!FGh)^9wZQc%a9Yw~^GxF)^|lV7ZGbAqApiVmxNKGODI}S-BPa^)=Z`RJI zvclbg!JSrf{V`#PN0zO?H*Wq8=%oe&#Zo`r0AMc6;h&vp+hxETjXFYp(i3AYBfKbz z35XUrClv@t2B8a*)~v)pA;1ft^JkDh%Xh?k-iYVy zUV&=^TrU0T$E%4CSBs>Efa@55-aMKA4|?o`!kF2t(xpCqs=2>QH34RWSqsc>O{C zGjv4guzzyTr5h%iDe1j1DkHE0H@A%KO}VfIWWgDNgr}k{iiu13&JTV}1sq0;=?$C6 z7yi6XvS!e%zNBA{lS;%GgMbq%iXy~u>;?Sg-7rLxQ<7&#dX91V<@hA}l9Ihhf`G~p zx)y9}93@xB|NQ$u;PQ{2jpGh|GoHOih2sRjc}FZDbO87n3p`%7WDVJXqZnX&JV-8L zu2o0c;AwN4y39qcpX0q?uz~_tz=$oAazrPGsBlF#Q;fxdg27v4qyN-dIRCU0Fvubw z{{649XrO}KcikCFAAG={Iv5zp&(RgZq?^wQO#5eG!w?0Y6t}Ygh5q^Y?R^j7yo;_x z8Uhn-P|I_H|2f_>h<@=#zdVc3J4>&G%HHP7q0Et(IwP*7{)B~s|I7}ug#VGTGkkKn zQ0FXrzwjx$!|{{Z$ciP{bEo+@;@xk@6(2hlul~=!$IhJmyukex5z7d;8Ce>5A@cMc=#?NvgPcb6S-C|P32M&5Q4n1%m z{QHYu$a-2BUGWGUPa>mojG$bEVO^|XT9;!kV+^9A^uVwc^v6Xw8%S$dGM@`YjEqyS znK+0GJorl_7fSrX04^pB_RqZe%kZ8{M%~S@X!l(pfA(y6*&}$$;`#XCyAHx9K6)lz z`;vdgt^+Ybu3Pd#Epwx29MGmwNr}mCK>A2y*^-N#@Nid$pO_pEkda84Fa1_pN+X~g z8NOtPDDYV}``tL9Ui`Pu#5pG&iub;4Uo7q~pv(aS8TQLdr1AVtJ#3Hs7vX5M$+)>lBns3qz#kRWn=(RL0*DnEvytm zfa$?KiV6X79Z!%$>RK3t{CX853L_X}kg+YyJ>_ML@o6N|!6D(?_V67;WFQjma;&3R ziZOqvAEl5w%!c$)Hyn*nL->F7tle?+VTa&NulgT6WtT+&=WKIg48M0z{=Uh88Wxt+)RO4=k;L8qQoH zzXbZ6n9Dyq)5e#%0Lm#(k(+9bpK%*Jy#f~&7z0D)aK`jh;ouU~~njmT7nNQgj?|sJsxc<{;Vj-=)f&*ffG7te}ri+Xl`gc5=~?O1F%p+#n`+Y9fCaeuAekwKTvNzVdZE_!#F^1hyI-rtmD`f3sX^ zd>iwd=uSj~Pfh{lC1P~-YX4_sB`n~}grqX;nW4w9r`>fWKtlEj8slpa(BSy5|M!nL z|BRFI_PzfXWhQQnjUW)?guAMxK3Pf6$r|uj-Ej@dxUPud1M1#d5pEo8jwLvzkPVC4 zDSz0U)gufnDm3Z>>PaC;vGY*b>&IUI@_d|h#G!c2%l;j5nh_-vDDtI63_)qHlL*Wy3 zRCGQNz2#gRA6|)!Cr$tHFaLmZX(SxH->dys7U36>TTElv&^2UfsBKYX5mLbXDd!K1 z2N3~*fnS%sYv9A!ZW!jGp_TH~#)px2YhnaVp3+AwuU)NUfK&C5eO~HMZFlCI*Q!7L z)_?vh7cBy|(J&8W>?z`+^K;%c05AAo9wKQB`zfw03nGL8%eKT}=>M2c7?K|>Myvc1 zkXd~D`#;8L4QK<96IC#+P({T(21N4dVh} zxgHw}h>$y+0eG62vEN{u!MZKSIWiffao#8XUU4kjD?V8>32gy*G0X+_WSnyZ)tNRaxWy!H9@i1OrG;1Cv8{PxsAVI;Z}>r_Oio{W=ZP-80kap04Lq)m!PU zx2oRrR@J??b(%g%{lqvQvqR6aOk=LcaxWf^<^3lEN|a+j*>u>Q+8>iIgn)(KVN6TL zX~5lmTNPKm{_`3du4$27=0iF4v!Z$a*F9D?`nEGae0|V@%@$%W*jWl?Eha=7h88KF zI1H8=I8NJYkTXyYH|dQJKY561fda!axL%Yu8dA(PaoD3Ul&-i>S8w_5Z`3Pa@_fy+ z>+(k>4p-G-cT%#0vEr}{yc4SUAiI38i&00P#WOucro$x3n$Lq%o*eFa{At#bKcCbJ486=^LM^*S+$ky6Qm>Ktb*Fk(vcstR0-8a{3gU@rRJ=t*D@NwNC1kG>TrorL?RT$>y>{#HC zX;p!fTyf>QeqfKD_sp;6gZZs``2EjUL7@&~wqw4hfYFk99H|3x?Xp}UjeSd@-BuD$ zO3$P^B8hOw@p9HT{U9M#^jNlBzxxcuVla{a1o8R<$J>trX zF}Nh3^zE2u{a9C0tEf*|ruv~`0cg3`Pa+bR)9o7$*QiWKUKvKJEK|yQjJtC^2_3%; zOQcO5<$9@SJ?WwP`5*rQJ>r3vX}tG3ZO>88jPrQdSErSO0=r=-_syrpBW{SIsKyAq z8fT)zto59+4jE%fyC^+Hj0YQ|o^t`da<0|$?yNrcna}CQ+YadVrBVUbU4@OGx^`D~ zd~iL+QA?vMW;$)P$lyPLWPcm%KIt)!)Q^1scWZrVpL~-w+s@@floF%6U`(>x%nbs!U3l?-iEB20DykOtDO@?hI>!abea< zg~S0xi}XCnPa98$fJ`$2WgBc{*a!qTHMi8|I)ibD^{8Cq;Sar?vKlD_?Q&(zCb^jsC=HJ!7qtDLJ1Utv&#tPFgd zUC_r60&8w8D}p_}(D>O&v9ndvQM zxpM&XZ4f-IU2M1yNiKn?rIInuojl_dXGxcwf3EIxuS<2~HJ{Xo(}Cak#h=x;z3K(b z9jQ&emVpfGxgsQ|R96g0wgJ;4#q9$jV6W;J82AWMxD=FyZ z{cGCBgnh}g9;5I7j@NT~{A2~LmBV0F;-ow9L2czy%ae+^Jm3n28cxW0vye0A3Qmz@ z-d34)r1qRXf74$5`tQ6)Bx%63?z9Vy*`#H8u7DyPacZa*Ux5>9{p1&R-*3DAI^Fk* z2Wq^$sO1BD)y+z6=R?+G;eO8>UdQSElXdU&J92LWAJiS)Nw0z5B5 zE*btAFvTQs;cpeYMCY zYiaK-I(K14ZB9qO_dkEDp8YjX(S0uHikXE@uIM5CqPI#%XH+$*RfqZkj*{GvOY$f3 zvkWJ&{2GXw6P!1#u_#1?sZnYN8}H%jKo7akF8$c|e!H%||HT^YyPh!}X@QgWrQ7#v z%;|jL0`%M`<1IGf;)y`;7Jt`U61R#zo!>+k;gU)7n-Nwwt2 zo4F1h_YOhx3}s=&wdKBRjSkV8v&jSyeVf2sbK<_ z@c7wBPE#3=Dd;{|l&GkqWb7*-*Xg+zZ{=)zEaD(FGC@70o#AKMX9ojJl6=Pw#SFWFR;Sxaji|9$im z*QscU8d624^~(}I;SN|_t|a<}(Bc%lBaP5od~5sBcEjvfy@*{EIco8XC-EH&EV;~^Ga z4g-8MSVT%!N8kFYXXsmA@e+*}_bB5Outokn%Tf&`SB(V*^rt@HOx8R8#Tn<)Y5!Pu8C?l1Jb}PRMj$c=qIPn+ zv%a)nx83l0-G1|Rdhq=&*B}1Yujx?_zC`Esls-zBHYhSw2p`j2m8eaABCiVgb2Y{{ zMnN+CixT#u$DGni$jN17_=g};(0QlfmK^tJ(b!zKmH0s?=`jyESFeA?H|XWh`#Q~{ zbo;m8s-3f4wJ<)$DC17s?Ns%6)7tU5h`gd~Km_L6s0wTrauqWRx@~b)zxj?o(B2g( zrz@60SJ!^-8eM$ZB^s_Ss)qr1;MVK(&ENO}z4Wlj2(#NixQ*1iIm>tv1~bs z2%(a%ON&bSqKmfa)h~LszU=BNwYv8Pc>~eKP_e$CDz)2fjX8yK_|BoC6~ITT=E9`{ zuOk66+y?T7Bm=l-4wRf43Y{D=pk^=_<~x}Va2xadul;J|_f%c|fO9wGXMRO_(Q3Vc z$f)sfNXY05phA9?lsIYpgUba{rN{|i!Pa-zY)3Ot9{u3U^@DHxX6@o2f9&yYWy+9o zMAnhs6tB-9sI|C{+H?W$7^Y= zzow$dQ@*?8RkJjy%8@#l@hz!Ca01x|;eeA(hDqnn7y8B*JVW30{okoDSk`EDzs}#i zAaN?1aX8UNW(B9v1t;CDI8duv*P|YGh2H+Z{)g^=>3Q0X{M-YoMGD&JyW^TX5#R9m zL~=yuWX>p|&gu8@fT|e}!KDtG18zsiJ;CiX?IvHTjLT-ctZVOmj`W(BK40hWnA1h) zY}f6#-lVzh^Aac2!8P-ZCTl7e9hgF#Py@cRWn?fM7VV8IG3Jaoo8}6uZw-R~nXx0~Fu0u;-Ik9W(cG@yB7HheBKv!RRKmFGqeuFMz%$SoN zw`BZ{*Vh&JsLNo0l%9;Yr!7DU6FGhtM6J{#th_YgAOzqUz}1biF&W12s+8lv?Q8PM zFNTE2YNf2G1mM501yC4L_t(RRFYA~q{%p; zSv{~{JK5pqoSt2@YhM5Ttv{?QE}Pe`miX9X9;dkxl%9!)a8h*oMYS8!vgD@YG_IpU zQ;!Qxqe;T>cbl^qfSyU~OGCxJbj2ktz44o#qgTG**}Bg~=gOztfqr!PU`4Kj+b!pI zg-u1r=DWKEAMOf+!9Ryq#-MekY-rCY>(1%gn{L%_|L!}Jf?Zx6DlZTk9bH*oRd0^P z*39{1Yxb1No#lD@+km6h6%9FM^^L;X{#*6@XMB}j_2RG5IVhC2>h)_qQrH@1T`9q9 zKag6lLm`_ug^F+k)9Y}?`pYEBN8WaUmx1SLfLEt0;(G_l{OEPz0)02;B=F%}Vfqiz zZ=Y$w^O}6>m(!0BfN;NeQ zojB@t8v(zJG#lmhlCybQfb-2(QA%t|D zE_jOxnIg29#w{q(h|BGe?tJ12$iyI+`1Eg5k35b=M<4$9r}Xhp-w0hQk%jY|2rRLt zd?aAtO`KC}MCOlgmjW%fn7=L7{Fs9j%WWHj^Tt;{OOO4MhbkMb%6~H##i%e^q59dM`T^Z*ccBZmNz40}(zqgJq@oQ3IB?ze32r#^cPKUzk2B!< zri(gm)HHL@pr%Z}6OTiw3(-Fx`C~uuJ@P4FfzjJyLtr@tM%rs5CZCKO&h<7Cw-}iP z4MdpqNNEmt_`J)TomjLrV6*hM|MWhs3?zo-uv&GRJ=tBwZ=@mvPcZ~VL}+A1PFS)s zt5`GzasRsbn*XRLJG)0d^a}mr+kQfAK3vS@G?>0d^ADT$_)v;TuT+C;hFvQKP_b|; zNUYC5%z`CGI zEsSXSXmV2G^z;HBFai5DG6_W#m7$1p6uB32Ru1*h``=GL|I$vK>>L%g4l6KFvm> zbbZq|JWpK|(`e;@W;p#TIc*7PBy|E$RGtMUM5U6s(CYN0kZWyJsp!tC)tlAt{{A28 zlh^Fg%&b?JSWV<7PsO}n^N(lf^nSeIT1A)4Y@6fYV?{T8{xj*y_lLgsTeY*LbRt_Y zk8-YJxrtTAJV9z2$6RAhOeN0-)x)RFCvFDQC{4$kq(C4Cj6Tv7bCyVQfj9LcXK{M8 z|G-j;`(|Lk=`SgI)FU3Q7e4pvX*X)XiB1TC4@#NzWWjvOvMj|ZcvRtIJE8pXq6F&rQ1DrRVFXe(3vjIfg=q))6Dxu~HkxGy2rXZ-o3R zS|ZCqE*=y+dc^!&ehnPo%x!9+7z*Xx8HK#11N#qXJ2wKe7}NW2yIHS#$#eD8uY4jr z-@-mCGR{U7>Ts3jL8cgK)u7=3<51`k0pgL6Xm?OX2?Nmn z-~9bQQkT^3*z6#abV4s$PvsjwMcl3f5F_i_LC**K=DyQvsoUpB|hx{!O4c5Eb)e3x;$d7Ps&#aLQsOJDXS_ts+{^$5PntMbqE zk4Js7l^}xJd8jvINYjH(8?KXBTEiv|idt!08M5e=b{tOT9zPna?QMYu-hc>~7m_7{3x{owb0m+pJC3*MF-()t*_at81F!^l!*VEn`Ou`LuS{DoPDlRoVak)3~uW{g3wzOtx`&fBfmzTz8n`MF)SV7N_P1v1R& zf$I>hRg$Ve&6)d1ggkyXAf*W&(Wlx}!uc$>j0!DFqbU$cG^$XL*X`uWFvg0O2bK^1 z{5Bz7z2pVY*F_hervta|kxvO{x?M$#rX)wM(*?hA16iso!h1s#hM^H+AeYh;(p!q; zt4NQGVMS|RHy`-df0h4V*cJxCB-;slO?x3uS7mC@Xf#q_9%Ok=zbcJ^grsAV)P$;?3bb(ly0B^%ZWKu`s#?*`uB7F}2 zNQDd|w6AV&R)72_f1-&FP|8fdbs9e*Zc4-xvCXTeEKWO@~ByMG`C)NtLFJ`z2>D))z^RRGqijAf&w3@ka(nm<8&@x zO|FtlHXdUe4JatOtugDab<)DrrT+ahL}&VE$lz#xod!SnmYHRh0I5T zzGgYS>b490K#OY?Qs3iZPTd!Xm=4=>BHk6TtLyE;T>3PIAC6clU)e!TP$|$$%l_(H!l0o@U99iFfl{^B1Q|MbaF#56It?5p7k(=VoTqZ54XOOJAZV ze(9B3Sz1$@Yc&5Nfh;@!s0Fes$!q=_xYmH20@4i0r+)kl)N$uH7&U)V^1Y7ZP<0!w zRq2Vm9V^LX+3j0g(cJt3wPqR)M{0L_+Ou!J?tj@r(!t$!i}frGnG1QADTI(91qnpr z5ntqr2|*%W)1~OBq6kf3LcriE<&$;u;E(@}H{GnYwTe7aWtpThDSARLkK@4=&^}DH zS||?qbXw%)?}~5GD#k*zpIh$elG#F9U$692KmH@yv28{doU>Cc#_vgwe~fx@|HTBNdMFO-VbfF)~PB^s~~#_$(=qPG}5cfMvhNQcEe@zlY)_0Btd!6 zmRNLEIhIdtW{apVzW=@SlCQm5=wzMGWR2{A#YEn|YnP?1*4#D?SJqNFr9lXUYuAMA z8#VRV-%VNjMqPsk+%fx^);Xn_pIwmqx(%L6cFY4#FfX{^JbAr5_Zbh@CA;PnhHL7w z73uI1Dw(WfzOYDyVwvJ0{HH1CCQ^frRVn0`V{tMf_B$xLjO&IGAGFzR^ZLhset)_W z=5R?0?195ZMY`u7w|xRY!A0hU=&x(>oXJ`I>1R0jXcuvyAO3;w(gW^yfo7RPD=Q<- z&9v0vL{R=M)*P*;JNv{*M*D|?B$M!xGczgh1zzME4#txnFh$a!(4*N|soRmSq{PY7 zrI(z?HD44uSFd^bv-A~@d4%@g^jU2yN)=3^jQJ9hvg6d5I;#XIjBP|V+JGicWKIEM zo@*h`G#IX{zqSG|r2=!X)uw&c-7s|YuOI!i-u0fp$K;g)Cxin|2R&DrJ*j4$;1drm z5C2CZCGsS53X>^LaS-HK1;vP@B2#)4L8Y8j!|O7TN6Iip0`p{k8>gO2`}EaMexj~? zzy;}JH`{4rWLIP&e+H;S%(u*-W>Y9m=wKHnQTxtgzk!n+w!aD5;6p2aF%%&icw}^F z8y{n0>0>d|Z6&_P@r>0q|CN5`RA70YjN_Ow>s2lsU!+v@wnEC zFyrqwK*c;s71KAfVPs@;NrOW2Z#t4J$c`qUnY69f*80-zs)nojPhaxIdhrXMjM1aC z86Tnhl+i#cMM;7gmE+aTQH#TPM|4e^{Uc$sSJd6KafF(Nqo{?2IW{$Qi8gb4X>m#C z?%E-LKVJN-r|Yuw&*9WZTB0vGa%?mHBXl+FGEahXhRHI~uJobpM3MH|RE%xZ7Lqe7@1eFswY^uDuGf!*=NxjVg01yC4L_t(KEVGOabi21r zAHU{0^`peJO4&}=SC+;WVH}Td0oEOl2y(0zySWw$xy6ny=ff5dTor>B_b;k9yN$^w zPDd8?@cUe(pZkHAt22_kb8#JkYY#EO6$*)ul>PR$?Xs^aL&qO2a;PQAe$9j5#{(%3 z)MHHQbDf29>MlS_Gwpe%MoS^I6l1Qu=%~YPSv(z>*%_oCc->31Gw&-57ByO5Whd5G zt2e84KC~tMsi-f?OVywtW$L(~mI;xMNHjfUsz6jGk+QzFrV2&2uyZG;fa_WxSGwlL z+tkl;CM!Ipd^%|lwhMBhU-8IL;?I3K91*#UIi;#p@cj%J(3#@;l3L}8sa`1cQI`ruOJav8lteomMrb<{rZKKivkdXH`&Q?!t>EYom#5ydy8+ZDv)cPo&m#Ae%xiQ9_JB4WhLRcnO&l$mQ|@ zf}#2$C6yyp))k08)zvG4=C}gxknMiEq_2IJ+g;c>`1cI{+{oLUyusvQ;M?*dD z+0PKFMoej?arIoD?i+Pwq}Yf+qC&DrN<0C$xAqUl!7-)$ra5AS5!eQI+ZhT zL_bRIoehOC6#_PD5&5PyMVrnOn1?yStk_(Q`)jQAw$}SYed1Hs$aO1kQ&Y;U<9%G( zNcA~jxH9_&2}P@|HirNr95z6VaTgLP&^zsn{z;6Z^neTJ^;m!hofG|BC%~;Tu+(rm;@QBcfJ=C14t!p8c+RCKFLq6r3)}(9l z5YM0+s+hNdIWIl(!58bwdtajVm~Zm1uZ(q+^PS052*^sD0SSns%SW(1GpptO`_)3Q z9Twg= z@pGSW-Yu&+rg6mmIO*G0Rx zYyH4&dh>UFyDr>4gNm21uVi@-hGQvFC~qG*LZEKmuHGQ15d1y}rW}fcGNcGEmpurk zfr)GC$cP^bKDXKqYhU-3kJH7wwyDJidcY}etH{;nx^sPPT~PsO^I_bX1jNx7LGt)l z%r)l|c_F1_mvUG7_@+-K;MiEe;R=W zijv~P7RLj%3I=M_*Q2h!N?-ZJhiW_;=!6>ADexeGL4U06b3spf;uEyIw5XyeRMKa$ z04q*^?mU-aDk~LRIYCL23%|npV;+mSr;mT)6Hr7u$vbUd?n~avwX}3Vt+}519ND{5 zZ5GsvpZ|P4>;ac1_0*^+ODHnmWH=(8#wU2Hx&bG8W^;aKn>kNxKKwxs(+gkl0=4o? zLrxdOoGE?sGaf6?SQ&#pw+~gyn_d={ks_`w1@7?WTrGC5L#{7YR@d~$fBL6d?MsnT ztsY-Z{0)X2zK3D{Ce!=~Y@}4gGFCtyIqRxkQzPUf!dt&aS-i6f`nBh;q(hd=zm+Rmo2^2Y{tBMaRdXZIH+3NH|Od*FiSfI&Q% zY!JR9=XM&T(MLDA?!X4XMzl8|&wbW2<=+tXT62|#%N$&9L$Pl-wecOk=YgmcVBt}Z z_#!>*p$}nR(k6?xFo;sNzI z&8paa&`EL~?&1__Iy^-txS7&SzAVf2v5$X3DTUEXJ~>ZM7rCk&OXX18x6dmeua(99 zddicYq^EwxBjoRtcdmx#jOCpA=|F=-IQ3o)dwmSK4ky74hclfS?O(i|onxg}zw%{z z^rIfBH8u`5W~%W}Gn`zkt}II!ZiCfTd7xcaUEU$1j@{AQA2_Fb-uTZHg%I@acmJhs z-@mR^A00D~B0F2fJ6E6Ux_c1O9*5Sv$y1KuSL7LUp=6q16k{k`R_#_UUkR=)?bj<_ z`l3{a=ZLRb1JxP5UWd?>GjS@Kf=8mIgnQ=I$2jj~(xsPNLLVz_UzlfeJkly-pS9bG z3A5NB5qLG@OOU=tJOgjT1)0(yk!>x3U^tcaR&bW}#Sghcm!DsxlM`4GXG19*k#(AY zu7p-n9m#LFv0qb9)|*>7Hjv}Q)e|27cy1ATYGM2h*SVjgpNW6^>S=B?e42C1(5Uk` zC3Vz+8f1#B8?T{^(@>`dHY1<>43Zcd+#n|1*OpbBhRFAF3I+EtO zRJrWZOBh;g41Y+R!2@U?gix;!f?v?t;0vLasmI^dn6@zwrfTq9nTnqBL9h1{VlpbI z3hcUU{3&Otp{$_^1Y)Mlv~#WO-=oKW=_B>Xhn&Z*Ug>9Iw6Q>S+CH;0-NgGP7jDy+ zKk>`bX%04^+O0yZG%=%8YZ=u~U{V6)6DJV9Hl9exz{5pPUPj5gPa)&HXz7~Ie_s9o zvOrD0@4fgglH+uCQNz)?w(aH`kL%HhA$YHgFVbsX@pAe9e-1;Xk5Nt+GvN7zrz9>D z44uTM9>&8N*c?8_Ix2%??17ItcRd5J3u>7_4zfiAn~e2q8-ob9&NDuOCb z$2(k$Q_Sx0`j3I)-LX%ly(_+L&tCoehdv|)bCD|)ok>_H>9L?1G%T1mfOCqKyVpQv zV+4I+ePRXTldCZ@d(vaSRL}gX$ExI}xS)z&u4KFn)-ayYKMa8C_2=4DKh!Owj-AwZ zuiZjxN{@fsS! z&wlz-(>jBsjB*7)jLNNGT*MVLzQYs56*f9Ok(18W9P8HV5uH{HZ0O0-M%Q`m(-Cr)Ah z?h@7*sG?|ZRAqgKxsm3>BOY-TBN$1G_!WrtFe4X23SMZW-jT~N_?@2RtcVcQlrjAp zP5{T1Q%_%by`LcWRzU%QWB7$km4O;b0#>CqEzETbE$+QVPk+jjl5*~$VY~ntkrQnD zrBfV5x)@>7z{90b@I2&2st$tE;}qv`#0n^9@xZ7nhGu5;x9y zl92eE@I1vrobXj*_>IRSSyy*DZ46{|3@4>Fn8na0r$B~U;el+P6l8%Q}Qzk1k$u2KM2&PwBYJ+blky{2}IBzj5IS7 z(s$*8^LA>ki`x9JxL!LG$+0dL4=&dKs5D$D)1w{XpoYL*Um;?MKFdS8-fMv zc6nu0x9wdLwacYQ-f|~R0*nAQM=U)`lE>U*iv6#vuewU|T=!h|Ku+-(TxKdxE%jKs zF!UxK0t8&1=TN1JHQ{fuH`O=Ze3Sem%Kl3XP7Dh6HsE|4kyA8~ofu#4aE-`vs1o>8 zBV5<(k5?;?8m%v(WFDo5UU3Q68&VQwIYOGHgg~A5bcFk70VYK0$}29@IlFc;4d`>f zuZWV>M*rf*1dHTwe{0{hAO;hrP%9|sh$=EmEHL@V5OOWA9HZs5000mGNklR5Lw5#Pm-RAFk`q}J?-bEp?2rQ?V`dv!NkN79W z9Ydq*Yio&j&yyA>JpIACX1g=#qg7DP*FWQFdfW5yb!SaD5%gE_Zh3tZ7hRacAqg(E(u|cRMG!;E5!HN&pU{LD9^A~il z%PvzzUxks@ZWT#SdWj^!-6s1@sPRdhxX9?sAFWBe@to83WXQ&{4CtneO~w;BL7hdt zcpf(J3AEtB^ROu|r5dE<>y&wlB2OHdyKOVl;~)JP4cGhXnC?EnX(X`$QSw_#AUbx( z@CV;HZ2XPKrShVsF`KEIZoVb0FsE(dDbey>;ij47I0Ylw zUBy#Gun~b)N#sZ}xk@wbLTiir^txBQOgmW$vo>PewhSP~uZcSJU6A z^U?Dc-S0jshXeWNkLuk>WYZZBV=sEP(f{be4o~F>lgWytQc9jODFng8! znBGDstL`FV(NUfz$-e6^{hEB^_OYgpQf!NSPfCFCaC6qP9^s4#!!OPgluV4IKbiMw z+&z*YkZI!5_jeK}CFOFG=?RoiCwa^#`);R-m9i|8f417U6VrxX2$RaF#QE_~X=izz z0D~4;BV46L=$IFZe*q$L6hBy7(t{uHMY`8T=ck|RLY;F>%Jn6JJ%NVrv7#Xa^x%=9 z02?S&aV6!T|I+%y9{eD!Eib8!(N_A8YLOnn=1E{sVA3WiP=Vx$mI4R8B1)=O#{K?4 ztyW88P75OZbh*;|_YZv}m5v9MIkh8^F*m$mjK~Llw7#mr%A)Rf`K5aH(;u&$^OEOC z>PhMxFUkooB;slEf?so5Fb~Xk#%SdjGY)T?+eS?4^6}a}m#1qtUu9nP&w@-l%S0#PC|`8i5}!t7(wm%c^z0<(mCgyFKkZz&hP%d)=R0wDcT*C zJ*c8Ph`?_8n0joD5a1R#dBT)=e9;B+MxfJTE==b3kbB#lxiMfY$(HikZxHJ$Q`FbHIb{dvA+c`SosM$! z${5?my7ksuZ%dnOXSQJ&6tIJsZ+x?|w0@j&{0M;>?*PYNb6qm4z2kky$;T7^>XdQ$B^N8VRJp1OOt?@> zW22KU5t$?=L!P43VyDIelT-oVx>yZ@8|nKW~(X}nLHX|u^PT#HdV7@j6=I@6MLuk$*3(&HYZ#eI8JkPx}b z>~%Y-49Q{|B~MT&B0u(169kfP;vpEUt*hPXDlgg^v4IGAOaJokAJW=TNz6wpcStpN zU-SqJOq23Fr^gYyWQ?w<)NL1B@vZ27_r6RsZJ}+<+H?uW8w3Kz#B;C#q~mvYRmlkd zH9Y1!cWkfYd}wgV7&NyMHTR!$>6S+pU&V}4Fw z@x&)2orQ2ISOMzdEhK)&&?)l3FS?;Z*P0=JU*^#v=kMO74)dmB4Ow>4c|b%YlyFw& zk|t;+at*kJaYFQK3#W(#qDKs90~efsu3F4{8-W!E4j}{u zf1rcX6VqFa>m)FtPhtmoDKF{vH_%LwgK|nCkWr`plbr~Q?xKx6|1fb#A~b~>C!IGf zum~Uj=trg>=+8;=^;gF`TW3v2>JE@_I=VCqDp`|Qkj8_qe1K-UZFZNH+%xn}^3xP^ zMD^1sqeyB(tm)1dm{du2kV+J{X%U*T+~yFH&a}i;-hfk_jbh|I-CRbW%JnrBT7O%1ur(FQxRq1%ab_dpIY{z^}D48#;`UeOf#>}*+{b8~>A zAklxCYCIaqSik`M@+W;6IY~Ikxs^LYow>#5CXkTlg*cm0mN`e#K&i0jM@*r5CN$v*>4=`c&*|Gdl^Oz# zOvO*-pe1ku{6%GaoQ+zHn<8^OAmwv*Zr4>0eX!DpotFH6Nh)`<7Loz+G;^Emy1zA= zp4|6d+q8500+Z0PsnZUfPNs?+ntqx|QzuSReDYBdruL92U;svC^3Pf>t*mmQL(*yN zJV(|J+^#Qq$b z>hgUrKVM(|gfEjnT%C4{5B1RBw?q0yfkP;VMlDyg0kHkV`jig;Wi8hFWKgMh*$D9~n^{|J?C#R`w(%gjXvp!_7USv>* zHlS1RiLy@geBQNVmazCZm2#Xw;y0vs{n`vWWYqNf-HbeS)C7%2WBCIW$lB&^%sC1^ zd<)y=#NmkQhr|sTHDPmLG;#anj^Gq><|@q%7L~d6oVfV>^A*@UIc`aKS|pGs0?WYV z45!yPv{Ozcr{^59X$(IR&d|flD{JyERcY%v=@K|}O4cJMMoK#OoZSp>m5_N6sD8?- zQidT|#}0nSsi?z7s%(nh1WV>w6ZSf5!l#R0uaV>AmgT_*s@epLYqP3|%%ZM*!2NZ> z?gAww*$B+pk%|=TMky;KhjltX70g$PoYXO=(*MPHIlB&F{AO98cad}huW^C-m{JHd zw1a%-kK~XtCT*T&_#n$uuDsQje-qeBDJIJ4`SJ;1CrB@O&a-vSoMh!qRPJ~j^`$r> zZ24Mk6P28#Op*@4q|2vY$J!&W$y!p( z3I47wLA97p;_^K6bRFP-Ms8TuX zYxj-?-S0m4B0gb-KB|E%fg@okG#&xB^=eHBcOQ6`Ey1+}RZ+@{LMv-)(C?t&R?(s zkh&diKZZl)d6slYIzOf1T2vyNX zfw?1*Tw;brN9ry+*dS3);;Ux(*cjO0od6q!R;N|>pXehVbg8z@^fVmw<&&C&D?YR_ z#|`I-$R^6dswspaudlCb+#hJ${JdK2juw}f74nwe|ABu)rerw9T~e;-i=?QK)tD>H zhjU_luKxpAc#oiQ4j&Coo>R_;;Sd62C;7J){d0gNn@L+5AyI3N1D=70YeHcv2jmSG z2zhk{WqGawRGd@{F^VEGbo^ltd5F^T(j7d3v<3uEO&5_t17SQ%yS8sv$tKDYkQamz zqR1%6O>eqoAL0?5Q)+}P6H_qdp_U}mHP)5K%kh-3^*YHCV5~eCz(d_I(<<`xOCUv7 z&vqMZ8RO~nYBH!4an!S<_BX4N4Z|kgi5p?(0poFL@<9moL_3Ifsz#DtJznMl1?$CB z5sGv>CSUdl1qA$%2VE&Q;@^thSVe5BvVv1V@|+Qle&$&w|2Ex-zF&Fe6?7HDrM7aB zi>97hYBhl{%0H$Ygp*&V9QyVSo*GplyTD{lzm?=7hh{vD;$EotyVvEK?{?G2D0nSJ zI>2#A*2W~Hf2q%DLOGNYt;07#_IuPFsWJFG68TQi*qt(2(#TgM$!q0HzT|3+pjTML zUSD-R0nfloxg9nfpMr%t6-~iBQ^uSbGav1jWungIoN@Ni+ zUNUxBkA3W8G{b4BXM;JnUu&SvX<=G}CXanf%-GCn>PZBKnOnz3v!`>c?swm`=8jNE>7z%b!>=$j000mGNklAY<$8OM=XK{s)F(qq0h+>%Mq!ST39d-z4C!7B&8n%(BTio zHi%8Q@i!7qg+QdAK0(0G;m17g;CdTdT^0J%SC%9tS%~=`Efb`mdf+7sK+O~7e~_G{;Sj~bh@6W?XPBzCxWM`?%Wz6U5Ue4$ell;(a+VbjfS?FVRxk+)J=ha;9>oao|=0czTFsZ$3L zRvLI^8%^9k$vB4$g18clC3LaS%QalCfpeyr#cU!QxlL(O5A|JJ18VhGF#WyS5#@$= zF$UDv4^rPg81Ri9@GV|votWnIygR2VE99Sd_F53%&(_2MtguDIC^}?8s zl&5`!oUybDl}ygcGJsOVp$w-46pJTIns4%xVxD@+ay|`N5XIEuV@MUWRjSizaZM;{ z67dt5Mzrk~8@Qb&och#6j^IZz;R8LOP)y8MEQK*t!)l;Xfq^sd|B90OAq|K9l&6!A z)nD1GXFvTZYKIZVjwo06rPb|8iNaiwQBbBJ9a-?UgP;U*o-!`L$uWZh$j^VCW44gO zQ2OdGdz5x|GqtOMBB)TKW43B#9#rO;D!`OL3{gt&jJc9C7ciEPTQw0(fEvd`#d4r- zr%;QrSX}FCn02JN-Fojoe~_A$l;dz@LsRl}7;^ngNTSARI7bDoqTX_niU$Wu(Bz4M zQXu)z>___`WhCP1!s;O!sLlLOfjL!?Qjy=hbbB4qWfc^f@3nQ`OD|$pNbOWoo%IOeM1|MW5x#IBm{y&t1BU({`y!9zjp1~uKsY9b(5(I6%YvMj!coUB`bbZ z3?-<5ggE8(XIcflK}25dc3V`SBIN2Xttc;ty6?s3>fRUbKz>Sc+YlNO2uA_r+8vsq zrk^H1{`-KG==@BM8om++;8pnXICim|7dq z7kEMl@@B)|;t&GtXB+X1zUTxW&u>L4IbM|XXjD-e!>%mHD)>lsfd4eiy)V0%R3)f+ zkaZJ0?R^GBDCHU<^)shaZDv&)$!6?8J@}D)l_7tU$iQSk1u0`f1r}y72^{W7!KFH! zNI8c$VdBX{gf~WBji30U?@U=_QNdWOPkB+A&L?Eh!idetlZR6Gy5s_#yK6x+T`O_2 z$u1_BxDNwlb^tA8bnV!m-zP!&hZdUnjKvxkbfEEiu>M6*lf^{Bja8f znUbDrBt*%H3rH;k_Ngq%De#V<0+El`m~_I7MQC+|y`k%G-KRdvmhXTn4xF*GO1|l^(|oN645w;;gOop+R0$ z%R4A%%rnYm=$f3-+r538dM&Z41XZW;6W~9WVwUH@1=U$^8x-dp8qnv{T+Xx>#x6&4M9%@o1p?bCC_ri z0mGGL6p#h*{6KQSfQ*N8z#wSnMEeP$mJu4-IgCU;=w<3OM47{JP?38$Qm>p7Xf%YN zF-NHZ;ri;%imcUYVMv#H_`@HDaHlJ`Fo;hCIf+3yjUD}OxNm~)f7*FB31_N58Nh%) z8Pd9^Q<0KgbB5999gGt#n^;!D;*ft3efvU3d7e8-&KtsJ;igPVJVRfpkI2v0wAKzS zM8QFw9`&e49<+8Esn|Cta;GHX-D3zL$+ig>MUmF*=Rf~>4N%yJ*5x(q%AMc-$-u^( zf_JwckVeE(-MM3j%zvUy(i}eB(a5~l>&f9XFYcmFxjv&A&xq-+%=`;j%GfC9l_6NZV1uP<_BHfAG-5oq08j`~_(Ao?^X!^_>t@M`+xDz`srW#U47>d z{j6T}syFJ%&v>yO^W?A7?tEpT6#9U324Yy7rcRx?%4D-Ev?>dzRO=ccrhDQ9LSE zxwROKD-F5d9RRY*hf&z7$Rmg@>Za?j7uKJAV(b5teay+6`%Tv&w{^BZ*@+4uge-kr zZ19vAf?|sikZ#54*FzrkpfnBLh_TUD)M=@FHuyg@korugCv-0~Pq@2MiBGCNr9Ai8 zNK7%N$>-%@RGs3Fj^$zB^2k|zk+~7skv{aH51Hg*J|IJiDulY6!_9}@;WHJMi4>G$ zlzGjF#}cq?M#BN85J++&n?n~(B<~k;6-HB*ZLU7YLkJ2XXwRNK7=n`R@66*esW60a zShdrX>F{A0?@B`Ahn+T>oVOx7_H)iX7Xz^F&ppP+UD>BY>za(M$G}N5y_qyNo)?B` zCjyUmQ%@7V0!KaHLpYdzN-%pjFySR)*Z?#f7T;v~%ZuOJ zHcv1}=mDWXTCKKbXJ=I8nMN2*t=UJ`U-amf8RiQ|3|Oa@BjHf>%aWW z+x61d|B#;gf^XB;Jn!Xt`M11DZ~B2B)7yUOH}qTo@6YtU4}VHGFZMNV&8fhM?Cv~Y z-Rr8p9#FJX( z*N^LyH{PzBx$j*p+gi_O)yLo~XSOSB+of@DL5pRs+g3-q>A<>fSsLnQ{M(iWx_zzG z@-V1hwN({!3Y~f7z3ozG8{ut=tyz_MPeZlIQ)r!f*2Y;1_hD=rgV@prIFcMrFscG4 zfS{Df|HHrk`V{q?o0 z2j&HOSz`>poM}?zA^nwhvM2<6D=9%y`^A_oUPA$YbR33gNElpU2yE=4`O+`$WWT z-9^>As*{IFC)_VH;b_LZ4IwCmVA2^2_ih?ZM{AwybsW?mjy1+GszR>7+$y>~Wu1;j z3iU5*U@ZOBKVPr!_|ad}mw)~1^@OLsNN@PQAJ)77{$I2>%E{B!u$a{dqitPXt&|1G zHOyxey9;VBoTD(aUH#DJB(F;FiQU|;3v~YF_tpG47iezhdFo*x7TtMOSy%ls)9N5< zX?;w*tuouhIB*>eMk?Tuh2H8(6NL86M!2SRm3L+~L3-ZqDQ`muMW?GVo7xdKlNocO z&4<}KZA3*ewMI*Db)z40}<6d|n1X-{aFu`MEl*>Uateg*E*9u}$%%PWy+8bxQ@1T$#A#Q+B%4N)FD8+tS3^3Y z?~^{)>7UQF0Z>egKxgg``so@N9Z4(NfYXH0mHb0qZqYEg@f|*1q&iqMo98r$8e0%yI4y-(q=u+)v}K=$$`&-jIh7s9*AT>rlB%}b^J@}EAWpZ~Q# z(wBeD%hS*3{q`UJi9UJl&En8b>zuHybLzH^vFD!$)Z~;d>urCb<=J8w9+4GwLeyWRB<|oj=>Pki;h|tYHf@+pVW=H z@+esV=2`F>@?05uD(9$w$PL9bMjS3R7^1iabaH(hsUIZ2B3x=f#RH7rZ3`Hw?T&Kl z^P2L>pJJ&_2bvYF&h95W(e5`NY74h0a%HSQRJP`3XOvO7QBO*=dh>~az<5jcb^1Sz z44R&}&pDI0iJ!TC^bjW0uezrZuLc85xMb1VpvVJ~Vexg_g?bo)Hgcel6w}XXR#D1p3mL<$sX-)brU{m{IZ>6%;C_0zxn2YT^qzFn{Vt{>FT{PJ&V zsb8te+v;uGp}8IBs58s8UBTf6$h$Kd#Z3MM3uB0ZHRh{c0)iUw>TTblqSH&ih|wR7 z@hioUE6a;1{K>viSbS0L~D%8KrD`DNk|P<}!P^85+G zWpoxjLBqj7ZFZy0{K>Ois7sYd;p(feVqOi==oRA}6^CGY1AF`m3Wg4TP53M^)2=)!nF;4-HyTeWnAt;0( zUiv~$W5|(P2Gir9BIeV1>^tF#2VSA|zL+n>+%Vp0?-)-+E{n`Yn&s&68lzJjJ`t46 z5zjBnXnlR1Q@}zyckWEafoZ2bCUYQ!u+bIE%aIT^X^tNcTvvlb%b$4dht_!XxW~>0 zk#m?I|NJhBf#?{oMl44fjt0s28{LAuO)&+qH8uE;FmnVIaL(hp1hlBL9bb(sr zmeEnM8q4vVG9+>%SSe=_TfG#}YDh(p0{H{;-|%MF$R!y?2om#m8oohl;6LPCQP!u2 zW99*$8C6`3L_--I1&Wr5-;DlHts2=`w|O&Z+MPiN@IpqHcbf#|Izwparigy)E`8jF z(Nya8r_s=l*Uyf(U{lV_I2=MPh!CT44>F^TV;D3MO4RgoaBMo|%m{}N6hb&NI&%lT z2x*xynk?`-n<<1g`i=}phJrlAe2B84v3-6{owgKoc0YO1LIB8&bwdDoL?D@Z-Z{I{ zS_&xEfSgnMM6Ot|EH<_jJoV6OnAw%j2wr%yL!x~>vOn4>T+d{q>O&& zSuUR#WUv(uXX|dHY*oD_fs-SF+R&4FoP4*q5;Yeg1ciV`z&IMNu0o3HudXPDOnIx5 zHjgFL&)%}8SHJPc_2wV_uX@Kj->v`n>8nP!!2th@jDK|_YvGzPX284+_I5r_}d8d~I(%?2D+4v!;fH<{0BMKnH zNX*FJ;tPK6?p^B8Q=+`jUYE0zcJCvKRF)d+$1*W4x#&XHQo#6;<;wysv)Q|w?pgP^)3ygZrnf6kG$>I^o_51 zz25hskLxox?a^8+^1 zKkIbT>u^+}6L-EuOSM-RhG5Fr3xqdO(bjQ-dgM0XGZ=0gSYA!s;UghJH1N)9mz`+u zJt0E~3W1M46N0x2G<2)T(=l$K0n?NQ#v(UNCE+x2$HFFJkb4(GPzd2zik_x?JxO&J36YheT>olgSa{o7?q~%%0^<|3>pF~hw z&z1zv1_^lMW5drs%HivL|5!lN@3IOS#kR)9jBZ;Qsbqun?!W&ReccORreFE(-`8hu zxJ|3$Oyj(#(3z83J(ZzQl@%-^?ilhMKa+wH&@&lm{5tHnm6A1yI)=>oVlMvvHBvMrS}y|2}^b&W?u zZs??5w>??fe8*LrQqgN_0*52(&b|^&JT%C=c7fA70s99dRU9U210P#ehr{g}Zu{6l z%d}m=`2^OHAgGQ9>kVKoU_o2Y;S$IWCmkE5QO=$p6~d2v;6n(GZomVuiDRq(l+T?e z*d~`@5)2_IgfNMn*`9lOs39mNYSIHyj6QP8GZr~>q~bbr7X~2uE9D~jtaV;L?tDNt z$>)`W5~XfqrzmpS;PL4nH{BD zYx{*<0r!>7?$lzxRCn7>&CG9CcV?E;^OlM{D9<9dbEUiy4Deu}%hnZ3s7ax=|u0wW$r)ygIRC>_m%|CObPhpyXo1O_3Gac_nP?Fr?1yHz2V37 z6Tk2q+BeL#T(-3uJDNZ56763bshFA9(rRC8{eecKK{EJqjHOP9YGW+raV!aaf&mvX z31jkYxJ`p})>%3UL@(5NpdC~$XQS^jfwQ-uWYnd)YI;Svig<%$rkMs(cq*;Qb7%Yr z5B`YIA|c0&N*M|w)SOJw-BUTcAS&`Qbe2CSsOh6PgH>QhVaY~}zs(Pthd`6Z%~*LG zkSNCc3~}xgnCcU;dU`PtEt-JOnuF#plST22}nM{2_Yzi zp!)wjP2{ut6})n45m>p`A*slekcBMCD>g!GGTcSJ>&>#7hl8$t?@av@-s2>1<4>Z= z^Sn=iBPF7S>z0p@m`H)X!(6JsuS8S~4ueqfIs;tOpGjAe%;;+J8`{KA9{D$9kO-5P ziN+-A@?@<=oM=>vA5{AUx7=9LRH_ zfhO}JCaMiO47iZPNsSUitt8%{LN{$9m+2Mh75R+ldeHU4*VL2dByG7}ukQN$NN#l8 zF@;kdf!bZR!tkqikM zT4L_8kCNzq@*|5Zu-I}Yc22$wiGer_-bA9Qiby+CXl?z5>pPjLR`^B$uJQ zEI49$p38ZUHtuBEaL)6E;Bh!~9=Xg$C3h6?91C@Z5GH{tDol`(6r$u5BMm$OPmL=1 zQIQP1MK+mkzb+wpO^L_+L_S`gLWn}5jLIQ?rPKOB1`MYB;}EFP^mifsvtE(Y*gVUW zA=3=$g%A`%5U+#~>TpmX5BUVb1P#r3m@}-nB*@4>`vY5G3=|T%boxK;Bfaqaz)O7! zfgaOu|I&nMcZ3j--~>%R*VQ0|(2!+X6A2-twwn0y_@3v(QIyxZ!wiyCvEIu8K65*c zRF3+}i$Y6-GW|~;gG~CTPv5RL{MgUyA3lD)T5~&;<%M!|f(^TXxz=VP*$B!Bi|=v- zV(jw_p0iwHUdd(nR_7Q?g~lq9A4L|BDFkLn;9EWl8jrJt(%&rTF)LJsoKl&pyrrsW zE4Dg{gc~C;z)#U`tDJm#b~zT3{|RONiG0Dnw>@l+s-TA7hR8>UGV-hfLvUZ))GT_QWf zZ5c<)cf9oMcnfZpb0?5{@(A zHa$e1i8)I9SgVN?Fz{+er0Gv0OveeEX3wa@4gnW3Ox!?2nE2Mhq>-C93N!#hcb~fl zYo?2Jzm&9uc-&os;0DcMV>9nmK@*L`a=V0)5|5hd99y_&C6HmP1uwqTEaT%Hj}@dh zJJ52Bv=YD7$yJf>^S4Vc`lfHw$8XrDJ*#6(o`Td(7f`lFMm?D-#@dMZaU)YqG3TmD zbtn=6IN4xHzSQY-)oypx>U30KNaZLC*uZ#-7&(D`*jSN`ST$N#H6ADs7XEN;S!)OO zYPOYW1{OP^R8b9-lU9IsHB<-mpwDELjM+SDj=c6AH#!(}cZ4V>g- z*;rlb^i}tGu&$N;`{7se-%_;x^#6b)=zpQM$&dJ?St#V!YU`g59eudTB)gCTs zF7&mtQ|X*urd_>UuWK?a{J2<5dm!-=gu-Z3^rA)gG^CHVm}T zDz&2%6CA!H>`eKpl(#M;ZSMpv7~M=)-1l;6+y?{MP|Pu`Gj1*08D|nUR&{nih~exO zz#a8|EA7rnAd=0%o46(xQ6wxh3_l0_^PvHSljntQ-+w@CeibJr&Q>pkpg`xx<1ue! z#k&GUE)9m#Xf#%ZaoEVIG5SIX*o+O80>|lk?C{wPr_vd9IuIF^Y+{Ti=1iQ18J>=B zg|{TIC4pm>!1OXoVb;0OkIFFqKvYXM(EPK1Y886bYrjpOyWwUXSXtNk7hkUZi_6jx z7pU&!6I4bcOTIUn)-Mr-5HOSi#-BLZ+tw{~{;ma`vwcRh`B-7F%t_v=<_MoVo9q19 zOuI4q=Hi+P47u{aEh-mpQN*x2Zzj{_ySM48`(LK7e*7c#^5;H7-}5c6(p%sB9r_aT)>FU;thin-IL_;!T-&W&{uxN=#C7PM6!s~Z zZOZ^WC5PqoV9HA;CIyt#S7O?sgs7AfYpp^gu+&J&a)rF5J$r8_uC2#XKrB8Tt%VVi z+#FtB9;6dM|6iBl!w^Y{d?Z2$PL{(da00K{*;!TGH%IEIYeglx4cdZKJ0~=#DVk2! z*0UvnGfx8dNRO~bWK`CZUR3BuWi2jmbB*C(|9T~pzUM7Jq3dtmr}z1w%w=d^bF&FaO0c6OzixSou> zTR-z*Wh;Ah#l^eytS^6zzVn;DLBH@5Kd5*9<}d3X{`3#@odOpVZI4 z^-cQ0|NLTo^9#R1&wI*MdfH3-+sx^%mA{*1J{E$zex-T`(IJ}O$| zqx(DQ`!3+WEW4ffcC4BOFi-gY_qs^EyizMvsxSgEbyfM~kN#y@KmrsziydM*i+jJ- z^%Impm?}RV-Xu5F5D4Lvp->eDdKzfR>0Ulq)cr5nt*?6Am+1F@{TKCr|M$=9SAX`u>aE}ZT7C0N zo~oxl?n*uUemiyf?o1bKlg^)&c6FqMTo{g0LFZz>RJk@%56-bC)xmFo&8LFC6$qk* ze}{-ti?S^YIvcAkjMkh!WYDat)|d8cu(qsj8*37}FmwC;0k}WxYp>H7Ss^kB`9;zl4x|}Y*pwi4e9m?3-nCn86tfk9;hIdhUrl&(_)x?{30v_k z32aH=m?aQGRF=T1!&TIPeWW!4DW9Bi@BQQ7*B`$9 zXY`YA`EEV(!S~mL?|Y#xMWzaxL^)6Nd2z1GW2 zdk(B>Zu>b}IR7Fo?>nIWaID_Nmr5?L<9h-2nn8m4TB=ZR9B>wMEUac4Z-ijYjV4XQxBoIEQ+AX z>zce6ovWcad~{I4bxbk@mXw|9m&T)^TCJAWmRHnjchu?4fLZ;=M?bEROX#slNy5Y7 zM)>RqL}b!VfBP*jiqz$Z+)$S)kj)1_^kKD8SP>OE=9)+$Qveb`n+abeoPe;nUS;?M<^D0v&`9LZGjK(3ZP15(uG|l{Zio^V?;zEVFk)2qC0# z2;m6+3DrC)%&8ZPO*DcojGSdj>sWxOO#R4D{LgglyIuy(Zr`ot^`X{>rSkdh3iPc% z=u<4*A;z43`KnVqmP-gh)(Ig5Wf-2FBGXK}P#pE;U--z%zSa)hrgOG+^^)g)o!<5P zKc_c;=gakf48tLQ9gt~6M+)7YjlqN5Dun0 zhw1i12(nCR+0ld|uVa>bBAnu{n%h&H$u0IyT?s^mw9uwGG{wutzUd(lW1;ypYs9W= zFdC!mD($^}EQ+gniir{AxRsRsGanIS&&x2Tu3APu1?VH1Tlefyh3sO;kTEix7FMFv zWm3VTyg~@7*bz6f9ZrnwPP?59^O>1hIo#BqF;01AevVQhWVdP%JVV`9x3(m3&qyHp zM2xj;gQ389@}GRM@%O$D-K2m2$R{-lEsep(FKRS>V|!iE+HPZ~vC-H`gT}TR+h}Y% ztFdj{wr$&uZ5!*Xz4v*4!2B|=G3I=po3+{a)50v(>8o)*HSdp)E>YrSTo(fGe*%8a zFvpiG4Lq7WraW18XH;ZF{Zbe0`VgC0Vge!eAk! zwC!&t0xs}btNdhiVNrF%e>7?NN70xBurSq9K4-Qi701IBqMU}o z)1V)R-hxP>TBqzLzW@jAm!x`HoM9*B7*&2~l~Z^i+?b+=On?d6NrxMgdRSk{e+I@z z3M^oOlCfM_v=oHp($hogto);{v5-daw|5z=r3_@iM`-;@ducLdiUgiTdw~&)-+(KC zbHca9<7#ExpL(2D6^R81EP|xhy~Wd7eRVwkcKV%Pmmw&BjAyxfp?BRmXX&B0pKObV zw5u0<7+0%uJkGzLPT6_pT>&)ZH|pN(>vz>>C6QTvnJUY^fzQwB{s?1tVGBhhNsxm* zD&L*nB{*Fi*2mxv&hGi8ig-sMjo6qL_g!ULFuP$5v;nuhOI*EUJx)vi9U3I%pXaW3 zbS(e*W3A0JfoTLqX8e`a%WPsVg`^-M zFia>e8?o9x_p!BQ{Dd8~clmX?f#R>#wy6KF0vG9_{+Lf$;Bmli8NIqYu{F^F=@J+> z-D8L#lbSm_D{kM%Q@*Eb6j)pge!UMRb{I{_F}Oc`iin09s=)r&8{A>y_l=dn$Vu_L zntNg9=JdXYtBgU#vs)e{dvez7cY2FGJ)g!{6D}&n=mrImXmGf<)e!+HBANksM8Bdj zBZ>-PSUBXI)6u)wc8wf&Xv~C{D2}bd{3Y>7TtW8oI2Pgox?>1s!@v&T_h=A(O-BX5 z_~?@&_{kRMjjx&@F$^tq_>*2<=MDbyVdk6Ey|1lb7P9FU!lw2X z6m3KBKINw)4Jc8-uMj|CVR`+-Kx@f5Wth(y(y=J5@_$&s73>!?c0eW7!M|ZsC=yV+ z&`yp}j84Nb3GJ+YfkGJ;;nZzUELavTZ-Blw^bRg>tDD;jDH4u53|9rrz+mUimm)k# zX@Bp2t_+1QfOEbFa3XSQrx{kVa9Vmu>`U^G6QBO~OXMT7ZCFF3pq{1Jv0H$y~boXh&QgX16@h`^+qTAVtS9Y!$Ht4N>ZjLoWoSc*KLdGY&up zT7($j2s$7?3W!bG6t5O?cyfEbGJ0f!w^PwyrPJ~|=aK5`7+D8~Bf4whOuEjRuC2Cr z4SoPWa;)&^863bXiz3YUtyKLEFmWR<->W!hN5#pV;-;mcH=2QTbM zQLt_4y}Jc|^ytR~2|Hn43rWBGS3RYS*lnS$D#G{U9RH|-2pv2_-k8J@2jN^Qs_DT~ z?En+!H^-i#R~izqnhx_SM@U=mJ+|M|^*UgQ8vZ$5M^0}a$9B>Jxfi@7)@o?|rmGgu zIC9b=ikh~VsvO;|t;NrP9SGVIxdVFRl68)&O6Os31IIIV>lYK8%x)}4U3DFYAnTkC z#!<)g)8PB{?f(rh9MXS-%pj&1UcU1H*YrPvJ|^_RJOOOdM^b~CVSgmg*!j98UKf$3_C^I)spY{tmGWe5}Wq4f`23hh9bb}ypMxLx!aRQc1$_UCa zX`6(C!BW*l-gv}=DLPY`QaE}1BI)}(&?L~w3tSot8QzW{#j%KxYCeWT21O5}V9}#; zeFAo*oWLjt{^WH))KcO5quN>t^y7yXu{4{U5Mln~YUa^3xr|1xtORk?z|JQ|Mx_W* zIn^&3b8yYk#5_p2Vm2mavco%4J3$1hrC;8w@_efY!=3VEx7l~CRN`=DN5ZC%GIv?*;rKPI58DxX1F0QMD^NZ4wabY7WRh9aa>@(ln1tYUjr(3m4*ly>{8K zG)w9?S5^Oa4({{L!KdC~R(RPZyuYh3;H!V%!1w4)_?!3IRqU5^%!)1iB9lZE*^uQr zVUO-|>%NZukH=bgT6odg)8K+UX*mrMK~rZ+@QH$OCBdi8!;sQQi`XS8VQ*>Ybs67$ zIlWoRyoLD*qacH5Y$+BlCp2^Lg?MWEatsu0%&=1(2+wZ{)8xn7dk@}w2br4*^JU3y zuDF9J$k`Mi8W<&Uo*b$S+Q@J}Y0xCyEbCKT2boxUA&mcPen1Ac%vHVSXon^+-SvSo zpCx)JYUh^1OH7`29Jh>&-_q% zz6LLhW!tiMcmg{uySO>oCj(nPDLor^F_V7H>kXdt51^DYH^?1@}suFgkR&cdBM99@6ha{~iH7RBvhS7BizES~u*36ySWm z*Us-Wz(9M{Y-NJ8l$Bvt4Fk2MCq+{AV;D_(?2UtT_nQ;0RiC}aY(6!rBPLG5mdde5 z-q&3|_hI_91=6F)_ES{*AGI?nYv`giS8Z1I7izk<92e`4L#5QeZ;<|?pi>MVqQJ5j zLK;g4ay*hyQ$d$ilkOF$b8SX=>&xPc;K{rVnpMv=6CRsy&!-0xMOI}8ThisMT{IEL+ChxJk0htF1;yU6bstAP>WXy_M}7V#ReG_60*dOw`W;0_qgR2bS?A8$ zgY;RmoL&S*cvqv`-7pi2a4`0V;fYn8=uLT!lVqSu*^zTFe`2 ze0r++Ok3HzRR6l4(~h4bCHz$`@mTL_prHs8wX0)@%*QP!6#rTL@I>0M9p8}%2p-9E`ph(61 zA;qLH&1kL;lYS7nhmpknBFcj<>*@00X#rtKRrD1b(=M-K$7h+B!bJ(dP)0#Mf)FKx zSgzc`sD(j@kWP+G@k~Ty{EV^^CG&_B2DpC>Xt0`$ve-;ypO*sN@;>w_h7?U^rcL~! zB-XXcb;D;#=!EcTq$q}BHvIU8SojtYD}MZX`npTiL0m~z5pU}?A1u0$esy@K zz*EIVVQKk*7sZYqPZ6`Q{=-RrE@Xk9CRg)! zeeq-9NQgkCT1OQ-C|M?1JfPsuLE$2Blb~HOt|E3fmkHVEafhx(p~WSUMf>2tm<3Vn zH>(^qZ^E$65`otQfy3ZJ*_(AH3F?4Cy3xj6p3X2>zI>5*`Bo{ZxA-W7zJ5Emb)D<# za^*kPKVPAWYg=4&@wa7R=UO5OE!7T&46#m-Z}tbP;YJ*e7G#D=Z>%ldy)ua_G5 zqY(Ek;%|(U{`pS2{=9gH2SI-95!mV!9NJ+EC56J zUf}s2#=SJp&0Rd!C^#&+2qAD;SQ0(=vP1sdgdMy7@qGV+kV-N2@Y5H$g#fL$BJ~uuyoHHzfQ#GneI|BP~hlRjb z?|bVk&3k0hFF9x@c!P2ri6m*s?I&RHZs+1`Aoe!3?HdyAs($|I2ki|hNqLWLuaa>G z)e>zhvc^H7C6=ewXWK(iq5DUx_ZtC7os|o-rN6pT5{ZNF+w#Exmq`wnnP^I<^?39= zoME?Yz^O84NjKwERax!8JZntR&^p#|N-8vv{VQL{-I!vp+s*M7Gz{sE}y=}lT!~gV5O|>6HE8YHsRt+v0ZCWM}=UPD5l^!BY9gKHUj%? z9nNZB>MXC0OTe_9{O>k-leUGz4?SLRuu*$pxFQ_4YybOpP?&_v(mVG zXBU&%`u|3_7@1x=mr0i*{3lhDicLkm-0vFiMgz&mdO$fo95op6-!9anE=vwQgc*OE zuG`x)!LPJZ7to0TO-;zu3&AK*=ins_<>m3a$g8~-um=os?>7Q&moAnrriqv~Q7^@j z!vy38I%XRG9C+1C=Lku;!n0Hvc`7_Rme!P|!M{k36zLbUH_M(sE(K1L#fr9Lkfh+C zo7SlZLP=RQO2Jwo^bRP%0#tLYG*r)UJ;#XC@{{`f}%lxPZr(5qlP1a6`%coD+_h_kLwn{1djV)$Nc&9v?6S35f|X_$;}$M<>8Tbq`dTjE@Y<0XY^_D4pMT#rJSA$Dw9>?Dpc3aZqD8~rI6 z@jJ$DvQcsbV}yv{AsQl2Z?gsw6*H%25Pr)-fuh+s&aI_Kd|u4={&;-- zDS=38LBNtoY+1YD*Y)?QKce^b5=BWC)E||0t1;qtAb*^th^>n# zfMmPTOziqKqi3am+6lz_#}AuV?3VshnQ=jNTwlU0a@#?&hFLS{ls`?_IEWZtJMO%n+QLJ8U7 zPxT5tSbU|(S!`MGe0$3+c4^h%jWYc&q9>Q0_bq(FA1-&Yo3`3C`3vu3vHGxiSE8c} z9DUotCZf-4Ry8X)7KzsFG9UC4Npagf7-owC;L0*J)jJnnW^Sq}An90M@VRV{#eT15 zG&zi`k;HvZth;cj30{BP2b0R%mHhWzNpG9^tJHv8S2wlK!|w9Vs+{*C{W*TDtMOEd z3uQrG3I8vKJGONlvoN;(7kSpi zXItA>QpO6R>LKLrbQ@HjqKfV{d>IdJlK*6;ZDp1uaM-=G#fZFl9) z5QS&Pz+9AmmGN6BA3iK$Nn-v9M>5|%)pKii`i9~h5^MM=XEmJ!XPxc|d^INyC-7FM z6)rqPW=-K@u=aj|jw50SI2=Y2>*h`KOr&`gG%q2l0eKYz)wf1E$Gu;}D4lnH-&MrJ zE!g4DBT>gs&xK2Qh?VU&_uH8`w(9fceV^`?wiGAf522u$90{YuY42pST=VAa70j|b z|6@v`o~TDULExP}r|+wwarc!KlV8g{9-x`{hh=Qm4B2V^{uyWLnl&+9HA@OT3w&Sb zQEf>%?KLP2RUB1JRjKP-9TGD?goO$NB|RhgQ>{S-!NELzAHvzUvhm^5HFH~asi}%- zJ&O~rFmR)7nv-Fd`*!tzSOD;vi9dHn$F1OFq%J~EzPnySOHA$8gC?qt(-8pJX*3M2y5Zg?6{Q-!)L>%j$*F2!G8-(_lj4MCKx6 zO=mg)`Vfbjvu}6t*)OXj9D@DhoH7j36yzjK|RYc#P-g`rxqH$Pu6Vz4sI9*wn14 zv%R`6G=YcOotEY=QmOvO60qXC?*sbTG=sT@)By9Lf<}IhNVUx7C#9Carrnn;5#YR`a*dn#3MMO`dg zQ=}vl5Y%|srTO{l1(XBc>{<;;s}9QHKFFw^r^@I+7^Bf9lN~w~A`edqy(v6@B=i+J zu<@&@siP_DRvSjBDM(rORxHKwf1GMRb^u<0KOMC#U6=#gz-c)j(WPZP{FpT;hkQaT zY1zu6v`Sg(CASF$+mpVdjt&vg z)d^pFn*cFR>VwPmmjWR3;}Vw@q}SIKIUJ9pD=808N7vH1vJkjlASl3-eqBd1`Mh_p zUJK{%r}W!y&WnZ5(UGd*lY@;kcSqYGYl&K10y93dCQFGCY0hHGFK5to&O#dU_uIH6 z$K3zZUX&zsIKWrkQq=N!F;l$#k_EDd&THwL(>K|CBSkPo)S_f8iK0?vTp)-)YblQE zjk4D@=fx{qu(hx?6`%1QUKld0FzwK(D8R#9V63Fgt<}KvV!iR+EEtfztgD8UZ1L~#L8nqJg&cNbO66OP*f?>kXh-*_gb9O zH}u(;GOPC{@4Ij?oW=kjk7KlC9brZR1JDxKK*l-g!-RK;>JdSc3YAE02Fvijp9d-m6mxpq zzc_v*e=NE@r-Z-yr>(XmBX9|875kf@lQQ3eI%DNWY};I7I$!ASM4{H<0uehGRu4U* z=LnR)`#wMW%KryY9A|lx-6s2NlnHbGRs8p7CSf?f6t@g9Vz1;%4Eh2(Q&1T#*GdX_ zMos7gz5_9d2kZVCzU811053W8zl%5WFyy|75uR*}zWr3EH!!@6B1Tub<&0X2jiZpK z3Rk&wd8VxZGgwF(oYJU*7{@ zJ^X2Eip^OmF9qZiGabPVLG!yaaj$R?q?|kn~%nD1&cQy z%nPLStn}4Q{d!56LnClyprQD+i=*P4jz*9(yxP^*i*zaMrA7XAjXQG|5#5C=JA@!56i+aBm+>#e zU{^oC#0AlxwF>Fy3#i~z7yZrUvyd7n^CjTMa2dzFJ!+(!uqr6bej^%>|_SwtwXTrgCrj|Ivl0p?@B1`?+^n;7PbK>7)ToIf_ zOc^2G9l-OB{oa;X;8J$65WK;piqQtjs+W{2i+Ftein*A}0KD{^A8kl&xW);)bqH6K zJbo!)mbg?ABH&dqmMx7Xsgo6IhaHOgkdXXn{AP#yao|vy##_3h#T%%zPb*>DwwSFP zp}aVkTXUI}E8#s?X;lhy6V*IfAM#D)kCZz-wKOx$O!Cj?4TCppD78=Q*2a>Qe(zyR) zTKSK;;+IaGh3&*H!#@u=@-}Avs2*m_Yi~@L!#~h;PcUbKT{r)neu>&WPUJe6m(g;E z>sWSjP* zN2VAbyo9@Xn9!KaY}n993?(N)Q$KG!Gg*NZ*ur%SxJ_tmi|Hqiv`u$CL%2*ud8ufv zX-|)`Du@K5fpo7?(tgNkjPq8DcF%JbbTScVuI+eH(yIHVZMw(igL2zFuN_efOGA7d zr)BalN>gOfxIgazU_m65FIdE=n_@FUMj7F$Zu_&QnH25vePvfuD#&!8?co;!GbLw; z6Pg1}Jb{(L*{wmvBr(?F=w2G zejY0+ib_J`ZER})Q@}@wem(sP5{QnJ2fVRPXDRWpvZ8TI<0oCU0)7!hMy^reI(M;&YPJ*LIaYXU7l`)0E=8Tc`dUg zakc<#`*p79V4Q%8Gvh$j0GB3hA&>O6#kESUGbM01`VSn(p@W9xG4!Dcdm=iL_d=_I z5*?-1s2t^=%E9^BB5RW~w);DFg{4aG zBVhT>Co->@yGwJ^eZmGbPyl7#a1Z7)p7ZJQL=L18VJh!uF+j3xB^;gZXzRh9&&Uu+~7=`yGDHugQ1LmDr|x80~v z_nBxpP0v_X&5KB$je+XutsG6nRJ^p6VQtWD;x2`kn;UF04>K$s+i$1q59xOv9kU7C zr9hpZh=bdaDvb%)$tj_uP)&2JT)4V$rMWipH{qU5fhOOqXIpgAf2+C!+SZ#{NNrvs ztcTRCYp(|TubU@jjvSG8PEKCO0)lJJo8m;O54COQpL92VMKDgaUV9vJbP66bH8ys( zN**eTl>{(FiNnt3?X3KNaD)u(N+{p?6ckr1VHfpgg~suX-o}DgI@^5&&UqF+Oibw} zKH`+`^NfiRZYWpxSTEO*=JRX=z>}5AQ0-*=m%oAEnZ~$)z!{hQ0}Cmj6+4&Be;_F=8D$@E92iP(#K>Nwj8sFe3k+pB1^1X?STJ>1@Nk+ z4xTxzbH4Ur(o9&H|Hq7`N$01WyU%>PBhnKwEuemUwdEJrP>MKF^3#FyK&^fnJz*_= ztQ%ns79Lq}4q7(U*XBn?mhyqqw zJ1(A1Zwn@r98ts{uI}uWN`X>g@>#n%9q@mNXjI@C4?5&?i04Izq#qvG1?uUy=*)VN z(Y{=6zZ-Ym&j6)0-Q=uJc33d*)P+8GL-B1Rty)Pp3~dvR2jZt`gR>*qobFmdbUhtrpn_XfpUJDQp$SR5 zrl&oht2%RH_b;z84J78J?AH*k4c~v2vttS0I52T(tql23%0u`>9TwFi-UD0>TvCjK z{vh2pGm;lW-o{QtD;1XH=nuHL<4r4Sd^ZiMq~dnk!2Fr`H3EFwZa4hCA-`T#IyUWb zNsOo*6;QTNrv=45b1PT|bJSO)Yo|**oWpU?4@WUF&eDP{Cg)hpFRr(BWf`jY-q7=~ z@m8b{WSFuQkl1@Nx%R`Hn!b*;i@Z~l2$)M{kw!z9AB=hV5kA5cu%mfa-_W?Ny~!^| zv2%!{S%nE-qg2c|@X$;*8D|$rhtVI0l1B{ZUp+MpH(mPj*YYSpRuto!Imw-&f2WzhBN0trCBS4TLG9Q-^OwAixO8SnFt z8Ae>$9dg%Y@_kx(G>P$sU6J17#yPa$7lOU&nm_LEbC*T3HQ9;>LDE+&aO6RQ5iV=P zUVHn*RW@D1Vgxi5f~cHemD&;T;q?C*L(@C)>LU{FK1j+y_TRn;r zQ+}HF48Rx1=h^i~p6#(hZT@0hmU8iKdyx2+D5qneaejNW1$p;rhKkbnOD|=f5gN)j zf-vd_&-IzPZQyHx+qoIwvc+Yg+hnS~?#jtK)y0%8TnKkqzhfao$Gz3{b};ZSy+VRZ z>hs#wSxw&>V^Gys4jUZXgj2d->5l^8-AjEfMZ4#=JIvuJukYqC!^+miCrzdg1$OUT zeAk#Y3)f$3+m=!A!79x?#7)6lIB0e=5Ak?mftQp`N?vfoaMI(!;4rU@4xGqEDvjz* zcZ+&!Ma9t%Zp7_EH6KqcXFXs2OjdvAJg>U0YMPSr)K(-s+)j*frn@aD?JStKiW-HwA@W?P*jf$G~$5y}FZt(su%(_>BOpLwwf<)JGA z);<3Rf0KjN?39iJuF;XZUr5v~4T}pp@n|EV&m7N8vM@DkQ5Pzn6uHeMexuNs?d%e; zlt~ z)!d)7W((3lR3)A@FtsGKI+Eg_$3M+J!p!DG;P{G+_B?1`FFtoY_E`N}tG;LKim_em z(6<^;;&IKY7~*?ZXjQ^)c81y~f~ny3#ev6rUWrFWG&qM8Tt&ZPAweK^phTB{C` z8>Z~p^TvhCm((WGnvLuFO&TSi1$T<}d=TXa(@9tw?YQARWDP-2#58Lh4!>;OnKy^PK66 z`16J7%a7lIPF$D6e6q5+UPFR&BB0d}vVFOnz%cNUBC(QfI0}O~8t-QshbD$!#~Z*M?bVTh=JXlhM1Z!u## zf~-ww`ZXbS=w4LQriML)4T5=ulqFpJFn|WmZiafFLq2DJC|tySK;>Ok5pJ%FBz$aL zW=6qSPVvn@CfM^Pc)V%J0_{V+3v|@uhjj&puYd_+KqAjl(J@plxQDHW~GLZtJBpOSr7y7*+@#_4L zhzjRrK3)HdOlU6_=hluFBK*ZlQ-s<8modK0!Q z>L{<2VKO8xBIH6Lsu#t_#w@6I&lg!EAG|5$g#Nfk)6L1FgE1e=Uzr01%J_Z&KI68Y zDtZdKCX$9XFS&c86goIc*xy_&Tz+3(@~Grz&>gH>wx}FN8%C z9Orw$EmKFAb^RpBt6+Y3`$M`tsfv z040D$&-hn3skjVE1?DD+vm~3aWMqb|hL30uTIPX90K6reB4jB|ZVXFogOoU+KFs&B zr6(+*SDu`(Ezlo-Ud_xD%Vjk|r_@~PL>fQ;3wJM^&ZJUg?IjXNjBL+1a>CK5w3vmF zqtE{5qB&>&sK&H%Qj(ELw*NysjKjh<9*Ps9KlP)Z9))*R}MHozs>(QHz zQ*jiPItUwI%q8VTW*EBiM~-wG?^Sjy0zL<7o6}i-NWJ;m zrK~&^VA6I_#KY@tx&6f2%d7A_b~ypdY|-D*-8BCn8D5*8lx4AHqzVU_C*TBm&1 z@43ETE50%{pVikZGoQoPCj%=wF-B*Wpvc6AasoZ?8$A?RAFpQN;Pq;jBq={Ftx!Ox+>Zgo!h`4`dN@%;)I_B5 znj|PFfBE-urj@6)=skPgF16_D(4W|pqm=wa=?ZXxQq@jkY8{HZ5Dx*R+6mrrVJB1Y zx&|5m-=kpd|0It{ea`3uMHpiIrPEhixaYyDb*j+SmqQY#F;>}*Gk-uwQsto0h74o6 zRBWosX_@M#93E^g{zGNdN~CG^wY>}MRA8Z&im2r$Qm1X(E!(LWlF|c{#yr`UrAu;V zfd2q|40k+C*?oX=hXuYW7PBO%S{m&fqCB-87(>g=)$zY$NTNzY*k%snl%YZ_3>zdl z=O>z^fh-kr51^Q?6YiHkYdoPbqMSqeYc$i_oBdmAMbDX#>0(DvD`w2WDD!*h^4~0m zYG*|f!Nv7@TS!vtyrWRaNNzdZ3UpY)P3XULROeJR9~#w;@B*8|+X%IGkzby-9*;e5 z>GVrdI^Q}TwtWv{9^$N?t&+5YHD_Cwl&znsH^k>46Nw)4twnf_)47iSC<=F(b4mZZ zu-yDzK=&m4(F1ss_k6_f)~@jwcs{WKo^ycW1AUYt26CT1Y#)uMlX_#hJ@-d>=jm;9{)Q?MazSOiI7K2jH}Q_i?`o(AD`UD_$CT-DZxO{Q5R!gxQXVF z=M>9gTg2JRa>0^IAZA%y;%IC~U3bFyaXhNzA_lqY*mJ2@koj!K^!(obDCRidU#Tg* z6d+x<${T^l>>|S7m5sphuL!6!+EO(A42wI`ug5p+G#nWFitRN}DHkBH^7$!k-|4q7 zhP-m~tp*cT2QWe)SCz7B^KHuWEjg*w>iOw${o1|3_qD}We&t`I#Ly(~$D{>sv zBxb7=@Hxrvb204V^VY0H+*Mce>~q2X`6}Qo4eBKY#bkYg&dC|aS1ZwDV#*n_yhrrb zOMpP$^T&NilZMMLr)d+*sjPyn4-fcEe&m%lf+NVfjV#5IrDgkBi8^(AO)2XAQLG;D z)e^jd#Buu8_d>#)2LBDNmAR@MV4~j3Ma|qo&I4@FT>D4LorQ|I3GKAsOXp%J&5`<4 zf?O!XN<$Fn!mFWiHl>QeKql%m_N|PoNWQh#yjng;R3{ZdkitnDB$=AWB5t?ORJ0m} zN$lzM6^caDI0ee#LB~Xm_m0oBnj$A6({L>P{)Yo$|6QGc?zPiZ=S{E+xW96S%j6lP z2Taacf#G+pur(QoAS`yd!`8_uUzpQM^cG?HyS!V@nLEN#8A{MY%_6tt*tGGl*;(t4 zU6K2QIL@e)#N$9%yyclwg}+S31q9^3>C1o3o$opPc`H4y5|5=!K)kMFA7C|v&hME^ zUU^jCmG^T?H+UD*cAQC{gZ=K|FnJI>w>|j@vx()65u_znO*)f!+y-*kv<5%9Nm0E- zw!OJxs#wUcZjH&;1dxU~j(6d-D@T_w1W*5fdud-O<@B^`+Mw^0rkj{ZPfJrA`&CAo zRW%5X46o#mB#>M-Q}r{K)B-(r&ErOi|MsHu*@sP#hW>J`!k^I4%~vQwhHkxQeFYRQ zXgTtlLYuKsHsnx!{# z5dzzoyrT4;80;3UzV4q-C4lctXV-*whYbIR1$;dkYay_fo4WezT3&Zbs@kR49_qOx zRW&v9D#7Y9T(k>b24&&Zz3XagCbzo6Qj_Ebk_GDr`O6IxSJ=W1Qv)tEJsm)%#_O(A zPrlQ-nEB_f(~ZXCW{7p>l>*(y(xjLg?b3E(^H7{6>WC|#*)?CUb=$bpl%48o*}@pN z1e_nFWN2n2Q=dUCYHlDnn($u*VK{OWA%psQ6s%C|S}=s4!v=-|&CE|aC24iXTerMV zyiR;X8#9khe2_y4+?Og%SI;%;OI`9g|2*6PE@w4K3ms~ok1OR@{E>AHk<2}!kfH`d zu(SS-ovalOlPTgmVodJHaz1x7so9y|>^5f^h`poRgk!PwVOdj=>rlv2C%m(5j&db5ZVRJ{9 z52(V={`|W<&n25R*RH4*xhuq-S`mx=B7C&%AWI~Ttu-D?9Gso04Bb91;wcZ{&3I5n z@)4*ht#|vuwEu!}BMqzuM8Mp6*n` zZt|oqaCl32-Rm$g$0kIycGHyn#)bwj0elQ;IRk9dow zGCD_>7b{`$KU-xs7jyae%K5_Fy3do}2|u^%KV0!e(SI4f-UWWPWL=mj4zMkSoNBPX zlv;{pMeF`)B8l<2`bAbdC5k>_wrjx;{uC*{&wMt~A@J#<<2yiQe?^h9>qFcD0r=7f z5-E54%gy%|c!2~0A#Qo|IDO5R8=5_40Ah))bWyS~#Rx}V=5e#(Uv8PvT{!rM?&X;F z$6&YuO&j8YPRP@dwqEwN%R!k>I`@^miWh8T3pxqDJwfUr7aIut@x4L6B;9U`3ST>Nc=^F zz@|!Ir=`H@tp_#(?-U*s;gZ~NM1XoP?!8g*eMxawX=3BI^J{X*)-m!=h6zU%D;IJL zB!0O#t}DUeUk8cJ75RtCvW?jxrWg7RNDfY96mG{Dgh-94^{^hPc9iypDYGkxZ@CH6wjEy1gI6-et59G4V)GF+wnpTNvc+m- zl^n##NAwLmw}wUFtt{Sx)krYAqxyHC7rB)^9GI4!RP5I_#)#UY0FT<<0d9Q1C*pJFp5qAP z!REjBdiOfK*AFFuUc84s**Cs10k#ck(FnT39b0wSsPCA?SznoNl-H-xneAk5mF+F! zyT(smF9DPxdMxMuVyl?wC7=YJYB^Vpw#(ml+X973qj+YI<2PiHhX?xJa zt{a|7;F-4bH7lpL=HH`<_aP4;(C*_ZXRif#0~omm4sGvC0PbU68i4ouH`oBHyP21S zo-de>Ex=dJ%lh*djvHVX?B7G4m;G(h^cc9iq^Rh&JK zr5UQ59Z{c9Sa`tB`JDYCJ9UsOhBvY`-r&Bus+7a>W-)+Szm_$+X#!gpAAz$w^|ly) zp%X6QICeKdo|`CxJ@N--jBQB(dyW_(KR$~u^wYt9m^pDBCwfE)zQ$ZrXAxi+$vUmh zjho+%;N8%tPZkfsNw4=6>^s5~rucK#a>m+^k_c6<)WnrOOCvX%PxN#>?MDj!ci zFT@7B9dfh5UE{1!{|M?w`g~{ay&#FM*36DH>_%bj#YPT>2vgolWQTv7N=r|NT*2ox zbl+;tUG29YMR5?n{LNH+o=Oy_J1DjfJ44RG5on|7)qUvR8XLm6K>EgDeMzt3`N?oj zb5sY*#X(gUx^+B$6`*?=R$>M2&RG0UBJ?ta?L@7OY$YvQJiRHl`8j|2UzoNXFM?U; z!)vD^)!e^#>fm!!q+yF2UvZV+=D8k?St23x6m;)_mOK{Ihn;DxML7x^ohaEH-=kw z`EiDu>omtTtv-+DDM?w>9({)Q9NGXZMFLQ~|km99JM!{l=vazG* z{1`)5IZ?aAp=smP8i_WJX!9k#!aHIV$08THQu2NJVJi{sY!vOXVNRI(FU->o+|6f4 zm-E_WrWbvfwp5e5Xna$?mNy><_5BnFTRA>Eb$jnygcPoJ;ECpq6r26D;y3N*!09@t zPtuZ!HbHrJCQ%Z`kOJ*dYpsj>UXD8=)MFZdGya~Puc~qQ(TTUAymV#zf*B3ywA}$?18Iox@R(N(G%#@c=~(Rh9rma`SjGsBoPJL55rTT(h;MM90)Pr7}a>=Swlm?7PJ{qSOiYBo9CDEs* zlBEOXkyo?e?>E2IbLTe0s#MRi`2H+k_iejTW{?(^Z zp|7P7EOOrBpL+!a29(%?V9YB*h%f8{tZq*?P@Ka3*khF&!ozPy-JEV^6YCZb35taK zA4V)`w!PVZ5P|Jf135qeV6Yu<4Coi*`=#v;tno$S`1i!6<2s?+`cfRT`*99{I@W#e z+tGW=-ne;+*$VmYd&F^@cKrd{bFU6W0v>KZ?U=pd)c=+jZ0QD@yMgA!^SY$&bG|CO zCUYi}_{=e`UlP@+`axuyEAr))v5>kam(7P=T&BvsgxaVm*03ZLKe);{$V~)fW-qa^ zj?8%Z*j(ACnBgPbj}Zt=B#7svq{4o%R4S_z?`8F^UTqleUUt?+b}FkLTP~PD!xG0M zsV;VZxE(PJIUZ7+9~^MYcyKYC@rjselV%w{z~EWasS9=&*wBJFN9_N(lePirEOQ#v zWlTWP3LEn1L}Fycslv965P}dLY^2NKKp?!kXAnY%<|&7Yb5ar2h7*;;ima`HrP`Xj z&Xg=%N6oE(0r4SKY9SnIix1 z<&VV+1)`${HuQV!TP&8eD4IR&-FEpT@j{dg?B0eD3}cD9+OVRu)a}7y&QO<;0235s zPtAZs1N$6T9HP;LfE})lM;~w!Pxj%2fyx`W0cYaC8OU~5tybqLJEL7C4g49FMs{8N z1~`co{E8rWaUvn?=RKkPpxkQ%TW#;$j+h?@%~q1b!r!%Ti|dS<(fYVXcveEx8<3(P zH#m@YH&=9{r(cXYUGHJhy1zA-lqgg!zp$h?$Xc@#?h!Z>K87!0lDIKnJpL#_zOO__ z=H(?M*3omcg{FQgpaNG*j+43%9e8jrq{r)&7|_ldz8twx7sdzxXkphEjHSVs=iCCR46) zC4Bb9Rf6%%IjCO4UYIi*jnSX@-;BK{9`m#?=4E)Kf;a*({B_y9956;_gm? z;uKn-NO89WDIVOdE$*dIq)?=|djiEBifeFp{nGpOz4txmIr)*y?&!|U?lYP7U-+7( zK;Bg;=omCE7(h^n>IqI|H4i%@h5ocU%X_lwFoIIY`Tyq50l}N=%VnUV8FZPhc1a&c zFRgBqtu<~-=(xJ6t4O;g*`+vx6+fx!f9K~)TOOCAJ-b3t@-Hh#Qnb+_z3%eQ?MaMZ z4ysZVTA2-p)&S(`Rg zR$qGX;vNU^8`~e~xV~-W=aV0;d1E+%qPK$l;+# zL3+RSIkzbAKva>-76+Sc|A(-GQfIeXo80ZX57^p@ta3zl_9z|E{9^)b2oDPNo8#Fu zr;azQfXbu9FJ5VuAW@bvqft~J@0}}VGCb$kdVMw^l4F4uEZ9G10hN_S;(bM&o_F0x zEY4_BZX*X?UGbd73Y4yG{Z2I0ifSRyXKVAHv7j7Rl~Skl zO^lN|l5C$VYpoK0>1uA~ZzP90&qXfcKT+^Xb1vMj8UhbCT~JJHl>>*&t?);-)VGf} z?&)_Mw~mN^qTP0#&%aALh_R89W}@dlW*y&={giux<5e1XXqwCAzC3Yqo*u(f?m8H@ zM+u9IX(Soo7cR#cDd5dbt^yLIpyLlH*~Dd`4TB0Exey0k;=R-@j6??HGWMd1la4Q+q|`3f=H1Js+xLbMfC52^y~-xIfHSbXDhEzNulk zqR*s-0woou>IqMJKVE8>ZCAO(hlg53goIXN+g--ugZ3-)D^;k(O{scMtBhf3p0r5p z7Pqx5uL0d-biKS^y>vN-drmVesS#!il>^#`G>3F!pPNf#g9NUPXsbtPtR)dY z`ov0K64b@vnsG0ljcj3`8ZHkS1V;9EFVGBL&r$frdx6cOW~CLYt+W7LGoWdYnsO$G zyNZ=E!cxBbAjf0i^ml&zy`t_3IrmFR`2u6sGHeFn2LvpsbDm-Gaw|phJu*|h7qJ!@ z`2eT(tA09PDCf#e6n3k7Wp@Mu)2&fBhusIBl;oWp-V|#hzOt|Za7Y#A&!BYG)n{bH zB768|8efXe?>c#k!jjTv?vn^?xF~%LLFc|Wmz{a0@~EqdlFr1(@2G-`PTh{RK3llX zFj3@xRo%y@n)uC|i2uyoV&om;x=lzdovQ0C=Wx%xs7qUHxS8V2nsu*A$fQdD&dBSY z@pPzNH8PdIOsavGWY)!Js&ttIoyhBQfWQkVb@ET#eW~F4s zbM*R-?&*B_cyL0aQ@p$l$EM{zY*q|~z|gDN!_n@W#z=TbC2^P`tCS`c^A`tmaAKC3 zTdSL;)y7T=$Y?_96(#yO`-A%rwLWaGb2`TsX%XP#*C)|Cr}a~_Sin&%HK-*i{ba%= z%eqUqtq(tiw@M6aW~=*@-@pCdP1KwR-(?*g7iorC(hJyv<9+J~q};kT1Mh}OM9Pmg z#$F{c4t?@?v7rNraE-N@?Ouy3sj@N+vMJ`C?0i(>9TeeZ>ct`Z&F)AkauAhy^6l=Z z`&I8rqBXgl6zQ}6LPB0*)yueChW3gtIMqUWw?nl+TVF(SVZ-T0#+9rPH}n*~dN+k8 zm_glRQ27}zl#m!mVuT$6`V#Lc%SgWwlST{LmvJ~+pOwfmCzeisg0|ly4}&k*K}R%n z-TEQ#)Nv;$?IvXhsOr>HCmKG)R;NDAl6nlzpt)L#2u{81!Sep8+pMk6-O$_@8In7Q zHXCLh({W#hAE`x4c0ei3WSL?(YlN=VYO&`Zk#+)c9S)chp<}u$7ny65Q)3mYDwX|H*lPQg4h+%UHNqtO?h^zXz zVw0tS+V0J|v^JRiNot87WYzl3!5a;bRsTp$?Pk;KHps)wTzO(Zb@ndJe5WvENU&u%dpXK9k8C%fbB^2qOVQ&l`Ce~cXq=f) zwxev`;~5}ISlU?Y7Y_yckw}l+;I?Zlf7I-U?D==utqFM`dBcP{dRLo-OFSRXhm@Qk=Fy6pT-+D>DK(^_F8MnR5;94V{Pa<1caI5zX1kBnxt60 zq1hU#9>pz96e@vn@}yZ_gUo)bZOtbcsoC+DZ{jT=-_2kyN9zfu#kyuUsCa>b9G5%Y zkpRCm>}icuJ$;E^0whX=&3YSS!K$gTgQ@x&#u=~5wwShX7O59L^mx^#z&rW?XH2c ztnHkU`W3Y2A zNOmE;M@K^nF}r^eI2m;bxEwAQJ>reN@$Dj5m$;z6B)mIV0hs>gAV(v=aE~t&5#evR z?G;6b0lds|0s9U^Wg;72coxwSDMD>7gYlw6f+9t!LE#6~yI#Md#QU}qXRAtr$`%#k zZgjQ;a2rA0tlyJKl9N=DO^jL&hO10IO^y7L_dNr>10VG~`|Ze5S0MiN+fG>3Sm zqe55RG2dHGMtT2`>d(Yk;i-^Sq=Tj3rgl~)EzQUq7~K69sD6@b5>Y4 zP7Z@i;+KezZ&8t9G;UC*2be1VBDMn}E%b5-qM03agwkE8ZgsU`p>*7ldf36Ad;1K$ zYuk;}A#n8N8nCeGY+AJu!rOPTHEO80HHc6<8~K97fwgQny=Mh;tW$V7W~w7ITahNX z$C2CG3ez~cs0*`8geokaZIPGGMJzsuVb3LJY-QlZ)(11d{*MpH%ZK$R;t-p|MqGFy z8)nc@p(4q`<;Hu*1rap*^|+0lT@pk>5h|n)Fk1D;Z^I4&8Q*O~uJ}EW)NEJW=Q+W{ zWm;|sb$d_yMF$<^;0vxd{=^`m-fO(*T=~QCg9nP}0mPJxEwO~GwHI5ztu-N?nVAy} zJo&-tFn=-F%2(u=5~GB<0J1}Jhxyx&q7j2Z5Br#J?Z;+*I`Y!mtjj{!u%i4^O@>UdD$nmmHW}mS5xJghf2ck-cWOt`R@W)0+A*8%wLw!Wb8(!x z!PL1{AN`KOH&Q#zns?=O)A?Zl-}2)pHwz9OO_5yv&__4)QQ{VsX^B^<%>>ykG1`12 zC+KdH^^!E=L?FT*5X$iLyW|NE;C?$MO-^e(KQ}*5i=qY|Pc>=B>g+6KK>|xeCWtqj z=K+FYNeXdx)=;!0tkUXHV;&Y2ff2m+LTgWU8_xS&P+UN&f`pF3aJaHYAK{jAMjtgm z;uOuPOtci+@-82Pu(v@UU;8d(=gF!Q;} zw|z#-v@kQ0N!-T_9V_nH7GOc-$77-|oc+jDM|fuKIot_58HHz*A8A!0ClI;fa0@)qCs`aL3(bMrlQ z^=|o=Bj=_7cY#5HV0!r0?ajf{CJO#^_g-`kapJ@V7Dc~vefNz=7CO2MK9Ax0H1vyO zrvw9m(EvW}U_X1bi0YPP5KzB^NnPg+8>u_y&tLUkrPIHN=v=WwTSEM+PM)>A1So#) z;mOVbwdCBN=8!z%Z282C7o`oD2hotDsZv*WRl!1(A)4>KbC{xyMroAcRd&{_{8y&A zMuwu$ZIQCrZed!XFKjdLepD8?MDAStvt4E__|C%|B)ha0hn!$VSKW1F!&3feMiJ=L zFCW+8xZd8uDhie&G|Y=BHDkwtb5ar2DV5-o{-`g+2alSP$I;~3;de)kG@4Q%@%?uB zJ)jc7y*818F#t7*@nS`oZFZ*TPq_|-G*QPDe#A5VP|OF(h_>`Z z5m9`F$_aaP|J`DAzPea+_AoqI3_xLBdRD|NPbFB2O&Me+s{158NtJPf7J;-aE`64Z_n3JZa z7N)@JJ5306&opBx#!GDSHz?aQjuo)X_I3RvVOeiCU}}Ov5J;$*B%Iw2H^19rRzyXf zOJhach~AGklC@1xJ*1+0gcN>#K;m}1bi9q^=Cg&HPlm@qnOvP{zmp|wlBCfU6>}~O zo823}?>Bws8>BIc7jW;KE_gWh(O5^B#9Wc+F=_{qpgt!{&lL{pMV0QBFbklNT+_#r zj;=&bBdt3$oaD?A>)oyWo{l0lVhSM&GOfhV)a-b?_DV(O4bKS~bb2^N6V?TV@+_qV zGRj36^%Ff^CKcY&C4n_D4 z`<88(Eo5yzZ?9p&T>MCD^s#C_!eS%vSv#D+7A{h!~|O_+H*?sam7AE>8YFc=Y9Nll#1_#-ZYH1)GJ1{QWs)zOK4_d(j6; z+SuG49O-x`AK3b2T$v+c(FL%r_l7HWF+l?78D75@?$8zq^M`6pN~pf-4UR{o?chyW zKktED(rc3@JcG%^qO3|9y1Nx$(1lg%S}44u0|LEQz0N;p0Y9go$>jGHWI#BufoZmH zf`+Jtzh)Fhiq6FQ^vCW-LFp6#;v#LKnLLCMMEv(E$n5K9Vm6Y-<#EQo5K42y*TKM- zyr$d~i*u-Ja$Q+5Os#x*?!NChY(G~9fHnx+3+B*;q;}B^IvklIe(cC!+yL!fLlz^n zCDdmcwF_xRH|1wY)Shc-sS{1Ilg8rVo&*U8e^Nva??yBu8ubLQmUDZ5X~`k}Jb$i) zA)Kfl>4fpyJZFt785T0oGgol3dPE_Fm`uDaZ1p)Y-=+a5J}FE>CQLmzcQq7$S%v8AhHT zU-Xk)avhEVp2i!Pjyb_7O7ui2CO(Op!$htIN9a=}XSxU+(JP0B5`9|H?s`A2;#wRF zths_!gidM#2I1xV9FF63Hji9!lUzp?c3`B(`loA8dspA6fi5u=8yw!;_k>5b3I2G{ z(JWR7;JT*eL%aAT?;1U$lsIGgMA~X7=QlzT2v?ajd|PA1{-C{TnfAucD=qEG^fTW` zPq}f;aFMAv*qBP>V29ZgDRZGD1I3dYSAn3iyO$@y{aWF0%70V8j~mwY0!r>Sd?VL= zH`wj=a}dans?AVpK-8T!ZOfAij?tvdJ%yBO1rMkUs#8h}^2Td4V-5>Q;6k`OQGYLp z+Pf+NT^Wz6dSz$}pl@XZ>1*h>Y&gih2GNaFJj-%IP4J>_n*8_f%D*sNmx^pQa2DS% zdm;zK4sXx`>Ya!`I4*{sWGAdvMFglIKii6Vg-ra$Jx5rheO5lnOMN z4evgysI--rN^*>OLkdcmP%VsC;7Tf7z~$>_^;J@CKcTkMbYI~7-BLIw?q#v+bB%|w zbZ6Aftv*C%wcvGQmkhpwkD2_|DV1_vg75;pB9tVI6{=d}=9AWD-u@9D<$a2ykJ=T_ zUZW(|z?frIy-f;~LxFT@(ywr0ss&01-WOyfCfW*TuKYwWLU&Jmp2xj`7Sk66D79qHNHb4kEWE=){p_-T#TGTnoYgjBxJ#A$v zO|T0^yf`XhJNs>r#vbuA#$GvArR#DQk`QoPZHgd9cJ9ch!%4DVqE}s(yqcY22k;nKFRKGcCq0 zl<$qp7b2)%toV`y;c|*S{Kh8G+)KAIcSm4O$XO(XnT#4wA3FL9V*=T-ev@C~_9*Ovej-Y*Cv4|@bP;^-W%dQ(BomKmK3WfuR*2TF~}%xn6|h*}0RLP;ZFzO;Z)C&ak#?+4(Pc+kIJLe7QM zrnVAlih{%y9e6)T-}3MKY!uD%7-5HhS8+>)J?jq9&YJ&idWEtqd--%g=GIv=ivTeBB@V9rcCwW18#jvCBj1hZnwxagL6`cqp9_7nj{@_v3 zq2k9}e&XXIJ^TzRcyVEn?U3K;n+MtFjd&QJNa(h4X-B>CyGHhcC_^po^Kv^h4-_IG zPrC7|vz(b)urix_ft>N{QYBu$=57jIo{Q- zuIL7F2#$oN$fMUG7Jit`DFJN!q*u_oWlm7k^F+e4R0XjyZFzxj=>D4scq;0;zq@Lr z3`sfO>wST)hvxf#b2$BnYC#AAzArsa8BXib(Lz7kUct-gCirX(fqRhCd_qcK?B zUQ^>sH|`faV?*fl%#F&MO>?f^TbC|e%e1t1HStPnP`yjjbx*BlmN?hCZFO}#Q#o+; zHv%Iy<}$FeL5={;+M9z0IpFvq(ZyDl@T7B45WQUeFi;~@crY1DuI$@p@(?N(k~I&J(81HdB*b3INrjSaf#~&d&Cmt9D%((s)Cbeqro| zuA?W*M!1~v{-#*Ipom|VAX>U@$P0H^{(#x$_f!OYoU7Gu;kp_m#kk_&u0S+xgAFGdIw{}diy6uJg zDJ^hz0J}!x0!j~N93|4cLyNo0ZJn8Fn(AMA7e6QMC<7Q)gBO*&@}H?$5ScFtcjB5w zqsyxzMhu#K@q&cjHqOM~Exv0J8#YW%htu4`soh$g^boi^T;g0;I4L@9f z+AS$8H|OjK-zbif$nlxV^~M+4ZS9KJ*rDEaG~cOWoJx>zQ_4L8RfoZ(ZBLo)@d@om z*0Ild>rPSa?P2S)3QW%_z+7@MBfD9OBbP&loq3fT_nJZpH_;aT7V)CY6#T^Ep60CT z4STRk*lNV+!i7kk+r;`ys*v`x+KROx?xnB^jafwM^(_+H<1>fJLq@bdyk1!;UeQJ> z(iax|;5A`CQJo>$xdG!*$!5>9_p{XCI+0Z)rNNKq%D=?a(Dl-Fqr(Qg&NB5yta75F zl<(K2pZE)Z5Y}1PBmqx&hH}KomP~FVK}Z}g!yt}Z)zK2Z3G{D=&Eo3bgk(y`sr=%O z1nD=g^7G_=vZ90SDH1ZI2fDm^(AI~jZ?w{MAvaP^Xtyc9-2blpTzRy3>UHUxu`Q%^ zvF^du9sWF>obq#=vc0k4hI}Qa4v0&l?U{3oYpC!N?|fYcNoCXvj}aPd&0fpg{^C5- zwf^NZC&rCDX^WgJXG)FYB!CME@RMSTw}anV8N4gV?NaNdqDbYj;~N2gSyH?WBOGR) zK%r+#k(VDe9bOC)EVP)ReA%=?>!cuELR(rd0*D7^I(tz|1w8pTY_2%JA7{$y_sxO~dBuznO=eGGCp0^oU7F$_ z9-fuAp5ls_sm49fK)3QxGQ-&-?PkT7(XMauTWwfA5@4w@1mdtta87c^8q6i0B>{&S zFTZ|+ zyCUS}tZW^E)jN9~$Buz$91@km*4{jRW$=8DC{SA zvga`U49tFbG(0-eq!JPsdIS?&x3&`oVdKY&z)!f)db2VivrtZyAxHo zxaS$I36IzamD={cn^-tNY{7{w)hpZN_gB`nbtd`Yh<2ObJxr6t6O?OQm7i2gI={zL zqLsI82wtzF2awOazZAN^cgs=jW($V33TEk6=1m8>)SY0&$r;)>+A3%`+?_fvpQC~6cK$-DpPla zoPNZ{ZM5V%+5AonvJtV!Ei*w+HJ3nFO;~z_%;cMtw_O{Z54*6=^ z&|{C-_V}BM_H#KSXie!7kgS}i4c5-g13vS%Sz9mxqdrc!<;o&|m5p|E>>XM9>aib* znf;(SFcFiTXru8lonoDJb^4nsa(dvWQG{1a=<9}v1SdJm2FN)-B_UJftpMrkOaJT1 zA9Rp@aUqNQ)c~b#;nxF2T0=!4Z?5A6hYVL?$3$R*xSNj1;#Yvho5*BMf9&M<57WAm znJ+*31s|fEo+tq?*-c`(mweIN{FaqKL)0cQHj+DO$B>-YZcZ>3JZjBhn)0l@2w{wm z1;w%PpwM`t*V|~c=cksegUyXN<=g46Wv%^6@*bjL(r4~Q$4w?%8SMnZEy;V2>af?I zS?YOsdF2h!%{h9PJBg)~ZC%sCo36yW34BIPA54t=`@I`BvSwDu!H12rVq_6FE;=Wj zb@IqjqzaXl5GVF{#Ts5fDb#CtA}5+Z;Ka&nqOoH00Zdljvh8RD8hh8->I#vh)@ut= zv(e8{g^-*IK=dxwDy-+Ii&e|b8|$^XO|F0It$yS7!;4>T3lf+1J0FeI*N5q~xf2yd z8h6EuY)lfr@Yj?Ci4Q++>aB&dbM+<%_0LV>+nh9h5Grq)-2gyxW7r+lKPQ8lqHoGu z`Y1AZvQP9rEt9&45U1w4xXCsitF`R3%&eB_wc?EULP}=q7~U03!DbzZ#qt!-$Uo~k z2sk(O*$oETy&k;Kj4}$1q~_{gUw5ve?fqnm38ZSGqSy8QQ|kvQ0cRZ_xK9`HS3S7>5o!Ha*srI_ zr21ny+BQ?>=)LX8a3A6ze}N5Z0d-{lN6q75!J#yYHVRs_(1 zKZ)%`iPv?-x)}PVKcKPt>SXOFdS@ALsq{pOV#b>#zE|m4v&lVS%Yxoru2q}F31_>Z zEp^?^{svQfY;Kb5&B#5wz4oYx=w~UH#%Hcy0!pbl`$AE98rLDhE2ge6aqbS|BMJN~ zEh&#PY9LH}1e$%+M3H;HlH71CC{4#a6pl%qv{>+sUJ(TvfDs%d34jvn5XG}92$q}G z2$6ufR-MSR;>4E&Xrx>Z!mh6cVJp-1?RdFfca+!H^CmF~@7_XsI!fC`JI7^;>JuXJ@t~||UMcy)w-h<3kg{N@W z@sNVdcfcVh4!zxn<#A;%_jytM`iuMbm4fLn#cl}Jq9S-=l^wr($LjL2eY3M1YF+5a zRNNISE|R)muurBEkQMJS%nIXdH8H0GgFK1)H`Zrg<-1@PYEj!zCp?MgO!k519Nq_y z1Z*(hKP0pzG&{p9-lZ-m0K_2&OoN7Tj*1h{V!IJvjkB%Qpf%L6kTx-68k23XVcQ03 zg*OJ9yWvzU?1(4`_9#=`x<68K_p&(quwV3rH6%McYLw44J9|m09TvQAJPXI(5pE1T zyfK=9t-jw)^>t~%$Zh%LvtB5?mIFU~Sx|TR0v4sC^SzsEcqkNJJv1!G-DgA~U9#!T z(0_-t|La~;s;ARiDeLR6Y|P9FnDh;b-%gz&@Tz$!*mrq|R|mS}w(i|<2bs|s&M$98 zxN2F)z1|S#>SA$e!0&#N!^JQDzH2$ZQ{&W?#G_`U zT&u-y6#Y1Wxi15ho{kXu@pPCcYHa1f0;h!cxVKp<7T?5|H&@y69Pg(WbZ+AV*~~=a zWFBl?sQkx#XcA zrJ5Y?9uL^PY@`p27p}$J4rWUC_^~B7u7XQA)909h-Pry7gUk=4G}4}ZF2pqoSz!SH zk4pOiR@?R@?lr{!2>Nu9@3J0K-$o2;xh9d=yJq{^Dy=ASgE79P0e-(2W8jJOcDGhV zC`=&#eZd&ngiP%9H}Ld>vr)&ag8_zN<)UPdMGXvnPNm?Wav#{JWC3d_V##Qy(5y@ zSNp!8k-LhHBIiaN&-Obazf;Lnw8r(q7?B)oRv6tI=X~`KtWmoNWQ@2e+`YT2OdJao zFV;m#-XCnd5@7{)U?6*t*{KDK&yqtyFC{@{?n2Jg0r~{2*D0}soTBOCJ=8@>Xb<0# zK;5zYRh8eSD9c~7C55i(KkbT0HowMd6#$x!^P_ND>qh%cUqyam-YTm1U<9&5i$Z`S= z>1&&#{61z+^Lc$-CO@ZmF&*0@f51dDt*lfn`U_922+^p;C}SBn0LWA9)nra+)HmU; zTpFSuue82;*DDYsM8W{r=7bfJE~PIae$%~92RXBfu} z*4P$Vs7DYnU*)Dup*fj@u|~eol?7np)(DK)4q8!I_b5YSSIYF-Fz8xu4E)xyuhx7` zp7^9L+o(a1f%lP#b@3;gvt}GaV=~kr-{7mC@CMr-M=1noC_U8hg-RTni=o~h**xjj zor%jO719fGvgE>UtB1vYMrGe)O@tnNbjp{L^oBCGWHkHHwVY7-J-Fp|)q@ZCudjW} zp$-A7s3P&{Z2K-<#wTnSjKV!3@%4FO4D%fT?hhlQ*CCB+rZ^R?6KiS>WE_xR!^ZeR zPq@q(X#ou8d!3f;7pB>}HKh| z{f?Wje^dmZnlwJ?c-hXdDAT4_^lrc;8mYMwbofahdWpx{}@;o z5;|@v%8rd`Bz-!$T8uPletPeBQhl{LkMBpb?6Y!qy)vY?{+wHaCV!GO=Rw8GbBdjp zZCdu6X1G&yhMb`SBQg+!LkjUOy)#YRn)!0TfHIEV;T!K{M@k%%*W*NP$H?!Bj%+%A zIxsAVdp)B4juUR3?Xlug_lPZL-v^HGCvt;ra$Fg6YW(@@@V@zda$?9>3LDOe81vgN zk{xsb;R;kaH3TPmI(AmW@7)uV1^wZ>3wl-%XWUT)x|?SS?TRM@Vg3#cA0` zEi&&0;=VoHeYY%@jVDavrf-s+2=3tnjPcdvG2_X0h?2)5!nRPuhq=-1z>6P9l&Fe587P3zHtMhpC=GW~A`B z!4SMGzXc3W!oM`K)0KKlIwH zLk5PsT$Y{)?}zn{I`G@Pr5}&$+r#PnpA4_7m+!b9Px0Flo-QKVseA6pu2`0zgwF2K z;OQd&HP?XO9yT4cxk3WJ;^`Y~y@Vy+JV^xUAZe22X10`NNF{7A0u;Oyl<5G2zN#vGn)tc?!KBNnUS&-y&g~hGd}UbV z{IVe46v-$>t;TgDMfhG)OZrJQDgk(@01F_El~u#rl_h$1kE+)9cjRd~_MNs^hke8N$@VBEGDi_mmH7FWULFAAvNC8COX5vO=pB>^?= zp7kU^&#y$C#pko(q>47C3Vip`*^RMB&nR9DSH*28vgUfahjtYFQmwn3UlNzN(rH%( ze*Y2+ys*~1ZqRy8Tq)PWL}p@~p}PH^TCVB;wN7k1*2mw?7X9_ud&(&B8N33YMb8;m znat2=>^h+i)ii+q@b7jw@_PG=dBFWoQMl#g*7V?u8UZV;gUa6y;LV0>|8F{7Nx#Kf-z;l{^MJ zG+T`sl-6$1hz1)HIp}Ail`<~7hwpy1#Fd#<6$xAmn+l;1gRfS_uct&)L_*WXEPgSs zQs*viF!Mf;)sObt7+<}{p<~(pxlDOAt0sXf@sgc1)Q9LrGQ8W(OVcXMVkyEj zRrbY4g_TSc3-5gFbT0$VtvN7j%FIfL_7uK0Rv1WsG z${~#Cd&=pVY46gHS(bn{SjWdJKT>d>>3}jQxN~LLFt`^I%r6gYGp|pGva6EE@Wit7 zHV}TsNRPx=`5nXSvXI2bW`r_XQFHcz-*|=?`%1Mvk7DfyPU+X~y}@^N7KdsD66}f{ z)^5F2dfkkbT1bT^DpgH`E{8^<`hK5Av|$W|+rfl(^{?9pf0WuPD4HgO0O0g~V*-*0 z&Ntg>nMo#FnI$6YjtxxYJ_SA^lhlc+-=0MrBc8I^-(bw@INTWMs4`YYqNw;=#(0(M z8&KLjf~&6iT7db3_Is&6x5l&iO?@v8Qbqf*qW?HtS3=g>Nu!zA>fWTri`3Oks-oYb zJcz!oG$IOBjFrDI(}3B`_VAkuDFd;e7oW1fqE|w>^IyhWduy0r$2kd#v2qOFXb7#KFjh}Q};W6yHS zC#iPvx3$;}jkw?W`YxT0x(Js)EEZLMgOnkA{Pt2Sn{am$IU{5T(^oa!uEWCbCN{|b z*h%s;Q?VA3J*82-$(St@5;NmG4PD(j3$73`y%ZVCfV~5)pFbCM_Ea@GoL>@yU!iI{ zhbkx}a)rb+9hVP@G<9B=9$Rs3W*T?ZFw&R_|6q0fQtF0xS^wl1H@6HgIINPQTl3+h zDV`;Syu-ErSU9-MjoHnt=H?da8^#*u-#N(bDv}~fF}Oi(KXK2q_5L_?mYE4cB?8|5B(5U# z;OOa{W_Gp7oznGPfOiV2=eMW8*b7xgB!{3t$bX1XQiabtMx+D&->ekCKj{6CVh;EI z!402QNdpe}sajj(FQC3L$|N;m8VlU4&rsmETl{CX|DZ4agMN3gWSLQmVDv4@9|tdk z5}y%>(~+-O_?MYKfR*DYWYcXp9bqRrcK^aIg7gXAI)lat&H3f-@mC^vfOhB>^_;EC zVn5yR#=FZ59B!;!Dl>H^kAg4U7R=%=f0FpY)W8q$?kXCa|BvlIK}80$4*@V5#oqLz z^bg4YKLANM!Ac`)&;J6NT45G@5bD$aS)C}wYyV=y_75AsV@ulq&Bm5GTna5?CI3PC z|1)yHYZ~n|(N@-gZ#e%ooafK*!8bqDlnz|3XjI?@C9vrBJJ>>5%y>XUmkNAgiO?e| z{Kxyq!3i~Dl}-M$*#6oIA`?9_u!Fg5yS)5ga|XZ_0~^tJuJ9j`zBKj5EEXixe+K`S z9JAp2AD{krxGV#FxR$lb14&SD;6F1|n*ITd)gt|`Fd{p&iS-w5tq{1lMi9$~I;*dk z8<4C=>N~d z!3~$-z<}>X?Uv-}2lPc}jdfhx3ATYoHnuo3HBYfGKNR!Q$-KQ%nGyg^0*?l-V^d-f zQ+cqBpQ%;Qd%m@No$MU8Nios_0(AB;DY1-Dj9Z5%+f0(y|Fu3EbLGs|+s}8e{Cf_ii(Opah`8D|PiWdTK%+L&J^L z0&qs|Ywq-QC=crj$Lusl`?3^&@x|GrPSwbTRL5ZcPUS13K6nSMzh>)rug1Np^Au3^IuI4{IMceViT)_=e1O8D$#h-$VW?(sPl*g zap7^@9*)Zb(FT(Wiypx^V>GUuI44Yp1O~RFCZ6h(tR89eax|Qi)&+6j>w)NY(=R>E z$(d<=DG~)Z&Pp(!S>~a>!VvpoA1gqVBrc>FHJOoR1vD|+DUXba`=!0td4vsi^2maR zS2s`N4$pj?=#_CdBYHWkmm3#$y=UH_f)J=#`AH z6(5L6j@2swkK~kGJ@Wo+*GM~H9ArTlPv<6LHo5xp7iK=;V%lddZGd#f+Uyt#G!Aku z4v-G^FZhhC2AMWnnT>l_|-694$`E)S=TAE;CsM~;S_ju6jnUN9%%Cw z?yiI6iL}1{F>MZ^AloYTH~z>ff#De!3jkt63c?@Nk%~F_X6Scuag_atR6yCZc*=g; zmt;hF*u>DE}-tqKe^1V`QTF*8Y8*TkkKAFHPq#;PMliM4tUPXKy_TGQpR1Y)tEs zxyhU5-=`Ex^#xxx#%Ymv_#HI7Zm)%~4I)U5t$t<@2McF#n2Bc;fnZ zI2cKFFBP$&#t*&u!*8IH#g~KB)J;`{6l?A9&Hwt*G639Wwf9watSB-KCp1`)FE z#eenASo!;p>YVt$K1RyO%IN*&OrH-PS5k3)u#x>Q0Odcgl(>P%6?~i8e<3OjhbIAV z`AK&p|Cl2QiaRNMM9?>8c(5wLEd5ssqHhI{p5;F=Uo_?aD|U0xz-6Nf)fN29PsbRc zmNTI~^B?q>H5va^F??qu2dCXW9rHpS1q-R^uW&m)!xONEjmW8gqyeV^5Z`fHIDlaE ze#u__9|M&^(anO>Nt%n~@t-*RcLw+-0Y2QQU@HD^n1By2BZA}B>wihI_}@ea?vPS) z;KRS!l#%^)PAj7*No+_1f8@!BtmpPGd5-^K^Bkwx|KEw;fFd05XP&P7{{VSXJAQ>) zA(Z2I*8VFcWB&sfowqalfeirxw?X-(>>K!Y3Z7a0lXsq50)wZTAl{SP9v#{CijA$sh3M-gnOVo!|MLbMH*jJVd%^ zMi+~F<}<^OYmLxUD28uLLZaFPn%DT<-sZaTAO`~km3JH$CiT{MM<=b=^|Enj`L=*+ z>-He$48qA@VJx%k$~j0)@9+eZDw`*7F+(&vaA82@U<*56;Yhm&7pe~0m(l*%#{5dI zgxKD|s#zp&F9XJSKLnY)Wq{`9sR6WG9ohxFm8asoVL!-x&y&pEL>6yj7WdpNokq}7 zociqjznfy4GH~H5#4^el(L6|CnTG^DWfpnp?aBI&rY|29j_*j5{1OqCUFv( zUd9*^H4GVCpcuCQ{7dIPV*3PcU!9d3hOT?xptsL~?fJ#2&$#Wgp#cHJb=3T!dqf>tv3iD9 zs_!+>>7^|4%;zckQ>Zo$b^55cePDubq10Wd>xw7q-Fh0f=dRJ*YvS19mMM-0eI-yG z-=Fmw$Jk{3AmXv2u`3;=ij}2OB#r&+`YAP!JB)o2cZevw>svDGIHFtkl{uqnG>i$M zmj4~4s%O^=V}j-9%D5I(OKn5obPID&vKo!P=VF)WGKW-TvO=l%$Q9i|4uPusN!D({ z&FhG!qJ0Ts&X!PQ7(DcMtUGTeU{A+|bG|&QWK)I$&g_eXnNQKonHEPKYXeYG#GsinhqH(^ zV?3pf{4NeM@!i9YF|J+LQXBBj!1-m)v^uu{F=INTJF19b|p0MEyioBIaW) z=f7YuU;38z{xPZ=UK!(-cm6=^9)vemesZ(1kYyFtG=*kt*&Qdih0E3&?Sp4$QugI1 zmG84;g(kWjh?REyjQ|YW5^RB%O*!Nb#=g(|_@}snQ3$K=@_5=+q7G7!vs=@1X(BVS zCOLzMwyyCM(23Jul6J<)bfFMIPeYwElGi?WI)oaVmE};gm5Qu!!$Z|$`2Ih)@N^qB zQko!fk2J40rLttuiv97zqe!;+F#6AEuP)x_$W-8=)9?1R>DrQR|J~8+`E@|G;kPxK zcs4&mSyK}1Qw1OKUqY0W1e?-zbCQY94@_Xv&MKL1J34(qI#ka2f~T>!1wkk3ze!>6 zp&LPW0MKB#Kc_U9tBGUl^}AeQqVin>)>J}AeW9B^u#_~?mn@7voi{lgHyMdEHYZ=1zcs8;+4`*2~9uXJ%fkAce{5@sll(9EIZ16i_6CF@P7cd6>U z8-(5+1|_JBj5_)fTgTYs?xGCs&F&J`vHfgW3pLV%Shp3LGI%?W@D^k3!lQ%`SkF}N zElW70F87?+x&OecqVoMQ)_FY2It}8qXGtTi#0)G3lvn%84y9LK6Q!M3{JP&k>>i5^ zl^k{o?mwt1=BZO<|KIFiaxVN@=J&<194KzKPuv-RSUO$`qvYCNMY5W zuue;x`+?ZC(LbPi((tJ!p9iOUrZ(IM|DBN4hoaa|z7GL=qP|tKr&FNxf5GU#jbSQM zQtzXrJ{s4BGLjmj5}rFot;XcEzSUPlOkPm}yTn_;3_}AE<-g<@EF8*D44m#KP{k!;! z1df-lzVai3!GSEFGoB*kKK;wtQNi2*2rtT>s`D~TT zk4KWFG(&kBmif|>b-pl(J<#UNC8i!jllG?QT+Fz;W%G?2Lw6u>SE$gm388aE3xAAT z_fu@z;t^rN*Gm((^T|6ScGU^{BF6I$6X#bEHbm{Rquv=rE?(gW?3w-`SXCaCn$kHAi*=7NN}Kb W;De$|o9f|-;PsS@C%R<- literal 0 HcmV?d00001 diff --git a/Desktop_App/requirements.txt b/Desktop_App/requirements.txt index 10307ee..8c420a1 100644 --- a/Desktop_App/requirements.txt +++ b/Desktop_App/requirements.txt @@ -4,3 +4,4 @@ langchain-core langchain-community npmai==0.1.9 npmai-agents==1.0.2 +supabase From 31413cee81a878ef2e6b3d7b77cd26612358b19d Mon Sep 17 00:00:00 2001 From: sonuramashishnpm Date: Wed, 22 Jul 2026 16:17:50 +0530 Subject: [PATCH 48/68] Added correct githubusercontent link --- Desktop_App/mcp_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Desktop_App/mcp_link.py b/Desktop_App/mcp_link.py index 2a5be2d..e1ead6c 100644 --- a/Desktop_App/mcp_link.py +++ b/Desktop_App/mcp_link.py @@ -10,7 +10,7 @@ CONFIG_PATH = Path.home() / ".npmai_agent" / "supabase_config.json" -CONFIG_URL = "https://raw.githubusercontent.com/yourusername/yourrepo/main/app_config.json" +CONFIG_URL = "https://raw.githubusercontent.com/npmaiecosystem/NPM-AutoCode-AI/refs/heads/main/Desktop_App/app_config.json" def load_config() -> dict: """Fetch latest config from GitHub""" From 5b8dc5cecfd845039d2733289685982ab367bf28 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 22 Jul 2026 18:30:43 +0530 Subject: [PATCH 49/68] Delete Desktop_App/__pycache__ directory --- .../__pycache__/mcp_link.cpython-312.pyc | Bin 10747 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Desktop_App/__pycache__/mcp_link.cpython-312.pyc diff --git a/Desktop_App/__pycache__/mcp_link.cpython-312.pyc b/Desktop_App/__pycache__/mcp_link.cpython-312.pyc deleted file mode 100644 index 5b22e271c15849259cb2f9e83d3edecfad090b8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10747 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 Date: Wed, 22 Jul 2026 18:48:00 +0530 Subject: [PATCH 50/68] Update app.py --- Desktop_App/app.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Desktop_App/app.py b/Desktop_App/app.py index c383d4a..a9a6a06 100644 --- a/Desktop_App/app.py +++ b/Desktop_App/app.py @@ -68,16 +68,19 @@ def _load_llm_stage_config() -> dict: - """Returns {stage: {'provider':..., 'model':...}} โ€” falls back to npmai defaults.""" cfg = {} if _LLM_CONFIG_PATH.exists(): try: cfg = json.loads(_LLM_CONFIG_PATH.read_text()) except Exception: cfg = {} + for stage_key, _, _ in AGENT_STAGES: - if stage_key not in cfg: - cfg[stage_key] = {"provider": "npmai", "model": LLM_PROVIDERS["npmai"]["model_default"]} + if stage_key not in cfg or cfg[stage_key].get("provider") in ["groq", "openai", "anthropic"]: + cfg[stage_key] = { + "provider": "npmai", + "model": LLM_PROVIDERS["npmai"]["model_default"] + } return cfg @@ -1478,4 +1481,4 @@ def closeEvent(self,e): pal.setColor(QPalette.Highlight, QColor(P["mint"])) pal.setColor(QPalette.HighlightedText, QColor("#050310")) app.setPalette(pal) - win=AppWindow(); win.show(); sys.exit(app.exec()) \ No newline at end of file + win=AppWindow(); win.show(); sys.exit(app.exec()) From d3cd84d765b14ab335c0efe9b7c1ca3ae3031d03 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 22 Jul 2026 19:01:53 +0530 Subject: [PATCH 51/68] \ --- Desktop_App/app.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/Desktop_App/app.py b/Desktop_App/app.py index a9a6a06..3d66a32 100644 --- a/Desktop_App/app.py +++ b/Desktop_App/app.py @@ -1042,11 +1042,19 @@ def __init__(self, parent=None, group_name="", existing_data=None, lock_name=Fal title="๐Ÿ”‘ Credential Group", subtitle=None): super().__init__(parent) self.setWindowTitle(title.replace("๐Ÿ”‘","").strip() or "Credential Group") - self.setWindowIcon("npmai.png") self.setMinimumWidth(440) self.setStyleSheet(f"QDialog{{background:{P['void']};}}") self._row_widgets = [] + # === FIXED ICON LOADING === + from pathlib import Path + from PySide6.QtGui import QIcon + icon_path = Path(__file__).parent / "npmai.png" + if icon_path.exists(): + self.setWindowIcon(QIcon(str(icon_path))) + else: + print("Warning: npmai.png not found for CredKeyValueDialog") + lay = QVBoxLayout(self); lay.setContentsMargins(26,22,26,22); lay.setSpacing(12) t = QLabel(title); t.setFont(QFont("Segoe UI",15,QFont.Bold)) @@ -1071,7 +1079,7 @@ def __init__(self, parent=None, group_name="", existing_data=None, lock_name=Fal lay.addWidget(self._rows_container) add_row_btn = GhostBtn("+ Add Key", P["cyan"]) - add_row_btn.clicked.connect(lambda: self._add_row()) + add_row_btn.clicked.connect(self._add_row) # Removed unnecessary lambda lay.addWidget(add_row_btn) existing_data = existing_data or {} @@ -1090,9 +1098,11 @@ def _add_row(self, key="", value=""): key_in = GlowInput("key name, e.g. token"); key_in.setText(key) val_in = GlowInput("value"); val_in.setText(value) rm_btn = GhostBtn("โœ•", P["rose"]); rm_btn.setFixedWidth(36) + def _remove(): self._rows_layout.removeWidget(row); row.deleteLater() self._row_widgets[:] = [r for r in self._row_widgets if r[2] is not row] + rm_btn.clicked.connect(_remove) rl.addWidget(key_in,1); rl.addWidget(val_in,2); rl.addWidget(rm_btn) self._rows_layout.addWidget(row) @@ -1108,6 +1118,7 @@ def get_data(self): return name, data + class SettingsPage(QWidget): def __init__(self,parent=None): super().__init__(parent); self.setAttribute(Qt.WA_TranslucentBackground); self._build() @@ -1271,13 +1282,13 @@ def _open_add_dialog(self): dlg=CredKeyValueDialog(self) if dlg.exec(): name,data=dlg.get_data() - if not name: - QMessageBox.warning(self,"Missing name","Please enter a group name."); return - if not data: - QMessageBox.warning(self,"No keys","Add at least one key/value pair."); return - existing=CredStore.load(name); existing.update(data); CredStore.save(name,existing) - QMessageBox.information(self,"Saved",f"'{name}' credentials saved securely.") - self._refresh_custom_creds() + if not name: + QMessageBox.warning(self,"Missing name","Please enter a group name."); return + if not data: + QMessageBox.warning(self,"No keys","Add at least one key/value pair."); return + existing=CredStore.load(name); existing.update(data); CredStore.save(name,existing) + QMessageBox.information(self,"Saved",f"'{name}' credentials saved securely.") + self._refresh_custom_creds() def _open_edit_dialog(self, name): data=CredStore.load(name) From 7027e417ceed2bcbc369af4a6726e491a77fe6fa Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 22 Jul 2026 21:13:03 +0530 Subject: [PATCH 52/68] Update app_config.json --- Desktop_App/app_config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop_App/app_config.json b/Desktop_App/app_config.json index 3ca7ba3..3f4f19c 100644 --- a/Desktop_App/app_config.json +++ b/Desktop_App/app_config.json @@ -1,5 +1,5 @@ { "SUPABASE_URL": "https://qyxuvuhhaspkuhbognyk.supabase.co", - "SUPABASE_ANON_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "SUPABASE_ANON_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InF5eHV2dWhoYXNwa3VoYm9nbnlrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODQ3MDYwNzYsImV4cCI6MjEwMDI4MjA3Nn0.h3hyVr_OV38SfY2_2qQ6Hgtm6_FbvNQ31AG5YO7mVko", "MCP_BASE_URL": "https://your-mcp-server.hf.space/mcp" -} \ No newline at end of file +} From 566565e2c990e2ac7b5ab377c3c30b766a52a2fc Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 22 Jul 2026 21:20:55 +0530 Subject: [PATCH 53/68] Update app_config.json --- Desktop_App/app_config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Desktop_App/app_config.json b/Desktop_App/app_config.json index 3f4f19c..fe3d1d8 100644 --- a/Desktop_App/app_config.json +++ b/Desktop_App/app_config.json @@ -1,5 +1,5 @@ { - "SUPABASE_URL": "https://qyxuvuhhaspkuhbognyk.supabase.co", - "SUPABASE_ANON_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InF5eHV2dWhoYXNwa3VoYm9nbnlrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODQ3MDYwNzYsImV4cCI6MjEwMDI4MjA3Nn0.h3hyVr_OV38SfY2_2qQ6Hgtm6_FbvNQ31AG5YO7mVko", - "MCP_BASE_URL": "https://your-mcp-server.hf.space/mcp" + "url": "https://qyxuvuhhaspkuhbognyk.supabase.co", + "annon_key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InF5eHV2dWhoYXNwa3VoYm9nbnlrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODQ3MDYwNzYsImV4cCI6MjEwMDI4MjA3Nn0.h3hyVr_OV38SfY2_2qQ6Hgtm6_FbvNQ31AG5YO7mVko", + "mcp_base_url": "https://your-mcp-server.hf.space/mcp" } From 33ef3968f99a99f7b9153aefbaeaf15b5dad051a Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 22 Jul 2026 21:22:34 +0530 Subject: [PATCH 54/68] Update app.py --- Desktop_App/app.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Desktop_App/app.py b/Desktop_App/app.py index 3d66a32..a74c7e5 100644 --- a/Desktop_App/app.py +++ b/Desktop_App/app.py @@ -1,4 +1,5 @@ """ +app.py NPM-AutoCode-AI Desktop โ€” v4 (npmai_agents edition) THIS IS NOT YET DEPLOYED. @@ -76,7 +77,7 @@ def _load_llm_stage_config() -> dict: cfg = {} for stage_key, _, _ in AGENT_STAGES: - if stage_key not in cfg or cfg[stage_key].get("provider") in ["groq", "openai", "anthropic"]: + if stage_key not in cfg: # โ† only this line changed cfg[stage_key] = { "provider": "npmai", "model": LLM_PROVIDERS["npmai"]["model_default"] @@ -701,16 +702,27 @@ def __init__(self, parent=None): nolbl.setFont(QFont("Segoe UI",9)); nolbl.setStyleSheet(f"color:{P['dim']};background:transparent;") cl.addWidget(nolbl) else: - save_btn=PulseBtn(f"๐Ÿ’พ Save {pkey}",P["cyan"],True); save_btn.setFixedHeight(36) - def _save(pk=pkey, f=fields): - data={} - for k,w in f.items(): - v=w.text().strip() - if v and v!="โ—"*8: data[k]=v - if data: - ex=CredStore.load(pk); ex.update(data); CredStore.save(pk,ex) - QMessageBox.information(self,"Saved",f"'{pk}' configuration saved.") - save_btn.clicked.connect(_save); cl.addWidget(save_btn) + save_btn = PulseBtn(f"๐Ÿ’พ Save {pkey}", P["cyan"], True) + save_btn.setFixedHeight(36) + + def make_save_handler(provider_key, field_widgets): + def _save(): + data = {} + for k, w in field_widgets.items(): + v = w.text().strip() + if v and v != "โ—" * 8: + data[k] = v + if data: + ex = CredStore.load(provider_key) + ex.update(data) + CredStore.save(provider_key, ex) + QMessageBox.information(self, "Saved", f"'{provider_key}' configuration saved.") + else: + QMessageBox.warning(self, "Nothing to save", "Please enter a real API key (don't leave the masked โ—โ—โ—โ—โ—โ—โ—โ—).") + return _save + + save_btn.clicked.connect(make_save_handler(pkey, fields)) + cl.addWidget(save_btn) lay.addWidget(card) self._provider_fields[pkey]=fields From ab8098ab6470ebfa593aa31819c54a6f1e4a9a89 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 22 Jul 2026 21:22:49 +0530 Subject: [PATCH 55/68] Update mcp_link.py --- Desktop_App/mcp_link.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Desktop_App/mcp_link.py b/Desktop_App/mcp_link.py index e1ead6c..be23d74 100644 --- a/Desktop_App/mcp_link.py +++ b/Desktop_App/mcp_link.py @@ -61,12 +61,12 @@ def _get_client(self): "The 'supabase' package isn't installed. Run: pip install supabase" ) cfg = load_config() - if not cfg.get("url") or not cfg.get("anon_key"): + if not cfg.get("SUPABASE_URL") or not cfg.get("SUPABASE_ANNON_KEY"): raise SupabaseAuthError( "Supabase isn't configured yet. Set SUPABASE_URL and SUPABASE_ANON_KEY " "env vars, or call mcp_link.save_config(url, anon_key) once from Settings." ) - self._client = create_client(cfg["url"], cfg["anon_key"]) + self._client = create_client(cfg["SUPABASE_URL"], cfg["SUPABASE_ANNON_KEY"]) return self._client def sign_up(self, email: str, password: str): @@ -124,7 +124,7 @@ def get_or_create_link(self) -> str: "platform": "desktop", "token": token, }).execute() - base = load_config().get("mcp_base_url", "https://YOUR-HF-SPACE.hf.space/mcp") + base = load_config().get("MCP_BASE_URL", "https://YOUR-HF-SPACE.hf.space/mcp") return f"{base}/{token}" def start_listener(self): From a88efc5302afeaec6f4c5056f9d4052b8a3b8a02 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Wed, 22 Jul 2026 21:23:17 +0530 Subject: [PATCH 56/68] Rename Desktop_App/npmai.png to npmai.png --- Desktop_App/npmai.png => npmai.png | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename Desktop_App/npmai.png => npmai.png (100%) diff --git a/Desktop_App/npmai.png b/npmai.png similarity index 100% rename from Desktop_App/npmai.png rename to npmai.png From b46bf5401e5b13ab3d105db57353a93bea6e0649 Mon Sep 17 00:00:00 2001 From: Mohammad Faiz Date: Wed, 22 Jul 2026 23:07:07 +0530 Subject: [PATCH 57/68] Add .gitignore and fix bugs in app, config, and docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This update makes several fixes across the project: 1. Add .gitignore โ€” stops Python cache files (__pycache__), virtual environments, IDE settings, OS files, and user credentials from being uploaded to GitHub. Keeps the repository clean and secure. 2. Fix app.py โ€” removed unused 'threading' import to make code cleaner. Fixed mouse hover effects in buttons (PulseBtn, GhostBtn, NavBtn) so they work correctly. Removed outdated comments from voice input function. 3. Fix app_config.json โ€” corrected a typo in the field name 'annon_key' to 'anon_key' so the Supabase connection works. 4. Fix mcp_link.py โ€” config loading now tries three places in order: the user's local file, the bundled app_config.json, then GitHub. Fixed the key names to match the actual config file format. Removed unused 'import os'. 5. Fix requirements.txt โ€” added 'cryptography' and 'requests' as dependencies so the app does not crash when starting up. 6. Fix run command in docs โ€” corrected both README.md and Docs.md from 'python main.py' (which doesn't exist) to the correct 'python Desktop_App/app.py'. 7. Remove empty file โ€” deleted Docs/app.py which was blank and served no purpose. --- .gitignore | 38 ++++++++++++++++++++++++++++++++++++ Desktop_App/app.py | 20 ++++++++----------- Desktop_App/app_config.json | 2 +- Desktop_App/mcp_link.py | 19 ++++++++++++++---- Desktop_App/requirements.txt | 2 ++ Docs/Docs.md | 2 +- Docs/app.py | 1 - README.md | 2 +- 8 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 .gitignore delete mode 100644 Docs/app.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c61ceb4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +*.egg-info/ +dist/ +build/ +*.egg +*.whl + +# Virtual environments +venv/ +.venv/ +env/ +.env/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# User config & credentials +.npmai_agent/ +Desktop_App/app_config.json + +# Build artifacts +*.exe +*.zip +*.dmg + +# Logs +*.log diff --git a/Desktop_App/app.py b/Desktop_App/app.py index a74c7e5..3de7e10 100644 --- a/Desktop_App/app.py +++ b/Desktop_App/app.py @@ -5,7 +5,7 @@ THIS IS NOT YET DEPLOYED. Here it is about Desktop APP. """ -import sys, os, math, random, threading, json +import sys, os, math, random, json from pathlib import Path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -77,7 +77,7 @@ def _load_llm_stage_config() -> dict: cfg = {} for stage_key, _, _ in AGENT_STAGES: - if stage_key not in cfg: # โ† only this line changed + if stage_key not in cfg: cfg[stage_key] = { "provider": "npmai", "model": LLM_PROVIDERS["npmai"]["model_default"] @@ -315,8 +315,8 @@ def _tick(self): if self._pulse<=0: self._pd=1 self.update() - def enterEvent(self,e): self._h=1.0 - def leaveEvent(self,e): self._h=0.0 + def enterEvent(self,e): self._h=1.0; super().enterEvent(e) + def leaveEvent(self,e): self._h=0.0; super().leaveEvent(e) def mousePressEvent(self,e): self._press=True; super().mousePressEvent(e) def mouseReleaseEvent(self,e): self._press=False; super().mouseReleaseEvent(e) @@ -345,8 +345,8 @@ def __init__(self,text,accent="#2AFFA0",parent=None): self.setFont(QFont("Segoe UI",11,QFont.DemiBold)) QTimer(self,timeout=self.update,interval=16).start() - def enterEvent(self,e): self._h=1.0 - def leaveEvent(self,e): self._h=0.0 + def enterEvent(self,e): self._h=1.0; super().enterEvent(e) + def leaveEvent(self,e): self._h=0.0; super().leaveEvent(e) def paintEvent(self,_): p=QPainter(self); p.setRenderHint(QPainter.Antialiasing) @@ -456,8 +456,8 @@ def _tick(self): self._h+=(t-self._h)*0.1; self.update() def set_active(self,v): self._active=v; self.update() - def enterEvent(self,e): self._hf=True - def leaveEvent(self,e): self._hf=False + def enterEvent(self,e): self._hf=True; super().enterEvent(e) + def leaveEvent(self,e): self._hf=False; super().leaveEvent(e) def mousePressEvent(self,e): self.clicked.emit(self._idx) def paintEvent(self,_): @@ -893,10 +893,6 @@ def _log(self, html:str): self._log_box.verticalScrollBar().setValue(self._log_box.verticalScrollBar().maximum()) def _voice_input(self): - """ If you are working with voice input kindly read the documentation of npmai_agents and this code uses - npmai_agents version 1.0.2, npmai_agents change frequently although not syntax but it will be better to read - before making any change.""" - """ old VoiceTool.listen(): SpeechAITool.transcribe_realtime """ from npmai_agents.Tools_security_ai import SpeechAITool r = SpeechAITool.transcribe_realtime(duration=5) if r.success and r.data: diff --git a/Desktop_App/app_config.json b/Desktop_App/app_config.json index fe3d1d8..efb9c7e 100644 --- a/Desktop_App/app_config.json +++ b/Desktop_App/app_config.json @@ -1,5 +1,5 @@ { "url": "https://qyxuvuhhaspkuhbognyk.supabase.co", - "annon_key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InF5eHV2dWhoYXNwa3VoYm9nbnlrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODQ3MDYwNzYsImV4cCI6MjEwMDI4MjA3Nn0.h3hyVr_OV38SfY2_2qQ6Hgtm6_FbvNQ31AG5YO7mVko", + "anon_key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InF5eHV2dWhoYXNwa3VoYm9nbnlrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODQ3MDYwNzYsImV4cCI6MjEwMDI4MjA3Nn0.h3hyVr_OV38SfY2_2qQ6Hgtm6_FbvNQ31AG5YO7mVko", "mcp_base_url": "https://your-mcp-server.hf.space/mcp" } diff --git a/Desktop_App/mcp_link.py b/Desktop_App/mcp_link.py index be23d74..8eca9d2 100644 --- a/Desktop_App/mcp_link.py +++ b/Desktop_App/mcp_link.py @@ -1,4 +1,3 @@ -import os import json import uuid import threading @@ -8,18 +7,30 @@ from typing import Callable, Optional CONFIG_PATH = Path.home() / ".npmai_agent" / "supabase_config.json" +BUNDLED_CONFIG_PATH = Path(__file__).resolve().parent / "app_config.json" CONFIG_URL = "https://raw.githubusercontent.com/npmaiecosystem/NPM-AutoCode-AI/refs/heads/main/Desktop_App/app_config.json" def load_config() -> dict: - """Fetch latest config from GitHub""" + """Load config from user override, then bundled file, then GitHub, then empty.""" + if CONFIG_PATH.exists(): + try: + return json.loads(CONFIG_PATH.read_text()) + except Exception as e: + print(f"Failed to read local config: {e}") + if BUNDLED_CONFIG_PATH.exists(): + try: + return json.loads(BUNDLED_CONFIG_PATH.read_text()) + except Exception as e: + print(f"Failed to read bundled config: {e}") try: response = requests.get(CONFIG_URL, timeout=8) if response.status_code == 200: return response.json() except Exception as e: print(f"Failed to fetch remote config: {e}") + return {} def save_config(url: str, anon_key: str, mcp_base_url: str = None): @@ -61,12 +72,12 @@ def _get_client(self): "The 'supabase' package isn't installed. Run: pip install supabase" ) cfg = load_config() - if not cfg.get("SUPABASE_URL") or not cfg.get("SUPABASE_ANNON_KEY"): + if not cfg.get("url") or not cfg.get("anon_key"): raise SupabaseAuthError( "Supabase isn't configured yet. Set SUPABASE_URL and SUPABASE_ANON_KEY " "env vars, or call mcp_link.save_config(url, anon_key) once from Settings." ) - self._client = create_client(cfg["SUPABASE_URL"], cfg["SUPABASE_ANNON_KEY"]) + self._client = create_client(cfg["url"], cfg["anon_key"]) return self._client def sign_up(self, email: str, password: str): diff --git a/Desktop_App/requirements.txt b/Desktop_App/requirements.txt index 8c420a1..25bd6c8 100644 --- a/Desktop_App/requirements.txt +++ b/Desktop_App/requirements.txt @@ -5,3 +5,5 @@ langchain-community npmai==0.1.9 npmai-agents==1.0.2 supabase +cryptography +requests diff --git a/Docs/Docs.md b/Docs/Docs.md index ae924c0..3c07c81 100644 --- a/Docs/Docs.md +++ b/Docs/Docs.md @@ -5,7 +5,7 @@ Install the app: [Download Latest Release](https://github.com/sonuramashishnpm/NPM-AutoCode-AI/releases/download/v2.0/NPM_AutoCode_AI.zip) 1. Unzip the file. -2. Run `NPM_AutoCode_AI.exe` (Windows) or `python main.py` (Python 3.12+). +2. Run `NPM_AutoCode_AI.exe` (Windows) or `python Desktop_App/app.py` (Python 3.12+). 3. Enter your task in the input box (e.g., "Plot a sine wave"). 4. Click **Generate & Execute**. 5. Watch logs and progress bar โ€“ AI generates, checks safety, executes, and fixes errors if needed. diff --git a/Docs/app.py b/Docs/app.py deleted file mode 100644 index 8b13789..0000000 --- a/Docs/app.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/README.md b/README.md index d76f6cf..136576b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Install the app: [Download Latest Release](https://github.com/sonuramashishnpm/NPM-AutoCode-AI/releases/download/v2.2/NPM_AutoCode_AI.zip) 1. Unzip the file. -2. Run `NPM_AutoCode_AI.exe` (Windows) or `python main.py` (Python 3.12+). +2. Run `NPM_AutoCode_AI.exe` (Windows) or `python Desktop_App/app.py` (Python 3.12+). 3. Enter your task in the input box (e.g., "Plot a sine wave"). 4. Click **Generate & Execute**. 5. Watch logs and progress bar โ€“ AI generates, checks safety, executes, and fixes errors if needed. From 26d30c2c2e57e03818788698ff4afbea18b8964e Mon Sep 17 00:00:00 2001 From: Mohammad Faiz Date: Wed, 22 Jul 2026 23:51:53 +0530 Subject: [PATCH 58/68] Address PR review: restore Docs/app.py, voice_input comments, simplify config loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per maintainer review feedback: 1. Restored Docs/app.py (empty file) as requested. 2. Restored documentation comments above _voice_input() function. 3. Simplified load_config() in mcp_link.py โ€” removed local user config override fallback. Now tries bundled app_config.json first, then GitHub fetch. Kept the key name fixes (url/anon_key) that match the actual JSON structure. --- Desktop_App/app.py | 4 ++++ Desktop_App/mcp_link.py | 17 ++++++----------- Docs/app.py | 1 + 3 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 Docs/app.py diff --git a/Desktop_App/app.py b/Desktop_App/app.py index 3de7e10..4df2d8b 100644 --- a/Desktop_App/app.py +++ b/Desktop_App/app.py @@ -893,6 +893,10 @@ def _log(self, html:str): self._log_box.verticalScrollBar().setValue(self._log_box.verticalScrollBar().maximum()) def _voice_input(self): + """If you are working with voice input kindly read the documentation of npmai_agents and this code uses + npmai_agents version 1.0.2, npmai_agents change frequently although not syntax but it will be better to read + before making any change.""" + """ old VoiceTool.listen(): SpeechAITool.transcribe_realtime """ from npmai_agents.Tools_security_ai import SpeechAITool r = SpeechAITool.transcribe_realtime(duration=5) if r.success and r.data: diff --git a/Desktop_App/mcp_link.py b/Desktop_App/mcp_link.py index 8eca9d2..26c2d30 100644 --- a/Desktop_App/mcp_link.py +++ b/Desktop_App/mcp_link.py @@ -7,23 +7,18 @@ from typing import Callable, Optional CONFIG_PATH = Path.home() / ".npmai_agent" / "supabase_config.json" -BUNDLED_CONFIG_PATH = Path(__file__).resolve().parent / "app_config.json" CONFIG_URL = "https://raw.githubusercontent.com/npmaiecosystem/NPM-AutoCode-AI/refs/heads/main/Desktop_App/app_config.json" def load_config() -> dict: - """Load config from user override, then bundled file, then GitHub, then empty.""" - if CONFIG_PATH.exists(): + """Fetch latest config from GitHub (primary), fall back to bundled file.""" + bundled = Path(__file__).resolve().parent / "app_config.json" + if bundled.exists(): try: - return json.loads(CONFIG_PATH.read_text()) - except Exception as e: - print(f"Failed to read local config: {e}") - if BUNDLED_CONFIG_PATH.exists(): - try: - return json.loads(BUNDLED_CONFIG_PATH.read_text()) - except Exception as e: - print(f"Failed to read bundled config: {e}") + return json.loads(bundled.read_text()) + except Exception: + pass try: response = requests.get(CONFIG_URL, timeout=8) if response.status_code == 200: diff --git a/Docs/app.py b/Docs/app.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Docs/app.py @@ -0,0 +1 @@ + From 83765c324ab337b9b4f35cf979ebcc3b82455752 Mon Sep 17 00:00:00 2001 From: Mohammad Faiz Date: Thu, 23 Jul 2026 00:10:31 +0530 Subject: [PATCH 59/68] Fix load_config order: GitHub first, then bundled fallback Per maintainer review: try GitHubUserContent first so the open-source org can push config updates centrally. Bundled app_config.json is only a fallback when offline. --- Desktop_App/mcp_link.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Desktop_App/mcp_link.py b/Desktop_App/mcp_link.py index 26c2d30..b89541f 100644 --- a/Desktop_App/mcp_link.py +++ b/Desktop_App/mcp_link.py @@ -13,18 +13,18 @@ def load_config() -> dict: """Fetch latest config from GitHub (primary), fall back to bundled file.""" - bundled = Path(__file__).resolve().parent / "app_config.json" - if bundled.exists(): - try: - return json.loads(bundled.read_text()) - except Exception: - pass try: response = requests.get(CONFIG_URL, timeout=8) if response.status_code == 200: return response.json() except Exception as e: print(f"Failed to fetch remote config: {e}") + bundled = Path(__file__).resolve().parent / "app_config.json" + if bundled.exists(): + try: + return json.loads(bundled.read_text()) + except Exception: + pass return {} From d3d0576fd900c7eba49b4cbf6ff7c8c4010b4dec Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Thu, 23 Jul 2026 00:58:07 +0530 Subject: [PATCH 60/68] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c61ceb4..beb22ef 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ Thumbs.db # User config & credentials .npmai_agent/ Desktop_App/app_config.json +LICENSE +README.md # Build artifacts *.exe From c38d716b0189cd8038033c6d08f2456347f883d8 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Thu, 23 Jul 2026 01:11:42 +0530 Subject: [PATCH 61/68] Create build-release.yml --- .github/workflows/build-release.yml | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 .github/workflows/build-release.yml diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..f8becf5 --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,94 @@ +name: Build & Release Desktop App + +on: + workflow_dispatch: + inputs: + version: + description: "NPM-AutoCode-AI big Update." + required: true + default: "v3.0.0" + +permissions: + contents: write + +jobs: + build: + name: Build (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + artifact_name: NPM-AutoCode-AI-linux + data_sep: ":" + - os: windows-latest + artifact_name: NPM-AutoCode-AI-windows + data_sep: ";" + - os: macos-latest + artifact_name: NPM-AutoCode-AI-macos + data_sep: ":" + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout NPM-AutoCode-AI + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + # npmai-agents / npmai are already pinned in requirements.txt and published + # on PyPI, so they install straight from there โ€” no separate source build needed. + - name: Install app dependencies + working-directory: Desktop_App + run: | + pip install -r requirements.txt + pip install pyinstaller + + - name: Build with PyInstaller + shell: bash + working-directory: Desktop_App + run: | + pyinstaller --noconfirm --onefile --windowed \ + --name "NPM-AutoCode-AI" \ + --icon "../npmai.png" \ + --add-data "../npmai.png${{ matrix.data_sep }}." \ + --add-data "app_config.json${{ matrix.data_sep }}." \ + app.py + + - name: Zip build output (Linux/macOS) + if: matrix.os != 'windows-latest' + working-directory: Desktop_App/dist + run: zip -r "../../${{ matrix.artifact_name }}.zip" . + + - name: Zip build output (Windows) + if: matrix.os == 'windows-latest' + working-directory: Desktop_App/dist + run: Compress-Archive -Path * -DestinationPath "../../${{ matrix.artifact_name }}.zip" + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact_name }} + path: ${{ matrix.artifact_name }}.zip + + release: + name: Publish Release + needs: build + runs-on: ubuntu-latest + steps: + - name: Download all built zips + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Create release with all three zips attached + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.event.inputs.version }} + name: "NPM-AutoCode-AI ${{ github.event.inputs.version }}" + files: | + artifacts/NPM-AutoCode-AI-linux/NPM-AutoCode-AI-linux.zip + artifacts/NPM-AutoCode-AI-windows/NPM-AutoCode-AI-windows.zip + artifacts/NPM-AutoCode-AI-macos/NPM-AutoCode-AI-macos.zip From ffb76b5733d8a590c25ab393f9fabf9dd5f77087 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Thu, 23 Jul 2026 02:23:57 +0530 Subject: [PATCH 62/68] Update build-release.yml --- .github/workflows/build-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index f8becf5..8050752 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -51,6 +51,7 @@ jobs: working-directory: Desktop_App run: | pyinstaller --noconfirm --onefile --windowed \ + --collect-all charset_normalizer \ --name "NPM-AutoCode-AI" \ --icon "../npmai.png" \ --add-data "../npmai.png${{ matrix.data_sep }}." \ From 652fdb149c29285c1bcebf482de6e5f39cd8c28e Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Thu, 23 Jul 2026 02:57:18 +0530 Subject: [PATCH 63/68] Update build-release.yml --- .github/workflows/build-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 8050752..65dd24e 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -45,6 +45,8 @@ jobs: run: | pip install -r requirements.txt pip install pyinstaller + pip install Pillow + pip install --no-binary charset_normalizer charset_normalizer - name: Build with PyInstaller shell: bash From 6ee66fb59e15ff8a3190841cb028503801b25a91 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Thu, 23 Jul 2026 03:18:46 +0530 Subject: [PATCH 64/68] Update build-release.yml --- .github/workflows/build-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 65dd24e..90effc2 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -46,7 +46,7 @@ jobs: pip install -r requirements.txt pip install pyinstaller pip install Pillow - pip install --no-binary charset_normalizer charset_normalizer + pip install --force-reinstall --no-binary charset_normalizer charset_normalizer - name: Build with PyInstaller shell: bash From a4550176a91bbacf0c3916a1ab22b3a8c7754f4e Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Thu, 23 Jul 2026 04:23:45 +0530 Subject: [PATCH 65/68] Update README.md --- README.md | 296 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 248 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 136576b..3c82e8a 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,262 @@ -# NPM AutoCode AI +
-## ๐Ÿš€ Quick Start -Install the app: -[Download Latest Release](https://github.com/sonuramashishnpm/NPM-AutoCode-AI/releases/download/v2.2/NPM_AutoCode_AI.zip) + -1. Unzip the file. -2. Run `NPM_AutoCode_AI.exe` (Windows) or `python Desktop_App/app.py` (Python 3.12+). -3. Enter your task in the input box (e.g., "Plot a sine wave"). -4. Click **Generate & Execute**. -5. Watch logs and progress bar โ€“ AI generates, checks safety, executes, and fixes errors if needed. +Typing SVG -**Note:** Requires Ollama with models `codellama:7b-instruct` and `qwen2.5-coder:7b`. Run `ollama pull` for them. +
-## To understand repo project with AI in detail with full documentation visit here:- -[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/sonuramashishnpm/NPM-AutoCode-AI) +[![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux-6C63FF?style=for-the-badge&logo=windowsterminal&logoColor=white)](https://npmcodeai.netlify.app) +[![Built with PySide6](https://img.shields.io/badge/GUI-PySide6-41CD52?style=for-the-badge&logo=qt&logoColor=white)](https://pypi.org/project/PySide6/) +[![Engine](https://img.shields.io/badge/Engine-npmai--agents-2AFFA0?style=for-the-badge&logo=pypi&logoColor=black)](https://pypi.org/project/npmai-agents/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](LICENSE) +[![Ask DeepWiki](https://img.shields.io/badge/Ask-DeepWiki-blueviolet?style=for-the-badge)](https://deepwiki.com/npmaiecosystem/NPM-AutoCode-AI) -## Workflow:- +### โฌ‡๏ธ [**[DOWNLOAD FOR YOUR OS โ†’ npmcodeai.netlify.app](https://npmcodeai.netlify.app/)**](https://npmcodeai.netlify.app) โฌ‡๏ธ +*Windows `.exe` ยท macOS `.app` ยท Linux binary โ€” all built straight from this repo, every release.* -Example Screenshot +
+--- -## ๐Ÿ“– Project Overview -NPM AutoCode AI is a Python desktop app for automatic code generation and execution using AI. Describe tasks in plain English, and it uses NPMAI (custom LLM tools) to create, validate, and run Python scripts safely. +## ๐Ÿ“œ Table of Contents +- [[What is this, really?](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-what-is-this-really)](#-what-is-this-really) +- [[The Idea: MCP Without the Toll Booth](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-the-idea-mcp-without-the-toll-booth)](#-the-idea-mcp-without-the-toll-booth) +- [[Architecture](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-architecture)](#-architecture) +- [[The 6-Role Agent Pipeline](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-the-6-role-agent-pipeline)](#-the-6-role-agent-pipeline) +- [[1,371 Tools, 10 Domains](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-1371-tools-10-domains)](#-1371-tools-10-domains) +- [[Features](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-features)](#-features) +- [[Download & Install](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-download--install)](#-download--install) +- [[Configure LLMs (Optional Upgrade Path)](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-configure-llms-optional-upgrade-path)](#-configure-llms-optional-upgrade-path) +- [[Credential Vault](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-credential-vault)](#-credential-vault) +- [[Tech Stack](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-tech-stack)](#-tech-stack) +- [[Roadmap](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-roadmap)](#-roadmap) +- [[Contributing](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-contributing)](#-contributing) +- [[License](https://claude.ai/chat/95ed3814-731c-4c7c-9b4b-8ec42b76fc82#-license)](#-license) -Built for non-technical users โ€“ turns ideas into working code without manual editing. +--- + +## ๐Ÿง  What is this, really? + +**NPM-AutoCode-AI** is a desktop app: you type a task in plain English โ€” +*"scrape this page and email me a summary"*, *"resize every image in this folder"*, *"push this fix to GitHub"* โ€” +and it plans, writes, safety-audits, and runs the Python for you. Live, in front of you, on your own machine. + +Under the hood, it's powered by **[`[npmai-agents](https://pypi.org/project/npmai-agents/)`](https://pypi.org/project/npmai-agents/)** โ€” the actual open-source +reasoning engine (published on PyPI) that gives the app its **1,371 tools**, its safety auditing, and its +multi-role planning pipeline. NPM-AutoCode-AI is the face; `npmai-agents` is the brain. + +--- + +## ๐Ÿ’ก The Idea: MCP Without the Toll Booth + +
+ +
+ +Most **MCP (Model Context Protocol)** setups work like this: your LLM calls a *remote* server, that server calls +a *paid* API for every tool action, and you get billed per call, per token, per automation. That's fine for a SaaS โ€” +but it's a toll booth on every single thing your AI does for you. + +**NPM-AutoCode-AI inverts the model:** + +- The **MCP link** (`mcp_link.py`) is just a thin, authless *job relay* over **Supabase Realtime** โ€” no tunnels, no + open ports, no third party sitting in the middle of your automation billing you per step. +- The **execution itself happens locally**, on your own desktop, using your own CPU/RAM โ€” not a metered cloud sandbox. +- The **default reasoning models are free, local-first Ollama models** (`llama3.2`, `codellama:7b-instruct`, + `qwen2.5-coder:7b`, `granite3.3:2b`) โ€” the entire Planner โ†’ Tool Manager โ†’ Coder โ†’ Auditor โ†’ Verifier โ†’ Chatter + pipeline runs **out of the box with zero API key**. +- Only if *you* want a stronger brain (GPT, Claude, Gemini...) do you ever touch an API key โ€” and even then, it's + **your** key, stored **locally, encrypted**, never proxied through us. + +So instead of *"pay per automation,"* it's: **connect any MCP-aware LLM to your own machine, and let it drive tools +that already live on your computer, for the cost of electricity.** + +--- + +## ๐Ÿ—๏ธ Architecture + +```mermaid +sequenceDiagram + participant You as You (Agent tab / Chat) + participant App as NPM-AutoCode-AI (Desktop) + participant Brain as npmai-agents Engine + participant Supa as Supabase Realtime + participant LLM as Any MCP-aware LLM Client + + You->>App: "Resize every PNG in ~/photos to 800px" + App->>Brain: AgentBrain.plan(task) + Brain->>Brain: Planner -> Tool Manager -> Coder + Brain->>Brain: Auditor checks the generated code + Brain->>App: Safe code, ready to run + App->>App: Executor runs it locally (your CPU, your disk) + Brain->>Brain: Verifier confirms the step actually worked + App-->>You: Live logs + progress + result + + rect rgba(42,255,160,0.08) + Note over LLM,App: Optional - remote-control path via MCP Link + LLM->>Supa: INSERT job row (mcp_jobs) + Supa-->>App: Realtime push (no polling, no open ports) + App->>App: Executor runs the job locally + App->>Supa: INSERT result row (mcp_job_results) + Supa-->>LLM: Result delivered back + end +``` + +**Why this matters:** the bottom half of that diagram is the part most products don't give you. Any MCP-compatible +LLM โ€” running anywhere โ€” can hand a job to *your* desktop app through Supabase's realtime channel, and the job +executes with your local Python, your local files, your local tools. No always-on server bill for you to run, +no per-call metering for the automation itself. + +--- + +## ๐Ÿงฌ The 6-Role Agent Pipeline + +`AgentBrain` (the core of `npmai-agents`) doesn't use one model for everything โ€” it splits the job across +**six specialized roles**, each independently swappable from **Configure LLMs**: + +| Stage | Job | Default (free, local) | +|---|---|---| +| ๐Ÿงญ **Planner** | Breaks your task into atomic steps | `llama3.2:3b` | +| ๐Ÿงฐ **Tool Manager** | Picks which of the 1,371 tools apply | `llama3.2` | +| ๐Ÿ‘จโ€๐Ÿ’ป **Coder** | Writes the actual Python for each step | `codellama:7b-instruct` | +| ๐Ÿ›ก **Auditor** | Reviews the code for risk *before* it ever runs | `qwen2.5-coder:7b` | +| โœ… **Verifier** | Confirms a step genuinely completed | `llama3.2:3b` | +| ๐Ÿ’ฌ **Chatter** | Handles plain conversation, non-task replies | `granite3.3:2b` | + +Every one of these runs against **free, local Ollama models by default**. Want GPT-4o doing the planning and +Claude doing the coding instead? One click each in **Configure LLMs** โ€” nothing else changes. + +--- + +## ๐Ÿงฐ 1,371 Tools, 10 Domains + +`npmai-agents` ships **1,371 tools across 121 classes**, organized into 10 domain files so the Tool Manager can +route intelligently instead of guessing: + +
+ +| Domain | Domain | Domain | +|---|---|---| +| ๐Ÿ–ฅ Developer & CLI | ๐Ÿ’ผ Business | โ˜๏ธ Cloud & DevOps | +| ๐Ÿ“ก Communication (extended) | ๐ŸŽจ Creative | ๐Ÿ“Š Data & Research | +| ๐ŸŽฌ Media | ๐Ÿ—‚ Productivity | ๐Ÿ” Security & AI | ๐Ÿ”ง System & Hardware | + +
+ +That's why the credentials system exists at all: most of these tools work with **zero setup** (local file ops, +system tasks, data processing) โ€” only the ones that talk to a *third-party service* (GitHub, Twilio, Stripe, +AWS...) ever need a key, and even those keys stay on your machine. + +--- ## โœจ Features -- **Natural Language to Code**: AI generates Python scripts from your description. -- **Safety Check**: Scans code for risks (e.g., file deletions, remote access) before running. -- **Auto-Debug**: If errors happen, AI fixes and retries using error logs. -- **Live Logs & Progress**: See real-time updates in GUI. -- **Dependency Handling**: Installs libraries via `subprocess` in code. -- **Isolated Execution**: Runs in safe namespace to protect your system. -- **Memory Chains**: Remembers task history for better fixes. - -## ๐Ÿ”„ How It Works -1. Enter task โ†’ AI (`codellama:7b-instruct`) generates code via NPMAI Ollama. -2. Safety AI (`qwen2.5-coder:7b`) reviews: If risky, stops with warning. -3. Execute code in thread โ†’ If error, feed back to AI for fix โ†’ Retry loop. -4. Success: Logs "Task Completed Successfully". - -All in background (QThread) so UI stays responsive. Uses LangChain for prompts. - -## ๐Ÿ› ๏ธ Tech Details -- **Language**: Python 3.12+ -- **GUI**: PySide6 (QWidget, QThread, etc.) -- **AI**: npmai.Ollama + Memory; LangChain Core for prompts/parsers. -- **Execution**: `exec()` in custom globals dict with error capture. - -## ๐Ÿ‘จโ€๐Ÿ’ป Developer -**Sonu Kumar Ramashish** (a.k.a. Bihar Viral Boy) -- Age: 14 | Student | TEDx Speaker | AI & Software Developer | DevOps Enthusiast -- Reach: 410K+ Facebook followers -- Location: Kota, Rajasthan - -Part of NPMAI ecosystem for AI automation tools. + +- ๐Ÿ—ฃ **Natural language โ†’ working Python** โ€” describe the task, watch it get built. +- ๐Ÿ›ก **Safety-audited before execution** โ€” the Auditor role flags risky code (file deletion, remote access) before + anything runs. +- ๐Ÿ” **Auto-debug loop** โ€” errors get fed back to the Coder role and retried, not just dumped on you. +- ๐Ÿ“ˆ **Live logs + progress bar** โ€” full visibility while the agent works, nothing happens silently. +- ๐Ÿ”‘ **Bring-your-own-key, never forced** โ€” every LLM provider and every tool credential is opt-in and locally + encrypted (`CredStore`). +- ๐ŸŒ **Remote-trigger via MCP Link** โ€” connect any MCP-aware LLM client to your desktop through Supabase Realtime. +- ๐Ÿ–ฅ **True cross-platform** โ€” native builds for Windows, macOS, and Linux from the same codebase. + +--- + +## ๐Ÿ“ฆ Download & Install + +
+ +### ๐Ÿ‘‰ **[[npmcodeai.netlify.app](https://npmcodeai.netlify.app/)](https://npmcodeai.netlify.app)** โ€” pick your OS, download, run. + +| ๐ŸชŸ Windows | ๐ŸŽ macOS | ๐Ÿง Linux | +|:---:|:---:|:---:| +| `.exe`, no install needed | `.app` bundle | portable binary | + +*Every build on that page is produced straight from this repo's GitHub Actions pipeline โ€” same source, three +native binaries, no cross-compiling shortcuts.* + +
+ +**Building from source instead?** + +```bash +git clone https://github.com/npmaiecosystem/NPM-AutoCode-AI.git +cd NPM-AutoCode-AI/Desktop_App +pip install -r requirements.txt +python app.py +``` + +--- + +## โš™๏ธ Configure LLMs (Optional Upgrade Path) + +Everything works day-one with the free local defaults above. If you want to swap in a hosted model for any of +the 6 roles โ€” OpenAI, Anthropic, Gemini, Groq, Mistral, Cohere, Azure OpenAI, AWS Bedrock, HuggingFace, or a +local llama.cpp server โ€” click **โš™ Configure LLMs** on the Agent tab: + +1. **โ‘  Provider Credentials** โ€” add only the fields that provider actually needs (most: just an API key). +2. **โ‘ก Assign Provider + Model per Stage** โ€” pick which provider handles Planner / Tool Manager / Coder / + Auditor / Verifier / Chatter, individually. + +Nothing here is required. It's a ceiling to raise if you want it, not a floor you have to clear to get started. + +--- + +## ๐Ÿ” Credential Vault + +Any tool that needs a third-party credential (GitHub, Twilio, Stripe, AWS, Notion, and 25+ more) stores it through +a **locally encrypted `CredStore`** โ€” never synced to us, never proxied through a middleman. Settings even has a +generic **"+ Add Credential Group"** button so you can wire up a tool that isn't hardcoded yet: name the group, +add whatever key/value pairs that tool's docs ask for, done. + +--- + +## ๐Ÿ›  Tech Stack + +
+ +![Python](https://img.shields.io/badge/Python-3.11+-3776AB?style=flat-square&logo=python&logoColor=white) +![PySide6](https://img.shields.io/badge/PySide6-Qt%20for%20Python-41CD52?style=flat-square&logo=qt&logoColor=white) +![LangChain](https://img.shields.io/badge/LangChain-Prompt%20Orchestration-1C3C3C?style=flat-square) +![Supabase](https://img.shields.io/badge/Supabase-Realtime%20Job%20Relay-3ECF8E?style=flat-square&logo=supabase&logoColor=white) +![PyInstaller](https://img.shields.io/badge/PyInstaller-3--OS%20Builds-FFD43B?style=flat-square&logo=python&logoColor=black) +![GitHub Actions](https://img.shields.io/badge/GitHub%20Actions-CI%2FCD-2088FF?style=flat-square&logo=githubactions&logoColor=white) + +
+ +- **GUI:** PySide6 (custom animated widgets โ€” glow cards, pulse buttons, cosmic background) +- **Engine:** `npmai-agents` (PyPI) โ€” `AgentBrain`, `Executor`, `CredStore`, `Workspace` +- **Default inference:** `npmai` (Ollama-backed), fully local/free +- **Remote link:** `mcp_link.py` + Supabase Realtime (Postgres changes -> live push, no polling) +- **Packaging:** PyInstaller, one native build per OS via a matrix GitHub Actions workflow, auto-published to + GitHub Releases and mirrored on [[npmcodeai.netlify.app](https://npmcodeai.netlify.app/)](https://npmcodeai.netlify.app) + +--- + +## ๐Ÿ—บ Roadmap + +- [ ] Expand the MCP Link job protocol beyond single-shot code execution (multi-step remote sessions) +- [ ] More hardcoded credential templates in Settings (currently GitHub/SMTP/Notion/Stripe/AWS + generic groups) +- [ ] In-app model download manager for the local Ollama defaults +- [ ] Signed installers for Windows/macOS (currently unsigned binaries) + +--- ## ๐Ÿค Contributing -Fork, add features (e.g., more models), and PR. License: MIT. -Star if useful! ๐Ÿš€ +Issues and PRs welcome โ€” especially new tools for `npmai-agents`, new hardcoded credential templates, or +platform-specific packaging fixes. Fork it, build it, send a PR. + +## ๐Ÿ“„ License + +MIT โ€” see [[LICENSE](https://claude.ai/chat/LICENSE)](LICENSE). + +
+ + + +**Built by the NPMAI ECOSYSTEM** โ€” automation that runs on *your* machine, not someone else's meter. + +
From 8e7180b3e6c770a481e044b240b1566451d534ba Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Thu, 23 Jul 2026 12:07:17 +0530 Subject: [PATCH 66/68] Update app.py --- Desktop_App/app.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Desktop_App/app.py b/Desktop_App/app.py index 4df2d8b..c7c4f90 100644 --- a/Desktop_App/app.py +++ b/Desktop_App/app.py @@ -9,6 +9,18 @@ from pathlib import Path sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +def resource_path(filename: str) -> str: + """Resolve a bundled data file (icon, config) whether running from source + or from a PyInstaller-frozen build. PyInstaller unpacks --add-data files + next to sys.executable (onedir) or into sys._MEIPASS (onefile); neither + is the current working directory, so a bare relative path silently fails.""" + base = getattr(sys, "_MEIPASS", None) or os.path.dirname(os.path.abspath(__file__)) + return os.path.join(base, filename) + + +APP_ICON_PATH = resource_path("npmai.png") + from npmai_agents import AgentBrain, Workspace, CredStore from PySide6.QtWidgets import ( @@ -495,7 +507,7 @@ def __init__(self, link_mgr, parent=None): super().__init__(parent) self._mgr = link_mgr self.setWindowTitle("Log in โ€” MCP Link") - self.setWindowIcon(QIcon("npmai.png")) + self.setWindowIcon(QIcon(APP_ICON_PATH)) self.setFixedSize(360, 260) self.setStyleSheet(f"QDialog{{background:{P['void']};}}") lay = QVBoxLayout(self); lay.setContentsMargins(28,24,28,24); lay.setSpacing(12) @@ -656,7 +668,7 @@ class LLMConfigDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Configure LLMs") - self.setWindowIcon(QIcon("npmai.png")) + self.setWindowIcon(QIcon(APP_ICON_PATH)) self.resize(640, 720) self.setStyleSheet(f"QDialog{{background:{P['void']};}}") @@ -1058,14 +1070,10 @@ def __init__(self, parent=None, group_name="", existing_data=None, lock_name=Fal self.setStyleSheet(f"QDialog{{background:{P['void']};}}") self._row_widgets = [] - # === FIXED ICON LOADING === - from pathlib import Path - from PySide6.QtGui import QIcon - icon_path = Path(__file__).parent / "npmai.png" - if icon_path.exists(): - self.setWindowIcon(QIcon(str(icon_path))) + if os.path.exists(APP_ICON_PATH): + self.setWindowIcon(QIcon(APP_ICON_PATH)) else: - print("Warning: npmai.png not found for CredKeyValueDialog") + print(f"Warning: npmai.png not found at {APP_ICON_PATH}") lay = QVBoxLayout(self); lay.setContentsMargins(26,22,26,22); lay.setSpacing(12) @@ -1450,7 +1458,7 @@ class AppWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("NPM-AutoCode-AI โ€” NPMAI Ecosystem v3.0") - self.setWindowIcon(QIcon("npmai.png")) + self.setWindowIcon(QIcon(APP_ICON_PATH)) self.resize(1220,780); self.setMinimumSize(920,640) self._build() From cbc1d98c92646061237b8c39d511b6d2165c12ca Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Thu, 23 Jul 2026 12:07:33 +0530 Subject: [PATCH 67/68] Update build-release.yml --- .github/workflows/build-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 90effc2..2aa7e99 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -52,7 +52,7 @@ jobs: shell: bash working-directory: Desktop_App run: | - pyinstaller --noconfirm --onefile --windowed \ + pyinstaller --noconfirm --onedir --windowed \ --collect-all charset_normalizer \ --name "NPM-AutoCode-AI" \ --icon "../npmai.png" \ From a7353cfa7b343e6bb24919c0f0038ed31e569307 Mon Sep 17 00:00:00 2001 From: Sonu Kumar Date: Sun, 26 Jul 2026 21:08:46 +0530 Subject: [PATCH 68/68] Update NPM_AutoCode_AI.html --- Docs/templates/NPM_AutoCode_AI.html | 73 +++++++++++++++-------------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/Docs/templates/NPM_AutoCode_AI.html b/Docs/templates/NPM_AutoCode_AI.html index 7e73fc7..0633079 100644 --- a/Docs/templates/NPM_AutoCode_AI.html +++ b/Docs/templates/NPM_AutoCode_AI.html @@ -1,12 +1,13 @@ +\ - - - - + + + + NPM-AutoCode-AI โ€” v3 ยท NPMAI Ecosystem - - - + + + - -
-
+ +
+
-
v3NPMAI Ecosystem ยท Free & Open Source
+
v3NPMAI Ecosystem ยท Free & Open Source

NPM-AutoCode-AI
turns plain English
into things done.

Describe a task. It plans it, writes the code, checks the code, runs it on your machine, and confirms it actually worked โ€” a five-role agent loop, not a chatbot pretending to be one.

-
0
Downloads
-
100
Tools
+
57,000+
Downloads
+
1371
Tools
Free
Forever
45+
LLMs
@@ -528,7 +529,7 @@

Pick any task. Watch it run.

Execution Logs
- +
@@ -541,7 +542,7 @@

Pick any task. Watch it run.

- + @@ -567,9 +568,9 @@

Connect it to Claude or Grok.
No API key required.<
๐Ÿ–ฅ
Your Desktop
executes locally
-
๐Ÿ›ฐ
MCP Server
plan ยท route ยท verify
+
๐Ÿ›ฐ
MCP Server(Hosted by NPMAI ECOSYSTEM)
Connect your web browser LLMs to your desktop
-
๐Ÿค–
Claude / Grok
thinks, writes, reviews
+
๐Ÿค–
Claude / Grok and others
thinks, writes, reviews
Code + results flow back โ†’โ† Prompts + tasks flow out @@ -593,6 +594,8 @@

Connect it to Claude or Grok.
No API key required.<
Capabilities

Built to compete.
Free to use.

Everything a coding agent should do โ€” on your own machine, with a GUI, for everyone.

+

A System which can automate everything without needed of API_Key.

+

A System by which you can automate your Local Machine from AI Websites like claude.com, grok.com

@@ -634,7 +637,7 @@

Built to compete.
Free to use.

Tool Registry
-

100 Tools. Zero Setup.

+

1371 Tools. Zero Setup.

Every tool below is available automatically. Just describe the task โ€” the agent picks what it needs.

@@ -718,7 +721,7 @@

Meet the Founder

- Sonu Kumar + Sonu Kumar
Sonu Kumar ๐Ÿ‡ฎ๐Ÿ‡ณ
@@ -749,7 +752,7 @@

Meet the Founder

๐Ÿง’
Age 9
Started Teaching Nursery Kids
While in 4th grade, taught nursery children โ€” planting the seed for education equity.
-
๐Ÿ”ฅ
Age 11 โ€” "Bihar Viral Boy"
Challenged Bihar's Chief Minister
Raised education & liquor ban issues. Went viral nationally. Received support from Cabinet Ministers, Bollywood actors & ALLEN Institute founder.
+
๐Ÿ”ฅ
Age 11 โ€” "Bihar Viral Boy"
Challenged Bihar's Chief Minister
Raised education & liquor ban issues. Went viral nationally. Received support from Cabinet Ministers, Bollywood actors & ALLEN Institute founder.
๐ŸŽค
Age 13
TEDx Speaker
One of India's youngest TEDx speakers โ€” rural education and using tech to bridge India's urban-rural divide.
๐Ÿ’ป
Age 13-15
Founded NPMAI ECOSYSTEM
Self-taught, zero CS background. Built the entire ecosystem on free cloud infrastructure.
๐Ÿ“„
2026
10 Research Papers Published
Spanning AI/agent architecture, chemistry, mathematics, and constitutional/political science.
@@ -772,19 +775,21 @@

Download free.
Run instantly.

3

Run .exe

4

Start automating

- โ†“ Download NPM-AutoCode-AI v3 -
Windows ยท Free Forever ยท Open Source ยท NPMAI Ecosystem
+ โ†“ For Windows:- Download NPM-AutoCode-AI v3

+ โ†“ For Linux:- Download NPM-AutoCode-AI v3

+ โ†“ For Macos:- Download NPM-AutoCode-AI v3 +
Windows ยท Linux ยท Macos ยท Free Forever ยท Open Source ยท NPMAI Ecosystem
57,000+ downloads โ€” determined as per the LLM requests received on our npmai_LLM server; that's how we identified this figure.
- +
@@ -1126,5 +1131,5 @@

Download free.
Run instantly.

} document.getElementById('demo-input').addEventListener('keypress',e=>{ if(e.key==='Enter') runDemo(); }); - - + +