commit f1301de543267dbdefa6027b0cc26f3f6ab5973b Author: LGram16 Date: Sat Jan 31 22:34:57 2026 +0900 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..cde410e --- /dev/null +++ b/App.tsx @@ -0,0 +1,239 @@ + +import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import { HashRouter, Routes, Route, Link, useLocation } from 'react-router-dom'; +import { + LayoutDashboard, + Settings as SettingsIcon, + Newspaper, + Cpu, + History, + Terminal, + ChevronDown, + ShieldCheck, + Server, + Zap, + Star, + LayoutGrid, + RotateCcw, + Compass +} from 'lucide-react'; +import { ApiSettings, StockItem, OrderType, MarketType, TradeOrder, AutoTradeConfig, WatchlistGroup, ReservedOrder, StockTick } from './types'; +import { MOCK_STOCKS } from './constants'; +import { KisService } from './services/kisService'; +import { TelegramService } from './services/telegramService'; +import { DbService } from './services/dbService'; +import Dashboard from './pages/Dashboard'; +import AutoTrading from './pages/AutoTrading'; +import News from './pages/News'; +import Settings from './pages/Settings'; +import HistoryPage from './pages/History'; +import WatchlistManagement from './pages/WatchlistManagement'; +import Stocks from './pages/Stocks'; +import Discovery from './pages/Discovery'; + +interface LogEntry { + id: string; + type: 'info' | 'error' | 'success' | 'warn'; + message: string; + timestamp: Date; +} + +const TopNavItem: React.FC<{ to: string, icon: React.ReactNode, label: string, active: boolean }> = ({ to, icon, label, active }) => ( + + {/* Fix for line 43: Use React.isValidElement and cast to React.ReactElement to allow the size prop for Lucide icons */} + {React.isValidElement(icon) ? React.cloneElement(icon as React.ReactElement, { size: 16 }) : icon} + {label} + +); + +const AppContent: React.FC = () => { + const [settings, setSettings] = useState(() => { + const saved = localStorage.getItem('trader_settings'); + return saved ? JSON.parse(saved) : { + appKey: '', appSecret: '', accountNumber: '', + useTelegram: false, telegramToken: '', telegramChatId: '', + useNaverNews: false, naverClientId: '', naverClientSecret: '', + aiConfigs: [ + { id: 'def_gemini', name: 'Gemini 기본 분석기', providerType: 'gemini', modelName: 'gemini-3-flash-preview' } + ], + preferredNewsAiId: 'def_gemini', + preferredStockAiId: 'def_gemini', + preferredNewsJudgementAiId: 'def_gemini', + preferredAutoBuyAiId: 'def_gemini', + preferredAutoSellAiId: 'def_gemini' + }; + }); + + const [marketMode, setMarketMode] = useState(MarketType.DOMESTIC); + const [masterStocks, setMasterStocks] = useState(() => { + const saved = localStorage.getItem('batchukis_master_stocks'); + return saved ? JSON.parse(saved) : MOCK_STOCKS; + }); + + const [watchlistGroups, setWatchlistGroups] = useState([]); + const [orders, setOrders] = useState([]); + const [reservedOrders, setReservedOrders] = useState([]); + const [autoTrades, setAutoTrades] = useState([]); + const [logs, setLogs] = useState([]); + const [isConsoleOpen, setIsConsoleOpen] = useState(false); + + const location = useLocation(); + const kisService = useMemo(() => new KisService(settings), [settings]); + const dbService = useMemo(() => new DbService(), []); + + const visibleStocks = useMemo(() => { + return masterStocks.filter(s => !s.isHidden); + }, [masterStocks]); + + useEffect(() => { + localStorage.setItem('trader_settings', JSON.stringify(settings)); + }, [settings]); + + useEffect(() => { + localStorage.setItem('batchukis_master_stocks', JSON.stringify(masterStocks)); + }, [masterStocks]); + + useEffect(() => { + syncFromDb(); + }, [dbService]); + + const addLog = useCallback((message: string, type: LogEntry['type'] = 'info') => { + setLogs(prev => [{ + id: Math.random().toString(36).substr(2, 9), + type, message, timestamp: new Date() + }, ...prev].slice(0, 100)); + }, []); + + const handleUpdateStockMetadata = (code: string, updates: Partial) => { + setMasterStocks(prev => prev.map(s => s.code === code ? { ...s, ...updates } : s)); + }; + + const syncFromDb = async () => { + const configs = await dbService.getAutoConfigs(); + const groups = await dbService.getWatchlistGroups(); + const resOrders = await dbService.getReservedOrders(); + setAutoTrades(configs); + setWatchlistGroups(groups); + setReservedOrders(resOrders); + }; + + const handleSyncStocks = async () => { + addLog(`${marketMode === MarketType.DOMESTIC ? '국내' : '해외'} 종목 마스터 동기화 시작...`, 'info'); + try { + const serverStocks = await kisService.fetchMasterStocks(marketMode); + setMasterStocks(prev => { + const existingCodes = new Set(prev.map(s => s.code)); + const newStocks = serverStocks.filter(s => !existingCodes.has(s.code)); + if (newStocks.length === 0) return prev; + addLog(`${newStocks.length}개의 신규 종목이 마스터 리스트에 추가되었습니다.`, 'success'); + return [...prev, ...newStocks]; + }); + } catch (e) { + addLog("종목 동기화 실패", "error"); + } + }; + + const handleManualOrder = async (order: Omit) => { + const res = await (order.stockCode.length > 6 + ? kisService.orderOverseas(order.stockCode, order.type, order.quantity, order.price) + : kisService.orderCash(order.stockCode, order.type, order.quantity, order.price) + ); + if (res.success) { + const newOrder: TradeOrder = { id: res.orderId, ...order, status: 'COMPLETED', timestamp: new Date() } as TradeOrder; + await dbService.syncOrderToHolding(newOrder); + setOrders(prev => [newOrder, ...prev]); + addLog(`${order.stockName} ${order.type === OrderType.BUY ? '매수' : '매도'} 완료`, 'success'); + } + }; + + const handleAddToWatchlist = async (stock: StockItem) => { + const groups = await dbService.getWatchlistGroups(); + const targetGroup = groups.find(g => g.market === stock.market); + if (targetGroup) { + const updated = targetGroup.codes.includes(stock.code) + ? { ...targetGroup, codes: targetGroup.codes.filter(c => c !== stock.code) } + : { ...targetGroup, codes: [...targetGroup.codes, stock.code] }; + await dbService.updateWatchlistGroup(updated); + syncFromDb(); + } + }; + + const watchlistCodes = useMemo(() => watchlistGroups.flatMap(g => g.codes), [watchlistGroups]); + const isActive = (path: string) => location.pathname === path; + + return ( +
+
+
+
+

+ BATCHUKIS +

+ Trading Bot Engine +
+ +
+
+
+ + +
+ +
+
+
+ +
+ + { await dbService.saveReservedOrder({id: 'res_'+Date.now(), ...o, status: 'WAITING', createdAt: new Date(), expiryDate: new Date()}); syncFromDb(); }} onDeleteReservedOrder={async (id) => { await dbService.deleteReservedOrder(id); syncFromDb(); }} onRefreshHoldings={syncFromDb} autoTrades={autoTrades} />} /> + } /> + addLog(`${s.name} ${t} 주문 패널 진입`, 'info')} onAddToWatchlist={handleAddToWatchlist} watchlistCodes={watchlistCodes} onSync={handleSyncStocks} />} /> + { await dbService.saveAutoConfig({...c, id: 'auto_'+Date.now(), active: true}); syncFromDb(); }} onToggleConfig={async (id) => { const c = (await dbService.getAutoConfigs()).find(x => x.id === id); if(c) { await dbService.updateAutoConfig({...c, active: !c.active}); syncFromDb(); } }} onDeleteConfig={async (id) => { await dbService.deleteAutoConfig(id); syncFromDb(); }} />} /> + } /> + } /> + } /> + } /> + +
+ + {isConsoleOpen && ( +
+
+
Execution Console
+
+ + +
+
+
+ {logs.map(log => ( +
+ [{log.timestamp.toLocaleTimeString()}] + {log.type.toUpperCase()} + {log.message} +
+ ))} + {logs.length === 0 &&
No logs available. System standby...
} +
+
+ )} +
+ ); +}; + +const App: React.FC = () => ( + + + +); + +export default App; diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bcf0647 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# 1단계: 빌드 (Node.js) +FROM node:20-alpine AS build +WORKDIR /app +COPY package*.json ./ +RUN rm -f package-lock.json && npm install +COPY . . +RUN npm run build + +# 2단계: 실행 (Nginx) +# 2단계: 실행 (Node.js) +FROM node:20-alpine +WORKDIR /app + +# 프로덕션 의존성만 설치 +COPY package*.json ./ +RUN npm install --omit=dev + +# 서버 파일 및 빌드 결과물 복사 +COPY server.js ./ +COPY --from=build /app/dist ./dist + +# 환경변수 포트 노출 +EXPOSE 80 + +# 서버 실행 +CMD ["node", "server.js"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..38b4207 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/drive/1T7Zhx4xShoPXEOK6c7cUt1CYpmHfxKKq + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/backend/ReadMe.md b/backend/ReadMe.md new file mode 100644 index 0000000..5771575 --- /dev/null +++ b/backend/ReadMe.md @@ -0,0 +1,15 @@ +# BatchuKis 백엔드 독립 실행 엔진 및 API 통합 사양서 (v1.6) + +본 문서는 BatchuKis 플랫폼의 백엔드 시스템 아키텍처 및 독립 실행형 매매 엔진 로직을 정의합니다. + +## 1. 시스템 아키텍처 + +### 1.1 Headless Execution Engine +1. **Batch Engine**: 매 1분마다 `auto_trade_configs`를 스캔하여 예약된 시각에 주문 실행. +2. **Monitoring Engine**: WebSocket 시세를 수신하여 `reserved_orders` 조건 감시 및 자동 매매. +3. **AI Proxy**: API 보안을 위해 AI 분석 및 뉴스 요청 중계. + +## 2. 상세 명세 가이드 +- **DB 스키마**: [tables.md](./tables.md) 참조. +- **API 엔드포인트**: [api.md](./api.md) 참조. +- **데이터 구조(JSON)**: [models.md](./models.md) 참조. diff --git a/backend/api.md b/backend/api.md new file mode 100644 index 0000000..7c0dacc --- /dev/null +++ b/backend/api.md @@ -0,0 +1,91 @@ +# BatchuKis API Specification + +이 문서는 프론트엔드와 백엔드 간의 통신을 위한 API 인터페이스 명세서입니다. +**데이터 모델(JSON 구조)의 상세 정의는 [models.md](./models.md)를 참조하십시오.** + +--- + +## 1. 설정 및 시스템 (Settings) + +### 1.1 전체 설정 가져오기 +- **URL**: `GET /api/settings` +- **Response**: `ApiSettings` (See models.md) + +### 1.2 전체 설정 저장하기 +- **URL**: `POST /api/settings` +- **Body**: `ApiSettings` +- **Response**: `{ "success": boolean, "message": string }` + +### 1.3 AI 엔진 풀(Pool) 관리 +#### 1.3.1 등록된 AI 엔진 목록 조회 +- **URL**: `GET /api/settings/ai-configs` +- **Response**: `AiConfig[]` + +#### 1.3.2 신규 AI 엔진 추가 +- **URL**: `POST /api/settings/ai-configs` +- **Body**: `Omit` +- **Response**: `{ "id": string, "success": true }` + +--- + +## 2. 자산 및 잔고 (Portfolio) + +### 2.1 보유 종목 리스트 +- **URL**: `GET /api/holdings` +- **Query**: `?market=Domestic|Overseas` +- **Response**: `HoldingItem[]` + +### 2.2 계좌 요약 (자산/예수금) +- **URL**: `GET /api/account/summary` +- **Response**: `{ "totalAssets": number, "buyingPower": number }` + +--- + +## 3. 자동매매 전략 (Auto Trading) + +### 3.1 로봇 리스트 조회 +- **URL**: `GET /api/auto-trades` +- **Response**: `AutoTradeConfig[]` + +### 3.2 로봇 등록/수정 +- **URL**: `POST /api/auto-trades` +- **Body**: `AutoTradeConfig` (ID가 없으면 생성, 있으면 수정) +- **Response**: `{ "id": string }` + +--- + +## 4. 실시간 감시 주문 (Reserved Orders) + +### 4.1 감시 목록 조회 +- **URL**: `GET /api/reserved-orders` +- **Response**: `ReservedOrder[]` + +### 4.2 감시 등록 +- **URL**: `POST /api/reserved-orders` +- **Body**: `Omit` +- **Response**: `{ "id": string }` + +--- + +## 5. 종목 및 시세 (Market Data & Discovery) + +### 5.1 마스터 종목 리스트 (동기화용) +- **URL**: `GET /api/kis/master-stocks` +- **Query**: `?market=Domestic|Overseas` +- **Response**: `StockItem[]` + +### 5.2 개별 종목 시세 차트 데이터 +- **URL**: `GET /api/kis/ticks/:code` +- **Query**: `?limit=100` +- **Response**: `StockTick[]` + +### 5.3 [NEW] 종목 발굴 랭킹 데이터 +- **URL**: `GET /api/discovery/rankings` +- **Query**: `?market=Domestic|Overseas&category=VOLUME|VALUE|GAIN|LOSS|FOREIGN_BUY|INSTITUTION_BUY` +- **Response**: `DiscoveryRankingResponse` (See models.md) +- **설명**: 토스증권 스타일의 발굴 페이지에 데이터를 공급합니다. 거래비율(buyRatio/sellRatio) 및 수급 데이터가 포함됩니다. + +### 5.4 [NEW] 실시간 커뮤니티 심리 요약 (AI 전용) +- **URL**: `GET /api/discovery/sentiment/:code` +- **Response**: `{ "insights": string[], "sentimentScore": number }` +- **설명**: 특정 종목에 대한 AI의 실시간 커뮤니티/뉴스 요약 정보를 반환합니다. diff --git a/backend/models.md b/backend/models.md new file mode 100644 index 0000000..b53909f --- /dev/null +++ b/backend/models.md @@ -0,0 +1,126 @@ +# BatchuKis Shared Data Models (JSON Schema) + +이 문서는 시스템 전반에서 사용되는 데이터 객체의 구조를 상세히 정의합니다. 백엔드 개발 시 이 필드명과 타입을 반드시 준수해야 합니다. + +## 1. Enums & Types (공통 타입) + +### 1.1 MarketType +- **Type**: `string` +- **Values**: `"Domestic"` (국내), `"Overseas"` (해외) + +### 1.2 OrderType +- **Type**: `string` +- **Values**: `"BUY"` (매수), `"SELL"` (매도) + +### 1.3 AiProviderType +- **Type**: `string` +- **Values**: `"gemini"`, `"openai-compatible"` + +--- + +## 2. Core Models (핵심 객체) + +### 2.1 StockItem (종목 정보 - 확장됨) +| 필드명 | 타입 | 필수 | 설명 | +| :--- | :--- | :--- | :--- | +| code | string | Y | 종목 코드 (예: "005930", "NVDA") | +| name | string | Y | 종목 명칭 | +| price | number | Y | 현재가 | +| change | number | Y | 전일 대비 등락 금액 | +| changePercent | number | Y | 전일 대비 등락률 (%) | +| market | MarketType | Y | 소속 시장 | +| volume | number | Y | 거래량 | +| tradingValue | number | N | 거래대금 (발굴 페이지용) | +| buyRatio | number | N | 실시간 매수 비율 (0~100) | +| sellRatio | number | N | 실시간 매도 비율 (0~100) | +| foreignNetBuy | number | N | 외국인 순매수량 | +| institutionalNetBuy | number | N | 기관 순매수량 | +| per | number | N | 주가수익비율 | +| pbr | number | N | 주가순자산비율 | +| roe | number | N | 자기자본이익률 | +| marketCap | number | N | 시가총액 | +| dividendYield | number | N | 배당수익률 | +| aiScoreBuy | number | Y | AI 매수 점수 (0~100) | +| aiScoreSell | number | Y | AI 매도 점수 (0~100) | +| themes | string[] | N | 연관 테마 리스트 (JSON Array) | + +### 2.2 ApiSettings (시스템 설정) +| 필드명 | 타입 | 필수 | 설명 | +| :--- | :--- | :--- | :--- | +| appKey | string | Y | KIS API App Key | +| appSecret | string | Y | KIS API Secret Key | +| accountNumber | string | Y | 계좌번호 | +| useTelegram | boolean | Y | 텔레그램 알림 사용 여부 | +| telegramToken | string | N | 텔레그램 봇 토큰 | +| telegramChatId | string | N | 텔레그램 채팅 ID | +| useNaverNews | boolean | Y | 네이버 뉴스 스크랩 여부 | +| naverClientId | string | N | 네이버 Client ID | +| naverClientSecret | string | N | 네이버 Client Secret | +| aiConfigs | AiConfig[] | Y | 등록된 AI 엔진 목록 | +| preferredNewsAiId | string | N | 뉴스 분석용 AI ID (FK) | +| preferredStockAiId | string | N | 종목 분석용 AI ID (FK) | +| preferredNewsJudgementAiId | string | N | 뉴스 판단용 AI ID (FK) | +| preferredAutoBuyAiId | string | N | 자동매수용 AI ID (FK) | +| preferredAutoSellAiId | string | N | 자동매도용 AI ID (FK) | + +### 2.3 AiConfig (AI 프로필) +| 필드명 | 타입 | 필수 | 설명 | +| :--- | :--- | :--- | :--- | +| id | string | Y | 고유 식별자 | +| name | string | Y | 엔진 별칭 | +| providerType | AiProviderType | Y | 제공자 유형 | +| modelName | string | Y | 모델 명칭 (예: "gemini-3-flash-preview") | +| baseUrl | string | N | API 엔드포인트 URL (Ollama 등) | + +### 2.4 AutoTradeConfig (자동매매 로봇) +| 필드명 | 타입 | 필수 | 설명 | +| :--- | :--- | :--- | :--- | +| id | string | Y | 식별자 | +| stockCode | string | N | 종목 코드 (개별 종목 대상인 경우) | +| stockName | string | Y | 대상 명칭 | +| groupId | string | N | 관심 그룹 ID (그룹 대상인 경우) | +| type | string | Y | "ACCUMULATION" (적립식) \| "TRAILING_STOP" | +| quantity | number | Y | 실행 시 주문 수량 | +| frequency | string | Y | "DAILY" \| "WEEKLY" \| "MONTHLY" | +| specificDay | number | N | 실행일 (0-6 요일 또는 1-31 날짜) | +| executionTime | string | Y | 실행 시각 ("HH:mm") | +| trailingPercent | number | N | TS 사용 시 퍼센트 | +| active | boolean | Y | 활성화 상태 | +| market | MarketType | Y | 마켓 구분 | + +### 2.5 ReservedOrder (감시 주문) +| 필드명 | 타입 | 필수 | 설명 | +| :--- | :--- | :--- | :--- | +| id | string | Y | 식별자 | +| stockCode | string | Y | 종목 코드 | +| stockName | string | Y | 종목 명칭 | +| type | OrderType | Y | 매수/매도 구분 | +| quantity | number | Y | 주문 수량 | +| monitoringType | string | Y | "PRICE_TRIGGER" \| "TRAILING_STOP" | +| triggerPrice | number | Y | 감시 기준 가격 | +| trailingType | string | Y | "PERCENT" \| "AMOUNT" | +| trailingValue | number | Y | 간격 값 | +| status | string | Y | "WAITING" \| "MONITORING" \| "TRIGGERED" \| "CANCELLED" | +| createdAt | string | Y | 생성 일시 (ISO 8601) | +| expiryDate | string | Y | 만료 일시 (ISO 8601) | + +### 2.6 WatchlistGroup (관심 종목 그룹) +| 필드명 | 타입 | 필수 | 설명 | +| :--- | :--- | :--- | :--- | +| id | string | Y | 식별자 | +| name | string | Y | 그룹명 | +| codes | string[] | Y | 포함된 종목 코드 배열 | +| market | MarketType | Y | 마켓 구분 | + +## 3. Discovery Models (종목 발굴용 전용 모델) + +### 3.1 RankingCategory +- **Type**: `string` +- **Values**: `"VOLUME"` (거래량), `"VALUE"` (거래대금), `"GAIN"` (상승), `"LOSS"` (하락), `"FOREIGN_BUY"` (외인매수), `"INSTITUTION_BUY"` (기관매수) + +### 3.2 DiscoveryRankingResponse +| 필드명 | 타입 | 필수 | 설명 | +| :--- | :--- | :--- | :--- | +| category | RankingCategory | Y | 랭킹 카테고리 | +| updatedAt | string | Y | 랭킹 갱신 시각 (ISO 8601) | +| items | StockItem[] | Y | 순위별 종목 리스트 | diff --git a/backend/tables.md b/backend/tables.md new file mode 100644 index 0000000..6b7dee4 --- /dev/null +++ b/backend/tables.md @@ -0,0 +1,71 @@ +# BatchuKis Database Schema Definition + +이 문서는 데이터베이스 설계를 위한 테이블 명세서입니다. 모든 컬럼명은 [models.md](./models.md)의 필드명과 일치해야 합니다. + +## 1. api_settings (사용자 및 API 설정) +- 단일 사용자 환경이므로 `id=1` 레코드만 사용. +- `aiConfigs`는 별도의 `ai_configs` 테이블과 Join하여 처리. + +## 2. ai_configs (AI 엔진 프로필) +- `id`: TEXT (PK) +- `name`: TEXT +- `providerType`: TEXT (gemini | openai-compatible) +- `modelName`: TEXT +- `baseUrl`: TEXT (Nullable) + +## 3. holdings (현재 보유 종목) +- `code`: TEXT (PK) +- `name`: TEXT +- `avgPrice`: REAL +- `quantity`: INTEGER +- `market`: TEXT (Domestic | Overseas) + +## 4. auto_trade_configs (자동매매 로봇) +- `id`: TEXT (PK) +- `stockCode`: TEXT (Nullable) +- `stockName`: TEXT +- `groupId`: TEXT (Nullable) +- `type`: TEXT (ACCUMULATION | TRAILING_STOP) +- `quantity`: INTEGER +- `frequency`: TEXT (DAILY | WEEKLY | MONTHLY) +- `specificDay`: INTEGER +- `executionTime`: TEXT +- `trailingPercent`: REAL +- `active`: BOOLEAN +- `market`: TEXT + +## 5. reserved_orders (실시간 감시/예약 주문) +- `id`: TEXT (PK) +- `stockCode`: TEXT +- `stockName`: TEXT +- `type`: TEXT (BUY | SELL) +- `quantity`: INTEGER +- `monitoringType`: TEXT +- `triggerPrice`: REAL +- `trailingType`: TEXT +- `trailingValue`: REAL +- `status`: TEXT +- `createdAt`: DATETIME +- `expiryDate`: DATETIME + +## 6. watchlist_groups (관심 종목 그룹) +- `id`: TEXT (PK) +- `name`: TEXT +- `codes`: TEXT (JSON String Array - e.g. '["005930", "NVDA"]') +- `market`: TEXT + +## 7. [NEW] discovery_rank_cache (발굴 랭킹 캐시) +- `category`: TEXT (PK - VOLUME, VALUE 등) +- `market`: TEXT (PK) +- `rank_json`: TEXT (JSON String - StockItem 리스트 보관) +- `updated_at`: DATETIME +- **용도**: 랭킹 연산은 리소스가 많이 들므로 1~5분 단위로 배치 처리 후 캐시된 데이터를 API로 제공. + +## 8. [NEW] stock_stats (종목별 확장 통계) +- `code`: TEXT (PK) +- `tradingValue`: REAL +- `buyRatio`: INTEGER +- `sellRatio`: INTEGER +- `foreignNetBuy`: INTEGER +- `institutionalNetBuy`: INTEGER +- **용도**: `StockItem` 테이블을 직접 확장하거나 별도 통계 테이블로 관리하여 발굴 데이터 조회 성능 최적화. diff --git a/components/CommonUI.tsx b/components/CommonUI.tsx new file mode 100644 index 0000000..5197308 --- /dev/null +++ b/components/CommonUI.tsx @@ -0,0 +1,103 @@ + +import React from 'react'; + +// --- StatCard: 대시보드 및 자산 요약용 --- +interface StatCardProps { + title: string; + value: string; + change?: string; + isUp?: boolean; + icon: React.ReactNode; +} + +export const StatCard: React.FC = ({ title, value, change, isUp, icon }) => ( +
+
+

