This commit is contained in:
2025-08-30 20:21:14 +09:00
parent d34f6b803f
commit d77f0542f5
8 changed files with 775 additions and 0 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules
.idea
logs

12
config.json Normal file
View File

@@ -0,0 +1,12 @@
{
"cctxSymbol": "ETH/USDT:USDT",
"symbol": "ETHUSDT",
"startBalance": 10000,
"orderSizeQuote": 100,
"interval": 60,
"ohlcvInterval": "1h",
"leverage": 5,
"feeTaker": 0.055,
"pendingCancelTime": 1200000,
"logPath": "./tradeLog.json"
}

239
index.js Normal file
View File

@@ -0,0 +1,239 @@
import ccxt from 'ccxt';
import WebSocket from 'ws';
import fs from 'fs';
import { aiSignal } from './strategy.js';
import logger from './logger.js';
const cfg = JSON.parse(fs.readFileSync('./config.json', 'utf8'));
const ex = new ccxt.bybit({
apiKey: '',
secret: '',
options: { defaultType: 'swap' }
});
let bars = [];
let balance = cfg.startBalance;
let position = null;
let trades = [];
let curPrice = null;
async function bootstrap() {
try {
const ohlcv = await ex.fetchOHLCV(cfg.cctxSymbol, cfg.ohlcvInterval, undefined, 200);
bars = ohlcv.map(c => ({
t: c[0],
o: c[1],
h: c[2],
l: c[3],
c: c[4],
v: c[5]
}));
logger.info(`Bootstrapped ${bars.length} bars | Balance: ${balance}`);
} catch (error) {
logger.critical('초기화 중 심각한 오류 발생', error, {
stage: 'bootstrap'
});
process.exit(1);
}
}
function initWs() {
try {
const ws = new WebSocket(`wss://stream.bybit.com/v5/public/linear`);
ws.on('open', () => {
logger.info('WebSocket 연결 성공');
ws.send(JSON.stringify({
op: 'subscribe',
args: [
`kline.${cfg.interval}.${cfg.symbol}`,
`tickers.${cfg.symbol}`,
]
}));
});
ws.on('message', (msg) => {
try {
const m = JSON.parse(msg);
if (m.op !== 'subscribe') {
if (m.topic?.startsWith('tickers')) {
const t = m.data;
if (t) {
if(t.lastPrice) {
curPrice = +t.lastPrice;
// logger.debug('현재 가격 업데이트', { curPrice });
}
}
}
if (m.topic?.startsWith('kline')) {
const k = m.data[0];
if (!k || !k.confirm) return;
const bar = {
t: +k.start,
o: +k.open,
h: +k.high,
l: +k.low,
c: +k.close,
v: +k.volume
};
bars.push(bar);
if (bars.length > 300) bars.shift();
logger.debug('새로운 분봉 수신', {
timestamp: new Date(bar.t).toISOString(),
open: bar.o,
high: bar.h,
low: bar.l,
close: bar.c,
volume: bar.v
});
onNewBar(bar);
}
}
} catch (e) {
logger.error('WebSocket 메시지 처리 중 오류', e, {
rawMessage: msg.toString().substr(0, 100) + '...'
});
}
});
ws.on('error', (error) => {
logger.error('WebSocket 연결 오류', error);
});
ws.on('close', (code, reason) => {
logger.warn('WebSocket 연결 종료', {
code,
reason: reason.toString()
});
// 재연결 시도 로직 추가 가능
});
} catch (error) {
logger.critical('WebSocket 초기화 실패', error);
setTimeout(initWs, 5000); // 5초 후 재시도
}
}
async function onNewBar(bar) {
logger.debug(`새로운 ${cfg.interval}분 봉 수신`, {
timestamp: new Date(bar.t).toISOString(),
open: bar.o,
high: bar.h,
low: bar.l,
close: bar.c,
volume: bar.v
});
if (position) {
logger.debug(`진입 보류: 이미 포지션 존재`, {
position: {
side: position.side,
entry: position.entry,
qty: position.qty
}
});
return;
}
const sig = await aiSignal(bars);
if(sig.side !== 'HOLD') enterMarket(sig);
}
function enterMarket(sig) {
const qty = ((balance * cfg.leverage) / curPrice).toFixed(4);
const notional = curPrice * +qty;
const fee = notional * cfg.feeTaker / 100;
balance -= fee;
position = {
id: `m${Date.now()}`,
side: sig.side,
entry: curPrice,
qty: +qty,
sl: sig.sl,
tp: sig.tp,
openTime: Date.now(),
fee,
initialBalance: balance + fee
};
logger.info(`[FILLED] ${sig.side} 시장가 @${curPrice} qty=${qty}`, { position });
}
function checkExit() {
if (!position || curPrice === null) return;
const { side, entry, qty, sl, tp } = position;
let exit = null;
if (side === 'LONG') {
if (curPrice >= tp) exit = 'TP';
if (curPrice <= sl) exit = 'SL';
} else {
if (curPrice <= tp) exit = 'TP';
if (curPrice >= sl) exit = 'SL';
}
if (!exit) return;
const pnl = (side === 'LONG' ? curPrice - entry : entry - curPrice) * qty;
const fee = qty * curPrice * (cfg.feeTaker / 100);
const netPnl = pnl - fee;
balance += netPnl;
// 음수 잔고 방지를 위한 체크
if (balance < 0) {
logger.critical('음수 잔고 발생! 시스템 중지 필요', {
balanceBefore: balance - netPnl,
pnl,
fee,
netPnl,
position
});
balance = 0; // 안전장치
}
const tradeRecord = {
...position,
closed: true,
exitPrice: curPrice,
exitReason: exit,
pnl: netPnl,
closeTime: Date.now(),
finalBalance: balance,
leverage: cfg.leverage
};
trades.push(tradeRecord);
fs.writeFileSync(cfg.logPath, JSON.stringify(trades, null, 2));
logger.info(`[EXIT] ${exit} @${curPrice.toFixed(4)} | PnL=${netPnl.toFixed(2)} | Balance=${balance.toFixed(2)}`, {
trade: tradeRecord,
market: { currentPrice: curPrice }
});
// 종료된 포지션 로깅
logger.logPosition(tradeRecord, `포지션 종료 (${exit})`);
position = null;
}
// 시스템 시작
try {
await bootstrap();
initWs();
setInterval(checkExit, 1000);
} catch (error) {
logger.critical('시스템 시작 중 심각한 오류', error);
process.exit(1);
}

