Feat/improve - #2
Conversation
…orm bot support with core services
…ized document generation and add freeze utility
ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughAdds end-to-end assignment workflow: persistent per-user sessions, interactive CLI setup, multi-provider LLM integration, PDF parsing, multiple output format generators, classroom/puppeteer upload stubs, a WebSocket dashboard with log broadcasting, and multi-platform bot startup (Telegram + placeholders for WhatsApp/Slack). Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Bot as "Messaging Bot"
participant Store as "State Store\n(.config/store.json)"
participant LLM as "LLM Provider\n(OpenAI/Gemini/OpenRouter)"
participant Parser as "PDF Parser"
participant Formatter as "Formatters\n(PDF/Word/Excel/Image)"
participant Uploader as "Uploader\n(Classroom/Puppeteer)"
participant Logger as "Logger/Broadcaster"
participant Dashboard as "Web Dashboard"
User->>Bot: Send /start or assignment (text/PDF)
Bot->>Store: loadStore(sessionId)
alt PDF received
Bot->>Parser: parsePdfBuffer(fileBuffer)
Parser-->>Bot: extractedText
Bot->>LLM: generateAssignmentSolution(prompt, extractedText)
else Text prompt
Bot->>LLM: generateAssignmentSolution(prompt, userContext)
end
LLM-->>Bot: solutionText
Bot->>Store: save session.solution
Bot->>User: ask for output format
User->>Bot: choose format
Bot->>Formatter: generate* (solutionText)
Formatter-->>Bot: fileBuffer
Bot->>User: send file
User->>Bot: choose platform
Bot->>Uploader: uploadToClassroom / uploadViaPuppeteer
Uploader-->>Bot: uploadResult
Bot->>Logger: logger.info / success
Logger->>Dashboard: broadcast system_log
Dashboard-->>User: realtime logs
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
Review Summary by QodoImplement user onboarding, multi-platform bots, LLM integration, and document processing with dashboard
WalkthroughsDescription• Implement comprehensive user onboarding flow with persistent user/session storage • Add multi-platform bot support (Telegram, WhatsApp, Slack) with core LLM integration • Create interactive CLI setup wizard for configuring LLM providers and messaging channels • Implement document processing pipeline with PDF parsing and multi-format output generation • Add web-based dashboard with real-time WebSocket logging and system monitoring • Support multiple LLM providers (OpenAI, Gemini, OpenRouter, Ollama) with flexible model selection Diagramflowchart LR
User["User/Messaging Platform"]
Bot["Multi-Platform Bot<br/>Telegram/WhatsApp/Slack"]
Onboard["User Onboarding<br/>Flow"]
Store["Persistent Store<br/>Users & Sessions"]
LLM["LLM Provider<br/>OpenAI/Gemini/Ollama"]
Parser["PDF Parser"]
Format["Format Generators<br/>PDF/Word/Excel/Image"]
Gateway["Gateway Daemon<br/>WebSocket Server"]
Dashboard["Web Dashboard<br/>Real-time Logs"]
User -->|Messages| Bot
Bot -->|First Time| Onboard
Onboard -->|Save| Store
Bot -->|Process| Parser
Parser -->|Extract Text| LLM
LLM -->|Generate Solution| Format
Bot -->|Broadcast Logs| Gateway
Gateway -->|Stream Events| Dashboard
Store -->|Load/Save| Bot
File Changes1. src/bot/index.ts
|
Code Review by Qodo
1. Committed bot token
|
| @@ -0,0 +1 @@ | |||
| TELEGRAM_BOT_TOKEN=8606625998:AAGuNWcFKDyXBSBITEie_NCSqZ2j2GZ6dC4 | |||
There was a problem hiding this comment.
1. Committed bot token 🐞 Bug ⛨ Security
A real TELEGRAM_BOT_TOKEN is committed in .env, allowing anyone with repository access (or leaked history) to fully control the Telegram bot. This secret must be rotated and removed from git history to prevent account takeover.
Agent Prompt
## Issue description
A real Telegram bot token is committed in `.env`, which is a credential that enables full bot control. Once committed, it remains accessible through git history even if later removed.
## Issue Context
`.gitignore` already lists `.env`, so this appears accidental but still security-critical.
## Fix
1. Rotate/revoke the leaked Telegram bot token immediately (via @BotFather) and issue a new one.
2. Remove `.env` from the repository (and ensure it is not tracked): `git rm --cached .env`.
3. Purge the secret from git history (e.g., `git filter-repo` / BFG) and force-push if this repo is shared.
4. Add a safe template file (e.g., `.env.example`) with placeholder values.
## Fix Focus Areas
- .env[1-1]
- .gitignore[65-77]
- README.md[57-71]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| { | ||
| "users": { | ||
| "1711055629": { | ||
| "name": "Ayush", | ||
| "roll": "6372373", | ||
| "uni": "IIITM Gwalior" | ||
| } | ||
| }, | ||
| "sessions": { | ||
| "1711055629": { | ||
| "step": "AWAITING_PLATFORM", | ||
| "context": "Make a assignment for bubble sort and give me pdf withe rhr code and code op screenshot", | ||
| "solution": "Assignment: Implementation and Performance Analysis of Bubble Sort Algorithm\nStudent Name: Ayush\nRoll Number: 6372373\nUniversity: IIITM Gwalior\nDate: 4/1/2026\n\n1. Objective\nThe objective of this assignment is to implement the Bubble Sort algorithm, document the step-by-step logic, provide the source code, and analyze its time complexity in the context of sorting an array of integers.\n\n2. Problem Statement\nWrite a program to sort an array of $n$ elements using the Bubble Sort technique. The program must:\na) Accept input size $n$ and an array of $n$ integers from the user.\nb) Perform the sort by repeatedly stepping through the list, comparing adjacent elements, and swapping them if they are in the wrong order.\nc) Display the sorted array.\n\n3. Algorithm Description\nBubble Sort is a comparison-based algorithm where each pair of adjacent elements is compared, and elements are swapped if they are not in order. This process repeats until the array is sorted.\n- Time Complexity: O(n²) in the worst and average cases.\n- Space Complexity: O(1) as it is an in-place sorting algorithm.\n\n4. Source Code (Python)\n```python\ndef bubble_sort(arr):\n n = len(arr)\n for i in range(n):\n for j in range(0, n - i - 1):\n if arr[j] > arr[j + 1]:\n arr[j], arr[j + 1] = arr[j + 1], arr[j]\n return arr\n\n# Driver code\nif __name__ == \"__main__\":\n data = [64, 34, 25, 12, 22, 11, 90]\n print(\"Original array:\", data)\n sorted_data = bubble_sort(data)\n print(\"Sorted array:\", sorted_data)\n```\n\n5. Output Screenshot Placeholder\n[Insert Screenshot of Program Execution Here]\n\n6. Conclusion\nThe implementation confirms that Bubble Sort is effective for smaller datasets but inefficient for large-scale data due to its quadratic time complexity. Proper documentation of the swapping mechanism demonstrates the fundamental working principle of the algorithm.", | ||
| "format": "Pdf with code op" | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
2. Committed user pii store 🐞 Bug ⛨ Security
The PR commits .config/store.json containing a user's name, roll number, university, and full session/solution content. This leaks personal data and must be removed from the repo (and git history) and treated as runtime-only state.
Agent Prompt
## Issue description
`.config/store.json` is committed with real user PII and session content. This file is runtime state and should never be shipped or tracked.
## Issue Context
The application persists onboarding/session state to `.config/store.json`, so committing it risks leaking real user data and future sessions.
## Fix
1. Remove `.config/store.json` from git tracking (`git rm --cached .config/store.json`).
2. Purge it from git history if already pushed.
3. Ensure `.config/` remains ignored (it already is) and consider adding an explicit `.config/store.json.example` if you need a schema/sample.
4. (Optional hardening) Store sensitive data with least privilege: file permissions (0600), encryption at rest, and/or move to a proper DB.
## Fix Focus Areas
- .config/store.json[1-17]
- src/core/store.ts[4-26]
- src/bot/index.ts[17-28]
- .gitignore[65-77]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| function appendLog(level, message, meta) { | ||
| const time = new Date().toLocaleTimeString(); | ||
| const el = document.createElement('div'); | ||
| el.className = 'log-entry'; | ||
|
|
||
| let html = `<span class="log-time">${time}</span>`; | ||
| html += `<span class="log-level ${level}">[${level}]</span>`; | ||
| html += `<span class="log-message">${message}</span>`; | ||
|
|
||
| if (meta && Object.keys(meta).length > 0) { | ||
| html += `<span class="log-meta">${JSON.stringify(meta, null, 2)}</span>`; | ||
| } | ||
|
|
||
| el.innerHTML = html; | ||
| terminal.appendChild(el); |
There was a problem hiding this comment.
3. Log viewer xss injection 🐞 Bug ⛨ Security
The dashboard renders log message and meta using innerHTML without escaping, while user-controlled Telegram text is logged and broadcast to the dashboard. An attacker can send a payload like </span><img src=x onerror=alert(1)> to execute script in the operator’s browser.
Agent Prompt
## Issue description
The dashboard uses `innerHTML` to render log fields coming from untrusted sources (Telegram user text inside `meta`). This allows HTML injection and XSS in the dashboard.
## Issue Context
User-controlled text is logged (`{ text }` meta), broadcast over the gateway websocket, and rendered by the dashboard.
## Fix
- Replace `innerHTML` rendering with DOM APIs and `textContent`.
- Create spans with `document.createElement('span')` and set `.textContent` for time/level/message.
- For `meta`, render into a `<pre>` (or `<span>`) and set `.textContent = JSON.stringify(meta, null, 2)`.
- (Defense-in-depth) Add a restrictive CSP header when serving the dashboard and consider sanitizing log payloads server-side.
## Fix Focus Areas
- public/index.html[199-213]
- src/bot/index.ts[34-42]
- src/core/logger.ts[16-32]
- src/daemon/gateway/server.ts[36-38]
- src/daemon/gateway/server.ts[81-88]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| this.server.listen(port, () => { | ||
| console.log(`Gateway & Dashboard running on http://localhost:${port}`); | ||
| }); | ||
| } | ||
|
|
||
| private initialize() { | ||
| this.wss.on('connection', (ws: WebSocket) => { | ||
| console.log('Client connected to Gateway'); | ||
| logger.info('Dashboard/Client connected to Gateway WebSocket'); | ||
| this.clients.add(ws); |
There was a problem hiding this comment.
4. Gateway exposed without auth 🐞 Bug ⛨ Security
GatewayServer listens without binding to loopback and accepts any WebSocket connection without authentication, despite the setup UI claiming loopback-only binding. If reachable on the network, anyone can connect and receive broadcast logs (including user content) and interact with the gateway.
Agent Prompt
## Issue description
The gateway/dashboard server is network-exposed by default and has no authentication for websocket clients, which can leak logs and enable unauthorized access.
## Issue Context
Setup text claims loopback binding, but implementation does not specify a host.
## Fix
1. Bind explicitly to loopback by default:
- `this.server.listen(port, '127.0.0.1', ...)`
- Optionally support a config/env override for advanced users.
2. Add websocket access control:
- Require an auth token (query param or header) and reject missing/invalid tokens.
- Validate `Origin` / `Host` as appropriate.
3. Consider disabling log broadcasting unless explicitly enabled.
## Fix Focus Areas
- src/daemon/gateway/server.ts[14-42]
- src/daemon/gateway/server.ts[44-63]
- src/cli/setup.ts[48-52]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| private handleRequest(ws: WebSocket, request: RequestMessage) { | ||
| console.log(`Received request command: ${request.command}`); | ||
|
|
||
| switch (request.command) { | ||
| case 'ping': | ||
| ws.send(JSON.stringify(createResponse(request.id, true, { message: 'pong' }))); | ||
| break; | ||
| case 'init_config': | ||
| console.log('Received config init payload:', request.payload); | ||
| // Implementation for saving the config logic would go here | ||
| ws.send(JSON.stringify(createResponse(request.id, true, { status: 'saved' }))); | ||
|
|
||
| // Broadcast an event to all connected clients that config changed | ||
| this.broadcastEvent('config_updated', { version: request.payload?.version || '1.0' }); | ||
| break; | ||
| default: | ||
| ws.send(JSON.stringify(createResponse(request.id, false, undefined, 'Unknown command'))); | ||
| } |
There was a problem hiding this comment.
5. Cli init no longer works 🐞 Bug ≡ Correctness
The CLI still sends an init_config request to the daemon, but the gateway no longer handles init_config and will respond with Unknown command. This breaks scholar init and prevents config initialization via the gateway.
Agent Prompt
## Issue description
`scholar init` is broken because it sends `init_config` but the gateway does not implement this command anymore.
## Issue Context
The CLI uses the gateway websocket to initialize configuration.
## Fix
Choose one:
- **Option A (restore behavior):** Re-add an `init_config` handler in `GatewayServer.handleRequest` that writes `.config/scholar.json` (or delegates to the setup flow) and returns success.
- **Option B (remove dependency):** Update/remove the CLI `init` command so it writes `.config/scholar.json` locally (similar to `runInteractiveSetup`) rather than requiring the daemon.
## Fix Focus Areas
- src/cli/index.ts[15-35]
- src/daemon/gateway/server.ts[71-78]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Pull request overview
This PR expands ScholarSync from a basic Telegram bot into a multi-component system with interactive onboarding/config, a local dashboard + WebSocket gateway, a richer Telegram workflow (onboarding → solve → format → submit), and new “formatters”/LLM integrations.
Changes:
- Add interactive CLI setup that persists configuration to
.config/scholar.jsonand update startup logic to bootstrap from both.envand JSON config. - Introduce a Gateway daemon that serves
public/index.htmland broadcasts application logs to a WebSocket dashboard. - Implement Telegram bot onboarding + document parsing + LLM solution generation + output formatting (PDF/DOCX/XLSX/image) + submission flow.
Reviewed changes
Copilot reviewed 16 out of 19 changed files in this pull request and generated 24 comments.
Show a summary per file
| File | Description |
|---|---|
| src/index.ts | Loads config from .env + .config/scholar.json, runs interactive setup when missing config, starts gateway + bots. |
| src/daemon/gateway/server.ts | Adds HTTP server for dashboard + attaches WebSocket server + log broadcasting. |
| src/core/store.ts | Adds a JSON file-backed user/session store under .config/store.json. |
| src/core/schema.ts | Extends LLM provider union to include openrouter. |
| src/core/parser.ts | Adds PDF buffer parsing via pdf-parse. |
| src/core/logger.ts | Adds a logger that also broadcasts to dashboard via a registered callback. |
| src/core/llm.ts | Adds LLM request logic for Gemini/OpenAI/OpenRouter (and a mock fallback). |
| src/core/formatters.ts | Adds PDF/DOCX generation via Docker+pandoc, XLSX generation via xlsx, and code screenshot generation via freeze. |
| src/core/classroom.ts | Adds placeholders for Google Classroom and Puppeteer-based uploads. |
| src/cli/setup.ts | Implements interactive onboarding to collect/store messaging + LLM credentials. |
| src/bot/index.ts | Major Telegram bot expansion: onboarding, PDF solving, formatting, and submission steps. |
| public/index.html | Adds a dashboard UI that connects to the gateway via WebSocket and renders logs. |
| README.md | Expands project documentation and feature list. |
| package.json | Adds start/daemon scripts and new dependencies (@clack/prompts, picocolors, xlsx). |
| package-lock.json | Locks newly added dependencies. |
| .gitignore | Updates ignored files/dirs (notably .config and .env). |
| .env | Adds an environment file (currently contains a real bot token). |
| .config/store.json | Adds a committed runtime store snapshot (contains PII/session data). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1 @@ | |||
| TELEGRAM_BOT_TOKEN=8606625998:AAGuNWcFKDyXBSBITEie_NCSqZ2j2GZ6dC4 | |||
There was a problem hiding this comment.
A real Telegram bot token is committed in this tracked .env file. This is a secret leak and should be removed from git history (rotate/revoke the token, delete the file from the repo, and replace with a non-secret .env.example/setup instructions).
| TELEGRAM_BOT_TOKEN=8606625998:AAGuNWcFKDyXBSBITEie_NCSqZ2j2GZ6dC4 | |
| TELEGRAM_BOT_TOKEN=YOUR_TELEGRAM_BOT_TOKEN_HERE |
| } catch (err) { | ||
| console.error(err); |
There was a problem hiding this comment.
This catch block logs with console.error, which bypasses the new global logger/dashboard broadcaster. Using logger.error here would keep error reporting consistent and visible in the dashboard.
| } catch (err) { | |
| console.error(err); | |
| } catch (err: any) { | |
| logger.error("Document processing failed", { error: err?.message ?? String(err) }); |
| this.server.listen(port, () => { | ||
| console.log(`Gateway & Dashboard running on http://localhost:${port}`); | ||
| }); |
There was a problem hiding this comment.
The HTTP server is started without an explicit host, which binds to all interfaces by default. Since this dashboard streams logs (including user-provided text) over WebSockets, it’s safer to bind to 127.0.0.1 by default (or make the bind address configurable via env) to avoid exposing it on the network unintentionally.
|
|
||
| // Run Bot and Daemon | ||
| console.log("Starting Gateway Daemon..."); | ||
| const gateway = new GatewayServer(8080); |
There was a problem hiding this comment.
gateway is assigned but never used. If the side-effect is just to start listening, instantiate without assigning (or store it somewhere and use it for shutdown handling).
| const gateway = new GatewayServer(8080); | |
| new GatewayServer(8080); |
| } | ||
|
|
||
| p.note( | ||
| `No auth configured for provider "anthropic". The agent may fail until credentials are added.`, |
There was a problem hiding this comment.
This warning references provider "anthropic", but the interactive provider list doesn’t include Anthropics/Claude. It reads like a copy/paste artifact and will confuse users; either add the provider option or remove/update the note to reflect supported providers.
| `No auth configured for provider "anthropic". The agent may fail until credentials are added.`, | |
| `No auth configured for your selected LLM provider yet. The agent may fail until API credentials are added.`, |
| import * as xlsx from 'xlsx'; | ||
| import puppeteer from 'puppeteer'; | ||
| import { logger } from './logger.js'; | ||
|
|
||
| const execPromise = util.promisify(exec); | ||
| const TEMP_DIR = path.resolve(process.cwd(), '.temp_docs'); |
There was a problem hiding this comment.
This code creates .temp_docs under the project root for generated files. Add .temp_docs/ to .gitignore to avoid accidentally committing generated documents.
| import * as xlsx from 'xlsx'; | |
| import puppeteer from 'puppeteer'; | |
| import { logger } from './logger.js'; | |
| const execPromise = util.promisify(exec); | |
| const TEMP_DIR = path.resolve(process.cwd(), '.temp_docs'); | |
| import os from 'os'; | |
| import * as xlsx from 'xlsx'; | |
| import puppeteer from 'puppeteer'; | |
| import { logger } from './logger.js'; | |
| const execPromise = util.promisify(exec); | |
| const TEMP_DIR = path.join(os.tmpdir(), 'temp_docs'); |
| if (provider === 'gemini') { | ||
| const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ |
There was a problem hiding this comment.
These outbound LLM requests don’t include timeout/abort handling. If the provider stalls, the bot can hang awaiting fetch indefinitely. Consider using AbortController with a reasonable timeout and handling timeouts with a user-friendly error.
| if (fs.existsSync(configPath)) { | ||
| const data = JSON.parse(fs.readFileSync(configPath, 'utf8')); | ||
| for (const key of Object.keys(data)) { | ||
| process.env[key] = data[key]; |
There was a problem hiding this comment.
Values from JSON are assigned directly to process.env here. Since process.env is string-valued, non-string JSON values (objects/arrays/booleans) will be coerced in surprising ways (e.g. [object Object]). Validate types and coerce only supported primitives before assignment.
| process.env[key] = data[key]; | |
| const value = (data as Record<string, unknown>)[key]; | |
| if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { | |
| process.env[key] = String(value); | |
| } else { | |
| // Skip non-primitive values to avoid unexpected coercion (e.g., "[object Object]") | |
| console.warn(`Skipping non-primitive config value for key "${key}" in .config/scholar.json`); | |
| } |
| existingConfig[key] = value; | ||
| } | ||
|
|
||
| fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2)); |
There was a problem hiding this comment.
This writes API keys into .config/scholar.json without setting restrictive file permissions. Consider writing with mode 0o600 (best-effort) to reduce accidental exposure on multi-user systems.
| fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2)); | |
| fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2), { mode: 0o600 }); | |
| try { | |
| fs.chmodSync(configPath, 0o600); | |
| } catch { | |
| // Best-effort: ignore errors adjusting file permissions | |
| } |
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import * as xlsx from 'xlsx'; | ||
| import puppeteer from 'puppeteer'; |
There was a problem hiding this comment.
puppeteer is imported here but not used anywhere in this module. Remove the unused import to avoid unnecessary dependency loading and to keep the module focused on the tools it actually uses.
| import puppeteer from 'puppeteer'; |
Summary by CodeRabbit
New Features
Documentation
Chores