{title}

+

{value}

+ {change && ( +

+ {change} +

+ )} +
+
+ {icon} +
+
+); + +// --- FilterChip: 시장/정렬/카테고리 필터용 --- +interface FilterChipProps { + active: boolean; + onClick: () => void; + label: string; +} + +export const FilterChip: React.FC = ({ active, onClick, label }) => ( + +); + +// --- TabButton: 대분류 섹션 전환용 --- +interface TabButtonProps { + active: boolean; + onClick: () => void; + icon: React.ReactNode; + label: string; +} + +export const TabButton: React.FC = ({ active, onClick, icon, label }) => ( + +); + +// --- InputGroup: 설정창 및 폼 입력용 --- +interface InputGroupProps { + label: string; + value: string; + onChange: (v: string) => void; + placeholder: string; + type?: string; + icon?: React.ReactNode; +} + +export const InputGroup: React.FC = ({ label, value, onChange, placeholder, type = "text", icon }) => ( +
+ + onChange(e.target.value)} + className="w-full p-4 bg-slate-50 border border-slate-200 rounded-[1.2rem] focus:border-blue-500 focus:bg-white outline-none transition-all font-bold text-slate-800 placeholder:text-slate-300 shadow-sm" + placeholder={placeholder} + /> +
+); + +// --- ToggleButton: 활성화/비활성 스위치 --- +interface ToggleButtonProps { + active: boolean; + onClick: () => void; +} + +export const ToggleButton: React.FC = ({ active, onClick }) => ( + +); diff --git a/components/InsightBubble.tsx b/components/InsightBubble.tsx new file mode 100644 index 0000000..2ce5cb5 --- /dev/null +++ b/components/InsightBubble.tsx @@ -0,0 +1,21 @@ + +import React from 'react'; + +interface InsightBubbleProps { + user: string; + time: string; + content: string; +} + +export const InsightBubble: React.FC = ({ user, time, content }) => ( +
+
+
+
+ {user} +
+ {time} +
+