180
logger.js Normal file
View File

@@ -0,0 +1,180 @@
import fs from 'fs';
import path from 'path';
// 로그 디렉토리 생성
const logDir = './logs';
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
// 로그 레벨 정의
const logLevels = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
CRITICAL: 4
};
const logLevelNames = Object.keys(logLevels);
const currentLogLevel = logLevels.info; // 설정에 따라 변경 가능
// 타임스탬프 생성
const getTimestamp = () => new Date().toISOString();
// 호출자 정보 추출 (ESM 호환)
const getCallerInfo = () => {
const err = new Error();
const stack = err.stack.split('\n');
// 스택에서 호출자 위치 찾기
let callerIndex = 2; // 기본값 (0: Error, 1: getCallerInfo, 2: logger function)
// 'at '로 시작하는 라인 찾기
for (let i = 2; i < stack.length; i++) {
if (stack[i].includes('at ')) {
callerIndex = i;
break;
}
}
if (stack.length > callerIndex) {
const callerLine = stack[callerIndex].trim();
// 파일 경로 추출 (Windows와 Unix 경로 모두 처리)
const fileMatch = callerLine.match(/\((.*?):\d+:\d+\)/) ||
callerLine.match(/(.*?):\d+:\d+$/);
if (fileMatch && fileMatch[1]) {
const filePath = fileMatch[1];
const fileName = path.basename(filePath);
return fileName;
}
}
return 'unknown';
};
// 로그 포맷팅
const formatLog = (level, message, metadata = {}) => {
const timestamp = getTimestamp();
const callerInfo = getCallerInfo();
const levelName = logLevelNames[level];
let logMessage = `[${levelName}] ${timestamp} [${callerInfo}] ${message}`;
if (Object.keys(metadata).length > 0) {
try {
logMessage += `\n Meta: ${JSON.stringify(metadata, null, 2)}`;
} catch (e) {
logMessage += `\n Meta: [Circular or invalid object]`;
}
}
return logMessage;
};
// 로그 파일 쓰기
const writeLog = (logMessage, level) => {
if (level < currentLogLevel) return;
// 콘솔 출력
if (level >= logLevels.ERROR) {
console.error(logMessage);
} else {
console.log(logMessage);
}
// 파일 로깅
try {
const date = new Date().toISOString().split('T')[0];
const logFile = path.join(logDir, `trading-${date}.log`);
fs.appendFileSync(logFile, logMessage + '\n');
} catch (e) {
// 파일 로깅 실패 시 콘솔에 경고
console.error(`[LOGGER] Failed to write log file: ${e.message}`);
}
};
// 로거 인스턴스 생성
const createLogger = () => {
return {
debug: (message, metadata = {}) => {
const logMessage = formatLog(logLevels.DEBUG, message, metadata);
writeLog(logMessage, logLevels.DEBUG);
},
info: (message, metadata = {}) => {
const logMessage = formatLog(logLevels.INFO, message, metadata);
writeLog(logMessage, logLevels.INFO);
},
warn: (message, metadata = {}) => {
const logMessage = formatLog(logLevels.WARN, message, metadata);
writeLog(logMessage, logLevels.WARN);
},
error: (message, error = null, metadata = {}) => {
const errorMetadata = { ...metadata };
if (error) {
errorMetadata.error = {
message: error.message || 'Unknown error',
stack: error.stack ?
error.stack.split('\n').slice(0, 5).join('\n') :
'No stack trace'
};
}
const logMessage = formatLog(logLevels.ERROR, message, errorMetadata);
writeLog(logMessage, logLevels.ERROR);
},
critical: (message, error = null, metadata = {}) => {
const errorMetadata = { ...metadata };
if (error) {
errorMetadata.error = {
message: error.message || 'Unknown critical error',
stack: error.stack || 'No stack trace'
};
}
const logMessage = formatLog(logLevels.CRITICAL, message, errorMetadata);
writeLog(logMessage, logLevels.CRITICAL);
},
logPosition: (position, message) => {
if (!position) return;
const metadata = {
position: {
id: position.id,
side: position.side,
entry: position.entry,
qty: position.qty,
sl: position.sl,
tp: position.tp,
openTime: position.openTime ?
new Date(position.openTime).toISOString() : 'N/A'
}
};
if (position.closed) {
metadata.position.exitPrice = position.exitPrice;
metadata.position.exitReason = position.exitReason;
metadata.position.pnl = position.pnl;
metadata.position.closeTime = position.closeTime ?
new Date(position.closeTime).toISOString() : 'N/A';
}
this.info(message, metadata);
}
};
};
// 싱글톤 인스턴스 생성
const logger = createLogger();
// 모듈 내보내기
export default logger;

