@ai.weget.jp/bot 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -22
- package/dist/src/gmoCoinRuntime.js +1 -0
- package/dist/src/gmoFxRuntime.js +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/ipc/registerHandlers.js +1 -1
- package/dist/src/main.js +1 -1
- package/dist/src/platformApi.js +1 -0
- package/dist/src/preload.cjs +15 -4
- package/dist/src/renderer/app.js +1 -1
- package/dist/src/renderer/index.html +153 -62
- package/dist/src/renderer/styles.css +324 -1
- package/dist/src/services/authApiService.js +1 -1
- package/dist/src/services/codexService.js +1 -0
- package/dist/src/services/windowManagerService.js +1 -1
- package/dist/src/skillHostState.js +1 -0
- package/dist/src/skills.js +1 -0
- package/dist/src/wsClient.js +1 -1
- package/package.json +6 -13
- package/scripts/build-ts.cjs +8 -2
- package/dist/src/services/aiChatService.js +0 -1
- package/dist/src/services/trade/gmoCoinGateway.js +0 -1
- package/dist/src/services/trade/gmoKlineService.js +0 -1
- package/dist/src/services/trade/gmoWindowService.js +0 -1
- package/dist/src/services/trade/tradeConfigService.js +0 -1
- package/dist/src/services/trade/tradeStatusService.js +0 -1
- package/dist/src/services/trade/types.js +0 -1
package/README.md
CHANGED
|
@@ -1,39 +1,55 @@
|
|
|
1
1
|
# @ai.weget.jp/bot
|
|
2
2
|
|
|
3
|
-
WeGet Bot
|
|
3
|
+
WeGet Bot host package (Electron + CLI).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
4
6
|
|
|
5
|
-
## 安装
|
|
6
7
|
```bash
|
|
7
8
|
npm i -g @ai.weget.jp/bot
|
|
8
9
|
```
|
|
9
|
-
Bot 当前为交易执行端。
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
1. Login to Codex on the local machine:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
codex login --device-auth
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
You can also open the local `Host Control` page and use `Start Login`.
|
|
20
|
+
|
|
21
|
+
2. Open the local UI:
|
|
22
|
+
|
|
13
23
|
```bash
|
|
14
24
|
weget-bot ui
|
|
15
25
|
```
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
3. Sign in the bot to the private control plane from the local login screen.
|
|
28
|
+
|
|
29
|
+
## What The Host Owns
|
|
30
|
+
|
|
31
|
+
- local Codex auth state
|
|
32
|
+
- Bot WebSocket connection
|
|
33
|
+
- local skill pages for installed packages
|
|
34
|
+
- local GMO credentials and default model
|
|
35
|
+
|
|
36
|
+
The host does not store or receive OpenAI keys from Platform.
|
|
37
|
+
|
|
38
|
+
AI interaction is routed through local Codex only.
|
|
39
|
+
|
|
40
|
+
Default built-in endpoints:
|
|
20
41
|
|
|
21
|
-
默认内置:
|
|
22
42
|
- `loginApiUrl=https://api.weget.jp/login`
|
|
23
|
-
- `
|
|
24
|
-
- `
|
|
43
|
+
- `botUploadApiUrl=https://api.weget.jp/bot/upload-url`
|
|
44
|
+
- `gmoCryptoPrivateApiBaseUrl=https://api.coin.z.com/private`
|
|
45
|
+
- `gmoFxPrivateApiBaseUrl=https://forex-api.coin.z.com/private`
|
|
25
46
|
- `aiModel=gpt-4.1-mini`
|
|
26
47
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
GMO 账户数据查询通过 API Key 完成:
|
|
30
|
-
- 暗号資産 API Key / Secret
|
|
31
|
-
- FX API Key / Secret
|
|
48
|
+
## Local UI Structure
|
|
32
49
|
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
- `Host Control`: local runtime, Codex auth, default model, installed skill state, local credentials
|
|
51
|
+
- `Codex Console`: local chat with Codex
|
|
52
|
+
- `Installed Skills`: per-skill pages such as `skill-gmo-coin`, `skill-gmo-fx`, `skill-browser`
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
weget-bot help
|
|
39
|
-
```
|
|
54
|
+
The package is the public runtime host. Private control plane behavior remains
|
|
55
|
+
in `platform/` and `infra/`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createGmoCoinExecutionWsService as e,createGmoGateway as t,createGmoMarketDataService as i,createTradeConfigService as r,createTradeStatusService as o,defaultGmoRuntimeConfig as s}from"@ai.weget.jp/skill-gmo-core";const n=e=>e&&"object"==typeof e?e:{},a=e=>{const t=Number(String(e??"").trim().replace(/,/g,""));return Number.isFinite(t)&&t>0?t:null},c=e=>"SELL"===String(e||"").trim().toUpperCase()?"SELL":"BUY",l=e=>{const t=n(e);return{market:"coin",symbol:String(t.symbol||"").trim().toUpperCase(),side:c(t.side),settleType:String(t.settleType||"").toUpperCase()||"OPEN",executionPrice:t.executionPrice??t.price??"",executionSize:t.executionSize??t.size??"",executionId:t.executionId??null,orderId:t.orderId??null,executionTimestamp:String(t.executionTimestamp||t.timestamp||"")}};export const createGmoCoinRuntime=({configPath:p,emitLog:m,emitTradeExecution:g,emitTradeHistory:u,getRuntimeConfig:d=s})=>{const P=r({configPath:p,emitLog:m}),x=t({cryptoApiBaseUrl:d().cryptoApiBaseUrl,fxApiBaseUrl:d().fxApiBaseUrl,assetsPath:d().assetsPath,fxAssetsPath:d().fxAssetsPath,coinMarginPath:d().coinMarginPath,positionsPath:d().positionsPath,positionSummaryPath:d().positionSummaryPath,fxOrderPath:d().fxOrderPath,coinCloseOrderPath:d().coinCloseOrderPath,fxCloseOrderPath:d().fxCloseOrderPath});x.setRuntimeConfigProvider(d),x.setCredentialsProvider(P.getCredentials),x.setLogEmitter(m);const y=i({getCoinPublicBaseUrl:()=>d().coinPublicBaseUrl,getFxPublicBaseUrl:()=>d().fxPublicBaseUrl}),C=o({gateway:x,env:{getRiskDailyLossLimitJpy:()=>P.get().riskDailyLossLimitJpy},emitLog:m}),S=e({getCoinPrivateApiBaseUrl:()=>d().cryptoApiBaseUrl,getCoinCredentials:()=>P.getCredentials("crypto"),emitLog:m,onExecutionEvent:async e=>{const t=n(e),i=(e=>{const t=n(e);return{category:"trade_history",market:"coin",kind:"CLOSE"===String(t.settleType||"").toUpperCase()?"close":"new",symbol:String(t.symbol||"").trim().toUpperCase(),side:c(t.side),size:String(t.executionSize??t.orderSize??"").trim(),price:a(t.executionPrice??t.price),price_source:null!==a(t.executionPrice??t.price)?"execution_event":"none",result:t,ts:(new Date).toISOString()}})(t);u?.(i),m("[trade] coin execution received",l(t)),g?.(l(t))}});return{tradeConfig:P,gateway:x,marketData:y,status:C,loadConfig:()=>P.load(),getConfig:()=>P.get(),saveConfig:async e=>P.save(e),startExecutionStreams:()=>S.start(),stopExecutionStreams:()=>S.stop(),restartExecutionStreams:()=>S.restart()}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createGmoFxExecutionWsService as e,createGmoGateway as t,createGmoMarketDataService as i,createTradeConfigService as r,createTradeStatusService as s,defaultGmoRuntimeConfig as o}from"@ai.weget.jp/skill-gmo-core";const a=e=>e&&"object"==typeof e?e:{},n=e=>{const t=Number(String(e??"").trim().replace(/,/g,""));return Number.isFinite(t)&&t>0?t:null},c=e=>"SELL"===String(e||"").trim().toUpperCase()?"SELL":"BUY",l=e=>{const t=a(e);return{market:"fx",symbol:String(t.symbol||"").trim().toUpperCase(),side:c(t.side),settleType:String(t.settleType||"").toUpperCase()||"OPEN",executionPrice:t.executionPrice??t.price??"",executionSize:t.executionSize??t.size??"",executionId:t.executionId??null,orderId:t.orderId??null,executionTimestamp:String(t.executionTimestamp||t.timestamp||"")}};export const createGmoFxRuntime=({configPath:m,emitLog:p,emitTradeExecution:g,emitTradeHistory:u,getRuntimeConfig:x=o})=>{const d=r({configPath:m,emitLog:p}),P=t({cryptoApiBaseUrl:x().cryptoApiBaseUrl,fxApiBaseUrl:x().fxApiBaseUrl,assetsPath:x().assetsPath,fxAssetsPath:x().fxAssetsPath,coinMarginPath:x().coinMarginPath,positionsPath:x().positionsPath,positionSummaryPath:x().positionSummaryPath,fxOrderPath:x().fxOrderPath,coinCloseOrderPath:x().coinCloseOrderPath,fxCloseOrderPath:x().fxCloseOrderPath});P.setRuntimeConfigProvider(x),P.setCredentialsProvider(d.getCredentials),P.setLogEmitter(p);const f=i({getCoinPublicBaseUrl:()=>x().coinPublicBaseUrl,getFxPublicBaseUrl:()=>x().fxPublicBaseUrl}),y=s({gateway:P,env:{getRiskDailyLossLimitJpy:()=>d.get().riskDailyLossLimitJpy},emitLog:p}),S=e({getFxPrivateApiBaseUrl:()=>x().fxApiBaseUrl,getFxCredentials:()=>d.getCredentials("fx"),emitLog:p,onExecutionEvent:async e=>{const t=a(e),i=(e=>{const t=a(e);return{category:"trade_history",market:"fx",kind:"CLOSE"===String(t.settleType||"").toUpperCase()?"close":"new",symbol:String(t.symbol||"").trim().toUpperCase(),side:c(t.side),size:String(t.executionSize??t.orderSize??"").trim(),price:n(t.executionPrice??t.price),price_source:null!==n(t.executionPrice??t.price)?"execution_event":"none",result:t,ts:(new Date).toISOString()}})(t);u?.(i),p("[trade] fx execution received",l(t)),g?.(l(t))}});return{tradeConfig:d,gateway:P,marketData:f,status:y,loadConfig:()=>d.load(),getConfig:()=>d.get(),saveConfig:async e=>d.save(e),startExecutionStreams:()=>S.start(),stopExecutionStreams:()=>S.stop(),restartExecutionStreams:()=>S.restart()}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./skills.js";export*from"./skillHostState.js";export*from"./platformApi.js";export*from"./gmoFxRuntime.js";export*from"./gmoCoinRuntime.js";export const createBotHostManifest=()=>({name:"@ai.weget.jp/bot",version:"0.1.11",skills:[]});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const r=r=>r&&"object"==typeof r?r:{};export const registerIpcHandlers=({ipcMain:e,env:t,signInWithLoginApi:o,saveLoginProfile:i,readSavedLoginProfile:n,
|
|
1
|
+
const r=r=>r&&"object"==typeof r?r:{};export const registerIpcHandlers=({ipcMain:e,env:t,signInWithLoginApi:o,saveLoginProfile:i,readSavedLoginProfile:n,getFxApiConfig:s,saveFxApiConfig:a,getCoinApiConfig:c,saveCoinApiConfig:g,getAiConfig:m,saveAiConfig:u,setCurrentSession:d,getCurrentSession:l,getActiveSkills:S,getCodexAuthStatus:k,startCodexLogin:y,getManagedSkills:f,emitLog:p,openGmoKlineWindow:h,getGmoMarketQuotes:b,getGmoSymbolRules:C,getGmoBuyingPower:w,getGmoAccountMetrics:L,getGmoPositionSummary:x,getGmoOpenPositions:E,placeGmoFxOrder:I,placeGmoCoinOrder:A,placeGmoFxCloseOrder:v,placeGmoCoinCloseOrder:U,getGmoApiState:G,testGmoApi:q,buildTradeStatusSnapshot:F,closeTradeWindows:P,startWsClient:z,stopWsClient:B,handleBotChatMessage:N,writeErrorLog:M,writeRuntimeLog:O})=>{e.handle("auth:login",async(e,t)=>{const n=r(t),s=String(n.botId||"").trim();if(!s)return{ok:!1,error:"Bot ID is required"};try{const r=await o(String(n.email||""),String(n.password||""));await i({botId:s,email:String(n.email||"").trim(),password:String(n.password||""),remember:Boolean(n.remember)});const e={...r,botId:s};return d(e),z({botId:s,userId:String(r.userId||""),accessToken:String(r.accessToken||"")}),p("[auth] login success",{userId:r.userId||"",email:r.email||"",botId:s}),{ok:!0,session:{userId:String(r.userId||""),email:String(r.email||""),botId:s}}}catch(r){const e=r instanceof Error?r.message:String(r);return p("[auth] login failed",{error:e}),{ok:!1,error:e}}}),e.handle("auth:logout",async()=>(B(),d(null),P(),p("[auth] logout"),{ok:!0})),e.handle("auth:getSavedProfile",async()=>{try{return{ok:!0,profile:await n()}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getFxApiConfig",async()=>({ok:!0,config:s()})),e.handle("trade:saveFxApiConfig",async(r,e)=>{try{return{ok:!0,config:await a(e)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getCoinApiConfig",async()=>({ok:!0,config:c()})),e.handle("trade:saveCoinApiConfig",async(r,e)=>{try{return{ok:!0,config:await g(e)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("ai:getConfig",async()=>({ok:!0,config:m()})),e.handle("ai:saveConfig",async(r,e)=>{try{return{ok:!0,config:await u(e)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("bot:getRuntimeInfo",async()=>{const e=l(),o=r(e),i=await k();return{ok:!0,status:e?"connected":"disconnected",session:e?{userId:String(o.userId||""),email:String(o.email||""),botId:String(o.botId||"")}:null,runtime:{loginApiUrl:t.loginApiUrl,capabilities:t.capabilities,gmoFxApiState:G("fx"),gmoCoinApiState:G("coin"),aiModel:m().aiModel,activeSkills:S(),codexAuthStatus:i.status,codexAuthDetail:i.detail}}}),e.handle("bot:getSkills",async()=>({ok:!0,skills:f()})),e.handle("codex:startLogin",async()=>y()),e.handle("trade:openGmoKlineWindow",async(e,t)=>{const o=r(t),i=String(o.symbol||"BTC_JPY").trim()||"BTC_JPY",n=String(o.interval||"1m").trim()||"1m",s="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await h({symbol:i,interval:n,market:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getMarketQuotes",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",n=Array.isArray(o.symbols)?o.symbols.map(r=>String(r||"").trim().toUpperCase()).filter(Boolean):void 0;try{return{ok:!0,...await b({market:i,symbols:n})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getSymbolRules",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await C({market:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getBuyingPower",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await w({market:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getAccountMetrics",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin";try{return{ok:!0,...await L({market:i})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getPositionSummary",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",n=String(o.symbol||"").trim().toUpperCase();if("coin"===i&&!n)return{ok:!1,error:"symbol is required for coin positionSummary"};try{return{ok:!0,...await x({market:i,symbol:n||void 0})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:getOpenPositions",async(e,t)=>{const o=r(t),i="fx"===String(o.market||"").trim().toLowerCase()?"fx":"coin",n=String(o.symbol||"").trim().toUpperCase();if("coin"===i&&!n)return{ok:!1,error:"symbol is required for coin openPositions"};const s=Number(o.page),a=Number(o.prevId),c=Number(o.count);try{return{ok:!0,...await E({market:i,symbol:n||void 0,page:Number.isFinite(s)?s:void 0,prevId:Number.isFinite(a)?a:void 0,count:Number.isFinite(c)?c:void 0})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:placeFxOrder",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!s)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await I({symbol:i,side:n,size:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:placeCoinOrder",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!s)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await A({symbol:i,side:n,size:s})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:closeFxPosition",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=Number(o.positionId),a=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!Number.isFinite(s)||s<=0)return{ok:!1,error:"positionId is required"};if(!a)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await v({symbol:i,side:n,positionId:s,size:a})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:closeCoinPosition",async(e,t)=>{const o=r(t),i=String(o.symbol||"").trim().toUpperCase(),n="SELL"===String(o.side||"").trim().toUpperCase()?"SELL":"BUY",s=Number(o.positionId),a=String(o.size||"").trim();if(!i)return{ok:!1,error:"symbol is required"};if(!Number.isFinite(s)||s<=0)return{ok:!1,error:"positionId is required"};if(!a)return{ok:!1,error:"size is required"};try{return{ok:!0,result:await U({symbol:i,side:n,positionId:s,size:a})}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("trade:testGmoApi",async(e,t)=>{const o=r(t),i="coin"===String(o.market||"").trim().toLowerCase()?"coin":"fx";return q(i)}),e.handle("trade:getStatusSnapshot",async(e,t)=>{const o=r(t),i="coin"===String(o.market||"").trim().toLowerCase()?"coin":"fx";try{return{ok:!0,snapshot:await F(i)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("chat:send",async(e,t)=>{const o=r(t),i=String(o.message||"").trim();if(!i)return{ok:!1,error:"message is required"};try{return{ok:!0,answer:(await N({text:i})).answer}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("log:writeError",async(e,t)=>{const o=r(t),i=String(o.source||"").trim()||"ui",n=String(o.message||"").trim()||"unknown error";try{return await M({source:i,message:n,details:o.details}),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("log:write",async(e,t)=>{const o=r(t),i=String(o.level||"").trim().toLowerCase(),n="error"===i?"error":"debug"===i?"debug":"info",s=String(o.source||"").trim()||"ui",a=String(o.message||"").trim()||"log";try{return await O({level:n,source:s,message:a,details:o.details}),{ok:!0}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}})};
|
package/dist/src/main.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:path";import t from"node:fs/promises";import{existsSync as s}from"node:fs";import{fileURLToPath as i}from"node:url";import{createRequire as o}from"node:module";import{SYSTEM_CONFIG as r}from"./config/systemConfig.js";import{BotWsClient as a}from"./wsClient.js";import{registerIpcHandlers as n}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createAiChatService as m}from"./services/aiChatService.js";import{createWindowManager as d}from"./services/windowManagerService.js";import{createGmoCoinGateway as c}from"./services/trade/gmoCoinGateway.js";import{createTradeConfigService as g}from"./services/trade/tradeConfigService.js";import{createGmoKlineService as p}from"./services/trade/gmoKlineService.js";import{createTradeStatusService as u}from"./services/trade/tradeStatusService.js";const y=o(import.meta.url),{app:w,BrowserWindow:h,ipcMain:P,nativeImage:f}=y("electron"),S=i(import.meta.url),b=e.dirname(S),v=["trade","ai"],A=()=>e.join(w.getPath("userData"),"login-profile.json");let C=null,x=()=>e.join(w.getPath("documents"),"weget-bot-logs");const k=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},O=async({level:s,source:i,message:o,details:r,ts:a})=>{const n=x();await t.mkdir(n,{recursive:!0});const l=new Date,m=l.getFullYear(),d=String(l.getMonth()+1).padStart(2,"0"),c=String(l.getDate()).padStart(2,"0"),g=e.join(n,`${s}-${m}${d}${c}.log`),p=JSON.stringify({ts:a||l.toISOString(),level:s,source:i,message:o,details:r??null},null,0);await t.appendFile(g,`${p}\n`,"utf8")},I=d({BrowserWindow:h,preloadPath:e.join(b,"preload.cjs"),rendererDir:e.join(b,"renderer"),windowIconPath:e.join(b,"renderer","logo.png")}),U=(e,t)=>{const s=(new Date).toISOString();I.emitBotLog({message:e,data:t||null,ts:s}),O({level:k(e),source:"runtime",message:e,details:t,ts:s}).catch(()=>{})},j=e=>{I.emitChatEvent(e)},M=g({configPath:e.join(w.getPath("userData"),"trade-api-config.json"),emitLog:U});M.load(),x=()=>String(M.get().errorLogDir||"").trim()||e.join(w.getPath("documents"),"weget-bot-logs");const B=m({getApiKey:()=>M.get().openAiApiKey,getModel:()=>M.get().aiModel}),L=l({getEnv:()=>({loginApiUrl:r.loginApiUrl,botUploadApiUrl:r.botUploadApiUrl}),emitLog:U,getCurrentSession:()=>C}),F=p({getCoinPublicBaseUrl:()=>r.gmoCryptoPublicApiBaseUrl,getFxPublicBaseUrl:()=>r.gmoFxPublicApiBaseUrl}),T=c({cryptoApiBaseUrl:r.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:r.gmoFxPrivateApiBaseUrl,assetsPath:r.gmoAssetsPath,fxAssetsPath:r.gmoFxAssetsPath,coinMarginPath:r.gmoCoinMarginPath,positionsPath:r.gmoPositionsPath,positionSummaryPath:r.gmoPositionSummaryPath,fxOrderPath:r.gmoFxOrderPath,coinCloseOrderPath:r.gmoCoinCloseOrderPath,fxCloseOrderPath:r.gmoFxCloseOrderPath});T.setRuntimeConfigProvider(()=>({cryptoApiBaseUrl:r.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:r.gmoFxPrivateApiBaseUrl,assetsPath:r.gmoAssetsPath,fxAssetsPath:r.gmoFxAssetsPath,coinMarginPath:r.gmoCoinMarginPath,positionsPath:r.gmoPositionsPath,positionSummaryPath:r.gmoPositionSummaryPath,fxOrderPath:r.gmoFxOrderPath,coinCloseOrderPath:r.gmoCoinCloseOrderPath,fxCloseOrderPath:r.gmoFxCloseOrderPath})),T.setCredentialsProvider(M.getCredentials),T.setLogEmitter(U);const E=u({gateway:T,env:{getRiskDailyLossLimitJpy:()=>M.get().riskDailyLossLimitJpy},emitLog:U}),z=new a({wsUrl:"wss://ws.weget.jp",version:"0.2.0",capabilities:v,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(e,t)=>U(e,t),onStatus:e=>I.emitBotStatus({status:e}),onLineMessage:async e=>{const t=String(e.line_user_id||""),s=String(e.text||"").trim();if(j({type:"line_user",lineUserId:t,text:s}),!s)return{answer:""};try{const e=await B.sendChat(s);return j({type:"line_assistant",lineUserId:t,text:e}),{answer:e}}catch(e){const s=e instanceof Error?e.message:String(e);U("[ai] line reply failed",{error:s});const i=`AI error: ${s}`;return j({type:"line_assistant",lineUserId:t,text:i}),{answer:i}}}}),G=({market:e,kind:t,symbol:s,side:i,size:o,result:r})=>{const a=`trade-${Date.now()}`,n={category:"trade_history",market:e,kind:t,symbol:s,side:i,size:o,result:r,ts:(new Date).toISOString()};z.sendTaskEvent(a,"trade_history",n),U("[trade] history event sent",n)};n({ipcMain:P,env:{capabilities:v,loginApiUrl:r.loginApiUrl},signInWithLoginApi:L.signInWithLoginApi,saveLoginProfile:async({botId:e,email:s,password:i,remember:o})=>{if(o)await t.writeFile(A(),JSON.stringify({botId:e,email:s,password:i,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await t.unlink(A())}catch(e){if("ENOENT"!==e?.code)throw e}},readSavedLoginProfile:async()=>{try{const e=await t.readFile(A(),"utf8"),s=JSON.parse(e);return{botId:String(s?.botId||""),email:String(s?.email||""),password:String(s?.password||""),remember:Boolean(s?.remember)}}catch(e){if("ENOENT"===e?.code)return{botId:"",email:"",password:"",remember:!1};throw e}},getTradeApiConfig:()=>M.get(),saveTradeApiConfig:e=>M.save(e),setCurrentSession:e=>{C=e||null;const t=C?"connected":"disconnected";I.emitBotStatus({status:t})},getCurrentSession:()=>C,emitLog:U,openGmoKlineWindow:async({symbol:e,interval:t,market:s})=>F.fetchKline({symbol:e,interval:t,market:s}),getGmoMarketQuotes:async({market:e,symbols:t})=>F.fetchQuotes({market:e,symbols:t}),getGmoSymbolRules:async({market:e})=>F.fetchSymbolRules({market:e}),getGmoBuyingPower:async({market:e})=>({market:e,availableJpy:await T.getBuyingPower(e)}),getGmoAccountMetrics:async({market:e})=>T.getAccountMetrics(e),getGmoPositionSummary:async({market:e,symbol:t})=>({market:e,symbol:t,items:await T.getPositionSummary({market:e,symbol:t})}),getGmoOpenPositions:async({market:e,symbol:t,page:s,prevId:i,count:o})=>({market:e,symbol:t||"",items:await T.getOpenPositions({market:"fx"===e?"fx":"crypto",symbol:t||void 0,page:s,prevId:i,count:o})}),placeGmoFxOrder:async({symbol:e,side:t,size:s})=>{const i=await T.placeFxOrder({symbol:e,side:t,size:s,executionType:"MARKET"});return G({market:"fx",kind:"new",symbol:e,side:t,size:s,result:i}),i},placeGmoCoinOrder:async({symbol:e,side:t,size:s})=>{const i=await T.placeCoinOrder({symbol:e,side:t,size:s,executionType:"MARKET"});return G({market:"coin",kind:"new",symbol:e,side:t,size:s,result:i}),i},placeGmoFxCloseOrder:async({symbol:e,side:t,positionId:s,size:i})=>{const o=await T.placeFxCloseOrder({symbol:e,side:t,positionId:s,size:i,executionType:"MARKET"});return G({market:"fx",kind:"close",symbol:e,side:t,size:i,result:{...o,positionId:s}}),o},placeGmoCoinCloseOrder:async({symbol:e,side:t,positionId:s,size:i})=>{const o=await T.placeCoinCloseOrder({symbol:e,side:t,positionId:s,size:i,executionType:"MARKET"});return G({market:"coin",kind:"close",symbol:e,side:t,size:i,result:{...o,positionId:s}}),o},getGmoApiState:()=>E.getApiState(),testGmoApi:()=>E.testApi(),buildTradeStatusSnapshot:()=>E.buildSnapshot(),closeTradeWindows:()=>{},startWsClient:e=>z.start(e),stopWsClient:()=>z.stop(),handleBotChatMessage:async({text:e})=>{j({type:"user",text:String(e||"")});const t=await B.sendChat(e);return j({type:"assistant",text:t}),{answer:t}},writeErrorLog:async({source:e,message:t,details:s})=>{await(async({source:e,message:t,details:s})=>{await O({level:"error",source:e,message:t,details:s})})({source:e,message:t,details:s})},writeRuntimeLog:async({level:e,source:t,message:s,details:i})=>{await O({level:e,source:t,message:s,details:i})}});const D=e.join(b,"renderer","logo.png");w.whenReady().then(()=>{(()=>{if("darwin"!==process.platform)return;if(!s(D))return;const e=f.createFromPath(D);e.isEmpty()||w.dock.setIcon(e)})(),I.createMainWindow(),w.on("activate",()=>{0===h.getAllWindows().length&&I.createMainWindow()})}),w.on("window-all-closed",()=>{z.stop(),"darwin"!==process.platform&&w.quit()});
|
|
1
|
+
import e from"node:path";import t from"node:fs/promises";import{existsSync as i}from"node:fs";import{fileURLToPath as o}from"node:url";import{createRequire as a}from"node:module";import{SYSTEM_CONFIG as s}from"./config/systemConfig.js";import{BotWsClient as r}from"./wsClient.js";import{registerIpcHandlers as n}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createCodexService as c}from"./services/codexService.js";import{createWindowManager as g}from"./services/windowManagerService.js";import{createGmoCoinRuntime as d}from"./gmoCoinRuntime.js";import{createGmoFxRuntime as m}from"./gmoFxRuntime.js";import{createSkillHostState as p}from"./skillHostState.js";const u=a(import.meta.url),{app:f,BrowserWindow:y,ipcMain:S,nativeImage:w}=u("electron"),h=o(import.meta.url),k=e.dirname(h),x=["trade","ai"],b=()=>e.join(f.getPath("userData"),"login-profile.json"),A=()=>e.join(f.getPath("userData"),"gmo-fx-config.json"),v=()=>e.join(f.getPath("userData"),"gmo-coin-config.json"),_=()=>e.join(f.getPath("userData"),"skill-states.json");let C=null,P=null;const D=new Map;let L=()=>e.join(f.getPath("documents"),"weget-bot-logs");const E=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},O=async({level:i,source:o,message:a,details:s,ts:r})=>{const n=L();await t.mkdir(n,{recursive:!0});const l=new Date,c=l.getFullYear(),g=String(l.getMonth()+1).padStart(2,"0"),d=String(l.getDate()).padStart(2,"0"),m=e.join(n,`${i}-${c}${g}${d}.log`),p=JSON.stringify({ts:r||l.toISOString(),level:i,source:o,message:a,details:s??null},null,0);await t.appendFile(m,`${p}\n`,"utf8")},j=g({BrowserWindow:y,preloadPath:e.join(k,"preload.cjs"),rendererDir:e.join(k,"renderer"),windowIconPath:e.join(k,"renderer","logo.png")}),I=(e,t)=>{const i=(new Date).toISOString();j.emitBotLog({message:e,data:t||null,ts:i}),O({level:E(e),source:"runtime",message:e,details:t,ts:i}).catch(()=>{})},M=e=>{j.emitChatEvent(e)},B=e=>e&&"object"==typeof e?e:{},T=m({configPath:A(),emitLog:I,getRuntimeConfig:()=>({cryptoApiBaseUrl:s.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:s.gmoFxPrivateApiBaseUrl,coinPublicBaseUrl:s.gmoCryptoPublicApiBaseUrl,fxPublicBaseUrl:s.gmoFxPublicApiBaseUrl,assetsPath:s.gmoAssetsPath,fxAssetsPath:s.gmoFxAssetsPath,coinMarginPath:s.gmoCoinMarginPath,positionsPath:s.gmoPositionsPath,positionSummaryPath:s.gmoPositionSummaryPath,fxOrderPath:s.gmoFxOrderPath,coinCloseOrderPath:s.gmoCoinCloseOrderPath,fxCloseOrderPath:s.gmoFxCloseOrderPath}),emitTradeExecution:e=>{j.emitTradeExecution(e)},emitTradeHistory:e=>{const t=`trade-${Date.now()}`;P?.sendTaskEvent(t,"trade_history",e),I("[trade] history event sent (execution)",e)}}),U=d({configPath:v(),emitLog:I,getRuntimeConfig:()=>({cryptoApiBaseUrl:s.gmoCryptoPrivateApiBaseUrl,fxApiBaseUrl:s.gmoFxPrivateApiBaseUrl,coinPublicBaseUrl:s.gmoCryptoPublicApiBaseUrl,fxPublicBaseUrl:s.gmoFxPublicApiBaseUrl,assetsPath:s.gmoAssetsPath,fxAssetsPath:s.gmoFxAssetsPath,coinMarginPath:s.gmoCoinMarginPath,positionsPath:s.gmoPositionsPath,positionSummaryPath:s.gmoPositionSummaryPath,fxOrderPath:s.gmoFxOrderPath,coinCloseOrderPath:s.gmoCoinCloseOrderPath,fxCloseOrderPath:s.gmoFxCloseOrderPath}),emitTradeExecution:e=>{j.emitTradeExecution(e)},emitTradeHistory:e=>{const t=`trade-${Date.now()}`;P?.sendTaskEvent(t,"trade_history",e),I("[trade] history event sent (execution)",e)}}),F=p({gmoFxConfigPath:A(),gmoCoinConfigPath:v(),log:I});T.loadConfig(),U.loadConfig(),async function(){try{const e=await t.readFile(_(),"utf8"),i=JSON.parse(e);return(Array.isArray(i?.items)?i.items:[]).filter(e=>Boolean(e&&"object"==typeof e)).map(e=>({user_id:String(e.user_id||""),bot_id:String(e.bot_id||""),skill_id:String(e.skill_id||""),package_name:String(e.package_name||""),enabled:Boolean(e.enabled),install_status:String(e.install_status||""),version:e.version?String(e.version):null,config_json:B(e.config_json),created_at:e.created_at?String(e.created_at):void 0,updated_at:e.updated_at?String(e.updated_at):void 0}))}catch(e){if("ENOENT"===e?.code)return[];throw e}}().then(e=>{e.length>0&&(F.applySnapshots(e),I("[skills] restored local snapshot",{count:e.length}))}).catch(e=>{I("[skills] failed to restore local snapshot",{error:e instanceof Error?e.message:String(e)})}),L=()=>String(T.getConfig().errorLogDir||U.getConfig().errorLogDir||"").trim()||e.join(f.getPath("documents"),"weget-bot-logs");const G=c({getModel:()=>T.getConfig().aiModel||U.getConfig().aiModel,getWorkingDir:()=>f.getPath("userData"),emitLog:I}),J=l({getEnv:()=>({loginApiUrl:s.loginApiUrl,botUploadApiUrl:s.botUploadApiUrl}),emitLog:I,getCurrentSession:()=>C});const K=()=>{const e=F.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"),t=F.isSkillEnabled("@ai.weget.jp/skill-gmo-coin");"connected"===P?.getStatus()&&e?T.startExecutionStreams():T.stopExecutionStreams(),"connected"===P?.getStatus()&&t?U.startExecutionStreams():U.stopExecutionStreams(),I("[skills] applied managed states",{active_skills:F.getActiveSkills().map(e=>e.name)})},R=async()=>{const e=J.getBotSkillsListApiUrl(),i=String(C?.botId||"").trim();if(!e||!i)return;const o=await J.callBotApi(e,{bot_id:i}),a=Array.isArray(o.states)?o.states.filter(e=>Boolean(e&&"object"==typeof e)).map(e=>({user_id:String(e.user_id||""),bot_id:String(e.bot_id||""),skill_id:String(e.skill_id||""),package_name:String(e.package_name||""),enabled:Boolean(e.enabled),install_status:String(e.install_status||""),version:e.version?String(e.version):null,config_json:B(e.config_json),created_at:e.created_at?String(e.created_at):void 0,updated_at:e.updated_at?String(e.updated_at):void 0})):[];F.applySnapshots(a),await(async e=>{await t.writeFile(_(),JSON.stringify({updatedAt:(new Date).toISOString(),items:e},null,2),"utf8")})(a),K(),I("[config] skill states synced",{count:a.length,bot_id:i,enabled:a.filter(e=>e.enabled).length})},N=e=>{if("fx"===e&&!F.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"))throw new Error("GMO FX skill is disabled");if("coin"===e&&!F.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"))throw new Error("GMO Coin skill is disabled")};P=new r({wsUrl:"wss://ws.weget.jp",version:"0.2.0",capabilities:x,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(e,t)=>I(e,t),onStatus:e=>j.emitBotStatus({status:e}),onLineMessage:async e=>{const t=String(e.line_user_id||""),i=String(e.text||"").trim();if(M({type:"line_user",lineUserId:t,text:i}),!i)return{answer:""};try{const e=await G.runPrompt({prompt:i,contextLabel:"line_message"});return M({type:"line_assistant",lineUserId:t,text:e}),{answer:e}}catch(e){const i=e instanceof Error?e.message:String(e);I("[ai] line reply failed",{error:i});const o=`AI error: ${i}`;return M({type:"line_assistant",lineUserId:t,text:o}),{answer:o}}},onRunTask:async e=>{const t=String(e.task_id||"").trim(),i=String(e.prompt||"").trim();if(t){if(D.set(t,{cancelled:!1}),P?.sendTaskEvent(t,"task_received",{prompt:i,conversation_id:String(e.conversation_id||""),continue_from_task_id:String(e.continue_from_task_id||""),ts:(new Date).toISOString()}),!i)return P?.sendTaskEvent(t,"task_failed",{error:"prompt is required",ts:(new Date).toISOString()}),void D.delete(t);try{P?.sendTaskEvent(t,"step_started",{step:"codex_chat",ts:(new Date).toISOString()});const e=await G.runPrompt({prompt:i,contextLabel:`task:${t}`}),o=D.get(t);if(o?.cancelled)return P?.sendTaskEvent(t,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()}),void D.delete(t);P?.sendTaskEvent(t,"ai_output",{summary:e,ts:(new Date).toISOString()}),P?.sendTaskEvent(t,"step_finished",{step:"codex_chat",ts:(new Date).toISOString()}),P?.sendTaskEvent(t,"task_completed",{summary:e.slice(0,500),ts:(new Date).toISOString()})}catch(e){P?.sendTaskEvent(t,"task_failed",{error:e instanceof Error?e.message:String(e),ts:(new Date).toISOString()})}finally{D.delete(t)}}else I("[task] run_task missing task_id")},onTaskControl:async e=>{const t=String(e.task_id||"").trim(),i=String(e.action||"").trim();if(t&&i&&"cancel_task"===i){const e=D.get(t);if(e)return e.cancelled=!0,void I("[task] cancel requested",{task_id:t});P?.sendTaskEvent(t,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()})}},onServerConfigUpdate:async e=>{const t=Array.isArray(e.scopes)?e.scopes.map(e=>String(e||"").trim()):[],i=t.length>0?t:["runtime","skills"];i.includes("runtime")&&await(async()=>{const e=J.getBotRuntimeConfigApiUrl(),t=String(C?.botId||"").trim();if(!e||!t)return;const i=await J.callBotApi(e,{bot_id:t}),o=String(i.chat_model||"").trim(),a={...o?{aiModel:o}:{}};0!==Object.keys(a).length&&(await T.saveConfig({...T.getConfig(),...a}),await U.saveConfig({...U.getConfig(),...a}),I("[config] runtime config synced",{chat_model:o||""}))})(),i.includes("skills")&&await R()}}),n({ipcMain:S,env:{capabilities:x,loginApiUrl:s.loginApiUrl},signInWithLoginApi:J.signInWithLoginApi,saveLoginProfile:async({botId:e,email:i,password:o,remember:a})=>{if(a)await t.writeFile(b(),JSON.stringify({botId:e,email:i,password:o,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await t.unlink(b())}catch(e){if("ENOENT"!==e?.code)throw e}},readSavedLoginProfile:async()=>{try{const e=await t.readFile(b(),"utf8"),i=JSON.parse(e);return{botId:String(i?.botId||""),email:String(i?.email||""),password:String(i?.password||""),remember:Boolean(i?.remember)}}catch(e){if("ENOENT"===e?.code)return{botId:"",email:"",password:"",remember:!1};throw e}},getFxApiConfig:()=>{const e=T.getConfig();return{riskDailyLossLimitJpy:e.riskDailyLossLimitJpy,fxApiKey:e.fxApiKey,fxApiSecret:e.fxApiSecret,errorLogDir:e.errorLogDir}},saveFxApiConfig:async e=>{const t=await T.saveConfig({...T.getConfig(),...e&&"object"==typeof e?e:{}});return"connected"===P?.getStatus()&&K(),{riskDailyLossLimitJpy:t.riskDailyLossLimitJpy,fxApiKey:t.fxApiKey,fxApiSecret:t.fxApiSecret,errorLogDir:t.errorLogDir}},getCoinApiConfig:()=>{const e=U.getConfig();return{riskDailyLossLimitJpy:e.riskDailyLossLimitJpy,cryptoApiKey:e.cryptoApiKey,cryptoApiSecret:e.cryptoApiSecret,errorLogDir:e.errorLogDir}},saveCoinApiConfig:async e=>{const t=await U.saveConfig({...U.getConfig(),...e&&"object"==typeof e?e:{}});return"connected"===P?.getStatus()&&K(),{riskDailyLossLimitJpy:t.riskDailyLossLimitJpy,cryptoApiKey:t.cryptoApiKey,cryptoApiSecret:t.cryptoApiSecret,errorLogDir:t.errorLogDir}},getAiConfig:()=>{const e=T.getConfig(),t=U.getConfig();return{aiModel:e.aiModel||t.aiModel}},saveAiConfig:async e=>{const t=e&&"object"==typeof e?e:{},i=await T.saveConfig({...T.getConfig(),...t});return await U.saveConfig({...U.getConfig(),...t}),{aiModel:i.aiModel}},setCurrentSession:e=>{C=e||null;const t=C?"connected":"disconnected";j.emitBotStatus({status:t})},getCurrentSession:()=>C,getActiveSkills:()=>F.getActiveSkills().map(e=>({name:e.name,displayName:e.displayName,version:e.version})),getCodexAuthStatus:()=>G.getAuthStatus(),startCodexLogin:()=>G.startLogin(),getManagedSkills:()=>F.getManagedSkills(),emitLog:I,openGmoKlineWindow:async({symbol:e,interval:t,market:i})=>(()=>{const o="fx"===i?"fx":"coin";return N(o),("fx"===o?T.marketData:U.marketData).fetchKline({symbol:e,interval:t,market:o})})(),getGmoMarketQuotes:async({market:e,symbols:t})=>(()=>{const i="fx"===e?"fx":"coin";return N(i),("fx"===i?T.marketData:U.marketData).fetchQuotes({market:i,symbols:t})})(),getGmoSymbolRules:async({market:e})=>(()=>{const t="fx"===e?"fx":"coin";return N(t),("fx"===t?T.marketData:U.marketData).fetchSymbolRules({market:t})})(),getGmoBuyingPower:async({market:e})=>({market:e,availableJpy:await(N(e),("fx"===e?T.gateway:U.gateway).getBuyingPower(e))}),getGmoAccountMetrics:async({market:e})=>(N(e),("fx"===e?T.gateway:U.gateway).getAccountMetrics(e)),getGmoPositionSummary:async({market:e,symbol:t})=>({market:e,symbol:t,items:await(N(e),("fx"===e?T.gateway:U.gateway).getPositionSummary({market:e,symbol:t}))}),getGmoOpenPositions:async({market:e,symbol:t,page:i,prevId:o,count:a})=>({market:e,symbol:t||"",items:await(N(e),("fx"===e?T.gateway:U.gateway).getOpenPositions({market:"fx"===e?"fx":"crypto",symbol:t||void 0,page:i,prevId:o,count:a}))}),placeGmoFxOrder:async({symbol:e,side:t,size:i})=>{N("fx");return await T.gateway.placeFxOrder({symbol:e,side:t,size:i,executionType:"MARKET"})},placeGmoCoinOrder:async({symbol:e,side:t,size:i})=>{N("coin");return await U.gateway.placeCoinOrder({symbol:e,side:t,size:i,executionType:"MARKET"})},placeGmoFxCloseOrder:async({symbol:e,side:t,positionId:i,size:o})=>{N("fx");return await T.gateway.placeFxCloseOrder({symbol:e,side:t,positionId:i,size:o,executionType:"MARKET"})},placeGmoCoinCloseOrder:async({symbol:e,side:t,positionId:i,size:o})=>{N("coin");return await U.gateway.placeCoinCloseOrder({symbol:e,side:t,positionId:i,size:o,executionType:"MARKET"})},getGmoApiState:e=>"fx"===e?T.status.getApiState():U.status.getApiState(),testGmoApi:e=>(N(e),"fx"===e?T.status.testApi():U.status.testApi()),buildTradeStatusSnapshot:e=>(N(e),"fx"===e?T.status.buildSnapshot():U.status.buildSnapshot()),closeTradeWindows:()=>{},startWsClient:e=>{P?.start(e),K()},stopWsClient:()=>{T.stopExecutionStreams(),U.stopExecutionStreams(),P?.stop()},handleBotChatMessage:async({text:e})=>{M({type:"user",text:String(e||"")});const t=await G.runPrompt({prompt:e,contextLabel:"desktop_chat"});return M({type:"assistant",text:t}),{answer:t}},writeErrorLog:async({source:e,message:t,details:i})=>{await(async({source:e,message:t,details:i})=>{await O({level:"error",source:e,message:t,details:i})})({source:e,message:t,details:i})},writeRuntimeLog:async({level:e,source:t,message:i,details:o})=>{await O({level:e,source:t,message:i,details:o})}});const W=e.join(k,"renderer","logo.png");f.whenReady().then(()=>{(()=>{if("darwin"!==process.platform)return;if(!i(W))return;const e=w.createFromPath(W);e.isEmpty()||f.dock.setIcon(e)})(),j.createMainWindow(),f.on("activate",()=>{0===y.getAllWindows().length&&j.createMainWindow()})}),f.on("window-all-closed",()=>{T.stopExecutionStreams(),U.stopExecutionStreams(),P?.stop(),"darwin"!==process.platform&&f.quit()});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=(r,e="")=>{if(null==r)return r;const o=String(e||"");if("string"==typeof r)return/(password|token|authorization|api_?key|secret|cookie|jwt)/i.test(o)?r.length<=8?"****":`${r.slice(0,3)}****${r.slice(-3)}`:r.length>1e3?`${r.slice(0,1e3)}...(truncated)`:r;if(Array.isArray(r))return r.slice(0,30).map(r=>t(r,o));if("object"==typeof r){const e={};for(const[o,s]of Object.entries(r))e[o]=t(s,o);return e}return r};export const createPlatformApiClient=({getBaseUrl:r,getSession:e,log:o})=>({request:async s=>{const i=e(),n=String(i?.accessToken||"").trim();if(!n)throw new Error("platform api access token missing");const a=s.method||"POST",c=((t,r)=>{const e=String(t||"").trim().replace(/\/$/,""),o=String(r.path||"").trim();if(!e)throw new Error("platform api base url is missing");if(!o)throw new Error("platform api path is missing");const s=new URL(o.startsWith("http")?o:`${e}${o.startsWith("/")?o:`/${o}`}`),i=r.query||{};for(const[t,r]of Object.entries(i))null!=r&&""!==String(r)&&s.searchParams.set(t,String(r));return s.toString()})(r(),s);o?.("[platform-api] request",{method:a,url:c,body:t(s.body)});const p=await fetch(c,{method:a,headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:"GET"===a||"DELETE"===a?void 0:JSON.stringify(s.body||{})}),l=await(async t=>{const r=await t.text();if(!r)return{};try{const t=JSON.parse(r);return t&&"object"==typeof t?t:{value:t}}catch{return{raw:r}}})(p);if(o?.("[platform-api] response",{method:a,url:c,status:p.status,ok:p.ok,body:t(l)}),!p.ok)throw new Error(String(l.error||l.message||`platform api failed: ${p.status}`));return l}});
|
package/dist/src/preload.cjs
CHANGED
|
@@ -4,9 +4,15 @@ contextBridge.exposeInMainWorld('botApi', {
|
|
|
4
4
|
login: (email, password, botId, remember) => ipcRenderer.invoke('auth:login', { email, password, botId, remember }),
|
|
5
5
|
logout: () => ipcRenderer.invoke('auth:logout'),
|
|
6
6
|
getSavedProfile: () => ipcRenderer.invoke('auth:getSavedProfile'),
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
getFxApiConfig: () => ipcRenderer.invoke('trade:getFxApiConfig'),
|
|
8
|
+
saveFxApiConfig: (config) => ipcRenderer.invoke('trade:saveFxApiConfig', config),
|
|
9
|
+
getCoinApiConfig: () => ipcRenderer.invoke('trade:getCoinApiConfig'),
|
|
10
|
+
saveCoinApiConfig: (config) => ipcRenderer.invoke('trade:saveCoinApiConfig', config),
|
|
11
|
+
getAiConfig: () => ipcRenderer.invoke('ai:getConfig'),
|
|
12
|
+
saveAiConfig: (config) => ipcRenderer.invoke('ai:saveConfig', config),
|
|
9
13
|
getRuntimeInfo: () => ipcRenderer.invoke('bot:getRuntimeInfo'),
|
|
14
|
+
getSkills: () => ipcRenderer.invoke('bot:getSkills'),
|
|
15
|
+
startCodexLogin: () => ipcRenderer.invoke('codex:startLogin'),
|
|
10
16
|
openGmoKlineWindow: ({ symbol, interval, market }) =>
|
|
11
17
|
ipcRenderer.invoke('trade:openGmoKlineWindow', { symbol, interval, market }),
|
|
12
18
|
getMarketQuotes: ({ market, symbols }) => ipcRenderer.invoke('trade:getMarketQuotes', { market, symbols }),
|
|
@@ -22,8 +28,8 @@ contextBridge.exposeInMainWorld('botApi', {
|
|
|
22
28
|
ipcRenderer.invoke('trade:closeCoinPosition', { symbol, side, positionId, size }),
|
|
23
29
|
closeFxPosition: ({ symbol, side, positionId, size }) =>
|
|
24
30
|
ipcRenderer.invoke('trade:closeFxPosition', { symbol, side, positionId, size }),
|
|
25
|
-
testGmoApi: () => ipcRenderer.invoke('trade:testGmoApi'),
|
|
26
|
-
getStatusSnapshot: () => ipcRenderer.invoke('trade:getStatusSnapshot'),
|
|
31
|
+
testGmoApi: ({ market } = {}) => ipcRenderer.invoke('trade:testGmoApi', { market }),
|
|
32
|
+
getStatusSnapshot: ({ market } = {}) => ipcRenderer.invoke('trade:getStatusSnapshot', { market }),
|
|
27
33
|
sendChat: (message) => ipcRenderer.invoke('chat:send', { message }),
|
|
28
34
|
writeErrorLog: ({ source, message, details }) => ipcRenderer.invoke('log:writeError', { source, message, details }),
|
|
29
35
|
writeLog: ({ level, source, message, details }) => ipcRenderer.invoke('log:write', { level, source, message, details }),
|
|
@@ -42,4 +48,9 @@ contextBridge.exposeInMainWorld('botApi', {
|
|
|
42
48
|
ipcRenderer.on('chat:event', listener);
|
|
43
49
|
return () => ipcRenderer.removeListener('chat:event', listener);
|
|
44
50
|
},
|
|
51
|
+
onTradeExecution: (handler) => {
|
|
52
|
+
const listener = (_event, payload) => handler(payload);
|
|
53
|
+
ipcRenderer.on('trade:execution', listener);
|
|
54
|
+
return () => ipcRenderer.removeListener('trade:execution', listener);
|
|
55
|
+
},
|
|
45
56
|
});
|
package/dist/src/renderer/app.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=document.getElementById("login-form"),t=document.getElementById("login-screen"),n=document.getElementById("login-notice"),i=document.getElementById("app-shell"),o=document.getElementById("user-notice"),r=document.getElementById("bot-id"),a=document.getElementById("email"),s=document.getElementById("password"),c=document.getElementById("remember-me"),l=document.getElementById("status"),d=document.getElementById("session"),m=document.getElementById("runtime-info"),u=document.getElementById("logs"),g=document.getElementById("messages"),f=document.getElementById("chat-form"),p=document.getElementById("chat-input"),y=document.getElementById("send-btn"),w=document.getElementById("error-log-dir"),b=document.getElementById("risk-daily-loss-limit-jpy"),S=document.getElementById("crypto-api-key"),x=document.getElementById("crypto-api-secret"),v=document.getElementById("fx-api-key"),h=document.getElementById("fx-api-secret"),E=document.getElementById("toggle-crypto-secret-btn"),k=document.getElementById("toggle-fx-secret-btn"),I=document.getElementById("save-api-config-btn"),L=document.getElementById("ai-model"),A=document.getElementById("openai-api-key"),B=document.getElementById("toggle-openai-key-btn"),C=document.getElementById("save-ai-config-btn"),N=document.getElementById("logout-btn"),$=document.getElementById("login-btn"),U=document.getElementById("coin-symbol-select"),M=document.getElementById("fx-symbol-select"),D=document.getElementById("coin-kline-intervals"),F=document.getElementById("fx-kline-intervals"),P=document.getElementById("coin-kline-canvas"),T=document.getElementById("fx-kline-canvas"),z=document.getElementById("coin-market-icon"),O=document.getElementById("fx-market-icon"),q=document.getElementById("coin-order-bid"),j=document.getElementById("coin-order-ask"),J=document.getElementById("coin-order-spread"),Y=document.getElementById("fx-order-bid"),R=document.getElementById("fx-order-ask"),K=document.getElementById("fx-order-spread"),_=document.getElementById("coin-order-qty"),H=document.getElementById("fx-order-qty"),W=document.getElementById("coin-buy-btn"),G=document.getElementById("coin-sell-btn"),Q=document.getElementById("fx-buy-btn"),V=document.getElementById("fx-sell-btn"),X=document.getElementById("coin-required-amount"),Z=document.getElementById("fx-required-amount"),ee=document.getElementById("coin-account-info-refresh-btn"),te=document.getElementById("fx-account-info-refresh-btn"),ne=document.getElementById("coin-account-pnl"),ie=document.getElementById("coin-account-margin"),oe=document.getElementById("coin-account-available"),re=document.getElementById("coin-account-margin-ratio"),ae=document.getElementById("fx-account-pnl"),se=document.getElementById("fx-account-margin"),ce=document.getElementById("fx-account-available"),le=document.getElementById("fx-account-margin-ratio"),de=document.getElementById("coin-position-summary-body"),me=document.getElementById("coin-position-list-body"),ue=document.getElementById("fx-position-summary-body"),ge=document.getElementById("fx-position-list-body"),fe=document.getElementById("coin-position-summary-refresh-btn"),pe=document.getElementById("coin-position-list-refresh-btn"),ye=document.getElementById("fx-position-summary-refresh-btn"),we=document.getElementById("fx-position-list-refresh-btn"),be=Array.from(document.querySelectorAll(".tab-btn")),Se=Array.from(document.querySelectorAll(".tab-panel"));let xe="15m",ve="15m",he=String(U?.value||"BTC_JPY").trim().toUpperCase(),Ee=String(M?.value||"USD_JPY").trim().toUpperCase(),ke=null,Ie=null,Le=null,Ae=null,Be=!1,Ce=!1,Ne=!1,$e=!1,Ue=!1,Me="",De="",Fe=0,Pe=0,Te=null,ze=null;const Oe=new Map,qe={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1},je={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1};let Je=[],Ye=[];const Re=e=>e&&"object"==typeof e?e:{},Ke=e=>{for(const t of be)t.classList.toggle("is-active",t.dataset.tab===e);for(const t of Se)t.classList.toggle("is-active",t.id===`tab-${e}`)};for(const e of be)e.addEventListener("click",()=>Ke(e.dataset.tab||"coin"));const _e=e=>{const n=Be!==e;Be=e,t.classList.toggle("hidden",e),i.classList.toggle("hidden",!e),e&&Ct(),e?n&&Tt():(ft(),Ce=!1)},He=e=>{const t=String(e||"disconnected").trim().toLowerCase();l.textContent=t,l.classList.remove("status-connected","status-disconnected","status-connecting"),"connected"!==t?"reconnecting"!==t&&"connecting"!==t?l.classList.add("status-disconnected"):l.classList.add("status-connecting"):l.classList.add("status-connected")},We=(e,t=6)=>{if(null===e||!Number.isFinite(e))return"-";const n=Math.abs(e);return n>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:3}):n>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:Math.min(t,5)}):e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:t})},Ge=e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,Qe=e=>`${e>0?"+":""}${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,Ve=e=>!Number.isFinite(e)||e<=0||e>1e5?"- %":(e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:2})} %`)(e),Xe=e=>{const t=String(e||"").trim().replace(/,/g,"");if(!t)return"";if(!/^\d+(\.\d+)?$/.test(t))return"";if(!t.includes("."))return t;return t.replace(/(\.\d*?[1-9])0+$/,"$1").replace(/\.0+$/,"").replace(/\.$/,"")},Ze=e=>{const t=String(e||"").trim(),n=t.indexOf(".");return n>=0?t.length-n-1:0},et=(e,t)=>{const n=String(e||"").trim(),i=n.startsWith("-"),o=i?n.slice(1):n,[r,a=""]=o.split("."),s=(a+"0".repeat(t)).slice(0,t),c=BigInt((r||"0")+s);return i?-c:c},tt=({symbol:e,quote:t,orderBidNode:n,orderAskNode:i,orderSpreadNode:o})=>{if(!t)return n.textContent="-",i.textContent="-",void(o.textContent="-");const r=We(t.bid),a=We(t.ask);n.textContent=r,i.textContent=a,o.textContent=We(t.spread,8)},nt=e=>{if("coin"===e){const e=Number(String(_.value||"").trim()),t=Te;return!Number.isFinite(e)||e<=0||null===t||!Number.isFinite(t)?void(X.textContent="- 円"):void(X.textContent=Ge(e*t/2))}const t=Number(String(H.value||"").trim()),n=ze;!Number.isFinite(t)||t<=0||null===n||!Number.isFinite(n)?Z.textContent="- 円":Z.textContent=Ge(t*n/20)},it=async(e,t)=>{if(!window.botApi?.getMarketQuotes)return null;const n=String(t||"").trim().toUpperCase(),i=await window.botApi.getMarketQuotes({market:e,symbols:[n]});if(!i?.ok)throw new Error(String(i?.error||"quote api failed"));const o=(i.quotes||[]).find(e=>String(e.symbol||"").toUpperCase()===n)||null;return"coin"===e?(tt({symbol:n,quote:o,orderBidNode:q,orderAskNode:j,orderSpreadNode:J}),Te=o?.ask??null,nt("coin")):(tt({symbol:n,quote:o,orderBidNode:Y,orderAskNode:R,orderSpreadNode:K}),ze=o?.ask??null,nt("fx")),o},ot=(e,t)=>{const n=String(t||"").trim().toUpperCase();if(!n)return;if("coin"===e){const e=n.replace("_","-").toLowerCase();return void(z.src=`https://coin.z.com/jp/member/imgs/icon-${e}.svg`)}const[i,o]=n.split("_"),r="JPY"===o?i:`${i}_${o}`;O.src=`https://coin.z.com/jp/member/imgs/fx/icon_${r}.svg`},rt=e=>"5m"===e?3e5:"15m"===e?9e5:"30m"===e?18e5:"1h"===e?36e5:6e4,at=e=>"coin"===e?qe:je,st=e=>"coin"===e?P:T,ct=e=>"coin"===e?he:Ee,lt=e=>{const t=ct(e),n=("coin"===e?Je:Ye).filter(e=>String(e.symbol||"").trim().toUpperCase()===t),i=[];for(const e of n){const t=String(e.side||"").trim().toUpperCase(),n=Number(e.averagePositionRate||0);if(!Number.isFinite(n)||n<=0)continue;const o="BUY"===t;i.push({price:n,label:o?"BUY Avg":"SELL Avg",color:o?"#1f9d55":"#e03131"})}return i},dt=e=>{const t=at(e);t.candles.length&&$t(st(e),t.candles,lt(e))},mt=(e,t)=>{if(!t)return;const n=at(e);if(!n.candles.length)return;if(n.symbol!==t.symbol)return;const i=n.candles[n.candles.length-1],o=null!==t.bid&&null!==t.ask?(t.bid+t.ask)/2:null!==t.ask?t.ask:t.bid;null!==o&&Number.isFinite(o)&&(i.close=o,i.high=Math.max(i.high,o),i.low=Math.min(i.low,o),$t(st(e),n.candles,lt(e)))},ut=async e=>{const t=at(e);if(!t.interval||!t.symbol||t.fetching)return;if(t.symbol!==ct(e))return;if(!(Math.floor(Date.now()/rt(t.interval))<=t.lastFetchBucket)){t.fetching=!0;try{"coin"===e?await Dt():await Ft()}finally{t.fetching=!1}}},gt=async e=>{if("coin"!==e){if(!Ue){Ue=!0;try{const e=await it("fx",Ee);mt("fx",e),await ut("fx")}catch(e){const t=kt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==De||n-Pe>15e3)&&(ht("[fx] quote poll failed",{error:t}),De=t,Pe=n)}finally{Ue=!1}}}else{if($e)return;$e=!0;try{const e=await it("coin",he);mt("coin",e),await ut("coin")}catch(e){const t=kt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==Me||n-Fe>15e3)&&(ht("[coin] quote poll failed",{error:t}),Me=t,Fe=n)}finally{$e=!1}}},ft=()=>{Le&&(clearInterval(Le),Le=null),Ae&&(clearInterval(Ae),Ae=null)},pt=async e=>{if(window.botApi?.getAccountMetrics){if(!e||"coin"===e){const e=await window.botApi.getAccountMetrics({market:"coin"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);ne.textContent=n>0?Qe(i):Ge(0),ie.textContent=Ge(n),oe.textContent=Ge(t),re.textContent=Ve(Number(e.marginRatio||0))}else ht("[coin] account metrics fetch failed",{error:e?.error||"unknown error"})}if(!e||"fx"===e){const e=await window.botApi.getAccountMetrics({market:"fx"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);ae.textContent=n>0?Qe(i):Ge(0),se.textContent=Ge(n),ce.textContent=Ge(t),le.textContent=Ve(Number(e.marginRatio||0))}else ht("[fx] account metrics fetch failed",{error:e?.error||"unknown error"})}}},yt=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").toUpperCase(),o=n.replace("_","/"),r=String(i.side||"-"),a=String(i.size||"").trim(),s=Number(a||0),c=Number(i.price||0),l=Number(i.lossGain||0),d=Number(i.totalSwap||0),m=String(i.timestamp||"-"),u=Number(i.positionId||0),g=Number.isFinite(u)&&u>0&&a?`data-market="${e}" data-symbol="${n}" data-side="${String(r||"").toUpperCase()}" data-position-id="${u}" data-size="${a}"`:"disabled",f=document.createElement("tr");f.innerHTML=`\n <td><button type="button" class="close-btn" ${g}>決済</button></td>\n <td>${o}<br />${r}</td>\n <td>${s.toLocaleString("ja-JP")}</td>\n <td>${c.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${Qe(l)}<br />${Qe(d)}</td>\n <td>${m.replace("T"," ").slice(0,19)}</td>\n `,t.appendChild(f)}}else t.innerHTML='<tr><td colspan="6" class="empty-cell">対象のお取引はございません。</td></tr>'},wt=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").replace("_","/"),o=String(i.side||"-"),r=Number("fx"===e?i.sumPositionSize||0:i.sumPositionQuantity||0),a=Number(i.averagePositionRate||0),s=Number(i.positionLossGain||0),c=Number("fx"===e&&i.sumTotalSwap||0),l=document.createElement("tr");l.innerHTML=`\n <td><button type="button" class="close-btn">決済</button></td>\n <td>${n}<br />${o}</td>\n <td>${r.toLocaleString("ja-JP")}</td>\n <td>${a.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${Qe(s)}<br />${Qe(c)}</td>\n `,t.appendChild(l)}}else t.innerHTML='<tr><td colspan="5" class="empty-cell">対象のお取引はございません。</td></tr>'},bt=async e=>{if(window.botApi?.getPositionSummary&&window.botApi?.getOpenPositions){if(!e||"coin"===e){const e=await window.botApi.getPositionSummary({market:"coin",symbol:he});e?.ok?(Je=Array.isArray(e.items)?e.items:[],wt("coin",de,Je),dt("coin")):(ht("[coin] position summary fetch failed",{error:e?.error||"unknown error"}),Je=[],wt("coin",de,[]),dt("coin"))}if(!e||"fx"===e){const e=await window.botApi.getPositionSummary({market:"fx"});e?.ok?(Ye=Array.isArray(e.items)?e.items:[],wt("fx",ue,Ye),dt("fx")):(ht("[fx] position summary fetch failed",{error:e?.error||"unknown error"}),Ye=[],wt("fx",ue,[]),dt("fx"))}if(!e||"coin"===e){const e=await window.botApi.getOpenPositions({market:"coin",symbol:he,page:1,count:100});e?.ok?yt("coin",me,Array.isArray(e.items)?e.items:[]):(ht("[coin] open positions fetch failed",{error:e?.error||"unknown error"}),yt("coin",me,[]))}if(!e||"fx"===e){const e=await window.botApi.getOpenPositions({market:"fx",count:100});e?.ok?yt("fx",ge,Array.isArray(e.items)?e.items:[]):(ht("[fx] open positions fetch failed",{error:e?.error||"unknown error"}),yt("fx",ge,[]))}}},St=async(e,t)=>{const n=window.botApi;if(!n?.placeFxOrder)return;const i=String(H.value||"").trim();i?await Nt(t,async()=>{const t=await n.placeFxOrder({symbol:Ee,side:e,size:i});if(!t?.ok){const n=kt(t?.error||"fx order failed");return ht("[fx] order failed",{symbol:Ee,side:e,size:i,error:n}),It("error",n),void await Lt("fx.order",n,t)}ht("[fx] order placed",{symbol:Ee,side:e,size:i,result:t.result||null}),It("success",`${e} 注文を送信しました。`),await pt("fx"),await bt("fx")}):It("error","数量を入力してください。")},xt=async(e,t)=>{const n=window.botApi;if(!n?.placeCoinOrder)return;const i=Xe(String(_.value||""));if(!i)return void It("error","数量の形式が不正です。");const o=((e,t)=>{const n=Oe.get(e);if(!n)return{ok:!0};const i=Xe(n.minOrderSize),o=Xe(n.sizeStep),r=Xe(n.maxOrderSize);if(!i||!o)return{ok:!0};const a=Math.max(Ze(t),Ze(i),Ze(o),Ze(r)),s=et(t,a),c=et(i,a),l=et(o,a);if(s<c)return{ok:!1,reason:`${e} の最小数量は ${i} です。`};if(l>0n&&s%l!==0n)return{ok:!1,reason:`${e} の数量刻みは ${o} です。`};if(r&&s>et(r,a))return{ok:!1,reason:`${e} の最大数量は ${r} です。`};return{ok:!0}})(he,i);o.ok?await Nt(t,async()=>{const t=await n.placeCoinOrder({symbol:he,side:e,size:i});if(!t?.ok){const n=kt(t?.error||"coin order failed");return ht("[coin] order failed",{symbol:he,side:e,size:i,error:n}),It("error",n),void await Lt("coin.order",n,t)}ht("[coin] order placed",{symbol:he,side:e,size:i,result:t.result||null}),It("success",`${e} 注文を送信しました。`),await pt("coin"),await bt("coin")}):It("error",o.reason||"数量が取引ルールに一致しません。")},vt=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},ht=(e,t=null,n=null)=>{if(window.botApi?.writeLog&&window.botApi.writeLog({level:vt(e),source:"ui.log",message:e,details:{data:t,ts:n||(new Date).toISOString()}}).catch(()=>{}),!u)return;const i=document.createElement("div");i.className="log-entry";const o=document.createElement("div");o.className="log-line";const r=document.createElement("span");if(r.className="log-ts",r.textContent=n||(new Date).toISOString(),o.appendChild(r),o.append(document.createTextNode(e||"")),i.appendChild(o),null!=t&&""!==t){const e=document.createElement("pre");e.className="status-output",e.textContent="string"==typeof t?t:JSON.stringify(t,null,2),i.appendChild(e)}for(u.appendChild(i);u.children.length>300;)u.removeChild(u.firstChild);u.scrollTop=u.scrollHeight},Et=(e,t,n="")=>{const i=document.createElement("div");i.className=`msg ${e}`;let o=n||("user"===e?"You":"Assistant");"line-user"===e&&(o=n||"LINE User"),"line-assistant"===e&&(o=n||"AI"),i.textContent=`${o}: ${t}`,g.appendChild(i),g.scrollTop=g.scrollHeight},kt=e=>{const t=String(e||"").trim(),n=t.toLowerCase();return t?n.includes("api key")&&n.includes("missing")?"API Key 未设置,请先在 Config 页面保存。":n.includes("auth")||n.includes("401")||n.includes("403")?"认证失败,请检查 API Key 和 Secret。":n.includes("network")||n.includes("fetch failed")||n.includes("timeout")?"网络连接异常,请检查网络后重试。":n.includes("message is required")?"请输入内容后再发送。":t:"处理失败,请稍后重试。"},It=(e,t)=>{ke&&(clearTimeout(ke),ke=null),o.classList.remove("hidden","notice-error","notice-success"),o.classList.add("error"===e?"notice-error":"notice-success"),o.textContent=t,ke=setTimeout(()=>{At()},"success"===e?2200:4200)},Lt=async(e,t,n=null)=>{window.botApi?.writeErrorLog&&await window.botApi.writeErrorLog({source:e,message:String(t||""),details:n})},At=()=>{ke&&(clearTimeout(ke),ke=null),o.classList.add("hidden"),o.classList.remove("notice-error","notice-success"),o.textContent=""},Bt=(e,t)=>{Ie&&(clearTimeout(Ie),Ie=null),n.classList.remove("hidden","notice-error","notice-success"),n.classList.add("error"===e?"notice-error":"notice-success"),n.textContent=t,Ie=setTimeout(()=>{Ct()},"success"===e?2200:4200)},Ct=()=>{Ie&&(clearTimeout(Ie),Ie=null),n.classList.add("hidden"),n.classList.remove("notice-error","notice-success"),n.textContent=""},Nt=async(e,t)=>{if(e.disabled)return;e.disabled=!0,e.classList.add("is-loading");const n=e.textContent||"";try{await t()}catch(e){const t=kt(e instanceof Error?e.message:String(e));ht("[ui] action failed",{error:t}),It("error",t)}finally{e.classList.remove("is-loading"),e.textContent=n,e.disabled=!1}},$t=(e,t,n=[])=>{const i=e.getContext("2d");if(!i)return;const o=e.width,r=e.height;if(i.clearRect(0,0,o,r),!t.length)return;const a=Math.max(...t.map(e=>e.high)),s=Math.min(...t.map(e=>e.low)),c=n.map(e=>e.price).filter(e=>Number.isFinite(e)),l=c.length?Math.max(a,...c):a,d=c.length?Math.min(s,...c):s,m=Math.max(1e-8,l-d),u=10,g=10,f=o-10-62-8,p=r-10-24-8,y=Math.max(2,f/t.length),w=e=>g+(l-e)/m*p,b=e=>e>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:0}):e>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:3}):e.toLocaleString("en-US",{minimumFractionDigits:5,maximumFractionDigits:5});i.strokeStyle="#c9d4ea",i.strokeRect(.5,.5,o-1,r-1),i.strokeStyle="#e3eaf5",i.lineWidth=1;for(let e=0;e<=4;e+=1){const t=g+p/4*e;i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke()}t.forEach((e,t)=>{const n=u+t*y+.5*y,o=w(e.high),r=w(e.low),a=w(e.open),s=w(e.close),c=e.close>=e.open;i.strokeStyle=c?"#1f9d55":"#d64545",i.fillStyle=c?"#1f9d55":"#d64545",i.lineWidth=1,i.beginPath(),i.moveTo(n,o),i.lineTo(n,r),i.stroke();const l=n-.3*y,d=Math.max(1,.6*y),m=Math.min(a,s),g=Math.max(1,Math.abs(s-a));i.fillRect(l,m,d,g)});for(const e of n){const t=w(e.price);i.save(),i.setLineDash([4,4]),i.strokeStyle=e.color,i.lineWidth=1,i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke(),i.restore(),i.font="12px Segoe UI";const n=`${e.label} ${b(e.price)}`,o=Math.ceil(i.measureText(n).width)+10,r=14,a=Math.max(12,Math.min(g+p-18,t-9));i.fillStyle=e.color,i.fillRect(r,a,o,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(n,r+5,a+9)}const S=t[t.length-1],x=S?.close;if(Number.isFinite(x)){const e=w(Number(x));i.save(),i.setLineDash([5,4]),i.strokeStyle="#2b79ff",i.lineWidth=1,i.beginPath(),i.moveTo(u,e),i.lineTo(u+f,e),i.stroke(),i.restore();const t=b(Number(x));i.font="12px Segoe UI";const n=Math.ceil(i.measureText(t).width)+10,o=u+f+6,r=Math.max(12,Math.min(g+p-18,e-9));i.fillStyle="#2b79ff",i.fillRect(o,r,n,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(t,o+5,r+9)}i.fillStyle="#66758d",i.font="12px Segoe UI",i.textBaseline="middle",i.textAlign="left";for(let e=0;e<=4;e+=1){const t=l-m/4*e,n=g+p/4*e;i.fillText(b(t),u+f+8,n)}const v=e=>{const t=new Date(e>1e12?e:1e3*e);if(Number.isNaN(t.getTime()))return"-";const n=String(t.getHours()).padStart(2,"0"),i=String(t.getMinutes()).padStart(2,"0");return`${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${n}:${i}`};i.textAlign="center",i.textBaseline="top";for(let e=0;e<=4;e+=1){const n=Math.min(t.length-1,Math.floor((t.length-1)*(e/4))),o=u+f*e/4;i.fillText(v(t[n]?.time||0),o,g+p+6)}},Ut=async()=>{if(!window.botApi?.getRuntimeInfo)return;const e=await window.botApi.getRuntimeInfo();e?.ok&&(He(e.status||"disconnected"),e.session?(_e(!0),d.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`):(_e(!1),d.textContent="local mode (not logged in)"),(e=>{m.innerHTML="";const t=Re(e),n=Re(t.runtime),i=t.session?Re(t.session):null,o=[["Bot ID",String(i?.botId||"-")],["User ID",String(i?.userId||"-")],["Email",String(i?.email||"-")],["Capabilities",Array.isArray(n.capabilities)?n.capabilities.map(e=>String(e)).join(", "):"-"],["GMO API State",String(n.gmoApiState||"unknown")],["AI Model",String(n.aiModel||"-")],["Login API",String(n.loginApiUrl||"-")]];for(const[e,t]of o){const n=document.createElement("div");n.className="runtime-key",n.textContent=e;const i=document.createElement("div");i.className="runtime-value",i.textContent=t,m.appendChild(n),m.appendChild(i)}})(e))};I.addEventListener("click",async()=>{await Nt(I,async()=>{if(!window.botApi?.saveApiConfig)return;const e={riskDailyLossLimitJpy:Number(b.value||"0"),errorLogDir:String(w.value||"").trim(),cryptoApiKey:String(S.value||"").trim(),cryptoApiSecret:String(x.value||"").trim(),fxApiKey:String(v.value||"").trim(),fxApiSecret:String(h.value||"").trim(),aiModel:String(L.value||"gpt-4.1-mini").trim()||"gpt-4.1-mini",openAiApiKey:String(A.value||"").trim()},t=await window.botApi.saveApiConfig(e);if(!t.ok)return ht(`[trade-config] save failed: ${t.error||"unknown"}`),It("error",kt(t.error)),void await Lt("trade-config.save",kt(t.error),t);ht("[trade-config] saved"),It("success","配置已保存到本机。"),await Ut()})}),C.addEventListener("click",async()=>{await Nt(C,async()=>{if(!window.botApi?.saveApiConfig)return;const e={riskDailyLossLimitJpy:Number(b.value||"0"),errorLogDir:String(w.value||"").trim(),cryptoApiKey:String(S.value||"").trim(),cryptoApiSecret:String(x.value||"").trim(),fxApiKey:String(v.value||"").trim(),fxApiSecret:String(h.value||"").trim(),aiModel:String(L.value||"gpt-4.1-mini").trim()||"gpt-4.1-mini",openAiApiKey:String(A.value||"").trim()},t=await window.botApi.saveApiConfig(e);if(!t.ok)return ht(`[ai-config] save failed: ${t.error||"unknown"}`),It("error",kt(t.error)),void await Lt("ai-config.save",kt(t.error),t);ht("[ai-config] saved"),It("success","AI 设置已保存。"),await Ut()})}),B.addEventListener("click",()=>{const e="password"===A.type?"text":"password";A.type=e,B.textContent="password"===e?"显示":"隐藏"}),E.addEventListener("click",()=>{const e="password"===x.type?"text":"password";x.type=e,E.textContent="password"===e?"显示":"隐藏"}),k.addEventListener("click",()=>{const e="password"===h.type?"text":"password";h.type=e,k.textContent="password"===e?"显示":"隐藏"}),e.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi)return;Ct();const t=a.value.trim(),n=r.value.trim(),i=s.value;if(t&&n&&i){$.disabled=!0,$.textContent="Loading...";try{const e=await window.botApi.login(t,i,n,c.checked);if(!e.ok)return ht(`[ui] login failed: ${e.error||"unknown"}`),void Bt("error",kt(e.error));ht("[ui] login success"),Bt("success","登录成功。"),It("success","登录成功。"),await Ut()}finally{$.disabled=!1,$.textContent="Login"}}}),N.addEventListener("click",async()=>{await Nt(N,async()=>{if(!window.botApi)return;const e=await window.botApi.logout();if(!e.ok)return ht(`[ui] logout failed: ${e.error||"unknown"}`),It("error",kt(e.error)),void await Lt("auth.logout",kt(e.error),e);ht("[ui] logout"),It("success","已登出。"),_e(!1),d.textContent="local mode (not logged in)",await Ut()})});const Mt=async({market:e,symbol:t,interval:n,canvas:i})=>{if(!window.botApi)return;const o=await window.botApi.openGmoKlineWindow({symbol:t,interval:n,market:e});if(!o.ok)return ht(`[trade] open ${e} kline failed: ${o.error||"unknown"}`),It("error",kt(o.error)),void await Lt(`${e}.kline`,kt(o.error),o);const r=Array.isArray(o.candles)?o.candles:[],a=at(e);a.symbol=String(o.symbol||t||"").toUpperCase(),a.interval=n,a.candles=r.map(e=>({time:Number(e.time||0),open:Number(e.open||0),high:Number(e.high||0),low:Number(e.low||0),close:Number(e.close||0)})),a.lastFetchBucket=Math.floor(Date.now()/rt(n)),$t(i,a.candles,lt(e)),ht("[trade] kline rendered",{market:e,symbol:o.symbol||t,interval:o.interval||n,candles:r.length}),At()},Dt=async()=>{await Mt({market:"coin",symbol:he,interval:xe,canvas:P})},Ft=async()=>{await Mt({market:"fx",symbol:Ee,interval:ve,canvas:T})},Pt=({container:e,getCurrent:t,setCurrent:n,onChange:i})=>{const o=Array.from(e.querySelectorAll(".interval-btn"));for(const e of o)e.addEventListener("click",async()=>{const r=String(e.dataset.interval||"").trim();if(r&&r!==t()){n(r);for(const t of o)t.classList.toggle("is-active",t===e);await i()}})};U.addEventListener("change",async()=>{he=String(U.value||"BTC_JPY").trim().toUpperCase(),ot("coin",he);try{await it("coin",he),await Dt(),await bt("coin")}catch(e){const t=kt(e instanceof Error?e.message:String(e));It("error",t)}}),M.addEventListener("change",async()=>{Ee=String(M.value||"USD_JPY").trim().toUpperCase(),ot("fx",Ee);try{await it("fx",Ee),await Ft(),await bt("fx")}catch(e){const t=kt(e instanceof Error?e.message:String(e));It("error",t)}}),Pt({container:D,getCurrent:()=>xe,setCurrent:e=>{xe=e},onChange:Dt}),Pt({container:F,getCurrent:()=>ve,setCurrent:e=>{ve=e},onChange:Ft}),ee.addEventListener("click",async()=>{await Nt(ee,async()=>{await pt("coin")})}),te.addEventListener("click",async()=>{await Nt(te,async()=>{await pt("fx")})}),_.addEventListener("input",()=>{nt("coin")}),H.addEventListener("input",()=>{nt("fx")}),me.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeCoinPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=Xe(String(n.dataset.size||""));o&&Number.isFinite(s)&&!(s<=0)&&c?await Nt(n,async()=>{const e=await i.closeCoinPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=kt(e?.error||"coin close order failed");return ht("[coin] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),It("error",t),void await Lt("coin.closeOrder",t,e)}ht("[coin] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),It("success","決済注文を送信しました。"),await pt("coin"),await bt("coin")}):It("error","決済対象データが不正です。")}),ge.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeFxPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=String(n.dataset.size||"").trim();o&&Number.isFinite(s)&&!(s<=0)&&c?await Nt(n,async()=>{const e=await i.closeFxPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=kt(e?.error||"fx close order failed");return ht("[fx] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),It("error",t),void await Lt("fx.closeOrder",t,e)}ht("[fx] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),It("success","決済注文を送信しました。"),await pt("fx"),await bt("fx")}):It("error","決済対象データが不正です。")}),W.addEventListener("click",async()=>{await xt("BUY",W)}),G.addEventListener("click",async()=>{await xt("SELL",G)}),Q.addEventListener("click",async()=>{await St("BUY",Q)}),V.addEventListener("click",async()=>{await St("SELL",V)}),fe.addEventListener("click",async()=>{await Nt(fe,async()=>{await bt("coin")})}),pe.addEventListener("click",async()=>{await Nt(pe,async()=>{await bt("coin")})}),ye.addEventListener("click",async()=>{await Nt(ye,async()=>{await bt("fx")})}),we.addEventListener("click",async()=>{await Nt(we,async()=>{await bt("fx")})}),f.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi?.sendChat)return;const t=String(p.value||"").trim();if(t){Et("user",t),p.value="",y.disabled=!0;try{const e=await window.botApi.sendChat(t);if(!e.ok)return Et("assistant",`Error: ${e.error||"unknown"}`),It("error",kt(e.error)),void await Lt("ai.chat",kt(e.error),e);Et("assistant",String(e.answer||"")),At()}catch(e){const t=kt(e instanceof Error?e.message:String(e));Et("assistant",`Error: ${t}`),It("error",t),await Lt("ai.chat.exception",t,e)}finally{y.disabled=!1}}}),window.addEventListener("error",e=>{const t=kt(e.error?.message||e.message||"Unknown UI error");ht("[ui] uncaught error",{error:t}),It("error",t),Lt("ui.error",t,{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}),window.addEventListener("unhandledrejection",e=>{const t=e.reason instanceof Error?e.reason.message:String(e.reason||"Unhandled promise rejection"),n=kt(t);ht("[ui] unhandled rejection",{error:n}),It("error",n),Lt("ui.unhandledrejection",n,e.reason)}),window.botApi&&(window.botApi.onStatus(e=>{He(String(e.status||""))}),window.botApi.onChatEvent(e=>{if(!e)return;const t=Re(e);if("line_user"===t.type){const e=t.lineUserId?`LINE(${String(t.lineUserId)})`:"LINE";return void Et("line-user",String(t.text||""),e)}if("line_assistant"===t.type){const e=t.lineUserId?`AI->LINE(${String(t.lineUserId)})`:"AI->LINE";Et("line-assistant",String(t.text||""),e)}}));const Tt=async()=>{if(Be&&!Ce&&!Ne){Ne=!0;try{await async function(){ot("coin",he),ot("fx",Ee);try{await it("coin",he)}catch(e){const t=kt(e instanceof Error?e.message:String(e));ht("[coin] quote fetch failed",{error:t}),It("error",t)}try{await it("fx",Ee)}catch(e){const t=kt(e instanceof Error?e.message:String(e));ht("[fx] quote fetch failed",{error:t}),It("error",t)}if(await Dt(),await Ft(),window.botApi?.getSymbolRules)try{const e=await window.botApi.getSymbolRules({market:"coin"});if(e?.ok&&Array.isArray(e.rules)){Oe.clear();for(const t of e.rules){const e=String(t.symbol||"").trim().toUpperCase();e&&Oe.set(e,{minOrderSize:String(t.minOrderSize||""),maxOrderSize:String(t.maxOrderSize||""),sizeStep:String(t.sizeStep||"")})}}}catch(e){ht("[coin] symbol rules fetch failed",{error:e instanceof Error?e.message:String(e)})}}(),await pt(),await bt(),Le||(Le=setInterval(()=>{gt("coin")},1e3)),Ae||(Ae=setInterval(()=>{gt("fx")},1e3)),Ce=!0}finally{Ne=!1}}};_e(!1),(async()=>{if(!window.botApi?.getSavedProfile)return;const e=await window.botApi.getSavedProfile();e?.ok&&e.profile&&(r.value=e.profile.botId||"",a.value=e.profile.email||"",s.value=e.profile.password||"",c.checked=Boolean(e.profile.remember))})(),(async()=>{if(!window.botApi?.getApiConfig)return;const e=await window.botApi.getApiConfig();e?.ok&&e.config&&(w.value=e.config.errorLogDir||"",b.value=String(e.config.riskDailyLossLimitJpy||5e4),S.value=e.config.cryptoApiKey||"",x.value=e.config.cryptoApiSecret||"",v.value=e.config.fxApiKey||"",h.value=e.config.fxApiSecret||"",L.value=e.config.aiModel||"gpt-4.1-mini",A.value=e.config.openAiApiKey||"")})(),Ut();export{};
|
|
1
|
+
const e=document.getElementById("login-form"),t=document.getElementById("login-screen"),n=document.getElementById("login-notice"),i=document.getElementById("app-shell"),o=document.getElementById("user-notice"),r=document.getElementById("bot-id"),a=document.getElementById("email"),s=document.getElementById("password"),c=document.getElementById("remember-me"),l=document.getElementById("status"),d=document.getElementById("session"),m=document.getElementById("runtime-info"),u=document.getElementById("skill-state-list"),g=document.getElementById("tab-btn-skill-gmo-coin"),f=document.getElementById("tab-btn-skill-gmo-fx"),p=document.getElementById("tab-btn-skill-browser"),y=document.getElementById("coin-skill-title"),b=document.getElementById("fx-skill-title"),w=document.getElementById("browser-skill-title"),x=document.getElementById("coin-skill-package"),k=document.getElementById("fx-skill-package"),h=document.getElementById("browser-skill-package"),S=document.getElementById("coin-skill-enabled"),v=document.getElementById("fx-skill-enabled"),E=document.getElementById("browser-skill-enabled"),C=document.getElementById("coin-skill-version"),I=document.getElementById("fx-skill-version"),A=document.getElementById("browser-skill-version"),B=document.getElementById("browser-skill-tools"),L=document.getElementById("logs"),N=document.getElementById("messages"),$=document.getElementById("chat-form"),U=document.getElementById("chat-input"),M=document.getElementById("send-btn"),P=document.getElementById("error-log-dir"),T=document.getElementById("risk-daily-loss-limit-jpy"),F=document.getElementById("crypto-api-key"),D=document.getElementById("crypto-api-secret"),z=document.getElementById("fx-api-key"),O=document.getElementById("fx-api-secret"),j=document.getElementById("toggle-crypto-secret-btn"),q=document.getElementById("toggle-fx-secret-btn"),J=document.getElementById("save-api-config-btn"),Y=document.getElementById("ai-model"),R=document.getElementById("save-ai-config-btn"),H=document.getElementById("codex-login-btn"),_=document.getElementById("codex-copy-login-btn"),K=document.getElementById("codex-refresh-auth-btn"),W=document.getElementById("codex-login-command"),G=document.getElementById("logout-btn"),V=document.getElementById("login-btn"),Q=document.getElementById("coin-symbol-select"),X=document.getElementById("fx-symbol-select"),Z=document.getElementById("coin-kline-intervals"),ee=document.getElementById("fx-kline-intervals"),te=document.getElementById("coin-kline-canvas"),ne=document.getElementById("fx-kline-canvas"),ie=document.getElementById("coin-market-icon"),oe=document.getElementById("fx-market-icon"),re=document.getElementById("coin-order-bid"),ae=document.getElementById("coin-order-ask"),se=document.getElementById("coin-order-spread"),ce=document.getElementById("fx-order-bid"),le=document.getElementById("fx-order-ask"),de=document.getElementById("fx-order-spread"),me=document.getElementById("coin-order-qty"),ue=document.getElementById("fx-order-qty"),ge=document.getElementById("coin-buy-btn"),fe=document.getElementById("coin-sell-btn"),pe=document.getElementById("fx-buy-btn"),ye=document.getElementById("fx-sell-btn"),be=document.getElementById("coin-required-amount"),we=document.getElementById("fx-required-amount"),xe=document.getElementById("coin-account-info-refresh-btn"),ke=document.getElementById("fx-account-info-refresh-btn"),he=document.getElementById("coin-account-pnl"),Se=document.getElementById("coin-account-margin"),ve=document.getElementById("coin-account-available"),Ee=document.getElementById("coin-account-margin-ratio"),Ce=document.getElementById("fx-account-pnl"),Ie=document.getElementById("fx-account-margin"),Ae=document.getElementById("fx-account-available"),Be=document.getElementById("fx-account-margin-ratio"),Le=document.getElementById("coin-position-summary-body"),Ne=document.getElementById("coin-position-list-body"),$e=document.getElementById("fx-position-summary-body"),Ue=document.getElementById("fx-position-list-body"),Me=document.getElementById("coin-position-summary-refresh-btn"),Pe=document.getElementById("coin-position-list-refresh-btn"),Te=document.getElementById("fx-position-summary-refresh-btn"),Fe=document.getElementById("fx-position-list-refresh-btn"),De=Array.from(document.querySelectorAll(".tab-btn")),ze=Array.from(document.querySelectorAll(".tab-panel"));let Oe="15m",je="15m",qe=String(Q?.value||"BTC_JPY").trim().toUpperCase(),Je=String(X?.value||"USD_JPY").trim().toUpperCase(),Ye=null,Re=null,He=null,_e=null,Ke=!1,We=!1,Ge=!1,Ve=!1,Qe=!1,Xe="",Ze="",et=0,tt=0,nt=null,it=null;const ot=new Map,rt={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1},at={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1};let st=[],ct=[];const lt=e=>e&&"object"==typeof e?e:{},dt=e=>{for(const t of De)t.classList.toggle("is-active",t.dataset.tab===e);for(const t of ze)t.classList.toggle("is-active",t.id===`tab-${e}`)};for(const e of De)e.addEventListener("click",()=>dt(e.dataset.tab||"coin"));const mt=e=>{const n=Ke!==e;Ke=e,t.classList.toggle("hidden",e),i.classList.toggle("hidden",!e),e&&Wt(),e?n&&on():(Mt(),We=!1)},ut=e=>{const t=String(e||"disconnected").trim().toLowerCase();l.textContent=t,l.classList.remove("status-connected","status-disconnected","status-connecting"),"connected"!==t?"reconnecting"!==t&&"connecting"!==t?l.classList.add("status-disconnected"):l.classList.add("status-connecting"):l.classList.add("status-connected")},gt=(e,t=6)=>{if(null===e||!Number.isFinite(e))return"-";const n=Math.abs(e);return n>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:3}):n>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:Math.min(t,5)}):e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:t})},ft=e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,pt=e=>`${e>0?"+":""}${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,yt=e=>!Number.isFinite(e)||e<=0||e>1e5?"- %":(e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:2})} %`)(e),bt=e=>{const t=String(e||"").trim().replace(/,/g,"");if(!t)return"";if(!/^\d+(\.\d+)?$/.test(t))return"";if(!t.includes("."))return t;return t.replace(/(\.\d*?[1-9])0+$/,"$1").replace(/\.0+$/,"").replace(/\.$/,"")},wt=e=>{const t=String(e||"").trim(),n=t.indexOf(".");return n>=0?t.length-n-1:0},xt=(e,t)=>{const n=String(e||"").trim(),i=n.startsWith("-"),o=i?n.slice(1):n,[r,a=""]=o.split("."),s=(a+"0".repeat(t)).slice(0,t),c=BigInt((r||"0")+s);return i?-c:c},kt=({symbol:e,quote:t,orderBidNode:n,orderAskNode:i,orderSpreadNode:o})=>{if(!t)return n.textContent="-",i.textContent="-",void(o.textContent="-");const r=gt(t.bid),a=gt(t.ask);n.textContent=r,i.textContent=a,o.textContent=gt(t.spread,8)},ht=e=>{if("coin"===e){const e=Number(String(me.value||"").trim()),t=nt;return!Number.isFinite(e)||e<=0||null===t||!Number.isFinite(t)?void(be.textContent="- 円"):void(be.textContent=ft(e*t/2))}const t=Number(String(ue.value||"").trim()),n=it;!Number.isFinite(t)||t<=0||null===n||!Number.isFinite(n)?we.textContent="- 円":we.textContent=ft(t*n/20)},St=async(e,t)=>{if(!window.botApi?.getMarketQuotes)return null;const n=String(t||"").trim().toUpperCase(),i=await window.botApi.getMarketQuotes({market:e,symbols:[n]});if(!i?.ok)throw new Error(String(i?.error||"quote api failed"));const o=(i.quotes||[]).find(e=>String(e.symbol||"").toUpperCase()===n)||null;return"coin"===e?(kt({symbol:n,quote:o,orderBidNode:re,orderAskNode:ae,orderSpreadNode:se}),nt=o?.ask??null,ht("coin")):(kt({symbol:n,quote:o,orderBidNode:ce,orderAskNode:le,orderSpreadNode:de}),it=o?.ask??null,ht("fx")),o},vt=(e,t)=>{const n=String(t||"").trim().toUpperCase();if(!n)return;if("coin"===e){const e=n.replace("_","-").toLowerCase();return void(ie.src=`https://coin.z.com/jp/member/imgs/icon-${e}.svg`)}const[i,o]=n.split("_"),r="JPY"===o?i:`${i}_${o}`;oe.src=`https://coin.z.com/jp/member/imgs/fx/icon_${r}.svg`},Et=e=>"5m"===e?3e5:"15m"===e?9e5:"30m"===e?18e5:"1h"===e?36e5:6e4,Ct=e=>"coin"===e?rt:at,It=e=>"coin"===e?te:ne,At=e=>"coin"===e?qe:Je,Bt=e=>{const t=At(e),n=("coin"===e?st:ct).filter(e=>String(e.symbol||"").trim().toUpperCase()===t),i=[];for(const e of n){const t=String(e.side||"").trim().toUpperCase(),n=Number(e.averagePositionRate||0);if(!Number.isFinite(n)||n<=0)continue;const o="BUY"===t;i.push({price:n,label:o?"BUY Avg":"SELL Avg",color:o?"#1f9d55":"#e03131"})}return i},Lt=e=>{const t=Ct(e);t.candles.length&&Vt(It(e),t.candles,Bt(e))},Nt=(e,t)=>{if(!t)return;const n=Ct(e);if(!n.candles.length)return;if(n.symbol!==t.symbol)return;const i=n.candles[n.candles.length-1],o=null!==t.bid&&null!==t.ask?(t.bid+t.ask)/2:null!==t.ask?t.ask:t.bid;null!==o&&Number.isFinite(o)&&(i.close=o,i.high=Math.max(i.high,o),i.low=Math.min(i.low,o),Vt(It(e),n.candles,Bt(e)))},$t=async e=>{const t=Ct(e);if(!t.interval||!t.symbol||t.fetching)return;if(t.symbol!==At(e))return;if(!(Math.floor(Date.now()/Et(t.interval))<=t.lastFetchBucket)){t.fetching=!0;try{"coin"===e?await en():await tn()}finally{t.fetching=!1}}},Ut=async e=>{if("coin"!==e){if(!Qe){Qe=!0;try{const e=await St("fx",Je);Nt("fx",e),await $t("fx")}catch(e){const t=Yt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==Ze||n-tt>15e3)&&(qt("[fx] quote poll failed",{error:t}),Ze=t,tt=n)}finally{Qe=!1}}}else{if(Ve)return;Ve=!0;try{const e=await St("coin",qe);Nt("coin",e),await $t("coin")}catch(e){const t=Yt(e instanceof Error?e.message:String(e)),n=Date.now();(t!==Xe||n-et>15e3)&&(qt("[coin] quote poll failed",{error:t}),Xe=t,et=n)}finally{Ve=!1}}},Mt=()=>{He&&(clearInterval(He),He=null),_e&&(clearInterval(_e),_e=null)},Pt=async e=>{if(window.botApi?.getAccountMetrics){if(!e||"coin"===e){const e=await window.botApi.getAccountMetrics({market:"coin"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);he.textContent=n>0?pt(i):ft(0),Se.textContent=ft(n),ve.textContent=ft(t),Ee.textContent=yt(Number(e.marginRatio||0))}else qt("[coin] account metrics fetch failed",{error:e?.error||"unknown error"})}if(!e||"fx"===e){const e=await window.botApi.getAccountMetrics({market:"fx"});if(e?.ok){const t=Number(e.availableAmount||0),n=Number(e.margin||0),i=Number(e.pnlWithSwap||0);Ce.textContent=n>0?pt(i):ft(0),Ie.textContent=ft(n),Ae.textContent=ft(t),Be.textContent=yt(Number(e.marginRatio||0))}else qt("[fx] account metrics fetch failed",{error:e?.error||"unknown error"})}}},Tt=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").toUpperCase(),o=n.replace("_","/"),r=String(i.side||"-"),a=String(i.size||"").trim(),s=Number(a||0),c=Number(i.price||0),l=Number(i.lossGain||0),d=Number(i.totalSwap||0),m=String(i.timestamp||"-"),u=Number(i.positionId||0),g=Number.isFinite(u)&&u>0&&a?`data-market="${e}" data-symbol="${n}" data-side="${String(r||"").toUpperCase()}" data-position-id="${u}" data-size="${a}"`:"disabled",f=document.createElement("tr");f.innerHTML=`\n <td><button type="button" class="close-btn" ${g}>決済</button></td>\n <td>${o}<br />${r}</td>\n <td>${s.toLocaleString("ja-JP")}</td>\n <td>${c.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${pt(l)}<br />${pt(d)}</td>\n <td>${m.replace("T"," ").slice(0,19)}</td>\n `,t.appendChild(f)}}else t.innerHTML='<tr><td colspan="6" class="empty-cell">対象のお取引はございません。</td></tr>'},Ft=(e,t,n)=>{if(n.length){t.innerHTML="";for(const i of n){const n=String(i.symbol||"").replace("_","/"),o=String(i.side||"-"),r=Number("fx"===e?i.sumPositionSize||0:i.sumPositionQuantity||0),a=Number(i.averagePositionRate||0),s=Number(i.positionLossGain||0),c=Number("fx"===e&&i.sumTotalSwap||0),l=document.createElement("tr");l.innerHTML=`\n <td><button type="button" class="close-btn">決済</button></td>\n <td>${n}<br />${o}</td>\n <td>${r.toLocaleString("ja-JP")}</td>\n <td>${a.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${pt(s)}<br />${pt(c)}</td>\n `,t.appendChild(l)}}else t.innerHTML='<tr><td colspan="5" class="empty-cell">対象のお取引はございません。</td></tr>'},Dt=async e=>{if(window.botApi?.getPositionSummary&&window.botApi?.getOpenPositions){if(!e||"coin"===e){const e=await window.botApi.getPositionSummary({market:"coin",symbol:qe});e?.ok?(st=Array.isArray(e.items)?e.items:[],Ft("coin",Le,st),Lt("coin")):(qt("[coin] position summary fetch failed",{error:e?.error||"unknown error"}),st=[],Ft("coin",Le,[]),Lt("coin"))}if(!e||"fx"===e){const e=await window.botApi.getPositionSummary({market:"fx"});e?.ok?(ct=Array.isArray(e.items)?e.items:[],Ft("fx",$e,ct),Lt("fx")):(qt("[fx] position summary fetch failed",{error:e?.error||"unknown error"}),ct=[],Ft("fx",$e,[]),Lt("fx"))}if(!e||"coin"===e){const e=await window.botApi.getOpenPositions({market:"coin",symbol:qe,page:1,count:100});e?.ok?Tt("coin",Ne,Array.isArray(e.items)?e.items:[]):(qt("[coin] open positions fetch failed",{error:e?.error||"unknown error"}),Tt("coin",Ne,[]))}if(!e||"fx"===e){const e=await window.botApi.getOpenPositions({market:"fx",count:100});e?.ok?Tt("fx",Ue,Array.isArray(e.items)?e.items:[]):(qt("[fx] open positions fetch failed",{error:e?.error||"unknown error"}),Tt("fx",Ue,[]))}}},zt=async(e,t)=>{const n=window.botApi;if(!n?.placeFxOrder)return;const i=String(ue.value||"").trim();i?await Gt(t,async()=>{const t=await n.placeFxOrder({symbol:Je,side:e,size:i});if(!t?.ok){const n=Yt(t?.error||"fx order failed");return qt("[fx] order failed",{symbol:Je,side:e,size:i,error:n}),Rt("error",n),void await Ht("fx.order",n,t)}qt("[fx] order placed",{symbol:Je,side:e,size:i,result:t.result||null}),Rt("success",`${e} 注文を送信しました。`),await Pt("fx"),await Dt("fx")}):Rt("error","数量を入力してください。")},Ot=async(e,t)=>{const n=window.botApi;if(!n?.placeCoinOrder)return;const i=bt(String(me.value||""));if(!i)return void Rt("error","数量の形式が不正です。");const o=((e,t)=>{const n=ot.get(e);if(!n)return{ok:!0};const i=bt(n.minOrderSize),o=bt(n.sizeStep),r=bt(n.maxOrderSize);if(!i||!o)return{ok:!0};const a=Math.max(wt(t),wt(i),wt(o),wt(r)),s=xt(t,a),c=xt(i,a),l=xt(o,a);if(s<c)return{ok:!1,reason:`${e} の最小数量は ${i} です。`};if(l>0n&&s%l!==0n)return{ok:!1,reason:`${e} の数量刻みは ${o} です。`};if(r&&s>xt(r,a))return{ok:!1,reason:`${e} の最大数量は ${r} です。`};return{ok:!0}})(qe,i);o.ok?await Gt(t,async()=>{const t=await n.placeCoinOrder({symbol:qe,side:e,size:i});if(!t?.ok){const n=Yt(t?.error||"coin order failed");return qt("[coin] order failed",{symbol:qe,side:e,size:i,error:n}),Rt("error",n),void await Ht("coin.order",n,t)}qt("[coin] order placed",{symbol:qe,side:e,size:i,result:t.result||null}),Rt("success",`${e} 注文を送信しました。`),await Pt("coin"),await Dt("coin")}):Rt("error",o.reason||"数量が取引ルールに一致しません。")},jt=e=>{const t=String(e||"").toLowerCase();return t.includes("debug")||t.includes("[debug]")?"debug":t.includes("error")||t.includes("failed")||t.includes("exception")?"error":"info"},qt=(e,t=null,n=null)=>{if(window.botApi?.writeLog&&window.botApi.writeLog({level:jt(e),source:"ui.log",message:e,details:{data:t,ts:n||(new Date).toISOString()}}).catch(()=>{}),!L)return;const i=document.createElement("div");i.className="log-entry";const o=document.createElement("div");o.className="log-line";const r=document.createElement("span");if(r.className="log-ts",r.textContent=n||(new Date).toISOString(),o.appendChild(r),o.append(document.createTextNode(e||"")),i.appendChild(o),null!=t&&""!==t){const e=document.createElement("pre");e.className="status-output",e.textContent="string"==typeof t?t:JSON.stringify(t,null,2),i.appendChild(e)}for(L.appendChild(i);L.children.length>300;)L.removeChild(L.firstChild);L.scrollTop=L.scrollHeight},Jt=(e,t,n="")=>{const i=document.createElement("div");i.className=`msg ${e}`;let o=n||("user"===e?"You":"Assistant");"line-user"===e&&(o=n||"LINE User"),"line-assistant"===e&&(o=n||"AI"),i.textContent=`${o}: ${t}`,N.appendChild(i),N.scrollTop=N.scrollHeight},Yt=e=>{const t=String(e||"").trim(),n=t.toLowerCase();return t?n.includes("api key")&&n.includes("missing")?"API Key 未设置,请先在 Config 页面保存。":n.includes("auth")||n.includes("401")||n.includes("403")?"认证失败,请检查 API Key 和 Secret。":n.includes("network")||n.includes("fetch failed")||n.includes("timeout")?"网络连接异常,请检查网络后重试。":n.includes("message is required")?"请输入内容后再发送。":t:"处理失败,请稍后重试。"},Rt=(e,t)=>{Ye&&(clearTimeout(Ye),Ye=null),o.classList.remove("hidden","notice-error","notice-success"),o.classList.add("error"===e?"notice-error":"notice-success"),o.textContent=t,Ye=setTimeout(()=>{_t()},"success"===e?2200:4200)},Ht=async(e,t,n=null)=>{window.botApi?.writeErrorLog&&await window.botApi.writeErrorLog({source:e,message:String(t||""),details:n})},_t=()=>{Ye&&(clearTimeout(Ye),Ye=null),o.classList.add("hidden"),o.classList.remove("notice-error","notice-success"),o.textContent=""},Kt=(e,t)=>{Re&&(clearTimeout(Re),Re=null),n.classList.remove("hidden","notice-error","notice-success"),n.classList.add("error"===e?"notice-error":"notice-success"),n.textContent=t,Re=setTimeout(()=>{Wt()},"success"===e?2200:4200)},Wt=()=>{Re&&(clearTimeout(Re),Re=null),n.classList.add("hidden"),n.classList.remove("notice-error","notice-success"),n.textContent=""},Gt=async(e,t)=>{if(e.disabled)return;e.disabled=!0,e.classList.add("is-loading");const n=e.textContent||"";try{await t()}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[ui] action failed",{error:t}),Rt("error",t)}finally{e.classList.remove("is-loading"),e.textContent=n,e.disabled=!1}},Vt=(e,t,n=[])=>{const i=e.getContext("2d");if(!i)return;const o=e.width,r=e.height;if(i.clearRect(0,0,o,r),!t.length)return;const a=Math.max(...t.map(e=>e.high)),s=Math.min(...t.map(e=>e.low)),c=n.map(e=>e.price).filter(e=>Number.isFinite(e)),l=c.length?Math.max(a,...c):a,d=c.length?Math.min(s,...c):s,m=Math.max(1e-8,l-d),u=10,g=10,f=o-10-62-8,p=r-10-24-8,y=Math.max(2,f/t.length),b=e=>g+(l-e)/m*p,w=e=>e>=1e3?e.toLocaleString("en-US",{maximumFractionDigits:0}):e>=1?e.toLocaleString("en-US",{minimumFractionDigits:3,maximumFractionDigits:3}):e.toLocaleString("en-US",{minimumFractionDigits:5,maximumFractionDigits:5});i.strokeStyle="#c9d4ea",i.strokeRect(.5,.5,o-1,r-1),i.strokeStyle="#e3eaf5",i.lineWidth=1;for(let e=0;e<=4;e+=1){const t=g+p/4*e;i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke()}t.forEach((e,t)=>{const n=u+t*y+.5*y,o=b(e.high),r=b(e.low),a=b(e.open),s=b(e.close),c=e.close>=e.open;i.strokeStyle=c?"#1f9d55":"#d64545",i.fillStyle=c?"#1f9d55":"#d64545",i.lineWidth=1,i.beginPath(),i.moveTo(n,o),i.lineTo(n,r),i.stroke();const l=n-.3*y,d=Math.max(1,.6*y),m=Math.min(a,s),g=Math.max(1,Math.abs(s-a));i.fillRect(l,m,d,g)});for(const e of n){const t=b(e.price);i.save(),i.setLineDash([4,4]),i.strokeStyle=e.color,i.lineWidth=1,i.beginPath(),i.moveTo(u,t),i.lineTo(u+f,t),i.stroke(),i.restore(),i.font="12px Segoe UI";const n=`${e.label} ${w(e.price)}`,o=Math.ceil(i.measureText(n).width)+10,r=14,a=Math.max(12,Math.min(g+p-18,t-9));i.fillStyle=e.color,i.fillRect(r,a,o,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(n,r+5,a+9)}const x=t[t.length-1],k=x?.close;if(Number.isFinite(k)){const e=b(Number(k));i.save(),i.setLineDash([5,4]),i.strokeStyle="#2b79ff",i.lineWidth=1,i.beginPath(),i.moveTo(u,e),i.lineTo(u+f,e),i.stroke(),i.restore();const t=w(Number(k));i.font="12px Segoe UI";const n=Math.ceil(i.measureText(t).width)+10,o=u+f+6,r=Math.max(12,Math.min(g+p-18,e-9));i.fillStyle="#2b79ff",i.fillRect(o,r,n,18),i.fillStyle="#ffffff",i.textAlign="left",i.textBaseline="middle",i.fillText(t,o+5,r+9)}i.fillStyle="#66758d",i.font="12px Segoe UI",i.textBaseline="middle",i.textAlign="left";for(let e=0;e<=4;e+=1){const t=l-m/4*e,n=g+p/4*e;i.fillText(w(t),u+f+8,n)}const h=e=>{const t=new Date(e>1e12?e:1e3*e);if(Number.isNaN(t.getTime()))return"-";const n=String(t.getHours()).padStart(2,"0"),i=String(t.getMinutes()).padStart(2,"0");return`${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")} ${n}:${i}`};i.textAlign="center",i.textBaseline="top";for(let e=0;e<=4;e+=1){const n=Math.min(t.length-1,Math.floor((t.length-1)*(e/4))),o=u+f*e/4;i.fillText(h(t[n]?.time||0),o,g+p+6)}},Qt=(e,t)=>{const{tabBtn:n,titleNode:i,packageNode:o,enabledNode:r,versionNode:a,fallbackTitle:s,fallbackPackage:c}=t,l=e?.displayName||s,d=e?.name||c,m=!e||Boolean(e.enabled),u=String(e?.installStatus||"builtin").trim()||"builtin",g=String(e?.configuredVersion||e?.version||"builtin").trim()||"builtin";n&&(n.textContent=d.replace("@ai.weget.jp/",""),n.classList.toggle("is-disabled",!m)),i&&(i.textContent=l),o&&(o.textContent=d),r&&(r.textContent=m?"enabled":"disabled",r.classList.toggle("is-disabled",!m)),a&&(a.textContent=`${u} · ${g}`)},Xt=async()=>{if(!window.botApi?.getRuntimeInfo)return;const[e,t]=await Promise.all([window.botApi.getRuntimeInfo(),window.botApi.getSkills?window.botApi.getSkills():(async()=>({ok:!1,skills:[]}))()]);if(!e?.ok)return;ut(e.status||"disconnected"),e.session?(mt(!0),d.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`):(mt(!1),d.textContent="local mode (not logged in)"),(e=>{m.innerHTML="";const t=lt(e),n=lt(t.runtime),i=t.session?lt(t.session):null,o=[["Bot ID",String(i?.botId||"-")],["User ID",String(i?.userId||"-")],["Email",String(i?.email||"-")],["Capabilities",Array.isArray(n.capabilities)?n.capabilities.map(e=>String(e)).join(", "):"-"],["Active Skills",Array.isArray(n.activeSkills)&&n.activeSkills.map(e=>{const t=lt(e);return String(t.displayName||t.name||"").trim()}).filter(Boolean).join(", ")||"-"],["GMO FX API State",String(n.gmoFxApiState||"unknown")],["GMO Coin API State",String(n.gmoCoinApiState||"unknown")],["Codex Auth",String(n.codexAuthStatus||"unknown")],["Codex Auth Detail",String(n.codexAuthDetail||"-")],["Default Model",String(n.aiModel||"-")],["Login API",String(n.loginApiUrl||"-")]];for(const[e,t]of o){const n=document.createElement("div");n.className="runtime-key",n.textContent=e;const i=document.createElement("div");i.className="runtime-value",i.textContent=t,m.appendChild(n),m.appendChild(i)}})(e);const n=t?.ok&&t.skills||[];(e=>{if(!u)return;u.innerHTML="";const t=Array.isArray(e)?e:[];if(0===t.length){const e=document.createElement("div");return e.className="skill-empty",e.textContent="No managed skills are available in this bot host.",void u.appendChild(e)}for(const e of t){const t=document.createElement("section");t.className="skill-card"+(e.enabled?"":" is-disabled");const n=document.createElement("div");n.className="skill-card-head";const i=document.createElement("div");i.className="skill-card-title";const o=document.createElement("strong");o.textContent=e.displayName||e.name;const r=document.createElement("code");if(r.textContent=e.name,i.appendChild(o),i.appendChild(r),e.description){const t=document.createElement("div");t.className="muted",t.textContent=e.description,i.appendChild(t)}const a=document.createElement("div");a.className="skill-badges";const s=document.createElement("span");s.className="skill-badge "+(e.enabled?"enabled":"disabled"),s.textContent=e.enabled?"enabled":"disabled",a.appendChild(s);const c=document.createElement("span");c.className="skill-badge status",c.textContent=e.installStatus||"unknown",a.appendChild(c),n.appendChild(i),n.appendChild(a),t.appendChild(n);const l=e=>{const t=document.createElement("div");t.className="skill-card-section";const n=document.createElement("div");return n.className="skill-section-label",n.textContent=e,t.appendChild(n),t},d=l("Package"),m=document.createElement("code");m.className="skill-package-code",m.textContent=e.name,d.appendChild(m),t.appendChild(d);const g=l("Tools"),f=document.createElement("div");f.className="skill-tags";const p=Array.isArray(e.tools)?e.tools:[];if(p.length>0)for(const e of p){const t=document.createElement("span");t.className="skill-tag",t.textContent=e,f.appendChild(t)}else{const e=document.createElement("span");e.className="muted",e.textContent="No tools declared",f.appendChild(e)}g.appendChild(f),t.appendChild(g);const y=l("Permissions");if(Array.isArray(e.permissions)&&e.permissions.length>0){const t=document.createElement("div");t.className="skill-tags";for(const n of e.permissions){const e=document.createElement("span");e.className="skill-tag permissions",e.textContent=n,t.appendChild(e)}y.appendChild(t)}else{const e=document.createElement("span");e.className="muted",e.textContent="No permissions declared",y.appendChild(e)}t.appendChild(y);const b=l("State"),w=document.createElement("div");w.className="skill-meta";const x=[["Bundled Version",e.version||"-"],["Configured Version",e.configuredVersion||"-"],["UI",e.hasUi?"has ui":"no ui"],["Config Keys",String(Object.keys(e.configJson||{}).length)]];for(const[e,t]of x){const n=document.createElement("div");n.className="skill-meta-row";const i=document.createElement("span");i.textContent=e;const o=document.createElement("strong");o.textContent=t,n.appendChild(i),n.appendChild(o),w.appendChild(n)}b.appendChild(w),t.appendChild(b),u.appendChild(t)}})(n),Qt(n.find(e=>"@ai.weget.jp/skill-gmo-coin"===e.name),{tabBtn:g,titleNode:y,packageNode:x,enabledNode:S,versionNode:C,fallbackTitle:"GMO Coin Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-coin"}),Qt(n.find(e=>"@ai.weget.jp/skill-gmo-fx"===e.name),{tabBtn:f,titleNode:b,packageNode:k,enabledNode:v,versionNode:I,fallbackTitle:"GMO FX Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-fx"});const i=n.find(e=>"@ai.weget.jp/skill-browser"===e.name);Qt(i,{tabBtn:p,titleNode:w,packageNode:h,enabledNode:E,versionNode:A,fallbackTitle:"Browser Skill",fallbackPackage:"@ai.weget.jp/skill-browser"}),((e,t,n)=>{if(e){if(e.innerHTML="",0===t.length){const t=document.createElement("span");return t.className="muted",t.textContent=n,void e.appendChild(t)}for(const n of t){const t=document.createElement("span");t.className="skill-tag",t.textContent=n,e.appendChild(t)}}})(B,Array.isArray(i?.tools)?i.tools:[],"No browser tools declared.")};J.addEventListener("click",async()=>{await Gt(J,async()=>{if(!window.botApi?.saveFxApiConfig||!window.botApi.saveCoinApiConfig)return;const e={riskDailyLossLimitJpy:Number(T.value||"0"),errorLogDir:String(P.value||"").trim()},t={...e,fxApiKey:String(z.value||"").trim(),fxApiSecret:String(O.value||"").trim()},n={...e,cryptoApiKey:String(F.value||"").trim(),cryptoApiSecret:String(D.value||"").trim()},[i,o]=await Promise.all([window.botApi.saveFxApiConfig(t),window.botApi.saveCoinApiConfig(n)]);return i.ok?o.ok?(qt("[trade-config] saved"),Rt("success","配置已保存到本机。"),void await Xt()):(qt(`[trade-config] coin save failed: ${o.error||"unknown"}`),Rt("error",Yt(o.error)),void await Ht("trade-config.coin.save",Yt(o.error),o)):(qt(`[trade-config] fx save failed: ${i.error||"unknown"}`),Rt("error",Yt(i.error)),void await Ht("trade-config.fx.save",Yt(i.error),i))})}),R.addEventListener("click",async()=>{await Gt(R,async()=>{if(!window.botApi?.saveAiConfig)return;const e={aiModel:String(Y.value||"gpt-4.1-mini").trim()||"gpt-4.1-mini"},t=await window.botApi.saveAiConfig(e);if(!t.ok)return qt(`[ai-config] save failed: ${t.error||"unknown"}`),Rt("error",Yt(t.error)),void await Ht("ai-config.save",Yt(t.error),t);qt("[ai-config] saved"),Rt("success","AI 设置已保存。"),await Xt()})}),H.addEventListener("click",async()=>{await Gt(H,async()=>{if(!window.botApi?.startCodexLogin)return;const e=await window.botApi.startCodexLogin();if(!e.ok)return qt(`[codex] login launch failed: ${e.detail||"unknown"}`),Rt("error",Yt(e.detail)),void await Ht("codex.login.launch",Yt(e.detail),e);qt("[codex] login launched",{detail:e.detail}),Rt("success",e.detail||"Codex login started."),setTimeout(()=>{Xt()},1500)})}),_.addEventListener("click",async()=>{const e=String(W.textContent||"codex login --device-auth").trim();try{if(!await(async e=>{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0;const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly","true"),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select();const n=document.execCommand("copy");return document.body.removeChild(t),n})(e))return void Rt("error","无法复制命令,请手动执行。");qt("[codex] login command copied"),Rt("success","Codex 登录命令已复制。")}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[codex] copy login command failed",{error:t}),Rt("error",t)}}),K.addEventListener("click",async()=>{await Gt(K,async()=>{await Xt(),qt("[codex] auth status refreshed"),Rt("success","Codex 状态已刷新。")})}),j.addEventListener("click",()=>{const e="password"===D.type?"text":"password";D.type=e,j.textContent="password"===e?"显示":"隐藏"}),q.addEventListener("click",()=>{const e="password"===O.type?"text":"password";O.type=e,q.textContent="password"===e?"显示":"隐藏"}),e.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi)return;Wt();const t=a.value.trim(),n=r.value.trim(),i=s.value;if(t&&n&&i){V.disabled=!0,V.textContent="Loading...";try{const e=await window.botApi.login(t,i,n,c.checked);if(!e.ok)return qt(`[ui] login failed: ${e.error||"unknown"}`),void Kt("error",Yt(e.error));qt("[ui] login success"),Kt("success","登录成功。"),Rt("success","登录成功。"),await Xt()}finally{V.disabled=!1,V.textContent="Login"}}}),G.addEventListener("click",async()=>{await Gt(G,async()=>{if(!window.botApi)return;const e=await window.botApi.logout();if(!e.ok)return qt(`[ui] logout failed: ${e.error||"unknown"}`),Rt("error",Yt(e.error)),void await Ht("auth.logout",Yt(e.error),e);qt("[ui] logout"),Rt("success","已登出。"),mt(!1),d.textContent="local mode (not logged in)",await Xt()})});const Zt=async({market:e,symbol:t,interval:n,canvas:i})=>{if(!window.botApi)return;const o=await window.botApi.openGmoKlineWindow({symbol:t,interval:n,market:e});if(!o.ok)return qt(`[trade] open ${e} kline failed: ${o.error||"unknown"}`),Rt("error",Yt(o.error)),void await Ht(`${e}.kline`,Yt(o.error),o);const r=Array.isArray(o.candles)?o.candles:[],a=Ct(e);a.symbol=String(o.symbol||t||"").toUpperCase(),a.interval=n,a.candles=r.map(e=>({time:Number(e.time||0),open:Number(e.open||0),high:Number(e.high||0),low:Number(e.low||0),close:Number(e.close||0)})),a.lastFetchBucket=Math.floor(Date.now()/Et(n)),Vt(i,a.candles,Bt(e)),qt("[trade] kline rendered",{market:e,symbol:o.symbol||t,interval:o.interval||n,candles:r.length}),_t()},en=async()=>{await Zt({market:"coin",symbol:qe,interval:Oe,canvas:te})},tn=async()=>{await Zt({market:"fx",symbol:Je,interval:je,canvas:ne})},nn=({container:e,getCurrent:t,setCurrent:n,onChange:i})=>{const o=Array.from(e.querySelectorAll(".interval-btn"));for(const e of o)e.addEventListener("click",async()=>{const r=String(e.dataset.interval||"").trim();if(r&&r!==t()){n(r);for(const t of o)t.classList.toggle("is-active",t===e);await i()}})};Q.addEventListener("change",async()=>{qe=String(Q.value||"BTC_JPY").trim().toUpperCase(),vt("coin",qe);try{await St("coin",qe),await en(),await Dt("coin")}catch(e){const t=Yt(e instanceof Error?e.message:String(e));Rt("error",t)}}),X.addEventListener("change",async()=>{Je=String(X.value||"USD_JPY").trim().toUpperCase(),vt("fx",Je);try{await St("fx",Je),await tn(),await Dt("fx")}catch(e){const t=Yt(e instanceof Error?e.message:String(e));Rt("error",t)}}),nn({container:Z,getCurrent:()=>Oe,setCurrent:e=>{Oe=e},onChange:en}),nn({container:ee,getCurrent:()=>je,setCurrent:e=>{je=e},onChange:tn}),xe.addEventListener("click",async()=>{await Gt(xe,async()=>{await Pt("coin")})}),ke.addEventListener("click",async()=>{await Gt(ke,async()=>{await Pt("fx")})}),me.addEventListener("input",()=>{ht("coin")}),ue.addEventListener("input",()=>{ht("fx")}),Ne.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeCoinPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=bt(String(n.dataset.size||""));o&&Number.isFinite(s)&&!(s<=0)&&c?await Gt(n,async()=>{const e=await i.closeCoinPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=Yt(e?.error||"coin close order failed");return qt("[coin] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),Rt("error",t),void await Ht("coin.closeOrder",t,e)}qt("[coin] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),Rt("success","決済注文を送信しました。"),await Pt("coin"),await Dt("coin")}):Rt("error","決済対象データが不正です。")}),Ue.addEventListener("click",async e=>{const t=e.target,n=t?.closest("button.close-btn");if(!n||n.disabled)return;const i=window.botApi;if(!i?.closeFxPosition)return;const o=String(n.dataset.symbol||"").trim().toUpperCase(),r="SELL"===String(n.dataset.side||"").trim().toUpperCase()?"SELL":"BUY",a="BUY"===r?"SELL":"BUY",s=Number(n.dataset.positionId||0),c=String(n.dataset.size||"").trim();o&&Number.isFinite(s)&&!(s<=0)&&c?await Gt(n,async()=>{const e=await i.closeFxPosition({symbol:o,side:a,positionId:s,size:c});if(!e?.ok){const t=Yt(e?.error||"fx close order failed");return qt("[fx] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,error:t}),Rt("error",t),void await Ht("fx.closeOrder",t,e)}qt("[fx] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:c,result:e.result||null}),Rt("success","決済注文を送信しました。"),await Pt("fx"),await Dt("fx")}):Rt("error","決済対象データが不正です。")}),ge.addEventListener("click",async()=>{await Ot("BUY",ge)}),fe.addEventListener("click",async()=>{await Ot("SELL",fe)}),pe.addEventListener("click",async()=>{await zt("BUY",pe)}),ye.addEventListener("click",async()=>{await zt("SELL",ye)}),Me.addEventListener("click",async()=>{await Gt(Me,async()=>{await Dt("coin")})}),Pe.addEventListener("click",async()=>{await Gt(Pe,async()=>{await Dt("coin")})}),Te.addEventListener("click",async()=>{await Gt(Te,async()=>{await Dt("fx")})}),Fe.addEventListener("click",async()=>{await Gt(Fe,async()=>{await Dt("fx")})}),$.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi?.sendChat)return;const t=String(U.value||"").trim();if(t){Jt("user",t),U.value="",M.disabled=!0;try{const e=await window.botApi.sendChat(t);if(!e.ok)return Jt("assistant",`Error: ${e.error||"unknown"}`),Rt("error",Yt(e.error)),void await Ht("ai.chat",Yt(e.error),e);Jt("assistant",String(e.answer||"")),_t()}catch(e){const t=Yt(e instanceof Error?e.message:String(e));Jt("assistant",`Error: ${t}`),Rt("error",t),await Ht("ai.chat.exception",t,e)}finally{M.disabled=!1}}}),window.addEventListener("error",e=>{const t=Yt(e.error?.message||e.message||"Unknown UI error");qt("[ui] uncaught error",{error:t}),Rt("error",t),Ht("ui.error",t,{message:e.message,filename:e.filename,lineno:e.lineno,colno:e.colno})}),window.addEventListener("unhandledrejection",e=>{const t=e.reason instanceof Error?e.reason.message:String(e.reason||"Unhandled promise rejection"),n=Yt(t);qt("[ui] unhandled rejection",{error:n}),Rt("error",n),Ht("ui.unhandledrejection",n,e.reason)}),window.botApi&&(window.botApi.onStatus(e=>{ut(String(e.status||""))}),window.botApi.onChatEvent(e=>{if(!e)return;const t=lt(e);if("line_user"===t.type){const e=t.lineUserId?`LINE(${String(t.lineUserId)})`:"LINE";return void Jt("line-user",String(t.text||""),e)}if("line_assistant"===t.type){const e=t.lineUserId?`AI->LINE(${String(t.lineUserId)})`:"AI->LINE";Jt("line-assistant",String(t.text||""),e)}}),window.botApi.onTradeExecution(e=>{if(!e)return;const t=lt(e),n="coin"===String(t.market||"").trim().toLowerCase()?"coin":"fx",i=String(t.symbol||"").trim().toUpperCase(),o=String(t.side||"").trim().toUpperCase()||"-",r=String(t.settleType||"").trim().toUpperCase()||"OPEN",a=String(t.executionSize??"").trim(),s=String(t.executionPrice??"").trim(),c="CLOSE"===r?"決済":"新規",l=`${i} ${o} ${a}${s?` @ ${s}`:""}`;qt(`[${n}] execution filled`,{symbol:i,side:o,settleType:r,size:a,price:s,raw:t}),Rt("success",`約定成功 (${c}) ${l}`.trim()),Pt(n).catch(()=>{}),Dt(n).catch(()=>{})}));const on=async()=>{if(Ke&&!We&&!Ge){Ge=!0;try{await async function(){vt("coin",qe),vt("fx",Je);try{await St("coin",qe)}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[coin] quote fetch failed",{error:t}),Rt("error",t)}try{await St("fx",Je)}catch(e){const t=Yt(e instanceof Error?e.message:String(e));qt("[fx] quote fetch failed",{error:t}),Rt("error",t)}if(await en(),await tn(),window.botApi?.getSymbolRules)try{const e=await window.botApi.getSymbolRules({market:"coin"});if(e?.ok&&Array.isArray(e.rules)){ot.clear();for(const t of e.rules){const e=String(t.symbol||"").trim().toUpperCase();e&&ot.set(e,{minOrderSize:String(t.minOrderSize||""),maxOrderSize:String(t.maxOrderSize||""),sizeStep:String(t.sizeStep||"")})}}}catch(e){qt("[coin] symbol rules fetch failed",{error:e instanceof Error?e.message:String(e)})}}(),await Pt(),await Dt(),He||(He=setInterval(()=>{Ut("coin")},1e3)),_e||(_e=setInterval(()=>{Ut("fx")},1e3)),We=!0}finally{Ge=!1}}};mt(!1),(async()=>{if(!window.botApi?.getSavedProfile)return;const e=await window.botApi.getSavedProfile();e?.ok&&e.profile&&(r.value=e.profile.botId||"",a.value=e.profile.email||"",s.value=e.profile.password||"",c.checked=Boolean(e.profile.remember))})(),(async()=>{if(!window.botApi?.getFxApiConfig||!window.botApi.getCoinApiConfig||!window.botApi.getAiConfig)return;const[e,t,n]=await Promise.all([window.botApi.getFxApiConfig(),window.botApi.getCoinApiConfig(),window.botApi.getAiConfig()]);e?.ok&&e.config&&(P.value=e.config.errorLogDir||"",T.value=String(e.config.riskDailyLossLimitJpy||5e4),z.value=e.config.fxApiKey||"",O.value=e.config.fxApiSecret||""),t?.ok&&t.config&&(P.value||(P.value=t.config.errorLogDir||""),T.value||(T.value=String(t.config.riskDailyLossLimitJpy||5e4)),F.value=t.config.cryptoApiKey||"",D.value=t.config.cryptoApiSecret||""),n?.ok&&n.config&&(Y.value=n.config.aiModel||"gpt-4.1-mini")})(),Xt();export{};
|