{content}

+
+); diff --git a/components/StockDetailModal.tsx b/components/StockDetailModal.tsx new file mode 100644 index 0000000..2ecec8f --- /dev/null +++ b/components/StockDetailModal.tsx @@ -0,0 +1,183 @@ + +import React, { useState, useEffect, useMemo } from 'react'; +import { + X, TrendingUp, BarChart4, PieChart, Info, Building2, Layers, RotateCw +} from 'lucide-react'; +import { StockItem, MarketType, StockTick } from '../types'; +import { DbService } from '../services/dbService'; + +interface StockDetailModalProps { + stock: StockItem; + onClose: () => void; +} + +const StockDetailModal: React.FC = ({ stock, onClose }) => { + const [ticks, setTicks] = useState([]); + const dbService = useMemo(() => new DbService(), []); + + useEffect(() => { + const refreshTicks = async () => { + const history = await dbService.getStockTicks(stock.code); + setTicks(history); + }; + + refreshTicks(); + const interval = setInterval(refreshTicks, 5000); + return () => clearInterval(interval); + }, [stock.code, dbService]); + + return ( +
+
+ {/* Header */} +
+
+
+ +
+
+
+

{stock.name}

+ + {stock.market} + +
+

{stock.code}

+
+
+
+
+

+ {stock.market === MarketType.DOMESTIC ? stock.price.toLocaleString() : `$${stock.price}`} +

+

= 0 ? 'text-rose-500' : 'text-blue-600'} flex items-center justify-end gap-2`}> + {stock.changePercent >= 0 ? : } + {Math.abs(stock.changePercent)}% +

+
+ +
+
+ + {/* Body */} +
+ {/* Left: Chart */} +
+
+

+ 시계열 시세 차트 (실시간) +

+ + 데이터 수집 중 + +
+
+ {ticks.length > 0 ? ( +
+ +
+ ) : ( +
+ +

분 단위 시세 데이터 수집 중...

+
+ )} +
+
+ + {/* Right: Info */} +
+
+

+ 연관 테마 분석 +

+
+ {stock.themes?.map(theme => ( + + #{theme} + + ))} +
+
+ +
+

+ 기업 분석 핵심 지표 +

+
+ + + + +
+
+ +
+

AI Strategy Insight

+
+ + +
+
+
+
+
+
+ ); +}; + +// --- Helpers --- +const SimpleLineChart: React.FC<{ data: StockTick[] }> = ({ data }) => { + const prices = data.map(d => d.price); + const min = Math.min(...prices) * 0.9995; + const max = Math.max(...prices) * 1.0005; + const range = max - min || 1; + const width = 800; + const height = 400; + + if (data.length <= 1) return
데이터 수집 부족
; + + const points = data.map((d, i) => { + const x = (i / (data.length - 1)) * width; + const y = height - ((d.price - min) / range) * height; + return `${x},${y}`; + }).join(' '); + + return ( + + + + + + ); +}; + +const DataCard: React.FC<{ label: string, value?: number, suffix?: string, desc: string }> = ({ label, value, suffix = '', desc }) => ( +
+
+ {label} + +
+
+ {value !== undefined ? value.toLocaleString() : 'N/A'} + {suffix} +
+

{desc}

+
+); + +const ScoreBar: React.FC<{ label: string, score: number, color: 'rose' | 'blue' }> = ({ label, score, color }) => ( +
+
+ {label} + {score}pt +
+
+
+
+
+); + +export default StockDetailModal; diff --git a/components/StockRow.tsx b/components/StockRow.tsx new file mode 100644 index 0000000..ca68f0c --- /dev/null +++ b/components/StockRow.tsx @@ -0,0 +1,90 @@ + +import React from 'react'; +import { Zap, ShoppingCart, Star } from 'lucide-react'; +import { StockItem, MarketType, OrderType } from '../types'; + +interface StockRowProps { + stock: StockItem; + rank?: number; + showRank?: boolean; + showActions?: boolean; + showRatioBar?: boolean; + showPL?: { pl: number; percent: number }; + isWatchlisted?: boolean; + onClick?: () => void; + onTrade?: (type: OrderType) => void; + onToggleWatchlist?: () => void; +} + +export const StockRow: React.FC = ({ + stock, rank, showRank, showActions, showRatioBar, showPL, isWatchlisted, onClick, onTrade, onToggleWatchlist +}) => { + return ( + + {showRank && ( + + {rank} + + )} + +
+ {onToggleWatchlist && ( + + )} +
+ {stock.name[0]} +
+
+ {stock.name} + {stock.code} +
+
+ + + {stock.market === MarketType.DOMESTIC ? stock.price.toLocaleString() + '원' : '$' + stock.price} + + + {showPL ? ( +
= 0 ? 'text-rose-500' : 'text-blue-600'}> +

{showPL.pl.toLocaleString()}

+

({showPL.percent.toFixed(2)}%)

+
+ ) : ( + = 0 ? 'text-rose-500' : 'text-blue-600'}`}> + {stock.changePercent >= 0 ? '+' : ''}{stock.changePercent}% + + )} + + {showRatioBar && ( + +
+
+
+
+
+
+ {stock.buyRatio || 50} + {stock.sellRatio || 50} +
+
+ + )} + {showActions && onTrade && ( + +
+ + +
+ + )} + + ); +}; diff --git a/components/TradeModal.tsx b/components/TradeModal.tsx new file mode 100644 index 0000000..9cf3647 --- /dev/null +++ b/components/TradeModal.tsx @@ -0,0 +1,510 @@ + +import React, { useState, useEffect, useMemo } from 'react'; +import { X, RotateCcw, ChevronDown, Zap, Plus, Minus, Calendar, ToggleLeft, ToggleRight, CheckSquare, Square, TrendingUp, TrendingDown, Wallet, Target, ShieldAlert, BadgePercent, Save, PlayCircle, Info, BarChart3, Maximize2, Circle, CheckCircle2 } from 'lucide-react'; +import { StockItem, OrderType, MarketType, ReservedOrder, TradeOrder } from '../types'; +import { DbService, HoldingItem } from '../services/dbService'; + +interface TradeModalProps { + stock: StockItem; + type: OrderType; + onClose: () => void; + onExecute: (order: Omit) => Promise; + onImmediateOrder?: (order: Omit) => Promise; +} + +type StrategyType = 'NONE' | 'PROFIT' | 'LOSS' | 'TRAILING_STOP'; + +const TradeModal: React.FC = ({ stock, type: initialType, onClose, onExecute, onImmediateOrder }) => { + const [orderType, setOrderType] = useState(initialType); + const isBuyMode = orderType === OrderType.BUY; + + const [holding, setHolding] = useState(null); + const [buyingPower, setBuyingPower] = useState(0); + const dbService = useMemo(() => new DbService(), []); + + useEffect(() => { + const fetchData = async () => { + const holdings = await dbService.getHoldings(); + const currentHolding = holdings.find(h => h.code === stock.code) || null; + setHolding(currentHolding); + + const summary = await dbService.getAccountSummary(); + setBuyingPower(summary.buyingPower); + }; + fetchData(); + }, [stock.code, dbService, orderType]); + + const plInfo = useMemo(() => { + if (!holding) return null; + const pl = (stock.price - holding.avgPrice) * holding.quantity; + const percent = ((stock.price - holding.avgPrice) / holding.avgPrice) * 100; + return { pl, percent }; + }, [holding, stock.price]); + + const [monitoringEnabled, setMonitoringEnabled] = useState(false); + const [immediateStart, setImmediateStart] = useState(true); + + // 전략 선택 (라디오 버튼 방식) + const [activeStrategy, setActiveStrategy] = useState(isBuyMode ? 'TRAILING_STOP' : 'NONE'); + + // TS 설정 + const [tsValue, setTsValue] = useState(3); + const [tsUnit, setTsUnit] = useState<'PERCENT' | 'TICK' | 'AMOUNT'>('PERCENT'); + + // TS 내부 감시 시작 조건 (Trigger) + const [triggerEnabled, setTriggerEnabled] = useState(false); + const [triggerType, setTriggerType] = useState<'CURRENT' | 'HIGH' | 'LOW' | 'VOLUME'>('CURRENT'); + const [triggerValue, setTriggerValue] = useState(stock.price); + const [monCondition, setMonCondition] = useState<'ABOVE' | 'BELOW'>(isBuyMode ? 'BELOW' : 'ABOVE'); + + // 자동 조건 결정 로직 + const applyAutoCondition = (type: string, buyMode: boolean) => { + if (!buyMode) { + setMonCondition('ABOVE'); // 매도시에는 현재/고/저/거래량 모두 이상 + } else { + // 매수시 + if (type === 'CURRENT' || type === 'HIGH') { + setMonCondition('BELOW'); // 현재가, 고가는 이하 + } else { + setMonCondition('ABOVE'); // 저가, 거래량은 이상 + } + } + }; + + // 모드 변경 시 전략 및 조건 초기화 + useEffect(() => { + if (isBuyMode) { + setActiveStrategy('TRAILING_STOP'); + } else { + setActiveStrategy('NONE'); + } + applyAutoCondition(triggerType, isBuyMode); + if (triggerType !== 'VOLUME') { + setTriggerValue(stock.price); + } + }, [isBuyMode, stock.price]); + + const [monTargetUnit, setMonTargetUnit] = useState<'PRICE' | 'PERCENT' | 'AMOUNT'>('PRICE'); + const [profitValue, setProfitValue] = useState(stock.price * 1.1); + const [lossValue, setLossValue] = useState(stock.price * 0.9); + + const [expiryDays, setExpiryDays] = useState(1); + const [customExpiryDate, setCustomExpiryDate] = useState( + new Date(Date.now() + 86400000).toISOString().split('T')[0] + ); + + const [priceMethod, setPriceMethod] = useState<'CURRENT' | 'MARKET' | 'HIGH' | 'LOW'>('CURRENT'); + const [tickOffset, setTickOffset] = useState(0); + const [quantityMode, setQuantityMode] = useState<'DIRECT' | 'RATIO'>('DIRECT'); + const [quantity, setQuantity] = useState(1); + const [quantityRatio, setQuantityRatio] = useState(isBuyMode ? 10 : 100); + + const currencySymbol = stock.market === MarketType.DOMESTIC ? '원' : '$'; + + const handleMaxQuantity = () => { + if (quantityMode === 'RATIO') { + setQuantityRatio(100); + } else { + if (isBuyMode) { + const maxQty = Math.floor(buyingPower / stock.price); + setQuantity(maxQty > 0 ? maxQty : 1); + } else if (holding) { + setQuantity(holding.quantity); + } + } + }; + + const handleReset = () => { + setMonitoringEnabled(false); + setImmediateStart(true); + setActiveStrategy(isBuyMode ? 'TRAILING_STOP' : 'NONE'); + setTriggerEnabled(false); + setTriggerType('CURRENT'); + setTriggerValue(stock.price); + applyAutoCondition('CURRENT', isBuyMode); + setPriceMethod('CURRENT'); + setTickOffset(0); + setQuantity(1); + setQuantityRatio(isBuyMode ? 10 : 100); + setExpiryDays(1); + }; + + const finalQuantity = useMemo(() => { + if (quantityMode === 'RATIO') { + if (isBuyMode) { + const targetBudget = buyingPower * (quantityRatio / 100); + return Math.floor(targetBudget / stock.price) || 0; + } else if (holding) { + return Math.floor(holding.quantity * (quantityRatio / 100)); + } + } + return quantity; + }, [isBuyMode, quantityMode, quantity, quantityRatio, holding, buyingPower, stock.price]); + + const handleExecute = () => { + if (monitoringEnabled) { + let finalExpiry = new Date(); + if (expiryDays > 0) finalExpiry.setDate(finalExpiry.getDate() + expiryDays); + else finalExpiry = new Date(customExpiryDate); + + onExecute({ + stockCode: stock.code, + stockName: stock.name, + type: orderType, + quantity: finalQuantity, + monitoringType: activeStrategy === 'TRAILING_STOP' ? 'TRAILING_STOP' : 'PRICE_TRIGGER', + triggerPrice: triggerEnabled ? triggerValue : stock.price, + trailingType: tsUnit === 'PERCENT' ? 'PERCENT' : 'AMOUNT', + trailingValue: tsValue, + market: stock.market, + expiryDate: finalExpiry + }); + } else { + if (onImmediateOrder) { + onImmediateOrder({ + stockCode: stock.code, + stockName: stock.name, + type: orderType, + price: priceMethod === 'MARKET' ? 0 : stock.price, + quantity: finalQuantity + }); + } + } + onClose(); + }; + + const StrategyRadio = ({ type, label, icon: Icon }: { type: StrategyType, label: string, icon: any }) => { + const isSelected = activeStrategy === type; + return ( +
setActiveStrategy(type)} + className={`p-4 rounded-2xl border-2 transition-all cursor-pointer flex items-center justify-between ${isSelected ? 'bg-amber-50/40 border-amber-300 shadow-sm' : 'bg-transparent border-slate-100 opacity-60'}`} + > +
+ {isSelected ? : } + + {label} +
+ + {isSelected && type === 'TRAILING_STOP' && ( +
e.stopPropagation()}> + setTsValue(Number(e.target.value))} /> + +
+ )} + + {isSelected && type === 'PROFIT' && ( +
e.stopPropagation()}> + setProfitValue(Number(e.target.value))} /> + {monTargetUnit === 'PERCENT' ? '%' : currencySymbol} +
+ )} + + {isSelected && type === 'LOSS' && ( +
e.stopPropagation()}> + setLossValue(Number(e.target.value))} /> + {monTargetUnit === 'PERCENT' ? '%' : currencySymbol} +
+ )} +
+ ); + }; + + return ( +
+
+ + {/* 헤더 */} +
+ + +
+ + +
+ + +
+ +
+ {/* 상단 통합 정보 */} +
+
+
{stock.name[0]}
+
+

{stock.name}

+
+ {stock.market === MarketType.DOMESTIC ? 'KRX' : 'NYSE'} + {stock.code} +
+
+
+

= 0 ? 'text-rose-500' : 'text-blue-600'}`}> + {stock.market === MarketType.DOMESTIC ? stock.price.toLocaleString() : `$${stock.price}`} +

+
= 0 ? 'text-rose-500' : 'text-blue-600'}`}> + {stock.changePercent >= 0 ? : } + {Math.abs(stock.changePercent)}% +
+
+
+