21
package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "bot",
"version": "1.0.0",
"description": "Auto Trade Bot",
"main": "index.mjs",
"type": "module",
"scripts": {
"dev": "yarn "
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/node": "^24.3.0",
"axios": "^1.11.0",
"ccxt": "^4.5.2",
"fs-extra": "^11.3.1",
"openai": "^5.16.0",
"regression": "^2.0.1",
"ws": "^8.18.3"
}
}

41
strategy.js Normal file
View File

@@ -0,0 +1,41 @@
// strategy.js
import logger from './logger.js';
import {OpenAI} from "openai";
const openai = new OpenAI({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: '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',
});
export async function aiSignal(bars) {
if (bars.length < 20) return null;
// const time = '15분'
const time = '1시간'
console.log(Date.now())
const completion = await openai.chat.completions.create({
model: 'qwen/qwen3-235b-a22b-2507',
messages: [
{
role: 'user',
content: `당신은 코인 매매 전문 트레이너 입니다.
매매 방법은 스윙매매 입니다. 1시간 봉으로 매매 하는 만큼 큰 수익을 얻어야 합니다. 너무 무리 하지 않는 선에서 제어가 필요합니다.
아래에 제공 되는 json ${time}봉 데이터를 보고 여러지표(macd, rsi, ema, 다이버전스, ovm, vwap) 등 을 대입하여 포지션, 타점, 스톱로스, 테이크피로핏을 정의 해줘야합니다
모든 대답은 간결하게 {side: 포지션, price: 진입가, sl: 스탑로스, tp: 테이크프로핏, resaon: 간단한이유} json 형태로 대답하세요.
포지션은 SHORT, LONG, HOLD 포지션이 LONG, SHORT 일때는 전체적인 추세를 보고 진입가 손절 익절 금액을 채워주세요.
reason 에는 포지션을 지지하는 이유를 요약 하여 채워주세요.
t: 시간 o: 시작가 h: 최고가 l: 최저가 c: 종료가 v: 볼륨
${JSON.stringify(bars)}
`,
},
],
});
console.log(Date.now())
const msg = completion.choices[0].message.content
logger.info(msg);
const sig = JSON.parse(msg)
return sig;
}

55
test.js Normal file
View File

