UI: ensure dark text on job detail and job history cards (avoid white-on-light backgrounds)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,116 +1,123 @@
|
|||||||
# Copilot Instructions for AITrader
|
# Copilot Instructions for AITrader
|
||||||
|
|
||||||
## Quick Start
|
This repo is a full‑stack React Router (v7) app with SSR, TypeScript, TailwindCSS, Playwright E2E tests, and optional MCP helpers. The existing AGENTS.md and workflows include helpful automation — this file consolidates the most important guidance Copilot sessions need.
|
||||||
|
|
||||||
This is a stock trading application built with React Router 7, TypeScript, and TailwindCSS, integrating with the Alpaca trading API.
|
## Build, test, and (lack of) lint commands
|
||||||
|
- Install deps: `npm install`
|
||||||
|
- Dev server (HMR): `npm run dev` (http://localhost:5173)
|
||||||
|
- Build: `npm run build` → output in `./build` (client + server)
|
||||||
|
- Serve production build: `npm start` (requires prior `npm run build`)
|
||||||
|
- Typecheck (must run before commit): `npm run typecheck` (runs `react-router typegen` then `tsc`)
|
||||||
|
|
||||||
### Essential Commands
|
Tests
|
||||||
- `npm install` – Install dependencies (first time only)
|
- Run unit tests (Vitest): `npm run test` (runs `vitest run`)
|
||||||
- `npm run dev` – Start development server at `http://localhost:5173`
|
- Watch mode: `npm run test:watch`
|
||||||
- `npm run build` – Create production build in `./build`
|
- Run a single Vitest file: `npx vitest run path/to/file.test.ts` or run tests by name: `npx vitest -t "test name"`
|
||||||
- `npm start` – Serve production build (requires `npm run build` first)
|
|
||||||
- `npm run typecheck` – Validate TypeScript (`react-router typegen` + `tsc`) — **must run before committing**
|
|
||||||
- `npm run test:e2e` – Run Playwright end-to-end tests
|
|
||||||
|
|
||||||
## Architecture Overview
|
E2E (Playwright)
|
||||||
|
- Full suite: `npm run test:e2e` (alias: `playwright test`)
|
||||||
|
- Run one spec: `npx playwright test tests/my.spec.ts`
|
||||||
|
- Run by title: `npx playwright test -g "test name"`
|
||||||
|
- HTML report: generated into `test-results/` (config in `playwright.config.ts`)
|
||||||
|
|
||||||
### Project Structure
|
Linting
|
||||||
```
|
- There is no lint script in package.json. Add ESLint/Prettier if desired; current CI/workflows don't run a linter by default.
|
||||||
app/
|
|
||||||
├── root.tsx # Root layout and error boundary
|
|
||||||
├── routes.ts # Route configuration (React Router 7 RouteConfig API)
|
|
||||||
├── routes/
|
|
||||||
│ ├── landing.tsx # Landing page
|
|
||||||
│ ├── home.tsx # Main application page
|
|
||||||
│ ├── stocks.tsx # Stock dashboard
|
|
||||||
│ └── api/ # Server-side API routes
|
|
||||||
│ ├── indicators.ts # Stock indicator calculations
|
|
||||||
│ └── alpaca/ # Alpaca broker integration
|
|
||||||
│ └── account.ts # Account data endpoints
|
|
||||||
├── components/ # Reusable React components
|
|
||||||
│ ├── StockViewer.tsx # Stock symbol search and indicator display
|
|
||||||
│ └── AlpacaAccountInfo.tsx # Account balance and portfolio info
|
|
||||||
├── utils/
|
|
||||||
│ ├── indicators.ts # Technical indicator logic (SMA, EMA, RSI, MACD)
|
|
||||||
│ └── __tests__/ # Unit tests via Vitest
|
|
||||||
├── types.ts # TypeScript interfaces (IndicatorData, AlpacaAccount)
|
|
||||||
└── app.css # Global styles
|
|
||||||
```
|
|
||||||
|
|
||||||
### Full-Stack Data Flow
|
MCP server helpers
|
||||||
1. **Client (React Components)** – User interacts with `StockViewer` or `AlpacaAccountInfo`
|
- Dev MCP server: `npm run mcp:dev` (runs `npx tsx mcp-server/index.ts`)
|
||||||
2. **Server Routes (`/routes/api/`)** – Handle business logic (fetch data from external APIs, run calculations)
|
- Build MCP server: `npm run mcp:build` (compiles `mcp-server` TypeScript)
|
||||||
3. **Utils** – Pure functions for indicators and shared logic (testable with Vitest)
|
|
||||||
4. **External APIs** – Alpaca API for account/trading data
|
|
||||||
|
|
||||||
### Server-Side Rendering (SSR)
|
## High-level architecture (big picture)
|
||||||
- Enabled by default (`ssr: true` in `react-router.config.ts`)
|
- Client: React components under `app/` (routes, root.tsx, components). Routes are file-based and can export loader functions for SSR.
|
||||||
- Routes can export loaders for initial data fetching
|
- Server: React Router build produces `build/server` that serves rendered routes; server-side API routes live under `app/routes/api/` and run server-only code.
|
||||||
- Use `loader` functions in route definitions for data pre-loading
|
- Utils: Pure functions and indicator logic in `app/utils/` (testable with Vitest).
|
||||||
|
- External integration: Alpaca trading API usage is colocated under `app/routes/api/alpaca/` and consumed by client components via `/api/*` endpoints.
|
||||||
|
- Tests: Playwright E2E tests in `tests/` use the dev server (configured in `playwright.config.ts`). Vitest unit tests configured in `vitest.config.ts` (jsdom environment, setup file `vitest.setup.ts`).
|
||||||
|
|
||||||
## Key Conventions
|
Build outputs & runtime ports
|
||||||
|
- Dev server: 5173 (vite/react-router dev)
|
||||||
|
- Production server (Docker): typically exposed on 3000
|
||||||
|
- Production build: `./build/client` (static assets) and `./build/server` (node server)
|
||||||
|
|
||||||
### TypeScript & Type Safety
|
## Key conventions (repo-specific)
|
||||||
- **Path alias** – Use `~/` for app imports (e.g., `import { IndicatorData } from "~/types"`)
|
- React Router 7 file-based routes: use `index()` only once at the same nesting level; prefer `route()` for additional segments.
|
||||||
- **Generated types** – React Router generates types in `.react-router/types/` after running `typecheck`
|
- Generated route types: always run `npm run typecheck` to produce `.react-router/types/` before `tsc` or commits.
|
||||||
- **Route types** – Import `type { Route }` from `./+types/[routename]` for loader/action types
|
- Path alias: `~/` maps to the app root for imports (e.g., `import { Foo } from "~/components/Foo"`).
|
||||||
- **Never skip `react-router typegen`** – Directly running `tsc` will fail; always run `npm run typecheck`
|
- ES Modules: package.json uses `"type": "module"` — include file extensions when Node requires them.
|
||||||
- **ES Module syntax** – Project uses `"type": "module"`; include file extensions in imports where needed
|
- Server-only code: place server-only logic under `app/routes/api/**` (these run on the server during SSR/build).
|
||||||
|
- Styling: Tailwind via Vite plugin; no separate processing step required.
|
||||||
|
|
||||||
### Component Patterns
|
## CI / GitHub Actions
|
||||||
- **Client-side interactivity** – Use React hooks (`useState`, `useEffect`) in components
|
- A Copilot setup workflow exists at `.github/workflows/copilot-setup-steps.yml` — it checks out code, sets up Node 20, runs `npm ci`, and installs Playwright browsers.
|
||||||
- **API calls** – Fetch from `/api/*` endpoints; proxy configured in `vite.config.ts` routes to local dev server
|
|
||||||
- **Error handling** – Wrap API calls in try/catch; set error state for UI display
|
|
||||||
- **Loading states** – Track `loading` boolean to show spinners/disable buttons during async work
|
|
||||||
|
|
||||||
### API Route Patterns
|
## Files important to Copilot sessions
|
||||||
- Handlers in `app/routes/api/**/*.ts` are server-only functions
|
- `AGENTS.md` — detailed quick‑start for agents (already includes many conventions). Keep synced with this file.
|
||||||
- Export a default `export default function(...)` that receives request context
|
- `.github/workflows/copilot-setup-steps.yml` — used for CI initialization and Playwright browser installation.
|
||||||
- Return JSON responses or error responses
|
- `playwright.config.ts` — webServer config (runs `npm run dev` on port 5173) and HTML reporter settings.
|
||||||
- Use utilities in `~/utils/` for shared logic (e.g., indicator calculations)
|
- `vitest.config.ts` & `vitest.setup.ts` — unit test env and globals.
|
||||||
|
|
||||||
### Testing
|
## Quick troubleshooting notes
|
||||||
- **Unit tests** – Use Vitest (`npm run test:e2e` actually runs Playwright, but unit tests exist via `vitest`)
|
- If `npm start` fails: confirm `npm run build` completed and `./build/server/index.js` exists.
|
||||||
- Located alongside source files in `__tests__/` directories
|
- If TypeScript errors appear after route changes: run `npm run typecheck` to regenerate route types before `tsc`.
|
||||||
- Test format: `*.test.ts` or `*.test.tsx`
|
- Playwright tests expect the dev server; allow up to 120s for the web server to start (configurable in `playwright.config.ts`).
|
||||||
- **E2E tests** – Playwright configured in `playwright.config.ts`
|
|
||||||
- Tests in `./tests/` directory
|
|
||||||
- Dev server starts automatically during test runs
|
|
||||||
- HTML report generated in `test-results/`
|
|
||||||
|
|
||||||
### Styling
|
---
|
||||||
- **TailwindCSS** – Configured via Vite plugin (`@tailwindcss/vite`); no separate build step needed
|
|
||||||
- **Global styles** – Edit `app/app.css`
|
|
||||||
- **Component styles** – Use Tailwind utility classes directly in JSX
|
|
||||||
|
|
||||||
### Import Paths
|
## Playwright MCP (Model Context Protocol) configuration for Copilot
|
||||||
- **Absolute imports** – Use `~/` alias for app folder (e.g., `~/components/StockViewer`)
|
|
||||||
- **Relative imports** – Use `./` or `../` sparingly within same directory tree
|
|
||||||
|
|
||||||
## Common Pitfalls
|
This repository already contains a Playwright-based MCP server at `mcp-server/index.ts`. To enable Copilot sessions to drive the web UI, follow these steps locally or in a Copilot runtime:
|
||||||
|
|
||||||
- **`npm start` fails if build doesn't exist** – Always run `npm run build` first
|
1. Install Playwright browsers (required once):
|
||||||
- **TypeScript compilation errors after route changes** – Missing `npm run typecheck` step; regenerated types in `.react-router/types/` are required
|
- `npx playwright install chromium --with-deps`
|
||||||
- **Vite proxy not working in dev** – Ensure dev server is running and API endpoints match `vite.config.ts` proxy config (default: `/api` → `http://127.0.0.1:3000`)
|
2. Start the MCP server (the server exposes tools Copilot can call):
|
||||||
- **No test framework exists for unit tests** – Repository includes Vitest/Playwright dependencies but no test runner script; configure as needed
|
- `npm run mcp:dev` (runs `npx tsx mcp-server/index.ts`)
|
||||||
- **Port conflicts** – Dev server uses `5173`, Docker/production uses `3000`
|
- The server logs "Playwright MCP Server started" to stderr when ready.
|
||||||
|
|
||||||
## Deployment
|
Available MCP tools (as implemented in `mcp-server/index.ts`):
|
||||||
|
- `navigate` — { url } → navigates and returns page title
|
||||||
|
- `getPageContent` — () → returns page body text
|
||||||
|
- `click` — { selector } → clicks element matching CSS selector
|
||||||
|
- `fillForm` — { selector, value } → fills an input
|
||||||
|
- `screenshot` — { path } → saves a screenshot at path
|
||||||
|
- `closeBrowser` — () → closes browser instance
|
||||||
|
|
||||||
|
Quick usage notes for Copilot sessions
|
||||||
|
- Ensure `npm run mcp:dev` is running in the environment where Copilot can reach stdin/stdout.
|
||||||
|
- Ensure Playwright browsers are installed and the runtime has necessary dependencies for Chromium.
|
||||||
|
- Tools accept JSON arguments and return structured content; errors are returned with `isError: true`.
|
||||||
|
|
||||||
|
Example tool call payload (navigate):
|
||||||
|
{
|
||||||
|
"name": "navigate",
|
||||||
|
"arguments": { "url": "http://localhost:5173" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Additions and CI
|
||||||
|
- The existing workflow `.github/workflows/copilot-setup-steps.yml` already installs Playwright browsers in CI. If Copilot sessions run in CI runners, the MCP server can be started there too.
|
||||||
|
- If desired, a short workflow can be added to launch the MCP server for integration tests; request if you want that added.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Configuration completed: MCP instructions added and linked to `mcp-server/index.ts`. Want a small GitHub Action to start the MCP server for CI runs (e.g., integration test job), or should a README be added inside `mcp-server/` with the same steps?
|
||||||
|
|
||||||
|
## Indexing for GitHub Copilot / Copilot Chat
|
||||||
|
|
||||||
|
To make Copilot (and Copilot Chat) index this repository so the assistant can answer repository-specific questions, follow these steps:
|
||||||
|
|
||||||
|
- In VS Code: install the **GitHub Copilot** and **GitHub Copilot Chat** extensions and sign into GitHub using the extensions' sign-in flow.
|
||||||
|
- From a terminal you can install the extensions with:
|
||||||
|
|
||||||
### Docker
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t aitrader .
|
code --install-extension GitHub.copilot
|
||||||
docker run -p 3000:3000 aitrader
|
code --install-extension GitHub.copilot-chat
|
||||||
```
|
```
|
||||||
Ensure `npm run build` is run in the Dockerfile before the final `CMD`.
|
|
||||||
|
|
||||||
### Environment Variables
|
- Open the Copilot Chat view (or the Command Palette) and run the workspace indexing command: `Copilot: Index workspace` (or `Copilot Chat: Index workspace`). This will scan project files and build the local index used by Copilot Chat.
|
||||||
- Check Dockerfile for any required environment setup
|
|
||||||
- Alpaca API credentials likely needed for trading features (not present in repo; set at runtime)
|
|
||||||
|
|
||||||
## Debugging Tips
|
- Exclude sensitive or large files from indexing: ensure secret files (API keys, `.env`) and large generated folders like `node_modules/`, `build/`, and `public/` are listed in `.gitignore` (or removed from the workspace) so they are not indexed. Do not commit credentials to the repo.
|
||||||
|
|
||||||
- **Type errors** – Run `npm run typecheck` to regenerate React Router types and validate all TS
|
- If your organization uses GitHub Copilot Enterprise / Copilot for Business and you want repo-level indexing on GitHub (server-side index), ask an org admin to enable repository indexing/code search for Copilot in the GitHub org settings.
|
||||||
- **Module resolution** – Check `tsconfig.json` for path aliases and ensure imports match configured paths
|
|
||||||
- **Component not rendering** – Check route configuration in `routes.ts` and ensure component is exported as default
|
- After indexing completes, verify by asking Copilot Chat repository-specific questions (for example: "Where is the landing page route?" or "Show the `AlpacaAccountInfo` component"). The Copilot Chat UI also shows indexing status and recent index actions.
|
||||||
- **API calls failing** – Verify Vite proxy config and that the target server is running
|
|
||||||
|
If you want, I can add a short `docs/README-indexing.md` with these steps or tighten the `copilot-instructions.md` wording further.
|
||||||
|
|||||||
@@ -14,21 +14,39 @@ interface ChartDataPoint {
|
|||||||
interface TradingViewChartProps {
|
interface TradingViewChartProps {
|
||||||
ticker: string;
|
ticker: string;
|
||||||
data?: ChartDataPoint[];
|
data?: ChartDataPoint[];
|
||||||
|
timeframe?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TradingViewChart({ ticker, data }: TradingViewChartProps) {
|
const TIMEFRAME_HEIGHTS: Record<string, number> = {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
"1D": 300,
|
||||||
|
"5Min": 250,
|
||||||
|
"15Min": 250,
|
||||||
|
"1H": 350,
|
||||||
|
"1W": 400,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TradingViewChart({ ticker, data, timeframe = "1D" }: TradingViewChartProps) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const height = TIMEFRAME_HEIGHTS[timeframe] ?? 400;
|
||||||
|
|
||||||
|
const isIntraday = ["1Min", "5Min", "15Min", "30Min", "1H"].includes(timeframe);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current) {
|
if (!containerRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chart = LightweightCharts.createChart(containerRef.current, {
|
const chart = LightweightCharts.createChart(containerRef.current, {
|
||||||
height: 400,
|
height,
|
||||||
autoSize: true,
|
autoSize: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Configure time scale based on timeframe and range
|
||||||
|
chart.timeScale().applyOptions({
|
||||||
|
timeVisible: isIntraday,
|
||||||
|
secondsVisible: timeframe === "1Min",
|
||||||
|
});
|
||||||
|
|
||||||
const candlestickSeries = chart.addSeries(LightweightCharts.CandlestickSeries, {
|
const candlestickSeries = chart.addSeries(LightweightCharts.CandlestickSeries, {
|
||||||
upColor: "#26a69a",
|
upColor: "#26a69a",
|
||||||
downColor: "#ef5350",
|
downColor: "#ef5350",
|
||||||
@@ -41,13 +59,15 @@ export default function TradingViewChart({ ticker, data }: TradingViewChartProps
|
|||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
try {
|
try {
|
||||||
candlestickSeries.setData(data as any);
|
candlestickSeries.setData(data as any);
|
||||||
|
// Fit the visible data range
|
||||||
|
chart.timeScale().fitContent();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`TradingViewChart: error setting data for ${ticker}`, err);
|
console.error(`TradingViewChart: error setting data for ${ticker}`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => chart.remove();
|
return () => chart.remove();
|
||||||
}, [data, ticker]);
|
}, [data, ticker, isIntraday, timeframe]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-xl shadow-lg p-4">
|
<div className="bg-white rounded-xl shadow-lg p-4">
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ export async function action({ request }: { request: Request }) {
|
|||||||
if (body.background) {
|
if (body.background) {
|
||||||
// Enqueue background analyze job and return 202 immediately
|
// Enqueue background analyze job and return 202 immediately
|
||||||
try {
|
try {
|
||||||
const { enqueueAnalyze } = await import("../../lib/jobQueue");
|
const { enqueueAnalyze } = await import("../../lib/queue");
|
||||||
const jobId = enqueueAnalyze(ticker, input);
|
const jobId = await enqueueAnalyze(ticker, input);
|
||||||
return Response.json({ status: "queued", jobId }, { status: 202 });
|
return Response.json({ status: "queued", jobId }, { status: 202 });
|
||||||
} catch (enqueueErr) {
|
} catch (enqueueErr) {
|
||||||
console.error("[analyze] enqueue error:", enqueueErr);
|
console.error("[analyze] enqueue error:", enqueueErr);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function JobDetail() {
|
|||||||
<Navbar />
|
<Navbar />
|
||||||
<div className="mx-auto max-w-4xl px-6 py-8">
|
<div className="mx-auto max-w-4xl px-6 py-8">
|
||||||
<h1 className="text-2xl font-bold text-gray-900 mb-4">Job {job?.id}</h1>
|
<h1 className="text-2xl font-bold text-gray-900 mb-4">Job {job?.id}</h1>
|
||||||
<div className="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
|
<div className="bg-white rounded-xl shadow-lg p-6 border border-gray-200 text-gray-900">
|
||||||
<div className="mb-3 text-sm text-gray-700">State: <strong className="ml-2">{job?.state}</strong></div>
|
<div className="mb-3 text-sm text-gray-700">State: <strong className="ml-2">{job?.state}</strong></div>
|
||||||
{job?.failedReason && (
|
{job?.failedReason && (
|
||||||
<div className="mb-3 text-red-600">Failed: {job.failedReason}</div>
|
<div className="mb-3 text-red-600">Failed: {job.failedReason}</div>
|
||||||
@@ -85,7 +85,7 @@ export default function JobDetail() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-sm text-gray-700 mb-3">Data:</div>
|
<div className="text-sm text-gray-700 mb-3">Data:</div>
|
||||||
<pre className="bg-gray-50 p-3 rounded text-xs overflow-x-auto">{JSON.stringify(job?.data || job?.returnValue || job, null, 2)}</pre>
|
<pre className="bg-gray-50 p-3 rounded text-xs overflow-x-auto text-gray-800">{JSON.stringify(job?.data || job?.returnValue || job, null, 2)}</pre>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,363 @@
|
|||||||
|
# Most Active Stocks Table Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Replace the StockViewer on `/stocks` with a table of most active stocks from Alpaca's screener API.
|
||||||
|
|
||||||
|
**Architecture:** Server proxy route calls Alpaca screener, React component fetches and auto-refreshes every 30s with clickable rows linking to `/analyze/TICKER`.
|
||||||
|
|
||||||
|
**Tech Stack:** React Router 7, React, TailwindCSS, fetch API, Alpaca Markets API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Add MostActiveStock type to types.ts
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `app/types.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add MostActiveStock interface**
|
||||||
|
|
||||||
|
Add to `app/types.ts` after the `AlpacaAccount` interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface MostActiveStock {
|
||||||
|
symbol: string;
|
||||||
|
name: string;
|
||||||
|
price: number;
|
||||||
|
changePercent: number;
|
||||||
|
volume: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add app/types.ts
|
||||||
|
git commit -m "types: add MostActiveStock interface"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Create API route for most active stocks
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `app/routes/api/stocks/most-actives.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the server proxy route**
|
||||||
|
|
||||||
|
Create `app/routes/api/stocks/most-actives.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { MostActiveStock } from "../../../types";
|
||||||
|
|
||||||
|
const ALPACA_API_KEY = process.env.ALPACA_API_KEY!;
|
||||||
|
const ALPACA_SECRET_KEY = process.env.ALPACA_SECRET_KEY!;
|
||||||
|
const ALPACA_DATA_URL = process.env.ALPACA_DATA_URL || "https://data.alpaca.markets";
|
||||||
|
|
||||||
|
export async function loader() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${ALPACA_DATA_URL}/v1beta1/screener/stocks/most-actives`, {
|
||||||
|
headers: {
|
||||||
|
"APCA-API-KEY-ID": ALPACA_API_KEY,
|
||||||
|
"APCA-API-SECRET-KEY": ALPACA_SECRET_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Alpaca API error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const stocks: MostActiveStock[] = (data.most_actives || []).map((item: any) => ({
|
||||||
|
symbol: item.symbol,
|
||||||
|
name: item.name || item.symbol,
|
||||||
|
price: parseFloat(item.price) || 0,
|
||||||
|
changePercent: parseFloat(item.change_percent) || 0,
|
||||||
|
volume: parseInt(item.volume) || 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Response.json(stocks);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Most active stocks API error:", error);
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return Response.json(
|
||||||
|
{ error: `Failed to fetch most active stocks: ${message}` },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add app/routes/api/stocks/most-actives.ts
|
||||||
|
git commit -m "feat: add most-actives API proxy route"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Register the new API route
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `app/routes.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add route entry**
|
||||||
|
|
||||||
|
Add this line to `app/routes.ts` after the existing `api/stocks` route (line 11):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
route("api/stocks/most-actives", "routes/api/stocks/most-actives.ts"),
|
||||||
|
```
|
||||||
|
|
||||||
|
The routes array should look like:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default [
|
||||||
|
index("routes/landing.tsx"),
|
||||||
|
route("api/alpaca/account", "routes/api/alpaca/account.ts"),
|
||||||
|
route("api/alpaca/quote/:ticker", "routes/api/alpaca/quote.ts"),
|
||||||
|
route("api/alpaca/orders", "routes/api/alpaca/orders.ts"),
|
||||||
|
route("api/alpaca/positions", "routes/api/alpaca/positions.ts"),
|
||||||
|
route("api/indicators", "routes/api/indicators.ts"),
|
||||||
|
route("api/analyze", "routes/api/analyze.ts"),
|
||||||
|
route("api/stocks", "routes/api/stocks/index.ts"),
|
||||||
|
route("api/stocks/most-actives", "routes/api/stocks/most-actives.ts"),
|
||||||
|
route("stocks", "routes/stocks.tsx"),
|
||||||
|
route("analyze", "routes/analyze.tsx"),
|
||||||
|
route("analyze/:ticker", "routes/analyze.ticker.tsx"),
|
||||||
|
] satisfies RouteConfig;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add app/routes.ts
|
||||||
|
git commit -m "routes: register most-actives API endpoint"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Create MostActiveStocks component
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `app/components/MostActiveStocks.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the component**
|
||||||
|
|
||||||
|
Create `app/components/MostActiveStocks.tsx`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
import { Link } from "react-router";
|
||||||
|
import type { MostActiveStock } from "../types";
|
||||||
|
|
||||||
|
function formatVolume(vol: number): string {
|
||||||
|
if (vol >= 1_000_000_000) return `${(vol / 1_000_000_000).toFixed(1)}B`;
|
||||||
|
if (vol >= 1_000_000) return `${(vol / 1_000_000).toFixed(1)}M`;
|
||||||
|
if (vol >= 1_000) return `${(vol / 1_000).toFixed(1)}K`;
|
||||||
|
return vol.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPrice(price: number): string {
|
||||||
|
return `$${price.toFixed(2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatChangePercent(pct: number): string {
|
||||||
|
return `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MostActiveStocks() {
|
||||||
|
const [stocks, setStocks] = useState<MostActiveStock[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const fetchData = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
const res = await fetch("/api/stocks/most-actives");
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
throw new Error(data.error || "Failed to fetch data");
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
setStocks(data);
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : "Failed to fetch most active stocks.";
|
||||||
|
setError(message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
const interval = setInterval(fetchData, 30000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [fetchData]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
|
||||||
|
<div className="space-y-3">
|
||||||
|
{Array.from({ length: 8 }).map((_, i) => (
|
||||||
|
<div key={i} className="animate-pulse flex gap-4 py-3 border-b border-gray-100 last:border-0">
|
||||||
|
<div className="h-5 bg-gray-200 rounded w-16" />
|
||||||
|
<div className="h-5 bg-gray-200 rounded w-32" />
|
||||||
|
<div className="h-5 bg-gray-200 rounded w-20" />
|
||||||
|
<div className="h-5 bg-gray-200 rounded w-20" />
|
||||||
|
<div className="h-5 bg-gray-200 rounded w-24" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error && stocks.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
|
||||||
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
|
<p className="text-red-600 text-sm mb-3">{error}</p>
|
||||||
|
<button
|
||||||
|
onClick={fetchData}
|
||||||
|
className="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors"
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden">
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red-50 border-b border-red-200 px-6 py-3">
|
||||||
|
<p className="text-red-600 text-sm">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-gray-50 border-b border-gray-200">
|
||||||
|
<th className="text-left px-6 py-4 text-sm font-semibold text-gray-600">Symbol</th>
|
||||||
|
<th className="text-left px-6 py-4 text-sm font-semibold text-gray-600">Name</th>
|
||||||
|
<th className="text-right px-6 py-4 text-sm font-semibold text-gray-600">Price</th>
|
||||||
|
<th className="text-right px-6 py-4 text-sm font-semibold text-gray-600">Change %</th>
|
||||||
|
<th className="text-right px-6 py-4 text-sm font-semibold text-gray-600">Volume</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{stocks.map((stock) => (
|
||||||
|
<tr key={stock.symbol} className="border-b border-gray-100 hover:bg-gray-50 transition-colors">
|
||||||
|
<td className="px-6 py-4">
|
||||||
|
<Link
|
||||||
|
to={`/analyze/${stock.symbol}`}
|
||||||
|
className="text-blue-600 font-semibold hover:text-blue-700 hover:underline"
|
||||||
|
>
|
||||||
|
{stock.symbol}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 text-gray-600">{stock.name}</td>
|
||||||
|
<td className="px-6 py-4 text-right font-mono text-gray-900">{formatPrice(stock.price)}</td>
|
||||||
|
<td className={`px-6 py-4 text-right font-mono font-medium ${stock.changePercent >= 0 ? "text-green-600" : "text-red-600"}`}>
|
||||||
|
{formatChangePercent(stock.changePercent)}
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 text-right text-gray-600">{formatVolume(stock.volume)}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add app/components/MostActiveStocks.tsx
|
||||||
|
git commit -m "feat: add MostActiveStocks table component with auto-refresh"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Update stocks.tsx page
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `app/routes/stocks.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace StockViewer with MostActiveStocks**
|
||||||
|
|
||||||
|
Replace the entire contents of `app/routes/stocks.tsx`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import MostActiveStocks from "../components/MostActiveStocks";
|
||||||
|
import Navbar from "../components/Navbar";
|
||||||
|
|
||||||
|
export default function Stocks() {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50">
|
||||||
|
<Navbar />
|
||||||
|
<div className="py-20">
|
||||||
|
<div className="mx-auto max-w-7xl px-6 sm:px-8 lg:px-8">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
||||||
|
Most Active Stocks
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||||
|
Real-time view of the most actively traded stocks, auto-refreshing every 30 seconds.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<MostActiveStocks />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add app/routes/stocks.tsx
|
||||||
|
git commit -m "feat: replace StockViewer with MostActiveStocks on stocks page"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Verify and test
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- All modified files
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run typecheck**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: No type errors. If there are errors, fix them before proceeding.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run dev server and verify**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigate to `http://localhost:5173/stocks` and verify:
|
||||||
|
- Table loads with most active stocks
|
||||||
|
- Symbol links navigate to `/analyze/TICKER`
|
||||||
|
- Change % is color-coded (green for positive, red for negative)
|
||||||
|
- Data auto-refreshes every 30 seconds
|
||||||
|
- Loading skeleton shows on initial load
|
||||||
|
- Error state shows with retry button if API fails
|
||||||
|
|
||||||
|
- [ ] **Step 3: Final commit (if any fixes needed)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "fix: address typecheck/dev issues"
|
||||||
|
```
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
y# Design: Most Active Stocks Table
|
||||||
|
|
||||||
|
**Date:** 2026-05-16
|
||||||
|
**Status:** Approved
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Replace the current StockViewer on the `/stocks` page with a table displaying the most active stocks from Alpaca's screener API.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Server Route: `api/stocks/most-actives`
|
||||||
|
- **File:** `app/routes/api/stocks/most-actives.ts`
|
||||||
|
- **Method:** GET (loader)
|
||||||
|
- **Function:** Proxies request to `https://data.alpaca.markets/v1beta1/screener/stocks/most-actives`
|
||||||
|
- **Response:** Normalized JSON array with fields: `symbol`, `name`, `price`, `changePercent`, `volume`
|
||||||
|
- **Auth:** Uses server-side `ALPACA_API_KEY` and `ALPACA_SECRET_KEY`
|
||||||
|
|
||||||
|
### Component: `MostActiveStocks`
|
||||||
|
- **File:** `app/components/MostActiveStocks.tsx`
|
||||||
|
- **Behavior:**
|
||||||
|
- Fetches from `/api/stocks/most-actives` on mount
|
||||||
|
- Auto-refreshes every 30 seconds via `setInterval`
|
||||||
|
- Cleans up interval on unmount
|
||||||
|
- **States:** loading (skeleton rows), success (table), error (red banner with retry button)
|
||||||
|
|
||||||
|
### Page: `stocks.tsx`
|
||||||
|
- Replaces `<StockViewer />` with `<MostActiveStocks />`
|
||||||
|
- Updates heading and description text to match new content
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
MostActiveStocks component
|
||||||
|
→ fetch /api/stocks/most-actives
|
||||||
|
→ server calls Alpaca screener API
|
||||||
|
→ returns normalized data
|
||||||
|
→ renders table
|
||||||
|
→ setInterval re-fetches every 30s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Table Columns
|
||||||
|
|
||||||
|
| Column | Source | Format |
|
||||||
|
|--------|--------|--------|
|
||||||
|
| Symbol | `symbol` | Clickable link → `/analyze/TICKER` |
|
||||||
|
| Name | `name` | Plain text |
|
||||||
|
| Price | `price` | `$X.XX` |
|
||||||
|
| Change % | `changePercent` | `+X.XX%` / `-X.XX%` (color-coded green/red) |
|
||||||
|
| Volume | `volume` | Formatted number (e.g., `12.3M`) |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- API errors: display red banner with error message and retry button
|
||||||
|
- Network failures: show last data if available, otherwise error state
|
||||||
|
- Empty response: show "No data available" message
|
||||||
|
|
||||||
|
## Route Changes
|
||||||
|
|
||||||
|
Add to `routes.ts`:
|
||||||
|
```
|
||||||
|
route("api/stocks/most-actives", "routes/api/stocks/most-actives.ts"),
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
| File | Action | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `app/routes/api/stocks/most-actives.ts` | Create | Server proxy to Alpaca |
|
||||||
|
| `app/components/MostActiveStocks.tsx` | Create | Table component with auto-refresh |
|
||||||
|
| `app/routes/stocks.tsx` | Modify | Replace StockViewer with MostActiveStocks |
|
||||||
|
| `app/routes.ts` | Modify | Add new API route |
|
||||||
Generated
+259
-5
@@ -10,6 +10,8 @@
|
|||||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||||
"@react-router/node": "7.15.0",
|
"@react-router/node": "7.15.0",
|
||||||
"@react-router/serve": "7.15.0",
|
"@react-router/serve": "7.15.0",
|
||||||
|
"bullmq": "^5.76.8",
|
||||||
|
"ioredis": "^5.10.1",
|
||||||
"isbot": "^5.1.36",
|
"isbot": "^5.1.36",
|
||||||
"lightweight-charts": "^5.2.0",
|
"lightweight-charts": "^5.2.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
@@ -1367,6 +1369,12 @@
|
|||||||
"deprecated": "Use @eslint/object-schema instead",
|
"deprecated": "Use @eslint/object-schema instead",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/@ioredis/commands": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
@@ -1731,6 +1739,84 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@napi-rs/wasm-runtime": {
|
"node_modules/@napi-rs/wasm-runtime": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||||
@@ -3648,6 +3734,23 @@
|
|||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/bullmq": {
|
||||||
|
"version": "5.76.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.76.8.tgz",
|
||||||
|
"integrity": "sha512-v3WTwA8diFtsADaJ8eK2ozyi2CYK9rDZCeoKF+dIPF/MUL8HxAOa3H72Gmu1lC4yKlho6t1PwNr/QpDVqaNEZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cron-parser": "4.9.0",
|
||||||
|
"ioredis": "5.10.1",
|
||||||
|
"msgpackr": "2.0.1",
|
||||||
|
"node-abort-controller": "3.1.1",
|
||||||
|
"semver": "7.8.0",
|
||||||
|
"tslib": "2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.22.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -3783,6 +3886,15 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cluster-key-slot": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -3919,6 +4031,18 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cron-parser": {
|
||||||
|
"version": "4.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
|
||||||
|
"integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"luxon": "^3.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -4020,6 +4144,15 @@
|
|||||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/denque": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -4053,7 +4186,7 @@
|
|||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -5115,6 +5248,30 @@
|
|||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ioredis": {
|
||||||
|
"version": "5.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz",
|
||||||
|
"integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ioredis/commands": "1.5.1",
|
||||||
|
"cluster-key-slot": "^1.1.0",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"denque": "^2.1.0",
|
||||||
|
"lodash.defaults": "^4.2.0",
|
||||||
|
"lodash.isarguments": "^3.1.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0",
|
||||||
|
"standard-as-callback": "^2.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.22.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/ioredis"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ip-address": {
|
"node_modules/ip-address": {
|
||||||
"version": "10.2.0",
|
"version": "10.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
|
||||||
@@ -5661,6 +5818,18 @@
|
|||||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.defaults": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isarguments": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@@ -5677,6 +5846,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/luxon": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lz-string": {
|
"node_modules/lz-string": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
@@ -5874,6 +6052,37 @@
|
|||||||
"safe-buffer": "^5.1.2"
|
"safe-buffer": "^5.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/msgpackr": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-9J+tqTEsbHqY8YohazYgty7LgerFIWxvMLpUjqETSmjHojtJm2WnX2kK/2a1fLI7CO7ERP1YSEUXMucz4j+yBA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"msgpackr-extract": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/msgpackr-extract": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"node-gyp-build-optional-packages": "5.2.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.12",
|
"version": "3.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||||
@@ -5928,6 +6137,27 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-abort-controller": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/node-gyp-build-optional-packages": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-gyp-build-optional-packages": "bin.js",
|
||||||
|
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||||
|
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.44",
|
"version": "2.0.44",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz",
|
||||||
@@ -6521,6 +6751,27 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/require-from-string": {
|
"node_modules/require-from-string": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||||
@@ -6752,7 +7003,6 @@
|
|||||||
"version": "7.8.0",
|
"version": "7.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -6963,6 +7213,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/standard-as-callback": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||||
@@ -7183,9 +7439,7 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"dev": true,
|
"license": "0BSD"
|
||||||
"license": "0BSD",
|
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.21.0",
|
"version": "4.21.0",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||||
"@react-router/node": "7.15.0",
|
"@react-router/node": "7.15.0",
|
||||||
"@react-router/serve": "7.15.0",
|
"@react-router/serve": "7.15.0",
|
||||||
|
"bullmq": "^5.76.8",
|
||||||
|
"ioredis": "^5.10.1",
|
||||||
"isbot": "^5.1.36",
|
"isbot": "^5.1.36",
|
||||||
"lightweight-charts": "^5.2.0",
|
"lightweight-charts": "^5.2.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Reference in New Issue
Block a user