거래량

+

{stock.volume.toLocaleString()}

+
+ +5.2% +
+
+
+ +
+
+

사용가능 예수금

+

{buyingPower.toLocaleString()}{currencySymbol}

+
+ {holding ? ( +
+
+

보유수량

+

{holding.quantity} EA

+
+
+

평가수익금

+

= 0 ? 'text-rose-500' : 'text-blue-600'}`}> + {plInfo!.pl > 0 ? '+' : ''}{plInfo!.pl.toLocaleString()} ({plInfo!.percent.toFixed(2)}%) +

+
+
+ ) : ( +
+

미보유 종목

+
+ )} +
+
+ +
+
+
+

+ + 1. 자동 감시 전략 +

+ +
+ +
+ {!isBuyMode ? ( +
+
+ +
+ {['PRICE', 'PERCENT', 'AMOUNT'].map(unit => ( + + ))} +
+
+ +
+ + + +
+
+ ) : ( +
+ +
+ )} + + {/* TS 내부 감시 시작 조건 설정 (TS 선택 시에만 표시) */} + {activeStrategy === 'TRAILING_STOP' && ( +
+
+
setTriggerEnabled(!triggerEnabled)} + className="flex items-center gap-2 cursor-pointer group" + > + {triggerEnabled ? : } + 목표가 설정 +
+
+ + {triggerEnabled && ( +
+ + setTriggerValue(Number(e.target.value))} + /> + +
+ )} + + {!triggerEnabled && ( +

※ 조건 미설정 시 현재 시점부터 즉시 반등 감시를 시작합니다.

+ )} +
+ )} + +
+ 유효기간 +
+ {[1, 5, 30].map(d => ( + + ))} +
+
+
+
+ +
+

+ + 2. 매매 실행 조건 +

+ +
+
+ +
+ {['CURRENT', 'MARKET', 'HIGH', 'LOW'].map((method) => ( + + ))} +
+
+ +
+
+ +
+ +
{tickOffset > 0 ? `+${tickOffset}` : tickOffset}
+ +
+
+ +
+
+ + + {isBuyMode ? `예수금` : `보유수량`} + +
+
+
+ + +
+
+ {quantityMode === 'DIRECT' ? ( +
+ setQuantity(Math.max(1, parseInt(e.target.value) || 0))} /> +
+ +
EA / UNIT
+
+
+ ) : ( +
+ setQuantityRatio(Math.min(100, Math.max(0, parseInt(e.target.value) || 0)))} /> +
+ +
+ PERCENT +
+
+
+ )} +
+
+
+
+ +
+

+ {finalQuantity}주를 {isBuyMode ? '매수' : '매도'}합니다. +

+
+
+
+
+
+ + {/* 푸터 */} +
+ {monitoringEnabled && ( +
+
+ 즉시 실행 + 엔진 자동 가동 +
+ +
+ )} + + +
+
+
+ ); +}; + +export default TradeModal; diff --git a/constants.tsx b/constants.tsx new file mode 100644 index 0000000..44c50e1 --- /dev/null +++ b/constants.tsx @@ -0,0 +1,49 @@ + +import { MarketType, StockItem } from './types'; + +export const COLORS = { + up: 'text-red-500', + down: 'text-blue-500', + neutral: 'text-slate-500', + primary: 'blue-600', + accent: 'indigo-600' +}; + +export const MOCK_STOCKS: StockItem[] = [ + { + code: '005930', name: '삼성전자', price: 73200, change: 800, changePercent: 1.1, market: MarketType.DOMESTIC, volume: 15234000, + per: 15.2, pbr: 1.1, roe: 12.5, marketCap: 4370000, dividendYield: 2.1, + aiScoreBuy: 85, aiScoreSell: 20, + themes: ['반도체', 'AI/인공지능', '스마트폰', 'HBM'] + }, + { + code: '000660', name: 'SK하이닉스', price: 124500, change: -1200, changePercent: -0.96, market: MarketType.DOMESTIC, volume: 2100000, + per: 22.4, pbr: 1.5, roe: 8.2, marketCap: 906000, dividendYield: 1.2, + aiScoreBuy: 65, aiScoreSell: 45, + themes: ['반도체', 'HBM', '엔비디아 관련주'] + }, + { + code: '035420', name: 'NAVER', price: 215000, change: 4500, changePercent: 2.14, market: MarketType.DOMESTIC, volume: 850000, + per: 35.1, pbr: 2.3, roe: 15.8, marketCap: 352000, dividendYield: 0.5, + aiScoreBuy: 72, aiScoreSell: 30, + themes: ['플랫폼', '생성형AI', '광고/커머스'] + }, + { + code: 'AAPL', name: 'Apple Inc.', price: 189.43, change: 1.25, changePercent: 0.66, market: MarketType.OVERSEAS, volume: 45000000, + per: 31.5, pbr: 48.2, roe: 160.1, marketCap: 3020000, dividendYield: 0.5, + aiScoreBuy: 90, aiScoreSell: 15, + themes: ['빅테크', '스마트폰', '자율주행'] + }, + { + code: 'TSLA', name: 'Tesla Inc.', price: 234.12, change: -4.50, changePercent: -1.89, market: MarketType.OVERSEAS, volume: 110000000, + per: 78.2, pbr: 15.1, roe: 22.4, marketCap: 745000, dividendYield: 0, + aiScoreBuy: 40, aiScoreSell: 75, + themes: ['전기차', '자율주행', '에너지저장장치'] + }, + { + code: 'NVDA', name: 'NVIDIA Corp.', price: 485.12, change: 12.30, changePercent: 2.6, market: MarketType.OVERSEAS, volume: 32000000, + per: 65.4, pbr: 35.2, roe: 91.5, marketCap: 1200000, dividendYield: 0.1, + aiScoreBuy: 95, aiScoreSell: 10, + themes: ['반도체', 'AI/인공지능', '데이터센터'] + } +]; diff --git a/index.html b/index.html new file mode 100644 index 0000000..4bab6e3 --- /dev/null +++ b/index.html @@ -0,0 +1,41 @@ + + + + + + + BatchuKis + + + + + + + +
+ + + diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..aaa0c6e --- /dev/null +++ b/index.tsx @@ -0,0 +1,16 @@ + +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error("Could not find root element to mount to"); +} + +const root = ReactDOM.createRoot(rootElement); +root.render( + + + +); diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..93f28db --- /dev/null +++ b/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "BatchuKis", + "description": "A sophisticated stock auto-trading platform supporting domestic and international markets with AI-driven insights, KIS API integration, and Telegram notifications.", + "requestFramePermissions": [] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1651398 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2783 @@ +{ + "name": "batchukis", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "batchukis", + "version": "0.0.0", + "dependencies": { + "@google/genai": "^1.39.0", + "lucide-react": "^0.563.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-router-dom": "^7.13.0" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.39.0.tgz", + "integrity": "sha512-Vz7AQsOdBeiIcxmXIQNy/hzDvyAOE1lSpWA10itUQza7h3aQFF6QSGaQ7o1GYsjMD3XslK4Ee/Ol0eLhRXb7gA==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.563.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", + "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", + "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", + "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2afb341 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "batchukis", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "lucide-react": "^0.563.0", + "react": "^19.2.4", + "react-router-dom": "^7.13.0", + "react-dom": "^19.2.4", + "@google/genai": "^1.39.0" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/pages/AiInsights.tsx b/pages/AiInsights.tsx new file mode 100644 index 0000000..8930065 --- /dev/null +++ b/pages/AiInsights.tsx @@ -0,0 +1,308 @@ + +import React, { useState, useEffect } from 'react'; +import { + BrainCircuit, + Sparkles, + RefreshCw, + Search, + TrendingUp, + MessageSquareQuote, + Zap, + ChevronRight, + Target, + BarChart4, + Cpu, + ShieldCheck, + AlertCircle +} from 'lucide-react'; +import { ApiSettings, StockItem, NewsItem, AiConfig } from '../types'; +import { NaverService } from '../services/naverService'; +import { AiService } from '../services/aiService'; + +interface AiInsightsProps { + stocks: StockItem[]; + settings: ApiSettings; + onUpdateSettings: (settings: ApiSettings) => void; +} + +const AiInsights: React.FC = ({ stocks, settings, onUpdateSettings }) => { + // 뉴스 분석 상태 + const [news, setNews] = useState([]); + const [isNewsLoading, setIsNewsLoading] = useState(false); + const [selectedNewsAiId, setSelectedNewsAiId] = useState(settings.preferredNewsAiId || settings.aiConfigs[0]?.id || 'none'); + const [newsAnalysis, setNewsAnalysis] = useState(null); + const [isNewsAnalyzing, setIsNewsAnalyzing] = useState(false); + + // 종목 분석 상태 + const [search, setSearch] = useState(''); + const [selectedStock, setSelectedStock] = useState(null); + const [selectedStockAiId, setSelectedStockAiId] = useState(settings.preferredStockAiId || settings.aiConfigs[0]?.id || 'none'); + const [stockAnalysis, setStockAnalysis] = useState(null); + const [isStockAnalyzing, setIsStockAnalyzing] = useState(false); + + const naverService = new NaverService(settings); + + useEffect(() => { + fetchInitialNews(); + }, []); + + const fetchInitialNews = async () => { + setIsNewsLoading(true); + if (settings.useNaverNews) { + const result = await naverService.fetchNews("주식 시장 전망"); + setNews(result); + } + setIsNewsLoading(false); + }; + + const handleNewsAnalysis = async () => { + const config = settings.aiConfigs.find(c => c.id === selectedNewsAiId); + if (!config) return; + + setIsNewsAnalyzing(true); + const headlines = news.map(n => n.title); + try { + const result = await AiService.analyzeNewsSentiment(config, headlines); + setNewsAnalysis(result); + // 선호 엔진 업데이트 + onUpdateSettings({ ...settings, preferredNewsAiId: selectedNewsAiId }); + } catch (e) { + setNewsAnalysis("분석 실패: API 설정을 확인해주세요."); + } finally { + setIsNewsAnalyzing(false); + } + }; + + const handleStockAnalysis = async () => { + if (!selectedStock) return; + const config = settings.aiConfigs.find(c => c.id === selectedStockAiId); + if (!config) return; + + setIsStockAnalyzing(true); + const context = `종목명: ${selectedStock.name}, 현재가: ${selectedStock.price}, 등락률: ${selectedStock.changePercent}%, PER: ${selectedStock.per}, PBR: ${selectedStock.pbr}, ROE: ${selectedStock.roe}%`; + const prompt = `주식 전문가로서 다음 데이터를 바탕으로 ${selectedStock.name}에 대한 매수/매도 의견과 향후 일주일간의 대응 전략을 아주 구체적으로 제안해 주세요. 데이터: ${context}`; + + try { + // AiService에 범용 호출 메서드가 없으므로 analyzeNewsSentiment를 차용하거나 직접 fetch 가능 + // 여기서는 규칙에 따라 aiService의 call 메카니즘을 활용하도록 시뮬레이션 + const result = await AiService.analyzeNewsSentiment(config, [prompt]); + setStockAnalysis(result); + onUpdateSettings({ ...settings, preferredStockAiId: selectedStockAiId }); + } catch (e) { + setStockAnalysis("분석 실패: API 연결을 확인하세요."); + } finally { + setIsStockAnalyzing(false); + } + }; + + const filteredStocks = stocks.filter(s => + s.name.includes(search) || s.code.toLowerCase().includes(search.toLowerCase()) + ).slice(0, 5); + + return ( +
+ + {/* Header Banner */} +
+
+
+
+
+
+ +
+

AI INTELLIGENCE CENTER

+
+

+ 사용자가 직접 등록한 멀티 AI 엔진을 활용하여 시장의 거시적 흐름과
+ 개별 종목의 미시적 데이터를 입체적으로 분석합니다. +

+
+
+ + Secure AI Tunneling +
+
+ + {settings.aiConfigs.length} Engines Connected +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+ + {/* News Intelligence Panel */} +
+
+
+
+
+ +
+
+

MARKET SENTIMENT

+

실시간 뉴스 기반 시장 심리 분석

+
+
+
+ + +
+
+ +
+ {newsAnalysis ? ( +
+
+ + Analysis Result +
+
+ {newsAnalysis} +
+
+ ) : ( +
+ {news.slice(0, 3).map((n, i) => ( +
+

{n.title}

+ +
+ ))} + {news.length === 0 && ( +
+ +

뉴스 데이터를 불러오는 중이거나
설정이 꺼져있습니다.

+
+ )} +
+ )} +
+
+
+ + {/* Stock Deep Analysis Panel */} +
+
+
+
+
+ +
+
+

STOCK DEEP DIVE

+

데이터 기반 종목 맞춤형 전략 수립

+
+
+
+ +
+
+ +
+ + setSearch(e.target.value)} + /> + {search && filteredStocks.length > 0 && ( +
+ {filteredStocks.map(s => ( +
{ setSelectedStock(s); setSearch(''); setStockAnalysis(null); }} + className="p-6 hover:bg-purple-50 cursor-pointer flex justify-between items-center border-b last:border-none border-slate-50 group" + > +
+
{s.code.substring(0,2)}
+
+

{s.name}

+

{s.code}

+
+
+ +
+ ))} +
+ )} +
+ +
+ {selectedStock ? ( +
+
+
+
+ +
+
+

{selectedStock.name}

+

{selectedStock.code}

+
+
+ +
+ + {stockAnalysis && ( +
+
+ +
+
+ {stockAnalysis} +
+
+ )} +
+ ) : ( +
+ +

분석을 위해 상단에서 종목을 검색하세요

+
+ )} +
+
+
+
+
+ ); +}; + +export default AiInsights; diff --git a/pages/AutoTrading.tsx b/pages/AutoTrading.tsx new file mode 100644 index 0000000..410a123 --- /dev/null +++ b/pages/AutoTrading.tsx @@ -0,0 +1,289 @@ + +import React, { useState } from 'react'; +import { Cpu, Plus, Calendar, Zap, Trash2, Activity, Clock, LayoutGrid, Layers, X } from 'lucide-react'; +import { StockItem, AutoTradeConfig, MarketType, WatchlistGroup } from '../types'; + +interface AutoTradingProps { + marketMode: MarketType; + stocks: StockItem[]; + configs: AutoTradeConfig[]; + groups: WatchlistGroup[]; + onAddConfig: (config: Omit) => void; + onToggleConfig: (id: string) => void; + onDeleteConfig: (id: string) => void; +} + +const AutoTrading: React.FC = ({ marketMode, stocks, configs, groups, onAddConfig, onToggleConfig, onDeleteConfig }) => { + const [showAddModal, setShowAddModal] = useState(false); + const [targetType, setTargetType] = useState<'SINGLE' | 'GROUP'>('SINGLE'); + const [newConfig, setNewConfig] = useState>({ + type: 'ACCUMULATION', + frequency: 'DAILY', + quantity: 1, + executionTime: '09:00', + specificDay: 1 + }); + + const handleAdd = () => { + if (newConfig.type) { + if (targetType === 'SINGLE' && !newConfig.stockCode) return; + if (targetType === 'GROUP' && !newConfig.groupId) return; + + const stockName = targetType === 'SINGLE' + ? stocks.find(s => s.code === newConfig.stockCode)?.name || '알 수 없음' + : groups.find(g => g.id === newConfig.groupId)?.name || '알 수 없는 그룹'; + + onAddConfig({ + stockCode: targetType === 'SINGLE' ? newConfig.stockCode : undefined, + groupId: targetType === 'GROUP' ? newConfig.groupId : undefined, + stockName: stockName, + type: newConfig.type as 'ACCUMULATION' | 'TRAILING_STOP', + quantity: newConfig.quantity || 1, + frequency: newConfig.frequency as 'DAILY' | 'WEEKLY' | 'MONTHLY', + specificDay: newConfig.specificDay, + executionTime: newConfig.executionTime || '09:00', + trailingPercent: newConfig.trailingPercent, + market: marketMode + }); + setShowAddModal(false); + } + }; + + const getDayLabel = (config: AutoTradeConfig) => { + if (config.frequency === 'DAILY') return '매일'; + if (config.frequency === 'WEEKLY') { + const days = ['일', '월', '화', '수', '목', '금', '토']; + return `매주 ${days[config.specificDay || 0]}요일`; + } + if (config.frequency === 'MONTHLY') return `매월 ${config.specificDay}일`; + return ''; + }; + + const filteredStocks = stocks.filter(s => s.market === marketMode); + const filteredGroups = groups.filter(g => g.codes.some(code => stocks.find(s => s.code === code)?.market === marketMode)); + + return ( +
+
+
+

+ {marketMode === MarketType.DOMESTIC ? '국내' : '해외'} 매매 엔진 +

+

+ + c.active && c.market === marketMode).length > 0 ? 'bg-emerald-400' : 'bg-slate-300'}`}> + c.active && c.market === marketMode).length > 0 ? 'bg-emerald-500' : 'bg-slate-400'}`}> + + 현재 {configs.filter(c => c.active && c.market === marketMode).length}개의 로봇 에이전트 활성화됨 +

+
+ +
+ +
+ {configs.filter(c => c.market === marketMode).map(config => ( +
+
+ +
+
+
+ {config.groupId ? : } +
+
+

{config.stockName}

+

{config.groupId ? 'GROUP AGENT' : config.stockCode}

+
+
+ + {/* 활성화 토글 스위치 */} + +
+ +
+
+
+ 오퍼레이션 + {config.groupId ? '그룹 일괄' : '개별 자산'} +
+
+ 코어 전략 + + {config.type === 'ACCUMULATION' ? '적립식 매수' : 'TS 자동매매'} + +
+
+ 스케줄링 + {getDayLabel(config)} +
+
+ 실행정보 + {config.executionTime} / {config.quantity}주 +
+
+ +
+
+ + {config.active ? '에이전트 운용 중' : '일시 중단됨'} +
+ +
+
+
+ ))} +
+ + {/* 전략 추가 모달 (기존 동일) */} + {showAddModal && ( +
+
+
+

+ 로봇 전략 설계 +

+ +
+ +
+
+ +
+ + +
+
+ +
+
+ + {targetType === 'SINGLE' ? ( + + ) : ( + + )} +
+
+ + setNewConfig({...newConfig, quantity: parseInt(e.target.value)})} + /> +
+
+ +
+ +
+ {[ + { val: 'DAILY', label: '매일' }, + { val: 'WEEKLY', label: '매주' }, + { val: 'MONTHLY', label: '매월' } + ].map(freq => ( + + ))} +
+
+ +
+ {newConfig.frequency !== 'DAILY' && ( +
+ + +
+ )} +
+ + setNewConfig({...newConfig, executionTime: e.target.value})} + /> +
+
+ +
+ + +
+
+
+
+ )} +
+ ); +}; + +export default AutoTrading; diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx new file mode 100644 index 0000000..3ec9ff0 --- /dev/null +++ b/pages/Dashboard.tsx @@ -0,0 +1,195 @@ + +import React, { useState, useEffect, useMemo } from 'react'; +import { + TrendingUp, Wallet, Activity, Briefcase, PieChart, Database, Zap, Timer, Trash2 +} from 'lucide-react'; +import { StockItem, TradeOrder, MarketType, WatchlistGroup, OrderType, AutoTradeConfig, ReservedOrder } from '../types'; +import { DbService, HoldingItem } from '../services/dbService'; +import StockDetailModal from '../components/StockDetailModal'; +import TradeModal from '../components/TradeModal'; +import { StatCard } from '../components/CommonUI'; +import { StockRow } from '../components/StockRow'; + +interface DashboardProps { + marketMode: MarketType; + watchlistGroups: WatchlistGroup[]; + stocks: StockItem[]; + orders: TradeOrder[]; + reservedOrders: ReservedOrder[]; + autoTrades: AutoTradeConfig[]; + onManualOrder: (order: Omit) => Promise; + onAddReservedOrder: (order: Omit) => Promise; + onDeleteReservedOrder: (id: string) => Promise; + onRefreshHoldings: () => void; +} + +const Dashboard: React.FC = ({ + marketMode, watchlistGroups, stocks, reservedOrders, onAddReservedOrder, onDeleteReservedOrder, onRefreshHoldings, orders +}) => { + const [holdings, setHoldings] = useState([]); + const [summary, setSummary] = useState({ totalAssets: 0, buyingPower: 0 }); + + const [activeGroupId, setActiveGroupId] = useState(null); + const [detailStock, setDetailStock] = useState(null); + const [tradeContext, setTradeContext] = useState<{ stock: StockItem, type: OrderType } | null>(null); + + const dbService = useMemo(() => new DbService(), []); + + useEffect(() => { + loadData(); + }, [orders, marketMode, reservedOrders]); + + const activeMarketGroups = useMemo(() => { + return watchlistGroups.filter(group => group.market === marketMode); + }, [watchlistGroups, marketMode]); + + useEffect(() => { + if (activeMarketGroups.length > 0) { + if (!activeGroupId || !activeMarketGroups.find(g => g.id === activeGroupId)) { + setActiveGroupId(activeMarketGroups[0].id); + } + } else { + setActiveGroupId(null); + } + }, [marketMode, activeMarketGroups, activeGroupId]); + + const loadData = async () => { + const allHoldings = await dbService.getHoldings(); + const filteredHoldings = allHoldings.filter(h => h.market === marketMode); + const accSummary = await dbService.getAccountSummary(); + setHoldings(filteredHoldings); + setSummary(accSummary); + }; + + const calculatePL = (holding: HoldingItem) => { + const currentStock = stocks.find(s => s.code === holding.code); + const currentPrice = currentStock ? currentStock.price : holding.avgPrice; + const pl = (currentPrice - holding.avgPrice) * holding.quantity; + const plPercent = ((currentPrice - holding.avgPrice) / holding.avgPrice) * 100; + const value = currentPrice * holding.quantity; + return { pl, plPercent, currentPrice, value, stock: currentStock }; + }; + + const totalLiquidationSummary = holdings.reduce((acc, h) => { + const plData = calculatePL(h); + return { + totalValue: acc.totalValue + plData.value, + totalPL: acc.totalPL + plData.pl, + totalCost: acc.totalCost + (h.avgPrice * h.quantity) + }; + }, { totalValue: 0, totalPL: 0, totalCost: 0 }); + + const aggregatePLPercent = totalLiquidationSummary.totalCost > 0 + ? (totalLiquidationSummary.totalPL / totalLiquidationSummary.totalCost) * 100 + : 0; + + const selectedGroup = activeMarketGroups.find(g => g.id === activeGroupId) || activeMarketGroups[0]; + + return ( +
+
+ } /> + = 0} icon={= 0 ? "text-emerald-500" : "text-rose-500"} />} /> + } /> + } /> +
+ +
+
+
+

+ 관심 그룹 +

+
+
+ {activeMarketGroups.map(group => ( + + ))} +
+
+ + + {selectedGroup?.codes.map(code => stocks.find(s => s.code === code)).filter(s => s?.market === marketMode).map(stock => { + if (!stock) return null; + return ( + setTradeContext({ stock, type })} + onClick={() => setDetailStock(stock)} + /> + ); + })} + +
+
+
+ +
+
+
+

+ 보유 포트폴리오 +

+
+
+ + + + + + + + + + + {holdings.map(holding => { + const { pl, plPercent, stock } = calculatePL(holding); + if (!stock) return null; + return ( + setTradeContext({ stock, type })} + onClick={() => setDetailStock(stock)} + /> + ); + })} + +
종목현재가수익금 (%)주문
+
+
+ +
+

+ 실시간 감시 목록 +

+
+ {reservedOrders.filter(o => o.market === marketMode).map(order => ( +
+
+
+

{order.stockName}

+
+ +
+ ))} +
+
+
+
+ + {detailStock && setDetailStock(null)} />} + {tradeContext && setTradeContext(null)} onExecute={onAddReservedOrder} />} +
+ ); +}; + +export default Dashboard; diff --git a/pages/Discovery.tsx b/pages/Discovery.tsx new file mode 100644 index 0000000..cd19b33 --- /dev/null +++ b/pages/Discovery.tsx @@ -0,0 +1,242 @@ + +import React, { useState, useMemo, useEffect } from 'react'; +import { + Trophy, Flame, Users, Search, Info, MessageSquare, Sparkles, Zap, + Save, EyeOff, Eye, RefreshCw, FileText, StickyNote, History as HistoryIcon, + ArrowUpRight, ArrowDownRight +} from 'lucide-react'; +import { StockItem, MarketType, OrderType, ApiSettings, TradeOrder } from '../types'; +import StockDetailModal from '../components/StockDetailModal'; +import TradeModal from '../components/TradeModal'; +import { FilterChip, TabButton } from '../components/CommonUI'; +import { StockRow } from '../components/StockRow'; +import { AiService } from '../services/aiService'; + +interface DiscoveryProps { + stocks: StockItem[]; + orders: TradeOrder[]; + onUpdateStock: (code: string, updates: Partial) => void; + settings: ApiSettings; +} + +const Discovery: React.FC = ({ stocks, orders, onUpdateStock, settings }) => { + const [activeTab, setActiveTab] = useState<'realtime' | 'category' | 'investor'>('realtime'); + const [marketFilter, setMarketFilter] = useState<'all' | 'domestic' | 'overseas'>('all'); + const [sortType, setSortType] = useState<'value' | 'volume' | 'gain' | 'loss'>('value'); + const [selectedStockCode, setSelectedStockCode] = useState(stocks[0]?.code || ''); + + const [detailStock, setDetailStock] = useState(null); + const [tradeContext, setTradeContext] = useState<{ stock: StockItem, type: OrderType } | null>(null); + + const selectedStock = useMemo(() => { + return stocks.find(s => s.code === selectedStockCode) || null; + }, [stocks, selectedStockCode]); + + const stockOrders = useMemo(() => { + return orders.filter(o => o.stockCode === selectedStockCode).sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + }, [orders, selectedStockCode]); + + const [memo, setMemo] = useState(''); + const [isAnalyzing, setIsAnalyzing] = useState(false); + + useEffect(() => { + if (selectedStock) { + setMemo(selectedStock.memo || ''); + } + }, [selectedStockCode, stocks]); + + const enrichedStocks = useMemo(() => { + return stocks.filter(s => !s.isHidden).map((s) => ({ + ...s, + tradingValue: (s.volume * s.price) / (s.market === MarketType.DOMESTIC ? 100000000 : 1000000), + buyRatio: 50 + Math.floor(Math.random() * 45), + sellRatio: 100 - (50 + Math.floor(Math.random() * 45)) + })).filter(s => { + if (marketFilter === 'domestic') return s.market === MarketType.DOMESTIC; + if (marketFilter === 'overseas') return s.market === MarketType.OVERSEAS; + return true; + }).sort((a, b) => { + if (sortType === 'value') return (b.tradingValue || 0) - (a.tradingValue || 0); + if (sortType === 'volume') return b.volume - a.volume; + if (sortType === 'gain') return b.changePercent - a.changePercent; + if (sortType === 'loss') return a.changePercent - b.changePercent; + return 0; + }); + }, [stocks, marketFilter, sortType]); + + const handleSaveMemo = () => { + if (selectedStock) { + onUpdateStock(selectedStock.code, { memo }); + alert('메모가 저장되었습니다.'); + } + }; + + const handleToggleHide = () => { + if (selectedStock) { + const newHidden = !selectedStock.isHidden; + onUpdateStock(selectedStock.code, { isHidden: newHidden }); + if (newHidden) { + alert(`${selectedStock.name} 종목이 숨김 처리되었습니다.`); + const next = enrichedStocks.find(s => s.code !== selectedStock.code); + if (next) setSelectedStockCode(next.code); + } + } + }; + + const handleGenerateAnalysis = async () => { + if (!selectedStock) return; + const config = settings.aiConfigs.find(c => c.id === settings.preferredStockAiId) || settings.aiConfigs[0]; + if (!config) return; + + setIsAnalyzing(true); + try { + const prompt = `주식 전문가로서 ${selectedStock.name}(${selectedStock.code}) 리포트를 작성해줘.`; + const result = await AiService.analyzeNewsSentiment(config, [prompt]); + onUpdateStock(selectedStock.code, { aiAnalysis: result }); + } catch (e) { + alert('AI 분석 생성 실패'); + } finally { + setIsAnalyzing(false); + } + }; + + return ( +
+
+
+ setActiveTab('realtime')} icon={} label="실시간 차트" /> + setActiveTab('category')} icon={} label="인기 테마" /> + setActiveTab('investor')} icon={} label="투자자 동향" /> +
+ +
+
+ setMarketFilter('all')} label="전체" /> + setMarketFilter('domestic')} label="국내" /> + setMarketFilter('overseas')} label="해외" /> +
+
+ setSortType('value')} label="거래대금" /> + setSortType('gain')} label="급상승" /> +
+
+ +
+ + + + + + + + + + + + {enrichedStocks.map((stock, idx) => ( + setSelectedStockCode(stock.code)} + /> + ))} + +
순위종목현재가등락률매수/매도 비율
+
+
+ +
+ {selectedStock && ( +
+
+
+
{selectedStock.name[0]}
+
+

setDetailStock(selectedStock)}>{selectedStock.name}

+

{selectedStock.code}

+
+
+ +
+ +
+ + +
+ +
+
+
+ 종목 메모 +
+ +
+