@@ -0,0 +1,55 @@
import { aiSignal } from './strategy.js';
import ccxt from "ccxt";
import fs from "fs";
import logger from "./logger.js";
const cfg = JSON.parse(fs.readFileSync('./config.json', 'utf8'))
const ex = new ccxt.bybit({
apiKey: '',
secret: '',
options: { defaultType: 'swap' }
});
const ohlcv = await ex.fetchOHLCV(cfg.cctxSymbol, cfg.ohlcvInterval, undefined, 200);
const bars = ohlcv.map(c => ({
t: c[0],
o: c[1],
h: c[2],
l: c[3],
c: c[4],
v: c[5]
}));
const sig = await aiSignal(bars);
// const sig = JSON.parse(msg)
console.log(sig)
console.log(sig.side)
console.log(sig.sl)
console.log(sig.tp)
console.log(sig.reason)
let position= {}
let balance = 10000
const curPrice = sig.price
if(sig.side !== 'HOLD') enterMarket(sig);
function enterMarket(sig) {
const qty = ((balance * cfg.leverage) / curPrice).toFixed(4);
const notional = curPrice * +qty;
const fee = notional * cfg.feeTaker / 100;
balance -= fee;
position = {
id: `m${Date.now()}`,
side: sig.side,
entry: curPrice,
qty: +qty,
sl: sig.sl,
tp: sig.tp,
openTime: Date.now(),
fee,
initialBalance: balance + fee
};
logger.info(`[FILLED] ${sig.side} 시장가 @${curPrice} qty=${qty}`, { position });
}

226
yarn.lock Normal file
View File

@@ -0,0 +1,226 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@^24.3.0":
version "24.3.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.3.0.tgz#89b09f45cb9a8ee69466f18ee5864e4c3eb84dec"
integrity sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==
dependencies:
undici-types "~7.10.0"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
axios@^1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.11.0.tgz#c2ec219e35e414c025b2095e8b8280278478fdb6"
integrity sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.4"
proxy-from-env "^1.1.0"
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
ccxt@^4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/ccxt/-/ccxt-4.5.2.tgz#a777d64f8122040229c37ad7adae3b08c75c2a84"
integrity sha512-IY21AVv8E+qJJPQFt2NNGiEFOygtl7dAOwojIh0Lxyz/+L7/q0m54Swjk/ALHCuvGQeBWQBgYrn+U6b45Zx9BQ==
dependencies:
ws "^8.8.1"
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
call-bind-apply-helpers "^1.0.1"
es-errors "^1.3.0"
gopd "^1.2.0"
es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
dependencies:
es-errors "^1.3.0"
es-set-tostringtag@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
dependencies:
es-errors "^1.3.0"
get-intrinsic "^1.2.6"
has-tostringtag "^1.0.2"
hasown "^2.0.2"
follow-redirects@^1.15.6:
version "1.15.11"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340"
integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==
form-data@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4"
integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
es-set-tostringtag "^2.1.0"
hasown "^2.0.2"
mime-types "^2.1.12"
fs-extra@^11.3.1:
version "11.3.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.1.tgz#ba7a1f97a85f94c6db2e52ff69570db3671d5a74"
integrity sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
get-intrinsic@^1.2.6:
version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
dependencies:
call-bind-apply-helpers "^1.0.2"
es-define-property "^1.0.1"
es-errors "^1.3.0"
es-object-atoms "^1.1.1"
function-bind "^1.1.2"
get-proto "^1.0.1"
gopd "^1.2.0"
has-symbols "^1.1.0"
hasown "^2.0.2"
math-intrinsics "^1.1.0"
get-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
gopd@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
has-symbols@^1.0.3, has-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
has-tostringtag@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
dependencies:
has-symbols "^1.0.3"
hasown@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
jsonfile@^6.0.1:
version "6.2.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62"
integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
openai@^5.16.0:
version "5.16.0"
resolved "https://registry.yarnpkg.com/openai/-/openai-5.16.0.tgz#a302d4ca92954598c79c72dd199c58994708130d"
integrity sha512-hoEH8ZNvg1HXjU9mp88L/ZH8O082Z8r6FHCXGiWAzVRrEv443aI57qhch4snu07yQydj+AUAWLenAiBXhu89Tw==
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
regression@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/regression/-/regression-2.0.1.tgz#8d29c3e8224a10850c35e337e85a8b2fac3b0c87"
integrity sha512-A4XYsc37dsBaNOgEjkJKzfJlE394IMmUPlI/p3TTI9u3T+2a+eox5Pr/CPUqF0eszeWZJPAc6QkroAhuUpWDJQ==
undici-types@~7.10.0:
version "7.10.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.10.0.tgz#4ac2e058ce56b462b056e629cc6a02393d3ff350"
integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
ws@^8.18.3, ws@^8.8.1:
version "8.18.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==